@@ -19,17 +19,21 @@ package testsuites
19
19
import (
20
20
"context"
21
21
"encoding/json"
22
+ "fmt"
22
23
"time"
23
24
24
25
"github.com/onsi/ginkgo/v2"
25
26
"github.com/onsi/gomega"
26
27
v1 "k8s.io/api/core/v1"
27
28
storagev1beta1 "k8s.io/api/storage/v1beta1"
29
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
28
30
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
31
"k8s.io/apimachinery/pkg/types"
30
32
"k8s.io/apimachinery/pkg/util/errors"
31
33
clientset "k8s.io/client-go/kubernetes"
34
+ "k8s.io/client-go/util/retry"
32
35
"k8s.io/kubernetes/pkg/features"
36
+ volumeutil "k8s.io/kubernetes/pkg/volume/util"
33
37
"k8s.io/kubernetes/test/e2e/framework"
34
38
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
35
39
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
@@ -44,6 +48,7 @@ const (
44
48
setVACWaitPeriod = 30 * time .Second
45
49
modifyVolumeWaitPeriod = 10 * time .Minute
46
50
vacCleanupWaitPeriod = 30 * time .Second
51
+ claimDeletingTimeout = 3 * time .Minute
47
52
)
48
53
49
54
type volumeModifyTestSuite struct {
@@ -266,6 +271,126 @@ func (v *volumeModifyTestSuite) DefineTests(driver storageframework.TestDriver,
266
271
err = e2epv .WaitForPersistentVolumeClaimModified (ctx , f .ClientSet , l .resource .Pvc , modifyVolumeWaitPeriod )
267
272
framework .ExpectNoError (err , "While waiting for PVC to have expected VAC" )
268
273
})
274
+
275
+ ginkgo .It ("should be protected by vac-protection finalizer" , func (ctx context.Context ) {
276
+ init (ctx , false /* volume created without VAC */ )
277
+ ginkgo .DeferCleanup (cleanup )
278
+
279
+ vacDriver , _ := driver .(storageframework.VolumeAttributesClassTestDriver )
280
+ newVAC := vacDriver .GetVolumeAttributesClass (ctx , l .config )
281
+ gomega .Expect (newVAC ).NotTo (gomega .BeNil ())
282
+ createdVAC , err := f .ClientSet .StorageV1beta1 ().VolumeAttributesClasses ().Create (ctx , newVAC , metav1.CreateOptions {})
283
+ framework .ExpectNoError (err , "While creating new VolumeAttributesClass" )
284
+ ginkgo .DeferCleanup (CleanupVAC , newVAC , f .ClientSet , vacCleanupWaitPeriod )
285
+
286
+ ginkgo .By ("Verifying that the vac-protection finalizer is set when it is created" )
287
+ gomega .Expect (createdVAC .Finalizers ).Should (gomega .ContainElement (volumeutil .VACProtectionFinalizer ),
288
+ "vac-protection finalizer was not set for VolumeAttributesClass %q" , createdVAC .Name )
289
+
290
+ ginkgo .By ("Creating a pod with dynamically provisioned volume" )
291
+ podConfig := e2epod.Config {
292
+ NS : f .Namespace .Name ,
293
+ PVCs : []* v1.PersistentVolumeClaim {l .resource .Pvc },
294
+ SeLinuxLabel : e2epod .GetLinuxLabel (),
295
+ NodeSelection : l .config .ClientNodeSelection ,
296
+ ImageID : e2epod .GetDefaultTestImageID (),
297
+ }
298
+ pod , err := e2epod .CreateSecPodWithNodeSelection (ctx , f .ClientSet , & podConfig , f .Timeouts .PodStart )
299
+ ginkgo .DeferCleanup (e2epod .DeletePodWithWait , f .ClientSet , pod )
300
+ framework .ExpectNoError (err , "While creating pod for modifying" )
301
+
302
+ ginkgo .By ("Modifying PVC via VAC" )
303
+ l .resource .Pvc = SetPVCVACName (ctx , l .resource .Pvc , newVAC .Name , f .ClientSet , setVACWaitPeriod )
304
+ gomega .Expect (l .resource .Pvc ).NotTo (gomega .BeNil ())
305
+
306
+ ginkgo .By ("Checking PVC status" )
307
+ err = e2epv .WaitForPersistentVolumeClaimModified (ctx , f .ClientSet , l .resource .Pvc , modifyVolumeWaitPeriod )
308
+ framework .ExpectNoError (err , "While waiting for PVC to have expected VAC" )
309
+
310
+ ginkgo .By ("Attempting to delete the VolumeAttributesClass should be stuck and the vac-protection finalizer is consistently exists" )
311
+ err = f .ClientSet .StorageV1beta1 ().VolumeAttributesClasses ().Delete (ctx , newVAC .Name , metav1.DeleteOptions {})
312
+ framework .ExpectNoError (err , "Failed to delete VolumeAttributesClass %q" , newVAC .Name )
313
+ // Check that the finalizer is consistently exists
314
+ gomega .Consistently (ctx , framework .GetObject (f .ClientSet .StorageV1beta1 ().VolumeAttributesClasses ().Get , createdVAC .Name , metav1.GetOptions {})).
315
+ WithPolling (framework .Poll ).WithTimeout (vacCleanupWaitPeriod ).Should (gomega .HaveField ("Finalizers" ,
316
+ gomega .ContainElement (volumeutil .VACProtectionFinalizer )), "finalizer %s was unexpectedly removed" , volumeutil .VACProtectionFinalizer )
317
+
318
+ ginkgo .By ("Deleting the pod gracefully" )
319
+ err = e2epod .DeletePodWithWait (ctx , f .ClientSet , pod )
320
+ framework .ExpectNoError (err , "failed to delete pod" )
321
+
322
+ ginkgo .By ("Update the PV reclaim policy to retain" )
323
+ pvc , err := f .ClientSet .CoreV1 ().PersistentVolumeClaims (f .Namespace .Name ).Get (ctx , l .resource .Pvc .Name , metav1.GetOptions {})
324
+ framework .ExpectNoError (err , "Failed to get PVC %q" , l .resource .Pvc .Name )
325
+ pvName := pvc .Spec .VolumeName
326
+ pv , err := f .ClientSet .CoreV1 ().PersistentVolumes ().Get (ctx , pvName , metav1.GetOptions {})
327
+ framework .ExpectNoError (err , "Failed to get PV %q" , pvName )
328
+ originPv := pv .DeepCopy ()
329
+ pv .Spec .PersistentVolumeReclaimPolicy = v1 .PersistentVolumeReclaimRetain
330
+ _ , err = f .ClientSet .CoreV1 ().PersistentVolumes ().Update (ctx , pv , metav1.UpdateOptions {})
331
+ ginkgo .DeferCleanup (recoverPvReclaimPolicyAndRemoveClaimRef , f .ClientSet , originPv )
332
+ framework .ExpectNoError (err , "Failed to update PV %q reclaim policy" , pvName )
333
+
334
+ // The vac_protection_controller make sure there is a VolumeAttributesClass that is not used by any PVC/PV
335
+ // and the VolumeAttributesClass has the vac-protection finalizer, so the VolumeAttributesClass should not be deleted
336
+ // after the PVC is deleted since the PVC bound PV which with the same vac is retained.
337
+ ginkgo .By (fmt .Sprintf ("Deleting PVC %q to make the vac unused for the PVC" , newVAC .Name ))
338
+ err = f .ClientSet .CoreV1 ().PersistentVolumeClaims (f .Namespace .Name ).Delete (ctx , l .resource .Pvc .Name , metav1.DeleteOptions {})
339
+ framework .ExpectNoError (err , "Failed to delete PVC %q" , l .resource .Pvc .Name )
340
+
341
+ ginkgo .By (fmt .Sprintf ("Waiting for PVC %q to be fully deleted" , l .resource .Pvc .Name ))
342
+ gomega .Eventually (ctx , func () bool {
343
+ _ , err := f .ClientSet .CoreV1 ().PersistentVolumeClaims (l .resource .Pvc .Name ).Get (ctx , l .resource .Pvc .Name , metav1.GetOptions {})
344
+ return apierrors .IsNotFound (err )
345
+ }).
346
+ WithPolling (framework .Poll ).
347
+ WithTimeout (claimDeletingTimeout ).
348
+ Should (gomega .BeTrueBecause ("pvc unexpectedly still exists" ))
349
+
350
+ ginkgo .By (fmt .Sprintf ("Waiting for PV %q to be released" , pvName ))
351
+ gomega .Eventually (func () v1.PersistentVolumePhase {
352
+ pv , err := f .ClientSet .CoreV1 ().PersistentVolumes ().Get (ctx , pvName , metav1.GetOptions {})
353
+ if err != nil {
354
+ framework .Logf ("Failed to get PV %q: %v" , pvName , err )
355
+ return ""
356
+ }
357
+ return pv .Status .Phase
358
+ }).
359
+ WithPolling (framework .Poll ).
360
+ WithTimeout (claimDeletingTimeout ).
361
+ Should (gomega .Equal (v1 .VolumeReleased ))
362
+
363
+ ginkgo .By (fmt .Sprintf ("Checking the vac-protection finalizer is still consistently exists on VolumeAttributesClass %q" , newVAC .Name ))
364
+ gomega .Consistently (ctx , framework .GetObject (f .ClientSet .StorageV1beta1 ().VolumeAttributesClasses ().Get , createdVAC .Name , metav1.GetOptions {})).
365
+ WithPolling (framework .Poll ).
366
+ WithTimeout (vacCleanupWaitPeriod ).
367
+ Should (gomega .HaveField ("Finalizers" ,
368
+ gomega .ContainElement (volumeutil .VACProtectionFinalizer )), "finalizer %s was unexpectedly removed" , volumeutil .VACProtectionFinalizer )
369
+
370
+ ginkgo .By (fmt .Sprintf ("Deleting PV %q to make the vac unused for the PV" , newVAC .Name ))
371
+ pv .Spec .PersistentVolumeReclaimPolicy = v1 .PersistentVolumeReclaimDelete
372
+ recoverPvReclaimPolicyAndRemoveClaimRef (ctx , f .ClientSet , pv )
373
+
374
+ ginkgo .By (fmt .Sprintf ("Waiting for PV %q to be deleted" , pvName ))
375
+ gomega .Eventually (func () bool {
376
+ _ , err := f .ClientSet .CoreV1 ().PersistentVolumes ().Get (ctx , pvName , metav1.GetOptions {})
377
+ return apierrors .IsNotFound (err )
378
+ }).
379
+ WithPolling (framework .Poll ).
380
+ WithTimeout (claimDeletingTimeout ).
381
+ Should (gomega .BeTrueBecause ("pv unexpectedly still exists" ))
382
+
383
+ ginkgo .By (fmt .Sprintf ("Confirming final deletion of VolumeAttributesClass %q" , newVAC .Name ))
384
+ gomega .Eventually (ctx , func (ctx context.Context ) bool {
385
+ _ , err := f .ClientSet .StorageV1beta1 ().VolumeAttributesClasses ().Get (ctx , newVAC .Name , metav1.GetOptions {})
386
+ return apierrors .IsNotFound (err )
387
+ }).
388
+ WithPolling (framework .Poll ).
389
+ WithTimeout (vacCleanupWaitPeriod ).
390
+ Should (gomega .BeTrueBecause ("vac unexpectedly still exists" ))
391
+
392
+ })
393
+
269
394
}
270
395
271
396
// SetPVCVACName sets the VolumeAttributesClassName on a PVC object
@@ -285,6 +410,7 @@ func SetPVCVACName(ctx context.Context, origPVC *v1.PersistentVolumeClaim, name
285
410
return patchedPVC
286
411
}
287
412
413
+ // MakeInvalidVAC creates a VolumeAttributesClass with an invalid parameter
288
414
func MakeInvalidVAC (config * storageframework.PerTestConfig ) * storagev1beta1.VolumeAttributesClass {
289
415
return storageframework .CopyVolumeAttributesClass (& storagev1beta1.VolumeAttributesClass {
290
416
DriverName : config .GetUniqueDriverName (),
@@ -294,8 +420,37 @@ func MakeInvalidVAC(config *storageframework.PerTestConfig) *storagev1beta1.Volu
294
420
}, config .Framework .Namespace .Name , "e2e-vac-invalid" )
295
421
}
296
422
423
+ // CleanupVAC cleans up the test VolumeAttributesClass
297
424
func CleanupVAC (ctx context.Context , vac * storagev1beta1.VolumeAttributesClass , c clientset.Interface , timeout time.Duration ) {
298
425
gomega .Eventually (ctx , func () error {
299
- return c .StorageV1beta1 ().VolumeAttributesClasses ().Delete (ctx , vac .Name , metav1.DeleteOptions {})
426
+ err := c .StorageV1beta1 ().VolumeAttributesClasses ().Delete (ctx , vac .Name , metav1.DeleteOptions {})
427
+ if apierrors .IsNotFound (err ) {
428
+ framework .Logf ("VolumeAttributesClass %q is already cleaned up" , vac .Name )
429
+ return nil
430
+ }
431
+ return err
300
432
}, timeout , modifyPollInterval ).Should (gomega .BeNil ())
301
433
}
434
+
435
+ // recoverPvReclaimPolicyAndRemoveClaimRef recovers the test pv's reclaim policy to expected used for clean up test PV
436
+ func recoverPvReclaimPolicyAndRemoveClaimRef (ctx context.Context , c clientset.Interface , expectedPv * v1.PersistentVolume ) {
437
+ setPvReclaimPolicyErr := retry .RetryOnConflict (retry .DefaultRetry , func () error {
438
+ pv , err := c .CoreV1 ().PersistentVolumes ().Get (ctx , expectedPv .Name , metav1.GetOptions {})
439
+ if err != nil {
440
+ if apierrors .IsNotFound (err ) {
441
+ framework .Logf ("PV %q is already cleaned up" , expectedPv .Name )
442
+ return nil
443
+ }
444
+ return err
445
+ }
446
+ if pv .Spec .PersistentVolumeReclaimPolicy == expectedPv .Spec .PersistentVolumeReclaimPolicy && pv .Spec .ClaimRef == nil {
447
+ framework .Logf ("PV %q reclaim policy is already recovered to %q" , expectedPv .Name , expectedPv .Spec .PersistentVolumeReclaimPolicy )
448
+ return nil
449
+ }
450
+ pv .Spec .ClaimRef = nil
451
+ pv .Spec .PersistentVolumeReclaimPolicy = expectedPv .Spec .PersistentVolumeReclaimPolicy
452
+ _ , err = c .CoreV1 ().PersistentVolumes ().Update (ctx , pv , metav1.UpdateOptions {})
453
+ return err
454
+ })
455
+ framework .ExpectNoError (setPvReclaimPolicyErr , "Failed to set PV %q reclaim policy to %q" , expectedPv .Name , expectedPv .Spec .PersistentVolumeReclaimPolicy )
456
+ }
0 commit comments