@@ -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
160176func (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