Skip to content

Commit

Permalink
Allow migration of migratable binding plugins
Browse files Browse the repository at this point in the history
Allow VM migration in case migratable binding plugin is used.

Signed-off-by: Alona Paz <[email protected]>
  • Loading branch information
AlonaKaplan committed Feb 4, 2024
1 parent dc14fae commit 6fd6313
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 39 deletions.
1 change: 1 addition & 0 deletions pkg/network/vmispec/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ go_test(
":go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//staging/src/kubevirt.io/client-go/testutils:go_default_library",
"//tests/libvmi:go_default_library",
"//vendor/github.com/onsi/ginkgo/v2:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
],
Expand Down
32 changes: 32 additions & 0 deletions pkg/network/vmispec/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
package vmispec

import (
"fmt"

v1 "kubevirt.io/api/core/v1"
)

Expand Down Expand Up @@ -52,6 +54,36 @@ func FilterInterfacesSpec(ifaces []v1.Interface, predicate func(i v1.Interface)
return filteredIfaces
}

func VerifyVMIMigratable(vmi *v1.VirtualMachineInstance, bindingPlugins map[string]v1.InterfaceBindingPlugin) error {
ifaces := vmi.Spec.Domain.Devices.Interfaces
if len(ifaces) == 0 {
return nil
}

_, allowPodBridgeNetworkLiveMigration := vmi.Annotations[v1.AllowPodBridgeNetworkLiveMigrationAnnotation]
if allowPodBridgeNetworkLiveMigration && IsPodNetworkWithBridgeBindingInterface(vmi.Spec.Networks, ifaces) {
return nil
}
if IsPodNetworkWithMasqueradeBindingInterface(vmi.Spec.Networks, ifaces) || IsPodNetworkWithMigratableBindingPlugin(vmi.Spec.Networks, ifaces, bindingPlugins) {
return nil
}

return fmt.Errorf("cannot migrate VMI which does not use masquerade, bridge with %s VM annotation or a migratable plugin to connect to the pod network", v1.AllowPodBridgeNetworkLiveMigrationAnnotation)

}

func IsPodNetworkWithMigratableBindingPlugin(networks []v1.Network, ifaces []v1.Interface, bindingPlugins map[string]v1.InterfaceBindingPlugin) bool {
if podNetwork := LookupPodNetwork(networks); podNetwork != nil {
if podInterface := LookupInterfaceByName(ifaces, podNetwork.Name); podInterface != nil {
if podInterface.Binding != nil {
binding, exist := bindingPlugins[podInterface.Binding.Name]
return exist && binding.Migration != nil
}
}
}
return false
}

