Skip to content

Commit 76f5af4

Browse files
committed
mock iptables in cns and add ut
1 parent 5cdcc1f commit 76f5af4

File tree

10 files changed

+297
-7
lines changed

10 files changed

+297
-7
lines changed

cns/client/client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func TestMain(m *testing.M) {
152152
config := common.ServiceConfig{}
153153

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

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
@@ -1735,7 +1735,7 @@ func startService(serviceConfig common.ServiceConfig, _ configuration.CNSConfig)
17351735
config.Store = fileStore
17361736

17371737
nmagentClient := &fakes.NMAgentClientFake{}
1738-
service, err = NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{},
1738+
service, err = NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{}, &IPtablesProvider{},
17391739
nmagentClient, nil, nil, nil, fakes.NewMockIMDSClient())
17401740
if err != nil {
17411741
return err

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(cns.KubernetesCRD)
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ const (
1616
pwshTimeout = 120 * time.Second
1717
)
1818

19+
var errUnsupportedAPI = errors.New("unsupported api")
20+
21+
type IPtablesProvider struct{}
22+
23+
func (_ *IPtablesProvider) GetIPTables() (iptablesClient, error) {
24+
return nil, errUnsupportedAPI
25+
}
26+
1927
// nolint
2028
func (service *HTTPRestService) programSNATRules(req *cns.CreateNetworkContainerRequest) (types.ResponseCode, string) {
2129
return types.Success, ""

cns/restserver/ipam_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ type ncState struct {
7676
func getTestService(orchestratorType string) *HTTPRestService {
7777
var config common.ServiceConfig
7878
httpsvc, _ := NewHTTPRestService(&config, &fakes.WireserverClientFake{}, &fakes.WireserverProxyFake{},
79-
&fakes.NMAgentClientFake{}, store.NewMockStore(""), nil, nil,
79+
&IPtablesProvider{}, &fakes.NMAgentClientFake{}, store.NewMockStore(""), nil, nil,
8080
fakes.NewMockIMDSClient())
8181
svc = httpsvc
8282
setOrchestratorTypeInternal(orchestratorType)

cns/restserver/restserver.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,24 @@ type imdsClient interface {
5454
GetVMUniqueID(ctx context.Context) (string, error)
5555
}
5656

57+
type iptablesClient interface {
58+
ChainExists(table string, chain string) (bool, error)
59+
NewChain(table string, chain string) error
60+
Append(table string, chain string, rulespec ...string) error
61+
Exists(table string, chain string, rulespec ...string) (bool, error)
62+
Insert(table string, chain string, pos int, rulespec ...string) error
63+
}
64+
65+
type iptablesGetter interface {
66+
GetIPTables() (iptablesClient, error)
67+
}
68+
5769
// HTTPRestService represents http listener for CNS - Container Networking Service.
5870
type HTTPRestService struct {
5971
*cns.Service
6072
dockerClient *dockerclient.Client
6173
wscli interfaceGetter
74+
iptables iptablesGetter
6275
nma nmagentClient
6376
wsproxy wireserverProxy
6477
homeAzMonitor *HomeAzMonitor
@@ -167,7 +180,7 @@ type networkInfo struct {
167180
}
168181

169182
// NewHTTPRestService creates a new HTTP Service object.
170-
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsproxy wireserverProxy, nmagentClient nmagentClient,
183+
func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsproxy wireserverProxy, iptg iptablesGetter, nmagentClient nmagentClient,
171184
endpointStateStore store.KeyValueStore, gen CNIConflistGenerator, homeAzMonitor *HomeAzMonitor,
172185
imdsClient imdsClient,
173186
) (*HTTPRestService, error) {
@@ -214,6 +227,7 @@ func NewHTTPRestService(config *common.ServiceConfig, wscli interfaceGetter, wsp
214227
store: service.Service.Store,
215228
dockerClient: dc,
216229
wscli: wscli,
230+
iptables: iptg,
217231
nma: nmagentClient,
218232
wsproxy: wsproxy,
219233
networkContainer: nc,

cns/restserver/v2/server_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ func startService(cnsPort, cnsURL string) error {
5050

5151
nmagentClient := &fakes.NMAgentClientFake{}
5252
service, err := restserver.NewHTTPRestService(&config, &fakes.WireserverClientFake{},
53-
&fakes.WireserverProxyFake{}, nmagentClient, nil, nil, nil,
53+
&fakes.WireserverProxyFake{}, &restserver.IPtablesProvider{}, nmagentClient, nil, nil, nil,
5454
fakes.NewMockIMDSClient())
55+
5556
if err != nil {
5657
return errors.Wrap(err, "Failed to initialize service")
5758
}

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
}
748748

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

0 commit comments

Comments
 (0)