k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/volume/emptydir/empty_dir_test.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2014 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 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 // Construct an instance of a plugin, by name. 43 func makePluginUnderTest(t *testing.T, plugName, basePath string) volume.VolumePlugin { 44 plugMgr := volume.VolumePluginMgr{} 45 plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, 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 //volumeDirExists indicates whether volumeDir already/still exists before volume setup/teardown 135 volumeDirExists bool 136 //readyDirExists indicates whether readyDir already/still exists before volume setup/teardown 137 readyDirExists bool 138 expectedSetupMounts int 139 shouldBeMountedBeforeTeardown bool 140 expectedTeardownMounts int 141 } 142 143 // doTestPlugin sets up a volume and tears it back down. 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 // Stat the directory and check the permission bits 214 testSetUp(mounter, metadataDir, volPath) 215 216 log := physicalMounter.GetLog() 217 // Check the number of mounts performed during setup 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 // Make an unmounter for the volume 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 // Tear down the volume 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 // Check the number of physicalMounter calls during tardown 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 // Stat the directory and check the permission bits 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 // TestMetrics tests that MetricProvider methods return sane values. 329 func TestMetrics(t *testing.T) { 330 // Create an empty temp directory for the volume 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 // Need to create the subdirectory 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 // TODO(pwittroc): Move this into a reusable testing utility 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 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 }