1
2
3
4
19
20 package emptydir
21
22 import (
23 "fmt"
24 "os"
25 "path/filepath"
26 "testing"
27
28 v1 "k8s.io/api/core/v1"
29 "k8s.io/apimachinery/pkg/api/resource"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/types"
32 utilfeature "k8s.io/apiserver/pkg/util/feature"
33 utiltesting "k8s.io/client-go/util/testing"
34 featuregatetesting "k8s.io/component-base/featuregate/testing"
35 "k8s.io/kubernetes/pkg/features"
36 "k8s.io/kubernetes/pkg/volume"
37 volumetest "k8s.io/kubernetes/pkg/volume/testing"
38 volumeutil "k8s.io/kubernetes/pkg/volume/util"
39 "k8s.io/mount-utils"
40 )
41
42
43 func makePluginUnderTest(t *testing.T, plugName, basePath string) volume.VolumePlugin {
44 plugMgr := volume.VolumePluginMgr{}
45 plugMgr.InitPlugins(ProbeVolumePlugins(), nil , volumetest.NewFakeVolumeHost(t, basePath, nil, nil))
46
47 plug, err := plugMgr.FindPluginByName(plugName)
48 if err != nil {
49 t.Fatal("Can't find the plugin by name")
50 }
51 return plug
52 }
53
54 func TestCanSupport(t *testing.T) {
55 tmpDir, err := utiltesting.MkTmpdir("emptydirTest")
56 if err != nil {
57 t.Fatalf("can't make a temp dir: %v", err)
58 }
59 defer os.RemoveAll(tmpDir)
60 plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir)
61
62 if plug.GetPluginName() != "kubernetes.io/empty-dir" {
63 t.Errorf("Wrong name: %s", plug.GetPluginName())
64 }
65 if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}}) {
66 t.Errorf("Expected true")
67 }
68 if plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) {
69 t.Errorf("Expected false")
70 }
71 }
72
73 type fakeMountDetector struct {
74 medium v1.StorageMedium
75 isMount bool
76 }
77
78 func (fake *fakeMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
79 return fake.medium, fake.isMount, nil, nil
80 }
81
82 func TestPluginEmptyRootContext(t *testing.T) {
83 doTestPlugin(t, pluginTestConfig{
84 volumeDirExists: true,
85 readyDirExists: true,
86 medium: v1.StorageMediumDefault,
87 expectedSetupMounts: 0,
88 expectedTeardownMounts: 0})
89 doTestPlugin(t, pluginTestConfig{
90 volumeDirExists: false,
91 readyDirExists: false,
92 medium: v1.StorageMediumDefault,
93 expectedSetupMounts: 0,
94 expectedTeardownMounts: 0})
95 doTestPlugin(t, pluginTestConfig{
96 volumeDirExists: true,
97 readyDirExists: false,
98 medium: v1.StorageMediumDefault,
99 expectedSetupMounts: 0,
100 expectedTeardownMounts: 0})
101 doTestPlugin(t, pluginTestConfig{
102 volumeDirExists: false,
103 readyDirExists: true,
104 medium: v1.StorageMediumDefault,
105 expectedSetupMounts: 0,
106 expectedTeardownMounts: 0})
107 }
108
109 func TestPluginHugetlbfs(t *testing.T) {
110 testCases := map[string]struct {
111 medium v1.StorageMedium
112 }{
113 "medium without size": {
114 medium: "HugePages",
115 },
116 "medium with size": {
117 medium: "HugePages-2Mi",
118 },
119 }
120 for tcName, tc := range testCases {
121 t.Run(tcName, func(t *testing.T) {
122 doTestPlugin(t, pluginTestConfig{
123 medium: tc.medium,
124 expectedSetupMounts: 1,
125 expectedTeardownMounts: 0,
126 shouldBeMountedBeforeTeardown: true,
127 })
128 })
129 }
130 }
131
132 type pluginTestConfig struct {
133 medium v1.StorageMedium
134
135 volumeDirExists bool
136
137 readyDirExists bool
138 expectedSetupMounts int
139 shouldBeMountedBeforeTeardown bool
140 expectedTeardownMounts int
141 }
142
143
144 func doTestPlugin(t *testing.T, config pluginTestConfig) {
145 basePath, err := utiltesting.MkTmpdir("emptydir_volume_test")
146 if err != nil {
147 t.Fatalf("can't make a temp rootdir: %v", err)
148 }
149 defer os.RemoveAll(basePath)
150
151 var (
152 volumePath = filepath.Join(basePath, "pods/poduid/volumes/kubernetes.io~empty-dir/test-volume")
153 metadataDir = filepath.Join(basePath, "pods/poduid/plugins/kubernetes.io~empty-dir/test-volume")
154
155 plug = makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath)
156 volumeName = "test-volume"
157 spec = &v1.Volume{
158 Name: volumeName,
159 VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{Medium: config.medium}},
160 }
161
162 physicalMounter = mount.NewFakeMounter(nil)
163 mountDetector = fakeMountDetector{}
164 pod = &v1.Pod{
165 ObjectMeta: metav1.ObjectMeta{
166 UID: types.UID("poduid"),
167 },
168 Spec: v1.PodSpec{
169 Containers: []v1.Container{
170 {
171 Resources: v1.ResourceRequirements{
172 Requests: v1.ResourceList{
173 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
174 },
175 },
176 },
177 },
178 },
179 }
180 )
181
182 if config.readyDirExists {
183 physicalMounter.MountPoints = []mount.MountPoint{
184 {
185 Path: volumePath,
186 },
187 }
188 volumeutil.SetReady(metadataDir)
189 }
190
191 mounter, err := plug.(*emptyDirPlugin).newMounterInternal(volume.NewSpecFromVolume(spec),
192 pod,
193 physicalMounter,
194 &mountDetector,
195 volume.VolumeOptions{})
196 if err != nil {
197 t.Errorf("Failed to make a new Mounter: %v", err)
198 }
199 if mounter == nil {
200 t.Errorf("Got a nil Mounter")
201 }
202
203 volPath := mounter.GetPath()
204 if volPath != volumePath {
205 t.Errorf("Got unexpected path: %s", volPath)
206 }
207 if config.volumeDirExists {
208 if err := os.MkdirAll(volPath, perm); err != nil {
209 t.Errorf("fail to create path: %s", volPath)
210 }
211 }
212
213
214 testSetUp(mounter, metadataDir, volPath)
215
216 log := physicalMounter.GetLog()
217
218 if e, a := config.expectedSetupMounts, len(log); e != a {
219 t.Errorf("Expected %v physicalMounter calls during setup, got %v", e, a)
220 } else if config.expectedSetupMounts == 1 &&
221 (log[0].Action != mount.FakeActionMount || (log[0].FSType != "tmpfs" && log[0].FSType != "hugetlbfs")) {
222 t.Errorf("Unexpected physicalMounter action during setup: %#v", log[0])
223 }
224 physicalMounter.ResetLog()
225
226
227 teardownMedium := v1.StorageMediumDefault
228 if config.medium == v1.StorageMediumMemory {
229 teardownMedium = v1.StorageMediumMemory
230 }
231 unmounterMountDetector := &fakeMountDetector{medium: teardownMedium, isMount: config.shouldBeMountedBeforeTeardown}
232 unmounter, err := plug.(*emptyDirPlugin).newUnmounterInternal(volumeName, types.UID("poduid"), physicalMounter, unmounterMountDetector)
233 if err != nil {
234 t.Errorf("Failed to make a new Unmounter: %v", err)
235 }
236 if unmounter == nil {
237 t.Errorf("Got a nil Unmounter")
238 }
239
240 if !config.readyDirExists {
241 if err := os.RemoveAll(metadataDir); err != nil && !os.IsNotExist(err) {
242 t.Errorf("failed to remove ready dir [%s]: %v", metadataDir, err)
243 }
244 }
245 if !config.volumeDirExists {
246 if err := os.RemoveAll(volPath); err != nil && !os.IsNotExist(err) {
247 t.Errorf("failed to remove ready dir [%s]: %v", metadataDir, err)
248 }
249 }
250
251 if err := testTearDown(unmounter, metadataDir, volPath); err != nil {
252 t.Errorf("Test failed with error %v", err)
253 }
254
255 log = physicalMounter.GetLog()
256
257 if e, a := config.expectedTeardownMounts, len(log); e != a {
258 t.Errorf("Expected %v physicalMounter calls during teardown, got %v", e, a)
259 } else if config.expectedTeardownMounts == 1 && log[0].Action != mount.FakeActionUnmount {
260 t.Errorf("Unexpected physicalMounter action during teardown: %#v", log[0])
261 }
262 physicalMounter.ResetLog()
263 }
264
265 func testSetUp(mounter volume.Mounter, metadataDir, volPath string) error {
266 if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
267 return fmt.Errorf("expected success, got: %w", err)
268 }
269
270 if !volumeutil.IsReady(metadataDir) {
271 return fmt.Errorf("SetUp() failed, ready file is not created")
272 }
273 fileinfo, err := os.Stat(volPath)
274 if err != nil {
275 if os.IsNotExist(err) {
276 return fmt.Errorf("SetUp() failed, volume path not created: %s", volPath)
277 }
278 return fmt.Errorf("SetUp() failed: %v", err)
279 }
280 if e, a := perm, fileinfo.Mode().Perm(); e != a {
281 return fmt.Errorf("unexpected file mode for %v: expected: %v, got: %v", volPath, e, a)
282 }
283 return nil
284 }
285
286 func testTearDown(unmounter volume.Unmounter, metadataDir, volPath string) error {
287 if err := unmounter.TearDown(); err != nil {
288 return err
289 }
290 if volumeutil.IsReady(metadataDir) {
291 return fmt.Errorf("Teardown() failed, ready file still exists")
292 }
293 if _, err := os.Stat(volPath); err == nil {
294 return fmt.Errorf("TearDown() failed, volume path still exists: %s", volPath)
295 } else if !os.IsNotExist(err) {
296 return fmt.Errorf("TearDown() failed: %v", err)
297 }
298 return nil
299 }
300
301 func TestPluginBackCompat(t *testing.T) {
302 basePath, err := utiltesting.MkTmpdir("emptydirTest")
303 if err != nil {
304 t.Fatalf("can't make a temp dir: %v", err)
305 }
306 defer os.RemoveAll(basePath)
307
308 plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", basePath)
309
310 spec := &v1.Volume{
311 Name: "vol1",
312 }
313 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
314 mounter, err := plug.NewMounter(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{})
315 if err != nil {
316 t.Errorf("Failed to make a new Mounter: %v", err)
317 }
318 if mounter == nil {
319 t.Fatalf("Got a nil Mounter")
320 }
321
322 volPath := mounter.GetPath()
323 if volPath != filepath.Join(basePath, "pods/poduid/volumes/kubernetes.io~empty-dir/vol1") {
324 t.Errorf("Got unexpected path: %s", volPath)
325 }
326 }
327
328
329 func TestMetrics(t *testing.T) {
330
331 tmpDir, err := utiltesting.MkTmpdir("empty_dir_test")
332 if err != nil {
333 t.Fatalf("Can't make a tmp dir: %v", err)
334 }
335 defer os.RemoveAll(tmpDir)
336
337 plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir)
338
339 spec := &v1.Volume{
340 Name: "vol1",
341 }
342 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
343 mounter, err := plug.NewMounter(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{})
344 if err != nil {
345 t.Errorf("Failed to make a new Mounter: %v", err)
346 }
347 if mounter == nil {
348 t.Fatalf("Got a nil Mounter")
349 }
350
351
352 os.MkdirAll(mounter.GetPath(), 0755)
353
354 expectedEmptyDirUsage, err := volumetest.FindEmptyDirectoryUsageOnTmpfs()
355 if err != nil {
356 t.Errorf("Unexpected error finding expected empty directory usage on tmpfs: %v", err)
357 }
358
359
360 metrics, err := mounter.GetMetrics()
361 if err != nil {
362 t.Errorf("Unexpected error when calling GetMetrics %v", err)
363 }
364 if e, a := expectedEmptyDirUsage.Value(), metrics.Used.Value(); e != a {
365 t.Errorf("Unexpected value for empty directory; expected %v, got %v", e, a)
366 }
367 if metrics.Capacity.Value() <= 0 {
368 t.Errorf("Expected Capacity to be greater than 0")
369 }
370 if metrics.Available.Value() <= 0 {
371 t.Errorf("Expected Available to be greater than 0")
372 }
373 }
374
375 func TestGetHugePagesMountOptions(t *testing.T) {
376 testCases := map[string]struct {
377 pod *v1.Pod
378 medium v1.StorageMedium
379 shouldFail bool
380 expectedResult string
381 }{
382 "ProperValues": {
383 pod: &v1.Pod{
384 Spec: v1.PodSpec{
385 Containers: []v1.Container{
386 {
387 Resources: v1.ResourceRequirements{
388 Requests: v1.ResourceList{
389 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
390 },
391 },
392 },
393 },
394 },
395 },
396 medium: v1.StorageMediumHugePages,
397 shouldFail: false,
398 expectedResult: "pagesize=2Mi",
399 },
400 "ProperValuesAndDifferentPageSize": {
401 pod: &v1.Pod{
402 Spec: v1.PodSpec{
403 Containers: []v1.Container{
404 {
405 Resources: v1.ResourceRequirements{
406 Requests: v1.ResourceList{
407 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
408 },
409 },
410 },
411 {
412 Resources: v1.ResourceRequirements{
413 Requests: v1.ResourceList{
414 v1.ResourceName("hugepages-1Gi"): resource.MustParse("4Gi"),
415 },
416 },
417 },
418 },
419 },
420 },
421 medium: v1.StorageMediumHugePages,
422 shouldFail: false,
423 expectedResult: "pagesize=1Gi",
424 },
425 "InitContainerAndContainerHasProperValues": {
426 pod: &v1.Pod{
427 Spec: v1.PodSpec{
428 InitContainers: []v1.Container{
429 {
430 Resources: v1.ResourceRequirements{
431 Requests: v1.ResourceList{
432 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
433 },
434 },
435 },
436 {
437 Resources: v1.ResourceRequirements{
438 Requests: v1.ResourceList{
439 v1.ResourceName("hugepages-1Gi"): resource.MustParse("4Gi"),
440 },
441 },
442 },
443 },
444 },
445 },
446 medium: v1.StorageMediumHugePages,
447 shouldFail: false,
448 expectedResult: "pagesize=1Gi",
449 },
450 "InitContainerAndContainerHasDifferentPageSizes": {
451 pod: &v1.Pod{
452 Spec: v1.PodSpec{
453 InitContainers: []v1.Container{
454 {
455 Resources: v1.ResourceRequirements{
456 Requests: v1.ResourceList{
457 v1.ResourceName("hugepages-2Mi"): resource.MustParse("2Gi"),
458 },
459 },
460 },
461 {
462 Resources: v1.ResourceRequirements{
463 Requests: v1.ResourceList{
464 v1.ResourceName("hugepages-1Gi"): resource.MustParse("4Gi"),
465 },
466 },
467 },
468 },
469 },
470 },
471 medium: v1.StorageMediumHugePages,
472 shouldFail: true,
473 expectedResult: "",
474 },
475 "ContainersWithMultiplePageSizes": {
476 pod: &v1.Pod{
477 Spec: v1.PodSpec{
478 Containers: []v1.Container{
479 {
480 Resources: v1.ResourceRequirements{
481 Requests: v1.ResourceList{
482 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
483 },
484 },
485 },
486 {
487 Resources: v1.ResourceRequirements{
488 Requests: v1.ResourceList{
489 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
490 },
491 },
492 },
493 },
494 },
495 },
496 medium: v1.StorageMediumHugePages,
497 shouldFail: true,
498 expectedResult: "",
499 },
500 "PodWithNoHugePagesRequest": {
501 pod: &v1.Pod{},
502 medium: v1.StorageMediumHugePages,
503 shouldFail: true,
504 expectedResult: "",
505 },
506 "ProperValuesMultipleSizes": {
507 pod: &v1.Pod{
508 Spec: v1.PodSpec{
509 Containers: []v1.Container{
510 {
511 Resources: v1.ResourceRequirements{
512 Requests: v1.ResourceList{
513 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
514 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
515 },
516 },
517 },
518 },
519 },
520 },
521 medium: v1.StorageMediumHugePagesPrefix + "1Gi",
522 shouldFail: false,
523 expectedResult: "pagesize=1Gi",
524 },
525 "InitContainerAndContainerHasProperValuesMultipleSizes": {
526 pod: &v1.Pod{
527 Spec: v1.PodSpec{
528 InitContainers: []v1.Container{
529 {
530 Resources: v1.ResourceRequirements{
531 Requests: v1.ResourceList{
532 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
533 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
534 },
535 },
536 },
537 {
538 Resources: v1.ResourceRequirements{
539 Requests: v1.ResourceList{
540 v1.ResourceName("hugepages-1Gi"): resource.MustParse("4Gi"),
541 v1.ResourceName("hugepages-2Mi"): resource.MustParse("50Mi"),
542 },
543 },
544 },
545 },
546 },
547 },
548 medium: v1.StorageMediumHugePagesPrefix + "2Mi",
549 shouldFail: false,
550 expectedResult: "pagesize=2Mi",
551 },
552 "MediumWithoutSizeMultipleSizes": {
553 pod: &v1.Pod{
554 Spec: v1.PodSpec{
555 Containers: []v1.Container{
556 {
557 Resources: v1.ResourceRequirements{
558 Requests: v1.ResourceList{
559 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
560 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
561 },
562 },
563 },
564 },
565 },
566 },
567 medium: v1.StorageMediumHugePagesPrefix,
568 shouldFail: true,
569 expectedResult: "",
570 },
571 "IncorrectMediumFormatMultipleSizes": {
572 pod: &v1.Pod{
573 Spec: v1.PodSpec{
574 Containers: []v1.Container{
575 {
576 Resources: v1.ResourceRequirements{
577 Requests: v1.ResourceList{
578 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
579 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
580 },
581 },
582 },
583 },
584 },
585 },
586 medium: "foo",
587 shouldFail: true,
588 expectedResult: "",
589 },
590 "MediumSizeDoesntMatchResourcesMultipleSizes": {
591 pod: &v1.Pod{
592 Spec: v1.PodSpec{
593 Containers: []v1.Container{
594 {
595 Resources: v1.ResourceRequirements{
596 Requests: v1.ResourceList{
597 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
598 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
599 },
600 },
601 },
602 },
603 },
604 },
605 medium: v1.StorageMediumHugePagesPrefix + "1Mi",
606 shouldFail: true,
607 expectedResult: "",
608 },
609 }
610
611 for testCaseName, testCase := range testCases {
612 t.Run(testCaseName, func(t *testing.T) {
613 value, err := getPageSizeMountOption(testCase.medium, testCase.pod)
614 if testCase.shouldFail && err == nil {
615 t.Errorf("%s: Unexpected success", testCaseName)
616 } else if !testCase.shouldFail && err != nil {
617 t.Errorf("%s: Unexpected error: %v", testCaseName, err)
618 } else if testCase.expectedResult != value {
619 t.Errorf("%s: Unexpected mountOptions for Pod. Expected %v, got %v", testCaseName, testCase.expectedResult, value)
620 }
621 })
622 }
623 }
624
625 type testMountDetector struct {
626 pageSize *resource.Quantity
627 isMnt bool
628 err error
629 }
630
631 func (md *testMountDetector) GetMountMedium(path string, requestedMedium v1.StorageMedium) (v1.StorageMedium, bool, *resource.Quantity, error) {
632 return v1.StorageMediumHugePages, md.isMnt, md.pageSize, md.err
633 }
634
635 func TestSetupHugepages(t *testing.T) {
636 tmpdir, err := os.MkdirTemp("", "TestSetupHugepages")
637 if err != nil {
638 t.Fatal(err)
639 }
640 defer os.RemoveAll(tmpdir)
641
642 pageSize2Mi := resource.MustParse("2Mi")
643
644 testCases := map[string]struct {
645 path string
646 ed *emptyDir
647 shouldFail bool
648 }{
649 "Valid: mount expected": {
650 path: tmpdir,
651 ed: &emptyDir{
652 medium: v1.StorageMediumHugePages,
653 pod: &v1.Pod{
654 Spec: v1.PodSpec{
655 Containers: []v1.Container{
656 {
657 Resources: v1.ResourceRequirements{
658 Requests: v1.ResourceList{
659 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
660 },
661 },
662 },
663 },
664 },
665 },
666 mounter: &mount.FakeMounter{},
667 mountDetector: &testMountDetector{
668 pageSize: &resource.Quantity{},
669 isMnt: false,
670 err: nil,
671 },
672 },
673 shouldFail: false,
674 },
675 "Valid: already mounted with correct pagesize": {
676 path: tmpdir,
677 ed: &emptyDir{
678 medium: "HugePages-2Mi",
679 pod: &v1.Pod{
680 Spec: v1.PodSpec{
681 Containers: []v1.Container{
682 {
683 Resources: v1.ResourceRequirements{
684 Requests: v1.ResourceList{
685 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
686 },
687 },
688 },
689 },
690 },
691 },
692 mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
693 mountDetector: &testMountDetector{
694 pageSize: &pageSize2Mi,
695 isMnt: true,
696 err: nil,
697 },
698 },
699 shouldFail: false,
700 },
701 "Valid: already mounted": {
702 path: tmpdir,
703 ed: &emptyDir{
704 medium: "HugePages",
705 pod: &v1.Pod{
706 Spec: v1.PodSpec{
707 Containers: []v1.Container{
708 {
709 Resources: v1.ResourceRequirements{
710 Requests: v1.ResourceList{
711 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
712 },
713 },
714 },
715 },
716 },
717 },
718 mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
719 mountDetector: &testMountDetector{
720 pageSize: nil,
721 isMnt: true,
722 err: nil,
723 },
724 },
725 shouldFail: false,
726 },
727 "Invalid: mounter is nil": {
728 path: tmpdir,
729 ed: &emptyDir{
730 medium: "HugePages-2Mi",
731 pod: &v1.Pod{
732 Spec: v1.PodSpec{
733 Containers: []v1.Container{
734 {
735 Resources: v1.ResourceRequirements{
736 Requests: v1.ResourceList{
737 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
738 },
739 },
740 },
741 },
742 },
743 },
744 mounter: nil,
745 },
746 shouldFail: true,
747 },
748 "Invalid: GetMountMedium error": {
749 path: tmpdir,
750 ed: &emptyDir{
751 medium: "HugePages-2Mi",
752 pod: &v1.Pod{
753 Spec: v1.PodSpec{
754 Containers: []v1.Container{
755 {
756 Resources: v1.ResourceRequirements{
757 Requests: v1.ResourceList{
758 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
759 },
760 },
761 },
762 },
763 },
764 },
765 mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
766 mountDetector: &testMountDetector{
767 pageSize: &pageSize2Mi,
768 isMnt: true,
769 err: fmt.Errorf("GetMountMedium error"),
770 },
771 },
772 shouldFail: true,
773 },
774 "Invalid: medium and page size differ": {
775 path: tmpdir,
776 ed: &emptyDir{
777 medium: "HugePages-1Gi",
778 pod: &v1.Pod{
779 Spec: v1.PodSpec{
780 Containers: []v1.Container{
781 {
782 Resources: v1.ResourceRequirements{
783 Requests: v1.ResourceList{
784 v1.ResourceName("hugepages-1Gi"): resource.MustParse("2Gi"),
785 },
786 },
787 },
788 },
789 },
790 },
791 mounter: mount.NewFakeMounter([]mount.MountPoint{{Path: tmpdir, Opts: []string{"rw", "pagesize=2M", "realtime"}}}),
792 mountDetector: &testMountDetector{
793 pageSize: &pageSize2Mi,
794 isMnt: true,
795 err: nil,
796 },
797 },
798 shouldFail: true,
799 },
800 "Invalid medium": {
801 path: tmpdir,
802 ed: &emptyDir{
803 medium: "HugePages-NN",
804 pod: &v1.Pod{
805 Spec: v1.PodSpec{
806 Containers: []v1.Container{
807 {
808 Resources: v1.ResourceRequirements{
809 Requests: v1.ResourceList{
810 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
811 },
812 },
813 },
814 },
815 },
816 },
817 mounter: &mount.FakeMounter{},
818 mountDetector: &testMountDetector{
819 pageSize: &resource.Quantity{},
820 isMnt: false,
821 err: nil,
822 },
823 },
824 shouldFail: true,
825 },
826 "Invalid: setupDir fails": {
827 path: "",
828 ed: &emptyDir{
829 medium: v1.StorageMediumHugePages,
830 pod: &v1.Pod{
831 Spec: v1.PodSpec{
832 Containers: []v1.Container{
833 {
834 Resources: v1.ResourceRequirements{
835 Requests: v1.ResourceList{
836 v1.ResourceName("hugepages-2Mi"): resource.MustParse("100Mi"),
837 },
838 },
839 },
840 },
841 },
842 },
843 mounter: &mount.FakeMounter{},
844 },
845 shouldFail: true,
846 },
847 }
848
849 for testCaseName, testCase := range testCases {
850 t.Run(testCaseName, func(t *testing.T) {
851 err := testCase.ed.setupHugepages(testCase.path)
852 if testCase.shouldFail && err == nil {
853 t.Errorf("%s: Unexpected success", testCaseName)
854 } else if !testCase.shouldFail && err != nil {
855 t.Errorf("%s: Unexpected error: %v", testCaseName, err)
856 }
857 })
858 }
859 }
860
861 func TestGetPageSize(t *testing.T) {
862 mounter := &mount.FakeMounter{
863 MountPoints: []mount.MountPoint{
864 {
865 Device: "/dev/sda2",
866 Type: "ext4",
867 Path: "/",
868 Opts: []string{"rw", "relatime", "errors=remount-ro"},
869 },
870 {
871 Device: "/dev/hugepages",
872 Type: "hugetlbfs",
873 Path: "/mnt/hugepages-2Mi",
874 Opts: []string{"rw", "relatime", "pagesize=2M"},
875 },
876 {
877 Device: "/dev/hugepages",
878 Type: "hugetlbfs",
879 Path: "/mnt/hugepages-2Mi",
880 Opts: []string{"rw", "relatime", "pagesize=2Mi"},
881 },
882 {
883 Device: "sysfs",
884 Type: "sysfs",
885 Path: "/sys",
886 Opts: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
887 },
888 {
889 Device: "/dev/hugepages",
890 Type: "hugetlbfs",
891 Path: "/mnt/hugepages-1Gi",
892 Opts: []string{"rw", "relatime", "pagesize=1024M"},
893 },
894 {
895 Device: "/dev/hugepages",
896 Type: "hugetlbfs",
897 Path: "/mnt/noopt",
898 Opts: []string{"rw", "relatime"},
899 },
900 {
901 Device: "/dev/hugepages",
902 Type: "hugetlbfs",
903 Path: "/mnt/badopt",
904 Opts: []string{"rw", "relatime", "pagesize=NN"},
905 },
906 },
907 }
908
909 testCases := map[string]struct {
910 path string
911 mounter mount.Interface
912 expectedResult resource.Quantity
913 shouldFail bool
914 }{
915 "Valid: existing 2Mi mount": {
916 path: "/mnt/hugepages-2Mi",
917 mounter: mounter,
918 shouldFail: false,
919 expectedResult: resource.MustParse("2Mi"),
920 },
921 "Valid: existing 1Gi mount": {
922 path: "/mnt/hugepages-1Gi",
923 mounter: mounter,
924 shouldFail: false,
925 expectedResult: resource.MustParse("1Gi"),
926 },
927 "Invalid: mount point doesn't exist": {
928 path: "/mnt/nomp",
929 mounter: mounter,
930 shouldFail: true,
931 },
932 "Invalid: no pagesize option": {
933 path: "/mnt/noopt",
934 mounter: mounter,
935 shouldFail: true,
936 },
937 "Invalid: incorrect pagesize option": {
938 path: "/mnt/badopt",
939 mounter: mounter,
940 shouldFail: true,
941 },
942 }
943
944 for testCaseName, testCase := range testCases {
945 t.Run(testCaseName, func(t *testing.T) {
946 pageSize, err := getPageSize(testCase.path, testCase.mounter)
947 if testCase.shouldFail && err == nil {
948 t.Errorf("%s: Unexpected success", testCaseName)
949 } else if !testCase.shouldFail && err != nil {
950 t.Errorf("%s: Unexpected error: %v", testCaseName, err)
951 }
952 if err == nil && pageSize.Cmp(testCase.expectedResult) != 0 {
953 t.Errorf("%s: Unexpected result: %s, expected: %s", testCaseName, pageSize.String(), testCase.expectedResult.String())
954 }
955 })
956 }
957 }
958
959 func TestCalculateEmptyDirMemorySize(t *testing.T) {
960 testCases := map[string]struct {
961 pod *v1.Pod
962 nodeAllocatableMemory resource.Quantity
963 emptyDirSizeLimit resource.Quantity
964 expectedResult resource.Quantity
965 featureGateEnabled bool
966 }{
967 "SizeMemoryBackedVolumesDisabled": {
968 pod: &v1.Pod{
969 Spec: v1.PodSpec{
970 Containers: []v1.Container{
971 {
972 Resources: v1.ResourceRequirements{
973 Requests: v1.ResourceList{
974 v1.ResourceName("memory"): resource.MustParse("10Gi"),
975 },
976 },
977 },
978 },
979 },
980 },
981 nodeAllocatableMemory: resource.MustParse("16Gi"),
982 emptyDirSizeLimit: resource.MustParse("1Gi"),
983 expectedResult: resource.MustParse("0"),
984 featureGateEnabled: false,
985 },
986 "EmptyDirLocalLimit": {
987 pod: &v1.Pod{
988 Spec: v1.PodSpec{
989 Containers: []v1.Container{
990 {
991 Resources: v1.ResourceRequirements{
992 Limits: v1.ResourceList{
993 v1.ResourceName("memory"): resource.MustParse("10Gi"),
994 },
995 },
996 },
997 },
998 },
999 },
1000 nodeAllocatableMemory: resource.MustParse("16Gi"),
1001 emptyDirSizeLimit: resource.MustParse("1Gi"),
1002 expectedResult: resource.MustParse("1Gi"),
1003 featureGateEnabled: true,
1004 },
1005 "PodLocalLimit": {
1006 pod: &v1.Pod{
1007 Spec: v1.PodSpec{
1008 Containers: []v1.Container{
1009 {
1010 Resources: v1.ResourceRequirements{
1011 Limits: v1.ResourceList{
1012 v1.ResourceName("memory"): resource.MustParse("10Gi"),
1013 },
1014 },
1015 },
1016 },
1017 },
1018 },
1019 nodeAllocatableMemory: resource.MustParse("16Gi"),
1020 emptyDirSizeLimit: resource.MustParse("0"),
1021 expectedResult: resource.MustParse("10Gi"),
1022 featureGateEnabled: true,
1023 },
1024 "NodeAllocatableLimit": {
1025 pod: &v1.Pod{
1026 Spec: v1.PodSpec{
1027 Containers: []v1.Container{
1028 {
1029 Resources: v1.ResourceRequirements{
1030 Requests: v1.ResourceList{
1031 v1.ResourceName("memory"): resource.MustParse("10Gi"),
1032 },
1033 },
1034 },
1035 },
1036 },
1037 },
1038 nodeAllocatableMemory: resource.MustParse("16Gi"),
1039 emptyDirSizeLimit: resource.MustParse("0"),
1040 expectedResult: resource.MustParse("16Gi"),
1041 featureGateEnabled: true,
1042 },
1043 }
1044
1045 for testCaseName, testCase := range testCases {
1046 t.Run(testCaseName, func(t *testing.T) {
1047 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SizeMemoryBackedVolumes, testCase.featureGateEnabled)()
1048 spec := &volume.Spec{
1049 Volume: &v1.Volume{
1050 VolumeSource: v1.VolumeSource{
1051 EmptyDir: &v1.EmptyDirVolumeSource{
1052 Medium: v1.StorageMediumMemory,
1053 SizeLimit: &testCase.emptyDirSizeLimit,
1054 },
1055 },
1056 }}
1057 result := calculateEmptyDirMemorySize(&testCase.nodeAllocatableMemory, spec, testCase.pod)
1058 if result.Cmp(testCase.expectedResult) != 0 {
1059 t.Errorf("%s: Unexpected result. Expected %v, got %v", testCaseName, testCase.expectedResult.String(), result.String())
1060 }
1061 })
1062 }
1063 }
1064
View as plain text