k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/kubelet_pods_linux_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2018 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package kubelet
    21  
    22  import (
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	v1 "k8s.io/api/core/v1"
    27  
    28  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    29  	_ "k8s.io/kubernetes/pkg/apis/core/install"
    30  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    31  	volumetest "k8s.io/kubernetes/pkg/volume/testing"
    32  	"k8s.io/kubernetes/pkg/volume/util/hostutil"
    33  	"k8s.io/kubernetes/pkg/volume/util/subpath"
    34  )
    35  
    36  func TestMakeMounts(t *testing.T) {
    37  	bTrue := true
    38  	propagationHostToContainer := v1.MountPropagationHostToContainer
    39  	propagationBidirectional := v1.MountPropagationBidirectional
    40  	propagationNone := v1.MountPropagationNone
    41  
    42  	testCases := map[string]struct {
    43  		container      v1.Container
    44  		podVolumes     kubecontainer.VolumeMap
    45  		supportsRRO    bool
    46  		expectErr      bool
    47  		expectedErrMsg string
    48  		expectedMounts []kubecontainer.Mount
    49  	}{
    50  		"valid mounts in unprivileged container": {
    51  			podVolumes: kubecontainer.VolumeMap{
    52  				"disk":  kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
    53  				"disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}},
    54  				"disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}},
    55  			},
    56  			container: v1.Container{
    57  				Name: "container1",
    58  				VolumeMounts: []v1.VolumeMount{
    59  					{
    60  						MountPath:        "/etc/hosts",
    61  						Name:             "disk",
    62  						ReadOnly:         false,
    63  						MountPropagation: &propagationHostToContainer,
    64  					},
    65  					{
    66  						MountPath:        "/mnt/path3",
    67  						Name:             "disk",
    68  						ReadOnly:         true,
    69  						MountPropagation: &propagationNone,
    70  					},
    71  					{
    72  						MountPath: "/mnt/path4",
    73  						Name:      "disk4",
    74  						ReadOnly:  false,
    75  					},
    76  					{
    77  						MountPath: "/mnt/path5",
    78  						Name:      "disk5",
    79  						ReadOnly:  false,
    80  					},
    81  				},
    82  			},
    83  			expectedMounts: []kubecontainer.Mount{
    84  				{
    85  					Name:           "disk",
    86  					ContainerPath:  "/etc/hosts",
    87  					HostPath:       "/mnt/disk",
    88  					ReadOnly:       false,
    89  					SELinuxRelabel: false,
    90  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
    91  				},
    92  				{
    93  					Name:           "disk",
    94  					ContainerPath:  "/mnt/path3",
    95  					HostPath:       "/mnt/disk",
    96  					ReadOnly:       true,
    97  					SELinuxRelabel: false,
    98  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
    99  				},
   100  				{
   101  					Name:           "disk4",
   102  					ContainerPath:  "/mnt/path4",
   103  					HostPath:       "/mnt/host",
   104  					ReadOnly:       false,
   105  					SELinuxRelabel: false,
   106  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
   107  				},
   108  				{
   109  					Name:           "disk5",
   110  					ContainerPath:  "/mnt/path5",
   111  					HostPath:       "/var/lib/kubelet/podID/volumes/empty/disk5",
   112  					ReadOnly:       false,
   113  					SELinuxRelabel: false,
   114  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
   115  				},
   116  			},
   117  			expectErr: false,
   118  		},
   119  		"valid mounts in privileged container": {
   120  			podVolumes: kubecontainer.VolumeMap{
   121  				"disk":  kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
   122  				"disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}},
   123  				"disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}},
   124  			},
   125  			container: v1.Container{
   126  				Name: "container1",
   127  				VolumeMounts: []v1.VolumeMount{
   128  					{
   129  						MountPath:        "/etc/hosts",
   130  						Name:             "disk",
   131  						ReadOnly:         false,
   132  						MountPropagation: &propagationBidirectional,
   133  					},
   134  					{
   135  						MountPath:        "/mnt/path3",
   136  						Name:             "disk",
   137  						ReadOnly:         true,
   138  						MountPropagation: &propagationHostToContainer,
   139  					},
   140  					{
   141  						MountPath: "/mnt/path4",
   142  						Name:      "disk4",
   143  						ReadOnly:  false,
   144  					},
   145  				},
   146  				SecurityContext: &v1.SecurityContext{
   147  					Privileged: &bTrue,
   148  				},
   149  			},
   150  			expectedMounts: []kubecontainer.Mount{
   151  				{
   152  					Name:           "disk",
   153  					ContainerPath:  "/etc/hosts",
   154  					HostPath:       "/mnt/disk",
   155  					ReadOnly:       false,
   156  					SELinuxRelabel: false,
   157  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_BIDIRECTIONAL,
   158  				},
   159  				{
   160  					Name:           "disk",
   161  					ContainerPath:  "/mnt/path3",
   162  					HostPath:       "/mnt/disk",
   163  					ReadOnly:       true,
   164  					SELinuxRelabel: false,
   165  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
   166  				},
   167  				{
   168  					Name:           "disk4",
   169  					ContainerPath:  "/mnt/path4",
   170  					HostPath:       "/mnt/host",
   171  					ReadOnly:       false,
   172  					SELinuxRelabel: false,
   173  					Propagation:    runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
   174  				},
   175  			},
   176  			expectErr: false,
   177  		},
   178  		"invalid absolute SubPath": {
   179  			podVolumes: kubecontainer.VolumeMap{
   180  				"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
   181  			},
   182  			container: v1.Container{
   183  				VolumeMounts: []v1.VolumeMount{
   184  					{
   185  						MountPath: "/mnt/path3",
   186  						SubPath:   "/must/not/be/absolute",
   187  						Name:      "disk",
   188  						ReadOnly:  true,
   189  					},
   190  				},
   191  			},
   192  			expectErr:      true,
   193  			expectedErrMsg: "error SubPath `/must/not/be/absolute` must not be an absolute path",
   194  		},
   195  		"invalid SubPath with backsteps": {
   196  			podVolumes: kubecontainer.VolumeMap{
   197  				"disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}},
   198  			},
   199  			container: v1.Container{
   200  				VolumeMounts: []v1.VolumeMount{
   201  					{
   202  						MountPath: "/mnt/path3",
   203  						SubPath:   "no/backsteps/../allowed",
   204  						Name:      "disk",
   205  						ReadOnly:  true,
   206  					},
   207  				},
   208  			},
   209  			expectErr:      true,
   210  			expectedErrMsg: "unable to provision SubPath `no/backsteps/../allowed`: must not contain '..'",
   211  		},
   212  		"volume doesn't exist": {
   213  			podVolumes: kubecontainer.VolumeMap{},
   214  			container: v1.Container{
   215  				VolumeMounts: []v1.VolumeMount{
   216  					{
   217  						MountPath: "/mnt/path3",
   218  						Name:      "disk",
   219  						ReadOnly:  true,
   220  					},
   221  				},
   222  			},
   223  			expectErr:      true,
   224  			expectedErrMsg: "cannot find volume \"disk\" to mount into container \"\"",
   225  		},
   226  		"volume mounter is nil": {
   227  			podVolumes: kubecontainer.VolumeMap{
   228  				"disk": kubecontainer.VolumeInfo{},
   229  			},
   230  			container: v1.Container{
   231  				VolumeMounts: []v1.VolumeMount{
   232  					{
   233  						MountPath: "/mnt/path3",
   234  						Name:      "disk",
   235  						ReadOnly:  true,
   236  					},
   237  				},
   238  			},
   239  			expectErr:      true,
   240  			expectedErrMsg: "cannot find volume \"disk\" to mount into container \"\"",
   241  		},
   242  	}
   243  
   244  	for name, tc := range testCases {
   245  		t.Run(name, func(t *testing.T) {
   246  			fhu := hostutil.NewFakeHostUtil(nil)
   247  			fsp := &subpath.FakeSubpath{}
   248  			pod := v1.Pod{
   249  				Spec: v1.PodSpec{
   250  					HostNetwork: true,
   251  				},
   252  			}
   253  
   254  			mounts, _, err := makeMounts(&pod, "/pod", &tc.container, "fakepodname", "", []string{""}, tc.podVolumes, fhu, fsp, nil, tc.supportsRRO)
   255  
   256  			// validate only the error if we expect an error
   257  			if tc.expectErr {
   258  				if err == nil || err.Error() != tc.expectedErrMsg {
   259  					t.Fatalf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err)
   260  				}
   261  				return
   262  			}
   263  
   264  			// otherwise validate the mounts
   265  			if err != nil {
   266  				t.Fatal(err)
   267  			}
   268  
   269  			assert.Equal(t, tc.expectedMounts, mounts, "mounts of container %+v", tc.container)
   270  		})
   271  	}
   272  }
   273  
   274  func TestMakeBlockVolumes(t *testing.T) {
   275  	testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
   276  	defer testKubelet.Cleanup()
   277  	kubelet := testKubelet.kubelet
   278  	testCases := map[string]struct {
   279  		container       v1.Container
   280  		podVolumes      kubecontainer.VolumeMap
   281  		expectErr       bool
   282  		expectedErrMsg  string
   283  		expectedDevices []kubecontainer.DeviceInfo
   284  	}{
   285  		"valid volumeDevices in container": {
   286  			podVolumes: kubecontainer.VolumeMap{
   287  				"disk1": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/", volName: "sda"}},
   288  				"disk2": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/disk/by-path/", volName: "diskPath"}, ReadOnly: true},
   289  				"disk3": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/disk/by-id/", volName: "diskUuid"}},
   290  				"disk4": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/var/lib/", volName: "rawdisk"}, ReadOnly: true},
   291  			},
   292  			container: v1.Container{
   293  				Name: "container1",
   294  				VolumeDevices: []v1.VolumeDevice{
   295  					{
   296  						DevicePath: "/dev/sda",
   297  						Name:       "disk1",
   298  					},
   299  					{
   300  						DevicePath: "/dev/xvda",
   301  						Name:       "disk2",
   302  					},
   303  					{
   304  						DevicePath: "/dev/xvdb",
   305  						Name:       "disk3",
   306  					},
   307  					{
   308  						DevicePath: "/mnt/rawdisk",
   309  						Name:       "disk4",
   310  					},
   311  				},
   312  			},
   313  			expectedDevices: []kubecontainer.DeviceInfo{
   314  				{
   315  					PathInContainer: "/dev/sda",
   316  					PathOnHost:      "/dev/sda",
   317  					Permissions:     "mrw",
   318  				},
   319  				{
   320  					PathInContainer: "/dev/xvda",
   321  					PathOnHost:      "/dev/disk/by-path/diskPath",
   322  					Permissions:     "r",
   323  				},
   324  				{
   325  					PathInContainer: "/dev/xvdb",
   326  					PathOnHost:      "/dev/disk/by-id/diskUuid",
   327  					Permissions:     "mrw",
   328  				},
   329  				{
   330  					PathInContainer: "/mnt/rawdisk",
   331  					PathOnHost:      "/var/lib/rawdisk",
   332  					Permissions:     "r",
   333  				},
   334  			},
   335  			expectErr: false,
   336  		},
   337  		"invalid absolute Path": {
   338  			podVolumes: kubecontainer.VolumeMap{
   339  				"disk": kubecontainer.VolumeInfo{BlockVolumeMapper: &stubBlockVolume{dirPath: "/dev/", volName: "sda"}},
   340  			},
   341  			container: v1.Container{
   342  				VolumeDevices: []v1.VolumeDevice{
   343  					{
   344  						DevicePath: "must/be/absolute",
   345  						Name:       "disk",
   346  					},
   347  				},
   348  			},
   349  			expectErr:      true,
   350  			expectedErrMsg: "error DevicePath `must/be/absolute` must be an absolute path",
   351  		},
   352  		"volume doesn't exist": {
   353  			podVolumes: kubecontainer.VolumeMap{},
   354  			container: v1.Container{
   355  				VolumeDevices: []v1.VolumeDevice{
   356  					{
   357  						DevicePath: "/dev/sdaa",
   358  						Name:       "disk",
   359  					},
   360  				},
   361  			},
   362  			expectErr:      true,
   363  			expectedErrMsg: "cannot find volume \"disk\" to pass into container \"\"",
   364  		},
   365  		"volume BlockVolumeMapper is nil": {
   366  			podVolumes: kubecontainer.VolumeMap{
   367  				"disk": kubecontainer.VolumeInfo{},
   368  			},
   369  			container: v1.Container{
   370  				VolumeDevices: []v1.VolumeDevice{
   371  					{
   372  						DevicePath: "/dev/sdzz",
   373  						Name:       "disk",
   374  					},
   375  				},
   376  			},
   377  			expectErr:      true,
   378  			expectedErrMsg: "cannot find volume \"disk\" to pass into container \"\"",
   379  		},
   380  	}
   381  
   382  	for name, tc := range testCases {
   383  		t.Run(name, func(t *testing.T) {
   384  			pod := v1.Pod{
   385  				Spec: v1.PodSpec{
   386  					HostNetwork: true,
   387  				},
   388  			}
   389  			blkutil := volumetest.NewBlockVolumePathHandler()
   390  			blkVolumes, err := kubelet.makeBlockVolumes(&pod, &tc.container, tc.podVolumes, blkutil)
   391  			// validate only the error if we expect an error
   392  			if tc.expectErr {
   393  				if err == nil || err.Error() != tc.expectedErrMsg {
   394  					t.Fatalf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err)
   395  				}
   396  				return
   397  			}
   398  			// otherwise validate the devices
   399  			if err != nil {
   400  				t.Fatal(err)
   401  			}
   402  			assert.Equal(t, tc.expectedDevices, blkVolumes, "devices of container %+v", tc.container)
   403  		})
   404  	}
   405  }