Skip to content

Commit 89ba368

Browse files
authored
fix(vi): respect storageClassName when creating from snapshot (#1533)
Fix the VirtualImage controller to respect user-specified storageClassName when creating images 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 abefef0 commit 89ba368

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/deckhouse/virtualization-controller/pkg/controller/service"
4040
vdsupplements "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/supplements"
4141
"github.com/deckhouse/virtualization-controller/pkg/eventrecord"
42+
"github.com/deckhouse/virtualization-controller/pkg/logger"
4243
"github.com/deckhouse/virtualization/api/core/v1alpha2"
4344
"github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition"
4445
)
@@ -255,8 +256,9 @@ func (s CreatePVCFromVDSnapshotStep) validateStorageClassCompatibility(ctx conte
255256
return fmt.Errorf("cannot fetch target storage class %q: %w", targetSCName, err)
256257
}
257258

259+
log, _ := logger.GetDataSourceContext(ctx, "objectref")
258260
if vs.Spec.Source.PersistentVolumeClaimName == nil || *vs.Spec.Source.PersistentVolumeClaimName == "" {
259-
// Can't determine original PVC, skip validation
261+
log.With("volumeSnapshot.name", vs.Name).Debug("Cannot determine original PVC from VolumeSnapshot, skipping storage class compatibility validation")
260262
return nil
261263
}
262264

@@ -274,7 +276,7 @@ func (s CreatePVCFromVDSnapshotStep) validateStorageClassCompatibility(ctx conte
274276
}
275277

276278
if originalProvisioner == "" {
277-
// Can't determine original provisioner, skip validation
279+
log.With("pvc.name", pvcName).Debug("Cannot determine original provisioner from PVC annotations, skipping storage class compatibility validation")
278280
return nil
279281
}
280282

images/virtualization-artifact/pkg/controller/vi/internal/source/step/create_pvc_step.go

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

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

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

108124
err = s.client.Create(ctx, pvc)
@@ -124,9 +140,14 @@ func (s CreatePersistentVolumeClaimStep) Take(ctx context.Context, vi *v1alpha2.
124140
}
125141

126142
func (s CreatePersistentVolumeClaimStep) buildPVC(vi *v1alpha2.VirtualImage, vs *vsv1.VolumeSnapshot) *corev1.PersistentVolumeClaim {
127-
storageClassName := vs.Annotations[annotations.AnnStorageClassName]
128-
if storageClassName == "" {
129-
storageClassName = vs.Annotations[annotations.AnnStorageClassNameDeprecated]
143+
var storageClassName string
144+
if vi.Spec.PersistentVolumeClaim.StorageClass != nil && *vi.Spec.PersistentVolumeClaim.StorageClass != "" {
145+
storageClassName = *vi.Spec.PersistentVolumeClaim.StorageClass
146+
} else {
147+
storageClassName = vs.Annotations[annotations.AnnStorageClassName]
148+
if storageClassName == "" {
149+
storageClassName = vs.Annotations[annotations.AnnStorageClassNameDeprecated]
150+
}
130151
}
131152
volumeMode := vs.Annotations[annotations.AnnVolumeMode]
132153
if volumeMode == "" {
@@ -185,3 +206,52 @@ func (s CreatePersistentVolumeClaimStep) buildPVC(vi *v1alpha2.VirtualImage, vs
185206
Spec: spec,
186207
}
187208
}
209+
210+
func (s CreatePersistentVolumeClaimStep) validateStorageClassCompatibility(ctx context.Context, vi *v1alpha2.VirtualImage, vdSnapshot *v1alpha2.VirtualDiskSnapshot, vs *vsv1.VolumeSnapshot) error {
211+
if vi.Spec.PersistentVolumeClaim.StorageClass == nil || *vi.Spec.PersistentVolumeClaim.StorageClass == "" {
212+
return nil
213+
}
214+
215+
targetSCName := *vi.Spec.PersistentVolumeClaim.StorageClass
216+
217+
var targetSC storagev1.StorageClass
218+
err := s.client.Get(ctx, types.NamespacedName{Name: targetSCName}, &targetSC)
219+
if err != nil {
220+
return fmt.Errorf("cannot fetch target storage class %q: %w", targetSCName, err)
221+
}
222+
223+
log, _ := logger.GetDataSourceContext(ctx, "objectref")
224+
if vs.Spec.Source.PersistentVolumeClaimName == nil || *vs.Spec.Source.PersistentVolumeClaimName == "" {
225+
log.With("volumeSnapshot.name", vs.Name).Debug("Cannot determine original PVC from VolumeSnapshot, skipping storage class compatibility validation")
226+
return nil
227+
}
228+
229+
pvcName := *vs.Spec.Source.PersistentVolumeClaimName
230+
231+
var originalPVC corev1.PersistentVolumeClaim
232+
err = s.client.Get(ctx, types.NamespacedName{Name: pvcName, Namespace: vdSnapshot.Namespace}, &originalPVC)
233+
if err != nil {
234+
return fmt.Errorf("cannot fetch original PVC %q: %w", pvcName, err)
235+
}
236+
237+
originalProvisioner := originalPVC.Annotations[annotations.AnnStorageProvisioner]
238+
if originalProvisioner == "" {
239+
originalProvisioner = originalPVC.Annotations[annotations.AnnStorageProvisionerDeprecated]
240+
}
241+
242+
if originalProvisioner == "" {
243+
log.With("pvc.name", pvcName).Debug("Cannot determine original provisioner from PVC annotations, skipping storage class compatibility validation")
244+
return nil
245+
}
246+
247+
if targetSC.Provisioner != originalProvisioner {
248+
return fmt.Errorf(
249+
"cannot restore snapshot to storage class %q: incompatible storage providers. "+
250+
"Original snapshot was created by %q, target storage class uses %q. "+
251+
"Cross-provider snapshot restore is not supported",
252+
targetSCName, originalProvisioner, targetSC.Provisioner,
253+
)
254+
}
255+
256+
return nil
257+
}

0 commit comments

Comments
 (0)