Skip to content

Commit 6ed0fef

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 d50a9a9 commit 6ed0fef

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

pkg/controller/snapshot_controller.go

Lines changed: 160 additions & 0 deletions
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
// ==================================================================
@@ -77,6 +78,8 @@ import (
7778
// In the future version, a retry policy will be added.
7879

7980
const pvcKind = "PersistentVolumeClaim"
81+
const snapshotKind = "VolumeSnapshot"
82+
const snapshotAPIGroup = crdv1.GroupName // "snapshot.storage.k8s.io"
8083
const controllerUpdateFailMsg = "snapshot controller failed to update"
8184

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

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

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

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

pkg/controller/util.go

Lines changed: 25 additions & 0 deletions
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)