Skip to content

Commit 281c33f

Browse files
authored
Clone from snapshot annotation
Add cloneFromSnapshot annotation and functionality
1 parent 5bdc66a commit 281c33f

File tree

2 files changed

+132
-6
lines changed

2 files changed

+132
-6
lines changed

frontend/csi/controller_helpers/kubernetes/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const (
4747
AnnBlockSize = annPrefix + "/blockSize"
4848
AnnFileSystem = annPrefix + "/fileSystem"
4949
AnnCloneFromPVC = annPrefix + "/cloneFromPVC"
50+
AnnCloneFromSnapshot = annPrefix + "/cloneFromSnapshot"
5051
AnnSplitOnClone = annPrefix + "/splitOnClone"
5152
AnnNotManaged = annPrefix + "/notManaged"
5253
AnnImportOriginalName = annPrefix + "/importOriginalName"

frontend/csi/controller_helpers/kubernetes/helper.go

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,28 @@ func (h *helper) GetVolumeConfig(
9292
annotations := processPVCAnnotations(pvc, fsType)
9393
volumeConfig := getVolumeConfig(ctx, pvc, pvName, pvcSize, annotations, sc, requisiteTopology, preferredTopology)
9494

95+
// Get clone annotations
96+
sourceSnapshotName := getAnnotation(annotations, AnnCloneFromSnapshot)
97+
sourcePVCName := getAnnotation(annotations, AnnCloneFromPVC)
98+
9599
// Check if we're cloning a PVC, and if so, do some further validation
96-
if cloneSourcePVName, err := h.getCloneSourceInfo(ctx, pvc); err != nil {
97-
return nil, err
98-
} else if cloneSourcePVName != "" {
99-
volumeConfig.CloneSourceVolume = cloneSourcePVName
100+
if sourcePVCName != "" && sourceSnapshotName == "" {
101+
if cloneSourcePVName, err := h.getCloneSourceInfo(ctx, pvc); err != nil {
102+
return nil, err
103+
} else if cloneSourcePVName != "" {
104+
volumeConfig.CloneSourceVolume = cloneSourcePVName
105+
}
106+
}
107+
108+
// Check if we're cloning from a snapshot, and if so, do some further validation
109+
if sourceSnapshotName != "" {
110+
cloneSourceVolume, cloneSourceSnapshot, err := h.getSnapshotCloneSourceInfo(ctx, pvc)
111+
if err != nil {
112+
return nil, err
113+
} else if cloneSourceVolume != "" && cloneSourceSnapshot != "" {
114+
volumeConfig.CloneSourceVolume = cloneSourceVolume
115+
volumeConfig.CloneSourceSnapshot = cloneSourceSnapshot
116+
}
100117
}
101118

102119
// Check if we're importing a volume and do some further validation
@@ -276,8 +293,73 @@ func (h *helper) getStorageClass(ctx context.Context, name string) (*k8sstoragev
276293
return sc, nil
277294
}
278295

296+
// getSnapshotCloneSourceInfo accepts the PVC of a volume being provisioned by CSI and inspects it
297+
// for the annotations indicating a snapshot clone operation (of which CSI is unaware).
298+
// The method completes several checks on the source snapshot, and PVC if provided, and returns the
299+
// name of the source PV and snapshot as needed by Trident to clone a volume.
300+
func (h *helper) getSnapshotCloneSourceInfo(
301+
ctx context.Context, clonePVC *v1.PersistentVolumeClaim,
302+
) (string, string, error) {
303+
// Check if this is a snapshot clone operation
304+
annotations := processPVCAnnotations(clonePVC, "")
305+
sourceSnapshotName := getAnnotation(annotations, AnnCloneFromSnapshot)
306+
if sourceSnapshotName == "" {
307+
return "", "", fmt.Errorf("annotation 'cloneFromSnapshot' is empty")
308+
}
309+
310+
// Get the VolumeSnapshot
311+
snapshot, err := h.getVolumeSnapshot(ctx, sourceSnapshotName, clonePVC.Namespace)
312+
if err != nil {
313+
return "", "", err
314+
}
315+
316+
// If the clone from PVC annotation is also set, ensure it matches the snapshot
317+
sourcePVCName := getAnnotation(annotations, AnnCloneFromPVC)
318+
if sourcePVCName != "" {
319+
snapSourcePVC := snapshot.Spec.Source.PersistentVolumeClaimName
320+
if snapSourcePVC == nil {
321+
return "", "", fmt.Errorf("cannot verify clone source PVC for snapshot '%s', "+
322+
"PersistentVolumeClaimName is not set in the snapshot spec", sourceSnapshotName)
323+
}
324+
if sourcePVCName != *snapSourcePVC {
325+
return "", "", fmt.Errorf("clone source snapshot '%s' does not originate from the given source "+
326+
"PVC '%s'", sourceSnapshotName, sourcePVCName)
327+
}
328+
}
329+
330+
// Ensure the VolumeSnapshot is ready to use
331+
if snapshot.Status.ReadyToUse == nil || !*snapshot.Status.ReadyToUse {
332+
return "", "", fmt.Errorf("snapshot '%s' is not ready to use", snapshot.Name)
333+
}
334+
335+
snapshotContent, err := h.getSnapshotContentFromSnapshot(ctx, snapshot)
336+
if err != nil {
337+
Logc(ctx).WithFields(LogFields{
338+
"sourceSnapshotName": sourceSnapshotName,
339+
}).Errorf("Clone source snapshot content not found: %v", err)
340+
return "", "", err
341+
}
342+
343+
// Ensure the VolumeSnapshotContent is ready to use
344+
if snapshotContent.Status.ReadyToUse == nil || !*snapshotContent.Status.ReadyToUse {
345+
return "", "", fmt.Errorf("volumeSnapshotContent '%s' is not ready to use", snapshotContent.Name)
346+
}
347+
348+
if snapshotContent.Status.SnapshotHandle == nil {
349+
return "", "", fmt.Errorf("volumeSnapshotContent '%s' does not have a snapshot handle",
350+
snapshotContent.Name)
351+
}
352+
353+
volumeName, snapshotName, err := storage.ParseSnapshotID(*snapshotContent.Status.SnapshotHandle)
354+
if err != nil {
355+
return "", "", err
356+
}
357+
358+
return volumeName, snapshotName, nil
359+
}
360+
279361
// getCloneSourceInfo accepts the PVC of a volume being provisioned by CSI and inspects it
280-
// for the annotations indicating a clone operation (of which CSI is unaware). If a clone is
362+
// for the annotations indicating a PVC clone operation (of which CSI is unaware). If a clone is
281363
// being created, the method completes several checks on the source PVC/PV and returns the
282364
// name of the source PV as needed by Trident to clone a volume as well as an optional
283365
// snapshot name (also potentially unknown to CSI). Note that these legacy clone annotations
@@ -287,7 +369,7 @@ func (h *helper) getCloneSourceInfo(ctx context.Context, clonePVC *v1.Persistent
287369
annotations := processPVCAnnotations(clonePVC, "")
288370
sourcePVCName := getAnnotation(annotations, AnnCloneFromPVC)
289371
if sourcePVCName == "" {
290-
return "", nil
372+
return "", fmt.Errorf("annotation 'cloneFromPVC' is empty")
291373
}
292374

293375
// Check that the source PVC is in the same namespace.
@@ -476,6 +558,49 @@ func (h *helper) getSnapshotContentByName(ctx context.Context, name string) (*vs
476558
return vsc, nil
477559
}
478560

561+
// getVolumeSnapshot returns a VolumeSnapshot if it exists.
562+
func (h *helper) getVolumeSnapshot(
563+
ctx context.Context, name, namespace string,
564+
) (*vsv1.VolumeSnapshot, error) {
565+
fields := LogFields{"snapshotName": name, "namespace": namespace}
566+
Logc(ctx).WithFields(fields).Trace(">>>> getVolumeSnapshot")
567+
defer Logc(ctx).WithFields(fields).Trace("<<<< getVolumeSnapshot")
568+
569+
// Get the VolumeSnapshot
570+
snapshot, err := h.snapClient.SnapshotV1().VolumeSnapshots(namespace).Get(ctx, name, getOpts)
571+
if err != nil {
572+
statusErr, ok := err.(*apierrors.StatusError)
573+
if ok && statusErr.Status().Reason == metav1.StatusReasonNotFound {
574+
return nil, errors.NotFoundError("snapshot %s not found; %v", name, statusErr)
575+
}
576+
return nil, err
577+
}
578+
return snapshot, nil
579+
}
580+
581+
// getSnapshotContentBySnapshotName returns the VolumeSnapshotContent referenced by a VolumeSnapshot
582+
func (h *helper) getSnapshotContentFromSnapshot(
583+
ctx context.Context, snapshot *vsv1.VolumeSnapshot,
584+
) (*vsv1.VolumeSnapshotContent, error) {
585+
fields := LogFields{"snapshotName": snapshot.Name}
586+
Logc(ctx).WithFields(fields).Trace(">>>> getSnapshotContentFromSnapshot")
587+
defer Logc(ctx).WithFields(fields).Trace("<<<< getSnapshotContentFromSnapshot")
588+
589+
// Extract the VolumeSnapshotContent name from the VolumeSnapshot status
590+
snapshotContentName := snapshot.Status.BoundVolumeSnapshotContentName
591+
if snapshotContentName == nil || *snapshotContentName == "" {
592+
return nil, errors.NotFoundError("boundVolumeSnapshotContentName not found for snapshot %s", snapshot.Name)
593+
}
594+
595+
// Get the VolumeSnapshotContent
596+
vsc, err := h.getSnapshotContentByName(ctx, *snapshotContentName)
597+
if err != nil {
598+
return nil, err
599+
}
600+
601+
return vsc, nil
602+
}
603+
479604
// getSnapshotInternalNameFromAnnotation gets the snapshotInternalName from an annotation on a VolumeSnapshotContent
480605
// for snapshot import.
481606
func (h *helper) getSnapshotInternalNameFromAnnotation(

0 commit comments

Comments
 (0)