k8s.io/kubernetes@v1.29.3/test/e2e/storage/testsuites/volumes.go (about) 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 }