From 4aa2a4b50851a132b988ef530fcede73b438581a Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Tue, 3 Feb 2026 21:18:00 +0300 Subject: [PATCH 1/3] wip Signed-off-by: Daniil Loktev --- .../pkg/controller/vmop/vmop_webhook.go | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go b/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go index 76116cfbb8..7f6964fd1d 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go @@ -18,12 +18,18 @@ package vmop import ( "context" + "fmt" + "slices" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/deckhouse/deckhouse/pkg/log" + commonvmop "github.com/deckhouse/virtualization-controller/pkg/common/vmop" "github.com/deckhouse/virtualization-controller/pkg/controller/validator" + "github.com/deckhouse/virtualization-controller/pkg/version" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -31,7 +37,10 @@ func NewValidator(c client.Client, log *log.Logger) admission.CustomValidator { return validator.NewValidator[*v1alpha2.VirtualMachineOperation](log. With("controller", "vmop-controller"). With("webhook", "validation"), - ).WithCreateValidators(&deprecateMigrateValidator{}) + ).WithCreateValidators( + &deprecateMigrateValidator{}, + &localStorageMigrationValidator{client: c}, + ) } type deprecateMigrateValidator struct{} @@ -44,3 +53,51 @@ func (v *deprecateMigrateValidator) ValidateCreate(_ context.Context, vmop *v1al return admission.Warnings{}, nil } + +type localStorageMigrationValidator struct { + client client.Client +} + +func (v *localStorageMigrationValidator) ValidateCreate(ctx context.Context, vmop *v1alpha2.VirtualMachineOperation) (admission.Warnings, error) { + if version.GetEdition() != version.EditionCE { + return nil, nil + } + + if !commonvmop.IsMigration(vmop) { + return nil, nil + } + + vm := &v1alpha2.VirtualMachine{} + err := v.client.Get(ctx, types.NamespacedName{Name: vmop.Spec.VirtualMachine, Namespace: vmop.Namespace}, vm) + if err != nil { + return nil, fmt.Errorf("failed to get VirtualMachine: %w", err) + } + + for _, bda := range vm.Status.BlockDeviceRefs { + if bda.Kind != v1alpha2.DiskDevice { + continue + } + + vd := &v1alpha2.VirtualDisk{} + err := v.client.Get(ctx, types.NamespacedName{Name: bda.Name, Namespace: vmop.Namespace}, vd) + if err != nil { + return nil, fmt.Errorf("failed to get VirtualDisk %s: %w", bda.Name, err) + } + + if vd.Status.Target.PersistentVolumeClaim == "" { + continue + } + + pvc := &corev1.PersistentVolumeClaim{} + err = v.client.Get(ctx, types.NamespacedName{Name: vd.Status.Target.PersistentVolumeClaim, Namespace: vmop.Namespace}, pvc) + if err != nil { + return nil, fmt.Errorf("failed to get PersistentVolumeClaim %s: %w", vd.Status.Target.PersistentVolumeClaim, err) + } + + if slices.Contains(pvc.Spec.AccessModes, corev1.ReadWriteOnce) { + return nil, fmt.Errorf("migration of VirtualMachines with local (RWO) storage is only available in the Enterprise Edition (EE)") + } + } + + return nil, nil +} From 0445170da061699719703665cfa119fc7627272a Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Tue, 3 Feb 2026 21:23:57 +0300 Subject: [PATCH 2/3] wip Signed-off-by: Daniil Loktev --- .../virtualization-artifact/pkg/controller/vmop/vmop_webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go b/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go index 7f6964fd1d..518a6cd775 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go @@ -94,7 +94,7 @@ func (v *localStorageMigrationValidator) ValidateCreate(ctx context.Context, vmo return nil, fmt.Errorf("failed to get PersistentVolumeClaim %s: %w", vd.Status.Target.PersistentVolumeClaim, err) } - if slices.Contains(pvc.Spec.AccessModes, corev1.ReadWriteOnce) { + if !slices.Contains(pvc.Spec.AccessModes, corev1.ReadWriteMany) { return nil, fmt.Errorf("migration of VirtualMachines with local (RWO) storage is only available in the Enterprise Edition (EE)") } } From 40ac139f080964a9d81883e0e847096ef6f3466f Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Thu, 19 Feb 2026 11:49:49 +0300 Subject: [PATCH 3/3] fix validation message Signed-off-by: Daniil Loktev --- .../virtualization-artifact/pkg/controller/vmop/vmop_webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go b/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go index 2110881e7d..7889c376ae 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vmop/vmop_webhook.go @@ -118,7 +118,7 @@ func (v *localStorageMigrationValidator) ValidateCreate(ctx context.Context, vmo } if !slices.Contains(pvc.Spec.AccessModes, corev1.ReadWriteMany) { - return nil, fmt.Errorf("migration of VirtualMachines with local (RWO) storage is only available in the Enterprise Edition (EE)") + return nil, fmt.Errorf("migration of virtual machines with local (RWO) virtual disks is available only in the Enterprise Edition (EE)") } }