Skip to content

Commit 55e2188

Browse files
authored
fix(vd): respect user-specified storage class when restoring from snapshot (#1417)
Fix the VirtualDisk controller to respect user-specified storageClassName when creating disks from VirtualDiskSnapshot. Previously, the controller was always using the storage class from the original disk (stored in VolumeSnapshot annotations), completely ignoring the user-specified value in spec.persistentVolumeClaim.storageClassName. Also add error message when user tries to perform cross-provider restore. Signed-off-by: Daniil Loktev <[email protected]>
1 parent eb54391 commit 55e2188

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

images/virtualization-artifact/pkg/common/annotations/annotations.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ const (
160160
// AnnAccessMode is the annotation for indicating that access mode. (USED IN STORAGE sds controllers)
161161
AnnAccessModes = AnnAPIGroupV + "/access-mode"
162162
AnnAccessModesDeprecated = "accessModes"
163+
// AnnStorageProvisioner is the annotation for indicating storage provisioner
164+
AnnStorageProvisioner = "volume.kubernetes.io/storage-provisioner"
165+
AnnStorageProvisionerDeprecated = "volume.beta.kubernetes.io/storage-provisioner"
163166

164167
// AppLabel is the app name label.
165168
AppLabel = "app"

images/virtualization-artifact/pkg/controller/vd/internal/source/step/create_pvc_from_vdsnapshot_step.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
2626
corev1 "k8s.io/api/core/v1"
27+
storagev1 "k8s.io/api/storage/v1"
2728
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2829
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/types"
@@ -103,6 +104,21 @@ func (s CreatePVCFromVDSnapshotStep) Take(ctx context.Context, vd *v1alpha2.Virt
103104
return &reconcile.Result{}, nil
104105
}
105106

107+
if err := s.validateStorageClassCompatibility(ctx, vd, vdSnapshot, vs); err != nil {
108+
vd.Status.Phase = v1alpha2.DiskFailed
109+
s.cb.
110+
Status(metav1.ConditionFalse).
111+
Reason(vdcondition.ProvisioningFailed).
112+
Message(err.Error())
113+
s.recorder.Event(
114+
vd,
115+
corev1.EventTypeWarning,
116+
v1alpha2.ReasonDataSourceSyncFailed,
117+
err.Error(),
118+
)
119+
return &reconcile.Result{}, nil
120+
}
121+
106122
pvc := s.buildPVC(vd, vs)
107123

108124
err = s.client.Create(ctx, pvc)
@@ -158,10 +174,16 @@ func (s CreatePVCFromVDSnapshotStep) AddOriginalMetadata(vd *v1alpha2.VirtualDis
158174
}
159175

160176
func (s CreatePVCFromVDSnapshotStep) buildPVC(vd *v1alpha2.VirtualDisk, vs *vsv1.VolumeSnapshot) *corev1.PersistentVolumeClaim {
161-
storageClassName := vs.Annotations[annotations.AnnStorageClassName]
162-
if storageClassName == "" {
163-
storageClassName = vs.Annotations[annotations.AnnStorageClassNameDeprecated]
177+
var storageClassName string
178+
if vd.Spec.PersistentVolumeClaim.StorageClass != nil && *vd.Spec.PersistentVolumeClaim.StorageClass != "" {
179+
storageClassName = *vd.Spec.PersistentVolumeClaim.StorageClass
180+
} else {
181+
storageClassName = vs.Annotations[annotations.AnnStorageClassName]
182+
if storageClassName == "" {
183+
storageClassName = vs.Annotations[annotations.AnnStorageClassNameDeprecated]
184+
}
164185
}
186+
165187
volumeMode := vs.Annotations[annotations.AnnVolumeMode]
166188
if volumeMode == "" {
167189
volumeMode = vs.Annotations[annotations.AnnVolumeModeDeprecated]
@@ -219,3 +241,51 @@ func (s CreatePVCFromVDSnapshotStep) buildPVC(vd *v1alpha2.VirtualDisk, vs *vsv1
219241
Spec: spec,
220242
}
221243
}
244+
245+
func (s CreatePVCFromVDSnapshotStep) validateStorageClassCompatibility(ctx context.Context, vd *v1alpha2.VirtualDisk, vdSnapshot *v1alpha2.VirtualDiskSnapshot, vs *vsv1.VolumeSnapshot) error {
246+
if vd.Spec.PersistentVolumeClaim.StorageClass == nil || *vd.Spec.PersistentVolumeClaim.StorageClass == "" {
247+
return nil
248+
}
249+
250+
targetSCName := *vd.Spec.PersistentVolumeClaim.StorageClass
251+
252+
var targetSC storagev1.StorageClass
253+
err := s.client.Get(ctx, types.NamespacedName{Name: targetSCName}, &targetSC)
254+
if err != nil {
255+
return fmt.Errorf("cannot fetch target storage class %q: %w", targetSCName, err)
256+
}
257+
258+
if vs.Spec.Source.PersistentVolumeClaimName == nil || *vs.Spec.Source.PersistentVolumeClaimName == "" {
259+
// Can't determine original PVC, skip validation
260+
return nil
261+
}
262+
263+
pvcName := *vs.Spec.Source.PersistentVolumeClaimName
264+
265+
var originalPVC corev1.PersistentVolumeClaim
266+
err = s.client.Get(ctx, types.NamespacedName{Name: pvcName, Namespace: vdSnapshot.Namespace}, &originalPVC)
267+
if err != nil {
268+
return fmt.Errorf("cannot fetch original PVC %q: %w", pvcName, err)
269+
}
270+
271+
originalProvisioner := originalPVC.Annotations[annotations.AnnStorageProvisioner]
272+
if originalProvisioner == "" {
273+
originalProvisioner = originalPVC.Annotations[annotations.AnnStorageProvisionerDeprecated]
274+
}
275+
276+
if originalProvisioner == "" {
277+
// Can't determine original provisioner, skip validation
278+
return nil
279+
}
280+
281+
if targetSC.Provisioner != originalProvisioner {
282+
return fmt.Errorf(
283+
"cannot restore snapshot to storage class %q: incompatible storage providers. "+
284+
"Original snapshot was created by %q, target storage class uses %q. "+
285+
"Cross-provider snapshot restore is not supported",
286+
targetSCName, originalProvisioner, targetSC.Provisioner,
287+
)
288+
}
289+
290+
return nil
291+
}

0 commit comments

Comments
 (0)