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