1
16
17 package util
18
19 import (
20 "os"
21 "reflect"
22 "runtime"
23 "strings"
24 "testing"
25
26 "github.com/google/go-cmp/cmp"
27 v1 "k8s.io/api/core/v1"
28 "k8s.io/apimachinery/pkg/api/resource"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/util/sets"
31 utilfeature "k8s.io/apiserver/pkg/util/feature"
32 featuregatetesting "k8s.io/component-base/featuregate/testing"
33 _ "k8s.io/kubernetes/pkg/apis/core/install"
34 "k8s.io/kubernetes/pkg/features"
35 "k8s.io/kubernetes/pkg/util/slice"
36 "k8s.io/kubernetes/pkg/volume"
37 utilptr "k8s.io/utils/pointer"
38 )
39
40 func TestLoadPodFromFile(t *testing.T) {
41 tests := []struct {
42 name string
43 content string
44 expectError bool
45 }{
46 {
47 "yaml",
48 `
49 apiVersion: v1
50 kind: Pod
51 metadata:
52 name: testpod
53 spec:
54 containers:
55 - image: registry.k8s.io/busybox
56 `,
57 false,
58 },
59
60 {
61 "json",
62 `
63 {
64 "apiVersion": "v1",
65 "kind": "Pod",
66 "metadata": {
67 "name": "testpod"
68 },
69 "spec": {
70 "containers": [
71 {
72 "image": "registry.k8s.io/busybox"
73 }
74 ]
75 }
76 }`,
77 false,
78 },
79
80 {
81 "invalid pod",
82 `
83 apiVersion: v1
84 kind: Pod
85 metadata:
86 name: testpod
87 spec:
88 - image: registry.k8s.io/busybox
89 `,
90 true,
91 },
92 }
93
94 for _, test := range tests {
95 tempFile, err := os.CreateTemp("", "podfile")
96 defer os.Remove(tempFile.Name())
97 if err != nil {
98 t.Fatalf("cannot create temporary file: %v", err)
99 }
100 if _, err = tempFile.Write([]byte(test.content)); err != nil {
101 t.Fatalf("cannot save temporary file: %v", err)
102 }
103 if err = tempFile.Close(); err != nil {
104 t.Fatalf("cannot close temporary file: %v", err)
105 }
106
107 pod, err := LoadPodFromFile(tempFile.Name())
108 if test.expectError {
109 if err == nil {
110 t.Errorf("test %q expected error, got nil", test.name)
111 }
112 } else {
113
114 if err != nil {
115 t.Errorf("error loading pod %q: %v", test.name, err)
116 }
117 if pod == nil {
118 t.Errorf("test %q expected pod, got nil", test.name)
119 }
120 }
121 }
122 }
123
124 func TestCalculateTimeoutForVolume(t *testing.T) {
125 pv := &v1.PersistentVolume{
126 Spec: v1.PersistentVolumeSpec{
127 Capacity: v1.ResourceList{
128 v1.ResourceName(v1.ResourceStorage): resource.MustParse("500M"),
129 },
130 },
131 }
132
133 timeout := CalculateTimeoutForVolume(50, 30, pv)
134 if timeout != 50 {
135 t.Errorf("Expected 50 for timeout but got %v", timeout)
136 }
137
138 pv.Spec.Capacity[v1.ResourceStorage] = resource.MustParse("2Gi")
139 timeout = CalculateTimeoutForVolume(50, 30, pv)
140 if timeout != 60 {
141 t.Errorf("Expected 60 for timeout but got %v", timeout)
142 }
143
144 pv.Spec.Capacity[v1.ResourceStorage] = resource.MustParse("150Gi")
145 timeout = CalculateTimeoutForVolume(50, 30, pv)
146 if timeout != 4500 {
147 t.Errorf("Expected 4500 for timeout but got %v", timeout)
148 }
149 }
150
151 func TestFsUserFrom(t *testing.T) {
152 tests := []struct {
153 desc string
154 pod *v1.Pod
155 wantFsUser *int64
156 }{
157 {
158 desc: "no runAsUser specified",
159 pod: &v1.Pod{
160 Spec: v1.PodSpec{},
161 },
162 wantFsUser: nil,
163 },
164 {
165 desc: "some have runAsUser specified",
166 pod: &v1.Pod{
167 Spec: v1.PodSpec{
168 SecurityContext: &v1.PodSecurityContext{},
169 InitContainers: []v1.Container{
170 {
171 SecurityContext: &v1.SecurityContext{
172 RunAsUser: utilptr.Int64Ptr(1000),
173 },
174 },
175 },
176 Containers: []v1.Container{
177 {
178 SecurityContext: &v1.SecurityContext{
179 RunAsUser: utilptr.Int64Ptr(1000),
180 },
181 },
182 {
183 SecurityContext: &v1.SecurityContext{},
184 },
185 },
186 },
187 },
188 wantFsUser: nil,
189 },
190 {
191 desc: "all have runAsUser specified but not the same",
192 pod: &v1.Pod{
193 Spec: v1.PodSpec{
194 SecurityContext: &v1.PodSecurityContext{},
195 InitContainers: []v1.Container{
196 {
197 SecurityContext: &v1.SecurityContext{
198 RunAsUser: utilptr.Int64Ptr(999),
199 },
200 },
201 },
202 Containers: []v1.Container{
203 {
204 SecurityContext: &v1.SecurityContext{
205 RunAsUser: utilptr.Int64Ptr(1000),
206 },
207 },
208 {
209 SecurityContext: &v1.SecurityContext{
210 RunAsUser: utilptr.Int64Ptr(1000),
211 },
212 },
213 },
214 },
215 },
216 wantFsUser: nil,
217 },
218 {
219 desc: "all have runAsUser specified and the same",
220 pod: &v1.Pod{
221 Spec: v1.PodSpec{
222 SecurityContext: &v1.PodSecurityContext{},
223 InitContainers: []v1.Container{
224 {
225 SecurityContext: &v1.SecurityContext{
226 RunAsUser: utilptr.Int64Ptr(1000),
227 },
228 },
229 },
230 Containers: []v1.Container{
231 {
232 SecurityContext: &v1.SecurityContext{
233 RunAsUser: utilptr.Int64Ptr(1000),
234 },
235 },
236 {
237 SecurityContext: &v1.SecurityContext{
238 RunAsUser: utilptr.Int64Ptr(1000),
239 },
240 },
241 },
242 },
243 },
244 wantFsUser: utilptr.Int64Ptr(1000),
245 },
246 }
247
248 for _, test := range tests {
249 t.Run(test.desc, func(t *testing.T) {
250 fsUser := FsUserFrom(test.pod)
251 if fsUser == nil && test.wantFsUser != nil {
252 t.Errorf("FsUserFrom(%v) = %v, want %d", test.pod, fsUser, *test.wantFsUser)
253 }
254 if fsUser != nil && test.wantFsUser == nil {
255 t.Errorf("FsUserFrom(%v) = %d, want %v", test.pod, *fsUser, test.wantFsUser)
256 }
257 if fsUser != nil && test.wantFsUser != nil && *fsUser != *test.wantFsUser {
258 t.Errorf("FsUserFrom(%v) = %d, want %d", test.pod, *fsUser, *test.wantFsUser)
259 }
260 })
261 }
262 }
263
264 func TestGenerateVolumeName(t *testing.T) {
265
266
267 v1 := GenerateVolumeName("kubernetes", "pv-cinder-abcde", 255)
268 if v1 != "kubernetes-dynamic-pv-cinder-abcde" {
269 t.Errorf("Expected kubernetes-dynamic-pv-cinder-abcde, got %s", v1)
270 }
271
272
273 prefix := strings.Repeat("0123456789", 9)
274 v2 := GenerateVolumeName(prefix, "pv-cinder-abcde", 100)
275 expect := prefix[:84] + "-pv-cinder-abcde"
276 if v2 != expect {
277 t.Errorf("Expected %s, got %s", expect, v2)
278 }
279
280
281 prefix = strings.Repeat("0123456789", 1000)
282 v3 := GenerateVolumeName(prefix, "pv-cinder-abcde", 100)
283 if v3 != expect {
284 t.Errorf("Expected %s, got %s", expect, v3)
285 }
286 }
287
288 func TestHasMountRefs(t *testing.T) {
289 testCases := map[string]struct {
290 mountPath string
291 mountRefs []string
292 expected bool
293 }{
294 "plugin mounts only": {
295 mountPath: "/var/lib/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
296 mountRefs: []string{
297 "/home/somewhere/var/lib/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
298 "/var/lib/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
299 "/mnt/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
300 "/mnt/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
301 },
302 expected: false,
303 },
304 "extra local mount": {
305 mountPath: "/var/lib/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
306 mountRefs: []string{
307 "/home/somewhere/var/lib/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
308 "/local/data/kubernetes.io/some-plugin/mounts/volume-XXXX",
309 "/mnt/kubelet/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
310 "/mnt/plugins/kubernetes.io/some-plugin/mounts/volume-XXXX",
311 },
312 expected: true,
313 },
314 }
315 for name, test := range testCases {
316 actual := HasMountRefs(test.mountPath, test.mountRefs)
317 if actual != test.expected {
318 t.Errorf("for %s expected %v but got %v", name, test.expected, actual)
319 }
320 }
321 }
322
323 func TestMountOptionFromSpec(t *testing.T) {
324 scenarios := map[string]struct {
325 volume *volume.Spec
326 expectedMountList []string
327 systemOptions []string
328 }{
329 "volume-with-mount-options": {
330 volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
331 PersistentVolumeSource: v1.PersistentVolumeSource{
332 NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
333 },
334 }),
335 expectedMountList: []string{"ro", "nfsvers=3"},
336 systemOptions: nil,
337 },
338 "volume-with-bad-mount-options": {
339 volume: createVolumeSpecWithMountOption("good-mount-opts", "", v1.PersistentVolumeSpec{
340 PersistentVolumeSource: v1.PersistentVolumeSource{
341 NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
342 },
343 }),
344 expectedMountList: []string{},
345 systemOptions: nil,
346 },
347 "vol-with-sys-opts": {
348 volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
349 PersistentVolumeSource: v1.PersistentVolumeSource{
350 NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
351 },
352 }),
353 expectedMountList: []string{"ro", "nfsvers=3", "fsid=100", "hard"},
354 systemOptions: []string{"fsid=100", "hard"},
355 },
356 "vol-with-sys-opts-with-dup": {
357 volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
358 PersistentVolumeSource: v1.PersistentVolumeSource{
359 NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
360 },
361 }),
362 expectedMountList: []string{"ro", "nfsvers=3", "fsid=100"},
363 systemOptions: []string{"fsid=100", "ro"},
364 },
365 }
366
367 for name, scenario := range scenarios {
368 mountOptions := MountOptionFromSpec(scenario.volume, scenario.systemOptions...)
369 if !reflect.DeepEqual(slice.SortStrings(mountOptions), slice.SortStrings(scenario.expectedMountList)) {
370 t.Errorf("for %s expected mount options : %v got %v", name, scenario.expectedMountList, mountOptions)
371 }
372 }
373 }
374
375 func createVolumeSpecWithMountOption(name string, mountOptions string, spec v1.PersistentVolumeSpec) *volume.Spec {
376 annotations := map[string]string{
377 v1.MountOptionAnnotation: mountOptions,
378 }
379 objMeta := metav1.ObjectMeta{
380 Name: name,
381 Annotations: annotations,
382 }
383
384 pv := &v1.PersistentVolume{
385 ObjectMeta: objMeta,
386 Spec: spec,
387 }
388 return &volume.Spec{PersistentVolume: pv}
389 }
390
391 func TestGetWindowsPath(t *testing.T) {
392 tests := []struct {
393 path string
394 expectedPath string
395 }{
396 {
397 path: `/var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4/volumes/kubernetes.io~disk`,
398 expectedPath: `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`,
399 },
400 {
401 path: `\var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`,
402 expectedPath: `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`,
403 },
404 {
405 path: `/`,
406 expectedPath: `c:\`,
407 },
408 {
409 path: ``,
410 expectedPath: ``,
411 },
412 }
413
414 for _, test := range tests {
415 result := GetWindowsPath(test.path)
416 if result != test.expectedPath {
417 t.Errorf("GetWindowsPath(%v) returned (%v), want (%v)", test.path, result, test.expectedPath)
418 }
419 }
420 }
421
422 func TestIsWindowsUNCPath(t *testing.T) {
423 tests := []struct {
424 goos string
425 path string
426 isUNCPath bool
427 }{
428 {
429 goos: "linux",
430 path: `/usr/bin`,
431 isUNCPath: false,
432 },
433 {
434 goos: "linux",
435 path: `\\.\pipe\foo`,
436 isUNCPath: false,
437 },
438 {
439 goos: "windows",
440 path: `C:\foo`,
441 isUNCPath: false,
442 },
443 {
444 goos: "windows",
445 path: `\\server\share\foo`,
446 isUNCPath: true,
447 },
448 {
449 goos: "windows",
450 path: `\\?\server\share`,
451 isUNCPath: true,
452 },
453 {
454 goos: "windows",
455 path: `\\?\c:\`,
456 isUNCPath: true,
457 },
458 {
459 goos: "windows",
460 path: `\\.\pipe\valid_pipe`,
461 isUNCPath: true,
462 },
463 }
464
465 for _, test := range tests {
466 result := IsWindowsUNCPath(test.goos, test.path)
467 if result != test.isUNCPath {
468 t.Errorf("IsWindowsUNCPath(%v) returned (%v), expected (%v)", test.path, result, test.isUNCPath)
469 }
470 }
471 }
472
473 func TestIsWindowsLocalPath(t *testing.T) {
474 tests := []struct {
475 goos string
476 path string
477 isWindowsLocalPath bool
478 }{
479 {
480 goos: "linux",
481 path: `/usr/bin`,
482 isWindowsLocalPath: false,
483 },
484 {
485 goos: "linux",
486 path: `\\.\pipe\foo`,
487 isWindowsLocalPath: false,
488 },
489 {
490 goos: "windows",
491 path: `C:\foo`,
492 isWindowsLocalPath: false,
493 },
494 {
495 goos: "windows",
496 path: `:\foo`,
497 isWindowsLocalPath: false,
498 },
499 {
500 goos: "windows",
501 path: `X:\foo`,
502 isWindowsLocalPath: false,
503 },
504 {
505 goos: "windows",
506 path: `\\server\share\foo`,
507 isWindowsLocalPath: false,
508 },
509 {
510 goos: "windows",
511 path: `\\?\server\share`,
512 isWindowsLocalPath: false,
513 },
514 {
515 goos: "windows",
516 path: `\\?\c:\`,
517 isWindowsLocalPath: false,
518 },
519 {
520 goos: "windows",
521 path: `\\.\pipe\valid_pipe`,
522 isWindowsLocalPath: false,
523 },
524 {
525 goos: "windows",
526 path: `foo`,
527 isWindowsLocalPath: false,
528 },
529 {
530 goos: "windows",
531 path: `:foo`,
532 isWindowsLocalPath: false,
533 },
534 {
535 goos: "windows",
536 path: `\foo`,
537 isWindowsLocalPath: true,
538 },
539 {
540 goos: "windows",
541 path: `\foo\bar`,
542 isWindowsLocalPath: true,
543 },
544 {
545 goos: "windows",
546 path: `/foo`,
547 isWindowsLocalPath: true,
548 },
549 {
550 goos: "windows",
551 path: `/foo/bar`,
552 isWindowsLocalPath: true,
553 },
554 }
555
556 for _, test := range tests {
557 result := IsWindowsLocalPath(test.goos, test.path)
558 if result != test.isWindowsLocalPath {
559 t.Errorf("isWindowsLocalPath(%v) returned (%v), expected (%v)", test.path, result, test.isWindowsLocalPath)
560 }
561 }
562 }
563
564 func TestMakeAbsolutePath(t *testing.T) {
565 tests := []struct {
566 goos string
567 path string
568 expectedPath string
569 name string
570 }{
571 {
572 goos: "linux",
573 path: "non-absolute/path",
574 expectedPath: "/non-absolute/path",
575 name: "linux non-absolute path",
576 },
577 {
578 goos: "linux",
579 path: "/absolute/path",
580 expectedPath: "/absolute/path",
581 name: "linux absolute path",
582 },
583 {
584 goos: "windows",
585 path: "some\\path",
586 expectedPath: "c:\\some\\path",
587 name: "basic windows",
588 },
589 {
590 goos: "windows",
591 path: "/some/path",
592 expectedPath: "c:/some/path",
593 name: "linux path on windows",
594 },
595 {
596 goos: "windows",
597 path: "\\some\\path",
598 expectedPath: "c:\\some\\path",
599 name: "windows path no drive",
600 },
601 {
602 goos: "windows",
603 path: "\\:\\some\\path",
604 expectedPath: "\\:\\some\\path",
605 name: "windows path with colon",
606 },
607 }
608 for _, test := range tests {
609 if runtime.GOOS == test.goos {
610 path := MakeAbsolutePath(test.goos, test.path)
611 if path != test.expectedPath {
612 t.Errorf("[%s] Expected %s saw %s", test.name, test.expectedPath, path)
613 }
614 }
615 }
616 }
617
618 func TestGetPodVolumeNames(t *testing.T) {
619 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SELinuxMountReadWriteOncePod, true)()
620 tests := []struct {
621 name string
622 pod *v1.Pod
623 expectedMounts sets.String
624 expectedDevices sets.String
625 expectedSELinuxContexts map[string][]*v1.SELinuxOptions
626 }{
627 {
628 name: "empty pod",
629 pod: &v1.Pod{
630 Spec: v1.PodSpec{},
631 },
632 expectedMounts: sets.NewString(),
633 expectedDevices: sets.NewString(),
634 },
635 {
636 name: "pod with volumes",
637 pod: &v1.Pod{
638 Spec: v1.PodSpec{
639 Containers: []v1.Container{
640 {
641 Name: "container",
642 VolumeMounts: []v1.VolumeMount{
643 {
644 Name: "vol1",
645 },
646 {
647 Name: "vol2",
648 },
649 },
650 VolumeDevices: []v1.VolumeDevice{
651 {
652 Name: "vol3",
653 },
654 {
655 Name: "vol4",
656 },
657 },
658 },
659 },
660 Volumes: []v1.Volume{
661 {
662 Name: "vol1",
663 },
664 {
665 Name: "vol2",
666 },
667 {
668 Name: "vol3",
669 },
670 {
671 Name: "vol4",
672 },
673 },
674 },
675 },
676 expectedMounts: sets.NewString("vol1", "vol2"),
677 expectedDevices: sets.NewString("vol3", "vol4"),
678 },
679 {
680 name: "pod with init containers",
681 pod: &v1.Pod{
682 Spec: v1.PodSpec{
683 InitContainers: []v1.Container{
684 {
685 Name: "initContainer",
686 VolumeMounts: []v1.VolumeMount{
687 {
688 Name: "vol1",
689 },
690 {
691 Name: "vol2",
692 },
693 },
694 VolumeDevices: []v1.VolumeDevice{
695 {
696 Name: "vol3",
697 },
698 {
699 Name: "vol4",
700 },
701 },
702 },
703 },
704 Volumes: []v1.Volume{
705 {
706 Name: "vol1",
707 },
708 {
709 Name: "vol2",
710 },
711 {
712 Name: "vol3",
713 },
714 {
715 Name: "vol4",
716 },
717 },
718 },
719 },
720 expectedMounts: sets.NewString("vol1", "vol2"),
721 expectedDevices: sets.NewString("vol3", "vol4"),
722 },
723 {
724 name: "pod with multiple containers",
725 pod: &v1.Pod{
726 Spec: v1.PodSpec{
727 InitContainers: []v1.Container{
728 {
729 Name: "initContainer1",
730 VolumeMounts: []v1.VolumeMount{
731 {
732 Name: "vol1",
733 },
734 },
735 },
736 {
737 Name: "initContainer2",
738 VolumeDevices: []v1.VolumeDevice{
739 {
740 Name: "vol2",
741 },
742 },
743 },
744 },
745 Containers: []v1.Container{
746 {
747 Name: "container1",
748 VolumeMounts: []v1.VolumeMount{
749 {
750 Name: "vol3",
751 },
752 },
753 },
754 {
755 Name: "container2",
756 VolumeDevices: []v1.VolumeDevice{
757 {
758 Name: "vol4",
759 },
760 },
761 },
762 },
763 Volumes: []v1.Volume{
764 {
765 Name: "vol1",
766 },
767 {
768 Name: "vol2",
769 },
770 {
771 Name: "vol3",
772 },
773 {
774 Name: "vol4",
775 },
776 },
777 },
778 },
779 expectedMounts: sets.NewString("vol1", "vol3"),
780 expectedDevices: sets.NewString("vol2", "vol4"),
781 },
782 {
783 name: "pod with ephemeral containers",
784 pod: &v1.Pod{
785 Spec: v1.PodSpec{
786 Containers: []v1.Container{
787 {
788 Name: "container1",
789 VolumeMounts: []v1.VolumeMount{
790 {
791 Name: "vol1",
792 },
793 },
794 },
795 },
796 EphemeralContainers: []v1.EphemeralContainer{
797 {
798 EphemeralContainerCommon: v1.EphemeralContainerCommon{
799 Name: "debugger",
800 VolumeMounts: []v1.VolumeMount{
801 {
802 Name: "vol1",
803 },
804 {
805 Name: "vol2",
806 },
807 },
808 },
809 },
810 },
811 Volumes: []v1.Volume{
812 {
813 Name: "vol1",
814 },
815 {
816 Name: "vol2",
817 },
818 },
819 },
820 },
821 expectedMounts: sets.NewString("vol1", "vol2"),
822 expectedDevices: sets.NewString(),
823 },
824 {
825 name: "pod with SELinuxOptions",
826 pod: &v1.Pod{
827 Spec: v1.PodSpec{
828 SecurityContext: &v1.PodSecurityContext{
829 SELinuxOptions: &v1.SELinuxOptions{
830 Type: "global_context_t",
831 Level: "s0:c1,c2",
832 },
833 },
834 InitContainers: []v1.Container{
835 {
836 Name: "initContainer1",
837 SecurityContext: &v1.SecurityContext{
838 SELinuxOptions: &v1.SELinuxOptions{
839 Type: "initcontainer1_context_t",
840 Level: "s0:c3,c4",
841 },
842 },
843 VolumeMounts: []v1.VolumeMount{
844 {
845 Name: "vol1",
846 },
847 },
848 },
849 },
850 Containers: []v1.Container{
851 {
852 Name: "container1",
853 SecurityContext: &v1.SecurityContext{
854 SELinuxOptions: &v1.SELinuxOptions{
855 Type: "container1_context_t",
856 Level: "s0:c5,c6",
857 },
858 },
859 VolumeMounts: []v1.VolumeMount{
860 {
861 Name: "vol1",
862 },
863 {
864 Name: "vol2",
865 },
866 },
867 },
868 {
869 Name: "container2",
870
871 VolumeMounts: []v1.VolumeMount{
872 {
873 Name: "vol2",
874 },
875 {
876 Name: "vol3",
877 },
878 },
879 },
880 },
881 Volumes: []v1.Volume{
882 {
883 Name: "vol1",
884 },
885 {
886 Name: "vol2",
887 },
888 {
889 Name: "vol3",
890 },
891 },
892 },
893 },
894 expectedMounts: sets.NewString("vol1", "vol2", "vol3"),
895 expectedSELinuxContexts: map[string][]*v1.SELinuxOptions{
896 "vol1": {
897 {
898 Type: "initcontainer1_context_t",
899 Level: "s0:c3,c4",
900 },
901 {
902 Type: "container1_context_t",
903 Level: "s0:c5,c6",
904 },
905 },
906 "vol2": {
907 {
908 Type: "container1_context_t",
909 Level: "s0:c5,c6",
910 },
911 {
912 Type: "global_context_t",
913 Level: "s0:c1,c2",
914 },
915 },
916 "vol3": {
917 {
918 Type: "global_context_t",
919 Level: "s0:c1,c2",
920 },
921 },
922 },
923 },
924 }
925
926 for _, test := range tests {
927 t.Run(test.name, func(t *testing.T) {
928 mounts, devices, contexts := GetPodVolumeNames(test.pod)
929 if !mounts.Equal(test.expectedMounts) {
930 t.Errorf("Expected mounts: %q, got %q", mounts.List(), test.expectedMounts.List())
931 }
932 if !devices.Equal(test.expectedDevices) {
933 t.Errorf("Expected devices: %q, got %q", devices.List(), test.expectedDevices.List())
934 }
935 if len(contexts) == 0 {
936 contexts = nil
937 }
938 if !reflect.DeepEqual(test.expectedSELinuxContexts, contexts) {
939 t.Errorf("Expected SELinuxContexts: %+v\ngot: %+v", test.expectedSELinuxContexts, contexts)
940 }
941 })
942 }
943 }
944
945 func TestGetPersistentVolumeNodeNames(t *testing.T) {
946 tests := []struct {
947 name string
948 pv *v1.PersistentVolume
949 expectedNodeNames []string
950 }{
951 {
952 name: "nil PV",
953 pv: nil,
954 },
955 {
956 name: "PV missing node affinity",
957 pv: &v1.PersistentVolume{
958 ObjectMeta: metav1.ObjectMeta{
959 Name: "foo",
960 },
961 },
962 },
963 {
964 name: "PV node affinity missing required",
965 pv: &v1.PersistentVolume{
966 ObjectMeta: metav1.ObjectMeta{
967 Name: "foo",
968 },
969 Spec: v1.PersistentVolumeSpec{
970 NodeAffinity: &v1.VolumeNodeAffinity{},
971 },
972 },
973 },
974 {
975 name: "PV node affinity required zero selector terms",
976 pv: &v1.PersistentVolume{
977 ObjectMeta: metav1.ObjectMeta{
978 Name: "foo",
979 },
980 Spec: v1.PersistentVolumeSpec{
981 NodeAffinity: &v1.VolumeNodeAffinity{
982 Required: &v1.NodeSelector{
983 NodeSelectorTerms: []v1.NodeSelectorTerm{},
984 },
985 },
986 },
987 },
988 expectedNodeNames: []string{},
989 },
990 {
991 name: "PV node affinity required zero selector terms",
992 pv: &v1.PersistentVolume{
993 ObjectMeta: metav1.ObjectMeta{
994 Name: "foo",
995 },
996 Spec: v1.PersistentVolumeSpec{
997 NodeAffinity: &v1.VolumeNodeAffinity{
998 Required: &v1.NodeSelector{
999 NodeSelectorTerms: []v1.NodeSelectorTerm{},
1000 },
1001 },
1002 },
1003 },
1004 expectedNodeNames: []string{},
1005 },
1006 {
1007 name: "PV node affinity required zero match expressions",
1008 pv: &v1.PersistentVolume{
1009 ObjectMeta: metav1.ObjectMeta{
1010 Name: "foo",
1011 },
1012 Spec: v1.PersistentVolumeSpec{
1013 NodeAffinity: &v1.VolumeNodeAffinity{
1014 Required: &v1.NodeSelector{
1015 NodeSelectorTerms: []v1.NodeSelectorTerm{
1016 {
1017 MatchExpressions: []v1.NodeSelectorRequirement{},
1018 },
1019 },
1020 },
1021 },
1022 },
1023 },
1024 expectedNodeNames: []string{},
1025 },
1026 {
1027 name: "PV node affinity required multiple match expressions",
1028 pv: &v1.PersistentVolume{
1029 ObjectMeta: metav1.ObjectMeta{
1030 Name: "foo",
1031 },
1032 Spec: v1.PersistentVolumeSpec{
1033 NodeAffinity: &v1.VolumeNodeAffinity{
1034 Required: &v1.NodeSelector{
1035 NodeSelectorTerms: []v1.NodeSelectorTerm{
1036 {
1037 MatchExpressions: []v1.NodeSelectorRequirement{
1038 {
1039 Key: "foo",
1040 Operator: v1.NodeSelectorOpIn,
1041 },
1042 {
1043 Key: "bar",
1044 Operator: v1.NodeSelectorOpIn,
1045 },
1046 },
1047 },
1048 },
1049 },
1050 },
1051 },
1052 },
1053 expectedNodeNames: []string{},
1054 },
1055 {
1056 name: "PV node affinity required single match expression with no values",
1057 pv: &v1.PersistentVolume{
1058 ObjectMeta: metav1.ObjectMeta{
1059 Name: "foo",
1060 },
1061 Spec: v1.PersistentVolumeSpec{
1062 NodeAffinity: &v1.VolumeNodeAffinity{
1063 Required: &v1.NodeSelector{
1064 NodeSelectorTerms: []v1.NodeSelectorTerm{
1065 {
1066 MatchExpressions: []v1.NodeSelectorRequirement{
1067 {
1068 Key: v1.LabelHostname,
1069 Operator: v1.NodeSelectorOpIn,
1070 Values: []string{},
1071 },
1072 },
1073 },
1074 },
1075 },
1076 },
1077 },
1078 },
1079 expectedNodeNames: []string{},
1080 },
1081 {
1082 name: "PV node affinity required single match expression with single node",
1083 pv: &v1.PersistentVolume{
1084 ObjectMeta: metav1.ObjectMeta{
1085 Name: "foo",
1086 },
1087 Spec: v1.PersistentVolumeSpec{
1088 NodeAffinity: &v1.VolumeNodeAffinity{
1089 Required: &v1.NodeSelector{
1090 NodeSelectorTerms: []v1.NodeSelectorTerm{
1091 {
1092 MatchExpressions: []v1.NodeSelectorRequirement{
1093 {
1094 Key: v1.LabelHostname,
1095 Operator: v1.NodeSelectorOpIn,
1096 Values: []string{
1097 "node1",
1098 },
1099 },
1100 },
1101 },
1102 },
1103 },
1104 },
1105 },
1106 },
1107 expectedNodeNames: []string{
1108 "node1",
1109 },
1110 },
1111 {
1112 name: "PV node affinity required single match expression with multiple nodes",
1113 pv: &v1.PersistentVolume{
1114 ObjectMeta: metav1.ObjectMeta{
1115 Name: "foo",
1116 },
1117 Spec: v1.PersistentVolumeSpec{
1118 NodeAffinity: &v1.VolumeNodeAffinity{
1119 Required: &v1.NodeSelector{
1120 NodeSelectorTerms: []v1.NodeSelectorTerm{
1121 {
1122 MatchExpressions: []v1.NodeSelectorRequirement{
1123 {
1124 Key: v1.LabelHostname,
1125 Operator: v1.NodeSelectorOpIn,
1126 Values: []string{
1127 "node1",
1128 "node2",
1129 },
1130 },
1131 },
1132 },
1133 },
1134 },
1135 },
1136 },
1137 },
1138 expectedNodeNames: []string{
1139 "node1",
1140 "node2",
1141 },
1142 },
1143 {
1144 name: "PV node affinity required multiple match expressions with multiple nodes",
1145 pv: &v1.PersistentVolume{
1146 ObjectMeta: metav1.ObjectMeta{
1147 Name: "foo",
1148 },
1149 Spec: v1.PersistentVolumeSpec{
1150 NodeAffinity: &v1.VolumeNodeAffinity{
1151 Required: &v1.NodeSelector{
1152 NodeSelectorTerms: []v1.NodeSelectorTerm{
1153 {
1154 MatchExpressions: []v1.NodeSelectorRequirement{
1155 {
1156 Key: "bar",
1157 Operator: v1.NodeSelectorOpIn,
1158 Values: []string{
1159 "node1",
1160 "node2",
1161 },
1162 },
1163 {
1164 Key: v1.LabelHostname,
1165 Operator: v1.NodeSelectorOpIn,
1166 Values: []string{
1167 "node3",
1168 "node4",
1169 },
1170 },
1171 },
1172 },
1173 },
1174 },
1175 },
1176 },
1177 },
1178 expectedNodeNames: []string{
1179 "node3",
1180 "node4",
1181 },
1182 },
1183 {
1184 name: "PV node affinity required multiple node selectors multiple match expressions with multiple nodes",
1185 pv: &v1.PersistentVolume{
1186 ObjectMeta: metav1.ObjectMeta{
1187 Name: "foo",
1188 },
1189 Spec: v1.PersistentVolumeSpec{
1190 NodeAffinity: &v1.VolumeNodeAffinity{
1191 Required: &v1.NodeSelector{
1192 NodeSelectorTerms: []v1.NodeSelectorTerm{
1193 {
1194 MatchExpressions: []v1.NodeSelectorRequirement{
1195 {
1196 Key: v1.LabelHostname,
1197 Operator: v1.NodeSelectorOpIn,
1198 Values: []string{
1199 "node1",
1200 "node2",
1201 },
1202 },
1203 {
1204 Key: v1.LabelHostname,
1205 Operator: v1.NodeSelectorOpIn,
1206 Values: []string{
1207 "node2",
1208 "node3",
1209 },
1210 },
1211 },
1212 },
1213 {
1214 MatchExpressions: []v1.NodeSelectorRequirement{
1215 {
1216 Key: v1.LabelHostname,
1217 Operator: v1.NodeSelectorOpIn,
1218 Values: []string{
1219 "node1",
1220 },
1221 },
1222 },
1223 },
1224 },
1225 },
1226 },
1227 },
1228 },
1229 expectedNodeNames: []string{
1230 "node1",
1231 "node2",
1232 },
1233 },
1234 }
1235
1236 for _, test := range tests {
1237 t.Run(test.name, func(t *testing.T) {
1238 nodeNames := GetLocalPersistentVolumeNodeNames(test.pv)
1239 if diff := cmp.Diff(test.expectedNodeNames, nodeNames); diff != "" {
1240 t.Errorf("Unexpected nodeNames (-want, +got):\n%s", diff)
1241 }
1242 })
1243 }
1244 }
1245
View as plain text