k8s.io/kubernetes@v1.29.3/test/e2e/storage/testsuites/provisioning.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 package testsuites 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 "strings" 24 "sync" 25 "time" 26 27 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl" 28 29 "github.com/onsi/ginkgo/v2" 30 "github.com/onsi/gomega" 31 32 appsv1 "k8s.io/api/apps/v1" 33 v1 "k8s.io/api/core/v1" 34 storagev1 "k8s.io/api/storage/v1" 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 "k8s.io/apimachinery/pkg/api/resource" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/client-go/dynamic" 41 clientset "k8s.io/client-go/kubernetes" 42 "k8s.io/kubernetes/test/e2e/feature" 43 "k8s.io/kubernetes/test/e2e/framework" 44 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 45 e2epv "k8s.io/kubernetes/test/e2e/framework/pv" 46 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 47 e2evolume "k8s.io/kubernetes/test/e2e/framework/volume" 48 storageframework "k8s.io/kubernetes/test/e2e/storage/framework" 49 storageutils "k8s.io/kubernetes/test/e2e/storage/utils" 50 admissionapi "k8s.io/pod-security-admission/api" 51 ) 52 53 // StorageClassTest represents parameters to be used by provisioning tests. 54 // Not all parameters are used by all tests. 55 type StorageClassTest struct { 56 Client clientset.Interface 57 Timeouts *framework.TimeoutContext 58 Claim *v1.PersistentVolumeClaim 59 SourceClaim *v1.PersistentVolumeClaim 60 Class *storagev1.StorageClass 61 Name string 62 CloudProviders []string 63 Provisioner string 64 Parameters map[string]string 65 DelayBinding bool 66 ClaimSize string 67 ExpectedSize string 68 PvCheck func(ctx context.Context, claim *v1.PersistentVolumeClaim) 69 VolumeMode v1.PersistentVolumeMode 70 AllowVolumeExpansion bool 71 NodeSelection e2epod.NodeSelection 72 MountOptions []string 73 } 74 75 type provisioningTestSuite struct { 76 tsInfo storageframework.TestSuiteInfo 77 } 78 79 // InitCustomProvisioningTestSuite returns provisioningTestSuite that implements TestSuite interface 80 // using custom test patterns 81 func InitCustomProvisioningTestSuite(patterns []storageframework.TestPattern) storageframework.TestSuite { 82 return &provisioningTestSuite{ 83 tsInfo: storageframework.TestSuiteInfo{ 84 Name: "provisioning", 85 TestPatterns: patterns, 86 SupportedSizeRange: e2evolume.SizeRange{ 87 Min: "1Mi", 88 }, 89 }, 90 } 91 } 92 93 // InitProvisioningTestSuite returns provisioningTestSuite that implements TestSuite interface\ 94 // using test suite default patterns 95 func InitProvisioningTestSuite() storageframework.TestSuite { 96 patterns := []storageframework.TestPattern{ 97 storageframework.DefaultFsDynamicPV, 98 storageframework.BlockVolModeDynamicPV, 99 storageframework.NtfsDynamicPV, 100 } 101 return InitCustomProvisioningTestSuite(patterns) 102 } 103 104 func (p *provisioningTestSuite) GetTestSuiteInfo() storageframework.TestSuiteInfo { 105 return p.tsInfo 106 } 107 108 func (p *provisioningTestSuite) SkipUnsupportedTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) { 109 // Check preconditions. 110 if pattern.VolType != storageframework.DynamicPV { 111 e2eskipper.Skipf("Suite %q does not support %v", p.tsInfo.Name, pattern.VolType) 112 } 113 dInfo := driver.GetDriverInfo() 114 if pattern.VolMode == v1.PersistentVolumeBlock && !dInfo.Capabilities[storageframework.CapBlock] { 115 e2eskipper.Skipf("Driver %s doesn't support %v -- skipping", dInfo.Name, pattern.VolMode) 116 } 117 } 118 119 func (p *provisioningTestSuite) DefineTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) { 120 type local struct { 121 config *storageframework.PerTestConfig 122 123 testCase *StorageClassTest 124 cs clientset.Interface 125 pvc *v1.PersistentVolumeClaim 126 sourcePVC *v1.PersistentVolumeClaim 127 sc *storagev1.StorageClass 128 129 migrationCheck *migrationOpCheck 130 } 131 var ( 132 dInfo = driver.GetDriverInfo() 133 dDriver storageframework.DynamicPVTestDriver 134 l local 135 ) 136 137 // Beware that it also registers an AfterEach which renders f unusable. Any code using 138 // f must run inside an It or Context callback. 139 f := framework.NewFrameworkWithCustomTimeouts("provisioning", storageframework.GetDriverTimeouts(driver)) 140 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 141 142 init := func(ctx context.Context) { 143 l = local{} 144 dDriver, _ = driver.(storageframework.DynamicPVTestDriver) 145 // Now do the more expensive test initialization. 146 l.config = driver.PrepareTest(ctx, f) 147 l.migrationCheck = newMigrationOpCheck(ctx, f.ClientSet, f.ClientConfig(), dInfo.InTreePluginName) 148 ginkgo.DeferCleanup(l.migrationCheck.validateMigrationVolumeOpCounts) 149 l.cs = l.config.Framework.ClientSet 150 testVolumeSizeRange := p.GetTestSuiteInfo().SupportedSizeRange 151 driverVolumeSizeRange := dDriver.GetDriverInfo().SupportedSizeRange 152 claimSize, err := storageutils.GetSizeRangesIntersection(testVolumeSizeRange, driverVolumeSizeRange) 153 framework.ExpectNoError(err, "determine intersection of test size range %+v and driver size range %+v", testVolumeSizeRange, driverVolumeSizeRange) 154 155 l.sc = dDriver.GetDynamicProvisionStorageClass(ctx, l.config, pattern.FsType) 156 if l.sc == nil { 157 e2eskipper.Skipf("Driver %q does not define Dynamic Provision StorageClass - skipping", dInfo.Name) 158 } 159 l.pvc = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 160 ClaimSize: claimSize, 161 StorageClassName: &(l.sc.Name), 162 VolumeMode: &pattern.VolMode, 163 }, l.config.Framework.Namespace.Name) 164 l.sourcePVC = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 165 ClaimSize: claimSize, 166 StorageClassName: &(l.sc.Name), 167 VolumeMode: &pattern.VolMode, 168 }, l.config.Framework.Namespace.Name) 169 framework.Logf("In creating storage class object and pvc objects for driver - sc: %v, pvc: %v, src-pvc: %v", l.sc, l.pvc, l.sourcePVC) 170 l.testCase = &StorageClassTest{ 171 Client: l.config.Framework.ClientSet, 172 Timeouts: f.Timeouts, 173 Claim: l.pvc, 174 SourceClaim: l.sourcePVC, 175 Class: l.sc, 176 Provisioner: l.sc.Provisioner, 177 ClaimSize: claimSize, 178 ExpectedSize: claimSize, 179 VolumeMode: pattern.VolMode, 180 NodeSelection: l.config.ClientNodeSelection, 181 } 182 } 183 184 ginkgo.It("should provision storage with mount options", func(ctx context.Context) { 185 if dInfo.SupportedMountOption == nil { 186 e2eskipper.Skipf("Driver %q does not define supported mount option - skipping", dInfo.Name) 187 } 188 if pattern.VolMode == v1.PersistentVolumeBlock { 189 e2eskipper.Skipf("Block volumes do not support mount options - skipping") 190 } 191 192 init(ctx) 193 194 l.testCase.Class.MountOptions = dInfo.SupportedMountOption.Union(dInfo.RequiredMountOption).List() 195 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 196 PVWriteReadSingleNodeCheck(ctx, l.cs, f.Timeouts, claim, l.config.ClientNodeSelection) 197 } 198 SetupStorageClass(ctx, l.testCase.Client, l.testCase.Class) 199 200 l.testCase.TestDynamicProvisioning(ctx) 201 }) 202 203 f.It("should provision storage with snapshot data source", feature.VolumeSnapshotDataSource, func(ctx context.Context) { 204 if !dInfo.Capabilities[storageframework.CapSnapshotDataSource] { 205 e2eskipper.Skipf("Driver %q does not support populating data from snapshot - skipping", dInfo.Name) 206 } 207 if !dInfo.SupportedFsType.Has(pattern.FsType) { 208 e2eskipper.Skipf("Driver %q does not support %q fs type - skipping", dInfo.Name, pattern.FsType) 209 } 210 211 sDriver, ok := driver.(storageframework.SnapshottableTestDriver) 212 if !ok { 213 framework.Failf("Driver %q has CapSnapshotDataSource but does not implement SnapshottableTestDriver", dInfo.Name) 214 } 215 216 init(ctx) 217 218 dc := l.config.Framework.DynamicClient 219 testConfig := storageframework.ConvertTestConfig(l.config) 220 expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) 221 dataSourceRef := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, l.pvc, l.sc, sDriver, pattern.VolMode, expectedContent) 222 223 l.pvc.Spec.DataSourceRef = dataSourceRef 224 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 225 ginkgo.By("checking whether the created volume has the pre-populated data") 226 tests := []e2evolume.Test{ 227 { 228 Volume: *storageutils.CreateVolumeSource(claim.Name, false /* readOnly */), 229 Mode: pattern.VolMode, 230 File: "index.html", 231 ExpectedContent: expectedContent, 232 }, 233 } 234 e2evolume.TestVolumeClientSlow(ctx, f, testConfig, nil, "", tests) 235 } 236 l.testCase.TestDynamicProvisioning(ctx) 237 }) 238 239 f.It("should provision storage with snapshot data source (ROX mode)", feature.VolumeSnapshotDataSource, func(ctx context.Context) { 240 if !dInfo.Capabilities[storageframework.CapSnapshotDataSource] { 241 e2eskipper.Skipf("Driver %q does not support populating data from snapshot - skipping", dInfo.Name) 242 } 243 if !dInfo.SupportedFsType.Has(pattern.FsType) { 244 e2eskipper.Skipf("Driver %q does not support %q fs type - skipping", dInfo.Name, pattern.FsType) 245 } 246 if !dInfo.Capabilities[storageframework.CapReadOnlyMany] { 247 e2eskipper.Skipf("Driver %q does not support ROX access mode - skipping", dInfo.Name) 248 } 249 250 sDriver, ok := driver.(storageframework.SnapshottableTestDriver) 251 if !ok { 252 framework.Failf("Driver %q has CapSnapshotDataSource but does not implement SnapshottableTestDriver", dInfo.Name) 253 } 254 255 init(ctx) 256 257 dc := l.config.Framework.DynamicClient 258 testConfig := storageframework.ConvertTestConfig(l.config) 259 expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) 260 dataSourceRef := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, l.pvc, l.sc, sDriver, pattern.VolMode, expectedContent) 261 262 l.pvc.Spec.DataSourceRef = dataSourceRef 263 l.pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{ 264 v1.PersistentVolumeAccessMode(v1.ReadOnlyMany), 265 } 266 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 267 ginkgo.By("checking whether the created volume has the pre-populated data") 268 tests := []e2evolume.Test{ 269 { 270 Volume: *storageutils.CreateVolumeSource(claim.Name, false /* readOnly */), 271 Mode: pattern.VolMode, 272 File: "index.html", 273 ExpectedContent: expectedContent, 274 }, 275 } 276 e2evolume.TestVolumeClientSlow(ctx, f, testConfig, nil, "", tests) 277 } 278 l.testCase.TestDynamicProvisioning(ctx) 279 }) 280 281 f.It("should provision storage with any volume data source", f.WithSerial(), func(ctx context.Context) { 282 if len(dInfo.InTreePluginName) != 0 { 283 e2eskipper.Skipf("AnyVolumeDataSource feature only works with CSI drivers - skipping") 284 } 285 if pattern.VolMode == v1.PersistentVolumeBlock { 286 e2eskipper.Skipf("Test for Block volumes is not implemented - skipping") 287 } 288 289 init(ctx) 290 291 ginkgo.By("Creating validator namespace") 292 valNamespace, err := f.CreateNamespace(ctx, fmt.Sprintf("%s-val", f.Namespace.Name), map[string]string{ 293 "e2e-framework": f.BaseName, 294 "e2e-test-namespace": f.Namespace.Name, 295 }) 296 framework.ExpectNoError(err) 297 ginkgo.DeferCleanup(f.DeleteNamespace, valNamespace.Name) 298 299 ginkgo.By("Deploying validator") 300 valManifests := []string{ 301 "test/e2e/testing-manifests/storage-csi/any-volume-datasource/crd/populator.storage.k8s.io_volumepopulators.yaml", 302 "test/e2e/testing-manifests/storage-csi/any-volume-datasource/volume-data-source-validator/rbac-data-source-validator.yaml", 303 "test/e2e/testing-manifests/storage-csi/any-volume-datasource/volume-data-source-validator/setup-data-source-validator.yaml", 304 } 305 err = storageutils.CreateFromManifests(ctx, f, valNamespace, 306 func(item interface{}) error { return nil }, 307 valManifests...) 308 309 framework.ExpectNoError(err) 310 311 ginkgo.By("Creating populator namespace") 312 popNamespace, err := f.CreateNamespace(ctx, fmt.Sprintf("%s-pop", f.Namespace.Name), map[string]string{ 313 "e2e-framework": f.BaseName, 314 "e2e-test-namespace": f.Namespace.Name, 315 }) 316 framework.ExpectNoError(err) 317 ginkgo.DeferCleanup(f.DeleteNamespace, popNamespace.Name) 318 319 ginkgo.By("Deploying hello-populator") 320 popManifests := []string{ 321 "test/e2e/testing-manifests/storage-csi/any-volume-datasource/crd/hello-populator-crd.yaml", 322 "test/e2e/testing-manifests/storage-csi/any-volume-datasource/hello-populator-deploy.yaml", 323 } 324 err = storageutils.CreateFromManifests(ctx, f, popNamespace, 325 func(item interface{}) error { 326 switch item := item.(type) { 327 case *appsv1.Deployment: 328 for i, container := range item.Spec.Template.Spec.Containers { 329 switch container.Name { 330 case "hello": 331 args := []string{} 332 var foundNS, foundImage bool 333 for _, arg := range container.Args { 334 if strings.HasPrefix(arg, "--namespace=") { 335 args = append(args, fmt.Sprintf("--namespace=%s", popNamespace.Name)) 336 foundNS = true 337 } else if strings.HasPrefix(arg, "--image-name=") { 338 args = append(args, fmt.Sprintf("--image-name=%s", container.Image)) 339 foundImage = true 340 } else { 341 args = append(args, arg) 342 } 343 } 344 if !foundNS { 345 args = append(args, fmt.Sprintf("--namespace=%s", popNamespace.Name)) 346 framework.Logf("container name: %s", container.Name) 347 } 348 if !foundImage { 349 args = append(args, fmt.Sprintf("--image-name=%s", container.Image)) 350 framework.Logf("container image: %s", container.Image) 351 } 352 container.Args = args 353 item.Spec.Template.Spec.Containers[i] = container 354 default: 355 } 356 } 357 } 358 return nil 359 }, 360 popManifests...) 361 362 framework.ExpectNoError(err) 363 364 dc := l.config.Framework.DynamicClient 365 366 // Make hello-populator handle Hello resource in hello.example.com group 367 ginkgo.By("Creating VolumePopulator CR datasource") 368 volumePopulatorGVR := schema.GroupVersionResource{Group: "populator.storage.k8s.io", Version: "v1beta1", Resource: "volumepopulators"} 369 helloPopulatorCR := &unstructured.Unstructured{ 370 Object: map[string]interface{}{ 371 "kind": "VolumePopulator", 372 "apiVersion": "populator.storage.k8s.io/v1beta1", 373 "metadata": map[string]interface{}{ 374 "name": fmt.Sprintf("%s-%s", "hello-populator", f.Namespace.Name), 375 }, 376 "sourceKind": map[string]interface{}{ 377 "group": "hello.example.com", 378 "kind": "Hello", 379 }, 380 }, 381 } 382 383 _, err = dc.Resource(volumePopulatorGVR).Create(ctx, helloPopulatorCR, metav1.CreateOptions{}) 384 framework.ExpectNoError(err) 385 386 defer func() { 387 framework.Logf("deleting VolumePopulator CR datasource %q/%q", helloPopulatorCR.GetNamespace(), helloPopulatorCR.GetName()) 388 err = dc.Resource(volumePopulatorGVR).Delete(ctx, helloPopulatorCR.GetName(), metav1.DeleteOptions{}) 389 if err != nil && !apierrors.IsNotFound(err) { 390 framework.Failf("Error deleting VolumePopulator CR datasource %q. Error: %v", helloPopulatorCR.GetName(), err) 391 } 392 }() 393 394 // Create Hello CR datasource 395 ginkgo.By("Creating Hello CR datasource") 396 helloCRName := "example-hello" 397 fileName := fmt.Sprintf("example-%s.txt", f.Namespace.Name) 398 expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) 399 helloGVR := schema.GroupVersionResource{Group: "hello.example.com", Version: "v1alpha1", Resource: "hellos"} 400 helloCR := &unstructured.Unstructured{ 401 Object: map[string]interface{}{ 402 "kind": "Hello", 403 "apiVersion": "hello.example.com/v1alpha1", 404 "metadata": map[string]interface{}{ 405 "name": helloCRName, 406 "namespace": f.Namespace.Name, 407 }, 408 "spec": map[string]interface{}{ 409 "fileName": fileName, 410 "fileContents": expectedContent, 411 }, 412 }, 413 } 414 415 _, err = dc.Resource(helloGVR).Namespace(f.Namespace.Name).Create(ctx, helloCR, metav1.CreateOptions{}) 416 framework.ExpectNoError(err) 417 418 defer func() { 419 framework.Logf("deleting Hello CR datasource %q/%q", helloCR.GetNamespace(), helloCR.GetName()) 420 err = dc.Resource(helloGVR).Namespace(helloCR.GetNamespace()).Delete(ctx, helloCR.GetName(), metav1.DeleteOptions{}) 421 if err != nil && !apierrors.IsNotFound(err) { 422 framework.Failf("Error deleting Hello CR datasource %q. Error: %v", helloCR.GetName(), err) 423 } 424 }() 425 426 apiGroup := "hello.example.com" 427 l.pvc.Spec.DataSourceRef = &v1.TypedObjectReference{ 428 APIGroup: &apiGroup, 429 Kind: "Hello", 430 Name: helloCRName, 431 } 432 433 testConfig := storageframework.ConvertTestConfig(l.config) 434 l.testCase.NodeSelection = testConfig.ClientNodeSelection 435 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 436 ginkgo.By("checking whether the created volume has the pre-populated data") 437 tests := []e2evolume.Test{ 438 { 439 Volume: *storageutils.CreateVolumeSource(claim.Name, false /* readOnly */), 440 Mode: pattern.VolMode, 441 File: fileName, 442 ExpectedContent: expectedContent, 443 }, 444 } 445 e2evolume.TestVolumeClientSlow(ctx, f, testConfig, nil, "", tests) 446 } 447 448 SetupStorageClass(ctx, l.testCase.Client, l.testCase.Class) 449 450 l.testCase.TestDynamicProvisioning(ctx) 451 }) 452 453 f.It("should provision correct filesystem size when restoring snapshot to larger size pvc", feature.VolumeSnapshotDataSource, func(ctx context.Context) { 454 //TODO: remove skip when issue is resolved - https://github.com/kubernetes/kubernetes/issues/113359 455 if framework.NodeOSDistroIs("windows") { 456 e2eskipper.Skipf("Test is not valid Windows - skipping") 457 } 458 459 if pattern.VolMode == "Block" { 460 e2eskipper.Skipf("Test is not valid for Block volume mode - skipping") 461 } 462 463 if dInfo.Capabilities[storageframework.CapFSResizeFromSourceNotSupported] { 464 e2eskipper.Skipf("Driver %q does not support filesystem resizing - skipping", dInfo.Name) 465 } 466 467 if !dInfo.Capabilities[storageframework.CapSnapshotDataSource] { 468 e2eskipper.Skipf("Driver %q does not support populating data from snapshot - skipping", dInfo.Name) 469 } 470 471 if !dInfo.SupportedFsType.Has(pattern.FsType) { 472 e2eskipper.Skipf("Driver %q does not support %q fs type - skipping", dInfo.Name, pattern.FsType) 473 } 474 475 sDriver, ok := driver.(storageframework.SnapshottableTestDriver) 476 if !ok { 477 framework.Failf("Driver %q has CapSnapshotDataSource but does not implement SnapshottableTestDriver", dInfo.Name) 478 } 479 480 init(ctx) 481 pvc2 := l.pvc.DeepCopy() 482 l.pvc.Name = "pvc-origin" 483 dc := l.config.Framework.DynamicClient 484 testConfig := storageframework.ConvertTestConfig(l.config) 485 dataSourceRef := prepareSnapshotDataSourceForProvisioning(ctx, f, testConfig, l.config, pattern, l.cs, dc, l.pvc, l.sc, sDriver, pattern.VolMode, "") 486 487 // Get the created PVC and record the actual size of the pv (from pvc status). 488 c, err := l.testCase.Client.CoreV1().PersistentVolumeClaims(l.pvc.Namespace).Get(ctx, l.pvc.Name, metav1.GetOptions{}) 489 framework.ExpectNoError(err, "Failed to get pvc: %v", err) 490 actualPVSize := c.Status.Capacity.Storage().Value() 491 492 createdClaims := []*v1.PersistentVolumeClaim{c} 493 pod, err := e2epod.CreatePod(ctx, l.testCase.Client, f.Namespace.Name, nil, createdClaims, f.NamespacePodSecurityLevel, "") 494 framework.ExpectNoError(err, "Failed to create pod: %v", err) 495 496 // Mount path should not be empty. 497 mountpath := findVolumeMountPath(pod, c) 498 gomega.Expect(mountpath).ShouldNot(gomega.BeEmpty()) 499 500 // Save filesystem size of the origin volume. 501 originFSSize, err := getFilesystemSizeBytes(pod, mountpath) 502 framework.ExpectNoError(err, "Failed to obtain filesystem size of a volume mount: %v", err) 503 504 // For the new PVC, request a size that is larger than the origin PVC actually provisioned. 505 storageRequest := resource.NewQuantity(actualPVSize, resource.BinarySI) 506 storageRequest.Add(resource.MustParse("1Gi")) 507 pvc2.Spec.Resources.Requests = v1.ResourceList{ 508 v1.ResourceStorage: *storageRequest, 509 } 510 511 // Set PVC snapshot data source. 512 pvc2.Spec.DataSourceRef = dataSourceRef 513 514 // Create a new claim and a pod that will use the new PVC. 515 c2, err := l.testCase.Client.CoreV1().PersistentVolumeClaims(pvc2.Namespace).Create(ctx, pvc2, metav1.CreateOptions{}) 516 framework.ExpectNoError(err, "Failed to create pvc: %v", err) 517 createdClaims2 := []*v1.PersistentVolumeClaim{c2} 518 pod2, err := e2epod.CreatePod(ctx, l.testCase.Client, f.Namespace.Name, nil, createdClaims2, f.NamespacePodSecurityLevel, "") 519 framework.ExpectNoError(err, "Failed to create pod: %v", err) 520 521 // Mount path should not be empty. 522 mountpath2 := findVolumeMountPath(pod2, c2) 523 gomega.Expect(mountpath2).ShouldNot(gomega.BeEmpty()) 524 525 // Get actual size of the restored filesystem. 526 restoredFSSize, err := getFilesystemSizeBytes(pod2, mountpath2) 527 framework.ExpectNoError(err, "Failed to obtain filesystem size of a volume mount: %v", err) 528 529 // Filesystem of a restored volume should be larger than the origin. 530 msg := fmt.Sprintf("Filesystem resize failed when restoring from snapshot to PVC with larger size. "+ 531 "Restored fs size: %v bytes is not larger than origin fs size: %v bytes.\n"+ 532 "HINT: Your driver needs to check the volume in NodeStageVolume and resize fs if needed.\n"+ 533 "HINT: For an example patch see: https://github.com/kubernetes/cloud-provider-openstack/pull/1563/files", 534 restoredFSSize, originFSSize) 535 gomega.Expect(restoredFSSize).Should(gomega.BeNumerically(">", originFSSize), msg) 536 }) 537 538 ginkgo.It("should provision storage with pvc data source", func(ctx context.Context) { 539 if !dInfo.Capabilities[storageframework.CapPVCDataSource] { 540 e2eskipper.Skipf("Driver %q does not support cloning - skipping", dInfo.Name) 541 } 542 init(ctx) 543 544 if l.config.ClientNodeSelection.Name == "" { 545 // Schedule all pods to the same topology segment (e.g. a cloud availability zone), some 546 // drivers don't support cloning across them. 547 if err := ensureTopologyRequirements(ctx, &l.config.ClientNodeSelection, l.cs, dInfo, 1); err != nil { 548 framework.Failf("Error setting topology requirements: %v", err) 549 } 550 } 551 testConfig := storageframework.ConvertTestConfig(l.config) 552 expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) 553 dataSourceRef := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) 554 l.pvc.Spec.DataSourceRef = dataSourceRef 555 l.testCase.NodeSelection = testConfig.ClientNodeSelection 556 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 557 ginkgo.By("checking whether the created volume has the pre-populated data") 558 tests := []e2evolume.Test{ 559 { 560 Volume: *storageutils.CreateVolumeSource(claim.Name, false /* readOnly */), 561 Mode: pattern.VolMode, 562 File: "index.html", 563 ExpectedContent: expectedContent, 564 }, 565 } 566 e2evolume.TestVolumeClientSlow(ctx, f, testConfig, nil, "", tests) 567 } 568 // Cloning fails if the source disk is still in the process of detaching, so we wait for the VolumeAttachment to be removed before cloning. 569 volumeAttachment := e2evolume.GetVolumeAttachmentName(ctx, f.ClientSet, testConfig, l.testCase.Provisioner, dataSourceRef.Name, l.sourcePVC.Namespace) 570 framework.ExpectNoError(e2evolume.WaitForVolumeAttachmentTerminated(ctx, volumeAttachment, f.ClientSet, f.Timeouts.DataSourceProvision)) 571 l.testCase.TestDynamicProvisioning(ctx) 572 }) 573 574 ginkgo.It("should provision storage with pvc data source (ROX mode)", func(ctx context.Context) { 575 if !dInfo.Capabilities[storageframework.CapPVCDataSource] { 576 e2eskipper.Skipf("Driver %q does not support cloning - skipping", dInfo.Name) 577 } 578 if !dInfo.Capabilities[storageframework.CapReadOnlyMany] { 579 e2eskipper.Skipf("Driver %q does not support ROX access mode - skipping", dInfo.Name) 580 } 581 init(ctx) 582 583 if l.config.ClientNodeSelection.Name == "" { 584 // Schedule all pods to the same topology segment (e.g. a cloud availability zone), some 585 // drivers don't support cloning across them. 586 if err := ensureTopologyRequirements(ctx, &l.config.ClientNodeSelection, l.cs, dInfo, 1); err != nil { 587 framework.Failf("Error setting topology requirements: %v", err) 588 } 589 } 590 testConfig := storageframework.ConvertTestConfig(l.config) 591 expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) 592 dataSourceRef := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) 593 l.pvc.Spec.DataSourceRef = dataSourceRef 594 l.pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{ 595 v1.PersistentVolumeAccessMode(v1.ReadOnlyMany), 596 } 597 l.testCase.NodeSelection = testConfig.ClientNodeSelection 598 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 599 ginkgo.By("checking whether the created volume has the pre-populated data") 600 tests := []e2evolume.Test{ 601 { 602 Volume: *storageutils.CreateVolumeSource(claim.Name, false /* readOnly */), 603 Mode: pattern.VolMode, 604 File: "index.html", 605 ExpectedContent: expectedContent, 606 }, 607 } 608 e2evolume.TestVolumeClientSlow(ctx, f, testConfig, nil, "", tests) 609 } 610 // Cloning fails if the source disk is still in the process of detaching, so we wait for the VolumeAttachment to be removed before cloning. 611 volumeAttachment := e2evolume.GetVolumeAttachmentName(ctx, f.ClientSet, testConfig, l.testCase.Provisioner, dataSourceRef.Name, l.sourcePVC.Namespace) 612 framework.ExpectNoError(e2evolume.WaitForVolumeAttachmentTerminated(ctx, volumeAttachment, f.ClientSet, f.Timeouts.DataSourceProvision)) 613 l.testCase.TestDynamicProvisioning(ctx) 614 }) 615 616 f.It("should provision storage with pvc data source in parallel", f.WithSlow(), func(ctx context.Context) { 617 // Test cloning a single volume multiple times. 618 if !dInfo.Capabilities[storageframework.CapPVCDataSource] { 619 e2eskipper.Skipf("Driver %q does not support cloning - skipping", dInfo.Name) 620 } 621 if pattern.VolMode == v1.PersistentVolumeBlock && !dInfo.Capabilities[storageframework.CapBlock] { 622 e2eskipper.Skipf("Driver %q does not support block volumes - skipping", dInfo.Name) 623 } 624 625 init(ctx) 626 627 if l.config.ClientNodeSelection.Name == "" { 628 // Schedule all pods to the same topology segment (e.g. a cloud availability zone), some 629 // drivers don't support cloning across them. 630 if err := ensureTopologyRequirements(ctx, &l.config.ClientNodeSelection, l.cs, dInfo, 1); err != nil { 631 framework.Failf("Error setting topology requirements: %v", err) 632 } 633 } 634 testConfig := storageframework.ConvertTestConfig(l.config) 635 expectedContent := fmt.Sprintf("Hello from namespace %s", f.Namespace.Name) 636 dataSourceRef := preparePVCDataSourceForProvisioning(ctx, f, testConfig, l.cs, l.sourcePVC, l.sc, pattern.VolMode, expectedContent) 637 l.pvc.Spec.DataSourceRef = dataSourceRef 638 639 var wg sync.WaitGroup 640 for i := 0; i < 5; i++ { 641 wg.Add(1) 642 go func(i int) { 643 defer ginkgo.GinkgoRecover() 644 defer wg.Done() 645 ginkgo.By(fmt.Sprintf("Cloning volume nr. %d", i)) 646 // Each go routine must have its own pod prefix 647 myTestConfig := testConfig 648 myTestConfig.Prefix = fmt.Sprintf("%s-%d", myTestConfig.Prefix, i) 649 650 t := *l.testCase 651 t.NodeSelection = testConfig.ClientNodeSelection 652 t.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 653 ginkgo.By(fmt.Sprintf("checking whether the created volume %d has the pre-populated data", i)) 654 tests := []e2evolume.Test{ 655 { 656 Volume: *storageutils.CreateVolumeSource(claim.Name, false /* readOnly */), 657 Mode: pattern.VolMode, 658 File: "index.html", 659 ExpectedContent: expectedContent, 660 }, 661 } 662 e2evolume.TestVolumeClientSlow(ctx, f, myTestConfig, nil, "", tests) 663 } 664 // Cloning fails if the source disk is still in the process of detaching, so we wait for the VolumeAttachment to be removed before cloning. 665 volumeAttachment := e2evolume.GetVolumeAttachmentName(ctx, f.ClientSet, testConfig, l.testCase.Provisioner, dataSourceRef.Name, l.sourcePVC.Namespace) 666 framework.ExpectNoError(e2evolume.WaitForVolumeAttachmentTerminated(ctx, volumeAttachment, f.ClientSet, f.Timeouts.DataSourceProvision)) 667 t.TestDynamicProvisioning(ctx) 668 }(i) 669 } 670 wg.Wait() 671 }) 672 673 ginkgo.It("should mount multiple PV pointing to the same storage on the same node", func(ctx context.Context) { 674 // csi-hostpath driver does not support this test case. In this test case, we have 2 PV containing the same underlying storage. 675 // during the NodeStage call for the second volume, csi-hostpath fails the call, because it thinks the volume is already staged at a different path. 676 // Note: This is not an issue with driver like PD CSI where the NodeStage is a no-op for block mode. 677 if pattern.VolMode == v1.PersistentVolumeBlock { 678 e2eskipper.Skipf("skipping multiple PV mount test for block mode") 679 } 680 681 if !dInfo.Capabilities[storageframework.CapMultiplePVsSameID] { 682 e2eskipper.Skipf("this driver does not support multiple PVs with the same volumeHandle") 683 } 684 685 init(ctx) 686 687 l.testCase.PvCheck = func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 688 MultiplePVMountSingleNodeCheck(ctx, l.cs, f.Timeouts, claim, l.config.ClientNodeSelection) 689 } 690 SetupStorageClass(ctx, l.testCase.Client, l.testCase.Class) 691 692 l.testCase.TestDynamicProvisioning(ctx) 693 }) 694 } 695 696 // SetupStorageClass ensures that a StorageClass from a spec exists, if the StorageClass already exists 697 // then it's returned as it is, if it doesn't exist then it's created first 698 // and then returned, if the spec is nil then we return the `default` StorageClass 699 func SetupStorageClass( 700 ctx context.Context, 701 client clientset.Interface, 702 class *storagev1.StorageClass, 703 ) *storagev1.StorageClass { 704 gomega.Expect(client).NotTo(gomega.BeNil(), "SetupStorageClass.client is required") 705 706 var err error 707 var computedStorageClass *storagev1.StorageClass 708 if class != nil { 709 computedStorageClass, err = client.StorageV1().StorageClasses().Get(ctx, class.Name, metav1.GetOptions{}) 710 if err == nil { 711 // skip storageclass creation if it already exists 712 ginkgo.By("Storage class " + computedStorageClass.Name + " is already created, skipping creation.") 713 } else { 714 ginkgo.By("Creating a StorageClass") 715 class, err = client.StorageV1().StorageClasses().Create(ctx, class, metav1.CreateOptions{}) 716 framework.ExpectNoError(err) 717 computedStorageClass, err = client.StorageV1().StorageClasses().Get(ctx, class.Name, metav1.GetOptions{}) 718 framework.ExpectNoError(err) 719 clearComputedStorageClass := func(ctx context.Context) { 720 framework.Logf("deleting storage class %s", computedStorageClass.Name) 721 err := client.StorageV1().StorageClasses().Delete(ctx, computedStorageClass.Name, metav1.DeleteOptions{}) 722 if err != nil && !apierrors.IsNotFound(err) { 723 framework.ExpectNoError(err, "delete storage class") 724 } 725 } 726 ginkgo.DeferCleanup(clearComputedStorageClass) 727 } 728 } else { 729 // StorageClass is nil, so the default one will be used 730 scName, err := e2epv.GetDefaultStorageClassName(ctx, client) 731 framework.ExpectNoError(err) 732 ginkgo.By("Wanted storage class is nil, fetching default StorageClass=" + scName) 733 computedStorageClass, err = client.StorageV1().StorageClasses().Get(ctx, scName, metav1.GetOptions{}) 734 framework.ExpectNoError(err) 735 } 736 737 return computedStorageClass 738 } 739 740 // TestDynamicProvisioning tests dynamic provisioning with specified StorageClassTest 741 // it's assumed that the StorageClass `t.Class` is already provisioned, 742 // see #ProvisionStorageClass 743 func (t StorageClassTest) TestDynamicProvisioning(ctx context.Context) *v1.PersistentVolume { 744 var err error 745 client := t.Client 746 gomega.Expect(client).NotTo(gomega.BeNil(), "StorageClassTest.Client is required") 747 claim := t.Claim 748 gomega.Expect(claim).NotTo(gomega.BeNil(), "StorageClassTest.Claim is required") 749 gomega.Expect(claim.GenerateName).NotTo(gomega.BeEmpty(), "StorageClassTest.Claim.GenerateName must not be empty") 750 class := t.Class 751 gomega.Expect(class).NotTo(gomega.BeNil(), "StorageClassTest.Class is required") 752 class, err = client.StorageV1().StorageClasses().Get(ctx, class.Name, metav1.GetOptions{}) 753 framework.ExpectNoError(err, "StorageClass.Class "+class.Name+" couldn't be fetched from the cluster") 754 755 ginkgo.By(fmt.Sprintf("creating claim=%+v", claim)) 756 claim, err = client.CoreV1().PersistentVolumeClaims(claim.Namespace).Create(ctx, claim, metav1.CreateOptions{}) 757 framework.ExpectNoError(err) 758 defer func() { 759 framework.Logf("deleting claim %q/%q", claim.Namespace, claim.Name) 760 // typically this claim has already been deleted 761 err = client.CoreV1().PersistentVolumeClaims(claim.Namespace).Delete(ctx, claim.Name, metav1.DeleteOptions{}) 762 if err != nil && !apierrors.IsNotFound(err) { 763 framework.Failf("Error deleting claim %q. Error: %v", claim.Name, err) 764 } 765 }() 766 767 // ensure that the claim refers to the provisioned StorageClass 768 gomega.Expect(*claim.Spec.StorageClassName).To(gomega.Equal(class.Name)) 769 770 // if late binding is configured, create and delete a pod to provision the volume 771 if *class.VolumeBindingMode == storagev1.VolumeBindingWaitForFirstConsumer { 772 ginkgo.By(fmt.Sprintf("creating a pod referring to the class=%+v claim=%+v", class, claim)) 773 var podConfig *e2epod.Config = &e2epod.Config{ 774 NS: claim.Namespace, 775 PVCs: []*v1.PersistentVolumeClaim{claim}, 776 NodeSelection: t.NodeSelection, 777 } 778 779 var pod *v1.Pod 780 pod, err := e2epod.CreateSecPod(ctx, client, podConfig, t.Timeouts.DataSourceProvision) 781 // Delete pod now, otherwise PV can't be deleted below 782 framework.ExpectNoError(err) 783 e2epod.DeletePodOrFail(ctx, client, pod.Namespace, pod.Name) 784 } 785 786 // Run the checker 787 if t.PvCheck != nil { 788 t.PvCheck(ctx, claim) 789 } 790 791 pv := t.checkProvisioning(ctx, client, claim, class) 792 793 ginkgo.By(fmt.Sprintf("deleting claim %q/%q", claim.Namespace, claim.Name)) 794 framework.ExpectNoError(client.CoreV1().PersistentVolumeClaims(claim.Namespace).Delete(ctx, claim.Name, metav1.DeleteOptions{})) 795 796 // Wait for the PV to get deleted if reclaim policy is Delete. (If it's 797 // Retain, there's no use waiting because the PV won't be auto-deleted and 798 // it's expected for the caller to do it.) Technically, the first few delete 799 // attempts may fail, as the volume is still attached to a node because 800 // kubelet is slowly cleaning up the previous pod, however it should succeed 801 // in a couple of minutes. Wait 20 minutes (or whatever custom value is specified in 802 // t.Timeouts.PVDeleteSlow) to recover from random cloud hiccups. 803 if pv != nil && pv.Spec.PersistentVolumeReclaimPolicy == v1.PersistentVolumeReclaimDelete { 804 ginkgo.By(fmt.Sprintf("deleting the claim's PV %q", pv.Name)) 805 framework.ExpectNoError(e2epv.WaitForPersistentVolumeDeleted(ctx, client, pv.Name, 5*time.Second, t.Timeouts.PVDeleteSlow)) 806 } 807 808 return pv 809 } 810 811 // getBoundPV returns a PV details. 812 func getBoundPV(ctx context.Context, client clientset.Interface, pvc *v1.PersistentVolumeClaim) (*v1.PersistentVolume, error) { 813 // Get new copy of the claim 814 claim, err := client.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(ctx, pvc.Name, metav1.GetOptions{}) 815 if err != nil { 816 return nil, err 817 } 818 819 // Get the bound PV 820 pv, err := client.CoreV1().PersistentVolumes().Get(ctx, claim.Spec.VolumeName, metav1.GetOptions{}) 821 return pv, err 822 } 823 824 // checkProvisioning verifies that the claim is bound and has the correct properties 825 func (t StorageClassTest) checkProvisioning(ctx context.Context, client clientset.Interface, claim *v1.PersistentVolumeClaim, class *storagev1.StorageClass) *v1.PersistentVolume { 826 err := e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, client, claim.Namespace, claim.Name, framework.Poll, t.Timeouts.ClaimProvision) 827 framework.ExpectNoError(err) 828 829 ginkgo.By("checking the claim") 830 pv, err := getBoundPV(ctx, client, claim) 831 framework.ExpectNoError(err) 832 833 // Check sizes 834 expectedCapacity := resource.MustParse(t.ExpectedSize) 835 pvCapacity := pv.Spec.Capacity[v1.ResourceName(v1.ResourceStorage)] 836 gomega.Expect(pvCapacity.Value()).To(gomega.BeNumerically(">=", expectedCapacity.Value()), "pvCapacity is not greater or equal to expectedCapacity") 837 838 requestedCapacity := resource.MustParse(t.ClaimSize) 839 claimCapacity := claim.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] 840 gomega.Expect(claimCapacity.Value()).To(gomega.BeNumerically(">=", requestedCapacity.Value()), "claimCapacity is not greater or equal to requestedCapacity") 841 842 // Check PV properties 843 ginkgo.By("checking the PV") 844 845 // Every access mode in PV should be in PVC 846 gomega.Expect(pv.Spec.AccessModes).NotTo(gomega.BeZero()) 847 for _, pvMode := range pv.Spec.AccessModes { 848 found := false 849 for _, pvcMode := range claim.Spec.AccessModes { 850 if pvMode == pvcMode { 851 found = true 852 break 853 } 854 } 855 if !found { 856 framework.Failf("Actual access modes %v are not in claim's access mode", pv.Spec.AccessModes) 857 } 858 } 859 860 gomega.Expect(pv.Spec.ClaimRef.Name).To(gomega.Equal(claim.ObjectMeta.Name)) 861 gomega.Expect(pv.Spec.ClaimRef.Namespace).To(gomega.Equal(claim.ObjectMeta.Namespace)) 862 if class == nil { 863 gomega.Expect(pv.Spec.PersistentVolumeReclaimPolicy).To(gomega.Equal(v1.PersistentVolumeReclaimDelete)) 864 } else { 865 gomega.Expect(pv.Spec.PersistentVolumeReclaimPolicy).To(gomega.Equal(*class.ReclaimPolicy)) 866 gomega.Expect(pv.Spec.MountOptions).To(gomega.Equal(class.MountOptions)) 867 } 868 if claim.Spec.VolumeMode != nil { 869 gomega.Expect(pv.Spec.VolumeMode).NotTo(gomega.BeNil()) 870 gomega.Expect(*pv.Spec.VolumeMode).To(gomega.Equal(*claim.Spec.VolumeMode)) 871 } 872 return pv 873 } 874 875 // PVWriteReadSingleNodeCheck checks that a PV retains data on a single node 876 // and returns the PV. 877 // 878 // It starts two pods: 879 // - The first pod writes 'hello word' to the /mnt/test (= the volume) on one node. 880 // - The second pod runs grep 'hello world' on /mnt/test on the same node. 881 // 882 // The node is selected by Kubernetes when scheduling the first 883 // pod. It's then selected via its name for the second pod. 884 // 885 // If both succeed, Kubernetes actually allocated something that is 886 // persistent across pods. 887 // 888 // This is a common test that can be called from a StorageClassTest.PvCheck. 889 func PVWriteReadSingleNodeCheck(ctx context.Context, client clientset.Interface, timeouts *framework.TimeoutContext, claim *v1.PersistentVolumeClaim, node e2epod.NodeSelection) *v1.PersistentVolume { 890 ginkgo.By(fmt.Sprintf("checking the created volume is writable on node %+v", node)) 891 command := "echo 'hello world' > /mnt/test/data" 892 pod := StartInPodWithVolume(ctx, client, claim.Namespace, claim.Name, "pvc-volume-tester-writer", command, node) 893 ginkgo.DeferCleanup(func(ctx context.Context) { 894 // pod might be nil now. 895 StopPod(ctx, client, pod) 896 }) 897 framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, client, pod.Name, pod.Namespace, timeouts.PodStartSlow)) 898 runningPod, err := client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 899 framework.ExpectNoError(err, "get pod") 900 actualNodeName := runningPod.Spec.NodeName 901 StopPod(ctx, client, pod) 902 pod = nil // Don't stop twice. 903 904 // Get a new copy of the PV 905 e2evolume, err := getBoundPV(ctx, client, claim) 906 framework.ExpectNoError(err) 907 908 ginkgo.By(fmt.Sprintf("checking the created volume has the correct mount options, is readable and retains data on the same node %q", actualNodeName)) 909 command = "grep 'hello world' /mnt/test/data" 910 911 // We give the second pod the additional responsibility of checking the volume has 912 // been mounted with the PV's mount options, if the PV was provisioned with any 913 for _, option := range e2evolume.Spec.MountOptions { 914 // Get entry, get mount options at 6th word, replace brackets with commas 915 command += fmt.Sprintf(" && ( mount | grep 'on /mnt/test' | awk '{print $6}' | sed 's/^(/,/; s/)$/,/' | grep -q ,%s, )", option) 916 } 917 command += " || (mount | grep 'on /mnt/test'; false)" 918 919 if framework.NodeOSDistroIs("windows") { 920 // agnhost doesn't support mount 921 command = "grep 'hello world' /mnt/test/data" 922 } 923 RunInPodWithVolume(ctx, client, timeouts, claim.Namespace, claim.Name, "pvc-volume-tester-reader", command, e2epod.NodeSelection{Name: actualNodeName, Selector: node.Selector}) 924 return e2evolume 925 } 926 927 // PVMultiNodeCheck checks that a PV retains data when moved between nodes. 928 // 929 // It starts these pods: 930 // - The first pod writes 'hello word' to the /mnt/test (= the volume) on one node. 931 // - The second pod runs grep 'hello world' on /mnt/test on another node. 932 // 933 // The first node is selected by Kubernetes when scheduling the first pod. The second pod uses the same criteria, except that a special anti-affinity 934 // for the first node gets added. This test can only pass if the cluster has more than one 935 // suitable node. The caller has to ensure that. 936 // 937 // If all succeeds, Kubernetes actually allocated something that is 938 // persistent across pods and across nodes. 939 // 940 // This is a common test that can be called from a StorageClassTest.PvCheck. 941 func PVMultiNodeCheck(ctx context.Context, client clientset.Interface, timeouts *framework.TimeoutContext, claim *v1.PersistentVolumeClaim, node e2epod.NodeSelection) { 942 gomega.Expect(node.Name).To(gomega.BeZero(), "this test only works when not locked onto a single node") 943 944 var pod *v1.Pod 945 defer func() { 946 // passing pod = nil is okay. 947 StopPod(ctx, client, pod) 948 }() 949 950 ginkgo.By(fmt.Sprintf("checking the created volume is writable on node %+v", node)) 951 command := "echo 'hello world' > /mnt/test/data" 952 pod = StartInPodWithVolume(ctx, client, claim.Namespace, claim.Name, "pvc-writer-node1", command, node) 953 framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, client, pod.Name, pod.Namespace, timeouts.PodStartSlow)) 954 runningPod, err := client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 955 framework.ExpectNoError(err, "get pod") 956 actualNodeName := runningPod.Spec.NodeName 957 StopPod(ctx, client, pod) 958 pod = nil // Don't stop twice. 959 960 // Add node-anti-affinity. 961 secondNode := node 962 e2epod.SetAntiAffinity(&secondNode, actualNodeName) 963 ginkgo.By(fmt.Sprintf("checking the created volume is readable and retains data on another node %+v", secondNode)) 964 command = "grep 'hello world' /mnt/test/data" 965 pod = StartInPodWithVolume(ctx, client, claim.Namespace, claim.Name, "pvc-reader-node2", command, secondNode) 966 framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, client, pod.Name, pod.Namespace, timeouts.PodStartSlow)) 967 runningPod, err = client.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 968 framework.ExpectNoError(err, "get pod") 969 gomega.Expect(runningPod.Spec.NodeName).ToNot(gomega.Equal(actualNodeName), "second pod should have run on a different node") 970 StopPod(ctx, client, pod) 971 pod = nil 972 } 973 974 // TestBindingWaitForFirstConsumerMultiPVC tests the binding with WaitForFirstConsumer mode 975 func (t StorageClassTest) TestBindingWaitForFirstConsumerMultiPVC(ctx context.Context, claims []*v1.PersistentVolumeClaim, nodeSelector map[string]string, expectUnschedulable bool) ([]*v1.PersistentVolume, *v1.Node) { 976 var err error 977 gomega.Expect(claims).ToNot(gomega.BeEmpty()) 978 namespace := claims[0].Namespace 979 980 ginkgo.By("creating claims") 981 var claimNames []string 982 var createdClaims []*v1.PersistentVolumeClaim 983 for _, claim := range claims { 984 c, err := t.Client.CoreV1().PersistentVolumeClaims(claim.Namespace).Create(ctx, claim, metav1.CreateOptions{}) 985 claimNames = append(claimNames, c.Name) 986 createdClaims = append(createdClaims, c) 987 framework.ExpectNoError(err) 988 } 989 defer func() { 990 errors := map[string]error{} 991 for _, claim := range createdClaims { 992 err := e2epv.DeletePersistentVolumeClaim(ctx, t.Client, claim.Name, claim.Namespace) 993 if err != nil { 994 errors[claim.Name] = err 995 } 996 } 997 if len(errors) > 0 { 998 for claimName, err := range errors { 999 framework.Logf("Failed to delete PVC: %s due to error: %v", claimName, err) 1000 } 1001 } 1002 }() 1003 1004 // Wait for ClaimProvisionTimeout (across all PVCs in parallel) and make sure the phase did not become Bound i.e. the Wait errors out 1005 ginkgo.By("checking the claims are in pending state") 1006 err = e2epv.WaitForPersistentVolumeClaimsPhase(ctx, v1.ClaimBound, t.Client, namespace, claimNames, 2*time.Second /* Poll */, t.Timeouts.ClaimProvisionShort, true) 1007 framework.ExpectError(err) 1008 verifyPVCsPending(ctx, t.Client, createdClaims) 1009 1010 ginkgo.By("creating a pod referring to the claims") 1011 // Create a pod referring to the claim and wait for it to get to running 1012 var pod *v1.Pod 1013 if expectUnschedulable { 1014 pod, err = e2epod.CreateUnschedulablePod(ctx, t.Client, namespace, nodeSelector, createdClaims, admissionapi.LevelPrivileged, "" /* command */) 1015 } else { 1016 pod, err = e2epod.CreatePod(ctx, t.Client, namespace, nil /* nodeSelector */, createdClaims, admissionapi.LevelPrivileged, "" /* command */) 1017 } 1018 framework.ExpectNoError(err) 1019 ginkgo.DeferCleanup(func(ctx context.Context) error { 1020 e2epod.DeletePodOrFail(ctx, t.Client, pod.Namespace, pod.Name) 1021 return e2epod.WaitForPodNotFoundInNamespace(ctx, t.Client, pod.Name, pod.Namespace, t.Timeouts.PodDelete) 1022 }) 1023 if expectUnschedulable { 1024 // Verify that no claims are provisioned. 1025 verifyPVCsPending(ctx, t.Client, createdClaims) 1026 return nil, nil 1027 } 1028 1029 // collect node details 1030 node, err := t.Client.CoreV1().Nodes().Get(ctx, pod.Spec.NodeName, metav1.GetOptions{}) 1031 framework.ExpectNoError(err) 1032 1033 ginkgo.By("re-checking the claims to see they bound") 1034 var pvs []*v1.PersistentVolume 1035 for _, claim := range createdClaims { 1036 // Get new copy of the claim 1037 claim, err = t.Client.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(ctx, claim.Name, metav1.GetOptions{}) 1038 framework.ExpectNoError(err) 1039 // make sure claim did bind 1040 err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, t.Client, claim.Namespace, claim.Name, framework.Poll, t.Timeouts.ClaimProvision) 1041 framework.ExpectNoError(err) 1042 1043 pv, err := t.Client.CoreV1().PersistentVolumes().Get(ctx, claim.Spec.VolumeName, metav1.GetOptions{}) 1044 framework.ExpectNoError(err) 1045 pvs = append(pvs, pv) 1046 } 1047 gomega.Expect(pvs).To(gomega.HaveLen(len(createdClaims))) 1048 return pvs, node 1049 } 1050 1051 // RunInPodWithVolume runs a command in a pod with given claim mounted to /mnt directory. 1052 // It starts, checks, collects output and stops it. 1053 func RunInPodWithVolume(ctx context.Context, c clientset.Interface, t *framework.TimeoutContext, ns, claimName, podName, command string, node e2epod.NodeSelection) *v1.Pod { 1054 pod := StartInPodWithVolume(ctx, c, ns, claimName, podName, command, node) 1055 defer StopPod(ctx, c, pod) 1056 framework.ExpectNoError(e2epod.WaitForPodSuccessInNamespaceTimeout(ctx, c, pod.Name, pod.Namespace, t.PodStartSlow)) 1057 // get the latest status of the pod 1058 pod, err := c.CoreV1().Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) 1059 framework.ExpectNoError(err) 1060 return pod 1061 } 1062 1063 // StartInPodWithVolume starts a command in a pod with given claim mounted to /mnt directory 1064 // The caller is responsible for checking the pod and deleting it. 1065 func StartInPodWithVolume(ctx context.Context, c clientset.Interface, ns, claimName, podName, command string, node e2epod.NodeSelection) *v1.Pod { 1066 return StartInPodWithVolumeSource(ctx, c, v1.VolumeSource{ 1067 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 1068 ClaimName: claimName, 1069 }, 1070 }, ns, podName, command, node) 1071 } 1072 1073 // StartInPodWithVolumeSource starts a command in a pod with given volume mounted to /mnt directory 1074 // The caller is responsible for checking the pod and deleting it. 1075 func StartInPodWithVolumeSource(ctx context.Context, c clientset.Interface, volSrc v1.VolumeSource, ns, podName, command string, node e2epod.NodeSelection) *v1.Pod { 1076 pod := &v1.Pod{ 1077 TypeMeta: metav1.TypeMeta{ 1078 Kind: "Pod", 1079 APIVersion: "v1", 1080 }, 1081 ObjectMeta: metav1.ObjectMeta{ 1082 GenerateName: podName + "-", 1083 Labels: map[string]string{ 1084 "app": podName, 1085 }, 1086 }, 1087 Spec: v1.PodSpec{ 1088 Containers: []v1.Container{ 1089 { 1090 Name: "volume-tester", 1091 Image: e2epod.GetDefaultTestImage(), 1092 Command: e2epod.GenerateScriptCmd(command), 1093 VolumeMounts: []v1.VolumeMount{ 1094 { 1095 Name: "my-volume", 1096 MountPath: "/mnt/test", 1097 }, 1098 }, 1099 }, 1100 }, 1101 RestartPolicy: v1.RestartPolicyNever, 1102 Volumes: []v1.Volume{ 1103 { 1104 Name: "my-volume", 1105 VolumeSource: volSrc, 1106 }, 1107 }, 1108 }, 1109 } 1110 1111 e2epod.SetNodeSelection(&pod.Spec, node) 1112 pod, err := c.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}) 1113 framework.ExpectNoError(err, "Failed to create pod: %v", err) 1114 return pod 1115 } 1116 1117 // StopPod first tries to log the output of the pod's container, then deletes the pod and 1118 // waits for that to succeed. 1119 func StopPod(ctx context.Context, c clientset.Interface, pod *v1.Pod) { 1120 if pod == nil { 1121 return 1122 } 1123 body, err := c.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &v1.PodLogOptions{}).Do(ctx).Raw() 1124 if err != nil { 1125 framework.Logf("Error getting logs for pod %s: %v", pod.Name, err) 1126 } else { 1127 framework.Logf("Pod %s has the following logs: %s", pod.Name, body) 1128 } 1129 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, c, pod)) 1130 } 1131 1132 // StopPodAndDependents first tries to log the output of the pod's container, 1133 // then deletes the pod and waits for that to succeed. Also waits for all owned 1134 // resources to be deleted. 1135 func StopPodAndDependents(ctx context.Context, c clientset.Interface, timeouts *framework.TimeoutContext, pod *v1.Pod) { 1136 if pod == nil { 1137 return 1138 } 1139 body, err := c.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &v1.PodLogOptions{}).Do(ctx).Raw() 1140 if err != nil { 1141 framework.Logf("Error getting logs for pod %s: %v", pod.Name, err) 1142 } else { 1143 framework.Logf("Pod %s has the following logs: %s", pod.Name, body) 1144 } 1145 1146 // We must wait explicitly for removal of the generic ephemeral volume PVs. 1147 // For that we must find them first... 1148 pvs, err := c.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{}) 1149 framework.ExpectNoError(err, "list PVs") 1150 var podPVs []v1.PersistentVolume 1151 for _, pv := range pvs.Items { 1152 if pv.Spec.ClaimRef == nil || 1153 pv.Spec.ClaimRef.Namespace != pod.Namespace { 1154 continue 1155 } 1156 pvc, err := c.CoreV1().PersistentVolumeClaims(pod.Namespace).Get(ctx, pv.Spec.ClaimRef.Name, metav1.GetOptions{}) 1157 if err != nil && apierrors.IsNotFound(err) { 1158 // Must have been some unrelated PV, otherwise the PVC should exist. 1159 continue 1160 } 1161 framework.ExpectNoError(err, "get PVC") 1162 if pv.Spec.ClaimRef.UID == pvc.UID && metav1.IsControlledBy(pvc, pod) { 1163 podPVs = append(podPVs, pv) 1164 } 1165 } 1166 1167 framework.Logf("Deleting pod %q in namespace %q", pod.Name, pod.Namespace) 1168 deletionPolicy := metav1.DeletePropagationForeground 1169 err = c.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, 1170 metav1.DeleteOptions{ 1171 // If the pod is the owner of some resources (like ephemeral inline volumes), 1172 // then we want to be sure that those are also gone before we return. 1173 // Blocking pod deletion via metav1.DeletePropagationForeground achieves that. 1174 PropagationPolicy: &deletionPolicy, 1175 }) 1176 if err != nil { 1177 if apierrors.IsNotFound(err) { 1178 return // assume pod was already deleted 1179 } 1180 framework.Logf("pod Delete API error: %v", err) 1181 } 1182 framework.Logf("Wait up to %v for pod %q to be fully deleted", timeouts.PodDelete, pod.Name) 1183 framework.ExpectNoError(e2epod.WaitForPodNotFoundInNamespace(ctx, c, pod.Name, pod.Namespace, timeouts.PodDelete)) 1184 if len(podPVs) > 0 { 1185 for _, pv := range podPVs { 1186 // As with CSI inline volumes, we use the pod delete timeout here because conceptually 1187 // the volume deletion needs to be that fast (whatever "that" is). 1188 framework.Logf("Wait up to %v for pod PV %s to be fully deleted", timeouts.PodDelete, pv.Name) 1189 framework.ExpectNoError(e2epv.WaitForPersistentVolumeDeleted(ctx, c, pv.Name, 5*time.Second, timeouts.PodDelete)) 1190 } 1191 } 1192 } 1193 1194 func verifyPVCsPending(ctx context.Context, client clientset.Interface, pvcs []*v1.PersistentVolumeClaim) { 1195 for _, claim := range pvcs { 1196 // Get new copy of the claim 1197 claim, err := client.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(ctx, claim.Name, metav1.GetOptions{}) 1198 framework.ExpectNoError(err) 1199 gomega.Expect(claim.Status.Phase).To(gomega.Equal(v1.ClaimPending)) 1200 } 1201 } 1202 1203 func prepareSnapshotDataSourceForProvisioning( 1204 ctx context.Context, 1205 f *framework.Framework, 1206 config e2evolume.TestConfig, 1207 perTestConfig *storageframework.PerTestConfig, 1208 pattern storageframework.TestPattern, 1209 client clientset.Interface, 1210 dynamicClient dynamic.Interface, 1211 initClaim *v1.PersistentVolumeClaim, 1212 class *storagev1.StorageClass, 1213 sDriver storageframework.SnapshottableTestDriver, 1214 mode v1.PersistentVolumeMode, 1215 injectContent string, 1216 ) *v1.TypedObjectReference { 1217 SetupStorageClass(ctx, client, class) 1218 1219 if initClaim.ResourceVersion != "" { 1220 ginkgo.By("Skipping creation of PVC, it already exists") 1221 } else { 1222 ginkgo.By("[Initialize dataSource]creating a initClaim") 1223 updatedClaim, err := client.CoreV1().PersistentVolumeClaims(initClaim.Namespace).Create(ctx, initClaim, metav1.CreateOptions{}) 1224 if apierrors.IsAlreadyExists(err) { 1225 err = nil 1226 } 1227 framework.ExpectNoError(err) 1228 initClaim = updatedClaim 1229 } 1230 1231 // write namespace to the /mnt/test (= the volume). 1232 tests := []e2evolume.Test{ 1233 { 1234 Volume: *storageutils.CreateVolumeSource(initClaim.Name, false /* readOnly */), 1235 Mode: mode, 1236 File: "index.html", 1237 ExpectedContent: injectContent, 1238 }, 1239 } 1240 e2evolume.InjectContent(ctx, f, config, nil, "", tests) 1241 1242 parameters := map[string]string{} 1243 snapshotResource := storageframework.CreateSnapshotResource(ctx, sDriver, perTestConfig, pattern, initClaim.GetName(), initClaim.GetNamespace(), f.Timeouts, parameters) 1244 group := "snapshot.storage.k8s.io" 1245 dataSourceRef := &v1.TypedObjectReference{ 1246 APIGroup: &group, 1247 Kind: "VolumeSnapshot", 1248 Name: snapshotResource.Vs.GetName(), 1249 } 1250 1251 cleanupFunc := func(ctx context.Context) { 1252 framework.Logf("deleting initClaim %q/%q", initClaim.Namespace, initClaim.Name) 1253 err := client.CoreV1().PersistentVolumeClaims(initClaim.Namespace).Delete(ctx, initClaim.Name, metav1.DeleteOptions{}) 1254 if err != nil && !apierrors.IsNotFound(err) { 1255 framework.Failf("Error deleting initClaim %q. Error: %v", initClaim.Name, err) 1256 } 1257 1258 err = snapshotResource.CleanupResource(ctx, f.Timeouts) 1259 framework.ExpectNoError(err) 1260 } 1261 ginkgo.DeferCleanup(cleanupFunc) 1262 1263 return dataSourceRef 1264 } 1265 1266 func preparePVCDataSourceForProvisioning( 1267 ctx context.Context, 1268 f *framework.Framework, 1269 config e2evolume.TestConfig, 1270 client clientset.Interface, 1271 source *v1.PersistentVolumeClaim, 1272 class *storagev1.StorageClass, 1273 mode v1.PersistentVolumeMode, 1274 injectContent string, 1275 ) *v1.TypedObjectReference { 1276 SetupStorageClass(ctx, client, class) 1277 1278 if source.ResourceVersion != "" { 1279 ginkgo.By("Skipping creation of PVC, it already exists") 1280 } else { 1281 ginkgo.By("[Initialize dataSource]creating a source PVC") 1282 var err error 1283 source, err = client.CoreV1().PersistentVolumeClaims(source.Namespace).Create(ctx, source, metav1.CreateOptions{}) 1284 framework.ExpectNoError(err) 1285 } 1286 1287 tests := []e2evolume.Test{ 1288 { 1289 Volume: *storageutils.CreateVolumeSource(source.Name, false /* readOnly */), 1290 Mode: mode, 1291 File: "index.html", 1292 ExpectedContent: injectContent, 1293 }, 1294 } 1295 e2evolume.InjectContent(ctx, f, config, nil, "", tests) 1296 1297 dataSourceRef := &v1.TypedObjectReference{ 1298 Kind: "PersistentVolumeClaim", 1299 Name: source.GetName(), 1300 } 1301 1302 cleanupFunc := func(ctx context.Context) { 1303 framework.Logf("deleting source PVC %q/%q", source.Namespace, source.Name) 1304 err := client.CoreV1().PersistentVolumeClaims(source.Namespace).Delete(ctx, source.Name, metav1.DeleteOptions{}) 1305 if err != nil && !apierrors.IsNotFound(err) { 1306 framework.Failf("Error deleting source PVC %q. Error: %v", source.Name, err) 1307 } 1308 } 1309 ginkgo.DeferCleanup(cleanupFunc) 1310 1311 return dataSourceRef 1312 } 1313 1314 // findVolumeMountPath looks for a claim name inside a pod and returns an absolute path of its volume mount point. 1315 func findVolumeMountPath(pod *v1.Pod, claim *v1.PersistentVolumeClaim) string { 1316 // Find volume name that the pod2 assigned to pvc. 1317 volumeName = "" 1318 for _, volume := range pod.Spec.Volumes { 1319 if volume.PersistentVolumeClaim.ClaimName == claim.Name { 1320 volumeName = volume.Name 1321 break 1322 } 1323 } 1324 1325 // Find where the pod mounted the volume inside a container. 1326 containerMountPath := "" 1327 for _, volumeMount := range pod.Spec.Containers[0].VolumeMounts { 1328 if volumeMount.Name == volumeName { 1329 containerMountPath = volumeMount.MountPath 1330 break 1331 } 1332 } 1333 return containerMountPath 1334 } 1335 1336 // getFilesystemSizeBytes returns a total size of a filesystem on given mountPath inside a pod. You can use findVolumeMountPath for mountPath lookup. 1337 func getFilesystemSizeBytes(pod *v1.Pod, mountPath string) (int, error) { 1338 cmd := fmt.Sprintf("stat -f -c %%s %v", mountPath) 1339 blockSize, err := e2ekubectl.RunKubectl(pod.Namespace, "exec", pod.Name, "--", "/bin/sh", "-c", cmd) 1340 if err != nil { 1341 return 0, err 1342 } 1343 1344 cmd = fmt.Sprintf("stat -f -c %%b %v", mountPath) 1345 blockCount, err := e2ekubectl.RunKubectl(pod.Namespace, "exec", pod.Name, "--", "/bin/sh", "-c", cmd) 1346 if err != nil { 1347 return 0, err 1348 } 1349 1350 bs, err := strconv.Atoi(strings.TrimSuffix(blockSize, "\n")) 1351 if err != nil { 1352 return 0, err 1353 } 1354 1355 bc, err := strconv.Atoi(strings.TrimSuffix(blockCount, "\n")) 1356 if err != nil { 1357 return 0, err 1358 } 1359 1360 return bs * bc, nil 1361 } 1362 1363 // MultiplePVMountSingleNodeCheck checks that multiple PV pointing to the same underlying storage can be mounted simultaneously on a single node. 1364 // 1365 // Steps: 1366 // - Start Pod1 using PVC1, PV1 (which points to a underlying volume v) on node N1. 1367 // - Create PVC2, PV2 and prebind them. PV2 points to the same underlying volume v. 1368 // - Start Pod2 using PVC2, PV2 (which points to a underlying volume v) on node N1. 1369 func MultiplePVMountSingleNodeCheck(ctx context.Context, client clientset.Interface, timeouts *framework.TimeoutContext, claim *v1.PersistentVolumeClaim, node e2epod.NodeSelection) { 1370 pod1Config := e2epod.Config{ 1371 NS: claim.Namespace, 1372 NodeSelection: node, 1373 PVCs: []*v1.PersistentVolumeClaim{claim}, 1374 } 1375 pod1, err := e2epod.CreateSecPodWithNodeSelection(ctx, client, &pod1Config, timeouts.PodStart) 1376 framework.ExpectNoError(err) 1377 defer func() { 1378 ginkgo.By(fmt.Sprintf("Deleting Pod %s/%s", pod1.Namespace, pod1.Name)) 1379 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, client, pod1)) 1380 }() 1381 ginkgo.By(fmt.Sprintf("Created Pod %s/%s on node %s", pod1.Namespace, pod1.Name, pod1.Spec.NodeName)) 1382 1383 // Create new PV which points to the same underlying storage. Retain policy is used so that deletion of second PVC does not trigger the deletion of its bound PV and underlying storage. 1384 e2evolume, err := getBoundPV(ctx, client, claim) 1385 framework.ExpectNoError(err) 1386 pv2Config := e2epv.PersistentVolumeConfig{ 1387 NamePrefix: fmt.Sprintf("%s-", "pv"), 1388 StorageClassName: *claim.Spec.StorageClassName, 1389 PVSource: e2evolume.Spec.PersistentVolumeSource, 1390 AccessModes: e2evolume.Spec.AccessModes, 1391 VolumeMode: e2evolume.Spec.VolumeMode, 1392 ReclaimPolicy: v1.PersistentVolumeReclaimRetain, 1393 } 1394 1395 pvc2Config := e2epv.PersistentVolumeClaimConfig{ 1396 NamePrefix: fmt.Sprintf("%s-", "pvc"), 1397 StorageClassName: &claim.Namespace, 1398 AccessModes: e2evolume.Spec.AccessModes, 1399 VolumeMode: e2evolume.Spec.VolumeMode, 1400 } 1401 1402 pv2, pvc2, err := e2epv.CreatePVCPV(ctx, client, timeouts, pv2Config, pvc2Config, claim.Namespace, true) 1403 framework.ExpectNoError(err, "PVC, PV creation failed") 1404 framework.Logf("Created PVC %s/%s and PV %s", pvc2.Namespace, pvc2.Name, pv2.Name) 1405 1406 pod2Config := e2epod.Config{ 1407 NS: pvc2.Namespace, 1408 NodeSelection: e2epod.NodeSelection{Name: pod1.Spec.NodeName, Selector: node.Selector}, 1409 PVCs: []*v1.PersistentVolumeClaim{pvc2}, 1410 } 1411 pod2, err := e2epod.CreateSecPodWithNodeSelection(ctx, client, &pod2Config, timeouts.PodStart) 1412 framework.ExpectNoError(err) 1413 ginkgo.By(fmt.Sprintf("Created Pod %s/%s on node %s", pod2.Namespace, pod2.Name, pod2.Spec.NodeName)) 1414 1415 ginkgo.By(fmt.Sprintf("Deleting Pod %s/%s", pod2.Namespace, pod2.Name)) 1416 framework.ExpectNoError(e2epod.DeletePodWithWait(ctx, client, pod2)) 1417 1418 err = e2epv.DeletePersistentVolumeClaim(ctx, client, pvc2.Name, pvc2.Namespace) 1419 framework.ExpectNoError(err, "Failed to delete PVC: %s/%s", pvc2.Namespace, pvc2.Name) 1420 1421 err = e2epv.DeletePersistentVolume(ctx, client, pv2.Name) 1422 framework.ExpectNoError(err, "Failed to delete PV: %s", pv2.Name) 1423 }