From 5994cee1ed05949cadb711ac37d7dcc0d1c76fac Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 22 Apr 2026 11:57:10 +0300 Subject: [PATCH 1/7] add webhook Signed-off-by: Valeriy Khorunzhin --- api/core/v1alpha2/vdcondition/condition.go | 2 + .../pkg/controller/vd/internal/life_cycle.go | 41 +++++- .../controller/vd/internal/life_cycle_test.go | 109 ++++++++++++++ .../vi_pvc_storage_class_validator.go | 120 +++++++++++++++ .../vi_pvc_storage_class_validator_test.go | 139 ++++++++++++++++++ .../pkg/controller/vd/vd_webhook.go | 1 + 6 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go create mode 100644 images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go diff --git a/api/core/v1alpha2/vdcondition/condition.go b/api/core/v1alpha2/vdcondition/condition.go index e8dd94d4ab..43a2ee9df5 100644 --- a/api/core/v1alpha2/vdcondition/condition.go +++ b/api/core/v1alpha2/vdcondition/condition.go @@ -127,6 +127,8 @@ const ( DatasourceIsNotFound ReadyReason = "DatasourceIsNotFound" // StorageClassIsNotReady indicates that Storage class is not ready. StorageClassIsNotReady ReadyReason = "StorageClassIsNotReady" + // StorageClassNotMatchingSource indicates that the VirtualDisk storage class does not match the source storage class. + StorageClassNotMatchingSource ReadyReason = "StorageClassNotMatchingSource" // InProgress indicates that the resize request has been detected and the operation is currently in progress. InProgress ResizedReason = "InProgress" diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go index b53e426b50..fdf6d703d9 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go @@ -22,10 +22,13 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/common/object" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/source" "github.com/deckhouse/virtualization-controller/pkg/eventrecord" "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -126,7 +129,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) cb. Status(metav1.ConditionFalse). Reason(vdcondition.StorageClassIsNotReady). - Message("Storage class in not ready") + Message("Storage class is not ready.") conditions.SetCondition(cb, &vd.Status.Conditions) return reconcile.Result{}, nil @@ -135,6 +138,17 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) if vd.Status.StorageClassName == "" { return reconcile.Result{}, fmt.Errorf("empty storage class in status") } + + err := h.validateVirtualImageStorageClassMatch(ctx, vd) + if err != nil { + cb. + Status(metav1.ConditionFalse). + Reason(vdcondition.StorageClassNotMatchingSource). + Message(service.CapitalizeFirstLetter(err.Error())) + conditions.SetCondition(cb, &vd.Status.Conditions) + + return reconcile.Result{}, nil + } } var ds source.Handler @@ -154,3 +168,28 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) return result, nil } + +func (h LifeCycleHandler) validateVirtualImageStorageClassMatch(ctx context.Context, vd *v1alpha2.VirtualDisk) error { + if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != v1alpha2.DataSourceTypeObjectRef { + return nil + } + + if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != v1alpha2.VirtualDiskObjectRefKindVirtualImage { + return nil + } + + vi, err := object.FetchObject(ctx, types.NamespacedName{Namespace: vd.Namespace, Name: vd.Spec.DataSource.ObjectRef.Name}, h.client, &v1alpha2.VirtualImage{}) + if err != nil { + return err + } + + if vi == nil || vi.Status.Phase != v1alpha2.ImageReady || vi.Spec.Storage == v1alpha2.StorageContainerRegistry { + return nil + } + + if vi.Status.StorageClassName != vd.Status.StorageClassName { + return fmt.Errorf("virtual disk storage class %q does not match virtual image storage class %q", vd.Status.StorageClassName, vi.Status.StorageClassName) + } + + return nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go index fe561d8663..b1a0730867 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go @@ -22,8 +22,11 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/testutil" @@ -289,6 +292,112 @@ var _ = Describe("LifeCycleHandler Run", func() { vdcondition.DatasourceIsNotFound.String(), ), ) + + It("should handle a VirtualDisk without data source", func() { + var sourcesMock SourcesMock + recorder := &eventrecord.EventRecorderLoggerMock{ + EventFunc: func(_ client.Object, _, _, _ string) {}, + } + ctx := logger.ToContext(context.TODO(), testutil.NewNoOpSlogLogger()) + syncCalled := false + blank := &source.HandlerMock{ + SyncFunc: func(_ context.Context, _ *v1alpha2.VirtualDisk) (reconcile.Result, error) { + syncCalled = true + return reconcile.Result{}, nil + }, + } + vd := v1alpha2.VirtualDisk{ + Status: v1alpha2.VirtualDiskStatus{ + StorageClassName: "vd-sc", + Conditions: []metav1.Condition{ + { + Type: vdcondition.DatasourceReadyType.String(), + Status: metav1.ConditionTrue, + }, + { + Type: vdcondition.StorageClassReadyType.String(), + Status: metav1.ConditionTrue, + }, + }, + }, + } + + sourcesMock.ChangedFunc = func(_ context.Context, _ *v1alpha2.VirtualDisk) bool { + return false + } + handler := NewLifeCycleHandler(recorder, blank, &sourcesMock, nil) + + Expect(func() { + _, _ = handler.Handle(ctx, &vd) + }).NotTo(Panic()) + Expect(syncCalled).To(BeTrue()) + }) + + It("should set a dedicated reason when storage class does not match the source virtual image", func() { + scheme := runtime.NewScheme() + Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) + Expect(storagev1.AddToScheme(scheme)).To(Succeed()) + + vi := &v1alpha2.VirtualImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "source-vi", + Namespace: "default", + }, + Spec: v1alpha2.VirtualImageSpec{ + Storage: v1alpha2.StoragePersistentVolumeClaim, + }, + Status: v1alpha2.VirtualImageStatus{ + Phase: v1alpha2.ImageReady, + StorageClassName: "vi-sc", + }, + } + + k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi).Build() + var sourcesMock SourcesMock + sourcesMock.ChangedFunc = func(_ context.Context, _ *v1alpha2.VirtualDisk) bool { + return false + } + recorder := &eventrecord.EventRecorderLoggerMock{ + EventFunc: func(_ client.Object, _, _, _ string) {}, + } + ctx := logger.ToContext(context.TODO(), testutil.NewNoOpSlogLogger()) + vd := v1alpha2.VirtualDisk{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: v1alpha2.VirtualDiskSpec{ + DataSource: &v1alpha2.VirtualDiskDataSource{ + Type: v1alpha2.DataSourceTypeObjectRef, + ObjectRef: &v1alpha2.VirtualDiskObjectRef{ + Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, + Name: vi.Name, + }, + }, + }, + Status: v1alpha2.VirtualDiskStatus{ + StorageClassName: "vd-sc", + Conditions: []metav1.Condition{ + { + Type: vdcondition.DatasourceReadyType.String(), + Status: metav1.ConditionTrue, + }, + { + Type: vdcondition.StorageClassReadyType.String(), + Status: metav1.ConditionTrue, + }, + }, + }, + } + + handler := NewLifeCycleHandler(recorder, &source.HandlerMock{}, &sourcesMock, k8sClient) + _, err := handler.Handle(ctx, &vd) + Expect(err).NotTo(HaveOccurred()) + + readyCond, ok := conditions.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + Expect(ok).To(BeTrue()) + Expect(readyCond.Reason).To(Equal(vdcondition.StorageClassNotMatchingSource.String())) + Expect(readyCond.Message).To(Equal(`Virtual disk storage class "vd-sc" does not match virtual image storage class "vi-sc"`)) + }) }) type cleanupAfterSpecChangeTestArgs struct { diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go new file mode 100644 index 0000000000..7950446aa0 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go @@ -0,0 +1,120 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validator + +import ( + "context" + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/deckhouse/virtualization-controller/pkg/common/object" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + intsvc "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/source" + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" +) + +type VirtualImagePVCStorageClassValidator struct { + client client.Client + scService *intsvc.VirtualDiskStorageClassService +} + +func NewVirtualImagePVCStorageClassValidator(client client.Client, scService *intsvc.VirtualDiskStorageClassService) *VirtualImagePVCStorageClassValidator { + return &VirtualImagePVCStorageClassValidator{ + client: client, + scService: scService, + } +} + +func (v *VirtualImagePVCStorageClassValidator) ValidateCreate(ctx context.Context, vd *v1alpha2.VirtualDisk) (admission.Warnings, error) { + return nil, v.validate(ctx, vd) +} + +func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Context, _, newVD *v1alpha2.VirtualDisk) (admission.Warnings, error) { + ready, _ := conditions.GetCondition(vdcondition.ReadyType, newVD.Status.Conditions) + if source.IsDiskProvisioningFinished(ready) { + return nil, nil + } + + return nil, v.validate(ctx, newVD) +} + +func (v *VirtualImagePVCStorageClassValidator) validate(ctx context.Context, vd *v1alpha2.VirtualDisk) error { + if vd.Spec.DataSource == nil { + return nil + } + + isObjectRef := vd.Spec.DataSource.Type == v1alpha2.DataSourceTypeObjectRef + objRef := vd.Spec.DataSource.ObjectRef + if isObjectRef && objRef != nil && objRef.Kind == v1alpha2.VirtualDiskObjectRefKindVirtualImage { + vi, err := object.FetchObject(ctx, types.NamespacedName{Namespace: vd.Namespace, Name: objRef.Name}, v.client, &v1alpha2.VirtualImage{}) + if err != nil { + return err + } + + if vi == nil || vi.Status.Phase != v1alpha2.ImageReady || vi.Spec.Storage == v1alpha2.StorageContainerRegistry { + return nil + } + + vdSc, err := v.extractVDStorageClassName(ctx, vd) + if err != nil { + return err + } + + if vdSc != vi.Status.StorageClassName { + return fmt.Errorf("virtual disk storage class %q does not match virtual image storage class %q", vdSc, vi.Status.StorageClassName) + } + } + + return nil +} + +func (v *VirtualImagePVCStorageClassValidator) extractVDStorageClassName(ctx context.Context, vd *v1alpha2.VirtualDisk) (string, error) { + if vd.Status.StorageClassName != "" { + return vd.Status.StorageClassName, nil + } + + if vd.Spec.PersistentVolumeClaim.StorageClass != nil { + return *vd.Spec.PersistentVolumeClaim.StorageClass, nil + } + + moduleStorageClass, err := v.scService.GetModuleStorageClass(ctx) + if err != nil { + return "", err + } + + if moduleStorageClass != nil { + return moduleStorageClass.Name, nil + } + + defaultStorageClass, err := v.scService.GetDefaultStorageClass(ctx) + if err != nil && !errors.Is(err, service.ErrDefaultStorageClassNotFound) { + return "", err + } + + if defaultStorageClass != nil { + return defaultStorageClass.Name, nil + } + + return "", fmt.Errorf("storage class for VirtualDisk %q cannot be determined", vd.Name) +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go new file mode 100644 index 0000000000..0436eab792 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2026 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validator + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" + "github.com/deckhouse/virtualization-controller/pkg/config" + basevc "github.com/deckhouse/virtualization-controller/pkg/controller/service" + intsvc "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/service" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +var _ = Describe("VirtualImagePVCStorageClassValidator", func() { + It("should use the default storage class when VirtualDisk storage class is not set", func() { + scheme := runtime.NewScheme() + Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) + Expect(storagev1.AddToScheme(scheme)).To(Succeed()) + + defaultSC := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-sc", + Annotations: map[string]string{ + annotations.AnnDefaultStorageClass: "true", + }, + }, + } + vi := &v1alpha2.VirtualImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "source-vi", + Namespace: "default", + }, + Spec: v1alpha2.VirtualImageSpec{ + Storage: v1alpha2.StoragePersistentVolumeClaim, + }, + Status: v1alpha2.VirtualImageStatus{ + Phase: v1alpha2.ImageReady, + StorageClassName: defaultSC.Name, + }, + } + vd := &v1alpha2.VirtualDisk{ + ObjectMeta: metav1.ObjectMeta{ + Name: "target-vd", + Namespace: "default", + }, + Spec: v1alpha2.VirtualDiskSpec{ + DataSource: &v1alpha2.VirtualDiskDataSource{ + Type: v1alpha2.DataSourceTypeObjectRef, + ObjectRef: &v1alpha2.VirtualDiskObjectRef{ + Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, + Name: vi.Name, + }, + }, + }, + } + + k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(defaultSC, vi).Build() + baseSCService := basevc.NewBaseStorageClassService(k8sClient) + vdSCService := intsvc.NewVirtualDiskStorageClassService(baseSCService, config.VirtualDiskStorageClassSettings{}) + validator := NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) + + var err error + Expect(func() { + _, err = validator.ValidateCreate(context.Background(), vd) + }).NotTo(Panic()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should return a readable mismatch error", func() { + scheme := runtime.NewScheme() + Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) + Expect(storagev1.AddToScheme(scheme)).To(Succeed()) + + vi := &v1alpha2.VirtualImage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "source-vi", + Namespace: "default", + }, + Spec: v1alpha2.VirtualImageSpec{ + Storage: v1alpha2.StoragePersistentVolumeClaim, + }, + Status: v1alpha2.VirtualImageStatus{ + Phase: v1alpha2.ImageReady, + StorageClassName: "vi-sc", + }, + } + vd := &v1alpha2.VirtualDisk{ + ObjectMeta: metav1.ObjectMeta{ + Name: "target-vd", + Namespace: "default", + }, + Spec: v1alpha2.VirtualDiskSpec{ + PersistentVolumeClaim: v1alpha2.VirtualDiskPersistentVolumeClaim{ + StorageClass: func() *string { + sc := "vd-sc" + return &sc + }(), + }, + DataSource: &v1alpha2.VirtualDiskDataSource{ + Type: v1alpha2.DataSourceTypeObjectRef, + ObjectRef: &v1alpha2.VirtualDiskObjectRef{ + Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, + Name: vi.Name, + }, + }, + }, + } + + k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi).Build() + baseSCService := basevc.NewBaseStorageClassService(k8sClient) + vdSCService := intsvc.NewVirtualDiskStorageClassService(baseSCService, config.VirtualDiskStorageClassSettings{}) + validator := NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) + + _, err := validator.ValidateCreate(context.Background(), vd) + Expect(err).To(MatchError(`virtual disk storage class "vd-sc" does not match virtual image storage class "vi-sc"`)) + }) +}) diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_webhook.go b/images/virtualization-artifact/pkg/controller/vd/vd_webhook.go index f78d76e72f..a86a3a4f3f 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_webhook.go @@ -49,6 +49,7 @@ func NewValidator(client client.Client, scService *intsvc.VirtualDiskStorageClas validator.NewISOSourceValidator(client), validator.NewNameValidator(), validator.NewMigrationStorageClassValidator(client, scService, modeGetter, featuregates.Default()), + validator.NewVirtualImagePVCStorageClassValidator(client, scService), }, } } From 72b68d6419e51776d1574f2430b0531c4417da6e Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 27 Apr 2026 14:42:50 +0300 Subject: [PATCH 2/7] refactoring Signed-off-by: Valeriy Khorunzhin --- .../pkg/common/vd/vd.go | 30 +++++++++++++ .../pkg/controller/vd/internal/life_cycle.go | 30 +------------ .../vi_pvc_storage_class_validator.go | 45 +++++-------------- 3 files changed, 43 insertions(+), 62 deletions(-) diff --git a/images/virtualization-artifact/pkg/common/vd/vd.go b/images/virtualization-artifact/pkg/common/vd/vd.go index 6ca3b64cfe..1a66eb7228 100644 --- a/images/virtualization-artifact/pkg/common/vd/vd.go +++ b/images/virtualization-artifact/pkg/common/vd/vd.go @@ -17,10 +17,15 @@ limitations under the License. package vd import ( + "context" + "fmt" "log/slog" + "k8s.io/apimachinery/pkg/types" "k8s.io/component-base/featuregate" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/deckhouse/virtualization-controller/pkg/common/object" "github.com/deckhouse/virtualization-controller/pkg/featuregates" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -68,3 +73,28 @@ func StorageClassChanged(vd *v1alpha2.VirtualDisk) bool { return *specSc != "" && statusSc != "" } + +func ValidateVirtualImageStorageClassMatch(ctx context.Context, vd *v1alpha2.VirtualDisk, client client.Client) error { + if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != v1alpha2.DataSourceTypeObjectRef { + return nil + } + + if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != v1alpha2.VirtualDiskObjectRefKindVirtualImage { + return nil + } + + vi, err := object.FetchObject(ctx, types.NamespacedName{Namespace: vd.Namespace, Name: vd.Spec.DataSource.ObjectRef.Name}, client, &v1alpha2.VirtualImage{}) + if err != nil { + return err + } + + if vi == nil || vi.Status.Phase != v1alpha2.ImageReady || vi.Spec.Storage == v1alpha2.StorageContainerRegistry { + return nil + } + + if vi.Status.StorageClassName != vd.Status.StorageClassName { + return fmt.Errorf("virtual disk storage class %q does not match virtual image storage class %q", vd.Status.StorageClassName, vi.Status.StorageClassName) + } + + return nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go index fdf6d703d9..2d458c88c8 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go @@ -22,11 +22,10 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/deckhouse/virtualization-controller/pkg/common/object" + commonvd "github.com/deckhouse/virtualization-controller/pkg/common/vd" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/source" @@ -139,7 +138,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) return reconcile.Result{}, fmt.Errorf("empty storage class in status") } - err := h.validateVirtualImageStorageClassMatch(ctx, vd) + err := commonvd.ValidateVirtualImageStorageClassMatch(ctx, vd, h.client) if err != nil { cb. Status(metav1.ConditionFalse). @@ -168,28 +167,3 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) return result, nil } - -func (h LifeCycleHandler) validateVirtualImageStorageClassMatch(ctx context.Context, vd *v1alpha2.VirtualDisk) error { - if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != v1alpha2.DataSourceTypeObjectRef { - return nil - } - - if vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != v1alpha2.VirtualDiskObjectRefKindVirtualImage { - return nil - } - - vi, err := object.FetchObject(ctx, types.NamespacedName{Namespace: vd.Namespace, Name: vd.Spec.DataSource.ObjectRef.Name}, h.client, &v1alpha2.VirtualImage{}) - if err != nil { - return err - } - - if vi == nil || vi.Status.Phase != v1alpha2.ImageReady || vi.Spec.Storage == v1alpha2.StorageContainerRegistry { - return nil - } - - if vi.Status.StorageClassName != vd.Status.StorageClassName { - return fmt.Errorf("virtual disk storage class %q does not match virtual image storage class %q", vd.Status.StorageClassName, vi.Status.StorageClassName) - } - - return nil -} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go index 7950446aa0..d41f10cd92 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go @@ -21,11 +21,10 @@ import ( "errors" "fmt" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - "github.com/deckhouse/virtualization-controller/pkg/common/object" + commonvd "github.com/deckhouse/virtualization-controller/pkg/common/vd" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" intsvc "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/service" @@ -47,7 +46,15 @@ func NewVirtualImagePVCStorageClassValidator(client client.Client, scService *in } func (v *VirtualImagePVCStorageClassValidator) ValidateCreate(ctx context.Context, vd *v1alpha2.VirtualDisk) (admission.Warnings, error) { - return nil, v.validate(ctx, vd) + scName, err := v.extractVDStorageClassName(ctx, vd) + if err != nil { + return nil, err + } + + vdWithStatusStorageClassName := vd.DeepCopy() + vdWithStatusStorageClassName.Status.StorageClassName = scName + + return nil, commonvd.ValidateVirtualImageStorageClassMatch(ctx, vdWithStatusStorageClassName, v.client) } func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Context, _, newVD *v1alpha2.VirtualDisk) (admission.Warnings, error) { @@ -56,37 +63,7 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Contex return nil, nil } - return nil, v.validate(ctx, newVD) -} - -func (v *VirtualImagePVCStorageClassValidator) validate(ctx context.Context, vd *v1alpha2.VirtualDisk) error { - if vd.Spec.DataSource == nil { - return nil - } - - isObjectRef := vd.Spec.DataSource.Type == v1alpha2.DataSourceTypeObjectRef - objRef := vd.Spec.DataSource.ObjectRef - if isObjectRef && objRef != nil && objRef.Kind == v1alpha2.VirtualDiskObjectRefKindVirtualImage { - vi, err := object.FetchObject(ctx, types.NamespacedName{Namespace: vd.Namespace, Name: objRef.Name}, v.client, &v1alpha2.VirtualImage{}) - if err != nil { - return err - } - - if vi == nil || vi.Status.Phase != v1alpha2.ImageReady || vi.Spec.Storage == v1alpha2.StorageContainerRegistry { - return nil - } - - vdSc, err := v.extractVDStorageClassName(ctx, vd) - if err != nil { - return err - } - - if vdSc != vi.Status.StorageClassName { - return fmt.Errorf("virtual disk storage class %q does not match virtual image storage class %q", vdSc, vi.Status.StorageClassName) - } - } - - return nil + return nil, commonvd.ValidateVirtualImageStorageClassMatch(ctx, newVD, v.client) } func (v *VirtualImagePVCStorageClassValidator) extractVDStorageClassName(ctx context.Context, vd *v1alpha2.VirtualDisk) (string, error) { From e356f6a08886c396e45352099ec4f813130f2e93 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Thu, 30 Apr 2026 12:27:15 +0300 Subject: [PATCH 3/7] csi, not sc Signed-off-by: Valeriy Khorunzhin --- api/core/v1alpha2/vdcondition/condition.go | 4 ++-- .../pkg/common/vd/vd.go | 23 ++++++++++++++++--- .../pkg/controller/vd/internal/life_cycle.go | 4 ++-- .../controller/vd/internal/life_cycle_test.go | 20 +++++++++++++--- .../vi_pvc_storage_class_validator.go | 4 ++-- .../vi_pvc_storage_class_validator_test.go | 17 ++++++++++++-- 6 files changed, 58 insertions(+), 14 deletions(-) diff --git a/api/core/v1alpha2/vdcondition/condition.go b/api/core/v1alpha2/vdcondition/condition.go index 43a2ee9df5..cff18b5bb5 100644 --- a/api/core/v1alpha2/vdcondition/condition.go +++ b/api/core/v1alpha2/vdcondition/condition.go @@ -127,8 +127,8 @@ const ( DatasourceIsNotFound ReadyReason = "DatasourceIsNotFound" // StorageClassIsNotReady indicates that Storage class is not ready. StorageClassIsNotReady ReadyReason = "StorageClassIsNotReady" - // StorageClassNotMatchingSource indicates that the VirtualDisk storage class does not match the source storage class. - StorageClassNotMatchingSource ReadyReason = "StorageClassNotMatchingSource" + // StorageClassCSIDriverMismatch indicates that the VirtualDisk and source VirtualImage storage classes have different CSI drivers. + StorageClassCSIDriverMismatch ReadyReason = "StorageClassCSIDriverMismatch" // InProgress indicates that the resize request has been detected and the operation is currently in progress. InProgress ResizedReason = "InProgress" diff --git a/images/virtualization-artifact/pkg/common/vd/vd.go b/images/virtualization-artifact/pkg/common/vd/vd.go index 1a66eb7228..d1c35a7de6 100644 --- a/images/virtualization-artifact/pkg/common/vd/vd.go +++ b/images/virtualization-artifact/pkg/common/vd/vd.go @@ -21,6 +21,7 @@ import ( "fmt" "log/slog" + storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/component-base/featuregate" "sigs.k8s.io/controller-runtime/pkg/client" @@ -74,7 +75,7 @@ func StorageClassChanged(vd *v1alpha2.VirtualDisk) bool { return *specSc != "" && statusSc != "" } -func ValidateVirtualImageStorageClassMatch(ctx context.Context, vd *v1alpha2.VirtualDisk, client client.Client) error { +func ValidateVirtualImageStorageClassProvisionerMatch(ctx context.Context, vd *v1alpha2.VirtualDisk, client client.Client) error { if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != v1alpha2.DataSourceTypeObjectRef { return nil } @@ -92,8 +93,24 @@ func ValidateVirtualImageStorageClassMatch(ctx context.Context, vd *v1alpha2.Vir return nil } - if vi.Status.StorageClassName != vd.Status.StorageClassName { - return fmt.Errorf("virtual disk storage class %q does not match virtual image storage class %q", vd.Status.StorageClassName, vi.Status.StorageClassName) + vdSc, err := object.FetchObject(ctx, types.NamespacedName{Name: vd.Status.StorageClassName}, client, &storagev1.StorageClass{}) + if err != nil { + return fmt.Errorf("get virtual disk storage class %q: %w", vd.Status.StorageClassName, err) + } + if vdSc == nil { + return fmt.Errorf("virtual disk storage class %q was not found", vd.Status.StorageClassName) + } + + viSc, err := object.FetchObject(ctx, types.NamespacedName{Name: vi.Status.StorageClassName}, client, &storagev1.StorageClass{}) + if err != nil { + return fmt.Errorf("get virtual image storage class %q: %w", vi.Status.StorageClassName, err) + } + if viSc == nil { + return fmt.Errorf("virtual image storage class %q was not found", vi.Status.StorageClassName) + } + + if vdSc.Provisioner != viSc.Provisioner { + return fmt.Errorf("virtual disk storage class %q csi driver does not match virtual image storage class %q csi driver", vd.Status.StorageClassName, vi.Status.StorageClassName) } return nil diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go index 2d458c88c8..2b5f0dbb87 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go @@ -138,11 +138,11 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) return reconcile.Result{}, fmt.Errorf("empty storage class in status") } - err := commonvd.ValidateVirtualImageStorageClassMatch(ctx, vd, h.client) + err := commonvd.ValidateVirtualImageStorageClassProvisionerMatch(ctx, vd, h.client) if err != nil { cb. Status(metav1.ConditionFalse). - Reason(vdcondition.StorageClassNotMatchingSource). + Reason(vdcondition.StorageClassCSIDriverMismatch). Message(service.CapitalizeFirstLetter(err.Error())) conditions.SetCondition(cb, &vd.Status.Conditions) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go index b1a0730867..18dcf47621 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go @@ -352,7 +352,21 @@ var _ = Describe("LifeCycleHandler Run", func() { }, } - k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi).Build() + vdSC := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vd-sc", + }, + Provisioner: "first.csi.example.com", + } + + viSC := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vi-sc", + }, + Provisioner: "second.csi.example.com", + } + + k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi, vdSC, viSC).Build() var sourcesMock SourcesMock sourcesMock.ChangedFunc = func(_ context.Context, _ *v1alpha2.VirtualDisk) bool { return false @@ -395,8 +409,8 @@ var _ = Describe("LifeCycleHandler Run", func() { readyCond, ok := conditions.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) Expect(ok).To(BeTrue()) - Expect(readyCond.Reason).To(Equal(vdcondition.StorageClassNotMatchingSource.String())) - Expect(readyCond.Message).To(Equal(`Virtual disk storage class "vd-sc" does not match virtual image storage class "vi-sc"`)) + Expect(readyCond.Reason).To(Equal(vdcondition.StorageClassCSIDriverMismatch.String())) + Expect(readyCond.Message).To(Equal(`Virtual disk storage class "vd-sc" csi driver does not match virtual image storage class "vi-sc" csi driver`)) }) }) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go index d41f10cd92..5e6497b114 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go @@ -54,7 +54,7 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateCreate(ctx context.Contex vdWithStatusStorageClassName := vd.DeepCopy() vdWithStatusStorageClassName.Status.StorageClassName = scName - return nil, commonvd.ValidateVirtualImageStorageClassMatch(ctx, vdWithStatusStorageClassName, v.client) + return nil, commonvd.ValidateVirtualImageStorageClassProvisionerMatch(ctx, vdWithStatusStorageClassName, v.client) } func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Context, _, newVD *v1alpha2.VirtualDisk) (admission.Warnings, error) { @@ -63,7 +63,7 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Contex return nil, nil } - return nil, commonvd.ValidateVirtualImageStorageClassMatch(ctx, newVD, v.client) + return nil, commonvd.ValidateVirtualImageStorageClassProvisionerMatch(ctx, newVD, v.client) } func (v *VirtualImagePVCStorageClassValidator) extractVDStorageClassName(ctx context.Context, vd *v1alpha2.VirtualDisk) (string, error) { diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go index 0436eab792..be5012056f 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go @@ -60,6 +60,7 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { StorageClassName: defaultSC.Name, }, } + vd := &v1alpha2.VirtualDisk{ ObjectMeta: metav1.ObjectMeta{ Name: "target-vd", @@ -106,6 +107,18 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { StorageClassName: "vi-sc", }, } + vdSC := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vd-sc", + }, + Provisioner: "first.csi.example.com", + } + viSC := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vi-sc", + }, + Provisioner: "second.csi.example.com", + } vd := &v1alpha2.VirtualDisk{ ObjectMeta: metav1.ObjectMeta{ Name: "target-vd", @@ -128,12 +141,12 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { }, } - k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi).Build() + k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi, vdSC, viSC).Build() baseSCService := basevc.NewBaseStorageClassService(k8sClient) vdSCService := intsvc.NewVirtualDiskStorageClassService(baseSCService, config.VirtualDiskStorageClassSettings{}) validator := NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) _, err := validator.ValidateCreate(context.Background(), vd) - Expect(err).To(MatchError(`virtual disk storage class "vd-sc" does not match virtual image storage class "vi-sc"`)) + Expect(err).To(MatchError(`virtual disk storage class "vd-sc" csi driver does not match virtual image storage class "vi-sc" csi driver`)) }) }) From 59cfeafef3a48f2ac7fdd9d85b28850ef02835ab Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Thu, 30 Apr 2026 14:16:36 +0300 Subject: [PATCH 4/7] provisioner Signed-off-by: Valeriy Khorunzhin --- api/core/v1alpha2/vdcondition/condition.go | 4 ++-- images/virtualization-artifact/pkg/common/vd/vd.go | 6 +++++- .../pkg/controller/vd/internal/life_cycle.go | 2 +- .../pkg/controller/vd/internal/life_cycle_test.go | 4 ++-- .../validator/vi_pvc_storage_class_validator_test.go | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/api/core/v1alpha2/vdcondition/condition.go b/api/core/v1alpha2/vdcondition/condition.go index cff18b5bb5..76606f7a54 100644 --- a/api/core/v1alpha2/vdcondition/condition.go +++ b/api/core/v1alpha2/vdcondition/condition.go @@ -127,8 +127,8 @@ const ( DatasourceIsNotFound ReadyReason = "DatasourceIsNotFound" // StorageClassIsNotReady indicates that Storage class is not ready. StorageClassIsNotReady ReadyReason = "StorageClassIsNotReady" - // StorageClassCSIDriverMismatch indicates that the VirtualDisk and source VirtualImage storage classes have different CSI drivers. - StorageClassCSIDriverMismatch ReadyReason = "StorageClassCSIDriverMismatch" + // StorageClassProvisionerMismatch indicates that the VirtualDisk and source VirtualImage storage classes have different provisioners. + StorageClassProvisionerMismatch ReadyReason = "StorageClassProvisionerMismatch" // InProgress indicates that the resize request has been detected and the operation is currently in progress. InProgress ResizedReason = "InProgress" diff --git a/images/virtualization-artifact/pkg/common/vd/vd.go b/images/virtualization-artifact/pkg/common/vd/vd.go index d1c35a7de6..26a9dc1294 100644 --- a/images/virtualization-artifact/pkg/common/vd/vd.go +++ b/images/virtualization-artifact/pkg/common/vd/vd.go @@ -110,7 +110,11 @@ func ValidateVirtualImageStorageClassProvisionerMatch(ctx context.Context, vd *v } if vdSc.Provisioner != viSc.Provisioner { - return fmt.Errorf("virtual disk storage class %q csi driver does not match virtual image storage class %q csi driver", vd.Status.StorageClassName, vi.Status.StorageClassName) + return fmt.Errorf( + "virtual disk storage class %q provisioner does not match virtual image storage class %q provisioner: source type with different provisioners is not supported yet", + vd.Status.StorageClassName, + vi.Status.StorageClassName, + ) } return nil diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go index 2b5f0dbb87..d8241ed882 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go @@ -142,7 +142,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) if err != nil { cb. Status(metav1.ConditionFalse). - Reason(vdcondition.StorageClassCSIDriverMismatch). + Reason(vdcondition.StorageClassProvisionerMismatch). Message(service.CapitalizeFirstLetter(err.Error())) conditions.SetCondition(cb, &vd.Status.Conditions) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go index 18dcf47621..08d13e55de 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle_test.go @@ -409,8 +409,8 @@ var _ = Describe("LifeCycleHandler Run", func() { readyCond, ok := conditions.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) Expect(ok).To(BeTrue()) - Expect(readyCond.Reason).To(Equal(vdcondition.StorageClassCSIDriverMismatch.String())) - Expect(readyCond.Message).To(Equal(`Virtual disk storage class "vd-sc" csi driver does not match virtual image storage class "vi-sc" csi driver`)) + Expect(readyCond.Reason).To(Equal(vdcondition.StorageClassProvisionerMismatch.String())) + Expect(readyCond.Message).To(Equal(`Virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner: source type with different provisioners is not supported yet`)) }) }) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go index be5012056f..584f54f356 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go @@ -147,6 +147,6 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { validator := NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) _, err := validator.ValidateCreate(context.Background(), vd) - Expect(err).To(MatchError(`virtual disk storage class "vd-sc" csi driver does not match virtual image storage class "vi-sc" csi driver`)) + Expect(err).To(MatchError(`virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner: source type with different provisioners is not supported yet`)) }) }) From e2887dd781e0a583089f37998323dbe446aea0a1 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Thu, 30 Apr 2026 14:36:36 +0300 Subject: [PATCH 5/7] rename Signed-off-by: Valeriy Khorunzhin --- images/virtualization-artifact/pkg/common/vd/vd.go | 2 +- .../pkg/controller/vd/internal/life_cycle.go | 2 +- ..._pvc_storage_class_provisioner_compatibility_validator.go} | 4 ++-- ...storage_class_provisioner_compatibility_validator_test.go} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename images/virtualization-artifact/pkg/controller/vd/internal/validator/{vi_pvc_storage_class_validator.go => vi_pvc_storage_class_provisioner_compatibility_validator.go} (97%) rename images/virtualization-artifact/pkg/controller/vd/internal/validator/{vi_pvc_storage_class_validator_test.go => vi_pvc_storage_class_provisioner_compatibility_validator_test.go} (100%) diff --git a/images/virtualization-artifact/pkg/common/vd/vd.go b/images/virtualization-artifact/pkg/common/vd/vd.go index 26a9dc1294..eea442746c 100644 --- a/images/virtualization-artifact/pkg/common/vd/vd.go +++ b/images/virtualization-artifact/pkg/common/vd/vd.go @@ -75,7 +75,7 @@ func StorageClassChanged(vd *v1alpha2.VirtualDisk) bool { return *specSc != "" && statusSc != "" } -func ValidateVirtualImageStorageClassProvisionerMatch(ctx context.Context, vd *v1alpha2.VirtualDisk, client client.Client) error { +func ValidateVirtualImageStorageClassProvisionerCompatibility(ctx context.Context, vd *v1alpha2.VirtualDisk, client client.Client) error { if vd.Spec.DataSource == nil || vd.Spec.DataSource.Type != v1alpha2.DataSourceTypeObjectRef { return nil } diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go index d8241ed882..5c0f0a01ad 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/life_cycle.go @@ -138,7 +138,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vd *v1alpha2.VirtualDisk) return reconcile.Result{}, fmt.Errorf("empty storage class in status") } - err := commonvd.ValidateVirtualImageStorageClassProvisionerMatch(ctx, vd, h.client) + err := commonvd.ValidateVirtualImageStorageClassProvisionerCompatibility(ctx, vd, h.client) if err != nil { cb. Status(metav1.ConditionFalse). diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go similarity index 97% rename from images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go rename to images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go index 5e6497b114..e99bbb43d1 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go @@ -54,7 +54,7 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateCreate(ctx context.Contex vdWithStatusStorageClassName := vd.DeepCopy() vdWithStatusStorageClassName.Status.StorageClassName = scName - return nil, commonvd.ValidateVirtualImageStorageClassProvisionerMatch(ctx, vdWithStatusStorageClassName, v.client) + return nil, commonvd.ValidateVirtualImageStorageClassProvisionerCompatibility(ctx, vdWithStatusStorageClassName, v.client) } func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Context, _, newVD *v1alpha2.VirtualDisk) (admission.Warnings, error) { @@ -63,7 +63,7 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Contex return nil, nil } - return nil, commonvd.ValidateVirtualImageStorageClassProvisionerMatch(ctx, newVD, v.client) + return nil, commonvd.ValidateVirtualImageStorageClassProvisionerCompatibility(ctx, newVD, v.client) } func (v *VirtualImagePVCStorageClassValidator) extractVDStorageClassName(ctx context.Context, vd *v1alpha2.VirtualDisk) (string, error) { diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go similarity index 100% rename from images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_validator_test.go rename to images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go From baa03b58f8589274ed413189608d060bf9a4283d Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 4 May 2026 16:57:20 +0300 Subject: [PATCH 6/7] resolve Signed-off-by: Valeriy Khorunzhin --- ...ass_provisioner_compatibility_validator.go | 7 +- ...rovisioner_compatibility_validator_test.go | 248 ++++++++++++------ 2 files changed, 171 insertions(+), 84 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go index e99bbb43d1..2d1707e1ba 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "reflect" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -57,7 +58,11 @@ func (v *VirtualImagePVCStorageClassValidator) ValidateCreate(ctx context.Contex return nil, commonvd.ValidateVirtualImageStorageClassProvisionerCompatibility(ctx, vdWithStatusStorageClassName, v.client) } -func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Context, _, newVD *v1alpha2.VirtualDisk) (admission.Warnings, error) { +func (v *VirtualImagePVCStorageClassValidator) ValidateUpdate(ctx context.Context, oldVD, newVD *v1alpha2.VirtualDisk) (admission.Warnings, error) { + if reflect.DeepEqual(oldVD.Spec.DataSource, newVD.Spec.DataSource) { + return nil, nil + } + ready, _ := conditions.GetCondition(vdcondition.ReadyType, newVD.Status.Conditions) if source.IsDiskProvisioningFinished(ready) { return nil, nil diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go index 584f54f356..714327f610 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go @@ -24,6 +24,7 @@ import ( storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/deckhouse/virtualization-controller/pkg/common/annotations" @@ -31,122 +32,203 @@ import ( basevc "github.com/deckhouse/virtualization-controller/pkg/controller/service" intsvc "github.com/deckhouse/virtualization-controller/pkg/controller/vd/internal/service" "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" ) var _ = Describe("VirtualImagePVCStorageClassValidator", func() { - It("should use the default storage class when VirtualDisk storage class is not set", func() { + const ( + namespace = "default" + viName = "source-vi" + vdName = "target-vd" + ) + + newScheme := func() *runtime.Scheme { scheme := runtime.NewScheme() Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) Expect(storagev1.AddToScheme(scheme)).To(Succeed()) + return scheme + } + + ptr := func(v string) *string { return &v } - defaultSC := &storagev1.StorageClass{ + newStorageClass := func(name, provisioner string, isDefault bool) *storagev1.StorageClass { + sc := &storagev1.StorageClass{ ObjectMeta: metav1.ObjectMeta{ - Name: "default-sc", - Annotations: map[string]string{ - annotations.AnnDefaultStorageClass: "true", - }, + Name: name, }, + Provisioner: provisioner, } - vi := &v1alpha2.VirtualImage{ + if isDefault { + sc.Annotations = map[string]string{ + annotations.AnnDefaultStorageClass: "true", + } + } + return sc + } + + newVirtualImage := func(scName string) *v1alpha2.VirtualImage { + return &v1alpha2.VirtualImage{ ObjectMeta: metav1.ObjectMeta{ - Name: "source-vi", - Namespace: "default", + Name: viName, + Namespace: namespace, }, Spec: v1alpha2.VirtualImageSpec{ Storage: v1alpha2.StoragePersistentVolumeClaim, }, Status: v1alpha2.VirtualImageStatus{ Phase: v1alpha2.ImageReady, - StorageClassName: defaultSC.Name, + StorageClassName: scName, }, } + } + + newDataSource := func(name string) *v1alpha2.VirtualDiskDataSource { + return &v1alpha2.VirtualDiskDataSource{ + Type: v1alpha2.DataSourceTypeObjectRef, + ObjectRef: &v1alpha2.VirtualDiskObjectRef{ + Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, + Name: name, + }, + } + } + newVD := func(statusSC string, specSC *string, ds *v1alpha2.VirtualDiskDataSource) *v1alpha2.VirtualDisk { vd := &v1alpha2.VirtualDisk{ ObjectMeta: metav1.ObjectMeta{ - Name: "target-vd", - Namespace: "default", + Name: vdName, + Namespace: namespace, }, Spec: v1alpha2.VirtualDiskSpec{ - DataSource: &v1alpha2.VirtualDiskDataSource{ - Type: v1alpha2.DataSourceTypeObjectRef, - ObjectRef: &v1alpha2.VirtualDiskObjectRef{ - Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, - Name: vi.Name, - }, - }, + PersistentVolumeClaim: v1alpha2.VirtualDiskPersistentVolumeClaim{StorageClass: specSC}, + DataSource: ds, }, } + vd.Status.StorageClassName = statusSC + return vd + } - k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(defaultSC, vi).Build() + newValidator := func(settings config.VirtualDiskStorageClassSettings, objs ...client.Object) *VirtualImagePVCStorageClassValidator { + k8sClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(objs...).Build() baseSCService := basevc.NewBaseStorageClassService(k8sClient) - vdSCService := intsvc.NewVirtualDiskStorageClassService(baseSCService, config.VirtualDiskStorageClassSettings{}) - validator := NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) + vdSCService := intsvc.NewVirtualDiskStorageClassService(baseSCService, settings) + return NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) + } - var err error - Expect(func() { - _, err = validator.ValidateCreate(context.Background(), vd) - }).NotTo(Panic()) - Expect(err).NotTo(HaveOccurred()) - }) + type updateCase struct { + oldVD *v1alpha2.VirtualDisk + newVD *v1alpha2.VirtualDisk + } - It("should return a readable mismatch error", func() { - scheme := runtime.NewScheme() - Expect(v1alpha2.AddToScheme(scheme)).To(Succeed()) - Expect(storagev1.AddToScheme(scheme)).To(Succeed()) + DescribeTable("ValidateCreate", func(vd *v1alpha2.VirtualDisk, settings config.VirtualDiskStorageClassSettings, objs []client.Object, expectedErr string) { + validator := newValidator(settings, objs...) + _, err := validator.ValidateCreate(context.Background(), vd) - vi := &v1alpha2.VirtualImage{ - ObjectMeta: metav1.ObjectMeta{ - Name: "source-vi", - Namespace: "default", - }, - Spec: v1alpha2.VirtualImageSpec{ - Storage: v1alpha2.StoragePersistentVolumeClaim, - }, - Status: v1alpha2.VirtualImageStatus{ - Phase: v1alpha2.ImageReady, - StorageClassName: "vi-sc", - }, + if expectedErr == "" { + Expect(err).NotTo(HaveOccurred()) + return } - vdSC := &storagev1.StorageClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "vd-sc", - }, - Provisioner: "first.csi.example.com", - } - viSC := &storagev1.StorageClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "vi-sc", - }, - Provisioner: "second.csi.example.com", + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(expectedErr)) + }, + Entry("uses status storage class first", func() *v1alpha2.VirtualDisk { + return newVD("status-sc", ptr("spec-sc"), newDataSource(viName)) + }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("status-sc", "csi.example.com", false), + newStorageClass("spec-sc", "other.csi.example.com", false), + newStorageClass("vi-sc", "csi.example.com", false), + }, ""), + Entry("uses spec storage class when status is empty", func() *v1alpha2.VirtualDisk { + return newVD("", ptr("spec-sc"), newDataSource(viName)) + }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("spec-sc", "csi.example.com", false), + newStorageClass("default-sc", "other.csi.example.com", true), + newStorageClass("vi-sc", "csi.example.com", false), + }, ""), + Entry("uses module storage class before default class", func() *v1alpha2.VirtualDisk { + return newVD("", nil, newDataSource(viName)) + }(), config.VirtualDiskStorageClassSettings{ + DefaultStorageClassName: "module-sc", + }, []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("module-sc", "csi.example.com", false), + newStorageClass("default-sc", "other.csi.example.com", true), + newStorageClass("vi-sc", "csi.example.com", false), + }, ""), + Entry("uses default storage class as fallback", func() *v1alpha2.VirtualDisk { + return newVD("", nil, newDataSource(viName)) + }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("default-sc", "csi.example.com", true), + newStorageClass("vi-sc", "csi.example.com", false), + }, ""), + Entry("returns clear error when storage class cannot be determined", func() *v1alpha2.VirtualDisk { + return newVD("", nil, nil) + }(), config.VirtualDiskStorageClassSettings{}, nil, `storage class for VirtualDisk "target-vd" cannot be determined`), + Entry("returns readable mismatch error", func() *v1alpha2.VirtualDisk { + return newVD("", ptr("vd-sc"), newDataSource(viName)) + }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("vd-sc", "first.csi.example.com", false), + newStorageClass("vi-sc", "second.csi.example.com", false), + }, `virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner`), + ) + + DescribeTable("ValidateUpdate", func(tc updateCase, objs []client.Object, expectedErr string) { + validator := newValidator(config.VirtualDiskStorageClassSettings{}, objs...) + _, err := validator.ValidateUpdate(context.Background(), tc.oldVD, tc.newVD) + + if expectedErr == "" { + Expect(err).NotTo(HaveOccurred()) + return } - vd := &v1alpha2.VirtualDisk{ - ObjectMeta: metav1.ObjectMeta{ - Name: "target-vd", - Namespace: "default", - }, - Spec: v1alpha2.VirtualDiskSpec{ - PersistentVolumeClaim: v1alpha2.VirtualDiskPersistentVolumeClaim{ - StorageClass: func() *string { - sc := "vd-sc" - return &sc - }(), + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(expectedErr)) + }, + Entry("returns nil when data source didn't change", func() updateCase { + ds := newDataSource(viName) + return updateCase{ + oldVD: newVD("vd-sc", ptr("vd-sc"), ds), + newVD: newVD("vd-sc", ptr("vd-sc"), ds), + } + }(), nil, ""), + Entry("skips validation after provisioning is finished", func() updateCase { + oldVD := newVD("vd-sc", ptr("vd-sc"), nil) + newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource(viName)) + newVD.Status.Conditions = []metav1.Condition{ + { + Type: vdcondition.ReadyType.String(), + Reason: vdcondition.Ready.String(), }, - DataSource: &v1alpha2.VirtualDiskDataSource{ - Type: v1alpha2.DataSourceTypeObjectRef, - ObjectRef: &v1alpha2.VirtualDiskObjectRef{ - Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, - Name: vi.Name, - }, + } + return updateCase{oldVD: oldVD, newVD: newVD} + }(), nil, ""), + Entry("validates and returns mismatch when provisioning is not finished", func() updateCase { + oldVD := newVD("vd-sc", ptr("vd-sc"), nil) + newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource(viName)) + newVD.Status.Conditions = []metav1.Condition{ + { + Type: vdcondition.ReadyType.String(), + Reason: vdcondition.Provisioning.String(), }, - }, - } - - k8sClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vi, vdSC, viSC).Build() - baseSCService := basevc.NewBaseStorageClassService(k8sClient) - vdSCService := intsvc.NewVirtualDiskStorageClassService(baseSCService, config.VirtualDiskStorageClassSettings{}) - validator := NewVirtualImagePVCStorageClassValidator(k8sClient, vdSCService) - - _, err := validator.ValidateCreate(context.Background(), vd) - Expect(err).To(MatchError(`virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner: source type with different provisioners is not supported yet`)) - }) + } + return updateCase{oldVD: oldVD, newVD: newVD} + }(), []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("vd-sc", "first.csi.example.com", false), + newStorageClass("vi-sc", "second.csi.example.com", false), + }, `virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner`), + Entry("validates successfully when provisioners are compatible", func() updateCase { + oldVD := newVD("vd-sc", ptr("vd-sc"), nil) + newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource(viName)) + return updateCase{oldVD: oldVD, newVD: newVD} + }(), []client.Object{ + newVirtualImage("vi-sc"), + newStorageClass("vd-sc", "csi.example.com", false), + newStorageClass("vi-sc", "csi.example.com", false), + }, ""), + ) }) From f77d60035b531e6ab296269c0d83bd8e66cbc828 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 4 May 2026 17:14:52 +0300 Subject: [PATCH 7/7] linter Signed-off-by: Valeriy Khorunzhin --- ...rovisioner_compatibility_validator_test.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go index 714327f610..3eab252af8 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/vi_pvc_storage_class_provisioner_compatibility_validator_test.go @@ -66,7 +66,7 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { return sc } - newVirtualImage := func(scName string) *v1alpha2.VirtualImage { + newVirtualImage := func() *v1alpha2.VirtualImage { return &v1alpha2.VirtualImage{ ObjectMeta: metav1.ObjectMeta{ Name: viName, @@ -77,17 +77,17 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { }, Status: v1alpha2.VirtualImageStatus{ Phase: v1alpha2.ImageReady, - StorageClassName: scName, + StorageClassName: "vi-sc", }, } } - newDataSource := func(name string) *v1alpha2.VirtualDiskDataSource { + newDataSource := func() *v1alpha2.VirtualDiskDataSource { return &v1alpha2.VirtualDiskDataSource{ Type: v1alpha2.DataSourceTypeObjectRef, ObjectRef: &v1alpha2.VirtualDiskObjectRef{ Kind: v1alpha2.VirtualDiskObjectRefKindVirtualImage, - Name: name, + Name: viName, }, } } @@ -132,35 +132,35 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { Expect(err.Error()).To(ContainSubstring(expectedErr)) }, Entry("uses status storage class first", func() *v1alpha2.VirtualDisk { - return newVD("status-sc", ptr("spec-sc"), newDataSource(viName)) + return newVD("status-sc", ptr("spec-sc"), newDataSource()) }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("status-sc", "csi.example.com", false), newStorageClass("spec-sc", "other.csi.example.com", false), newStorageClass("vi-sc", "csi.example.com", false), }, ""), Entry("uses spec storage class when status is empty", func() *v1alpha2.VirtualDisk { - return newVD("", ptr("spec-sc"), newDataSource(viName)) + return newVD("", ptr("spec-sc"), newDataSource()) }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("spec-sc", "csi.example.com", false), newStorageClass("default-sc", "other.csi.example.com", true), newStorageClass("vi-sc", "csi.example.com", false), }, ""), Entry("uses module storage class before default class", func() *v1alpha2.VirtualDisk { - return newVD("", nil, newDataSource(viName)) + return newVD("", nil, newDataSource()) }(), config.VirtualDiskStorageClassSettings{ DefaultStorageClassName: "module-sc", }, []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("module-sc", "csi.example.com", false), newStorageClass("default-sc", "other.csi.example.com", true), newStorageClass("vi-sc", "csi.example.com", false), }, ""), Entry("uses default storage class as fallback", func() *v1alpha2.VirtualDisk { - return newVD("", nil, newDataSource(viName)) + return newVD("", nil, newDataSource()) }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("default-sc", "csi.example.com", true), newStorageClass("vi-sc", "csi.example.com", false), }, ""), @@ -168,9 +168,9 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { return newVD("", nil, nil) }(), config.VirtualDiskStorageClassSettings{}, nil, `storage class for VirtualDisk "target-vd" cannot be determined`), Entry("returns readable mismatch error", func() *v1alpha2.VirtualDisk { - return newVD("", ptr("vd-sc"), newDataSource(viName)) + return newVD("", ptr("vd-sc"), newDataSource()) }(), config.VirtualDiskStorageClassSettings{}, []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("vd-sc", "first.csi.example.com", false), newStorageClass("vi-sc", "second.csi.example.com", false), }, `virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner`), @@ -189,7 +189,7 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { Expect(err.Error()).To(ContainSubstring(expectedErr)) }, Entry("returns nil when data source didn't change", func() updateCase { - ds := newDataSource(viName) + ds := newDataSource() return updateCase{ oldVD: newVD("vd-sc", ptr("vd-sc"), ds), newVD: newVD("vd-sc", ptr("vd-sc"), ds), @@ -197,7 +197,7 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { }(), nil, ""), Entry("skips validation after provisioning is finished", func() updateCase { oldVD := newVD("vd-sc", ptr("vd-sc"), nil) - newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource(viName)) + newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource()) newVD.Status.Conditions = []metav1.Condition{ { Type: vdcondition.ReadyType.String(), @@ -208,7 +208,7 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { }(), nil, ""), Entry("validates and returns mismatch when provisioning is not finished", func() updateCase { oldVD := newVD("vd-sc", ptr("vd-sc"), nil) - newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource(viName)) + newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource()) newVD.Status.Conditions = []metav1.Condition{ { Type: vdcondition.ReadyType.String(), @@ -217,16 +217,16 @@ var _ = Describe("VirtualImagePVCStorageClassValidator", func() { } return updateCase{oldVD: oldVD, newVD: newVD} }(), []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("vd-sc", "first.csi.example.com", false), newStorageClass("vi-sc", "second.csi.example.com", false), }, `virtual disk storage class "vd-sc" provisioner does not match virtual image storage class "vi-sc" provisioner`), Entry("validates successfully when provisioners are compatible", func() updateCase { oldVD := newVD("vd-sc", ptr("vd-sc"), nil) - newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource(viName)) + newVD := newVD("vd-sc", ptr("vd-sc"), newDataSource()) return updateCase{oldVD: oldVD, newVD: newVD} }(), []client.Object{ - newVirtualImage("vi-sc"), + newVirtualImage(), newStorageClass("vd-sc", "csi.example.com", false), newStorageClass("vi-sc", "csi.example.com", false), }, ""),