From dd788c14fea5659291d22372fdaf9dc2b399bc9f Mon Sep 17 00:00:00 2001 From: Dmitry Lopatin Date: Sat, 13 Jun 2026 12:52:41 +0300 Subject: [PATCH 1/2] fix(vm): skip template network sync for main-only VMs Do not run the network template synchronization path for virtual machines that only use the implicit Main network. The network hotplug reconciliation introduced a networksOutOfSync path that materialized the implicit default pod network in the KubeVirt VM template for already running VMs. Older virt-controller versions can observe that [] -> [default] template diff during module rollout and mark the VM with RestartRequired, which DVP then exposes as AwaitingRestartToApplyConfiguration. The same path also caused a misleading ConfigurationApplied=False message about waiting for SDN even though there are no additional SDN-managed networks. Skip the out-of-sync network reconciliation and readiness/update path for Main-only VMs. Additional networks still use the existing SDN readiness flow. Signed-off-by: Dmitry Lopatin --- .../pkg/controller/vm/internal/sync_kvvm.go | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 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 60bbe9bafc..66609ad57b 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go @@ -215,7 +215,7 @@ func (h *SyncKvvmHandler) Handle(ctx context.Context, s state.VirtualMachineStat cbConfApplied. Status(metav1.ConditionFalse). Reason(vmcondition.ReasonConfigurationNotApplied). - Message("Waiting for SDN to configure network interfaces on the pod.") + Message("Waiting for SDN to configure network interfaces.") case kvvmSyncErr != nil: h.recorder.Event(current, corev1.EventTypeWarning, v1alpha2.ReasonErrVmNotSynced, kvvmSyncErr.Error()) cbConfApplied. @@ -758,7 +758,7 @@ func (h *SyncKvvmHandler) applyVMChangesToKVVM(ctx context.Context, s state.Virt return fmt.Errorf("unable to check pod network status: %w", err) } if !ready { - msg := "Waiting for SDN to configure network interfaces on the pod" + msg := "Waiting for SDN to configure network interfaces" log.Info(msg) h.recorder.Event(current, corev1.EventTypeNormal, v1alpha2.ReasonVMChangesApplied, msg) return errWaitForNetworkReady @@ -828,7 +828,22 @@ func (h *SyncKvvmHandler) networksOutOfSync(ctx context.Context, s state.Virtual if kvvm == nil { return false, nil } - filteredVM, err := filterReadyNetworks(ctx, s.Client(), s.VirtualMachine().Current()) + vm := s.VirtualMachine().Current() + kvvmi, err := s.KVVMI(ctx) + if err != nil { + return false, err + } + // For Main-only VMs, the default pod network may be implicit in the KVVM + // template but explicit in the running VMI: KubeVirt defaulting adds it to + // the VMI spec when the instance starts. If we later "fix" the KVVM template + // from [] to [default] while the VM is already Running, KubeVirt treats that + // template diff as a non-live-updatable network change and sets RestartRequired. + // When there are no active additional interfaces, this is only an implicit-vs- + // explicit default-network drift, so do not reconcile it. + if hasOnlyDefaultNetwork(vm) && !hasActiveAdditionalInterfaces(kvvm) && isKVVMIRunning(kvvmi) { + return false, nil + } + filteredVM, err := filterReadyNetworks(ctx, s.Client(), vm) if err != nil { return false, err } @@ -855,7 +870,45 @@ func (h *SyncKvvmHandler) networksOutOfSync(ctx context.Context, s state.Virtual return false, nil } +func isKVVMIRunning(kvvmi *virtv1.VirtualMachineInstance) bool { + return kvvmi != nil && kvvmi.Status.Phase == virtv1.Running +} + +func hasActiveAdditionalInterfaces(kvvm *virtv1.VirtualMachine) bool { + if kvvm == nil || kvvm.Spec.Template == nil { + return false + } + for _, iface := range kvvm.Spec.Template.Spec.Domain.Devices.Interfaces { + if iface.Name == network.NameDefaultInterface || iface.State == virtv1.InterfaceStateAbsent { + continue + } + return true + } + return false +} + func (h *SyncKvvmHandler) applyNetworkReadinessSync(ctx context.Context, s state.VirtualMachineState) error { + vm := s.VirtualMachine().Current() + if hasOnlyDefaultNetwork(vm) { + kvvm, err := s.KVVM(ctx) + if err != nil { + return fmt.Errorf("find the internal virtual machine: %w", err) + } + kvvmi, err := s.KVVMI(ctx) + if err != nil { + return fmt.Errorf("find the internal virtual machine instance: %w", err) + } + // For Main-only VMs, KubeVirt may have already defaulted the pod network into + // the running VMI while the KVVM template still has no explicit interfaces. + // Waiting for SDN and updating the template would materialize [] -> [default] + // after the VM is Running, which KubeVirt can report as RestartRequired. + // If there are no active additional interfaces, there is nothing SDN-managed + // to wait for or sync, so skip the network readiness/update path. + if !hasActiveAdditionalInterfaces(kvvm) && isKVVMIRunning(kvvmi) { + return nil + } + } + desired, err := h.patchPodNetworkAnnotation(ctx, s) if err != nil { return fmt.Errorf("patch pod network annotation: %w", err) From 36b0739b2d0e458639ed6decc38664053f6cac01 Mon Sep 17 00:00:00 2001 From: Dmitry Lopatin Date: Tue, 16 Jun 2026 09:28:46 +0300 Subject: [PATCH 2/2] ++ fix Signed-off-by: Dmitry Lopatin --- .../pkg/controller/vm/internal/sync_kvvm.go | 14 ++++++++------ 1 file changed, 8 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 66609ad57b..82dad67cb8 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/sync_kvvm.go @@ -829,10 +829,6 @@ func (h *SyncKvvmHandler) networksOutOfSync(ctx context.Context, s state.Virtual return false, nil } vm := s.VirtualMachine().Current() - kvvmi, err := s.KVVMI(ctx) - if err != nil { - return false, err - } // For Main-only VMs, the default pod network may be implicit in the KVVM // template but explicit in the running VMI: KubeVirt defaulting adds it to // the VMI spec when the instance starts. If we later "fix" the KVVM template @@ -840,8 +836,14 @@ func (h *SyncKvvmHandler) networksOutOfSync(ctx context.Context, s state.Virtual // template diff as a non-live-updatable network change and sets RestartRequired. // When there are no active additional interfaces, this is only an implicit-vs- // explicit default-network drift, so do not reconcile it. - if hasOnlyDefaultNetwork(vm) && !hasActiveAdditionalInterfaces(kvvm) && isKVVMIRunning(kvvmi) { - return false, nil + if hasOnlyDefaultNetwork(vm) { + kvvmi, err := s.KVVMI(ctx) + if err != nil { + return false, err + } + if !hasActiveAdditionalInterfaces(kvvm) && isKVVMIRunning(kvvmi) { + return false, nil + } } filteredVM, err := filterReadyNetworks(ctx, s.Client(), vm) if err != nil {