Skip to content

Commit b09ca83

Browse files
authored
[CNI] Add GET_ENDPOINT_STATE command to dump CNI state to stdout (#891)
* inital dump state and ipam interface update * add reconcile command to CNI * add integration test * pass endpoint id on add * address some feedback * fix test path and linting * address feedback and logging * remove return and rename to PodEndpointID
1 parent cfa1f50 commit b09ca83

23 files changed

+605
-74
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,8 @@ PRETTYGOTEST := $(shell command -v gotest 2> /dev/null)
462462
# run all tests
463463
.PHONY: test-all
464464
test-all:
465-
go test -coverpkg=./... -v -race -covermode atomic -failfast -coverprofile=coverage.out ./...
465+
go test -tags "unit" -coverpkg=./... -v -race -covermode atomic -failfast -coverprofile=coverage.out ./...
466+
466467

467468
# run all tests
468469
.PHONY: test-integration

cni/api/api.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"net"
6+
"os"
7+
8+
"github.com/Azure/azure-container-networking/log"
9+
)
10+
11+
type PodNetworkInterfaceInfo struct {
12+
PodName string
13+
PodNamespace string
14+
PodEndpointId string
15+
ContainerID string
16+
IPAddresses []net.IPNet
17+
}
18+
19+
type CNIState interface {
20+
PrintResult() error
21+
}
22+
23+
type AzureCNIState struct {
24+
ContainerInterfaces map[string]PodNetworkInterfaceInfo
25+
}
26+
27+
func (a *AzureCNIState) PrintResult() error {
28+
b, err := json.MarshalIndent(a, "", " ")
29+
if err != nil {
30+
log.Errorf("Failed to unmarshall Azure CNI state, err:%v.\n", err)
31+
}
32+
33+
// write result to stdout to be captured by caller
34+
_, err = os.Stdout.Write(b)
35+
if err != nil {
36+
log.Printf("Failed to write response to stdout %v", err)
37+
return err
38+
}
39+
40+
return nil
41+
}

cni/client/client.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package client
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
8+
"github.com/Azure/azure-container-networking/cni"
9+
"github.com/Azure/azure-container-networking/cni/api"
10+
"github.com/Azure/azure-container-networking/log"
11+
utilexec "k8s.io/utils/exec"
12+
)
13+
14+
const (
15+
azureVnetBinName = "./azure-vnet"
16+
azureVnetBinDirectory = "/opt/cni/bin"
17+
)
18+
19+
type CNIClient interface {
20+
GetEndpointState() (api.CNIState, error)
21+
}
22+
23+
type AzureCNIClient struct {
24+
exec utilexec.Interface
25+
}
26+
27+
func NewCNIClient(exec utilexec.Interface) *AzureCNIClient {
28+
return &AzureCNIClient{
29+
exec: exec,
30+
}
31+
}
32+
33+
func (c *AzureCNIClient) GetEndpointState() (api.CNIState, error) {
34+
cmd := c.exec.Command(azureVnetBinName)
35+
cmd.SetDir(azureVnetBinDirectory)
36+
37+
envs := os.Environ()
38+
cmdenv := fmt.Sprintf("%s=%s", cni.Cmd, cni.CmdGetEndpointsState)
39+
log.Printf("Setting cmd to %s", cmdenv)
40+
envs = append(envs, cmdenv)
41+
cmd.SetEnv(envs)
42+
43+
output, err := cmd.CombinedOutput()
44+
if err != nil {
45+
return nil, fmt.Errorf("failed to call Azure CNI bin with err: [%w], output: [%s]", err, string(output))
46+
}
47+
48+
state := &api.AzureCNIState{}
49+
if err := json.Unmarshal(output, state); err != nil {
50+
return nil, fmt.Errorf("failed to decode response from Azure CNI when retrieving state: [%w], response from CNI: [%s]", err, string(output))
51+
}
52+
53+
return state, nil
54+
}

