...

Source file src/k8s.io/kubernetes/test/e2e/storage/testsuites/volumes.go

Documentation: k8s.io/kubernetes/test/e2e/storage/testsuites

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This test checks that various VolumeSources are working.
    18  
    19  package testsuites
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"path/filepath"
    25  
    26  	"github.com/onsi/ginkgo/v2"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/util/errors"
    31  	"k8s.io/kubernetes/test/e2e/framework"
    32  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    33  	e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    34  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    35  	e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
    36  	storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
    37  	imageutils "k8s.io/kubernetes/test/utils/image"
    38  	admissionapi "k8s.io/pod-security-admission/api"
    39  )
    40  
    41  type volumesTestSuite struct {
    42  	tsInfo storageframework.TestSuiteInfo
    43  }
    44  
    45  var _ storageframework.TestSuite = &volumesTestSuite{}
    46  
    47  // InitCustomVolumesTestSuite returns volumesTestSuite that implements TestSuite interface
    48  // using custom test patterns
    49  func InitCustomVolumesTestSuite(patterns []storageframework.TestPattern) storageframework.TestSuite {
    50  	return &volumesTestSuite{
    51  		tsInfo: storageframework.TestSuiteInfo{
    52  			Name:         "volumes",
    53  			TestPatterns: patterns,
    54  			SupportedSizeRange: e2evolume.SizeRange{
    55  				Min: "1Mi",
    56  			},
    57  		},
    58  	}
    59  }
    60  
    61  // InitVolumesTestSuite returns volumesTestSuite that implements TestSuite interface
    62  // using testsuite default patterns
    63  func InitVolumesTestSuite() storageframework.TestSuite {
    64  	patterns := []storageframework.TestPattern{
    65  		// Default fsType
    66  		storageframework.DefaultFsInlineVolume,
    67  		storageframework.DefaultFsPreprovisionedPV,
    68  		storageframework.DefaultFsDynamicPV,
    69  		// ext3
    70  		storageframework.Ext3InlineVolume,
    71  		storageframework.Ext3PreprovisionedPV,
    72  		storageframework.Ext3DynamicPV,
    73  		// ext4
    74  		storageframework.Ext4InlineVolume,
    75  		storageframework.Ext4PreprovisionedPV,
    76  		storageframework.Ext4DynamicPV,
    77  		// xfs
    78  		storageframework.XfsInlineVolume,
    79  		storageframework.XfsPreprovisionedPV,
    80  		storageframework.XfsDynamicPV,
    81  		// ntfs
    82  		storageframework.NtfsInlineVolume,
    83  		storageframework.NtfsPreprovisionedPV,
    84  		storageframework.NtfsDynamicPV,
    85  		// block volumes
    86  		storageframework.BlockVolModePreprovisionedPV,
    87  		storageframework.BlockVolModeDynamicPV,
    88  	}
    89  	return InitCustomVolumesTestSuite(patterns)
    90  }
    91  
    92  func (t *volumesTestSuite) GetTestSuiteInfo() storageframework.TestSuiteInfo {
    93  	return t.tsInfo
    94  }
    95  
    96  func (t *volumesTestSuite) SkipUnsupportedTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) {
    97  	if pattern.VolMode == v1.PersistentVolumeBlock {
    98  		skipTestIfBlockNotSupported(driver)
    99  	}
   100  }
   101  
   102  func skipExecTest(driver storageframework.TestDriver) {
   103  	dInfo := driver.GetDriverInfo()
   104  	if !dInfo.Capabilities[storageframework.CapExec] {
   105  		e2eskipper.Skipf("Driver %q does not support exec - skipping", dInfo.Name)
   106  	}
   107  }
   108  
   109  func skipTestIfBlockNotSupported(driver storageframework.TestDriver) {
   110  	dInfo := driver.GetDriverInfo()
   111  	if !dInfo.Capabilities[storageframework.CapBlock] {
   112  		e2eskipper.Skipf("Driver %q does not provide raw block - skipping", dInfo.Name)
   113  	}
   114  }
   115  
   116  func (t *volumesTestSuite) DefineTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) {
   117  	type local struct {
   118  		config *storageframework.PerTestConfig
   119  
   120  		resource *storageframework.VolumeResource
   121  
   122  		migrationCheck *migrationOpCheck
   123  	}
   124  	var dInfo = driver.GetDriverInfo()
   125  	var l local
   126  
   127  	// Beware that it also registers an AfterEach which renders f unusable. Any code using
   128  	// f must run inside an It or Context callback.
   129  	f := framework.NewFrameworkWithCustomTimeouts("volume", storageframework.GetDriverTimeouts(driver))
   130  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
   131  
   132  	init := func(ctx context.Context) {
   133  		l = local{}
   134  
   135  		// Now do the more expensive test initialization.
   136  		l.config = driver.PrepareTest(ctx, f)
   137  		l.migrationCheck = newMigrationOpCheck(ctx, f.ClientSet, f.ClientConfig(), dInfo.InTreePluginName)
   138  		testVolumeSizeRange := t.GetTestSuiteInfo().SupportedSizeRange
   139  		l.resource = storageframework.CreateVolumeResource(ctx, driver, l.config, pattern, testVolumeSizeRange)
   140  		if l.resource.VolSource == nil {
   141  			e2eskipper.Skipf("Driver %q does not define volumeSource - skipping", dInfo.Name)
   142  		}
   143  	}
   144  
   145  	cleanup := func(ctx context.Context) {
   146  		var errs []error
   147  		if l.resource != nil {
   148  			errs = append(errs, l.resource.CleanupResource(ctx))
   149  			l.resource = nil
   150  		}
   151  
   152  		framework.ExpectNoError(errors.NewAggregate(errs), "while cleaning up resource")
   153  		l.migrationCheck.validateMigrationVolumeOpCounts(ctx)
   154  	}
   155  
   156  	ginkgo.It("should store data", func(ctx context.Context) {
   157  		init(ctx)
   158  		ginkgo.DeferCleanup(e2evolume.TestServerCleanup, f, storageframework.ConvertTestConfig(l.config))
   159  		ginkgo.DeferCleanup(cleanup)
   160  
   161  		tests := []e2evolume.Test{
   162  			{
   163  				Volume: *l.resource.VolSource,
   164  				Mode:   pattern.VolMode,
   165  				File:   "index.html",
   166  				// Must match content
   167  				ExpectedContent: fmt.Sprintf("Hello from %s from namespace %s",
   168  					dInfo.Name, f.Namespace.Name),
   169  			},
   170  		}
   171  		config := storageframework.ConvertTestConfig(l.config)
   172  		var fsGroup *int64
   173  		if framework.NodeOSDistroIs("windows") && dInfo.Capabilities[storageframework.CapFsGroup] {
   174  			fsGroupVal := int64(1234)
   175  			fsGroup = &fsGroupVal
   176  		}
   177  		// We set same fsGroup for both pods, because for same volumes (e.g.
   178  		// local), plugin skips setting fsGroup if volume is already mounted
   179  		// and we don't have reliable way to detect volumes are unmounted or
   180  		// not before starting the second pod.
   181  		e2evolume.InjectContent(ctx, f, config, fsGroup, pattern.FsType, tests)
   182  		if driver.GetDriverInfo().Capabilities[storageframework.CapPersistence] {
   183  			e2evolume.TestVolumeClient(ctx, f, config, fsGroup, pattern.FsType, tests)
   184  		} else {
   185  			ginkgo.By("Skipping persistence check for non-persistent volume")
   186  		}
   187  	})
   188  
   189  	// Exec works only on filesystem volumes
   190  	if pattern.VolMode != v1.PersistentVolumeBlock {
   191  		ginkgo.It("should allow exec of files on the volume", func(ctx context.Context) {
   192  			skipExecTest(driver)
   193  			init(ctx)
   194  			ginkgo.DeferCleanup(cleanup)
   195  
   196  			testScriptInPod(ctx, f, string(pattern.VolType), l.resource.VolSource, l.config)
   197  		})
   198  	}
   199  }
   200  
   201  func testScriptInPod(
   202  	ctx context.Context,
   203  	f *framework.Framework,
   204  	volumeType string,
   205  	source *v1.VolumeSource,
   206  	config *storageframework.PerTestConfig) {
   207  
   208  	const (
   209  		volPath = "/vol1"
   210  		volName = "vol1"
   211  	)
   212  	suffix := generateSuffixForPodName(volumeType)
   213  	fileName := fmt.Sprintf("test-%s", suffix)
   214  	var content string
   215  	if framework.NodeOSDistroIs("windows") {
   216  		content = fmt.Sprintf("ls -n %s", volPath)
   217  	} else {
   218  		content = fmt.Sprintf("ls %s", volPath)
   219  	}
   220  	command := generateWriteandExecuteScriptFileCmd(content, fileName, volPath)
   221  	pod := &v1.Pod{
   222  		ObjectMeta: metav1.ObjectMeta{
   223  			Name:      fmt.Sprintf("exec-volume-test-%s", suffix),
   224  			Namespace: f.Namespace.Name,
   225  		},
   226  		Spec: v1.PodSpec{
   227  			Containers: []v1.Container{
   228  				{
   229  					Name:    fmt.Sprintf("exec-container-%s", suffix),
   230  					Image:   e2epod.GetTestImage(imageutils.Nginx),
   231  					Command: command,
   232  					VolumeMounts: []v1.VolumeMount{
   233  						{
   234  							Name:      volName,
   235  							MountPath: volPath,
   236  						},
   237  					},
   238  				},
   239  			},
   240  			Volumes: []v1.Volume{
   241  				{
   242  					Name:         volName,
   243  					VolumeSource: *source,
   244  				},
   245  			},
   246  			RestartPolicy: v1.RestartPolicyNever,
   247  		},
   248  	}
   249  	e2epod.SetNodeSelection(&pod.Spec, config.ClientNodeSelection)
   250  	ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
   251  	e2eoutput.TestContainerOutput(ctx, f, "exec-volume-test", pod, 0, []string{fileName})
   252  
   253  	ginkgo.By(fmt.Sprintf("Deleting pod %s", pod.Name))
   254  	err := e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
   255  	framework.ExpectNoError(err, "while deleting pod")
   256  }
   257  
   258  // generateWriteandExecuteScriptFileCmd generates the corresponding command lines to write a file with the given file path
   259  // and also execute this file.
   260  // Depending on the Node OS is Windows or linux, the command will use powershell or /bin/sh
   261  func generateWriteandExecuteScriptFileCmd(content, fileName, filePath string) []string {
   262  	// for windows cluster, modify the Pod spec.
   263  	if framework.NodeOSDistroIs("windows") {
   264  		scriptName := fmt.Sprintf("%s.ps1", fileName)
   265  		fullPath := filepath.Join(filePath, scriptName)
   266  
   267  		cmd := "echo \"" + content + "\" > " + fullPath + "; .\\" + fullPath
   268  		framework.Logf("generated pod command %s", cmd)
   269  		return []string{"powershell", "/c", cmd}
   270  	}
   271  	scriptName := fmt.Sprintf("%s.sh", fileName)
   272  	fullPath := filepath.Join(filePath, scriptName)
   273  	cmd := fmt.Sprintf("echo \"%s\" > %s; chmod u+x %s; %s;", content, fullPath, fullPath, fullPath)
   274  	return []string{"/bin/sh", "-ec", cmd}
   275  }
   276  

View as plain text