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