1
16
17 package config
18
19 import (
20 "context"
21 "fmt"
22 "os"
23 "path/filepath"
24 "reflect"
25 "strconv"
26 "strings"
27 "testing"
28 "time"
29
30 "github.com/pkg/errors"
31
32 v1 "k8s.io/api/core/v1"
33 apierrors "k8s.io/apimachinery/pkg/api/errors"
34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35 "k8s.io/apimachinery/pkg/runtime"
36 clientsetfake "k8s.io/client-go/kubernetes/fake"
37 clienttesting "k8s.io/client-go/testing"
38
39 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
40 kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
41 "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
42 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
43 testresources "k8s.io/kubernetes/cmd/kubeadm/test/resources"
44 )
45
46 var k8sVersionString = kubeadmconstants.MinimumControlPlaneVersion.String()
47 var nodeName = "mynode"
48 var cfgFiles = map[string][]byte{
49 "InitConfiguration_v1beta3": []byte(fmt.Sprintf(`
50 apiVersion: %s
51 kind: InitConfiguration
52 `, kubeadmapiv1.SchemeGroupVersion.String())),
53 "ClusterConfiguration_v1beta3": []byte(fmt.Sprintf(`
54 apiVersion: %s
55 kind: ClusterConfiguration
56 kubernetesVersion: %s
57 `, kubeadmapiv1.SchemeGroupVersion.String(), k8sVersionString)),
58 "Kube-proxy_componentconfig": []byte(`
59 apiVersion: kubeproxy.config.k8s.io/v1alpha1
60 kind: KubeProxyConfiguration
61 `),
62 "Kubelet_componentconfig": []byte(`
63 apiVersion: kubelet.config.k8s.io/v1beta1
64 kind: KubeletConfiguration
65 `),
66 }
67
68 var kubeletConfFiles = map[string][]byte{
69 "withoutX509Cert": []byte(`
70 apiVersion: v1
71 clusters:
72 - cluster:
73 server: https://10.0.2.15:6443
74 name: kubernetes
75 contexts:
76 - context:
77 cluster: kubernetes
78 user: system:node:mynode
79 name: system:node:mynode@kubernetes
80 current-context: system:node:mynode@kubernetes
81 kind: Config
82 preferences: {}
83 users:
84 - name: system:node:mynode
85 user:
86 `),
87 "configWithEmbeddedCert": []byte(`
88 apiVersion: v1
89 clusters:
90 - cluster:
91 server: https://10.0.2.15:6443
92 name: kubernetes
93 contexts:
94 - context:
95 cluster: kubernetes
96 user: system:node:mynode
97 name: system:node:mynode@kubernetes
98 current-context: system:node:mynode@kubernetes
99 kind: Config
100 preferences: {}
101 users:
102 - name: system:node:mynode
103 user:
104 client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQWl3VURhYk5vZ1F3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ERXhOVE14TWpaYUZ3MHhPVEE1TURFeE5qQXhOVGxhTURReApGVEFUQmdOVkJBb1RESE41YzNSbGJUcHViMlJsY3pFYk1Ca0dBMVVFQXhNU2MzbHpkR1Z0T201dlpHVTZiWGx1CmIyUmxNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQWs2UXUzeStyNEZYUzZ4VkoKWU1vNE9kSkt3R1d1MHY4TEJIUnhhOUhvVHo1RXZLQnB1OVJoemt5dStUaFczb0xta2ZTRmNJcitHa0M5MW0zOApFelRmVE5JY2dsL0V5YkpmR1QvdGdUazZYd1kxY1UrUUdmSEFNNTBCVzFXTFVHc25CSllJZjA5eENnZTVoTkxLCnREeUJOWWNQZzg1bUJpOU9CNFJ2UlgyQVFRMjJwZ0xrQUpJWklOU0FEdUFrODN2b051SXM2YVY2bHBkR2Vva3YKdDlpTFdNR3p3a3ZTZUZQTlNGeWZ3Q055eENjb1FDQUNtSnJRS3NoeUE2bWNzdVhORWVXdlRQdVVFSWZPVFl4dwpxdkszRVBOK0xUYlA2anhUMWtTcFJUOSt4Z29uSlFhU0RsbUNBd20zRGJkSVppWUt3R2ppMkxKL0kvYWc0cTlzCjNLb0J2UUlEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLcVVrU21jdW85OG5EK015b005VFdEV0pyTndySXpQTUNqRQpCSkdyREhVaHIwcEZlRjc0RHViODNzRXlaNjFxNUVQd2Y0enNLSzdzdDRUTzZhcE9pZWJYVmN3dnZoa09HQ2dFCmFVdGNOMjFhUGxtU0tOd0c4ai8yK3ZhbU80bGplK1NnZzRUUVB0eDZWejh5VXN2RFhxSUZycjNNd1gzSDA1RW4KWXAzN05JYkhKbGxHUW5LVHA5aTg5aXF4WXVhSERqZldiVHlEY3B5NldNVjdVaFYvY1plc3lGL0NBamNHd1V6YgowRlo5bW5tMnFONlBGWHZ4RmdMSGFWZzN2SVVCbkNmVVVyY1BDNE94VFNPK21aUmUxazh3eUFpVWovSk0rZllvCkcrMi9sbThUYVZqb1U3Rmk1S2E1RzVIWTJHTGFSN1ArSXhZY3JNSENsNjJZN1JxY3JuYz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
105 `),
106 "configWithLinkedCert": []byte(`
107 apiVersion: v1
108 clusters:
109 - cluster:
110 server: https://10.0.2.15:6443
111 name: kubernetes
112 contexts:
113 - context:
114 cluster: kubernetes
115 user: system:node:mynode
116 name: system:node:mynode@kubernetes
117 current-context: system:node:mynode@kubernetes
118 kind: Config
119 preferences: {}
120 users:
121 - name: system:node:mynode
122 user:
123 client-certificate: kubelet.pem
124 `),
125 "configWithInvalidContext": []byte(`
126 apiVersion: v1
127 clusters:
128 - cluster:
129 server: https://10.0.2.15:6443
130 name: kubernetes
131 contexts:
132 - context:
133 cluster: kubernetes
134 user: system:node:mynode
135 name: system:node:mynode@kubernetes
136 current-context: invalidContext
137 kind: Config
138 preferences: {}
139 users:
140 - name: system:node:mynode
141 user:
142 client-certificate: kubelet.pem
143 `),
144 "configWithInvalidUser": []byte(`
145 apiVersion: v1
146 clusters:
147 - cluster:
148 server: https://10.0.2.15:6443
149 name: kubernetes
150 contexts:
151 - context:
152 cluster: kubernetes
153 user: invalidUser
154 name: system:node:mynode@kubernetes
155 current-context: system:node:mynode@kubernetes
156 kind: Config
157 preferences: {}
158 users:
159 - name: system:node:mynode
160 user:
161 client-certificate: kubelet.pem
162 `),
163 }
164
165 var pemFiles = map[string][]byte{
166 "mynode.pem": []byte(`
167 -----BEGIN CERTIFICATE-----
168 MIIC8jCCAdqgAwIBAgIIAiwUDabNogQwDQYJKoZIhvcNAQELBQAwFTETMBEGA1UE
169 AxMKa3ViZXJuZXRlczAeFw0xODA5MDExNTMxMjZaFw0xOTA5MDExNjAxNTlaMDQx
170 FTATBgNVBAoTDHN5c3RlbTpub2RlczEbMBkGA1UEAxMSc3lzdGVtOm5vZGU6bXlu
171 b2RlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk6Qu3y+r4FXS6xVJ
172 YMo4OdJKwGWu0v8LBHRxa9HoTz5EvKBpu9Rhzkyu+ThW3oLmkfSFcIr+GkC91m38
173 EzTfTNIcgl/EybJfGT/tgTk6XwY1cU+QGfHAM50BW1WLUGsnBJYIf09xCge5hNLK
174 tDyBNYcPg85mBi9OB4RvRX2AQQ22pgLkAJIZINSADuAk83voNuIs6aV6lpdGeokv
175 t9iLWMGzwkvSeFPNSFyfwCNyxCcoQCACmJrQKshyA6mcsuXNEeWvTPuUEIfOTYxw
176 qvK3EPN+LTbP6jxT1kSpRT9+xgonJQaSDlmCAwm3DbdIZiYKwGji2LJ/I/ag4q9s
177 3KoBvQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
178 AwIwDQYJKoZIhvcNAQELBQADggEBAKqUkSmcuo98nD+MyoM9TWDWJrNwrIzPMCjE
179 BJGrDHUhr0pFeF74Dub83sEyZ61q5EPwf4zsKK7st4TO6apOiebXVcwvvhkOGCgE
180 aUtcN21aPlmSKNwG8j/2+vamO4lje+Sgg4TQPtx6Vz8yUsvDXqIFrr3MwX3H05En
181 Yp37NIbHJllGQnKTp9i89iqxYuaHDjfWbTyDcpy6WMV7UhV/cZesyF/CAjcGwUzb
182 0FZ9mnm2qN6PFXvxFgLHaVg3vIUBnCfUUrcPC4OxTSO+mZRe1k8wyAiUj/JM+fYo
183 G+2/lm8TaVjoU7Fi5Ka5G5HY2GLaR7P+IxYcrMHCl62Y7Rqcrnc=
184 -----END CERTIFICATE-----
185 `),
186 }
187
188 func TestGetNodeNameFromKubeletConfig(t *testing.T) {
189 tmpdir, err := os.MkdirTemp("", "")
190 if err != nil {
191 t.Fatalf("Couldn't create tmpdir")
192 }
193 defer os.RemoveAll(tmpdir)
194
195 var tests = []struct {
196 name string
197 kubeconfigContent []byte
198 pemContent []byte
199 expectedError bool
200 }{
201 {
202 name: "valid - with embedded cert",
203 kubeconfigContent: kubeletConfFiles["configWithEmbeddedCert"],
204 },
205 {
206 name: "invalid - linked cert missing",
207 kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
208 expectedError: true,
209 },
210 {
211 name: "valid - with linked cert",
212 kubeconfigContent: kubeletConfFiles["configWithLinkedCert"],
213 pemContent: pemFiles["mynode.pem"],
214 },
215 {
216 name: "invalid - without embedded or linked X509Cert",
217 kubeconfigContent: kubeletConfFiles["withoutX509Cert"],
218 expectedError: true,
219 },
220 {
221 name: "invalid - the current context is invalid",
222 kubeconfigContent: kubeletConfFiles["configWithInvalidContext"],
223 expectedError: true,
224 },
225 {
226 name: "invalid - the user of the current context is invalid",
227 kubeconfigContent: kubeletConfFiles["configWithInvalidUser"],
228 expectedError: true,
229 },
230 }
231
232 for _, rt := range tests {
233 t.Run(rt.name, func(t2 *testing.T) {
234 if len(rt.pemContent) > 0 {
235 pemPath := filepath.Join(tmpdir, "kubelet.pem")
236 err := os.WriteFile(pemPath, rt.pemContent, 0644)
237 if err != nil {
238 t.Errorf("Couldn't create pem file: %v", err)
239 return
240 }
241 rt.kubeconfigContent = []byte(strings.Replace(string(rt.kubeconfigContent), "kubelet.pem", pemPath, -1))
242 }
243
244 kubeconfigPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
245 err := os.WriteFile(kubeconfigPath, rt.kubeconfigContent, 0644)
246 if err != nil {
247 t.Errorf("Couldn't create kubeconfig: %v", err)
248 return
249 }
250
251 name, err := getNodeNameFromKubeletConfig(kubeconfigPath)
252 if rt.expectedError != (err != nil) {
253 t.Errorf("unexpected return err from getNodeRegistration: %v", err)
254 return
255 }
256 if rt.expectedError {
257 return
258 }
259
260 if name != nodeName {
261 t.Errorf("invalid name")
262 }
263 })
264 }
265 }
266
267 func TestGetNodeRegistration(t *testing.T) {
268 tmpdir, err := os.MkdirTemp("", "")
269 if err != nil {
270 t.Fatalf("Couldn't create tmpdir")
271 }
272 defer os.RemoveAll(tmpdir)
273
274 var tests = []struct {
275 name string
276 fileContents []byte
277 node *v1.Node
278 expectedError bool
279 }{
280 {
281 name: "invalid - no kubelet.conf",
282 expectedError: true,
283 },
284 {
285 name: "valid",
286 fileContents: kubeletConfFiles["configWithEmbeddedCert"],
287 node: &v1.Node{
288 ObjectMeta: metav1.ObjectMeta{
289 Name: nodeName,
290 Annotations: map[string]string{
291 kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
292 },
293 },
294 Spec: v1.NodeSpec{
295 Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
296 },
297 },
298 },
299 {
300 name: "invalid - no node",
301 fileContents: kubeletConfFiles["configWithEmbeddedCert"],
302 expectedError: true,
303 },
304 }
305
306 for _, rt := range tests {
307 t.Run(rt.name, func(t2 *testing.T) {
308 cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
309 if len(rt.fileContents) > 0 {
310 err := os.WriteFile(cfgPath, rt.fileContents, 0644)
311 if err != nil {
312 t.Errorf("Couldn't create file")
313 return
314 }
315 }
316
317 client := clientsetfake.NewSimpleClientset()
318
319 if rt.node != nil {
320 _, err := client.CoreV1().Nodes().Create(context.TODO(), rt.node, metav1.CreateOptions{})
321 if err != nil {
322 t.Errorf("couldn't create Node")
323 return
324 }
325 }
326
327 cfg := &kubeadmapi.InitConfiguration{}
328 err = GetNodeRegistration(cfgPath, client, &cfg.NodeRegistration)
329 if rt.expectedError != (err != nil) {
330 t.Errorf("unexpected return err from getNodeRegistration: %v", err)
331 return
332 }
333 if rt.expectedError {
334 return
335 }
336
337 if cfg.NodeRegistration.Name != nodeName {
338 t.Errorf("invalid cfg.NodeRegistration.Name")
339 }
340 if cfg.NodeRegistration.CRISocket != "myCRIsocket" {
341 t.Errorf("invalid cfg.NodeRegistration.CRISocket")
342 }
343 if len(cfg.NodeRegistration.Taints) != 1 {
344 t.Errorf("invalid cfg.NodeRegistration.Taints")
345 }
346 })
347 }
348 }
349
350 func TestGetAPIEndpointWithBackoff(t *testing.T) {
351 var tests = []struct {
352 name string
353 nodeName string
354 staticPod *testresources.FakeStaticPod
355 expectedEndpoint *kubeadmapi.APIEndpoint
356 expectedErr bool
357 }{
358 {
359 name: "no pod annotations",
360 nodeName: nodeName,
361 expectedErr: true,
362 },
363 {
364 name: "valid ipv4 endpoint in pod annotation",
365 nodeName: nodeName,
366 staticPod: &testresources.FakeStaticPod{
367 Component: kubeadmconstants.KubeAPIServer,
368 Annotations: map[string]string{
369 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234",
370 },
371 },
372 expectedEndpoint: &kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
373 },
374 {
375 name: "invalid ipv4 endpoint in pod annotation",
376 nodeName: nodeName,
377 staticPod: &testresources.FakeStaticPod{
378 Component: kubeadmconstants.KubeAPIServer,
379 Annotations: map[string]string{
380 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3::1234",
381 },
382 },
383 expectedErr: true,
384 },
385 {
386 name: "invalid negative port with ipv4 address in pod annotation",
387 nodeName: nodeName,
388 staticPod: &testresources.FakeStaticPod{
389 Component: kubeadmconstants.KubeAPIServer,
390 Annotations: map[string]string{
391 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:-1234",
392 },
393 },
394 expectedErr: true,
395 },
396 {
397 name: "invalid high port with ipv4 address in pod annotation",
398 nodeName: nodeName,
399 staticPod: &testresources.FakeStaticPod{
400 Component: kubeadmconstants.KubeAPIServer,
401 Annotations: map[string]string{
402 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:65536",
403 },
404 },
405 expectedErr: true,
406 },
407 {
408 name: "valid ipv6 endpoint in pod annotation",
409 nodeName: nodeName,
410 staticPod: &testresources.FakeStaticPod{
411 Component: kubeadmconstants.KubeAPIServer,
412 Annotations: map[string]string{
413 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1]:1234",
414 },
415 },
416 expectedEndpoint: &kubeadmapi.APIEndpoint{AdvertiseAddress: "::1", BindPort: 1234},
417 },
418 {
419 name: "invalid ipv6 endpoint in pod annotation",
420 nodeName: nodeName,
421 staticPod: &testresources.FakeStaticPod{
422 Component: kubeadmconstants.KubeAPIServer,
423 Annotations: map[string]string{
424 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1:1234",
425 },
426 },
427 expectedErr: true,
428 },
429 {
430 name: "invalid negative port with ipv6 address in pod annotation",
431 nodeName: nodeName,
432 staticPod: &testresources.FakeStaticPod{
433 Component: kubeadmconstants.KubeAPIServer,
434 Annotations: map[string]string{
435 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1]:-1234",
436 },
437 },
438 expectedErr: true,
439 },
440 {
441 name: "invalid high port with ipv6 address in pod annotation",
442 nodeName: nodeName,
443 staticPod: &testresources.FakeStaticPod{
444 Component: kubeadmconstants.KubeAPIServer,
445 Annotations: map[string]string{
446 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "[::1]:65536",
447 },
448 },
449 expectedErr: true,
450 },
451 }
452
453 for _, rt := range tests {
454 t.Run(rt.name, func(t *testing.T) {
455 client := clientsetfake.NewSimpleClientset()
456 if rt.staticPod != nil {
457 rt.staticPod.NodeName = rt.nodeName
458 if err := rt.staticPod.Create(client); err != nil {
459 t.Error("could not create static pod")
460 return
461 }
462 }
463 apiEndpoint := kubeadmapi.APIEndpoint{}
464 err := getAPIEndpointWithRetry(client, rt.nodeName, &apiEndpoint,
465 time.Millisecond*10, time.Millisecond*100)
466 if err != nil && !rt.expectedErr {
467 t.Errorf("got error %q; was expecting no errors", err)
468 return
469 } else if err == nil && rt.expectedErr {
470 t.Error("got no error; was expecting an error")
471 return
472 }
473
474 if rt.expectedEndpoint != nil && !reflect.DeepEqual(apiEndpoint, *rt.expectedEndpoint) {
475 t.Errorf("expected API endpoint: %v; got %v", rt.expectedEndpoint, apiEndpoint)
476 }
477 })
478 }
479 }
480
481 func TestGetInitConfigurationFromCluster(t *testing.T) {
482 tmpdir, err := os.MkdirTemp("", "")
483 if err != nil {
484 t.Fatalf("Couldn't create tmpdir")
485 }
486 defer os.RemoveAll(tmpdir)
487
488 var tests = []struct {
489 name string
490 fileContents []byte
491 node *v1.Node
492 staticPods []testresources.FakeStaticPod
493 configMaps []testresources.FakeConfigMap
494 newControlPlane bool
495 expectedError bool
496 }{
497 {
498 name: "invalid - No kubeadm-config ConfigMap",
499 expectedError: true,
500 },
501 {
502 name: "invalid - No ClusterConfiguration in kubeadm-config ConfigMap",
503 configMaps: []testresources.FakeConfigMap{
504 {
505 Name: kubeadmconstants.KubeadmConfigConfigMap,
506 Data: map[string]string{},
507 },
508 },
509 expectedError: true,
510 },
511 {
512 name: "valid v1beta3 - new control plane == false",
513 staticPods: []testresources.FakeStaticPod{
514 {
515 NodeName: nodeName,
516 Component: kubeadmconstants.KubeAPIServer,
517 Annotations: map[string]string{
518 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234",
519 },
520 },
521 },
522 configMaps: []testresources.FakeConfigMap{
523 {
524 Name: kubeadmconstants.KubeadmConfigConfigMap,
525 Data: map[string]string{
526 kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta3"]),
527 },
528 },
529 {
530 Name: kubeadmconstants.KubeProxyConfigMap,
531 Data: map[string]string{
532 kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
533 },
534 },
535 {
536 Name: kubeadmconstants.KubeletBaseConfigurationConfigMap,
537 Data: map[string]string{
538 kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
539 },
540 },
541 },
542 fileContents: kubeletConfFiles["configWithEmbeddedCert"],
543 node: &v1.Node{
544 ObjectMeta: metav1.ObjectMeta{
545 Name: nodeName,
546 Annotations: map[string]string{
547 kubeadmconstants.AnnotationKubeadmCRISocket: "myCRIsocket",
548 },
549 },
550 Spec: v1.NodeSpec{
551 Taints: []v1.Taint{kubeadmconstants.ControlPlaneTaint},
552 },
553 },
554 },
555 {
556 name: "valid v1beta3 - new control plane == true",
557 staticPods: []testresources.FakeStaticPod{
558 {
559 NodeName: nodeName,
560 Component: kubeadmconstants.KubeAPIServer,
561 Annotations: map[string]string{
562 kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234",
563 },
564 },
565 },
566 configMaps: []testresources.FakeConfigMap{
567 {
568 Name: kubeadmconstants.KubeadmConfigConfigMap,
569 Data: map[string]string{
570 kubeadmconstants.ClusterConfigurationConfigMapKey: string(cfgFiles["ClusterConfiguration_v1beta3"]),
571 },
572 },
573 {
574 Name: kubeadmconstants.KubeProxyConfigMap,
575 Data: map[string]string{
576 kubeadmconstants.KubeProxyConfigMapKey: string(cfgFiles["Kube-proxy_componentconfig"]),
577 },
578 },
579 {
580 Name: kubeadmconstants.KubeletBaseConfigurationConfigMap,
581 Data: map[string]string{
582 kubeadmconstants.KubeletBaseConfigurationConfigMapKey: string(cfgFiles["Kubelet_componentconfig"]),
583 },
584 },
585 },
586 newControlPlane: true,
587 },
588 }
589
590 for _, rt := range tests {
591 t.Run(rt.name, func(t *testing.T) {
592 cfgPath := filepath.Join(tmpdir, kubeadmconstants.KubeletKubeConfigFileName)
593 if len(rt.fileContents) > 0 {
594 err := os.WriteFile(cfgPath, rt.fileContents, 0644)
595 if err != nil {
596 t.Errorf("Couldn't create file")
597 return
598 }
599 }
600
601 client := clientsetfake.NewSimpleClientset()
602
603 if rt.node != nil {
604 _, err := client.CoreV1().Nodes().Create(context.TODO(), rt.node, metav1.CreateOptions{})
605 if err != nil {
606 t.Errorf("couldn't create Node")
607 return
608 }
609 }
610
611 for _, p := range rt.staticPods {
612 err := p.Create(client)
613 if err != nil {
614 t.Errorf("couldn't create pod for nodename %s", p.NodeName)
615 return
616 }
617 }
618
619 for _, c := range rt.configMaps {
620 err := c.Create(client)
621 if err != nil {
622 t.Errorf("couldn't create ConfigMap %s", c.Name)
623 return
624 }
625 }
626
627 cfg, err := getInitConfigurationFromCluster(tmpdir, client, rt.newControlPlane, false)
628 if rt.expectedError != (err != nil) {
629 t.Errorf("unexpected return err from getInitConfigurationFromCluster: %v", err)
630 return
631 }
632 if rt.expectedError {
633 return
634 }
635
636
637 if cfg == nil {
638 t.Errorf("unexpected nil return value")
639 return
640 }
641 if cfg.ClusterConfiguration.KubernetesVersion != k8sVersionString {
642 t.Errorf("invalid ClusterConfiguration.KubernetesVersion")
643 }
644 if cfg.NodeRegistration.ImagePullPolicy != kubeadmapiv1.DefaultImagePullPolicy {
645 t.Errorf("invalid cfg.NodeRegistration.ImagePullPolicy %v", cfg.NodeRegistration.ImagePullPolicy)
646 }
647 if !rt.newControlPlane && (cfg.LocalAPIEndpoint.AdvertiseAddress != "1.2.3.4" || cfg.LocalAPIEndpoint.BindPort != 1234) {
648 t.Errorf("invalid cfg.LocalAPIEndpoint: %v", cfg.LocalAPIEndpoint)
649 }
650 if !rt.newControlPlane && (cfg.NodeRegistration.Name != nodeName || cfg.NodeRegistration.CRISocket != "myCRIsocket" || len(cfg.NodeRegistration.Taints) != 1) {
651 t.Errorf("invalid cfg.NodeRegistration: %v", cfg.NodeRegistration)
652 }
653 if rt.newControlPlane && len(cfg.NodeRegistration.CRISocket) > 0 {
654 t.Errorf("invalid cfg.NodeRegistration.CRISocket: expected empty CRISocket, but got %v", cfg.NodeRegistration.CRISocket)
655 }
656 if _, ok := cfg.ComponentConfigs[componentconfigs.KubeletGroup]; !ok {
657 t.Errorf("no cfg.ComponentConfigs[%q]", componentconfigs.KubeletGroup)
658 }
659 if _, ok := cfg.ComponentConfigs[componentconfigs.KubeProxyGroup]; !ok {
660 t.Errorf("no cfg.ComponentConfigs[%q]", componentconfigs.KubeProxyGroup)
661 }
662 })
663 }
664 }
665
666 func TestGetAPIEndpointFromPodAnnotation(t *testing.T) {
667 var tests = []struct {
668 name string
669 nodeName string
670 pods []testresources.FakeStaticPod
671 clientSetup func(*clientsetfake.Clientset)
672 expectedEndpoint kubeadmapi.APIEndpoint
673 expectedErr bool
674 }{
675 {
676 name: "exactly one pod with annotation",
677 nodeName: nodeName,
678 pods: []testresources.FakeStaticPod{
679 {
680 Component: kubeadmconstants.KubeAPIServer,
681 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
682 },
683 },
684 expectedEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
685 },
686 {
687 name: "no pods with annotation",
688 nodeName: nodeName,
689 expectedErr: true,
690 },
691 {
692 name: "exactly one pod with annotation; all requests fail",
693 nodeName: nodeName,
694 pods: []testresources.FakeStaticPod{
695 {
696 Component: kubeadmconstants.KubeAPIServer,
697 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
698 },
699 },
700 clientSetup: func(clientset *clientsetfake.Clientset) {
701 clientset.PrependReactor("list", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
702 return true, nil, apierrors.NewInternalError(errors.New("API server down"))
703 })
704 },
705 expectedErr: true,
706 },
707 }
708 for _, rt := range tests {
709 t.Run(rt.name, func(t *testing.T) {
710 client := clientsetfake.NewSimpleClientset()
711 for i, pod := range rt.pods {
712 pod.NodeName = rt.nodeName
713 if err := pod.CreateWithPodSuffix(client, strconv.Itoa(i)); err != nil {
714 t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
715 return
716 }
717 }
718 if rt.clientSetup != nil {
719 rt.clientSetup(client)
720 }
721 apiEndpoint := kubeadmapi.APIEndpoint{}
722 err := getAPIEndpointFromPodAnnotation(client, rt.nodeName, &apiEndpoint,
723 time.Millisecond*10, time.Millisecond*100)
724 if err != nil && !rt.expectedErr {
725 t.Errorf("got error %v, but wasn't expecting any error", err)
726 return
727 } else if err == nil && rt.expectedErr {
728 t.Error("didn't get any error; but was expecting an error")
729 return
730 } else if err != nil && rt.expectedErr {
731 return
732 }
733 if !reflect.DeepEqual(apiEndpoint, rt.expectedEndpoint) {
734 t.Errorf("expected API endpoint: %v; got %v", rt.expectedEndpoint, apiEndpoint)
735 }
736 })
737 }
738 }
739
740 func TestGetRawAPIEndpointFromPodAnnotationWithoutRetry(t *testing.T) {
741 var tests = []struct {
742 name string
743 nodeName string
744 pods []testresources.FakeStaticPod
745 clientSetup func(*clientsetfake.Clientset)
746 expectedEndpoint string
747 expectedErr bool
748 }{
749 {
750 name: "no pods",
751 nodeName: nodeName,
752 expectedErr: true,
753 },
754 {
755 name: "exactly one pod with annotation",
756 nodeName: nodeName,
757 pods: []testresources.FakeStaticPod{
758 {
759 Component: kubeadmconstants.KubeAPIServer,
760 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
761 },
762 },
763 expectedEndpoint: "1.2.3.4:1234",
764 },
765 {
766 name: "two pods: one with annotation, one missing annotation",
767 nodeName: nodeName,
768 pods: []testresources.FakeStaticPod{
769 {
770 Component: kubeadmconstants.KubeAPIServer,
771 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
772 },
773 {
774 Component: kubeadmconstants.KubeAPIServer,
775 },
776 },
777 expectedErr: true,
778 },
779 {
780 name: "two pods: different annotations",
781 nodeName: nodeName,
782 pods: []testresources.FakeStaticPod{
783 {
784 Component: kubeadmconstants.KubeAPIServer,
785 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
786 },
787 {
788 Component: kubeadmconstants.KubeAPIServer,
789 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.5:1234"},
790 },
791 },
792 expectedErr: true,
793 },
794 {
795 name: "two pods: both missing annotation",
796 nodeName: nodeName,
797 pods: []testresources.FakeStaticPod{
798 {
799 Component: kubeadmconstants.KubeAPIServer,
800 },
801 {
802 Component: kubeadmconstants.KubeAPIServer,
803 },
804 },
805 expectedErr: true,
806 },
807 {
808 name: "exactly one pod with annotation; request fails",
809 nodeName: nodeName,
810 pods: []testresources.FakeStaticPod{
811 {
812 Component: kubeadmconstants.KubeAPIServer,
813 Annotations: map[string]string{kubeadmconstants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey: "1.2.3.4:1234"},
814 },
815 },
816 clientSetup: func(clientset *clientsetfake.Clientset) {
817 clientset.PrependReactor("list", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
818 return true, nil, apierrors.NewInternalError(errors.New("API server down"))
819 })
820 },
821 expectedErr: true,
822 },
823 }
824 for _, rt := range tests {
825 t.Run(rt.name, func(t *testing.T) {
826 client := clientsetfake.NewSimpleClientset()
827 for i, pod := range rt.pods {
828 pod.NodeName = rt.nodeName
829 if err := pod.CreateWithPodSuffix(client, strconv.Itoa(i)); err != nil {
830 t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
831 return
832 }
833 }
834 if rt.clientSetup != nil {
835 rt.clientSetup(client)
836 }
837 endpoint, err := getRawAPIEndpointFromPodAnnotationWithoutRetry(context.Background(), client, rt.nodeName)
838 if err != nil && !rt.expectedErr {
839 t.Errorf("got error %v, but wasn't expecting any error", err)
840 return
841 } else if err == nil && rt.expectedErr {
842 t.Error("didn't get any error; but was expecting an error")
843 return
844 } else if err != nil && rt.expectedErr {
845 return
846 }
847 if endpoint != rt.expectedEndpoint {
848 t.Errorf("expected API endpoint: %v; got: %v", rt.expectedEndpoint, endpoint)
849 }
850 })
851 }
852 }
853
View as plain text