From 43c4dbca922f03158aa77de02164ebca4f3759ac Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Tue, 30 Sep 2025 17:22:35 +0300 Subject: [PATCH 1/3] fix(vm): recreate unschedulable intvirtvmi's pod This workaround is required due to a bug in the KVVM workflow. When a KVVM is created with a non-existent nodeSelector and cannot be scheduled, it remains unschedulable even if the nodeSelector is changed or removed. Signed-off-by: Roman Sysoev --- .../pkg/controller/vm/internal/sync_kvvm.go | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go index 1615eea430..8943f4d0bd 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go @@ -279,6 +279,26 @@ func (h *SyncKvvmHandler) syncKVVM(ctx context.Context, s state.VirtualMachineSt } switch { + // This workaround is required due to a bug in the KVVM workflow. + // When a KVVM is created with a non-existent nodeSelector and cannot be scheduled, + // it remains unschedulable even if the nodeSelector is changed or removed. + case h.isVMUnschedulable(s.VirtualMachine().Current(), kvvm): + nodeSelectorChanged, err := h.isNodeSelectorChanged(ctx, s) + if err != nil { + return false, fmt.Errorf("failed to detect changes in internal virtual machine's nodeSelector: %w", err) + } + if nodeSelectorChanged { + err := h.updateKVVM(ctx, s) + if err != nil { + return false, fmt.Errorf("failed to update internal virtual machine: %w", err) + } + + err = object.DeleteObject(ctx, h.client, pod) + if err != nil { + return false, fmt.Errorf("failed to delete the internal virtual machine instance's pod: %w", err) + } + } + return true, nil case h.isVMStopped(s.VirtualMachine().Current(), kvvm, pod): // KVVM must be updated when the VM is stopped because all its components, // like VirtualDisk and other resources, @@ -659,3 +679,28 @@ func (h *SyncKvvmHandler) updateKVVMLastAppliedSpec( return nil } + +func (h *SyncKvvmHandler) isVMUnschedulable( + vm *virtv2.VirtualMachine, + kvvm *virtv1.VirtualMachine, +) bool { + if vm.Status.Phase == virtv2.MachinePending && kvvm.Status.PrintableStatus == virtv1.VirtualMachineStatusUnschedulable { + return true + } + + return false +} + +func (h *SyncKvvmHandler) isNodeSelectorChanged(ctx context.Context, s state.VirtualMachineState) (bool, error) { + currentKvvm, err := s.KVVM(ctx) + if err != nil { + return false, err + } + + newKvvm, err := MakeKVVMFromVMSpec(ctx, s) + if err != nil { + return false, err + } + + return !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.NodeSelector, &newKvvm.Spec.Template.Spec.NodeSelector), nil +} From f4d53863b44fcf42f266c56d55478fd5e32018d2 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Wed, 1 Oct 2025 10:33:24 +0300 Subject: [PATCH 2/3] fix(vm): check also affinity and toleration Signed-off-by: Roman Sysoev --- .../pkg/controller/vm/internal/sync_kvvm.go | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go index 8943f4d0bd..c7732543ee 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go @@ -280,14 +280,14 @@ func (h *SyncKvvmHandler) syncKVVM(ctx context.Context, s state.VirtualMachineSt switch { // This workaround is required due to a bug in the KVVM workflow. - // When a KVVM is created with a non-existent nodeSelector and cannot be scheduled, - // it remains unschedulable even if the nodeSelector is changed or removed. + // When a KVVM is created with conflicting placement rules and cannot be scheduled, + // it remains unschedulable even if these rules are changed or removed. case h.isVMUnschedulable(s.VirtualMachine().Current(), kvvm): - nodeSelectorChanged, err := h.isNodeSelectorChanged(ctx, s) + placementPolicyChanged, err := h.isPlacementPolicyChanged(ctx, s) if err != nil { return false, fmt.Errorf("failed to detect changes in internal virtual machine's nodeSelector: %w", err) } - if nodeSelectorChanged { + if placementPolicyChanged { err := h.updateKVVM(ctx, s) if err != nil { return false, fmt.Errorf("failed to update internal virtual machine: %w", err) @@ -691,7 +691,8 @@ func (h *SyncKvvmHandler) isVMUnschedulable( return false } -func (h *SyncKvvmHandler) isNodeSelectorChanged(ctx context.Context, s state.VirtualMachineState) (bool, error) { +// isPlacementPolicyChanged returns true if any of the Affinity, NodePlacement, or Toleration rules have changed. +func (h *SyncKvvmHandler) isPlacementPolicyChanged(ctx context.Context, s state.VirtualMachineState) (bool, error) { currentKvvm, err := s.KVVM(ctx) if err != nil { return false, err @@ -702,5 +703,12 @@ func (h *SyncKvvmHandler) isNodeSelectorChanged(ctx context.Context, s state.Vir return false, err } - return !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.NodeSelector, &newKvvm.Spec.Template.Spec.NodeSelector), nil + isChanged := false + if !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.Affinity, &newKvvm.Spec.Template.Spec.Affinity) || + !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.NodeSelector, &newKvvm.Spec.Template.Spec.NodeSelector) || + !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.Tolerations, &newKvvm.Spec.Template.Spec.Tolerations) { + isChanged = true + } + + return isChanged, nil } From 2648246f65c694230f0fca3b0f580c79ec1a7ea8 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 2 Oct 2025 16:27:22 +0300 Subject: [PATCH 3/3] fix(vm): use allChanges when detecting placement policy changes Signed-off-by: Roman Sysoev --- .../pkg/controller/vm/internal/sync_kvvm.go | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go index c7732543ee..8dbf081c5e 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go @@ -282,21 +282,14 @@ func (h *SyncKvvmHandler) syncKVVM(ctx context.Context, s state.VirtualMachineSt // This workaround is required due to a bug in the KVVM workflow. // When a KVVM is created with conflicting placement rules and cannot be scheduled, // it remains unschedulable even if these rules are changed or removed. - case h.isVMUnschedulable(s.VirtualMachine().Current(), kvvm): - placementPolicyChanged, err := h.isPlacementPolicyChanged(ctx, s) + case h.isVMUnschedulable(s.VirtualMachine().Current(), kvvm) && h.isPlacementPolicyChanged(allChanges): + err := h.updateKVVM(ctx, s) if err != nil { - return false, fmt.Errorf("failed to detect changes in internal virtual machine's nodeSelector: %w", err) + return false, fmt.Errorf("failed to update internal virtual machine: %w", err) } - if placementPolicyChanged { - err := h.updateKVVM(ctx, s) - if err != nil { - return false, fmt.Errorf("failed to update internal virtual machine: %w", err) - } - - err = object.DeleteObject(ctx, h.client, pod) - if err != nil { - return false, fmt.Errorf("failed to delete the internal virtual machine instance's pod: %w", err) - } + err = object.DeleteObject(ctx, h.client, pod) + if err != nil { + return false, fmt.Errorf("failed to delete the internal virtual machine instance's pod: %w", err) } return true, nil case h.isVMStopped(s.VirtualMachine().Current(), kvvm, pod): @@ -681,10 +674,10 @@ func (h *SyncKvvmHandler) updateKVVMLastAppliedSpec( } func (h *SyncKvvmHandler) isVMUnschedulable( - vm *virtv2.VirtualMachine, + vm *v1alpha2.VirtualMachine, kvvm *virtv1.VirtualMachine, ) bool { - if vm.Status.Phase == virtv2.MachinePending && kvvm.Status.PrintableStatus == virtv1.VirtualMachineStatusUnschedulable { + if vm.Status.Phase == v1alpha2.MachinePending && kvvm.Status.PrintableStatus == virtv1.VirtualMachineStatusUnschedulable { return true } @@ -692,23 +685,15 @@ func (h *SyncKvvmHandler) isVMUnschedulable( } // isPlacementPolicyChanged returns true if any of the Affinity, NodePlacement, or Toleration rules have changed. -func (h *SyncKvvmHandler) isPlacementPolicyChanged(ctx context.Context, s state.VirtualMachineState) (bool, error) { - currentKvvm, err := s.KVVM(ctx) - if err != nil { - return false, err - } - - newKvvm, err := MakeKVVMFromVMSpec(ctx, s) - if err != nil { - return false, err - } - - isChanged := false - if !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.Affinity, &newKvvm.Spec.Template.Spec.Affinity) || - !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.NodeSelector, &newKvvm.Spec.Template.Spec.NodeSelector) || - !equality.Semantic.DeepEqual(¤tKvvm.Spec.Template.Spec.Tolerations, &newKvvm.Spec.Template.Spec.Tolerations) { - isChanged = true +func (h *SyncKvvmHandler) isPlacementPolicyChanged(allChanges vmchange.SpecChanges) bool { + for _, c := range allChanges.GetAll() { + switch c.Path { + case "affinity", "nodeSelector", "tolerations": + if !equality.Semantic.DeepEqual(c.CurrentValue, c.DesiredValue) { + return true + } + } } - return isChanged, nil + return false }