diff --git a/build/components/versions.yml b/build/components/versions.yml index 7e93569e86..ff1e5c907e 100644 --- a/build/components/versions.yml +++ b/build/components/versions.yml @@ -3,7 +3,7 @@ firmware: libvirt: v10.9.0 edk2: stable202411 core: - 3p-kubevirt: v1.3.1-v12n.17 + 3p-kubevirt: v1.3.1-v12n.18 3p-containerized-data-importer: v1.60.3-v12n.12 distribution: 2.8.3 package: diff --git a/images/virtualization-artifact/go.mod b/images/virtualization-artifact/go.mod index 2a65806fc1..7acf9e46f5 100644 --- a/images/virtualization-artifact/go.mod +++ b/images/virtualization-artifact/go.mod @@ -155,7 +155,6 @@ replace ( k8s.io/client-go => k8s.io/client-go v0.33.3 k8s.io/component-base => k8s.io/component-base v0.33.3 k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911 - kubevirt.io/api => github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v1.3.1-v12n.17 ) // CVE Replaces @@ -164,3 +163,6 @@ replace ( golang.org/x/net => golang.org/x/net v0.40.0 // CVE-2025-22870, CVE-2025-22872 golang.org/x/oauth2 => golang.org/x/oauth2 v0.27.0 // CVE-2025-22868 ) + +// Kubevirt API replaces +replace kubevirt.io/api => github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v1.3.1-v12n.18 diff --git a/images/virtualization-artifact/go.sum b/images/virtualization-artifact/go.sum index ec2bb8a255..e1a15aaec0 100644 --- a/images/virtualization-artifact/go.sum +++ b/images/virtualization-artifact/go.sum @@ -45,8 +45,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v1.3.1-v12n.17 h1:IQPK5oGRSONOKPH8TIuDq7vCjbFTj0NEWQzo6ZBD7uY= -github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v1.3.1-v12n.17/go.mod h1:tCn7VAZktEvymk490iPSMPCmKM9UjbbfH2OsFR/IOLU= +github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v1.3.1-v12n.18 h1:9pstD3PiPmby/Chh24ickwUNbAcqbceOPp253/jSr8k= +github.com/deckhouse/3p-kubevirt/staging/src/kubevirt.io/api v1.3.1-v12n.18/go.mod h1:tCn7VAZktEvymk490iPSMPCmKM9UjbbfH2OsFR/IOLU= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250226105106-176cd3afcdd5 h1:PsN1E0oxC/+4zdA977txrqUCuObFL3HAuu5Xnud8m8c= github.com/deckhouse/deckhouse/pkg/log v0.0.0-20250226105106-176cd3afcdd5/go.mod h1:Mk5HRzkc5pIcDIZ2JJ6DPuuqnwhXVkb3you8M8Mg+4w= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go b/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go index d097bad772..69ae4a4836 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/migrating.go @@ -185,7 +185,7 @@ func (h *MigratingHandler) syncMigrating(ctx context.Context, s state.VirtualMac cb.Status(metav1.ConditionTrue).Reason(vmcondition.ReasonMigratingInProgress) case vmopcondition.ReasonOperationFailed.String(): - cb.Reason(vmcondition.ReasonLastMigrationFinishedWithError).Message("") + cb.Reason(vmcondition.ReasonLastMigrationFinishedWithError).Message(completed.Message) case vmopcondition.ReasonNotApplicableForVMPhase.String(): cb.Reason(vmcondition.ReasonLastMigrationFinishedWithError).Message("Migration is not applicable for the current virtual machine phase") diff --git a/images/virtualization-artifact/pkg/controller/vmop/migration/internal/handler/lifecycle.go b/images/virtualization-artifact/pkg/controller/vmop/migration/internal/handler/lifecycle.go index e9c0b2ada9..00584a401b 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/migration/internal/handler/lifecycle.go +++ b/images/virtualization-artifact/pkg/controller/vmop/migration/internal/handler/lifecycle.go @@ -268,6 +268,11 @@ func (h LifecycleHandler) syncOperationComplete(ctx context.Context, vmop *v1alp if mig.Status.MigrationState != nil && mig.Status.MigrationState.FailureReason != "" { msg += ": " + mig.Status.MigrationState.FailureReason } + msgByFailedReason := getMessageByMigrationFailedReason(mig) + if msgByFailedReason != "" { + msg += ": " + msgByFailedReason + } + completedCond. Status(metav1.ConditionFalse). Reason(vmopcondition.ReasonOperationFailed). @@ -292,10 +297,15 @@ func (h LifecycleHandler) syncOperationComplete(ctx context.Context, vmop *v1alp vmop.Status.Phase = v1alpha2.VMOPPhasePending } + msg, err := h.getConditionCompletedMessageByReason(ctx, reason, mig) + if err != nil { + return err + } + completedCond. Status(metav1.ConditionFalse). Reason(reason). - Message("Wait until operation is completed") + Message(msg) conditions.SetCondition(completedCond, &vmop.Status.Conditions) return nil @@ -435,9 +445,11 @@ func (h LifecycleHandler) execute(ctx context.Context, vmop *v1alpha2.VirtualMac // The Operation is successfully executed. // Turn the phase to InProgress and set the send signal condition to true. - msg := fmt.Sprintf("Sent signal %q to VM without errors.", vmop.Spec.Type) - log.Debug(msg) - h.recorder.Event(vmop, corev1.EventTypeNormal, v1alpha2.ReasonVMOPInProgress, msg) + { + msg := fmt.Sprintf("Sent signal %q to VM without errors.", vmop.Spec.Type) + log.Debug(msg) + h.recorder.Event(vmop, corev1.EventTypeNormal, v1alpha2.ReasonVMOPInProgress, msg) + } mig, err := h.migration.GetMigration(ctx, vmop) if mig == nil || err != nil { @@ -450,11 +462,16 @@ func (h LifecycleHandler) execute(ctx context.Context, vmop *v1alpha2.VirtualMac vmop.Status.Phase = v1alpha2.VMOPPhasePending } + msg, err := h.getConditionCompletedMessageByReason(ctx, reason, mig) + if err != nil { + return err + } + conditions.SetCondition( conditions.NewConditionBuilder(vmopcondition.TypeCompleted). Generation(vmop.GetGeneration()). Reason(reason). - Message("Wait for operation to complete"). + Message(msg). Status(metav1.ConditionFalse), &vmop.Status.Conditions) conditions.SetCondition( @@ -498,3 +515,74 @@ var mapMigrationPhaseToReason = map[virtv1.VirtualMachineInstanceMigrationPhase] virtv1.MigrationSucceeded: vmopcondition.ReasonOperationCompleted, virtv1.MigrationFailed: vmopcondition.ReasonOperationFailed, } + +func getMessageByMigrationFailedReason(mig *virtv1.VirtualMachineInstanceMigration) string { + cond, found := conditions.GetKVVMIMCondition(virtv1.VirtualMachineInstanceMigrationFailed, mig.Status.Conditions) + + if cond.Status == corev1.ConditionTrue && found { + switch cond.Reason { + case virtv1.VirtualMachineInstanceMigrationFailedReasonVMIDoesNotExist, virtv1.VirtualMachineInstanceMigrationFailedReasonVMIIsShutdown: + return "VirtualMachine is stopped" + default: + return cond.Message + } + } + + return "" +} + +func (h LifecycleHandler) getTargetPod(ctx context.Context, mig *virtv1.VirtualMachineInstanceMigration) (*corev1.Pod, error) { + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + virtv1.AppLabel: "virt-launcher", + virtv1.MigrationJobLabel: string(mig.UID), + }, + }) + if err != nil { + return nil, err + } + + pods := &corev1.PodList{} + err = h.client.List(ctx, pods, client.InNamespace(mig.Namespace), client.MatchingLabelsSelector{Selector: selector}) + if err != nil { + return nil, err + } + + if len(pods.Items) > 0 { + return &pods.Items[0], nil + } + + return nil, nil +} + +func isPodPendingUnschedulable(pod *corev1.Pod) bool { + if pod == nil { + return false + } + if pod.Status.Phase != corev1.PodPending || pod.DeletionTimestamp != nil { + return false + } + + for _, condition := range pod.Status.Conditions { + if condition.Type == corev1.PodScheduled && + condition.Status == corev1.ConditionFalse && + condition.Reason == corev1.PodReasonUnschedulable { + return true + } + } + return false +} + +func (h LifecycleHandler) getConditionCompletedMessageByReason(ctx context.Context, reason vmopcondition.ReasonCompleted, mig *virtv1.VirtualMachineInstanceMigration) (string, error) { + msg := "Wait until operation is completed" + if reason == vmopcondition.ReasonMigrationPending || reason == vmopcondition.ReasonMigrationPrepareTarget { + pod, err := h.getTargetPod(ctx, mig) + if err != nil { + return "", err + } + if isPodPendingUnschedulable(pod) { + msg += fmt.Sprintf(" (target pod is unschedulable: %s/%s)", pod.Namespace, pod.Name) + } + } + return msg + ".", nil +}