1
16
17 package storage
18
19 import (
20 "context"
21 "fmt"
22 "path"
23
24 "github.com/onsi/ginkgo/v2"
25 "github.com/onsi/gomega"
26
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/uuid"
31 "k8s.io/kubernetes/test/e2e/framework"
32 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
33 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
34 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
35 "k8s.io/kubernetes/test/e2e/nodefeature"
36 imageutils "k8s.io/kubernetes/test/utils/image"
37 admissionapi "k8s.io/pod-security-admission/api"
38 )
39
40 const (
41 volumePath = "/test-volume"
42 )
43
44 var (
45 nonRootUID = int64(1001)
46 )
47
48 var _ = SIGDescribe("EmptyDir volumes", func() {
49 f := framework.NewDefaultFramework("emptydir")
50 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
51
52 f.Context("when FSGroup is specified [LinuxOnly]", nodefeature.FSGroup, func() {
53
54 ginkgo.BeforeEach(func() {
55
56 e2eskipper.SkipIfNodeOSDistroIs("windows")
57 })
58
59 ginkgo.It("new files should be created with FSGroup ownership when container is root", func(ctx context.Context) {
60 doTestSetgidFSGroup(ctx, f, 0, v1.StorageMediumMemory)
61 })
62
63 ginkgo.It("new files should be created with FSGroup ownership when container is non-root", func(ctx context.Context) {
64 doTestSetgidFSGroup(ctx, f, nonRootUID, v1.StorageMediumMemory)
65 })
66
67 ginkgo.It("nonexistent volume subPath should have the correct mode and owner using FSGroup", func(ctx context.Context) {
68 doTestSubPathFSGroup(ctx, f, nonRootUID, v1.StorageMediumMemory)
69 })
70
71 ginkgo.It("files with FSGroup ownership should support (root,0644,tmpfs)", func(ctx context.Context) {
72 doTest0644FSGroup(ctx, f, 0, v1.StorageMediumMemory)
73 })
74
75 ginkgo.It("volume on default medium should have the correct mode using FSGroup", func(ctx context.Context) {
76 doTestVolumeModeFSGroup(ctx, f, 0, v1.StorageMediumDefault)
77 })
78
79 ginkgo.It("volume on tmpfs should have the correct mode using FSGroup", func(ctx context.Context) {
80 doTestVolumeModeFSGroup(ctx, f, 0, v1.StorageMediumMemory)
81 })
82 })
83
84
90 framework.ConformanceIt("volume on tmpfs should have the correct mode [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
91 doTestVolumeMode(ctx, f, 0, v1.StorageMediumMemory)
92 })
93
94
100 framework.ConformanceIt("should support (root,0644,tmpfs) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
101 doTest0644(ctx, f, 0, v1.StorageMediumMemory)
102 })
103
104
110 framework.ConformanceIt("should support (root,0666,tmpfs) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
111 doTest0666(ctx, f, 0, v1.StorageMediumMemory)
112 })
113
114
120 framework.ConformanceIt("should support (root,0777,tmpfs) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
121 doTest0777(ctx, f, 0, v1.StorageMediumMemory)
122 })
123
124
130 framework.ConformanceIt("should support (non-root,0644,tmpfs) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
131 doTest0644(ctx, f, nonRootUID, v1.StorageMediumMemory)
132 })
133
134
140 framework.ConformanceIt("should support (non-root,0666,tmpfs) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
141 doTest0666(ctx, f, nonRootUID, v1.StorageMediumMemory)
142 })
143
144
150 framework.ConformanceIt("should support (non-root,0777,tmpfs) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
151 doTest0777(ctx, f, nonRootUID, v1.StorageMediumMemory)
152 })
153
154
160 framework.ConformanceIt("volume on default medium should have the correct mode [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
161 doTestVolumeMode(ctx, f, 0, v1.StorageMediumDefault)
162 })
163
164
170 framework.ConformanceIt("should support (root,0644,default) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
171 doTest0644(ctx, f, 0, v1.StorageMediumDefault)
172 })
173
174
180 framework.ConformanceIt("should support (root,0666,default) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
181 doTest0666(ctx, f, 0, v1.StorageMediumDefault)
182 })
183
184
190 framework.ConformanceIt("should support (root,0777,default) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
191 doTest0777(ctx, f, 0, v1.StorageMediumDefault)
192 })
193
194
200 framework.ConformanceIt("should support (non-root,0644,default) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
201 doTest0644(ctx, f, nonRootUID, v1.StorageMediumDefault)
202 })
203
204
210 framework.ConformanceIt("should support (non-root,0666,default) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
211 doTest0666(ctx, f, nonRootUID, v1.StorageMediumDefault)
212 })
213
214
220 framework.ConformanceIt("should support (non-root,0777,default) [LinuxOnly]", f.WithNodeConformance(), func(ctx context.Context) {
221 doTest0777(ctx, f, nonRootUID, v1.StorageMediumDefault)
222 })
223
224
230 framework.ConformanceIt("pod should support shared volumes between containers", func(ctx context.Context) {
231 var (
232 volumeName = "shared-data"
233 busyBoxMainVolumeMountPath = "/usr/share/volumeshare"
234 busyBoxSubVolumeMountPath = "/pod-data"
235 busyBoxMainVolumeFilePath = fmt.Sprintf("%s/shareddata.txt", busyBoxMainVolumeMountPath)
236 busyBoxSubVolumeFilePath = fmt.Sprintf("%s/shareddata.txt", busyBoxSubVolumeMountPath)
237 message = "Hello from the busy-box sub-container"
238 busyBoxMainContainerName = "busybox-main-container"
239 busyBoxSubContainerName = "busybox-sub-container"
240 resultString = ""
241 deletionGracePeriod = int64(0)
242 )
243
244 pod := &v1.Pod{
245 ObjectMeta: metav1.ObjectMeta{
246 Name: "pod-sharedvolume-" + string(uuid.NewUUID()),
247 },
248 Spec: v1.PodSpec{
249 Volumes: []v1.Volume{
250 {
251 Name: volumeName,
252 VolumeSource: v1.VolumeSource{
253 EmptyDir: new(v1.EmptyDirVolumeSource),
254 },
255 },
256 },
257 Containers: []v1.Container{
258 {
259 Name: busyBoxMainContainerName,
260 Image: imageutils.GetE2EImage(imageutils.BusyBox),
261 Command: []string{"/bin/sh"},
262 Args: []string{"-c", "sleep 100000"},
263 VolumeMounts: []v1.VolumeMount{
264 {
265 Name: volumeName,
266 MountPath: busyBoxMainVolumeMountPath,
267 },
268 },
269 },
270 {
271 Name: busyBoxSubContainerName,
272 Image: imageutils.GetE2EImage(imageutils.BusyBox),
273 Command: []string{"/bin/sh"},
274 Args: []string{"-c", fmt.Sprintf("echo %s > %s", message, busyBoxSubVolumeFilePath)},
275 VolumeMounts: []v1.VolumeMount{
276 {
277 Name: volumeName,
278 MountPath: busyBoxSubVolumeMountPath,
279 },
280 },
281 },
282 },
283 TerminationGracePeriodSeconds: &deletionGracePeriod,
284 RestartPolicy: v1.RestartPolicyNever,
285 },
286 }
287
288 ginkgo.By("Creating Pod")
289 e2epod.NewPodClient(f).Create(ctx, pod)
290 framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name))
291
292 ginkgo.By("Reading file content from the nginx-container")
293 result := e2epod.ExecShellInContainer(f, pod.Name, busyBoxMainContainerName, fmt.Sprintf("cat %s", busyBoxMainVolumeFilePath))
294 gomega.Expect(result).To(gomega.Equal(message), "failed to match expected string %s with %s", message, resultString)
295 })
296
297
302 ginkgo.It("pod should support memory backed volumes of specified size", func(ctx context.Context) {
303 var (
304 volumeName = "shared-data"
305 busyBoxMainVolumeMountPath = "/usr/share/volumeshare"
306 busyBoxMainContainerName = "busybox-main-container"
307 expectedResult = "10240"
308 deletionGracePeriod = int64(0)
309 sizeLimit = resource.MustParse("10Mi")
310 )
311
312 pod := &v1.Pod{
313 ObjectMeta: metav1.ObjectMeta{
314 Name: "pod-size-memory-volume-" + string(uuid.NewUUID()),
315 },
316 Spec: v1.PodSpec{
317 Volumes: []v1.Volume{
318 {
319 Name: volumeName,
320 VolumeSource: v1.VolumeSource{
321 EmptyDir: &v1.EmptyDirVolumeSource{
322 Medium: v1.StorageMediumMemory,
323 SizeLimit: &sizeLimit,
324 },
325 },
326 },
327 },
328 Containers: []v1.Container{
329 {
330 Name: busyBoxMainContainerName,
331 Image: imageutils.GetE2EImage(imageutils.BusyBox),
332 Command: []string{"/bin/sh"},
333 Args: []string{"-c", "sleep 100000"},
334 VolumeMounts: []v1.VolumeMount{
335 {
336 Name: volumeName,
337 MountPath: busyBoxMainVolumeMountPath,
338 },
339 },
340 },
341 },
342 TerminationGracePeriodSeconds: &deletionGracePeriod,
343 RestartPolicy: v1.RestartPolicyNever,
344 },
345 }
346
347 var err error
348 ginkgo.By("Creating Pod")
349 pod = e2epod.NewPodClient(f).CreateSync(ctx, pod)
350
351 ginkgo.By("Waiting for the pod running")
352 err = e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
353 framework.ExpectNoError(err, "failed to deploy pod %s", pod.Name)
354
355 ginkgo.By("Getting the pod")
356 pod, err = e2epod.NewPodClient(f).Get(ctx, pod.Name, metav1.GetOptions{})
357 framework.ExpectNoError(err, "failed to get pod %s", pod.Name)
358
359 ginkgo.By("Reading empty dir size")
360 result := e2epod.ExecShellInContainer(f, pod.Name, busyBoxMainContainerName, fmt.Sprintf("df | grep %s | awk '{print $2}'", busyBoxMainVolumeMountPath))
361 gomega.Expect(result).To(gomega.Equal(expectedResult), "failed to match expected string %s with %s", expectedResult, result)
362 })
363 })
364
365 const (
366 containerName = "test-container"
367 volumeName = "test-volume"
368 )
369
370 func doTestSetgidFSGroup(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
371 var (
372 filePath = path.Join(volumePath, "test-file")
373 source = &v1.EmptyDirVolumeSource{Medium: medium}
374 pod = testPodWithVolume(uid, volumePath, source)
375 )
376
377 pod.Spec.Containers[0].Args = []string{
378 "mounttest",
379 fmt.Sprintf("--fs_type=%v", volumePath),
380 fmt.Sprintf("--new_file_0660=%v", filePath),
381 fmt.Sprintf("--file_perm=%v", filePath),
382 fmt.Sprintf("--file_owner=%v", filePath),
383 }
384
385 fsGroup := int64(123)
386 pod.Spec.SecurityContext.FSGroup = &fsGroup
387
388 msg := fmt.Sprintf("emptydir 0644 on %v", formatMedium(medium))
389 out := []string{
390 "perms of file \"/test-volume/test-file\": -rw-rw----",
391 "content of file \"/test-volume/test-file\": mount-tester new file",
392 "owner GID of \"/test-volume/test-file\": 123",
393 }
394 if medium == v1.StorageMediumMemory {
395 out = append(out, "mount type of \"/test-volume\": tmpfs")
396 }
397 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
398 }
399
400 func doTestSubPathFSGroup(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
401 var (
402 subPath = "test-sub"
403 source = &v1.EmptyDirVolumeSource{Medium: medium}
404 pod = testPodWithVolume(uid, volumePath, source)
405 )
406
407 pod.Spec.Containers[0].Args = []string{
408 "mounttest",
409 fmt.Sprintf("--fs_type=%v", volumePath),
410 fmt.Sprintf("--file_perm=%v", volumePath),
411 fmt.Sprintf("--file_owner=%v", volumePath),
412 fmt.Sprintf("--file_mode=%v", volumePath),
413 }
414
415 pod.Spec.Containers[0].VolumeMounts[0].SubPath = subPath
416
417 fsGroup := int64(123)
418 pod.Spec.SecurityContext.FSGroup = &fsGroup
419
420 msg := fmt.Sprintf("emptydir subpath on %v", formatMedium(medium))
421 out := []string{
422 "perms of file \"/test-volume\": -rwxrwxrwx",
423 "owner UID of \"/test-volume\": 0",
424 "owner GID of \"/test-volume\": 123",
425 "mode of file \"/test-volume\": dgtrwxrwxrwx",
426 }
427 if medium == v1.StorageMediumMemory {
428 out = append(out, "mount type of \"/test-volume\": tmpfs")
429 }
430 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
431 }
432
433 func doTestVolumeModeFSGroup(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
434 var (
435 source = &v1.EmptyDirVolumeSource{Medium: medium}
436 pod = testPodWithVolume(uid, volumePath, source)
437 )
438
439 pod.Spec.Containers[0].Args = []string{
440 "mounttest",
441 fmt.Sprintf("--fs_type=%v", volumePath),
442 fmt.Sprintf("--file_perm=%v", volumePath),
443 }
444
445 fsGroup := int64(1001)
446 pod.Spec.SecurityContext.FSGroup = &fsGroup
447
448 msg := fmt.Sprintf("emptydir volume type on %v", formatMedium(medium))
449 out := []string{
450 "perms of file \"/test-volume\": -rwxrwxrwx",
451 }
452 if medium == v1.StorageMediumMemory {
453 out = append(out, "mount type of \"/test-volume\": tmpfs")
454 }
455 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
456 }
457
458 func doTest0644FSGroup(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
459 var (
460 filePath = path.Join(volumePath, "test-file")
461 source = &v1.EmptyDirVolumeSource{Medium: medium}
462 pod = testPodWithVolume(uid, volumePath, source)
463 )
464
465 pod.Spec.Containers[0].Args = []string{
466 "mounttest",
467 fmt.Sprintf("--fs_type=%v", volumePath),
468 fmt.Sprintf("--new_file_0644=%v", filePath),
469 fmt.Sprintf("--file_perm=%v", filePath),
470 }
471
472 fsGroup := int64(123)
473 pod.Spec.SecurityContext.FSGroup = &fsGroup
474
475 msg := fmt.Sprintf("emptydir 0644 on %v", formatMedium(medium))
476 out := []string{
477 "perms of file \"/test-volume/test-file\": -rw-r--r--",
478 "content of file \"/test-volume/test-file\": mount-tester new file",
479 }
480 if medium == v1.StorageMediumMemory {
481 out = append(out, "mount type of \"/test-volume\": tmpfs")
482 }
483 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
484 }
485
486 func doTestVolumeMode(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
487 var (
488 source = &v1.EmptyDirVolumeSource{Medium: medium}
489 pod = testPodWithVolume(uid, volumePath, source)
490 )
491
492 pod.Spec.Containers[0].Args = []string{
493 "mounttest",
494 fmt.Sprintf("--fs_type=%v", volumePath),
495 fmt.Sprintf("--file_perm=%v", volumePath),
496 }
497
498 msg := fmt.Sprintf("emptydir volume type on %v", formatMedium(medium))
499 out := []string{
500 "perms of file \"/test-volume\": -rwxrwxrwx",
501 }
502 if medium == v1.StorageMediumMemory {
503 out = append(out, "mount type of \"/test-volume\": tmpfs")
504 }
505 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
506 }
507
508 func doTest0644(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
509 var (
510 filePath = path.Join(volumePath, "test-file")
511 source = &v1.EmptyDirVolumeSource{Medium: medium}
512 pod = testPodWithVolume(uid, volumePath, source)
513 )
514
515 pod.Spec.Containers[0].Args = []string{
516 "mounttest",
517 fmt.Sprintf("--fs_type=%v", volumePath),
518 fmt.Sprintf("--new_file_0644=%v", filePath),
519 fmt.Sprintf("--file_perm=%v", filePath),
520 }
521
522 msg := fmt.Sprintf("emptydir 0644 on %v", formatMedium(medium))
523 out := []string{
524 "perms of file \"/test-volume/test-file\": -rw-r--r--",
525 "content of file \"/test-volume/test-file\": mount-tester new file",
526 }
527 if medium == v1.StorageMediumMemory {
528 out = append(out, "mount type of \"/test-volume\": tmpfs")
529 }
530 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
531 }
532
533 func doTest0666(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
534 var (
535 filePath = path.Join(volumePath, "test-file")
536 source = &v1.EmptyDirVolumeSource{Medium: medium}
537 pod = testPodWithVolume(uid, volumePath, source)
538 )
539
540 pod.Spec.Containers[0].Args = []string{
541 "mounttest",
542 fmt.Sprintf("--fs_type=%v", volumePath),
543 fmt.Sprintf("--new_file_0666=%v", filePath),
544 fmt.Sprintf("--file_perm=%v", filePath),
545 }
546
547 msg := fmt.Sprintf("emptydir 0666 on %v", formatMedium(medium))
548 out := []string{
549 "perms of file \"/test-volume/test-file\": -rw-rw-rw-",
550 "content of file \"/test-volume/test-file\": mount-tester new file",
551 }
552 if medium == v1.StorageMediumMemory {
553 out = append(out, "mount type of \"/test-volume\": tmpfs")
554 }
555 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
556 }
557
558 func doTest0777(ctx context.Context, f *framework.Framework, uid int64, medium v1.StorageMedium) {
559 var (
560 filePath = path.Join(volumePath, "test-file")
561 source = &v1.EmptyDirVolumeSource{Medium: medium}
562 pod = testPodWithVolume(uid, volumePath, source)
563 )
564
565 pod.Spec.Containers[0].Args = []string{
566 "mounttest",
567 fmt.Sprintf("--fs_type=%v", volumePath),
568 fmt.Sprintf("--new_file_0777=%v", filePath),
569 fmt.Sprintf("--file_perm=%v", filePath),
570 }
571
572 msg := fmt.Sprintf("emptydir 0777 on %v", formatMedium(medium))
573 out := []string{
574 "perms of file \"/test-volume/test-file\": -rwxrwxrwx",
575 "content of file \"/test-volume/test-file\": mount-tester new file",
576 }
577 if medium == v1.StorageMediumMemory {
578 out = append(out, "mount type of \"/test-volume\": tmpfs")
579 }
580 e2epodoutput.TestContainerOutput(ctx, f, msg, pod, 0, out)
581 }
582
583 func formatMedium(medium v1.StorageMedium) string {
584 if medium == v1.StorageMediumMemory {
585 return "tmpfs"
586 }
587
588 return "node default medium"
589 }
590
591
592
593 func testPodWithVolume(uid int64, path string, source *v1.EmptyDirVolumeSource) *v1.Pod {
594 podName := "pod-" + string(uuid.NewUUID())
595 pod := &v1.Pod{
596 TypeMeta: metav1.TypeMeta{
597 Kind: "Pod",
598 APIVersion: "v1",
599 },
600 ObjectMeta: metav1.ObjectMeta{
601 Name: podName,
602 },
603 Spec: v1.PodSpec{
604 Containers: []v1.Container{
605 {
606 Name: containerName,
607 Image: imageutils.GetE2EImage(imageutils.Agnhost),
608 VolumeMounts: []v1.VolumeMount{
609 {
610 Name: volumeName,
611 MountPath: path,
612 },
613 },
614 },
615 },
616 SecurityContext: &v1.PodSecurityContext{
617 SELinuxOptions: &v1.SELinuxOptions{
618 Level: "s0",
619 },
620 },
621 RestartPolicy: v1.RestartPolicyNever,
622 Volumes: []v1.Volume{
623 {
624 Name: volumeName,
625 VolumeSource: v1.VolumeSource{
626 EmptyDir: source,
627 },
628 },
629 },
630 },
631 }
632
633 if uid != 0 {
634 pod.Spec.SecurityContext.RunAsUser = &uid
635 }
636
637 return pod
638 }
639
View as plain text