Skip to content

Commit 70877b5

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 e36d31f commit 70877b5

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

pkg/controller/snapshot_controller.go

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

4041
// ==================================================================
@@ -78,6 +79,9 @@ import (
7879

7980
const pvcKind = "PersistentVolumeClaim"
8081
const apiGroup = ""
82+
const snapshotKind = "VolumeSnapshot"
83+
const snapshotAPIGroup = crdv1.GroupName
84+
8185
const controllerUpdateFailMsg = "snapshot controller failed to update"
8286

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

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

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

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

pkg/controller/util.go

+25
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"k8s.io/apimachinery/pkg/util/validation"
2828
"k8s.io/client-go/kubernetes"
2929
"k8s.io/client-go/tools/cache"
30+
"k8s.io/kubernetes/pkg/util/slice"
3031
"os"
3132
"strconv"
3233
"time"
@@ -39,6 +40,10 @@ var (
3940
const snapshotterSecretNameKey = "csiSnapshotterSecretName"
4041
const snapshotterSecretNamespaceKey = "csiSnapshotterSecretNamespace"
4142

43+
// Name of finalizer on VolumeSnapshotContents that are bound by VolumeSnapshots
44+
const VolumeSnapshotContentFinalizer = "snapshot.storage.kubernetes.io/volumesnapshotcontent-protection"
45+
const VolumeSnapshotFinalizer = "snapshot.storage.kubernetes.io/volumesnapshot-protection"
46+
4247
func snapshotKey(vs *crdv1.VolumeSnapshot) string {
4348
return fmt.Sprintf("%s/%s", vs.Namespace, vs.Name)
4449
}
@@ -240,3 +245,23 @@ func GetCredentials(k8s kubernetes.Interface, ref *v1.SecretReference) (map[stri
240245
func NoResyncPeriodFunc() time.Duration {
241246
return 0
242247
}
248+
249+
// isContentDeletionCandidate checks if a volume snapshot content is a deletion candidate.
250+
func isContentDeletionCandidate(content *crdv1.VolumeSnapshotContent) bool {
251+
return content.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(content.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer, nil)
252+
}
253+
254+
// needToAddContentFinalizer checks if a Finalizer needs to be added for the volume snapshot content.
255+
func needToAddContentFinalizer(content *crdv1.VolumeSnapshotContent) bool {
256+
return content.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(content.ObjectMeta.Finalizers, VolumeSnapshotContentFinalizer, nil)
257+
}
258+
259+
// isSnapshotDeletionCandidate checks if a volume snapshot is a deletion candidate.
260+
func isSnapshotDeletionCandidate(snapshot *crdv1.VolumeSnapshot) bool {
261+
return snapshot.ObjectMeta.DeletionTimestamp != nil && slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotFinalizer, nil)
262+
}
263+
264+
// needToAddSnapshotFinalizer checks if a Finalizer needs to be added for the volume snapshot.
265+
func needToAddSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot) bool {
266+
return snapshot.ObjectMeta.DeletionTimestamp == nil && !slice.ContainsString(snapshot.ObjectMeta.Finalizers, VolumeSnapshotFinalizer, nil)
267+
}

0 commit comments

Comments
 (0)