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  }