Skip to content

Commit 5de6d93

Browse files
committed
Add Finalizer for VolumeSnapshot/VolumeSnapshotContent
This PR adds a Finalizer for VolumeSnapshotContent. If the VolumeSnapshotContent is bound to a VolumeSnapshot, the VolumeSnapshotContent is being used and cannot be deleted. This PR also adds a Finalizer for VolumeSnapshot. If a volume is being created from the snapshot, the VolumeSnapshot is being used and cannot be deleted.
1 parent 30701bb commit 5de6d93

7 files changed

+254
-55
lines changed

pkg/controller/framework_test.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ type reactorError struct {
166166
error error
167167
}
168168

169+
func withSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot) *crdv1.VolumeSnapshot {
170+
snapshot.ObjectMeta.Finalizers = append(snapshot.ObjectMeta.Finalizers, VolumeSnapshotFinalizer)
171+
return snapshot
172+
}
173+
174+
func withContentFinalizer(content *crdv1.VolumeSnapshotContent) *crdv1.VolumeSnapshotContent {
175+
content.ObjectMeta.Finalizers = append(content.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer)
176+
return content
177+
}
178+
169179
// React is a callback called by fake kubeClient from the controller.
170180
// In other words, every snapshot/content change performed by the controller ends
171181
// here.
@@ -744,7 +754,7 @@ func newTestController(kubeClient kubernetes.Interface, clientset clientset.Inte
744754
}
745755

746756
// newContent returns a new content with given attributes
747-
func newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName string, size *int64, creationTime *int64) *crdv1.VolumeSnapshotContent {
757+
func newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName string, size *int64, creationTime *int64, withFinalizer bool) *crdv1.VolumeSnapshotContent {
748758
content := crdv1.VolumeSnapshotContent{
749759
ObjectMeta: metav1.ObjectMeta{
750760
Name: name,
@@ -778,17 +788,20 @@ func newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToS
778788
}
779789
}
780790

791+
if withFinalizer {
792+
return withContentFinalizer(&content)
793+
}
781794
return &content
782795
}
783796

784-
func newContentArray(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName string, size *int64, creationTime *int64) []*crdv1.VolumeSnapshotContent {
797+
func newContentArray(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName string, size *int64, creationTime *int64, withFinalizer bool) []*crdv1.VolumeSnapshotContent {
785798
return []*crdv1.VolumeSnapshotContent{
786-
newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName, size, creationTime),
799+
newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName, size, creationTime, withFinalizer),
787800
}
788801
}
789802

790803
func newContentWithUnmatchDriverArray(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName string, size *int64, creationTime *int64) []*crdv1.VolumeSnapshotContent {
791-
content := newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName, size, creationTime)
804+
content := newContent(name, className, snapshotHandle, volumeUID, volumeName, boundToSnapshotUID, boundToSnapshotName, size, creationTime, false)
792805
content.Spec.VolumeSnapshotSource.CSI.Driver = "fake"
793806
return []*crdv1.VolumeSnapshotContent{
794807
content,
@@ -820,7 +833,7 @@ func newSnapshot(name, className, boundToContent, snapshotUID, claimName string,
820833
},
821834
}
822835

823-
return &snapshot
836+
return withSnapshotFinalizer(&snapshot)
824837
}
825838

826839
func newSnapshotArray(name, className, boundToContent, snapshotUID, claimName string, ready bool, err *storagev1beta1.VolumeError, creationTime *metav1.Time, size *resource.Quantity) []*crdv1.VolumeSnapshot {

pkg/controller/snapshot_controller.go

+161
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
ref "k8s.io/client-go/tools/reference"
3535
"k8s.io/kubernetes/pkg/util/goroutinemap"
3636
"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
37+
"k8s.io/kubernetes/pkg/util/slice"
3738
)
3839

