1
16
17 package staticpod
18
19 import (
20 "io"
21 "os"
22 "path/filepath"
23 "reflect"
24 "sort"
25 "strconv"
26 "strings"
27 "testing"
28
29 v1 "k8s.io/api/core/v1"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31
32 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
33 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
34 testutil "k8s.io/kubernetes/cmd/kubeadm/test"
35 )
36
37 func TestComponentResources(t *testing.T) {
38 a := ComponentResources("250m")
39 if a.Requests == nil {
40 t.Errorf(
41 "failed componentResources, return value was nil",
42 )
43 }
44 }
45
46 func TestGetAPIServerProbeAddress(t *testing.T) {
47 tests := []struct {
48 desc string
49 endpoint *kubeadmapi.APIEndpoint
50 expected string
51 }{
52 {
53 desc: "nil endpoint returns 127.0.0.1",
54 expected: "127.0.0.1",
55 },
56 {
57 desc: "empty AdvertiseAddress endpoint returns 127.0.0.1",
58 endpoint: &kubeadmapi.APIEndpoint{},
59 expected: "127.0.0.1",
60 },
61 {
62 desc: "filled in AdvertiseAddress endpoint returns it",
63 endpoint: &kubeadmapi.APIEndpoint{
64 AdvertiseAddress: "10.10.10.10",
65 },
66 expected: "10.10.10.10",
67 },
68 {
69 desc: "filled in ipv6 AdvertiseAddress endpoint returns it",
70 endpoint: &kubeadmapi.APIEndpoint{
71 AdvertiseAddress: "2001:abcd:bcda::1",
72 },
73 expected: "2001:abcd:bcda::1",
74 },
75 {
76 desc: "filled in 0.0.0.0 AdvertiseAddress endpoint returns empty",
77 endpoint: &kubeadmapi.APIEndpoint{
78 AdvertiseAddress: "0.0.0.0",
79 },
80 expected: "",
81 },
82 {
83 desc: "filled in :: AdvertiseAddress endpoint returns empty",
84 endpoint: &kubeadmapi.APIEndpoint{
85 AdvertiseAddress: "::",
86 },
87 expected: "",
88 },
89 }
90
91 for _, test := range tests {
92 t.Run(test.desc, func(t *testing.T) {
93 actual := GetAPIServerProbeAddress(test.endpoint)
94 if actual != test.expected {
95 t.Errorf("Unexpected result from GetAPIServerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual)
96 }
97 })
98 }
99 }
100
101 func TestGetControllerManagerProbeAddress(t *testing.T) {
102 tests := []struct {
103 desc string
104 cfg *kubeadmapi.ClusterConfiguration
105 expected string
106 }{
107 {
108 desc: "no controller manager extra args leads to 127.0.0.1 being used",
109 cfg: &kubeadmapi.ClusterConfiguration{
110 ControllerManager: kubeadmapi.ControlPlaneComponent{
111 ExtraArgs: []kubeadmapi.Arg{},
112 },
113 },
114 expected: "127.0.0.1",
115 },
116 {
117 desc: "setting controller manager extra address arg to something acknowledges it",
118 cfg: &kubeadmapi.ClusterConfiguration{
119 ControllerManager: kubeadmapi.ControlPlaneComponent{
120 ExtraArgs: []kubeadmapi.Arg{
121 {Name: kubeControllerManagerBindAddressArg, Value: "10.10.10.10"},
122 },
123 },
124 },
125 expected: "10.10.10.10",
126 },
127 {
128 desc: "setting controller manager extra ipv6 address arg to something acknowledges it",
129 cfg: &kubeadmapi.ClusterConfiguration{
130 ControllerManager: kubeadmapi.ControlPlaneComponent{
131 ExtraArgs: []kubeadmapi.Arg{
132 {Name: kubeControllerManagerBindAddressArg, Value: "2001:abcd:bcda::1"},
133 },
134 },
135 },
136 expected: "2001:abcd:bcda::1",
137 },
138 {
139 desc: "setting controller manager extra address arg to 0.0.0.0 returns empty",
140 cfg: &kubeadmapi.ClusterConfiguration{
141 ControllerManager: kubeadmapi.ControlPlaneComponent{
142 ExtraArgs: []kubeadmapi.Arg{
143 {Name: kubeControllerManagerBindAddressArg, Value: "0.0.0.0"},
144 },
145 },
146 },
147 expected: "",
148 },
149 {
150 desc: "setting controller manager extra ipv6 address arg to :: returns empty",
151 cfg: &kubeadmapi.ClusterConfiguration{
152 ControllerManager: kubeadmapi.ControlPlaneComponent{
153 ExtraArgs: []kubeadmapi.Arg{
154 {Name: kubeControllerManagerBindAddressArg, Value: "::"},
155 },
156 },
157 },
158 expected: "",
159 },
160 }
161
162 for _, test := range tests {
163 t.Run(test.desc, func(t *testing.T) {
164 actual := GetControllerManagerProbeAddress(test.cfg)
165 if actual != test.expected {
166 t.Errorf("Unexpected result from GetControllerManagerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual)
167 }
168 })
169 }
170 }
171
172 func TestGetSchedulerProbeAddress(t *testing.T) {
173 tests := []struct {
174 desc string
175 cfg *kubeadmapi.ClusterConfiguration
176 expected string
177 }{
178 {
179 desc: "no scheduler extra args leads to 127.0.0.1 being used",
180 cfg: &kubeadmapi.ClusterConfiguration{
181 Scheduler: kubeadmapi.ControlPlaneComponent{
182 ExtraArgs: []kubeadmapi.Arg{},
183 },
184 },
185 expected: "127.0.0.1",
186 },
187 {
188 desc: "setting scheduler extra address arg to something acknowledges it",
189 cfg: &kubeadmapi.ClusterConfiguration{
190 Scheduler: kubeadmapi.ControlPlaneComponent{
191 ExtraArgs: []kubeadmapi.Arg{
192 {Name: kubeSchedulerBindAddressArg, Value: "10.10.10.10"},
193 },
194 },
195 },
196 expected: "10.10.10.10",
197 },
198 {
199 desc: "setting scheduler extra ipv6 address arg to something acknowledges it",
200 cfg: &kubeadmapi.ClusterConfiguration{
201 Scheduler: kubeadmapi.ControlPlaneComponent{
202 ExtraArgs: []kubeadmapi.Arg{
203 {Name: kubeSchedulerBindAddressArg, Value: "2001:abcd:bcda::1"},
204 },
205 },
206 },
207 expected: "2001:abcd:bcda::1",
208 },
209 {
210 desc: "setting scheduler extra ipv6 address arg to 0.0.0.0 returns empty",
211 cfg: &kubeadmapi.ClusterConfiguration{
212 Scheduler: kubeadmapi.ControlPlaneComponent{
213 ExtraArgs: []kubeadmapi.Arg{
214 {Name: kubeSchedulerBindAddressArg, Value: "0.0.0.0"},
215 },
216 },
217 },
218 expected: "",
219 },
220 {
221 desc: "setting scheduler extra ipv6 address arg to :: returns empty",
222 cfg: &kubeadmapi.ClusterConfiguration{
223 Scheduler: kubeadmapi.ControlPlaneComponent{
224 ExtraArgs: []kubeadmapi.Arg{
225 {Name: kubeSchedulerBindAddressArg, Value: "::"},
226 },
227 },
228 },
229 expected: "",
230 },
231 }
232
233 for _, test := range tests {
234 t.Run(test.desc, func(t *testing.T) {
235 actual := GetSchedulerProbeAddress(test.cfg)
236 if actual != test.expected {
237 t.Errorf("Unexpected result from GetSchedulerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual)
238 }
239 })
240 }
241 }
242 func TestGetEtcdProbeEndpoint(t *testing.T) {
243 var tests = []struct {
244 name string
245 cfg *kubeadmapi.Etcd
246 isIPv6 bool
247 expectedHostname string
248 expectedPort int32
249 expectedScheme v1.URIScheme
250 }{
251 {
252 name: "etcd probe URL from two URLs",
253 cfg: &kubeadmapi.Etcd{
254 Local: &kubeadmapi.LocalEtcd{
255 ExtraArgs: []kubeadmapi.Arg{
256 {Name: "listen-metrics-urls", Value: "https://1.2.3.4:1234,https://4.3.2.1:2381"},
257 },
258 },
259 },
260 isIPv6: false,
261 expectedHostname: "1.2.3.4",
262 expectedPort: 1234,
263 expectedScheme: v1.URISchemeHTTPS,
264 },
265 {
266 name: "etcd probe URL with HTTP scheme",
267 cfg: &kubeadmapi.Etcd{
268 Local: &kubeadmapi.LocalEtcd{
269 ExtraArgs: []kubeadmapi.Arg{
270 {Name: "listen-metrics-urls", Value: "http://1.2.3.4:1234"},
271 },
272 },
273 },
274 isIPv6: false,
275 expectedHostname: "1.2.3.4",
276 expectedPort: 1234,
277 expectedScheme: v1.URISchemeHTTP,
278 },
279 {
280 name: "etcd probe URL without scheme should result in defaults",
281 cfg: &kubeadmapi.Etcd{
282 Local: &kubeadmapi.LocalEtcd{
283 ExtraArgs: []kubeadmapi.Arg{
284 {Name: "listen-metrics-urls", Value: "1.2.3.4"},
285 },
286 },
287 },
288 isIPv6: false,
289 expectedHostname: "127.0.0.1",
290 expectedPort: kubeadmconstants.EtcdMetricsPort,
291 expectedScheme: v1.URISchemeHTTP,
292 },
293 {
294 name: "etcd probe URL without port",
295 cfg: &kubeadmapi.Etcd{
296 Local: &kubeadmapi.LocalEtcd{
297 ExtraArgs: []kubeadmapi.Arg{
298 {Name: "listen-metrics-urls", Value: "https://1.2.3.4"},
299 },
300 },
301 },
302 isIPv6: false,
303 expectedHostname: "1.2.3.4",
304 expectedPort: kubeadmconstants.EtcdMetricsPort,
305 expectedScheme: v1.URISchemeHTTPS,
306 },
307 {
308 name: "etcd probe URL from two IPv6 URLs",
309 cfg: &kubeadmapi.Etcd{
310 Local: &kubeadmapi.LocalEtcd{
311 ExtraArgs: []kubeadmapi.Arg{
312 {Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]:1234,https://[2001:abcd:bcda::2]:2381"},
313 },
314 },
315 },
316 isIPv6: true,
317 expectedHostname: "2001:abcd:bcda::1",
318 expectedPort: 1234,
319 expectedScheme: v1.URISchemeHTTPS,
320 },
321 {
322 name: "etcd probe localhost IPv6 URL with HTTP scheme",
323 cfg: &kubeadmapi.Etcd{
324 Local: &kubeadmapi.LocalEtcd{
325 ExtraArgs: []kubeadmapi.Arg{
326 {Name: "listen-metrics-urls", Value: "http://[::1]:1234"},
327 },
328 },
329 },
330 isIPv6: true,
331 expectedHostname: "::1",
332 expectedPort: 1234,
333 expectedScheme: v1.URISchemeHTTP,
334 },
335 {
336 name: "etcd probe IPv6 URL with HTTP scheme",
337 cfg: &kubeadmapi.Etcd{
338 Local: &kubeadmapi.LocalEtcd{
339 ExtraArgs: []kubeadmapi.Arg{
340 {Name: "listen-metrics-urls", Value: "http://[2001:abcd:bcda::1]:1234"},
341 },
342 },
343 },
344 isIPv6: true,
345 expectedHostname: "2001:abcd:bcda::1",
346 expectedPort: 1234,
347 expectedScheme: v1.URISchemeHTTP,
348 },
349 {
350 name: "etcd probe IPv6 URL without port",
351 cfg: &kubeadmapi.Etcd{
352 Local: &kubeadmapi.LocalEtcd{
353 ExtraArgs: []kubeadmapi.Arg{
354 {Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]"},
355 },
356 },
357 },
358 isIPv6: true,
359 expectedHostname: "2001:abcd:bcda::1",
360 expectedPort: kubeadmconstants.EtcdMetricsPort,
361 expectedScheme: v1.URISchemeHTTPS,
362 },
363 {
364 name: "etcd probe URL from defaults",
365 cfg: &kubeadmapi.Etcd{
366 Local: &kubeadmapi.LocalEtcd{},
367 },
368 isIPv6: false,
369 expectedHostname: "127.0.0.1",
370 expectedPort: kubeadmconstants.EtcdMetricsPort,
371 expectedScheme: v1.URISchemeHTTP,
372 },
373 {
374 name: "etcd probe URL from defaults if IPv6",
375 cfg: &kubeadmapi.Etcd{
376 Local: &kubeadmapi.LocalEtcd{},
377 },
378 isIPv6: true,
379 expectedHostname: "::1",
380 expectedPort: kubeadmconstants.EtcdMetricsPort,
381 expectedScheme: v1.URISchemeHTTP,
382 },
383 }
384 for _, rt := range tests {
385 t.Run(rt.name, func(t *testing.T) {
386 hostname, port, scheme := GetEtcdProbeEndpoint(rt.cfg, rt.isIPv6)
387 if hostname != rt.expectedHostname {
388 t.Errorf("%q test case failed:\n\texpected hostname: %s\n\tgot: %s",
389 rt.name, rt.expectedHostname, hostname)
390 }
391 if port != rt.expectedPort {
392 t.Errorf("%q test case failed:\n\texpected port: %d\n\tgot: %d",
393 rt.name, rt.expectedPort, port)
394 }
395 if scheme != rt.expectedScheme {
396 t.Errorf("%q test case failed:\n\texpected scheme: %v\n\tgot: %v",
397 rt.name, rt.expectedScheme, scheme)
398 }
399 })
400 }
401 }
402
403 func TestComponentPod(t *testing.T) {
404
405 priority := int32(2000001000)
406 var tests = []struct {
407 name string
408 expected v1.Pod
409 }{
410 {
411 name: "foo",
412 expected: v1.Pod{
413 TypeMeta: metav1.TypeMeta{
414 APIVersion: "v1",
415 Kind: "Pod",
416 },
417 ObjectMeta: metav1.ObjectMeta{
418 Name: "foo",
419 Namespace: "kube-system",
420 Labels: map[string]string{"component": "foo", "tier": "control-plane"},
421 },
422 Spec: v1.PodSpec{
423 SecurityContext: &v1.PodSecurityContext{
424 SeccompProfile: &v1.SeccompProfile{
425 Type: v1.SeccompProfileTypeRuntimeDefault,
426 },
427 },
428 Containers: []v1.Container{
429 {
430 Name: "foo",
431 },
432 },
433 Priority: &priority,
434 PriorityClassName: "system-node-critical",
435 HostNetwork: true,
436 Volumes: []v1.Volume{},
437 },
438 },
439 },
440 }
441
442 for _, rt := range tests {
443 t.Run(rt.name, func(t *testing.T) {
444 c := v1.Container{Name: rt.name}
445 actual := ComponentPod(c, map[string]v1.Volume{}, nil)
446 if !reflect.DeepEqual(rt.expected, actual) {
447 t.Errorf(
448 "failed componentPod:\n\texpected: %v\n\t actual: %v",
449 rt.expected,
450 actual,
451 )
452 }
453 })
454 }
455 }
456
457 func TestNewVolume(t *testing.T) {
458 hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
459 var tests = []struct {
460 name string
461 path string
462 expected v1.Volume
463 pathType *v1.HostPathType
464 }{
465 {
466 name: "foo",
467 path: "/etc/foo",
468 expected: v1.Volume{
469 Name: "foo",
470 VolumeSource: v1.VolumeSource{
471 HostPath: &v1.HostPathVolumeSource{
472 Path: "/etc/foo",
473 Type: &hostPathDirectoryOrCreate,
474 },
475 },
476 },
477 pathType: &hostPathDirectoryOrCreate,
478 },
479 }
480
481 for _, rt := range tests {
482 t.Run(rt.name, func(t *testing.T) {
483 actual := NewVolume(rt.name, rt.path, rt.pathType)
484 if !reflect.DeepEqual(actual, rt.expected) {
485 t.Errorf(
486 "failed newVolume:\n\texpected: %v\n\t actual: %v",
487 rt.expected,
488 actual,
489 )
490 }
491 })
492 }
493 }
494
495 func TestNewVolumeMount(t *testing.T) {
496 var tests = []struct {
497 name string
498 path string
499 ro bool
500 expected v1.VolumeMount
501 }{
502 {
503 name: "foo",
504 path: "/etc/foo",
505 ro: false,
506 expected: v1.VolumeMount{
507 Name: "foo",
508 MountPath: "/etc/foo",
509 ReadOnly: false,
510 },
511 },
512 {
513 name: "bar",
514 path: "/etc/foo/bar",
515 ro: true,
516 expected: v1.VolumeMount{
517 Name: "bar",
518 MountPath: "/etc/foo/bar",
519 ReadOnly: true,
520 },
521 },
522 }
523
524 for _, rt := range tests {
525 t.Run(rt.name, func(t *testing.T) {
526 actual := NewVolumeMount(rt.name, rt.path, rt.ro)
527 if !reflect.DeepEqual(actual, rt.expected) {
528 t.Errorf(
529 "failed newVolumeMount:\n\texpected: %v\n\t actual: %v",
530 rt.expected,
531 actual,
532 )
533 }
534 })
535 }
536 }
537 func TestVolumeMapToSlice(t *testing.T) {
538 testVolumes := map[string]v1.Volume{
539 "foo": {
540 Name: "foo",
541 },
542 "bar": {
543 Name: "bar",
544 },
545 }
546 volumeSlice := VolumeMapToSlice(testVolumes)
547 if len(volumeSlice) != 2 {
548 t.Errorf("Expected slice length of 1, got %d", len(volumeSlice))
549 }
550 if volumeSlice[0].Name != "bar" {
551 t.Errorf("Expected first volume name \"bar\", got %s", volumeSlice[0].Name)
552 }
553 if volumeSlice[1].Name != "foo" {
554 t.Errorf("Expected second volume name \"foo\", got %s", volumeSlice[1].Name)
555 }
556 }
557
558 func TestVolumeMountMapToSlice(t *testing.T) {
559 testVolumeMounts := map[string]v1.VolumeMount{
560 "foo": {
561 Name: "foo",
562 },
563 "bar": {
564 Name: "bar",
565 },
566 }
567 volumeMountSlice := VolumeMountMapToSlice(testVolumeMounts)
568 if len(volumeMountSlice) != 2 {
569 t.Errorf("Expected slice length of 1, got %d", len(volumeMountSlice))
570 }
571 if volumeMountSlice[0].Name != "bar" {
572 t.Errorf("Expected first volume mount name \"bar\", got %s", volumeMountSlice[0].Name)
573 }
574 if volumeMountSlice[1].Name != "foo" {
575 t.Errorf("Expected second volume name \"foo\", got %s", volumeMountSlice[1].Name)
576 }
577 }
578
579 func TestGetExtraParameters(t *testing.T) {
580 var tests = []struct {
581 name string
582 overrides map[string]string
583 defaults map[string]string
584 expected []string
585 }{
586 {
587 name: "with admission-control default NamespaceLifecycle",
588 overrides: map[string]string{
589 "admission-control": "NamespaceLifecycle,LimitRanger",
590 },
591 defaults: map[string]string{
592 "admission-control": "NamespaceLifecycle",
593 "allow-privileged": "true",
594 },
595 expected: []string{
596 "--admission-control=NamespaceLifecycle,LimitRanger",
597 "--allow-privileged=true",
598 },
599 },
600 {
601 name: "without admission-control default",
602 overrides: map[string]string{
603 "admission-control": "NamespaceLifecycle,LimitRanger",
604 },
605 defaults: map[string]string{
606 "allow-privileged": "true",
607 },
608 expected: []string{
609 "--admission-control=NamespaceLifecycle,LimitRanger",
610 "--allow-privileged=true",
611 },
612 },
613 }
614
615 for _, rt := range tests {
616 t.Run(rt.name, func(t *testing.T) {
617 actual := GetExtraParameters(rt.overrides, rt.defaults)
618 sort.Strings(actual)
619 sort.Strings(rt.expected)
620 if !reflect.DeepEqual(actual, rt.expected) {
621 t.Errorf("failed getExtraParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
622 }
623 })
624 }
625 }
626
627 const (
628 validPod = `
629 apiVersion: v1
630 kind: Pod
631 metadata:
632 labels:
633 component: etcd
634 tier: control-plane
635 name: etcd
636 namespace: kube-system
637 spec:
638 containers:
639 - image: gcr.io/google_containers/etcd-amd64:3.1.11
640 status: {}
641 `
642 validPodWithDifferentFieldsOrder = `
643 apiVersion: v1
644 kind: Pod
645 metadata:
646 labels:
647 tier: control-plane
648 component: etcd
649 name: etcd
650 namespace: kube-system
651 spec:
652 containers:
653 - image: gcr.io/google_containers/etcd-amd64:3.1.11
654 status: {}
655 `
656 invalidWithDefaultFields = `
657 apiVersion: v1
658 kind: Pod
659 metadata:
660 labels:
661 tier: control-plane
662 component: etcd
663 name: etcd
664 namespace: kube-system
665 spec:
666 containers:
667 - image: gcr.io/google_containers/etcd-amd64:3.1.11
668 restartPolicy: "Always"
669 status: {}
670 `
671
672 validPod2 = `
673 apiVersion: v1
674 kind: Pod
675 metadata:
676 labels:
677 component: etcd
678 tier: control-plane
679 name: etcd
680 namespace: kube-system
681 spec:
682 containers:
683 - image: gcr.io/google_containers/etcd-amd64:3.1.12
684 status: {}
685 `
686
687 invalidPod = `---{ broken yaml @@@`
688 )
689
690 func TestReadStaticPodFromDisk(t *testing.T) {
691 tests := []struct {
692 description string
693 podYaml string
694 expectErr bool
695 writeManifest bool
696 }{
697 {
698 description: "valid pod is marshaled",
699 podYaml: validPod,
700 writeManifest: true,
701 expectErr: false,
702 },
703 {
704 description: "invalid pod fails to unmarshal",
705 podYaml: invalidPod,
706 writeManifest: true,
707 expectErr: true,
708 },
709 {
710 description: "non-existent file returns error",
711 podYaml: ``,
712 writeManifest: false,
713 expectErr: true,
714 },
715 }
716
717 for _, rt := range tests {
718 t.Run(rt.description, func(t *testing.T) {
719 tmpdir := testutil.SetupTempDir(t)
720 defer os.RemoveAll(tmpdir)
721
722 manifestPath := filepath.Join(tmpdir, "pod.yaml")
723 if rt.writeManifest {
724 err := os.WriteFile(manifestPath, []byte(rt.podYaml), 0644)
725 if err != nil {
726 t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err)
727 }
728 }
729
730 _, actualErr := ReadStaticPodFromDisk(manifestPath)
731 if (actualErr != nil) != rt.expectErr {
732 t.Errorf(
733 "ReadStaticPodFromDisk failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
734 rt.description,
735 rt.expectErr,
736 (actualErr != nil),
737 actualErr,
738 )
739 }
740 })
741 }
742 }
743
744 func TestManifestFilesAreEqual(t *testing.T) {
745 var tests = []struct {
746 description string
747 podYamls []string
748 expectedResult bool
749 expectedDiff string
750 expectErr bool
751 }{
752 {
753 description: "manifests are equal",
754 podYamls: []string{validPod, validPod},
755 expectedResult: true,
756 expectErr: false,
757 },
758 {
759 description: "manifests are equal, ignore different fields order",
760 podYamls: []string{validPod, validPodWithDifferentFieldsOrder},
761 expectedResult: true,
762 expectErr: false,
763 },
764 {
765 description: "manifests are not equal",
766 podYamls: []string{validPod, validPod2},
767 expectedResult: false,
768 expectErr: false,
769 expectedDiff: `@@ -12 +12 @@
770 - - image: gcr.io/google_containers/etcd-amd64:3.1.11
771 + - image: gcr.io/google_containers/etcd-amd64:3.1.12
772 `,
773 },
774 {
775 description: "manifests are not equal for adding new defaults",
776 podYamls: []string{validPod, invalidWithDefaultFields},
777 expectedResult: false,
778 expectErr: false,
779 expectedDiff: `@@ -14,0 +15 @@
780 + restartPolicy: Always
781 `,
782 },
783 {
784 description: "first manifest doesn't exist",
785 podYamls: []string{validPod, ""},
786 expectedResult: false,
787 expectErr: true,
788 },
789 {
790 description: "second manifest doesn't exist",
791 podYamls: []string{"", validPod},
792 expectedResult: false,
793 expectErr: true,
794 },
795 }
796
797 for _, rt := range tests {
798 t.Run(rt.description, func(t *testing.T) {
799 tmpdir := testutil.SetupTempDir(t)
800 defer os.RemoveAll(tmpdir)
801
802
803 for i := 0; i < 2; i++ {
804 if rt.podYamls[i] != "" {
805 manifestPath := filepath.Join(tmpdir, strconv.Itoa(i)+".yaml")
806 err := os.WriteFile(manifestPath, []byte(rt.podYamls[i]), 0644)
807 if err != nil {
808 t.Fatalf("Failed to write manifest file\n%s\n\tfatal error: %v", rt.description, err)
809 }
810 }
811 }
812
813
814 result, diff, actualErr := ManifestFilesAreEqual(filepath.Join(tmpdir, "0.yaml"), filepath.Join(tmpdir, "1.yaml"))
815 if result != rt.expectedResult {
816 t.Errorf(
817 "ManifestFilesAreEqual failed\n%s\nexpected result: %t\nactual result: %t",
818 rt.description,
819 rt.expectedResult,
820 result,
821 )
822 }
823 if (actualErr != nil) != rt.expectErr {
824 t.Errorf(
825 "ManifestFilesAreEqual failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
826 rt.description,
827 rt.expectErr,
828 (actualErr != nil),
829 actualErr,
830 )
831 }
832 if !strings.Contains(diff, rt.expectedDiff) {
833 t.Errorf(
834 "ManifestFilesAreEqual diff doesn't expected\n%s\n\texpected diff: %s\n\tactual diff: %s",
835 rt.description,
836 rt.expectedDiff,
837 diff,
838 )
839 }
840 })
841 }
842 }
843
844 func TestPatchStaticPod(t *testing.T) {
845 type file struct {
846 name string
847 data string
848 }
849
850 tests := []struct {
851 name string
852 files []*file
853 pod *v1.Pod
854 expectedPod *v1.Pod
855 expectedError bool
856 }{
857 {
858 name: "valid: patch a kube-apiserver target using a couple of ordered patches",
859 pod: &v1.Pod{
860 ObjectMeta: metav1.ObjectMeta{
861 Name: "kube-apiserver",
862 Namespace: "foo",
863 },
864 },
865 expectedPod: &v1.Pod{
866 ObjectMeta: metav1.ObjectMeta{
867 Name: "kube-apiserver",
868 Namespace: "bar2",
869 },
870 },
871 files: []*file{
872 {
873 name: "kube-apiserver1+merge.json",
874 data: `{"metadata":{"namespace":"bar2"}}`,
875 },
876 {
877 name: "kube-apiserver0+json.json",
878 data: `[{"op": "replace", "path": "/metadata/namespace", "value": "bar1"}]`,
879 },
880 },
881 },
882 {
883 name: "invalid: unknown patch target name",
884 pod: &v1.Pod{
885 ObjectMeta: metav1.ObjectMeta{
886 Name: "foo",
887 Namespace: "bar",
888 },
889 },
890 expectedError: true,
891 },
892 }
893
894 for _, tc := range tests {
895 t.Run(tc.name, func(t *testing.T) {
896 tempDir, err := os.MkdirTemp("", "patch-files")
897 if err != nil {
898 t.Fatal(err)
899 }
900 defer os.RemoveAll(tempDir)
901
902 for _, file := range tc.files {
903 filePath := filepath.Join(tempDir, file.name)
904 err := os.WriteFile(filePath, []byte(file.data), 0644)
905 if err != nil {
906 t.Fatalf("could not write temporary file %q", filePath)
907 }
908 }
909
910 pod, err := PatchStaticPod(tc.pod, tempDir, io.Discard)
911 if (err != nil) != tc.expectedError {
912 t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err)
913 }
914 if err != nil {
915 return
916 }
917
918 if tc.expectedPod.String() != pod.String() {
919 t.Fatalf("expected object:\n%s\ngot:\n%s", tc.expectedPod.String(), pod.String())
920 }
921 })
922 }
923 }
924
View as plain text