From f6f5c725ee2fcd3f8a057c1f753aa849a39d4fd3 Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Wed, 3 Dec 2025 17:36:16 +0300 Subject: [PATCH 1/5] wip Signed-off-by: Daniil Loktev --- .../vd/internal/validator/storage_class_validator.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/validator/storage_class_validator.go b/images/virtualization-artifact/pkg/controller/vd/internal/validator/storage_class_validator.go index 70b8346715..fe06ca4745 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/validator/storage_class_validator.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/validator/storage_class_validator.go @@ -125,12 +125,6 @@ func (v *StorageClassValidator) validateTargetStorageClassForVolumeMigration(ctx return fmt.Errorf("storage class cannot be changed unless the VirtualDisk is mounted to a running virtual machine") } - for _, bd := range vm.Status.BlockDeviceRefs { - if bd.Hotplugged { - return fmt.Errorf("for now, changing the storage class is not allowed if the virtual machine has hot-plugged block devices") - } - } - currentStorageClassName := newVD.Status.StorageClassName currentStorageClass, err := v.scService.GetStorageClass(ctx, currentStorageClassName) if err != nil { From 366c1730112c45cecfa9a7853bdfe6ba94c355a2 Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Tue, 9 Dec 2025 16:48:30 +0300 Subject: [PATCH 2/5] wip Signed-off-by: Daniil Loktev --- .../vm/internal/service/migration_volumes.go | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go index bba2336fbc..599d82e58c 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go @@ -118,7 +118,14 @@ func (s MigrationVolumesService) SyncVolumes(ctx context.Context, vmState state. kvvmiSynced := equality.Semantic.DeepEqual(kvvmInClusterCopy.Spec.Template.Spec.Volumes, kvvmiInCluster.Spec.Volumes) if !kvvmiSynced { - // kubevirt does not sync volumes with kvvmi yet + hasStale, err := s.hasStaleVolumePVCReferences(ctx, kvvmiInCluster) + if err != nil { + return reconcile.Result{}, err + } + if hasStale { + log.Info("KVVMI has stale PVC references, patching KVVM to trigger re-sync") + return reconcile.Result{}, s.patchVolumes(ctx, builtKVVM) + } log.Info("kvvmi volumes are not synced yet, skip volume migration.") return reconcile.Result{}, nil } @@ -235,6 +242,25 @@ func (s MigrationVolumesService) shouldRevert(kvvmi *virtv1.VirtualMachineInstan return false } +func (s MigrationVolumesService) hasStaleVolumePVCReferences(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + for _, v := range kvvmi.Spec.Volumes { + if v.PersistentVolumeClaim != nil { + pvc := &corev1.PersistentVolumeClaim{} + err := s.client.Get(ctx, types.NamespacedName{ + Name: v.PersistentVolumeClaim.ClaimName, + Namespace: kvvmi.Namespace, + }, pvc) + if k8serrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, err + } + } + } + return false, nil +} + func (s MigrationVolumesService) patchVolumes(ctx context.Context, kvvm *virtv1.VirtualMachine) error { patchBytes, err := patch.NewJSONPatch( patch.WithReplace("/spec/updateVolumesStrategy", kvvm.Spec.UpdateVolumesStrategy), From 8971b82406b4e0a6bb0f9d0274d297ded6e0f909 Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Tue, 9 Dec 2025 18:20:03 +0300 Subject: [PATCH 3/5] wip Signed-off-by: Daniil Loktev --- .../vm/internal/service/migration_volumes.go | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go index 599d82e58c..badd84f3ea 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go @@ -123,8 +123,13 @@ func (s MigrationVolumesService) SyncVolumes(ctx context.Context, vmState state. return reconcile.Result{}, err } if hasStale { - log.Info("KVVMI has stale PVC references, patching KVVM to trigger re-sync") - return reconcile.Result{}, s.patchVolumes(ctx, builtKVVM) + vdsByName, err := vmState.VirtualDisksByName(ctx) + if err != nil { + return reconcile.Result{}, err + } + revertKVVM := s.buildRevertKVVM(kvvmInCluster, vdsByName) + log.Info("KVVMI has stale PVC references, patching KVVM to revert to source PVCs") + return reconcile.Result{}, s.patchVolumes(ctx, revertKVVM) } log.Info("kvvmi volumes are not synced yet, skip volume migration.") return reconcile.Result{}, nil @@ -261,6 +266,27 @@ func (s MigrationVolumesService) hasStaleVolumePVCReferences(ctx context.Context return false, nil } +func (s MigrationVolumesService) buildRevertKVVM(kvvm *virtv1.VirtualMachine, vdsByName map[string]*v1alpha2.VirtualDisk) *virtv1.VirtualMachine { + revertKVVM := kvvm.DeepCopy() + revertKVVM.Spec.UpdateVolumesStrategy = nil + + for i, v := range revertKVVM.Spec.Template.Spec.Volumes { + if v.PersistentVolumeClaim == nil || !v.PersistentVolumeClaim.Hotpluggable { + continue + } + name, _ := kvbuilder.GetOriginalDiskName(v.Name) + vd, ok := vdsByName[name] + if !ok || vd == nil { + continue + } + if vd.Status.MigrationState.SourcePVC != "" { + revertKVVM.Spec.Template.Spec.Volumes[i].PersistentVolumeClaim.ClaimName = vd.Status.MigrationState.SourcePVC + } + } + + return revertKVVM +} + func (s MigrationVolumesService) patchVolumes(ctx context.Context, kvvm *virtv1.VirtualMachine) error { patchBytes, err := patch.NewJSONPatch( patch.WithReplace("/spec/updateVolumesStrategy", kvvm.Spec.UpdateVolumesStrategy), From 92f5526b0b667b8cd11d9d5fad88906e7c764270 Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Wed, 10 Dec 2025 11:34:05 +0300 Subject: [PATCH 4/5] wip Signed-off-by: Daniil Loktev --- .../pkg/controller/kvbuilder/kvvm_utils.go | 8 ++- .../vm/internal/service/migration_volumes.go | 53 ------------------- 2 files changed, 7 insertions(+), 54 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go index aeca27b35e..eaa1aa0a50 100644 --- a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go +++ b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go @@ -257,8 +257,14 @@ func ApplyVirtualMachineSpec( switch { case device.PVCName != "": + pvcName := device.PVCName + if kind == v1alpha2.DiskDevice { + if vd := vdByName[name]; vd != nil && vd.Status.Target.PersistentVolumeClaim != "" { + pvcName = vd.Status.Target.PersistentVolumeClaim + } + } if err := kvvm.SetDisk(device.VolumeName, SetDiskOptions{ - PersistentVolumeClaim: pointer.GetPointer(device.PVCName), + PersistentVolumeClaim: pointer.GetPointer(pvcName), IsHotplugged: true, Serial: GenerateSerialFromObject(obj), }); err != nil { diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go index badd84f3ea..e49dede5f2 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go @@ -118,19 +118,6 @@ func (s MigrationVolumesService) SyncVolumes(ctx context.Context, vmState state. kvvmiSynced := equality.Semantic.DeepEqual(kvvmInClusterCopy.Spec.Template.Spec.Volumes, kvvmiInCluster.Spec.Volumes) if !kvvmiSynced { - hasStale, err := s.hasStaleVolumePVCReferences(ctx, kvvmiInCluster) - if err != nil { - return reconcile.Result{}, err - } - if hasStale { - vdsByName, err := vmState.VirtualDisksByName(ctx) - if err != nil { - return reconcile.Result{}, err - } - revertKVVM := s.buildRevertKVVM(kvvmInCluster, vdsByName) - log.Info("KVVMI has stale PVC references, patching KVVM to revert to source PVCs") - return reconcile.Result{}, s.patchVolumes(ctx, revertKVVM) - } log.Info("kvvmi volumes are not synced yet, skip volume migration.") return reconcile.Result{}, nil } @@ -247,46 +234,6 @@ func (s MigrationVolumesService) shouldRevert(kvvmi *virtv1.VirtualMachineInstan return false } -func (s MigrationVolumesService) hasStaleVolumePVCReferences(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - for _, v := range kvvmi.Spec.Volumes { - if v.PersistentVolumeClaim != nil { - pvc := &corev1.PersistentVolumeClaim{} - err := s.client.Get(ctx, types.NamespacedName{ - Name: v.PersistentVolumeClaim.ClaimName, - Namespace: kvvmi.Namespace, - }, pvc) - if k8serrors.IsNotFound(err) { - return true, nil - } - if err != nil { - return false, err - } - } - } - return false, nil -} - -func (s MigrationVolumesService) buildRevertKVVM(kvvm *virtv1.VirtualMachine, vdsByName map[string]*v1alpha2.VirtualDisk) *virtv1.VirtualMachine { - revertKVVM := kvvm.DeepCopy() - revertKVVM.Spec.UpdateVolumesStrategy = nil - - for i, v := range revertKVVM.Spec.Template.Spec.Volumes { - if v.PersistentVolumeClaim == nil || !v.PersistentVolumeClaim.Hotpluggable { - continue - } - name, _ := kvbuilder.GetOriginalDiskName(v.Name) - vd, ok := vdsByName[name] - if !ok || vd == nil { - continue - } - if vd.Status.MigrationState.SourcePVC != "" { - revertKVVM.Spec.Template.Spec.Volumes[i].PersistentVolumeClaim.ClaimName = vd.Status.MigrationState.SourcePVC - } - } - - return revertKVVM -} - func (s MigrationVolumesService) patchVolumes(ctx context.Context, kvvm *virtv1.VirtualMachine) error { patchBytes, err := patch.NewJSONPatch( patch.WithReplace("/spec/updateVolumesStrategy", kvvm.Spec.UpdateVolumesStrategy), From 9926a39761093d6c7e5bfcc3716e56d2fd8d1c41 Mon Sep 17 00:00:00 2001 From: Daniil Loktev Date: Wed, 10 Dec 2025 11:57:58 +0300 Subject: [PATCH 5/5] wip Signed-off-by: Daniil Loktev --- .../pkg/controller/vm/internal/service/migration_volumes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go index e49dede5f2..bba2336fbc 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/service/migration_volumes.go @@ -118,6 +118,7 @@ func (s MigrationVolumesService) SyncVolumes(ctx context.Context, vmState state. kvvmiSynced := equality.Semantic.DeepEqual(kvvmInClusterCopy.Spec.Template.Spec.Volumes, kvvmiInCluster.Spec.Volumes) if !kvvmiSynced { + // kubevirt does not sync volumes with kvvmi yet log.Info("kvvmi volumes are not synced yet, skip volume migration.") return reconcile.Result{}, nil }