cni/client/client_common_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// +build unit integration
2+
3+
package client
4+
5+
import (
6+
"net"
7+
8+
"github.com/Azure/azure-container-networking/cni/api"
9+
)
10+
11+
func testGetPodNetworkInterfaceInfo(podendpointid, podname, podnamespace, containerid, ipwithcidr string) api.PodNetworkInterfaceInfo {
12+
ip, ipnet, _ := net.ParseCIDR(ipwithcidr)
13+
ipnet.IP = ip
14+
return api.PodNetworkInterfaceInfo{
15+
PodName: podname,
16+
PodNamespace: podnamespace,
17+
PodEndpointId: podendpointid,
18+
ContainerID: containerid,
19+
IPAddresses: []net.IPNet{
20+
*ipnet,
21+
},
22+
}
23+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// +build linux
2+
// +build integration
3+
4+
package client
5+
6+
import (
7+
"io"
8+
"os"
9+
"testing"
10+
11+
"github.com/Azure/azure-container-networking/cni/api"
12+
testutils "github.com/Azure/azure-container-networking/test/utils"
13+
"github.com/stretchr/testify/require"
14+
"k8s.io/utils/exec"
15+
)
16+
17+
// todo: enable this test in CI, requires built azure vnet
18+
func TestGetStateFromAzureCNI(t *testing.T) {
19+
testutils.RequireRootforTest(t)
20+
21+
// copy test state file to /var/run/azure-vnet.json
22+
in, err := os.Open("./testresources/azure-vnet-test.json")
23+
require.NoError(t, err)
24+
25+
defer in.Close()
26+
27+
out, err := os.Create("/var/run/azure-vnet.json")
28+
require.NoError(t, err)
29+
30+
defer func() {
31+
out.Close()
32+
err := os.Remove("/var/run/azure-vnet.json")
33+
require.NoError(t, err)
34+
}()
35+
36+
_, err = io.Copy(out, in)
37+
require.NoError(t, err)
38+
39+
out.Close()
40+
41+
realexec := exec.New()
42+
c := NewCNIClient(realexec)
43+
state, err := c.GetEndpointState()
44+
require.NoError(t, err)
45+
46+
res := &api.AzureCNIState{
47+
ContainerInterfaces: map[string]api.PodNetworkInterfaceInfo{
48+
"3f813b02-eth0": testGetPodNetworkInterfaceInfo("3f813b02-eth0", "metrics-server-77c8679d7d-6ksdh", "kube-system", "3f813b029429b4e41a09ab33b6f6d365d2ed704017524c78d1d0dece33cdaf46", "10.241.0.17/16"),
49+
"6e688597-eth0": testGetPodNetworkInterfaceInfo("6e688597-eth0", "tunnelfront-5d96f9b987-65xbn", "kube-system", "6e688597eafb97c83c84e402cc72b299bfb8aeb02021e4c99307a037352c0bed", "10.241.0.13/16"),
50+
},
51+
}
52+
53+
require.Exactly(t, res, state)
54+
}

cni/client/client_unit_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// +build unit
2+
3+
package client
4+
5+
import (
6+
"testing"
7+
8+
"github.com/Azure/azure-container-networking/cni/api"
9+
testutils "github.com/Azure/azure-container-networking/test/utils"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestGetState(t *testing.T) {
14+
calls := []testutils.TestCmd{
15+
{Cmd: []string{"./azure-vnet"}, Stdout: `{"ContainerInterfaces":{"3f813b02-eth0":{"PodName":"metrics-server-77c8679d7d-6ksdh","PodNamespace":"kube-system","PodEndpointID":"3f813b02-eth0","ContainerID":"3f813b029429b4e41a09ab33b6f6d365d2ed704017524c78d1d0dece33cdaf46","IPAddresses":[{"IP":"10.241.0.17","Mask":"//8AAA=="}]},"6e688597-eth0":{"PodName":"tunnelfront-5d96f9b987-65xbn","PodNamespace":"kube-system","PodEndpointID":"6e688597-eth0","ContainerID":"6e688597eafb97c83c84e402cc72b299bfb8aeb02021e4c99307a037352c0bed","IPAddresses":[{"IP":"10.241.0.13","Mask":"//8AAA=="}]}}}`},
16+
}
17+
18+
fakeexec, _ := testutils.GetFakeExecWithScripts(calls)
19+
20+
c := NewCNIClient(fakeexec)
21+
state, err := c.GetEndpointState()
22+
require.NoError(t, err)
23+
24+
res := &api.AzureCNIState{
25+
ContainerInterfaces: map[string]api.PodNetworkInterfaceInfo{
26+
"3f813b02-eth0": testGetPodNetworkInterfaceInfo("3f813b02-eth0", "metrics-server-77c8679d7d-6ksdh", "kube-system", "3f813b029429b4e41a09ab33b6f6d365d2ed704017524c78d1d0dece33cdaf46", "10.241.0.17/16"),
27+
"6e688597-eth0": testGetPodNetworkInterfaceInfo("6e688597-eth0", "tunnelfront-5d96f9b987-65xbn", "kube-system", "6e688597eafb97c83c84e402cc72b299bfb8aeb02021e4c99307a037352c0bed", "10.241.0.13/16"),
28+
},
29+
}
30+
31+
require.Equal(t, res, state)
32+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
"Network": {
3+
"Version": "v1.2.6",
4+
"TimeStamp": "2021-06-04T17:15:58.638215441Z",
5+
"ExternalInterfaces": {
6+
"eth0": {
7+
"Name": "eth0",
8+
"Networks": {
9+
"azure": {
10+
"Id": "azure",
11+
"Mode": "transparent",
12+
"VlanId": 0,
13+
"Subnets": [
14+
{
15+
"Family": 2,
16+
"Prefix": {
17+
"IP": "10.240.0.0",
18+
"Mask": "//8AAA=="
19+
},
20+
"Gateway": "10.241.0.1",
21+
"PrimaryIP": ""
22+
}
23+
],
24+
"Endpoints": {
25+
"3f813b02-eth0": {
26+
"Id": "3f813b02-eth0",
27+
"SandboxKey": "",
28+
"IfName": "azvd805fb1b0f82",
29+
"HostIfName": "azvd805fb1b0f8",
30+
"MacAddress": "ipNAs8jK",
31+
"InfraVnetIP": {
32+
"IP": "",
33+
"Mask": null
34+
},
35+
"LocalIP": "",
36+
"IPAddresses": [
37+
{
38+
"IP": "10.241.0.17",
39+
"Mask": "//8AAA=="
40+
}
41+
],
42+
"Gateways": [
43+
"0.0.0.0"
44+
],
45+
"DNS": {
46+
"Suffix": "",
47+
"Servers": null,
48+
"Options": null
49+
},
50+
"Routes": [
51+
{
52+
"Dst": {
53+
"IP": "0.0.0.0",
54+
"Mask": "AAAAAA=="
55+
},
56+
"Src": "",
57+
"Gw": "10.241.0.1",
58+
"Protocol": 0,
59+
"DevName": "",
60+
"Scope": 0,
61+
"Priority": 0
62+
}
63+
],
64+
"VlanID": 0,
65+
"EnableSnatOnHost": false,
66+
"EnableInfraVnet": false,
67+
"EnableMultitenancy": false,
68+
"AllowInboundFromHostToNC": false,
69+
"AllowInboundFromNCToHost": false,
70+
"NetworkContainerID": "",
71+
"NetworkNameSpace": "/var/run/netns/cni-e66c52d6-44be-555b-fb65-0b36296b6861",
72+
"ContainerID": "3f813b029429b4e41a09ab33b6f6d365d2ed704017524c78d1d0dece33cdaf46",
73+
"PODName": "metrics-server-77c8679d7d-6ksdh",
74+
"PODNameSpace": "kube-system"
75+
},
76+
"6e688597-eth0": {
77+
"Id": "6e688597-eth0",
78+
"SandboxKey": "",
79+
"IfName": "azvc214c1237ce2",
80+
"HostIfName": "azvc214c1237ce",
81+
"MacAddress": "ZjL4HSJ+",
82+
"InfraVnetIP": {
83+
"IP": "",
84+
"Mask": null
85+
},
86+
"LocalIP": "",
87+
"IPAddresses": [
88+
{
89+
"IP": "10.241.0.13",
90+
"Mask": "//8AAA=="
91+
}
92+
],
93+
"Gateways": [
94+
"0.0.0.0"
95+
],
96+
"DNS": {
97+
"Suffix": "",
98+
"Servers": null,
99+
"Options": null
100+
},
101+
"Routes": [
102+
{
103+
"Dst": {
104+
"IP": "0.0.0.0",
105+
"Mask": "AAAAAA=="
106+
},
107+
"Src": "",
108+
"Gw": "10.241.0.1",
109+
"Protocol": 0,
110+
"DevName": "",
111+
"Scope": 0,
112+
"Priority": 0
113+
}
114+
],
115+
"VlanID": 0,
116+
"EnableSnatOnHost": false,
117+
"EnableInfraVnet": false,
118+
"EnableMultitenancy": false,
119+
"AllowInboundFromHostToNC": false,
120+
"AllowInboundFromNCToHost": false,
121+
"NetworkContainerID": "",
122+
"NetworkNameSpace": "/var/run/netns/cni-56872dcd-b3ab-fc90-ccd8-6a6dd11d56cf",
123+
"ContainerID": "6e688597eafb97c83c84e402cc72b299bfb8aeb02021e4c99307a037352c0bed",
124+
"PODName": "tunnelfront-5d96f9b987-65xbn",
125+
"PODNameSpace": "kube-system"
126+
}
127+
},
128+
"DNS": {
129+
"Suffix": "",
130+
"Servers": null,
131+
"Options": null
132+
},
133+
"EnableSnatOnHost": false,
134+
"NetNs": "",
135+
"SnatBridgeIP": ""
136+
}
137+
},
138+
"Subnets": [
139+
"10.240.0.0/16"
140+
],
141+
"BridgeName": "",
142+
"DNSInfo": {
143+
"Suffix": "",
144+
"Servers": null,
145+
"Options": null
146+
},
147+
"MacAddress": "AA06xXdb",
148+
"IPAddresses": null,
149+
"Routes": null,
150+
"IPv4Gateway": "0.0.0.0",
151+
"IPv6Gateway": "::"
152+
}
153+
}
154+
}
155+
}

cni/cni.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ const (
1515
CmdDel = "DEL"
1616
CmdUpdate = "UPDATE"
1717

18+
// nonstandard CNI spec command, used to dump CNI state to stdout
19+
CmdGetEndpointsState = "GET_ENDPOINT_STATE"
20+
1821
// CNI errors.
1922
ErrRuntime = 100
2023

0 commit comments

Comments
 (0)