diff --git a/go-controller/pkg/kubevirt/pod.go b/go-controller/pkg/kubevirt/pod.go index 8a28db7c8d1..a35ce161fbf 100644 --- a/go-controller/pkg/kubevirt/pod.go +++ b/go-controller/pkg/kubevirt/pod.go @@ -21,6 +21,12 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" ) +// IsVMPod will return true if the pod belongs to kubevirt +func IsVMPod(pod *corev1.Pod) bool { + _, ok := pod.Labels[kubevirtv1.VirtualMachineNameLabel] + return ok +} + // IsPodLiveMigratable will return true if the pod belongs // to kubevirt and should use the live migration features func IsPodLiveMigratable(pod *corev1.Pod) bool { @@ -343,3 +349,72 @@ func ZoneContainsPodSubnetOrUntracked(watchFactory *factory.WatchFactory, lsMana // to a node return hostSubnets, !util.IsContainedInAnyCIDR(annotation.IPs[0], hostSubnets...), nil } + +func podDuringMigration(vmPods []*corev1.Pod) bool { + return len(vmPods) > 0 +} + +func isSourcePodAlreadyStale(pod *corev1.Pod, vmPods []*corev1.Pod) bool { + if util.PodCompleted(pod) { + return true + } + for _, vmPod := range vmPods { + if vmPod.CreationTimestamp.After(pod.CreationTimestamp.Time) { + return true + } + } + + return false +} + +func getTargetPod(vmPods []*corev1.Pod) *corev1.Pod { + targetPodNominee := vmPods[0] + for i := 1; i < len(vmPods); i++ { + if vmPods[i].CreationTimestamp.After(targetPodNominee.CreationTimestamp.Time) { + targetPodNominee = vmPods[i] + } + } + return targetPodNominee +} + +func isTargetPodReady(targetPod *corev1.Pod) bool { + if targetPod == nil { + return false + } + + // This annotation only appears on live migration scenarios, and it signals + // that target VM pod is ready to receive traffic, so we can route + // traffic to it. + targetReadyTimestamp := targetPod.Annotations[kubevirtv1.MigrationTargetReadyTimestamp] + + // VM is ready to receive traffic + return targetReadyTimestamp != "" +} + +func filterNotComplete(vmPods []*corev1.Pod) []*corev1.Pod { + var notCompletePods []*corev1.Pod + for _, vmPod := range vmPods { + if !util.PodCompleted(vmPod) { + notCompletePods = append(notCompletePods, vmPod) + } + } + + return notCompletePods +} + +func IsMigrationReadyForTrafficHandoff(client *factory.WatchFactory, pod *corev1.Pod) (bool, error) { + vmPods, err := findVMRelatedPods(client, pod) + if err != nil { + return false, fmt.Errorf("failed finding related pods for pod %s/%s when checking live migration left overs: %v", pod.Namespace, pod.Name, err) + } + vmPods = filterNotComplete(vmPods) + + if !podDuringMigration(vmPods) { + return false, nil + } + + if isSourcePodAlreadyStale(pod, vmPods) && isTargetPodReady(getTargetPod(vmPods)) { + return true, nil + } + return false, nil +} diff --git a/go-controller/pkg/ovn/base_network_controller_pods.go b/go-controller/pkg/ovn/base_network_controller_pods.go index 7de0b6e4438..25764d1a507 100644 --- a/go-controller/pkg/ovn/base_network_controller_pods.go +++ b/go-controller/pkg/ovn/base_network_controller_pods.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" + "k8s.io/utils/ptr" libovsdbclient "github.com/ovn-org/libovsdb/client" "github.com/ovn-org/libovsdb/ovsdb" @@ -588,6 +589,20 @@ func (bnc *BaseNetworkController) addLogicalPortToNetwork(pod *kapi.Pod, nadName } } + lsp.Enabled = ptr.To(true) + if kubevirt.IsVMPod(pod) && + isAllowedForMigration(bnc.IsSecondary(), bnc.IsPrimaryNetwork(), bnc.isLayer2Interconnect()) { + migrationProcessReady, err := kubevirt.IsMigrationReadyForTrafficHandoff(bnc.watchFactory, pod) + if err != nil { + return nil, nil, nil, false, err + } + + if migrationProcessReady || util.PodWantsHostNetwork(pod) { + // Perform Traffic handoff by disabling src pod LSP + lsp.Enabled = ptr.To(false) + } + } + ops, err = libovsdbops.CreateOrUpdateLogicalSwitchPortsOnSwitchOps(bnc.nbClient, nil, ls, lsp) if err != nil { return nil, nil, nil, false, @@ -1162,3 +1177,7 @@ func (bnc *BaseNetworkController) wasPodReleasedBeforeStartup(uid, nad string) b } return bnc.releasedPodsBeforeStartup[nad].Has(uid) } + +func isAllowedForMigration(isSecondary, isPrimaryNetwork, isl2Topology bool) bool { + return isSecondary && !isPrimaryNetwork && isl2Topology +}