k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/storage/volume_provisioning.go (about) 1 //go:build !providerless 2 // +build !providerless 3 4 /* 5 Copyright 2016 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 storage 21 22 import ( 23 "context" 24 "fmt" 25 "strings" 26 "time" 27 28 "github.com/onsi/ginkgo/v2" 29 "github.com/onsi/gomega" 30 31 v1 "k8s.io/api/core/v1" 32 rbacv1 "k8s.io/api/rbac/v1" 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/apimachinery/pkg/util/rand" 38 "k8s.io/apimachinery/pkg/util/wait" 39 "k8s.io/apiserver/pkg/authentication/serviceaccount" 40 clientset "k8s.io/client-go/kubernetes" 41 storageutil "k8s.io/kubernetes/pkg/apis/storage/util" 42 "k8s.io/kubernetes/test/e2e/feature" 43 "k8s.io/kubernetes/test/e2e/framework" 44 e2eauth "k8s.io/kubernetes/test/e2e/framework/auth" 45 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 46 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 47 e2epv "k8s.io/kubernetes/test/e2e/framework/pv" 48 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 49 "k8s.io/kubernetes/test/e2e/storage/testsuites" 50 "k8s.io/kubernetes/test/e2e/storage/utils" 51 admissionapi "k8s.io/pod-security-admission/api" 52 ) 53 54 const ( 55 // Plugin name of the external provisioner 56 externalPluginName = "example.com/nfs" 57 ) 58 59 var _ = utils.SIGDescribe("Dynamic Provisioning", func() { 60 f := framework.NewDefaultFramework("volume-provisioning") 61 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 62 63 // filled in BeforeEach 64 var c clientset.Interface 65 var timeouts *framework.TimeoutContext 66 var ns string 67 68 ginkgo.BeforeEach(func() { 69 c = f.ClientSet 70 ns = f.Namespace.Name 71 timeouts = f.Timeouts 72 }) 73 74 f.Describe("DynamicProvisioner", framework.WithSlow(), feature.StorageProvider, func() { 75 ginkgo.It("should provision storage with different parameters", func(ctx context.Context) { 76 77 // This test checks that dynamic provisioning can provision a volume 78 // that can be used to persist data among pods. 79 tests := []testsuites.StorageClassTest{ 80 // GCE/GKE 81 { 82 Name: "SSD PD on GCE/GKE", 83 CloudProviders: []string{"gce", "gke"}, 84 Timeouts: f.Timeouts, 85 Provisioner: "kubernetes.io/gce-pd", 86 Parameters: map[string]string{ 87 "type": "pd-ssd", 88 "zone": getRandomClusterZone(ctx, c), 89 }, 90 ClaimSize: "1.5Gi", 91 ExpectedSize: "2Gi", 92 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 93 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 94 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 95 }, 96 }, 97 { 98 Name: "HDD PD on GCE/GKE", 99 CloudProviders: []string{"gce", "gke"}, 100 Timeouts: f.Timeouts, 101 Provisioner: "kubernetes.io/gce-pd", 102 Parameters: map[string]string{ 103 "type": "pd-standard", 104 }, 105 ClaimSize: "1.5Gi", 106 ExpectedSize: "2Gi", 107 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 108 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 109 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 110 }, 111 }, 112 // AWS 113 { 114 Name: "gp2 EBS on AWS", 115 CloudProviders: []string{"aws"}, 116 Timeouts: f.Timeouts, 117 Provisioner: "kubernetes.io/aws-ebs", 118 Parameters: map[string]string{ 119 "type": "gp2", 120 "zone": getRandomClusterZone(ctx, c), 121 }, 122 ClaimSize: "1.5Gi", 123 ExpectedSize: "2Gi", 124 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 125 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 126 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 127 }, 128 }, 129 { 130 Name: "io1 EBS on AWS", 131 CloudProviders: []string{"aws"}, 132 Timeouts: f.Timeouts, 133 Provisioner: "kubernetes.io/aws-ebs", 134 Parameters: map[string]string{ 135 "type": "io1", 136 "iopsPerGB": "50", 137 }, 138 ClaimSize: "3.5Gi", 139 ExpectedSize: "4Gi", // 4 GiB is minimum for io1 140 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 141 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 142 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 143 }, 144 }, 145 { 146 Name: "sc1 EBS on AWS", 147 CloudProviders: []string{"aws"}, 148 Timeouts: f.Timeouts, 149 Provisioner: "kubernetes.io/aws-ebs", 150 Parameters: map[string]string{ 151 "type": "sc1", 152 }, 153 ClaimSize: "500Gi", // minimum for sc1 154 ExpectedSize: "500Gi", 155 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 156 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 157 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 158 }, 159 }, 160 { 161 Name: "st1 EBS on AWS", 162 CloudProviders: []string{"aws"}, 163 Timeouts: f.Timeouts, 164 Provisioner: "kubernetes.io/aws-ebs", 165 Parameters: map[string]string{ 166 "type": "st1", 167 }, 168 ClaimSize: "500Gi", // minimum for st1 169 ExpectedSize: "500Gi", 170 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 171 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 172 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 173 }, 174 }, 175 { 176 Name: "encrypted EBS on AWS", 177 CloudProviders: []string{"aws"}, 178 Timeouts: f.Timeouts, 179 Provisioner: "kubernetes.io/aws-ebs", 180 Parameters: map[string]string{ 181 "encrypted": "true", 182 }, 183 ClaimSize: "1Gi", 184 ExpectedSize: "1Gi", 185 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 186 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 187 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 188 }, 189 }, 190 // OpenStack generic tests (works on all OpenStack deployments) 191 { 192 Name: "generic Cinder volume on OpenStack", 193 CloudProviders: []string{"openstack"}, 194 Timeouts: f.Timeouts, 195 Provisioner: "kubernetes.io/cinder", 196 Parameters: map[string]string{}, 197 ClaimSize: "1.5Gi", 198 ExpectedSize: "2Gi", 199 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 200 testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 201 }, 202 }, 203 { 204 Name: "Cinder volume with empty volume type and zone on OpenStack", 205 CloudProviders: []string{"openstack"}, 206 Timeouts: f.Timeouts, 207 Provisioner: "kubernetes.io/cinder", 208 Parameters: map[string]string{ 209 "type": "", 210 "availability": "", 211 }, 212 ClaimSize: "1.5Gi", 213 ExpectedSize: "2Gi", 214 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 215 testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 216 }, 217 }, 218 // vSphere generic test 219 { 220 Name: "generic vSphere volume", 221 CloudProviders: []string{"vsphere"}, 222 Timeouts: f.Timeouts, 223 Provisioner: "kubernetes.io/vsphere-volume", 224 Parameters: map[string]string{}, 225 ClaimSize: "1.5Gi", 226 ExpectedSize: "1.5Gi", 227 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 228 testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 229 }, 230 }, 231 // Azure 232 { 233 Name: "Azure disk volume with empty sku and location", 234 CloudProviders: []string{"azure"}, 235 Timeouts: f.Timeouts, 236 Provisioner: "kubernetes.io/azure-disk", 237 Parameters: map[string]string{}, 238 ClaimSize: "1Gi", 239 ExpectedSize: "1Gi", 240 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 241 testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 242 }, 243 }, 244 } 245 246 for i, t := range tests { 247 // Beware of closure, use local variables instead of those from 248 // outer scope 249 test := t 250 251 if !framework.ProviderIs(test.CloudProviders...) { 252 framework.Logf("Skipping %q: cloud providers is not %v", test.Name, test.CloudProviders) 253 continue 254 } 255 256 if zone, ok := test.Parameters["zone"]; ok { 257 gomega.Expect(zone).ToNot(gomega.BeEmpty(), "expect at least one zone") 258 } 259 260 ginkgo.By("Testing " + test.Name) 261 suffix := fmt.Sprintf("%d", i) 262 test.Client = c 263 264 // overwrite StorageClass spec with provisioned StorageClass 265 storageClass := testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, suffix)) 266 267 test.Class = storageClass 268 test.Claim = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 269 ClaimSize: test.ClaimSize, 270 StorageClassName: &test.Class.Name, 271 VolumeMode: &test.VolumeMode, 272 }, ns) 273 274 test.TestDynamicProvisioning(ctx) 275 } 276 }) 277 278 ginkgo.It("should provision storage with non-default reclaim policy Retain", func(ctx context.Context) { 279 e2eskipper.SkipUnlessProviderIs("gce", "gke") 280 281 test := testsuites.StorageClassTest{ 282 Client: c, 283 Name: "HDD PD on GCE/GKE", 284 CloudProviders: []string{"gce", "gke"}, 285 Provisioner: "kubernetes.io/gce-pd", 286 Timeouts: f.Timeouts, 287 Parameters: map[string]string{ 288 "type": "pd-standard", 289 }, 290 ClaimSize: "1Gi", 291 ExpectedSize: "1Gi", 292 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 293 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, f.Timeouts, claim, e2epod.NodeSelection{}) 294 gomega.Expect(volume).NotTo(gomega.BeNil(), "get bound PV") 295 }, 296 } 297 test.Class = newStorageClass(test, ns, "reclaimpolicy") 298 retain := v1.PersistentVolumeReclaimRetain 299 test.Class.ReclaimPolicy = &retain 300 storageClass := testsuites.SetupStorageClass(ctx, test.Client, test.Class) 301 test.Class = storageClass 302 303 test.Claim = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 304 ClaimSize: test.ClaimSize, 305 StorageClassName: &test.Class.Name, 306 VolumeMode: &test.VolumeMode, 307 }, ns) 308 309 pv := test.TestDynamicProvisioning(ctx) 310 311 ginkgo.By(fmt.Sprintf("waiting for the provisioned PV %q to enter phase %s", pv.Name, v1.VolumeReleased)) 312 framework.ExpectNoError(e2epv.WaitForPersistentVolumePhase(ctx, v1.VolumeReleased, c, pv.Name, 1*time.Second, 30*time.Second)) 313 314 ginkgo.By(fmt.Sprintf("deleting the storage asset backing the PV %q", pv.Name)) 315 framework.ExpectNoError(e2epv.DeletePDWithRetry(ctx, pv.Spec.GCEPersistentDisk.PDName)) 316 317 ginkgo.By(fmt.Sprintf("deleting the PV %q", pv.Name)) 318 framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pv.Name), "Failed to delete PV ", pv.Name) 319 framework.ExpectNoError(e2epv.WaitForPersistentVolumeDeleted(ctx, c, pv.Name, 1*time.Second, 30*time.Second)) 320 }) 321 322 ginkgo.It("should test that deleting a claim before the volume is provisioned deletes the volume.", func(ctx context.Context) { 323 // This case tests for the regressions of a bug fixed by PR #21268 324 // REGRESSION: Deleting the PVC before the PV is provisioned can result in the PV 325 // not being deleted. 326 // NOTE: Polls until no PVs are detected, times out at 5 minutes. 327 328 e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") 329 330 const raceAttempts int = 100 331 var residualPVs []*v1.PersistentVolume 332 ginkgo.By(fmt.Sprintf("Creating and deleting PersistentVolumeClaims %d times", raceAttempts)) 333 test := testsuites.StorageClassTest{ 334 Name: "deletion race", 335 Provisioner: "", // Use a native one based on current cloud provider 336 Timeouts: f.Timeouts, 337 ClaimSize: "1Gi", 338 } 339 340 class := newStorageClass(test, ns, "race") 341 class, err := c.StorageV1().StorageClasses().Create(ctx, class, metav1.CreateOptions{}) 342 framework.ExpectNoError(err) 343 ginkgo.DeferCleanup(deleteStorageClass, c, class.Name) 344 345 // To increase chance of detection, attempt multiple iterations 346 for i := 0; i < raceAttempts; i++ { 347 prefix := fmt.Sprintf("race-%d", i) 348 claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 349 NamePrefix: prefix, 350 ClaimSize: test.ClaimSize, 351 StorageClassName: &class.Name, 352 VolumeMode: &test.VolumeMode, 353 }, ns) 354 tmpClaim, err := e2epv.CreatePVC(ctx, c, ns, claim) 355 framework.ExpectNoError(err) 356 framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, tmpClaim.Name, ns)) 357 } 358 359 ginkgo.By(fmt.Sprintf("Checking for residual PersistentVolumes associated with StorageClass %s", class.Name)) 360 residualPVs, err = waitForProvisionedVolumesDeleted(ctx, c, class.Name) 361 // Cleanup the test resources before breaking 362 ginkgo.DeferCleanup(deleteProvisionedVolumesAndDisks, c, residualPVs) 363 framework.ExpectNoError(err, "PersistentVolumes were not deleted as expected. %d remain", len(residualPVs)) 364 365 framework.Logf("0 PersistentVolumes remain.") 366 }) 367 368 ginkgo.It("deletion should be idempotent", func(ctx context.Context) { 369 // This test ensures that deletion of a volume is idempotent. 370 // It creates a PV with Retain policy, deletes underlying AWS / GCE 371 // volume and changes the reclaim policy to Delete. 372 // PV controller should delete the PV even though the underlying volume 373 // is already deleted. 374 e2eskipper.SkipUnlessProviderIs("gce", "gke", "aws") 375 ginkgo.By("creating PD") 376 diskName, err := e2epv.CreatePDWithRetry(ctx) 377 framework.ExpectNoError(err) 378 379 ginkgo.By("creating PV") 380 pv := e2epv.MakePersistentVolume(e2epv.PersistentVolumeConfig{ 381 NamePrefix: "volume-idempotent-delete-", 382 // Use Retain to keep the PV, the test will change it to Delete 383 // when the time comes. 384 ReclaimPolicy: v1.PersistentVolumeReclaimRetain, 385 AccessModes: []v1.PersistentVolumeAccessMode{ 386 v1.ReadWriteOnce, 387 }, 388 Capacity: "1Gi", 389 // PV is bound to non-existing PVC, so it's reclaim policy is 390 // executed immediately 391 Prebind: &v1.PersistentVolumeClaim{ 392 ObjectMeta: metav1.ObjectMeta{ 393 Name: "dummy-claim-name", 394 Namespace: ns, 395 UID: types.UID("01234567890"), 396 }, 397 }, 398 }) 399 switch framework.TestContext.Provider { 400 case "aws": 401 pv.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{ 402 AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{ 403 VolumeID: diskName, 404 }, 405 } 406 case "gce", "gke": 407 pv.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{ 408 GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{ 409 PDName: diskName, 410 }, 411 } 412 } 413 pv, err = c.CoreV1().PersistentVolumes().Create(ctx, pv, metav1.CreateOptions{}) 414 framework.ExpectNoError(err) 415 416 ginkgo.By("waiting for the PV to get Released") 417 err = e2epv.WaitForPersistentVolumePhase(ctx, v1.VolumeReleased, c, pv.Name, 2*time.Second, timeouts.PVReclaim) 418 framework.ExpectNoError(err) 419 420 ginkgo.By("deleting the PD") 421 err = e2epv.DeletePVSource(ctx, &pv.Spec.PersistentVolumeSource) 422 framework.ExpectNoError(err) 423 424 ginkgo.By("changing the PV reclaim policy") 425 pv, err = c.CoreV1().PersistentVolumes().Get(ctx, pv.Name, metav1.GetOptions{}) 426 framework.ExpectNoError(err) 427 pv.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimDelete 428 pv, err = c.CoreV1().PersistentVolumes().Update(ctx, pv, metav1.UpdateOptions{}) 429 framework.ExpectNoError(err) 430 431 ginkgo.By("waiting for the PV to get deleted") 432 err = e2epv.WaitForPersistentVolumeDeleted(ctx, c, pv.Name, 5*time.Second, timeouts.PVDelete) 433 framework.ExpectNoError(err) 434 }) 435 }) 436 437 ginkgo.Describe("DynamicProvisioner External", func() { 438 f.It("should let an external dynamic provisioner create and delete persistent volumes", f.WithSlow(), func(ctx context.Context) { 439 // external dynamic provisioner pods need additional permissions provided by the 440 // persistent-volume-provisioner clusterrole and a leader-locking role 441 serviceAccountName := "default" 442 subject := rbacv1.Subject{ 443 Kind: rbacv1.ServiceAccountKind, 444 Namespace: ns, 445 Name: serviceAccountName, 446 } 447 448 err := e2eauth.BindClusterRole(ctx, c.RbacV1(), "system:persistent-volume-provisioner", ns, subject) 449 framework.ExpectNoError(err) 450 451 roleName := "leader-locking-nfs-provisioner" 452 _, err = f.ClientSet.RbacV1().Roles(ns).Create(ctx, &rbacv1.Role{ 453 ObjectMeta: metav1.ObjectMeta{ 454 Name: roleName, 455 }, 456 Rules: []rbacv1.PolicyRule{{ 457 APIGroups: []string{""}, 458 Resources: []string{"endpoints"}, 459 Verbs: []string{"get", "list", "watch", "create", "update", "patch"}, 460 }}, 461 }, metav1.CreateOptions{}) 462 framework.ExpectNoError(err, "Failed to create leader-locking role") 463 464 err = e2eauth.BindRoleInNamespace(ctx, c.RbacV1(), roleName, ns, subject) 465 framework.ExpectNoError(err) 466 467 err = e2eauth.WaitForAuthorizationUpdate(ctx, c.AuthorizationV1(), 468 serviceaccount.MakeUsername(ns, serviceAccountName), 469 "", "get", schema.GroupResource{Group: "storage.k8s.io", Resource: "storageclasses"}, true) 470 framework.ExpectNoError(err, "Failed to update authorization") 471 472 ginkgo.By("creating an external dynamic provisioner pod") 473 pod := utils.StartExternalProvisioner(ctx, c, ns, externalPluginName) 474 ginkgo.DeferCleanup(e2epod.DeletePodOrFail, c, ns, pod.Name) 475 476 ginkgo.By("creating a StorageClass") 477 test := testsuites.StorageClassTest{ 478 Client: c, 479 Name: "external provisioner test", 480 Provisioner: externalPluginName, 481 Timeouts: f.Timeouts, 482 ClaimSize: "1500Mi", 483 ExpectedSize: "1500Mi", 484 } 485 486 storageClass := testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, "external")) 487 test.Class = storageClass 488 489 test.Claim = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 490 ClaimSize: test.ClaimSize, 491 StorageClassName: &test.Class.Name, 492 VolumeMode: &test.VolumeMode, 493 }, ns) 494 495 ginkgo.By("creating a claim with a external provisioning annotation") 496 497 test.TestDynamicProvisioning(ctx) 498 }) 499 }) 500 501 ginkgo.Describe("DynamicProvisioner Default", func() { 502 f.It("should create and delete default persistent volumes", f.WithSlow(), func(ctx context.Context) { 503 e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") 504 e2epv.SkipIfNoDefaultStorageClass(ctx, c) 505 506 ginkgo.By("creating a claim with no annotation") 507 test := testsuites.StorageClassTest{ 508 Client: c, 509 Name: "default", 510 Timeouts: f.Timeouts, 511 ClaimSize: "2Gi", 512 ExpectedSize: "2Gi", 513 } 514 515 test.Claim = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 516 ClaimSize: test.ClaimSize, 517 VolumeMode: &test.VolumeMode, 518 }, ns) 519 // NOTE: this test assumes that there's a default storageclass 520 test.Class = testsuites.SetupStorageClass(ctx, test.Client, nil) 521 522 test.TestDynamicProvisioning(ctx) 523 }) 524 525 // Modifying the default storage class can be disruptive to other tests that depend on it 526 f.It("should be disabled by changing the default annotation", f.WithSerial(), f.WithDisruptive(), func(ctx context.Context) { 527 e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") 528 e2epv.SkipIfNoDefaultStorageClass(ctx, c) 529 530 scName, scErr := e2epv.GetDefaultStorageClassName(ctx, c) 531 framework.ExpectNoError(scErr) 532 533 test := testsuites.StorageClassTest{ 534 Name: "default", 535 Timeouts: f.Timeouts, 536 ClaimSize: "2Gi", 537 } 538 539 ginkgo.By("setting the is-default StorageClass annotation to false") 540 verifyDefaultStorageClass(ctx, c, scName, true) 541 ginkgo.DeferCleanup(updateDefaultStorageClass, c, scName, "true") 542 updateDefaultStorageClass(ctx, c, scName, "false") 543 544 ginkgo.By("creating a claim with default storageclass and expecting it to timeout") 545 claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 546 ClaimSize: test.ClaimSize, 547 VolumeMode: &test.VolumeMode, 548 }, ns) 549 claim, err := c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, claim, metav1.CreateOptions{}) 550 framework.ExpectNoError(err) 551 ginkgo.DeferCleanup(e2epv.DeletePersistentVolumeClaim, c, claim.Name, ns) 552 553 // The claim should timeout phase:Pending 554 err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, c, ns, claim.Name, 2*time.Second, framework.ClaimProvisionShortTimeout) 555 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("not all in phase Bound"))) 556 framework.Logf(err.Error()) 557 claim, err = c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, claim.Name, metav1.GetOptions{}) 558 framework.ExpectNoError(err) 559 gomega.Expect(claim.Status.Phase).To(gomega.Equal(v1.ClaimPending)) 560 }) 561 562 // Modifying the default storage class can be disruptive to other tests that depend on it 563 f.It("should be disabled by removing the default annotation", f.WithSerial(), f.WithDisruptive(), func(ctx context.Context) { 564 e2eskipper.SkipUnlessProviderIs("openstack", "gce", "aws", "gke", "vsphere", "azure") 565 e2epv.SkipIfNoDefaultStorageClass(ctx, c) 566 567 scName, scErr := e2epv.GetDefaultStorageClassName(ctx, c) 568 framework.ExpectNoError(scErr) 569 570 test := testsuites.StorageClassTest{ 571 Name: "default", 572 Timeouts: f.Timeouts, 573 ClaimSize: "2Gi", 574 } 575 576 ginkgo.By("removing the is-default StorageClass annotation") 577 verifyDefaultStorageClass(ctx, c, scName, true) 578 ginkgo.DeferCleanup(updateDefaultStorageClass, c, scName, "true") 579 updateDefaultStorageClass(ctx, c, scName, "") 580 581 ginkgo.By("creating a claim with default storageclass and expecting it to timeout") 582 claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 583 ClaimSize: test.ClaimSize, 584 VolumeMode: &test.VolumeMode, 585 }, ns) 586 claim, err := c.CoreV1().PersistentVolumeClaims(ns).Create(ctx, claim, metav1.CreateOptions{}) 587 framework.ExpectNoError(err) 588 defer func() { 589 framework.ExpectNoError(e2epv.DeletePersistentVolumeClaim(ctx, c, claim.Name, ns)) 590 }() 591 592 // The claim should timeout phase:Pending 593 err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, c, ns, claim.Name, 2*time.Second, framework.ClaimProvisionShortTimeout) 594 gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("not all in phase Bound"))) 595 framework.Logf(err.Error()) 596 claim, err = c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, claim.Name, metav1.GetOptions{}) 597 framework.ExpectNoError(err) 598 gomega.Expect(claim.Status.Phase).To(gomega.Equal(v1.ClaimPending)) 599 }) 600 }) 601 602 ginkgo.Describe("Invalid AWS KMS key", func() { 603 ginkgo.It("should report an error and create no PV", func(ctx context.Context) { 604 e2eskipper.SkipUnlessProviderIs("aws") 605 test := testsuites.StorageClassTest{ 606 Client: c, 607 Name: "AWS EBS with invalid KMS key", 608 Provisioner: "kubernetes.io/aws-ebs", 609 Timeouts: f.Timeouts, 610 ClaimSize: "2Gi", 611 Parameters: map[string]string{"kmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/55555555-5555-5555-5555-555555555555"}, 612 } 613 614 ginkgo.By("creating a StorageClass") 615 test.Class = testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, "invalid-aws")) 616 617 ginkgo.By("creating a claim object") 618 claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 619 ClaimSize: test.ClaimSize, 620 StorageClassName: &test.Class.Name, 621 VolumeMode: &test.VolumeMode, 622 }, ns) 623 claim, err := c.CoreV1().PersistentVolumeClaims(claim.Namespace).Create(ctx, claim, metav1.CreateOptions{}) 624 framework.ExpectNoError(err) 625 defer func() { 626 framework.Logf("deleting claim %q/%q", claim.Namespace, claim.Name) 627 err = c.CoreV1().PersistentVolumeClaims(claim.Namespace).Delete(ctx, claim.Name, metav1.DeleteOptions{}) 628 if err != nil && !apierrors.IsNotFound(err) { 629 framework.Failf("Error deleting claim %q. Error: %v", claim.Name, err) 630 } 631 }() 632 633 // Watch events until the message about invalid key appears. 634 // Event delivery is not reliable and it's used only as a quick way how to check if volume with wrong KMS 635 // key was not provisioned. If the event is not delivered, we check that the volume is not Bound for whole 636 // ClaimProvisionTimeout in the very same loop. 637 err = wait.Poll(time.Second, framework.ClaimProvisionTimeout, func() (bool, error) { 638 events, err := c.CoreV1().Events(claim.Namespace).List(ctx, metav1.ListOptions{}) 639 if err != nil { 640 return false, fmt.Errorf("could not list PVC events in %s: %w", claim.Namespace, err) 641 } 642 for _, event := range events.Items { 643 if strings.Contains(event.Message, "failed to create encrypted volume: the volume disappeared after creation, most likely due to inaccessible KMS encryption key") { 644 return true, nil 645 } 646 } 647 648 pvc, err := c.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(ctx, claim.Name, metav1.GetOptions{}) 649 if err != nil { 650 return true, err 651 } 652 if pvc.Status.Phase != v1.ClaimPending { 653 // The PVC was bound to something, i.e. PV was created for wrong KMS key. That's bad! 654 return true, fmt.Errorf("PVC got unexpectedly %s (to PV %q)", pvc.Status.Phase, pvc.Spec.VolumeName) 655 } 656 657 return false, nil 658 }) 659 if wait.Interrupted(err) { 660 framework.Logf("The test missed event about failed provisioning, but checked that no volume was provisioned for %v", framework.ClaimProvisionTimeout) 661 err = nil 662 } 663 framework.ExpectNoError(err, "Error waiting for PVC to fail provisioning: %v", err) 664 }) 665 }) 666 }) 667 668 func verifyDefaultStorageClass(ctx context.Context, c clientset.Interface, scName string, expectedDefault bool) { 669 sc, err := c.StorageV1().StorageClasses().Get(ctx, scName, metav1.GetOptions{}) 670 framework.ExpectNoError(err) 671 gomega.Expect(storageutil.IsDefaultAnnotation(sc.ObjectMeta)).To(gomega.Equal(expectedDefault)) 672 } 673 674 func updateDefaultStorageClass(ctx context.Context, c clientset.Interface, scName string, defaultStr string) { 675 sc, err := c.StorageV1().StorageClasses().Get(ctx, scName, metav1.GetOptions{}) 676 framework.ExpectNoError(err) 677 678 if defaultStr == "" { 679 delete(sc.Annotations, storageutil.BetaIsDefaultStorageClassAnnotation) 680 delete(sc.Annotations, storageutil.IsDefaultStorageClassAnnotation) 681 } else { 682 if sc.Annotations == nil { 683 sc.Annotations = make(map[string]string) 684 } 685 sc.Annotations[storageutil.BetaIsDefaultStorageClassAnnotation] = defaultStr 686 sc.Annotations[storageutil.IsDefaultStorageClassAnnotation] = defaultStr 687 } 688 689 _, err = c.StorageV1().StorageClasses().Update(ctx, sc, metav1.UpdateOptions{}) 690 framework.ExpectNoError(err) 691 692 expectedDefault := false 693 if defaultStr == "true" { 694 expectedDefault = true 695 } 696 verifyDefaultStorageClass(ctx, c, scName, expectedDefault) 697 } 698 699 // waitForProvisionedVolumesDelete is a polling wrapper to scan all PersistentVolumes for any associated to the test's 700 // StorageClass. Returns either an error and nil values or the remaining PVs and their count. 701 func waitForProvisionedVolumesDeleted(ctx context.Context, c clientset.Interface, scName string) ([]*v1.PersistentVolume, error) { 702 var remainingPVs []*v1.PersistentVolume 703 704 err := wait.Poll(10*time.Second, 300*time.Second, func() (bool, error) { 705 remainingPVs = []*v1.PersistentVolume{} 706 707 allPVs, err := c.CoreV1().PersistentVolumes().List(ctx, metav1.ListOptions{}) 708 if err != nil { 709 return true, err 710 } 711 for _, pv := range allPVs.Items { 712 if pv.Spec.StorageClassName == scName { 713 pv := pv 714 remainingPVs = append(remainingPVs, &pv) 715 } 716 } 717 if len(remainingPVs) > 0 { 718 return false, nil // Poll until no PVs remain 719 } 720 return true, nil // No PVs remain 721 }) 722 if err != nil { 723 return remainingPVs, fmt.Errorf("error waiting for PVs to be deleted: %w", err) 724 } 725 return nil, nil 726 } 727 728 // deleteStorageClass deletes the passed in StorageClass and catches errors other than "Not Found" 729 func deleteStorageClass(ctx context.Context, c clientset.Interface, className string) { 730 err := c.StorageV1().StorageClasses().Delete(ctx, className, metav1.DeleteOptions{}) 731 if err != nil && !apierrors.IsNotFound(err) { 732 framework.ExpectNoError(err) 733 } 734 } 735 736 // deleteProvisionedVolumes [gce||gke only] iteratively deletes persistent volumes and attached GCE PDs. 737 func deleteProvisionedVolumesAndDisks(ctx context.Context, c clientset.Interface, pvs []*v1.PersistentVolume) { 738 framework.Logf("Remaining PersistentVolumes:") 739 for i, pv := range pvs { 740 framework.Logf("\t%d) %s", i+1, pv.Name) 741 } 742 for _, pv := range pvs { 743 framework.ExpectNoError(e2epv.DeletePDWithRetry(ctx, pv.Spec.PersistentVolumeSource.GCEPersistentDisk.PDName)) 744 framework.ExpectNoError(e2epv.DeletePersistentVolume(ctx, c, pv.Name)) 745 } 746 } 747 748 func getRandomClusterZone(ctx context.Context, c clientset.Interface) string { 749 zones, err := e2enode.GetClusterZones(ctx, c) 750 zone := "" 751 framework.ExpectNoError(err) 752 if len(zones) != 0 { 753 zonesList := zones.UnsortedList() 754 zone = zonesList[rand.Intn(zones.Len())] 755 } 756 return zone 757 }