k8s.io/kubernetes@v1.29.3/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 "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 // no error expected 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 // Normal operation, no truncate 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 // Truncate trailing "6789-dynamic" 273 prefix := strings.Repeat("0123456789", 9) // 90 characters prefix + 8 chars. of "-dynamic" 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 // Truncate really long cluster name 281 prefix = strings.Repeat("0123456789", 1000) // 10000 characters prefix 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 // No SELinux context, will be inherited from PodSecurityContext 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 }