Skip to content

Commit 158dda6

Browse files
committed
mock iptables in cns and add ut
1 parent c026871 commit 158dda6

File tree

10 files changed

+298
-7
lines changed

10 files changed

+298
-7
lines changed

cns/client/client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func TestMain(m *testing.M) {
151151
logger.InitLogger(logName, 0, 0, tmpLogDir+"/")
152152
config := common.ServiceConfig{}
153153

154-
httpRestService, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &fakes.NMAgentClientFake{}, nil, nil, nil)
154+
httpRestService, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &restserver.IPtablesProvider{}, &fakes.NMAgentClientFake{}, nil, nil, nil)
155155
svc = httpRestService
156156
httpRestService.Name = "cns-test-server"
157157

cns/fakes/iptablesfake.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package fakes
2+
3+
import (
4+
"errors"
5+
"strings"
6+
7+
"github.com/Azure/azure-container-networking/iptables"
8+
)
9+
10+
var (
11+
errChainExists = errors.New("chain already exists")
12+
errChainNotFound = errors.New("chain not found")
13+
errRuleExists = errors.New("rule already exists")
14+
)
15+
16+
type IPTablesMock struct {
17+
state map[string]map[string][]string
18+
}
19+
20+
func NewIPTablesMock() *IPTablesMock {
21+
return &IPTablesMock{
22+
state: make(map[string]map[string][]string),
23+
}
24+
}
25+
26+
type iptablesClient interface {
27+
ChainExists(table string, chain string) (bool, error)
28+
NewChain(table string, chain string) error
29+
Exists(table string, chain string, rulespec ...string) (bool, error)
30+
Append(table string, chain string, rulespec ...string) error
31+
Insert(table string, chain string, pos int, rulespec ...string) error
32+
}
33+
34+
func (c *IPTablesMock) ensureTableExists(table string) {
35+
_, exists := c.state[table]
36+
if !exists {
37+
c.state[table] = make(map[string][]string)
38+
}
39+
}
40+
41+
func (c *IPTablesMock) ChainExists(table, chain string) (bool, error) {
42+
c.ensureTableExists(table)
43+
44+
builtins := []string{iptables.Input, iptables.Output, iptables.Prerouting, iptables.Postrouting, iptables.Forward}
45+
46+
_, exists := c.state[table][chain]
47+
48+
// these chains always exist
49+
for _, val := range builtins {
50+
if chain == val && !exists {
51+
c.state[table][chain] = []string{}
52+
return true, nil
53+
}
54+
}
55+
56+
return exists, nil
57+
}
58+
59+
func (c *IPTablesMock) NewChain(table, chain string) error {
60+
c.ensureTableExists(table)
61+
62+
exists, _ := c.ChainExists(table, chain)
63+
64+
if exists {
65+
return errChainExists
66+
}
67+
68+
c.state[table][chain] = []string{}
69+
return nil
70+
}
71+
72+
func (c *IPTablesMock) Exists(table string, chain string, rulespec ...string) (bool, error) {
73+
c.ensureTableExists(table)
74+
75+
chainExists, _ := c.ChainExists(table, chain)
76+
if !chainExists {
77+
return false, nil
78+
}
79+
80+
targetRule := strings.Join(rulespec[:], " ")
81+
chainRules := c.state[table][chain]
82+
83+
for _, chainRule := range chainRules {
84+
if targetRule == chainRule {
85+
return true, nil
86+
}
87+
}
88+
return false, nil
89+
}
90+
91+
func (c *IPTablesMock) Append(table string, chain string, rulespec ...string) error {
92+
c.ensureTableExists(table)
93+
94+
chainExists, _ := c.ChainExists(table, chain)
95+
if !chainExists {
96+
return errChainNotFound
97+
}
98+
99+
ruleExists, _ := c.Exists(table, chain, rulespec...)
100+
if ruleExists {
101+
return errRuleExists
102+
}
103+
104+
targetRule := strings.Join(rulespec[:], " ")
105+
c.state[table][chain] = append(c.state[table][chain], targetRule)
106+
return nil
107+
}
108+
109+
func (c *IPTablesMock) Insert(table string, chain string, _ int, rulespec ...string) error {
110+
return c.Append(table, chain, rulespec...)
111+
}

