@@ -21,6 +21,7 @@ import (
2121 "errors"
2222 "fmt"
2323 "reflect"
24+ sysruntime "runtime"
2425 "strconv"
2526 "strings"
2627 "sync"
@@ -53,6 +54,7 @@ import (
5354 "k8s.io/client-go/tools/cache"
5455 "k8s.io/client-go/tools/record"
5556 "k8s.io/klog"
57+ "k8s.io/kubernetes/pkg/util/slice"
5658)
5759
5860// This is a unit test framework for snapshot controller.
@@ -110,7 +112,8 @@ type controllerTest struct {
110112 // List of expected CSI list snapshot calls
111113 expectedListCalls []listCall
112114 // Function to call as the test.
113- test testCall
115+ test testCall
116+ expectSuccess bool
114117}
115118
116119type testCall func (ctrl * csiSnapshotController , reactor * snapshotReactor , test controllerTest ) error
@@ -177,6 +180,11 @@ func withContentFinalizer(content *crdv1.VolumeSnapshotContent) *crdv1.VolumeSna
177180 return content
178181}
179182
183+ func withPVCFinalizer (pvc * v1.PersistentVolumeClaim ) * v1.PersistentVolumeClaim {
184+ pvc .ObjectMeta .Finalizers = append (pvc .ObjectMeta .Finalizers , PVCFinalizer )
185+ return pvc
186+ }
187+
180188// React is a callback called by fake kubeClient from the controller.
181189// In other words, every snapshot/content change performed by the controller ends
182190// here.
@@ -331,6 +339,32 @@ func (r *snapshotReactor) React(action core.Action) (handled bool, ret runtime.O
331339 klog .V (4 ).Infof ("GetClaim: claim %s not found" , name )
332340 return true , nil , fmt .Errorf ("cannot find claim %s" , name )
333341
342+ case action .Matches ("update" , "persistentvolumeclaims" ):
343+ obj := action .(core.UpdateAction ).GetObject ()
344+ claim := obj .(* v1.PersistentVolumeClaim )
345+
346+ // Check and bump object version
347+ storedClaim , found := r .claims [claim .Name ]
348+ if found {
349+ storedVer , _ := strconv .Atoi (storedClaim .ResourceVersion )
350+ requestedVer , _ := strconv .Atoi (claim .ResourceVersion )
351+ if storedVer != requestedVer {
352+ return true , obj , errVersionConflict
353+ }
354+ // Don't modify the existing object
355+ claim = claim .DeepCopy ()
356+ claim .ResourceVersion = strconv .Itoa (storedVer + 1 )
357+ } else {
358+ return true , nil , fmt .Errorf ("cannot update claim %s: claim not found" , claim .Name )
359+ }
360+
361+ // Store the updated object to appropriate places.
362+ r .claims [claim .Name ] = claim
363+ r .changedObjects = append (r .changedObjects , claim )
364+ r .changedSinceLastSync ++
365+ klog .V (4 ).Infof ("saved updated claim %s" , claim .Name )
366+ return true , claim , nil
367+
334368 case action .Matches ("get" , "storageclasses" ):
335369 name := action .(core.GetAction ).GetName ()
336370 storageClass , found := r .storageClasses [name ]
@@ -550,6 +584,9 @@ func (r *snapshotReactor) syncAll() {
550584 for _ , v := range r .contents {
551585 r .changedObjects = append (r .changedObjects , v )
552586 }
587+ for _ , pvc := range r .claims {
588+ r .changedObjects = append (r .changedObjects , pvc )
589+ }
553590 r .changedSinceLastSync = 0
554591}
555592
@@ -699,6 +736,7 @@ func newSnapshotReactor(kubeClient *kubefake.Clientset, client *fake.Clientset,
699736 client .AddReactor ("delete" , "volumesnapshotcontents" , reactor .React )
700737 client .AddReactor ("delete" , "volumesnapshots" , reactor .React )
701738 kubeClient .AddReactor ("get" , "persistentvolumeclaims" , reactor .React )
739+ kubeClient .AddReactor ("update" , "persistentvolumeclaims" , reactor .React )
702740 kubeClient .AddReactor ("get" , "persistentvolumes" , reactor .React )
703741 kubeClient .AddReactor ("get" , "storageclasses" , reactor .React )
704742 kubeClient .AddReactor ("get" , "secrets" , reactor .React )
@@ -746,6 +784,7 @@ func newTestController(kubeClient kubernetes.Interface, clientset clientset.Inte
746784 ctrl .contentListerSynced = alwaysReady
747785 ctrl .snapshotListerSynced = alwaysReady
748786 ctrl .classListerSynced = alwaysReady
787+ ctrl .pvcListerSynced = alwaysReady
749788
750789 return ctrl , nil
751790}
@@ -845,7 +884,7 @@ func newSnapshotArray(name, className, boundToContent, snapshotUID, claimName st
845884}
846885
847886// newClaim returns a new claim with given attributes
848- func newClaim (name , claimUID , capacity , boundToVolume string , phase v1.PersistentVolumeClaimPhase , class * string ) * v1.PersistentVolumeClaim {
887+ func newClaim (name , claimUID , capacity , boundToVolume string , phase v1.PersistentVolumeClaimPhase , class * string , bFinalizer bool ) * v1.PersistentVolumeClaim {
849888 claim := v1.PersistentVolumeClaim {
850889 ObjectMeta : metav1.ObjectMeta {
851890 Name : name ,
@@ -877,14 +916,25 @@ func newClaim(name, claimUID, capacity, boundToVolume string, phase v1.Persisten
877916 claim .Status .Capacity = claim .Spec .Resources .Requests
878917 }
879918
919+ if bFinalizer {
920+ return withPVCFinalizer (& claim )
921+ }
880922 return & claim
881923}
882924
883925// newClaimArray returns array with a single claim that would be returned by
884926// newClaim() with the same parameters.
885927func newClaimArray (name , claimUID , capacity , boundToVolume string , phase v1.PersistentVolumeClaimPhase , class * string ) []* v1.PersistentVolumeClaim {
886928 return []* v1.PersistentVolumeClaim {
887- newClaim (name , claimUID , capacity , boundToVolume , phase , class ),
929+ newClaim (name , claimUID , capacity , boundToVolume , phase , class , false ),
930+ }
931+ }
932+
933+ // newClaimArrayFinalizer returns array with a single claim that would be returned by
934+ // newClaim() with the same parameters plus finalizer.
935+ func newClaimArrayFinalizer (name , claimUID , capacity , boundToVolume string , phase v1.PersistentVolumeClaimPhase , class * string ) []* v1.PersistentVolumeClaim {
936+ return []* v1.PersistentVolumeClaim {
937+ newClaim (name , claimUID , capacity , boundToVolume , phase , class , true ),
888938 }
889939}
890940
@@ -961,6 +1011,14 @@ func testSyncContent(ctrl *csiSnapshotController, reactor *snapshotReactor, test
9611011 return ctrl .syncContent (test .initialContents [0 ])
9621012}
9631013
1014+ func testAddPVCFinalizer (ctrl * csiSnapshotController , reactor * snapshotReactor , test controllerTest ) error {
1015+ return ctrl .ensureSnapshotSourceFinalizer (test .initialSnapshots [0 ])
1016+ }
1017+
1018+ func testRemovePVCFinalizer (ctrl * csiSnapshotController , reactor * snapshotReactor , test controllerTest ) error {
1019+ return ctrl .checkandRemoveSnapshotSourceFinalizer (test .initialSnapshots [0 ])
1020+ }
1021+
9641022var (
9651023 classEmpty string
9661024 classGold = "gold"
@@ -1097,6 +1155,113 @@ func runSyncTests(t *testing.T, tests []controllerTest, snapshotClasses []*crdv1
10971155 }
10981156}
10991157
1158+ // This tests ensureSnapshotSourceFinalizer and checkandRemoveSnapshotSourceFinalizer
1159+ func runPVCFinalizerTests (t * testing.T , tests []controllerTest , snapshotClasses []* crdv1.VolumeSnapshotClass ) {
1160+ snapshotscheme .AddToScheme (scheme .Scheme )
1161+ for _ , test := range tests {
1162+ klog .V (4 ).Infof ("starting test %q" , test .name )
1163+
1164+ // Initialize the controller
1165+ kubeClient := & kubefake.Clientset {}
1166+ client := & fake.Clientset {}
1167+
1168+ ctrl , err := newTestController (kubeClient , client , nil , t , test )
1169+ if err != nil {
1170+ t .Fatalf ("Test %q construct persistent content failed: %v" , test .name , err )
1171+ }
1172+
1173+ reactor := newSnapshotReactor (kubeClient , client , ctrl , nil , nil , test .errors )
1174+ for _ , snapshot := range test .initialSnapshots {
1175+ ctrl .snapshotStore .Add (snapshot )
1176+ reactor .snapshots [snapshot .Name ] = snapshot
1177+ }
1178+ for _ , content := range test .initialContents {
1179+ if ctrl .isDriverMatch (test .initialContents [0 ]) {
1180+ ctrl .contentStore .Add (content )
1181+ reactor .contents [content .Name ] = content
1182+ }
1183+ }
1184+
1185+ pvcIndexer := cache .NewIndexer (cache .MetaNamespaceKeyFunc , cache.Indexers {})
1186+ for _ , claim := range test .initialClaims {
1187+ reactor .claims [claim .Name ] = claim
1188+ pvcIndexer .Add (claim )
1189+ }
1190+ ctrl .pvcLister = corelisters .NewPersistentVolumeClaimLister (pvcIndexer )
1191+
1192+ for _ , volume := range test .initialVolumes {
1193+ reactor .volumes [volume .Name ] = volume
1194+ }
1195+ for _ , storageClass := range test .initialStorageClasses {
1196+ reactor .storageClasses [storageClass .Name ] = storageClass
1197+ }
1198+ for _ , secret := range test .initialSecrets {
1199+ reactor .secrets [secret .Name ] = secret
1200+ }
1201+
1202+ // Inject classes into controller via a custom lister.
1203+ indexer := cache .NewIndexer (cache .MetaNamespaceKeyFunc , cache.Indexers {})
1204+ for _ , class := range snapshotClasses {
1205+ indexer .Add (class )
1206+ }
1207+ ctrl .classLister = storagelisters .NewVolumeSnapshotClassLister (indexer )
1208+
1209+ // Run the tested functions
1210+ err = test .test (ctrl , reactor , test )
1211+ if err != nil {
1212+ t .Errorf ("Test %q failed: %v" , test .name , err )
1213+ }
1214+
1215+ // Verify PVCFinalizer tests results
1216+ evaluatePVCFinalizerTests (ctrl , reactor , test , t )
1217+ }
1218+ }
1219+
1220+ // Evaluate PVCFinalizer tests results
1221+ func evaluatePVCFinalizerTests (ctrl * csiSnapshotController , reactor * snapshotReactor , test controllerTest , t * testing.T ) {
1222+ // Evaluate results
1223+ bHasPVCFinalizer := false
1224+ name := sysruntime .FuncForPC (reflect .ValueOf (test .test ).Pointer ()).Name ()
1225+ index := strings .LastIndex (name , "." )
1226+ if index == - 1 {
1227+ t .Errorf ("Test %q: failed to test finalizer - invalid test call name [%s]" , test .name , name )
1228+ return
1229+ }
1230+ names := []rune (name )
1231+ funcName := string (names [index + 1 : len (name )])
1232+ klog .V (4 ).Infof ("test %q: PVCFinalizer test func name: [%s]" , test .name , funcName )
1233+
1234+ if funcName == "testAddPVCFinalizer" {
1235+ for _ , pvc := range reactor .claims {
1236+ if test .initialClaims [0 ].Name == pvc .Name {
1237+ if ! slice .ContainsString (test .initialClaims [0 ].ObjectMeta .Finalizers , PVCFinalizer , nil ) && slice .ContainsString (pvc .ObjectMeta .Finalizers , PVCFinalizer , nil ) {
1238+ klog .V (4 ).Infof ("test %q succeeded. PVCFinalizer is added to PVC %s" , test .name , pvc .Name )
1239+ bHasPVCFinalizer = true
1240+ }
1241+ break
1242+ }
1243+ }
1244+ if test .expectSuccess && ! bHasPVCFinalizer {
1245+ t .Errorf ("Test %q: failed to add finalizer to PVC %s" , test .name , test .initialClaims [0 ].Name )
1246+ }
1247+ }
1248+ bHasPVCFinalizer = true
1249+ if funcName == "testRemovePVCFinalizer" {
1250+ for _ , pvc := range reactor .claims {
1251+ if test .initialClaims [0 ].Name == pvc .Name {
1252+ if slice .ContainsString (test .initialClaims [0 ].ObjectMeta .Finalizers , PVCFinalizer , nil ) && ! slice .ContainsString (pvc .ObjectMeta .Finalizers , PVCFinalizer , nil ) {
1253+ klog .V (4 ).Infof ("test %q succeeded. PVCFinalizer is removed from PVC %s" , test .name , pvc .Name )
1254+ bHasPVCFinalizer = false
1255+ }
1256+ break
1257+ }
1258+ }
1259+ if test .expectSuccess && bHasPVCFinalizer {
1260+ t .Errorf ("Test %q: failed to remove finalizer from PVC %s" , test .name , test .initialClaims [0 ].Name )
1261+ }
1262+ }
1263+ }
1264+
11001265func getSize (size int64 ) * resource.Quantity {
11011266 return resource .NewQuantity (size , resource .BinarySI )
11021267}
0 commit comments