3940
// ==================================================================
@@ -77,6 +78,9 @@ import (
7778

7879
const pvcKind = "PersistentVolumeClaim"
7980
const apiGroup = ""
81+
const snapshotKind = "VolumeSnapshot"
82+
const snapshotAPIGroup = crdv1.GroupName
83+
8084
const controllerUpdateFailMsg = "snapshot controller failed to update"
8185

8286
const IsDefaultSnapshotClassAnnotation = "snapshot.storage.kubernetes.io/is-default-class"
@@ -85,6 +89,26 @@ const IsDefaultSnapshotClassAnnotation = "snapshot.storage.kubernetes.io/is-defa
8589
func (ctrl *csiSnapshotController) syncContent(content *crdv1.VolumeSnapshotContent) error {
8690
glog.V(5).Infof("synchronizing VolumeSnapshotContent[%s]", content.Name)
8791

92+
if isContentDeletionCandidate(content) {
93+
// Volume snapshot content should be deleted. Check if it's used
94+
// and remove finalizer if it's not.
95+
// Check if snapshot content is still bound to a snapshot.
96+
isUsed := ctrl.isSnapshotContentBeingUsed(content)
97+
if !isUsed {
98+
glog.V(5).Infof("syncContent: Remove Finalizer for VolumeSnapshotContent[%s]", content.Name)
99+
return ctrl.removeContentFinalizer(content)
100+
}
101+
}
102+
103+
if needToAddContentFinalizer(content) {
104+
// Content is not being deleted -> it should have the finalizer. The
105+
// finalizer should be added by admission plugin, this is just to add
106+
// the finalizer to old volume snapshot contents that were created before
107+
// the admission plugin was enabled.
108+
glog.V(5).Infof("syncContent: Add Finalizer for VolumeSnapshotContent[%s]", content.Name)
109+
return ctrl.addContentFinalizer(content)
110+
}
111+
88112
// VolumeSnapshotContent is not bound to any VolumeSnapshot, in this case we just return err
89113
if content.Spec.VolumeSnapshotRef == nil {
90114
// content is not bound
@@ -139,6 +163,26 @@ func (ctrl *csiSnapshotController) syncContent(content *crdv1.VolumeSnapshotCont
139163
func (ctrl *csiSnapshotController) syncSnapshot(snapshot *crdv1.VolumeSnapshot) error {
140164
glog.V(5).Infof("synchonizing VolumeSnapshot[%s]: %s", snapshotKey(snapshot), getSnapshotStatusForLogging(snapshot))
141165

166+
if isSnapshotDeletionCandidate(snapshot) {
167+
// Volume snapshot should be deleted. Check if it's used
168+
// and remove finalizer if it's not.
169+
// Check if a volume is being created from snapshot.
170+
isUsed := ctrl.isVolumeBeingCreatedFromSnapshot(snapshot)
171+
if !isUsed {
172+
glog.V(5).Infof("syncSnapshot: Remove Finalizer for VolumeSnapshot[%s]", snapshot.Name)
173+
return ctrl.removeSnapshotFinalizer(snapshot)
174+
}
175+
}
176+
177+
if needToAddSnapshotFinalizer(snapshot) {
178+
// Snapshot is not being deleted -> it should have the finalizer. The
179+
// finalizer should be added by admission plugin, this is just to add
180+
// the finalizer to old volume snapshots that were created before
181+
// the admission plugin was enabled.
182+
glog.V(5).Infof("syncSnapshot: Add Finalizer for VolumeSnapshot[%s]", snapshot.Name)
183+
return ctrl.addSnapshotFinalizer(snapshot)
184+
}
185+
142186
if !snapshot.Status.Ready {
143187
return ctrl.syncUnreadySnapshot(snapshot)
144188
} else {
@@ -395,6 +439,48 @@ func IsSnapshotBound(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapsh
395439
return false
396440
}
397441

442+
// isSnapshotConentBeingUsed checks if snapshot content is bound to snapshot.
443+
func (ctrl *csiSnapshotController) isSnapshotContentBeingUsed(content *crdv1.VolumeSnapshotContent) bool {
444+
if content.Spec.VolumeSnapshotRef != nil {
445+
snapshotObj, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshots(content.Spec.VolumeSnapshotRef.Namespace).Get(content.Spec.VolumeSnapshotRef.Name, metav1.GetOptions{})
446+
if err != nil {
447+
glog.Infof("isSnapshotContentBeingUsed: Cannot get snapshot %s from api server: [%v]. VolumeSnapshot object may be deleted already.", content.Spec.VolumeSnapshotRef.Name, err)
448+
return false
449+
}
450+
451+
// Check if the snapshot content is bound to the snapshot
452+
if IsSnapshotBound(snapshotObj, content) && snapshotObj.Spec.SnapshotContentName == content.Name {
453+
glog.Infof("isSnapshotContentBeingUsed: VolumeSnapshot %s is bound to volumeSnapshotContent [%s]", snapshotObj.Name, content.Name)
454+
return true
455+
}
456+
}
457+
458+
glog.V(5).Infof("isSnapshotContentBeingUsed: Snapshot content %s is not being used", content.Name)
459+
return false
460+
}
461+
462+
// isVolumeBeingCreatedFromSnapshot checks if an volume is being created from the snapshot.
463+
func (ctrl *csiSnapshotController) isVolumeBeingCreatedFromSnapshot(snapshot *crdv1.VolumeSnapshot) bool {
464+
pvcList, err := ctrl.client.CoreV1().PersistentVolumeClaims(snapshot.Namespace).List(metav1.ListOptions{})
465+
if err != nil {
466+
glog.Errorf("Failed to retrieve PVCs from the API server to check if volume snapshot %s is being used by a volume: %q", snapshot.Name, err)
467+
return false
468+
}
469+
for _, pvc := range pvcList.Items {
470+
if pvc.Spec.DataSource != nil && len(pvc.Spec.DataSource.Name) > 0 && pvc.Spec.DataSource.Name == snapshot.Name {
471+
if pvc.Spec.DataSource.Kind == snapshotKind && *(pvc.Spec.DataSource.APIGroup) == snapshotAPIGroup {
472+
if pvc.Status.Phase == v1.ClaimPending {
473+
// A volume is being created from the snapshot
474+
glog.Infof("isVolumeBeingCreatedFromSnapshot: volume %s is being created from snapshot %s", pvc.Name, pvc.Spec.DataSource.Name)
475+
return true
476+
}
477+
}
478+
}
479+
}
480+
glog.V(5).Infof("isVolumeBeingCreatedFromSnapshot: no volume is being created from snapshot %s", snapshot.Name)
481+
return false
482+
}
483+
398484
// The function checks whether the volumeSnapshotRef in snapshot content matches the given snapshot. If match, it binds the content with the snapshot
399485
func (ctrl *csiSnapshotController) checkandBindSnapshotContent(snapshot *crdv1.VolumeSnapshot, content *crdv1.VolumeSnapshotContent) error {
400486
if content.Spec.VolumeSnapshotRef == nil || content.Spec.VolumeSnapshotRef.Name != snapshot.Name {
@@ -878,3 +964,78 @@ func isControllerUpdateFailError(err *storage.VolumeError) bool {
878964
}
879965
return false
880966
}
967+
968+
// addContentFinalizer adds a Finalizer for VolumeSnapshotContent.
969+
func (ctrl *csiSnapshotController) addContentFinalizer(content *crdv1.VolumeSnapshotContent) error {
970+
contentClone := content.DeepCopy()
971+
contentClone.ObjectMeta.Finalizers = append(contentClone.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer)
972+
973+
_, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshotContents().Update(contentClone)
974+
if err != nil {
975+
return newControllerUpdateError(content.Name, err.Error())
976+
}
977+
978+
_, err = ctrl.storeContentUpdate(contentClone)
979+
if err != nil {
980+
glog.Errorf("failed to update content store %v", err)
981+
}
982+
983+
glog.V(5).Infof("Added protection finalizer to volume snapshot content %s", content.Name)
984+
return nil
985+
}
986+
987+
// removeContentFinalizer removes a Finalizer for VolumeSnapshotContent.
988+
func (ctrl *csiSnapshotController) removeContentFinalizer(content *crdv1.VolumeSnapshotContent) error {
989+
contentClone := content.DeepCopy()
990+
contentClone.ObjectMeta.Finalizers = slice.RemoveString(contentClone.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer, nil)
991+
992+
_, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshotContents().Update(contentClone)
993+
if err != nil {
994+
return newControllerUpdateError(content.Name, err.Error())
995+
}
996+
997+
_, err = ctrl.storeContentUpdate(contentClone)
998+
if err != nil {
999+
glog.Errorf("failed to update content store %v", err)
1000+
}
1001+
1002+
glog.V(5).Infof("Removed protection finalizer from volume snapshot content %s", content.Name)
1003+
return nil
1004+
}
1005+
1006+
// addSnapshotFinalizer adds a Finalizer for VolumeSnapshot.
1007+
func (ctrl *csiSnapshotController) addSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot) error {
1008+
snapshotClone := snapshot.DeepCopy()
1009+
snapshotClone.ObjectMeta.Finalizers = append(snapshotClone.ObjectMeta.Finalizers, VolumeSnapshotFinalizer)
1010+
_, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshots(snapshotClone.Namespace).Update(snapshotClone)
1011+
if err != nil {
1012+
return newControllerUpdateError(snapshot.Name, err.Error())
1013+
}
1014+
1015+
_, err = ctrl.storeSnapshotUpdate(snapshotClone)
1016+
if err != nil {
1017+
glog.Errorf("failed to update snapshot store %v", err)
1018+
}
1019+
1020+
glog.V(5).Infof("Added protection finalizer to volume snapshot %s", snapshot.Name)
1021+
return nil
1022+
}
1023+
1024+
// removeContentFinalizer removes a Finalizer for VolumeSnapshot.
1025+
func (ctrl *csiSnapshotController) removeSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot) error {
1026+
snapshotClone := snapshot.DeepCopy()
1027+
snapshotClone.ObjectMeta.Finalizers = slice.RemoveString(snapshotClone.ObjectMeta.Finalizers, VolumeSnapshotFinalizer, nil)
1028+
1029+
_, err := ctrl.clientset.VolumesnapshotV1alpha1().VolumeSnapshots(snapshotClone.Namespace).Update(snapshotClone)
1030+
if err != nil {
1031+
return newControllerUpdateError(snapshot.Name, err.Error())
1032+
}
1033+
1034+
_, err = ctrl.storeSnapshotUpdate(snapshotClone)
1035+
if err != nil {
1036+
glog.Errorf("failed to update snapshot store %v", err)
1037+
}
1038+
1039+
glog.V(5).Infof("Removed protection finalizer from volume snapshot %s", snapshot.Name)
1040+
return nil
1041+
}

pkg/controller/snapshot_controller_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
)
2424

2525
func storeVersion(t *testing.T, prefix string, c cache.Store, version string, expectedReturn bool) {
26-
content := newContent("contentName", classEmpty, "sid1-1", "vuid1-1", "volume1-1", "snapuid1-1", "snap1-1", nil, nil)
26+
content := newContent("contentName", classEmpty, "sid1-1", "vuid1-1", "volume1-1", "snapuid1-1", "snap1-1", nil, nil, false)
2727
content.ResourceVersion = version
2828
ret, err := storeObjectUpdate(c, content, "content")
2929
if err != nil {
@@ -82,7 +82,7 @@ func TestControllerCacheParsingError(t *testing.T) {
8282
// There must be something in the cache to compare with
8383
storeVersion(t, "Step1", c, "1", true)
8484

85-
content := newContent("contentName", classEmpty, "sid1-1", "vuid1-1", "volume1-1", "snapuid1-1", "snap1-1", nil, nil)
85+
content := newContent("contentName", classEmpty, "sid1-1", "vuid1-1", "volume1-1", "snapuid1-1", "snap1-1", nil, nil, false)
8686
content.ResourceVersion = "xxx"
8787
_, err := storeObjectUpdate(c, content, "content")
8888
if err == nil {

pkg/controller/snapshot_create_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestCreateSnapshotSync(t *testing.T) {
6565
{
6666
name: "6-1 - successful create snapshot with snapshot class gold",
6767
initialContents: nocontents,
68-
expectedContents: newContentArray("snapcontent-snapuid6-1", classGold, "sid6-1", "pv-uid6-1", "volume6-1", "snapuid6-1", "snap6-1", &defaultSize, &timeNow),
68+
expectedContents: newContentArray("snapcontent-snapuid6-1", classGold, "sid6-1", "pv-uid6-1", "volume6-1", "snapuid6-1", "snap6-1", &defaultSize, &timeNow, false),
6969
initialSnapshots: newSnapshotArray("snap6-1", classGold, "", "snapuid6-1", "claim6-1", false, nil, nil, nil),
7070
expectedSnapshots: newSnapshotArray("snap6-1", classGold, "snapcontent-snapuid6-1", "snapuid6-1", "claim6-1", false, nil, metaTimeNowUnix, getSize(defaultSize)),
7171
initialClaims: newClaimArray("claim6-1", "pvc-uid6-1", "1Gi", "volume6-1", v1.ClaimBound, &classEmpty),
@@ -89,7 +89,7 @@ func TestCreateSnapshotSync(t *testing.T) {
8989
{
9090
name: "6-2 - successful create snapshot with snapshot class silver",
9191
initialContents: nocontents,
92-
expectedContents: newContentArray("snapcontent-snapuid6-2", classSilver, "sid6-2", "pv-uid6-2", "volume6-2", "snapuid6-2", "snap6-2", &defaultSize, &timeNow),
92+
expectedContents: newContentArray("snapcontent-snapuid6-2", classSilver, "sid6-2", "pv-uid6-2", "volume6-2", "snapuid6-2", "snap6-2", &defaultSize, &timeNow, false),
9393
initialSnapshots: newSnapshotArray("snap6-2", classSilver, "", "snapuid6-2", "claim6-2", false, nil, nil, nil),
9494
expectedSnapshots: newSnapshotArray("snap6-2", classSilver, "snapcontent-snapuid6-2", "snapuid6-2", "claim6-2", false, nil, metaTimeNowUnix, getSize(defaultSize)),
9595
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
@@ -113,7 +113,7 @@ func TestCreateSnapshotSync(t *testing.T) {
113113
{
114114
name: "6-3 - successful create snapshot with snapshot class valid-secret-class",
115115
initialContents: nocontents,
116-
expectedContents: newContentArray("snapcontent-snapuid6-3", validSecretClass, "sid6-3", "pv-uid6-3", "volume6-3", "snapuid6-3", "snap6-3", &defaultSize, &timeNow),
116+
expectedContents: newContentArray("snapcontent-snapuid6-3", validSecretClass, "sid6-3", "pv-uid6-3", "volume6-3", "snapuid6-3", "snap6-3", &defaultSize, &timeNow, false),
117117
initialSnapshots: newSnapshotArray("snap6-3", validSecretClass, "", "snapuid6-3", "claim6-3", false, nil, nil, nil),
118118
expectedSnapshots: newSnapshotArray("snap6-3", validSecretClass, "snapcontent-snapuid6-3", "snapuid6-3", "claim6-3", false, nil, metaTimeNowUnix, getSize(defaultSize)),
119119
initialClaims: newClaimArray("claim6-3", "pvc-uid6-3", "1Gi", "volume6-3", v1.ClaimBound, &classEmpty),
@@ -139,7 +139,7 @@ func TestCreateSnapshotSync(t *testing.T) {
139139
{
140140
name: "6-4 - successful create snapshot with snapshot class empty-secret-class",
141141
initialContents: nocontents,
142-
expectedContents: newContentArray("snapcontent-snapuid6-4", emptySecretClass, "sid6-4", "pv-uid6-4", "volume6-4", "snapuid6-4", "snap6-4", &defaultSize, &timeNow),
142+
expectedContents: newContentArray("snapcontent-snapuid6-4", emptySecretClass, "sid6-4", "pv-uid6-4", "volume6-4", "snapuid6-4", "snap6-4", &defaultSize, &timeNow, false),
143143
initialSnapshots: newSnapshotArray("snap6-4", emptySecretClass, "", "snapuid6-4", "claim6-4", false, nil, nil, nil),
144144
expectedSnapshots: newSnapshotArray("snap6-4", emptySecretClass, "snapcontent-snapuid6-4", "snapuid6-4", "claim6-4", false, nil, metaTimeNowUnix, getSize(defaultSize)),
145145
initialClaims: newClaimArray("claim6-4", "pvc-uid6-4", "1Gi", "volume6-4", v1.ClaimBound, &classEmpty),
@@ -165,7 +165,7 @@ func TestCreateSnapshotSync(t *testing.T) {
165165
{
166166
name: "6-5 - successful create snapshot with status uploading",
167167
initialContents: nocontents,
168-
expectedContents: newContentArray("snapcontent-snapuid6-5", classGold, "sid6-5", "pv-uid6-5", "volume6-5", "snapuid6-5", "snap6-5", &defaultSize, &timeNow),
168+
expectedContents: newContentArray("snapcontent-snapuid6-5", classGold, "sid6-5", "pv-uid6-5", "volume6-5", "snapuid6-5", "snap6-5", &defaultSize, &timeNow, false),
169169
initialSnapshots: newSnapshotArray("snap6-5", classGold, "", "snapuid6-5", "claim6-5", false, nil, nil, nil),
170170
expectedSnapshots: newSnapshotArray("snap6-5", classGold, "snapcontent-snapuid6-5", "snapuid6-5", "claim6-5", false, nil, metaTimeNowUnix, getSize(defaultSize)),
171171
initialClaims: newClaimArray("claim6-5", "pvc-uid6-5", "1Gi", "volume6-5", v1.ClaimBound, &classEmpty),
@@ -189,7 +189,7 @@ func TestCreateSnapshotSync(t *testing.T) {
189189
{
190190
name: "6-6 - successful create snapshot with status error uploading",
191191
initialContents: nocontents,
192-
expectedContents: newContentArray("snapcontent-snapuid6-6", classGold, "sid6-6", "pv-uid6-6", "volume6-6", "snapuid6-6", "snap6-6", &defaultSize, &timeNow),
192+
expectedContents: newContentArray("snapcontent-snapuid6-6", classGold, "sid6-6", "pv-uid6-6", "volume6-6", "snapuid6-6", "snap6-6", &defaultSize, &timeNow, false),
193193
initialSnapshots: newSnapshotArray("snap6-6", classGold, "", "snapuid6-6", "claim6-6", false, nil, nil, nil),
194194
expectedSnapshots: newSnapshotArray("snap6-6", classGold, "snapcontent-snapuid6-6", "snapuid6-6", "claim6-6", false, nil, metaTimeNowUnix, getSize(defaultSize)),
195195
initialClaims: newClaimArray("claim6-6", "pvc-uid6-6", "1Gi", "volume6-6", v1.ClaimBound, &classEmpty),

0 commit comments

Comments
 (0)