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 }