func IsPodNetworkWithMasqueradeBindingInterface(networks []v1.Network, ifaces []v1.Interface) bool {
if podNetwork := LookupPodNetwork(networks); podNetwork != nil {
if podInterface := LookupInterfaceByName(ifaces, podNetwork.Name); podInterface != nil {
Expand Down
110 changes: 109 additions & 1 deletion pkg/network/vmispec/interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ package vmispec_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

v1 "kubevirt.io/api/core/v1"

netvmispec "kubevirt.io/kubevirt/pkg/network/vmispec"
"kubevirt.io/kubevirt/tests/libvmi"
)

var _ = Describe("VMI network spec", func() {
Expand Down Expand Up @@ -126,6 +126,107 @@ var _ = Describe("VMI network spec", func() {
Entry("mid", vmiStatusInterfaces(iface1, netName, iface2)),
)
})

Context("migratable", func() {
const (
migratablePlugin = "mig"
nonMigratablePlugin = "non_mig"
podNet0 = "default"
)

bindingPlugins := map[string]v1.InterfaceBindingPlugin{
migratablePlugin: {Migration: &v1.InterfaceBindingMigration{}},
nonMigratablePlugin: {},
}

Context("pod network with migratable binding plugin", func() {
It("returns false when there is no pod network", func() {
const nonPodNet = "nonPodNet"
networks := []v1.Network{
{Name: nonPodNet, NetworkSource: v1.NetworkSource{Multus: &v1.MultusNetwork{}}},
}
ifaces := []v1.Interface{interfaceWithBridgeBinding(nonPodNet)}
Expect(netvmispec.IsPodNetworkWithMigratableBindingPlugin(networks, ifaces, bindingPlugins)).To(BeFalse())
})
It("returns false when the binding is not a plugin", func() {
networks := []v1.Network{podNetwork(podNet0)}
ifaces := []v1.Interface{interfaceWithBridgeBinding(podNet0)}
Expect(netvmispec.IsPodNetworkWithMigratableBindingPlugin(networks, ifaces, bindingPlugins)).To(BeFalse())
})

It("returns false when the plugin is not migratable", func() {
networks := []v1.Network{podNetwork(podNet0)}
ifaces := []v1.Interface{interfaceWithBindingPlugin(podNet0, nonMigratablePlugin)}
Expect(netvmispec.IsPodNetworkWithMigratableBindingPlugin(networks, ifaces, bindingPlugins)).To(BeFalse())
})

It("returns false when non pod network is migratable", func() {
const nonPodNetName = "nonPod"
nonPodNetwork := v1.Network{
Name: nonPodNetName,
NetworkSource: v1.NetworkSource{Multus: &v1.MultusNetwork{}},
}
networks := []v1.Network{nonPodNetwork}
ifaces := []v1.Interface{interfaceWithBindingPlugin(nonPodNetName, migratablePlugin)}
Expect(netvmispec.IsPodNetworkWithMigratableBindingPlugin(networks, ifaces, bindingPlugins)).To(BeFalse())
})

It("returns true when the plugin is migratable", func() {
networks := []v1.Network{podNetwork(podNet0)}
ifaces := []v1.Interface{interfaceWithBindingPlugin(podNet0, migratablePlugin)}
Expect(netvmispec.IsPodNetworkWithMigratableBindingPlugin(networks, ifaces, bindingPlugins)).To(BeTrue())
})
It("returns true when the secondary interface has is migratable", func() {
networks := []v1.Network{podNetwork(podNet0)}
ifaces := []v1.Interface{interfaceWithBindingPlugin(podNet0, migratablePlugin)}
Expect(netvmispec.IsPodNetworkWithMigratableBindingPlugin(networks, ifaces, bindingPlugins)).To(BeTrue())
})
})

Context("vmi", func() {
It("shouldn't allow migration if the VMI use non-migratable binding plugin to connect to the pod network", func() {
network := podNetwork(podNet0)
vmi := libvmi.New(
libvmi.WithInterface(interfaceWithBindingPlugin(podNet0, nonMigratablePlugin)),
libvmi.WithNetwork(&network),
)
Expect(netvmispec.VerifyVMIMigratable(vmi, bindingPlugins)).ToNot(Succeed())
})
It("shouldn't allow migration if the VMI uses bridge binding to connect to the pod network", func() {
network := podNetwork(podNet0)
vmi := libvmi.New(
libvmi.WithInterface(*v1.DefaultBridgeNetworkInterface()),
libvmi.WithNetwork(&network),
)
Expect(netvmispec.VerifyVMIMigratable(vmi, bindingPlugins)).ToNot(Succeed())
})
It("should allow migration if the VMI uses masquerade to connect to the pod network", func() {
network := podNetwork(podNet0)
vmi := libvmi.New(
libvmi.WithInterface(*v1.DefaultMasqueradeNetworkInterface()),
libvmi.WithNetwork(&network),
)
Expect(netvmispec.VerifyVMIMigratable(vmi, bindingPlugins)).To(Succeed())
})
It("should allow migration if the VMI use bridge to connect to the pod network and has AllowLiveMigrationBridgePodNetwork annotation", func() {
network := podNetwork(podNet0)
vmi := libvmi.New(
libvmi.WithInterface(*v1.DefaultBridgeNetworkInterface()),
libvmi.WithNetwork(&network),
libvmi.WithAnnotation(v1.AllowPodBridgeNetworkLiveMigrationAnnotation, ""),
)
Expect(netvmispec.VerifyVMIMigratable(vmi, bindingPlugins)).To(Succeed())
})
It("should allow migration if the VMI use migratable binding plugin to connect to the pod network", func() {
network := podNetwork(podNet0)
vmi := libvmi.New(
libvmi.WithInterface(interfaceWithBindingPlugin(podNet0, migratablePlugin)),
libvmi.WithNetwork(&network),
)
Expect(netvmispec.VerifyVMIMigratable(vmi, bindingPlugins)).To(Succeed())
})
})
})
})

func podNetwork(name string) v1.Network {
Expand All @@ -149,6 +250,13 @@ func interfaceWithMasqueradeBinding(name string) v1.Interface {
}
}

func interfaceWithBindingPlugin(name, pluginName string) v1.Interface {
return v1.Interface{
Name: name,
Binding: &v1.PluginBinding{Name: pluginName},
}
}

func vmiStatusInterfaces(names ...string) []v1.VirtualMachineInstanceNetworkInterface {
var statusInterfaces []v1.VirtualMachineInstanceNetworkInterface
for _, name := range names {
Expand Down
15 changes: 1 addition & 14 deletions pkg/virt-handler/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -2452,20 +2452,7 @@ func (d *VirtualMachineController) isPreMigrationTarget(vmi *v1.VirtualMachineIn
}

func (d *VirtualMachineController) checkNetworkInterfacesForMigration(vmi *v1.VirtualMachineInstance) error {
ifaces := vmi.Spec.Domain.Devices.Interfaces
if len(ifaces) == 0 {
return nil
}

_, allowPodBridgeNetworkLiveMigration := vmi.Annotations[v1.AllowPodBridgeNetworkLiveMigrationAnnotation]
if allowPodBridgeNetworkLiveMigration && netvmispec.IsPodNetworkWithBridgeBindingInterface(vmi.Spec.Networks, ifaces) {
return nil
}
if netvmispec.IsPodNetworkWithMasqueradeBindingInterface(vmi.Spec.Networks, ifaces) {
return nil
}

return fmt.Errorf("cannot migrate VMI which does not use masquerade to connect to the pod network or bridge with %s VM annotation", v1.AllowPodBridgeNetworkLiveMigrationAnnotation)
return netvmispec.VerifyVMIMigratable(vmi, d.clusterConfig.GetNetworkBindings())
}

func (d *VirtualMachineController) checkVolumesForMigration(vmi *v1.VirtualMachineInstance) (blockMigrate bool, err error) {
Expand Down
33 changes: 9 additions & 24 deletions pkg/virt-handler/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ var _ = Describe("VirtualMachineInstance", func() {

var certDir string

const migratableNetworkBindingPlugin = "mig_plug"

getCgroupManager = func(_ *v1.VirtualMachineInstance) (cgroup.Manager, error) {
return mockCgroupManager, nil
}
Expand Down Expand Up @@ -186,7 +188,11 @@ var _ = Describe("VirtualMachineInstance", func() {
vmiInterface = kubecli.NewMockVirtualMachineInstanceInterface(ctrl)
virtClient.EXPECT().VirtualMachineInstance(metav1.NamespaceDefault).Return(vmiInterface).AnyTimes()
mockWatchdog = &MockWatchdog{shareDir}
config, _, _ := testutils.NewFakeClusterConfigUsingKVConfig(&v1.KubeVirtConfiguration{})
kv := &v1.KubeVirtConfiguration{}
kv.NetworkConfiguration = &v1.NetworkConfiguration{Binding: map[string]v1.InterfaceBindingPlugin{
migratableNetworkBindingPlugin: {Migration: &v1.InterfaceBindingMigration{}},
}}
config, _, _ := testutils.NewFakeClusterConfigUsingKVConfig(kv)

Expect(os.MkdirAll(filepath.Join(vmiShareDir, "dev"), 0755)).To(Succeed())
f, err := os.OpenFile(filepath.Join(vmiShareDir, "dev", "kvm"), os.O_CREATE, 0755)
Expand Down Expand Up @@ -2687,7 +2693,7 @@ var _ = Describe("VirtualMachineInstance", func() {
Expect(condition.Reason).To(Equal(v1.VirtualMachineInstanceReasonVirtIOFSNotMigratable))
})

It("should not be allowed to live-migrate if the VMI does not use masquerade to connect to the pod network", func() {
It("should not be allowed to live-migrate if the VMI has non-migratable interface", func() {
vmi := api2.NewMinimalVMI("testvmi")

strategy := v1.EvictionStrategyLiveMigrate
Expand All @@ -2699,28 +2705,7 @@ var _ = Describe("VirtualMachineInstance", func() {
conditionManager := virtcontroller.NewVirtualMachineInstanceConditionManager()
controller.updateLiveMigrationConditions(vmi, conditionManager)

testutils.ExpectEvent(recorder, fmt.Sprintf("cannot migrate VMI which does not use masquerade to connect to the pod network or bridge with %s VM annotation", v1.AllowPodBridgeNetworkLiveMigrationAnnotation))
})
Context("with AllowLiveMigrationBridgePodNetwork annotation", func() {
It("should allow to live-migrate if the VMI use bridge to connect to the pod network", func() {
vmi := api2.NewMinimalVMI("testvmi")

vmi.Annotations = map[string]string{v1.AllowPodBridgeNetworkLiveMigrationAnnotation: ""}

strategy := v1.EvictionStrategyLiveMigrate
vmi.Spec.EvictionStrategy = &strategy

vmi.Spec.Domain.Devices.Interfaces = []v1.Interface{*v1.DefaultBridgeNetworkInterface()}
vmi.Spec.Networks = []v1.Network{*v1.DefaultPodNetwork()}

conditionManager := virtcontroller.NewVirtualMachineInstanceConditionManager()
controller.updateLiveMigrationConditions(vmi, conditionManager)
Expect(vmi.Status.Conditions).To(ContainElement(v1.VirtualMachineInstanceCondition{
Type: v1.VirtualMachineInstanceIsMigratable,
Status: k8sv1.ConditionTrue,
}))
Expect(vmi.Status.MigrationMethod).To(Equal(v1.LiveMigration))
})
testutils.ExpectEvent(recorder, fmt.Sprintf("cannot migrate VMI which does not use masquerade, bridge with %s VM annotation or a migratable plugin to connect to the pod network", v1.AllowPodBridgeNetworkLiveMigrationAnnotation))
})

Context("check that migration is not supported when using Host Devices", func() {
Expand Down

0 comments on commit 6fd6313

Please sign in to comment.