...

Source file src/k8s.io/kubernetes/pkg/volume/emptydir/empty_dir_test.go

Documentation: k8s.io/kubernetes/pkg/volume/emptydir

     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  			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