cns/restserver/api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1679,7 +1679,7 @@ func startService() error {
16791679
config.Store = fileStore
16801680

16811681
nmagentClient := &fakes.NMAgentClientFake{}
1682-
service, err = NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, nmagentClient, nil, nil, nil)
1682+
service, err = NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &IPtablesProvider{}, nmagentClient, nil, nil, nil)
16831683
if err != nil {
16841684
return err
16851685
}

cns/restserver/internalapi_linux.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ import (
1515

1616
const SWIFT = "SWIFT-POSTROUTING"
1717

18+
type IPtablesProvider struct {
19+
iptc iptablesClient
20+
}
21+
22+
func (c *IPtablesProvider) GetIPTables() (iptablesClient, error) {
23+
return goiptables.New()
24+
}
25+
1826
// nolint
1927
func (service *HTTPRestService) programSNATRules(req *cns.CreateNetworkContainerRequest) (types.ResponseCode, string) {
2028
service.Lock()
@@ -24,7 +32,7 @@ func (service *HTTPRestService) programSNATRules(req *cns.CreateNetworkContainer
2432
// in podsubnet case, ncPrimaryIP is the pod subnet's primary ip
2533
// in vnet scale case, ncPrimaryIP is the node's ip
2634
ncPrimaryIP, _, _ := net.ParseCIDR(req.IPConfiguration.IPSubnet.IPAddress + "/" + fmt.Sprintf("%d", req.IPConfiguration.IPSubnet.PrefixLength))
27-
ipt, err := goiptables.New()
35+
ipt, err := service.iptables.GetIPTables()
2836
if err != nil {
2937
return types.UnexpectedError, fmt.Sprintf("[Azure CNS] Error. Failed to create iptables interface : %v", err)
3038
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright 2020 Microsoft. All rights reserved.
2+
// MIT License
3+
4+
package restserver
5+
6+
import (
7+
"strconv"
8+
"testing"
9+
10+
"github.com/Azure/azure-container-networking/cns"
11+
"github.com/Azure/azure-container-networking/cns/fakes"
12+
"github.com/Azure/azure-container-networking/cns/types"
13+
"github.com/Azure/azure-container-networking/iptables"
14+
"github.com/Azure/azure-container-networking/network/networkutils"
15+
)
16+
17+
type FakeIPTablesProvider struct {
18+
iptables *fakes.IPTablesMock
19+
}
20+
21+
func (c *FakeIPTablesProvider) GetIPTables() (iptablesClient, error) {
22+
// persist iptables in testing
23+
if c.iptables == nil {
24+
c.iptables = fakes.NewIPTablesMock()
25+
}
26+
return c.iptables, nil
27+
}
28+
29+
func TestAddSNATRules(t *testing.T) {
30+
type expectedScenario struct {
31+
table string
32+
chain string
33+
rule []string
34+
}
35+
36+
tests := []struct {
37+
name string
38+
input *cns.CreateNetworkContainerRequest
39+
expected []expectedScenario
40+
}{
41+
{
42+
// in pod subnet, the primary nic ip is in the same address space as the pod subnet
43+
name: "podsubnet",
44+
input: &cns.CreateNetworkContainerRequest{
45+
NetworkContainerid: ncID,
46+
IPConfiguration: cns.IPConfiguration{
47+
IPSubnet: cns.IPSubnet{
48+
IPAddress: "240.1.2.1",
49+
PrefixLength: 24,
50+
},
51+
},
52+
SecondaryIPConfigs: map[string]cns.SecondaryIPConfig{
53+
"abc": {
54+
IPAddress: "240.1.2.7",
55+
},
56+
},
57+
HostPrimaryIP: "10.0.0.4",
58+
},
59+
expected: []expectedScenario{
60+
{
61+
table: iptables.Nat,
62+
chain: SWIFT,
63+
rule: []string{
64+
"-m", "addrtype", "!", "--dst-type", "local", "-s", "240.1.2.0/24", "-d",
65+
networkutils.AzureDNS, "-p", iptables.UDP, "--dport", strconv.Itoa(iptables.DNSPort), "-j", iptables.Snat, "--to", "240.1.2.1",
66+
},
67+
},
68+
{
69+
table: iptables.Nat,
70+
chain: SWIFT,
71+
rule: []string{
72+
"-m", "addrtype", "!", "--dst-type", "local", "-s", "240.1.2.0/24", "-d",
73+
networkutils.AzureDNS, "-p", iptables.TCP, "--dport", strconv.Itoa(iptables.DNSPort), "-j", iptables.Snat, "--to", "240.1.2.1",
74+
},
75+
},
76+
{
77+
table: iptables.Nat,
78+
chain: SWIFT,
79+
rule: []string{
80+
"-m", "addrtype", "!", "--dst-type", "local", "-s", "240.1.2.0/24", "-d",
81+
networkutils.AzureIMDS, "-p", iptables.TCP, "--dport", strconv.Itoa(iptables.HTTPPort), "-j", iptables.Snat, "--to", "10.0.0.4",
82+
},
83+
},
84+
},
85+
},
86+
{
87+
// in vnet scale, the primary nic ip becomes the node ip (diff address space from pod subnet)
88+
name: "vnet scale",
89+
input: &cns.CreateNetworkContainerRequest{
90+
NetworkContainerid: ncID,
91+
IPConfiguration: cns.IPConfiguration{
92+
IPSubnet: cns.IPSubnet{
93+
IPAddress: "10.0.0.4",
94+
PrefixLength: 28,
95+
},
96+
},
97+
SecondaryIPConfigs: map[string]cns.SecondaryIPConfig{
98+
"abc": {
99+
IPAddress: "240.1.2.15",
100+
},
101+
},
102+
HostPrimaryIP: "10.0.0.4",
103+
},
104+
expected: []expectedScenario{
105+
{
106+
table: iptables.Nat,
107+
chain: SWIFT,
108+
rule: []string{
109+
"-m", "addrtype", "!", "--dst-type", "local", "-s", "240.1.2.0/28", "-d",
110+
networkutils.AzureDNS, "-p", iptables.UDP, "--dport", strconv.Itoa(iptables.DNSPort), "-j", iptables.Snat, "--to", "10.0.0.4",
111+
},
112+
},
113+
{
114+
table: iptables.Nat,
115+
chain: SWIFT,
116+
rule: []string{
117+
"-m", "addrtype", "!", "--dst-type", "local", "-s", "240.1.2.0/28", "-d",
118+
networkutils.AzureDNS, "-p", iptables.TCP, "--dport", strconv.Itoa(iptables.DNSPort), "-j", iptables.Snat, "--to", "10.0.0.4",
119+
},
120+
},
121+
{
122+
table: iptables.Nat,
123+
chain: SWIFT,
124+
rule: []string{
125+
"-m", "addrtype", "!", "--dst-type", "local", "-s", "240.1.2.0/28", "-d",
126+
networkutils.AzureIMDS, "-p", iptables.TCP, "--dport", strconv.Itoa(iptables.HTTPPort), "-j", iptables.Snat, "--to", "10.0.0.4",
127+
},
128+
},
129+
},
130+
},
131+
}
132+
133+
for _, tt := range tests {
134+
service := getTestService()
135+
service.iptables = &FakeIPTablesProvider{}
136+
resp, msg := service.programSNATRules(tt.input)
137+
if resp != types.Success {
138+
t.Fatal("failed to program snat rules", msg, " case: ", tt.name)
139+
}
140+
finalState, _ := service.iptables.GetIPTables()
141+
for _, ex := range tt.expected {
142+
exists, err := finalState.Exists(ex.table, ex.chain, ex.rule...)
143+
if err != nil || !exists {
144+
t.Fatal("rule not found", ex.rule, " case: ", tt.name)
145+
}
146+
}
147+
}
148+
}

cns/restserver/internalapi_windows.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
package restserver
22

33
import (
4+
"errors"
5+
46
"github.com/Azure/azure-container-networking/cns"
57
"github.com/Azure/azure-container-networking/cns/types"
68
)
79

10+
var errUnsupportedAPI = errors.New("unsupported api")
11+
12+
type IPtablesProvider struct{}
13+
14+
func (_ *IPtablesProvider) GetIPTables() (iptablesClient, error) {
15+
return nil, errUnsupportedAPI
16+
}
17+
818
// nolint
919
func (service *HTTPRestService) programSNATRules(req *cns.CreateNetworkContainerRequest) (types.ResponseCode, string) {
1020
return types.Success, ""

cns/restserver/ipam_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ type ncState struct {
6363

6464
func getTestService() *HTTPRestService {
6565
var config common.ServiceConfig
66-
httpsvc, _ := NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &fakes.NMAgentClientFake{}, store.NewMockStore(""), nil, nil)
66+
httpsvc, _ := NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &IPtablesProvider{}, &fakes.NMAgentClientFake{}, store.NewMockStore(""), nil, nil)
6767
svc = httpsvc
6868
setOrchestratorTypeInternal(cns.KubernetesCRD)
6969

cns/restserver/restserver.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,25 @@ type wireserverProxy interface {
4949
UnpublishNC(ctx context.Context, ncParams cns.NetworkContainerParameters, payload []byte) (*http.Response, error)
5050
}
5151

52+
type iptablesClient interface {
53+
ChainExists(table string, chain string) (bool, error)
54+
NewChain(table string, chain string) error
55+
Append(table string, chain string, rulespec ...string) error
56+
Exists(table string, chain string, rulespec ...string) (bool, error)
57+
Insert(table string, chain string, pos int, rulespec ...string) error
58+
}
59+
60+
type iptablesGetter interface {
61+
GetIPTables() (iptablesClient, error)
62+
}
63+
5264
// HTTPRestService represents http listener for CNS - Container Networking Service.
5365
type HTTPRestService struct {
5466
*cns.Service
5567
dockerClient *dockerclient.Client
5668
wscli interfaceGetter
5769
ipamClient *ipamclient.IpamClient
70+
iptables iptablesGetter
5871
nma nmagentClient
5972
wsproxy wireserverProxy
6073
homeAzMonitor *HomeAzMonitor
@@ -159,7 +172,7 @@ type networkInfo struct {
159172
}
160173

161174
// NewHTTPRestService creates a new HTTP Service object.
162-
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsproxy wireserverProxy, nmagentClient nmagentClient,
175+
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsproxy wireserverProxy, iptg iptablesGetter, nmagentClient nmagentClient,
163176
endpointStateStore store.KeyValueStore, gen CNIConflistGenerator, homeAzMonitor *HomeAzMonitor,
164177
) (*HTTPRestService, error) {
165178
service, err := cns.NewService(config.Name, config.Version, config.ChannelMode, config.Store)
@@ -210,6 +223,7 @@ func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsp
210223
dockerClient: dc,
211224
wscli: wscli,
212225
ipamClient: ic,
226+
iptables: iptg,
213227
nma: nmagentClient,
214228
wsproxy: wsproxy,
215229
networkContainer: nc,

cns/restserver/v2/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func startService(cnsPort, cnsURL string) error {
4949
config := common.ServiceConfig{}
5050

5151
nmagentClient := &fakes.NMAgentClientFake{}
52-
service, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, nmagentClient, nil, nil, nil)
52+
service, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &restserver.IPtablesProvider{}, nmagentClient, nil, nil, nil)
5353
if err != nil {
5454
return errors.Wrap(err, "Failed to initialize service")
5555
}

cns/service/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ func main() {
747747
Logger: logger.Log,
748748
}
749749

750-
httpRemoteRestService, err := restserver.NewHTTPRestService(&config, wsclient, &wsProxy, nmaClient,
750+
httpRemoteRestService, err := restserver.NewHTTPRestService(&config, wsclient, &wsProxy, &restserver.IPtablesProvider{}, nmaClient,
751751
endpointStateStore, conflistGenerator, homeAzMonitor)
752752
if err != nil {
753753
logger.Errorf("Failed to create CNS object, err:%v.\n", err)

0 commit comments

Comments
 (0)