...

Source file src/k8s.io/kubernetes/pkg/volume/util/util_test.go

Documentation: k8s.io/kubernetes/pkg/volume/util

     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  }
  1245  

View as plain text