Skip to content

Commit e0ea5b0

Browse files
committed
test: add unit tests
1 parent d19c64f commit e0ea5b0

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

cns/healthserver/healthz_test.go

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
package healthserver
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"testing"
9+
10+
"github.com/Azure/azure-container-networking/cns/configuration"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
const nncCRD = `{
15+
"kind": "APIResourceList",
16+
"apiVersion": "v1",
17+
"groupVersion": "acn.azure.com/v1alpha",
18+
"resources": [
19+
{
20+
"name": "nodenetworkconfigs",
21+
"singularName": "nodenetworkconfig",
22+
"namespaced": true,
23+
"kind": "NodeNetworkConfig",
24+
"verbs": [
25+
"delete",
26+
"deletecollection",
27+
"get",
28+
"list",
29+
"patch",
30+
"create",
31+
"update",
32+
"watch"
33+
],
34+
"shortNames": [
35+
"nnc"
36+
],
37+
"storageVersionHash": "aGVsbG93cmxk"
38+
},
39+
{
40+
"name": "nodenetworkconfigs/status",
41+
"singularName": "",
42+
"namespaced": true,
43+
"kind": "NodeNetworkConfig",
44+
"verbs": [
45+
"get",
46+
"patch",
47+
"update"
48+
]
49+
}
50+
]
51+
}`
52+
53+
const nncResult = `{
54+
"apiVersion": "acn.azure.com/v1alpha",
55+
"items": [
56+
{
57+
"apiVersion": "acn.azure.com/v1alpha",
58+
"kind": "NodeNetworkConfig",
59+
"metadata": {
60+
"creationTimestamp": "2024-12-04T20:42:17Z",
61+
"finalizers": [
62+
"finalizers.acn.azure.com/dnc-operations"
63+
],
64+
"generation": 1,
65+
"labels": {
66+
"kubernetes.azure.com/podnetwork-delegationguid": "",
67+
"kubernetes.azure.com/podnetwork-subnet": "",
68+
"kubernetes.azure.com/podnetwork-type": "overlay",
69+
"managed": "true",
70+
"owner": "aks-nodepool1-1234567-vmss000000"
71+
},
72+
"managedFields": [
73+
{
74+
"apiVersion": "acn.azure.com/v1alpha",
75+
"fieldsType": "FieldsV1",
76+
"fieldsV1": {
77+
"f:metadata": {
78+
"f:finalizers": {
79+
".": {},
80+
"v:\"finalizers.acn.azure.com/dnc-operations\"": {}
81+
},
82+
"f:labels": {
83+
".": {},
84+
"f:kubernetes.azure.com/podnetwork-delegationguid": {},
85+
"f:kubernetes.azure.com/podnetwork-subnet": {},
86+
"f:kubernetes.azure.com/podnetwork-type": {},
87+
"f:managed": {},
88+
"f:owner": {}
89+
},
90+
"f:ownerReferences": {
91+
".": {},
92+
"k:{\"uid\":\"f5117020-bbc5-11ef-8433-1b9e59caeb1d\"}": {}
93+
}
94+
},
95+
"f:spec": {
96+
".": {},
97+
"f:requestedIPCount": {}
98+
}
99+
},
100+
"manager": "dnc-rc",
101+
"operation": "Update",
102+
"time": "2024-12-04T20:42:17Z"
103+
},
104+
{
105+
"apiVersion": "acn.azure.com/v1alpha",
106+
"fieldsType": "FieldsV1",
107+
"fieldsV1": {
108+
"f:status": {
109+
".": {},
110+
"f:assignedIPCount": {},
111+
"f:networkContainers": {}
112+
}
113+
},
114+
"manager": "dnc-rc",
115+
"operation": "Update",
116+
"subresource": "status",
117+
"time": "2024-12-04T20:42:18Z"
118+
}
119+
],
120+
"name": "aks-nodepool1-1234567-vmss000000",
121+
"namespace": "kube-system",
122+
"ownerReferences": [
123+
{
124+
"apiVersion": "v1",
125+
"blockOwnerDeletion": true,
126+
"controller": true,
127+
"kind": "Node",
128+
"name": "aks-nodepool1-29763700-vmss000000",
129+
"uid": "02df1fcc-bbc6-11ef-a76a-4b1af8d399a2"
130+
}
131+
],
132+
"resourceVersion": "123456789",
133+
"uid": "0dc75e5e-bbc6-11ef-878f-ab45432262d6"
134+
},
135+
"spec": {
136+
"requestedIPCount": 0
137+
},
138+
"status": {
139+
"assignedIPCount": 256,
140+
"networkContainers": [
141+
{
142+
"assignmentMode": "static",
143+
"id": "13f630c0-bbc6-11ef-b3b7-bb8e46de5973",
144+
"nodeIP": "10.224.0.4",
145+
"primaryIP": "10.244.2.0/24",
146+
"subnetAddressSpace": "10.244.0.0/16",
147+
"subnetName": "routingdomain_1f7eb6ba-bbc6-11ef-8c54-7b2c1e3cbbe4_overlaysubnet",
148+
"type": "overlay",
149+
"version": 0
150+
}
151+
]
152+
}
153+
}
154+
],
155+
"kind": "NodeNetworkConfigList",
156+
"metadata": {
157+
"continue": "",
158+
"resourceVersion": "9876543210"
159+
}
160+
}`
161+
162+
func TestNewHealthzHandlerWithChecks(t *testing.T) {
163+
tests := []struct {
164+
name string
165+
cnsConfig *configuration.CNSConfig
166+
apiStatusCode int
167+
expectedHealthy bool
168+
}{
169+
{
170+
name: "list NNC gives 200 should indicate healthy",
171+
cnsConfig: &configuration.CNSConfig{
172+
ChannelMode: "CRD",
173+
},
174+
apiStatusCode: http.StatusOK,
175+
expectedHealthy: true,
176+
},
177+
{
178+
name: "unauthorized (401) from apiserver should be unhealthy",
179+
cnsConfig: &configuration.CNSConfig{
180+
ChannelMode: "CRD",
181+
},
182+
apiStatusCode: http.StatusUnauthorized,
183+
expectedHealthy: false,
184+
},
185+
{
186+
name: "channel nodesubnet should not call apiserver so it doesn't matter if the status code is a 401",
187+
cnsConfig: &configuration.CNSConfig{
188+
ChannelMode: "AzureHost",
189+
},
190+
apiStatusCode: http.StatusUnauthorized,
191+
expectedHealthy: true,
192+
},
193+
}
194+
195+
for _, tt := range tests {
196+
t.Run(tt.name, func(t *testing.T) {
197+
configureLocalAPIServer(t, tt.apiStatusCode)
198+
199+
responseRecorder := httptest.NewRecorder()
200+
healthHandler := NewHealthzHandlerWithChecks(tt.cnsConfig)
201+
healthHandler.ServeHTTP(responseRecorder, httptest.NewRequest("GET", "/healthz", nil))
202+
203+
require.Equal(t, tt.expectedHealthy, responseRecorder.Code == http.StatusOK)
204+
})
205+
}
206+
}
207+
208+
func configureLocalAPIServer(t *testing.T, expectedNNCStatusCode int) {
209+
// setup apiserver
210+
server := setupMockAPIServer(expectedNNCStatusCode)
211+
212+
// write kubeConfig for test server
213+
kubeConfigFile, err := writeTmpKubeConfig(server.URL)
214+
require.NoError(t, err)
215+
216+
// set env var to kubeconfig
217+
os.Setenv("KUBECONFIG", kubeConfigFile)
218+
219+
t.Cleanup(func() {
220+
server.Close()
221+
os.Remove(kubeConfigFile)
222+
os.Unsetenv("KUBECONFIG")
223+
})
224+
}
225+
226+
func writeTmpKubeConfig(host string) (string, error) {
227+
tempKubeConfig := `
228+
apiVersion: v1
229+
clusters:
230+
- cluster:
231+
server: ` + host + `
232+
name: test-cluster
233+
contexts:
234+
- context:
235+
cluster: test-cluster
236+
user: test-user
237+
name: test-context
238+
current-context: test-context
239+
kind: Config
240+
preferences: {}
241+
users:
242+
- name: test-user
243+
user:
244+
token: test-token
245+
`
246+
kubeConfigFile, err := os.CreateTemp("", "kubeconfig")
247+
if err != nil {
248+
return "", fmt.Errorf("failed to create temp kubeconfig file: %w", err)
249+
}
250+
251+
_, err = kubeConfigFile.Write([]byte(tempKubeConfig))
252+
if err != nil {
253+
return "", fmt.Errorf("failed to write kubeconfig to temp file: %w", err)
254+
}
255+
kubeConfigFile.Close()
256+
return kubeConfigFile.Name(), nil
257+
}
258+
259+
func setupMockAPIServer(code int) *httptest.Server {
260+
// Start a mock HTTP server
261+
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
262+
// Handle requests based on the path
263+
switch r.URL.Path {
264+
case "/apis/acn.azure.com/v1alpha":
265+
w.Write([]byte(nncCRD))
266+
case "/apis/acn.azure.com/v1alpha/namespaces/kube-system/nodenetworkconfigs":
267+
if code == http.StatusOK {
268+
w.Header().Set("Cache-Control", "no-cache, private")
269+
w.Header().Set("Content-Type", "application/json")
270+
w.Write([]byte(nncResult))
271+
} else {
272+
w.WriteHeader(code)
273+
}
274+
default:
275+
w.WriteHeader(http.StatusNotFound)
276+
}
277+
}))
278+
279+
return mockServer
280+
}

0 commit comments

Comments
 (0)