mirror of
https://github.com/harvester/vm-import-controller.git
synced 2025-06-03 01:44:51 +00:00
Add option to select network interface model while importing VM to Harvester using the vm-import-controller (#78)
Add the field `DefaultNetworkInterfaceModel` to `VirtualMachineImportSpec` and `NetworkInterfaceModel` to `NetworkMapping`. With this new fields it is possible to customize the interface models of the VM NICs. Related to: https://github.com/harvester/harvester/issues/7999 Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
parent
8d4b8a01d9
commit
d3ad44e039
2
go.mod
2
go.mod
@ -20,6 +20,7 @@ require (
|
|||||||
k8s.io/apiextensions-apiserver v0.31.3
|
k8s.io/apiextensions-apiserver v0.31.3
|
||||||
k8s.io/apimachinery v0.31.3
|
k8s.io/apimachinery v0.31.3
|
||||||
k8s.io/client-go v12.0.0+incompatible
|
k8s.io/client-go v12.0.0+incompatible
|
||||||
|
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
|
||||||
kubevirt.io/api v1.1.0
|
kubevirt.io/api v1.1.0
|
||||||
kubevirt.io/kubevirt v1.1.0
|
kubevirt.io/kubevirt v1.1.0
|
||||||
sigs.k8s.io/cluster-api v1.9.4
|
sigs.k8s.io/cluster-api v1.9.4
|
||||||
@ -110,7 +111,6 @@ require (
|
|||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-aggregator v0.26.4 // indirect
|
k8s.io/kube-aggregator v0.26.4 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
|
||||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
|
||||||
kubevirt.io/client-go v1.1.0 // indirect
|
kubevirt.io/client-go v1.1.0 // indirect
|
||||||
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect
|
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect
|
||||||
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect
|
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/rancher/wrangler/pkg/condition"
|
"github.com/rancher/wrangler/pkg/condition"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
kubevirtv1 "kubevirt.io/api/core/v1"
|
kubevirtv1 "kubevirt.io/api/core/v1"
|
||||||
|
|
||||||
"github.com/harvester/vm-import-controller/pkg/apis/common"
|
"github.com/harvester/vm-import-controller/pkg/apis/common"
|
||||||
@ -30,9 +31,17 @@ type VirtualMachineImportSpec struct {
|
|||||||
// Examples: "vm-1234", "my-VM" or "5649cac7-3871-4bb5-aab6-c72b8c18d0a2"
|
// Examples: "vm-1234", "my-VM" or "5649cac7-3871-4bb5-aab6-c72b8c18d0a2"
|
||||||
VirtualMachineName string `json:"virtualMachineName"`
|
VirtualMachineName string `json:"virtualMachineName"`
|
||||||
|
|
||||||
Folder string `json:"folder,omitempty"`
|
Folder string `json:"folder,omitempty"`
|
||||||
Mapping []NetworkMapping `json:"networkMapping,omitempty"` //If empty new VirtualMachineImport will be mapped to Management Network
|
|
||||||
StorageClass string `json:"storageClass,omitempty"`
|
// If empty new VirtualMachineImport will be mapped to Management Network.
|
||||||
|
Mapping []NetworkMapping `json:"networkMapping,omitempty"`
|
||||||
|
// The default network interface model. This is always used when:
|
||||||
|
// - Auto-detection fails (OpenStack source client does not have auto-detection, therefore this field is used for every network interface).
|
||||||
|
// - No network mapping is provided and a "pod-network" is auto-created.
|
||||||
|
// Defaults to "virtio".
|
||||||
|
DefaultNetworkInterfaceModel *string `json:"defaultNetworkInterfaceModel,omitempty" wrangler:"type=string,options=e1000|e1000e|ne2k_pci|pcnet|rtl8139|virtio"`
|
||||||
|
|
||||||
|
StorageClass string `json:"storageClass,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtualMachineImportStatus tracks the status of the VirtualMachineImport export from migration and import into the Harvester cluster
|
// VirtualMachineImportStatus tracks the status of the VirtualMachineImport export from migration and import into the Harvester cluster
|
||||||
@ -70,6 +79,9 @@ type DiskInfo struct {
|
|||||||
type NetworkMapping struct {
|
type NetworkMapping struct {
|
||||||
SourceNetwork string `json:"sourceNetwork"`
|
SourceNetwork string `json:"sourceNetwork"`
|
||||||
DestinationNetwork string `json:"destinationNetwork"`
|
DestinationNetwork string `json:"destinationNetwork"`
|
||||||
|
// Override the network interface model that is auto-detected (VMware)
|
||||||
|
// or defaulted (OpenStack).
|
||||||
|
NetworkInterfaceModel *string `json:"networkInterfaceModel,omitempty" wrangler:"type=string,options=e1000|e1000e|ne2k_pci|pcnet|rtl8139|virtio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImportStatus string
|
type ImportStatus string
|
||||||
@ -93,3 +105,23 @@ const (
|
|||||||
VirtualMachineExportFailed condition.Cond = "VMExportFailed"
|
VirtualMachineExportFailed condition.Cond = "VMExportFailed"
|
||||||
VirtualMachineMigrationFailed ImportStatus = "VMMigrationFailed"
|
VirtualMachineMigrationFailed ImportStatus = "VMMigrationFailed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The supported network interface models.
|
||||||
|
// This can be: e1000, e1000e, ne2k_pci, pcnet, rtl8139, virtio.
|
||||||
|
// See https://kubevirt.io/user-guide/network/interfaces_and_networks/#interfaces
|
||||||
|
const (
|
||||||
|
NetworkInterfaceModelE1000 = "e1000"
|
||||||
|
NetworkInterfaceModelE1000e = "e1000e"
|
||||||
|
NetworkInterfaceModelNe2kPci = "ne2k_pci"
|
||||||
|
NetworkInterfaceModelPcnet = "pcnet"
|
||||||
|
NetworkInterfaceModelRtl8139 = "rtl8139"
|
||||||
|
NetworkInterfaceModelVirtio = "virtio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (in *VirtualMachineImport) GetDefaultNetworkInterfaceModel() string {
|
||||||
|
return ptr.Deref[string](in.Spec.DefaultNetworkInterfaceModel, NetworkInterfaceModelVirtio)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *NetworkMapping) GetNetworkInterfaceModel() string {
|
||||||
|
return ptr.Deref[string](in.NetworkInterfaceModel, NetworkInterfaceModelVirtio)
|
||||||
|
}
|
||||||
|
83
pkg/source/network.go
Normal file
83
pkg/source/network.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package source
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
kubevirt "kubevirt.io/api/core/v1"
|
||||||
|
|
||||||
|
migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkInfo struct {
|
||||||
|
NetworkName string
|
||||||
|
MAC string
|
||||||
|
MappedNetwork string
|
||||||
|
Model string
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapNetworks(networkInfos []NetworkInfo, networkMappings []migration.NetworkMapping) []NetworkInfo {
|
||||||
|
result := make([]NetworkInfo, 0)
|
||||||
|
|
||||||
|
for _, ni := range networkInfos {
|
||||||
|
for _, nm := range networkMappings {
|
||||||
|
if nm.SourceNetwork == ni.NetworkName {
|
||||||
|
ni.MappedNetwork = nm.DestinationNetwork
|
||||||
|
|
||||||
|
// Override the auto-detected interface model if it is
|
||||||
|
// customized by the user via the `NetworkMapping`.
|
||||||
|
if nm.NetworkInterfaceModel != nil {
|
||||||
|
ni.Model = nm.GetNetworkInterfaceModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, ni)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateNetworkInterfaceConfigs(networkInfos []NetworkInfo, defaultNetworkInterfaceModel string) ([]kubevirt.Network, []kubevirt.Interface) {
|
||||||
|
networks := make([]kubevirt.Network, 0, len(networkInfos))
|
||||||
|
interfaces := make([]kubevirt.Interface, 0, len(networkInfos))
|
||||||
|
|
||||||
|
for i, ni := range networkInfos {
|
||||||
|
networks = append(networks, kubevirt.Network{
|
||||||
|
NetworkSource: kubevirt.NetworkSource{
|
||||||
|
Multus: &kubevirt.MultusNetwork{
|
||||||
|
NetworkName: ni.MappedNetwork,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: fmt.Sprintf("migrated-%d", i),
|
||||||
|
})
|
||||||
|
|
||||||
|
interfaces = append(interfaces, kubevirt.Interface{
|
||||||
|
Name: fmt.Sprintf("migrated-%d", i),
|
||||||
|
MacAddress: ni.MAC,
|
||||||
|
Model: ni.Model,
|
||||||
|
InterfaceBindingMethod: kubevirt.InterfaceBindingMethod{
|
||||||
|
Bridge: &kubevirt.InterfaceBridge{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no network, attach to Pod network. Essential for VM to
|
||||||
|
// be booted up.
|
||||||
|
if len(networks) == 0 {
|
||||||
|
networks = append(networks, kubevirt.Network{
|
||||||
|
Name: "pod-network",
|
||||||
|
NetworkSource: kubevirt.NetworkSource{
|
||||||
|
Pod: &kubevirt.PodNetwork{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
interfaces = append(interfaces, kubevirt.Interface{
|
||||||
|
Name: "pod-network",
|
||||||
|
Model: defaultNetworkInterfaceModel,
|
||||||
|
InterfaceBindingMethod: kubevirt.InterfaceBindingMethod{
|
||||||
|
Masquerade: &kubevirt.InterfaceMasquerade{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return networks, interfaces
|
||||||
|
}
|
@ -435,7 +435,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
return nil, fmt.Errorf("error getting firware settings: %v", err)
|
return nil, fmt.Errorf("error getting firware settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
networkInfos, err := generateNetworkInfo(vmObj.Addresses)
|
networkInfos, err := generateNetworkInfos(vmObj.Addresses, vm.GetDefaultNetworkInterfaceModel())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -488,46 +488,8 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedNetwork := mapNetworkCards(networkInfos, vm.Spec.Mapping)
|
mappedNetwork := source.MapNetworks(networkInfos, vm.Spec.Mapping)
|
||||||
networkConfig := make([]kubevirt.Network, 0, len(mappedNetwork))
|
networkConfig, interfaceConfig := source.GenerateNetworkInterfaceConfigs(mappedNetwork, vm.GetDefaultNetworkInterfaceModel())
|
||||||
for i, v := range mappedNetwork {
|
|
||||||
networkConfig = append(networkConfig, kubevirt.Network{
|
|
||||||
NetworkSource: kubevirt.NetworkSource{
|
|
||||||
Multus: &kubevirt.MultusNetwork{
|
|
||||||
NetworkName: v.MappedNetwork,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: fmt.Sprintf("migrated-%d", i),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaces := make([]kubevirt.Interface, 0, len(mappedNetwork))
|
|
||||||
for i, v := range mappedNetwork {
|
|
||||||
interfaces = append(interfaces, kubevirt.Interface{
|
|
||||||
Name: fmt.Sprintf("migrated-%d", i),
|
|
||||||
MacAddress: v.MAC,
|
|
||||||
Model: "virtio",
|
|
||||||
InterfaceBindingMethod: kubevirt.InterfaceBindingMethod{
|
|
||||||
Bridge: &kubevirt.InterfaceBridge{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// if there is no network, attach to Pod network. Essential for VM to be booted up
|
|
||||||
if len(networkConfig) == 0 {
|
|
||||||
networkConfig = append(networkConfig, kubevirt.Network{
|
|
||||||
Name: "pod-network",
|
|
||||||
NetworkSource: kubevirt.NetworkSource{
|
|
||||||
Pod: &kubevirt.PodNetwork{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
interfaces = append(interfaces, kubevirt.Interface{
|
|
||||||
Name: "pod-network",
|
|
||||||
Model: "virtio",
|
|
||||||
InterfaceBindingMethod: kubevirt.InterfaceBindingMethod{
|
|
||||||
Masquerade: &kubevirt.InterfaceMasquerade{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup BIOS/EFI, SecureBoot and TPM settings.
|
// Setup BIOS/EFI, SecureBoot and TPM settings.
|
||||||
if uefi {
|
if uefi {
|
||||||
@ -535,7 +497,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
}
|
}
|
||||||
|
|
||||||
vmSpec.Template.Spec.Networks = networkConfig
|
vmSpec.Template.Spec.Networks = networkConfig
|
||||||
vmSpec.Template.Spec.Domain.Devices.Interfaces = interfaces
|
vmSpec.Template.Spec.Domain.Devices.Interfaces = interfaceConfig
|
||||||
newVM.Spec = vmSpec
|
newVM.Spec = vmSpec
|
||||||
|
|
||||||
// disk attachment needs query by core controller for storage classes, so will be added by the migration controller
|
// disk attachment needs query by core controller for storage classes, so will be added by the migration controller
|
||||||
@ -663,26 +625,6 @@ func (c *Client) findVM(name string) (*ExtendedServer, error) {
|
|||||||
return &s, err
|
return &s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type networkInfo struct {
|
|
||||||
NetworkName string
|
|
||||||
MAC string
|
|
||||||
MappedNetwork string
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapNetworkCards(networkCards []networkInfo, mapping []migration.NetworkMapping) []networkInfo {
|
|
||||||
var retNetwork []networkInfo
|
|
||||||
for _, nc := range networkCards {
|
|
||||||
for _, m := range mapping {
|
|
||||||
if m.SourceNetwork == nc.NetworkName {
|
|
||||||
nc.MappedNetwork = m.DestinationNetwork
|
|
||||||
retNetwork = append(retNetwork, nc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retNetwork
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ImageFirmwareSettings(instance *servers.Server) (bool, bool, bool, error) {
|
func (c *Client) ImageFirmwareSettings(instance *servers.Server) (bool, bool, bool, error) {
|
||||||
var imageID string
|
var imageID string
|
||||||
var uefiType, tpmEnabled, secureBoot bool
|
var uefiType, tpmEnabled, secureBoot bool
|
||||||
@ -721,9 +663,10 @@ func (c *Client) ImageFirmwareSettings(instance *servers.Server) (bool, bool, bo
|
|||||||
return uefiType, tpmEnabled, secureBoot, nil
|
return uefiType, tpmEnabled, secureBoot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateNetworkInfo(info map[string]interface{}) ([]networkInfo, error) {
|
func generateNetworkInfos(info map[string]interface{}, defaultInterfaceModel string) ([]source.NetworkInfo, error) {
|
||||||
networkInfos := make([]networkInfo, 0)
|
networkInfos := make([]source.NetworkInfo, 0)
|
||||||
uniqueNetworks := make([]networkInfo, 0)
|
uniqueNetworks := make([]source.NetworkInfo, 0)
|
||||||
|
|
||||||
for network, values := range info {
|
for network, values := range info {
|
||||||
valArr, ok := values.([]interface{})
|
valArr, ok := values.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -734,15 +677,19 @@ func generateNetworkInfo(info map[string]interface{}) ([]networkInfo, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("error asserting network array element into map[string]string")
|
return nil, fmt.Errorf("error asserting network array element into map[string]string")
|
||||||
}
|
}
|
||||||
networkInfos = append(networkInfos, networkInfo{
|
networkInfos = append(networkInfos, source.NetworkInfo{
|
||||||
NetworkName: network,
|
NetworkName: network,
|
||||||
MAC: valMap["OS-EXT-IPS-MAC:mac_addr"].(string),
|
MAC: valMap["OS-EXT-IPS-MAC:mac_addr"].(string),
|
||||||
|
// Note, the interface model is not provided via the OpenStack
|
||||||
|
// Nova API, therefore we need to set it ourselves.
|
||||||
|
Model: defaultInterfaceModel,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case of interfaces with ipv6 and ipv4 addresses they are reported twice, so we need to dedup them
|
// in case of interfaces with ipv6 and ipv4 addresses they are reported twice, so we need to dedup them
|
||||||
// based on a mac address
|
// based on a mac address
|
||||||
networksMap := make(map[string]networkInfo)
|
networksMap := make(map[string]source.NetworkInfo)
|
||||||
for _, v := range networkInfos {
|
for _, v := range networkInfos {
|
||||||
networksMap[v.MAC] = v
|
networksMap[v.MAC] = v
|
||||||
}
|
}
|
||||||
@ -750,6 +697,7 @@ func generateNetworkInfo(info map[string]interface{}) ([]networkInfo, error) {
|
|||||||
for _, v := range networksMap {
|
for _, v := range networksMap {
|
||||||
uniqueNetworks = append(uniqueNetworks, v)
|
uniqueNetworks = append(uniqueNetworks, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uniqueNetworks, nil
|
return uniqueNetworks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,8 +126,9 @@ func Test_GenerateVirtualMachine(t *testing.T) {
|
|||||||
assert.NoError(err, "expected no error during GenerateVirtualMachine")
|
assert.NoError(err, "expected no error during GenerateVirtualMachine")
|
||||||
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.CPU, "expected CPU's to not be empty")
|
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.CPU, "expected CPU's to not be empty")
|
||||||
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.Resources.Limits.Memory(), "expected memory limit to not be empty")
|
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.Resources.Limits.Memory(), "expected memory limit to not be empty")
|
||||||
assert.NotEmpty(newVM.Spec.Template.Spec.Networks, "expected to find atleast 1 network as pod network should have been applied")
|
assert.NotEmpty(newVM.Spec.Template.Spec.Networks, "expected to find at least 1 network as pod network should have been applied")
|
||||||
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.Devices.Interfaces, "expected to find atleast 1 interface for pod-network")
|
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.Devices.Interfaces, "expected to find at least 1 interface for pod-network")
|
||||||
|
assert.Equal(newVM.Spec.Template.Spec.Domain.Devices.Interfaces[0].Model, migration.NetworkInterfaceModelVirtio, "expected to have a NIC with virtio model")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_generateNetworkInfo(t *testing.T) {
|
func Test_generateNetworkInfo(t *testing.T) {
|
||||||
@ -137,10 +138,11 @@ func Test_generateNetworkInfo(t *testing.T) {
|
|||||||
err := json.Unmarshal(networkInfoByte, &networkInfoMap)
|
err := json.Unmarshal(networkInfoByte, &networkInfoMap)
|
||||||
assert.NoError(err, "expected no error while unmarshalling network info")
|
assert.NoError(err, "expected no error while unmarshalling network info")
|
||||||
|
|
||||||
vmInterfaceDetails, err := generateNetworkInfo(networkInfoMap)
|
vmInterfaceDetails, err := generateNetworkInfos(networkInfoMap, migration.NetworkInterfaceModelVirtio)
|
||||||
assert.NoError(err, "expected no error while generating network info")
|
assert.NoError(err, "expected no error while generating network info")
|
||||||
assert.Len(vmInterfaceDetails, 2, "expected to find 2 interfaces only")
|
assert.Len(vmInterfaceDetails, 2, "expected to find 2 interfaces only")
|
||||||
|
assert.Equal(vmInterfaceDetails[0].Model, migration.NetworkInterfaceModelVirtio, "expected to have a NIC with virtio model")
|
||||||
|
assert.Equal(vmInterfaceDetails[1].Model, migration.NetworkInterfaceModelVirtio, "expected to have a NIC with virtio model")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ClientOptions(t *testing.T) {
|
func Test_ClientOptions(t *testing.T) {
|
||||||
|
@ -323,8 +323,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
"spec": o,
|
"spec": o,
|
||||||
}, []string{"spec"})).Info("Origin spec of the VM to be imported")
|
}, []string{"spec"})).Info("Origin spec of the VM to be imported")
|
||||||
|
|
||||||
// Need CPU, Socket, Memory, VirtualNIC information to perform the mapping
|
networkInfos := generateNetworkInfos(o.Config.Hardware.Device)
|
||||||
networkInfo := identifyNetworkCards(o.Config.Hardware.Device)
|
|
||||||
|
|
||||||
vmSpec := kubevirt.VirtualMachineSpec{
|
vmSpec := kubevirt.VirtualMachineSpec{
|
||||||
RunStrategy: &[]kubevirt.VirtualMachineRunStrategy{kubevirt.RunStrategyRerunOnFailure}[0],
|
RunStrategy: &[]kubevirt.VirtualMachineRunStrategy{kubevirt.RunStrategyRerunOnFailure}[0],
|
||||||
@ -360,46 +359,8 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedNetwork := mapNetworkCards(networkInfo, vm.Spec.Mapping)
|
mappedNetwork := source.MapNetworks(networkInfos, vm.Spec.Mapping)
|
||||||
networkConfig := make([]kubevirt.Network, 0, len(mappedNetwork))
|
networkConfig, interfaceConfig := source.GenerateNetworkInterfaceConfigs(mappedNetwork, vm.GetDefaultNetworkInterfaceModel())
|
||||||
for i, v := range mappedNetwork {
|
|
||||||
networkConfig = append(networkConfig, kubevirt.Network{
|
|
||||||
NetworkSource: kubevirt.NetworkSource{
|
|
||||||
Multus: &kubevirt.MultusNetwork{
|
|
||||||
NetworkName: v.MappedNetwork,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Name: fmt.Sprintf("migrated-%d", i),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaces := make([]kubevirt.Interface, 0, len(mappedNetwork))
|
|
||||||
for i, v := range mappedNetwork {
|
|
||||||
interfaces = append(interfaces, kubevirt.Interface{
|
|
||||||
Name: fmt.Sprintf("migrated-%d", i),
|
|
||||||
MacAddress: v.MAC,
|
|
||||||
Model: "virtio",
|
|
||||||
InterfaceBindingMethod: kubevirt.InterfaceBindingMethod{
|
|
||||||
Bridge: &kubevirt.InterfaceBridge{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// if there is no network, attach to Pod network. Essential for VM to be booted up
|
|
||||||
if len(networkConfig) == 0 {
|
|
||||||
networkConfig = append(networkConfig, kubevirt.Network{
|
|
||||||
Name: "pod-network",
|
|
||||||
NetworkSource: kubevirt.NetworkSource{
|
|
||||||
Pod: &kubevirt.PodNetwork{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
interfaces = append(interfaces, kubevirt.Interface{
|
|
||||||
Name: "pod-network",
|
|
||||||
Model: "virtio",
|
|
||||||
InterfaceBindingMethod: kubevirt.InterfaceBindingMethod{
|
|
||||||
Masquerade: &kubevirt.InterfaceMasquerade{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup BIOS/EFI, SecureBoot and TPM settings.
|
// Setup BIOS/EFI, SecureBoot and TPM settings.
|
||||||
uefi := strings.EqualFold(o.Config.Firmware, string(types.GuestOsDescriptorFirmwareTypeEfi))
|
uefi := strings.EqualFold(o.Config.Firmware, string(types.GuestOsDescriptorFirmwareTypeEfi))
|
||||||
@ -413,7 +374,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
}
|
}
|
||||||
|
|
||||||
vmSpec.Template.Spec.Networks = networkConfig
|
vmSpec.Template.Spec.Networks = networkConfig
|
||||||
vmSpec.Template.Spec.Domain.Devices.Interfaces = interfaces
|
vmSpec.Template.Spec.Domain.Devices.Interfaces = interfaceConfig
|
||||||
newVM.Spec = vmSpec
|
newVM.Spec = vmSpec
|
||||||
|
|
||||||
// disk attachment needs query by core controller for storage classes, so will be added by the migration controller
|
// disk attachment needs query by core controller for storage classes, so will be added by the migration controller
|
||||||
@ -430,64 +391,57 @@ func (c *Client) findVM(path, name string) (*object.VirtualMachine, error) {
|
|||||||
return f.VirtualMachine(c.ctx, vmPath)
|
return f.VirtualMachine(c.ctx, vmPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
type networkInfo struct {
|
func generateNetworkInfos(devices []types.BaseVirtualDevice) []source.NetworkInfo {
|
||||||
NetworkName string
|
result := make([]source.NetworkInfo, 0, len(devices))
|
||||||
MAC string
|
|
||||||
MappedNetwork string
|
|
||||||
}
|
|
||||||
|
|
||||||
func identifyNetworkCards(devices []types.BaseVirtualDevice) []networkInfo {
|
|
||||||
var resp []networkInfo
|
|
||||||
for _, d := range devices {
|
for _, d := range devices {
|
||||||
switch d := d.(type) {
|
switch d := d.(type) {
|
||||||
case *types.VirtualVmxnet:
|
case *types.VirtualVmxnet:
|
||||||
obj := d
|
obj := d
|
||||||
resp = append(resp, networkInfo{
|
result = append(result, source.NetworkInfo{
|
||||||
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
||||||
MAC: obj.MacAddress,
|
MAC: obj.MacAddress,
|
||||||
|
Model: migration.NetworkInterfaceModelVirtio,
|
||||||
})
|
})
|
||||||
case *types.VirtualE1000e:
|
case *types.VirtualE1000e:
|
||||||
obj := d
|
obj := d
|
||||||
resp = append(resp, networkInfo{
|
result = append(result, source.NetworkInfo{
|
||||||
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
||||||
MAC: obj.MacAddress,
|
MAC: obj.MacAddress,
|
||||||
|
Model: migration.NetworkInterfaceModelE1000e,
|
||||||
})
|
})
|
||||||
case *types.VirtualE1000:
|
case *types.VirtualE1000:
|
||||||
obj := d
|
obj := d
|
||||||
resp = append(resp, networkInfo{
|
result = append(result, source.NetworkInfo{
|
||||||
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
||||||
MAC: obj.MacAddress,
|
MAC: obj.MacAddress,
|
||||||
|
Model: migration.NetworkInterfaceModelE1000,
|
||||||
})
|
})
|
||||||
case *types.VirtualVmxnet3:
|
case *types.VirtualVmxnet3:
|
||||||
obj := d
|
obj := d
|
||||||
resp = append(resp, networkInfo{
|
result = append(result, source.NetworkInfo{
|
||||||
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
||||||
MAC: obj.MacAddress,
|
MAC: obj.MacAddress,
|
||||||
|
Model: migration.NetworkInterfaceModelVirtio,
|
||||||
})
|
})
|
||||||
case *types.VirtualVmxnet2:
|
case *types.VirtualVmxnet2:
|
||||||
obj := d
|
obj := d
|
||||||
resp = append(resp, networkInfo{
|
result = append(result, source.NetworkInfo{
|
||||||
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
||||||
MAC: obj.MacAddress,
|
MAC: obj.MacAddress,
|
||||||
|
Model: migration.NetworkInterfaceModelVirtio,
|
||||||
|
})
|
||||||
|
case *types.VirtualPCNet32:
|
||||||
|
obj := d
|
||||||
|
result = append(result, source.NetworkInfo{
|
||||||
|
NetworkName: obj.DeviceInfo.GetDescription().Summary,
|
||||||
|
MAC: obj.MacAddress,
|
||||||
|
Model: migration.NetworkInterfaceModelPcnet,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp
|
return result
|
||||||
}
|
|
||||||
|
|
||||||
func mapNetworkCards(networkCards []networkInfo, mapping []migration.NetworkMapping) []networkInfo {
|
|
||||||
var retNetwork []networkInfo
|
|
||||||
for _, nc := range networkCards {
|
|
||||||
for _, m := range mapping {
|
|
||||||
if m.SourceNetwork == nc.NetworkName {
|
|
||||||
nc.MappedNetwork = m.DestinationNetwork
|
|
||||||
retNetwork = append(retNetwork, nc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retNetwork
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// adapterType tries to identify the disk bus type from vmware
|
// adapterType tries to identify the disk bus type from vmware
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1"
|
migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1"
|
||||||
"github.com/harvester/vm-import-controller/pkg/server"
|
"github.com/harvester/vm-import-controller/pkg/server"
|
||||||
|
"github.com/harvester/vm-import-controller/pkg/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
var vcsimPort string
|
var vcsimPort string
|
||||||
@ -256,7 +257,7 @@ func Test_GenerateVirtualMachine(t *testing.T) {
|
|||||||
assert.Len(newVM.Spec.Template.Spec.Domain.Devices.Interfaces, 1, "should have found a network map")
|
assert.Len(newVM.Spec.Template.Spec.Domain.Devices.Interfaces, 1, "should have found a network map")
|
||||||
assert.Equal(newVM.Spec.Template.Spec.Domain.Memory.Guest.String(), "32M", "expected VM to have 32M memory")
|
assert.Equal(newVM.Spec.Template.Spec.Domain.Memory.Guest.String(), "32M", "expected VM to have 32M memory")
|
||||||
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.Resources.Limits, "expect to find resource requests to be present")
|
assert.NotEmpty(newVM.Spec.Template.Spec.Domain.Resources.Limits, "expect to find resource requests to be present")
|
||||||
|
assert.Equal(newVM.Spec.Template.Spec.Domain.Devices.Interfaces[0].Model, migration.NetworkInterfaceModelE1000, "expected to have a NIC with e1000 model")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GenerateVirtualMachine_secureboot(t *testing.T) {
|
func Test_GenerateVirtualMachine_secureboot(t *testing.T) {
|
||||||
@ -365,7 +366,7 @@ func Test_identifyNetworkCards(t *testing.T) {
|
|||||||
err = vmObj.Properties(c.ctx, vmObj.Reference(), []string{}, &o)
|
err = vmObj.Properties(c.ctx, vmObj.Reference(), []string{}, &o)
|
||||||
assert.NoError(err, "expected no error looking up vmObj properties")
|
assert.NoError(err, "expected no error looking up vmObj properties")
|
||||||
|
|
||||||
networkInfo := identifyNetworkCards(o.Config.Hardware.Device)
|
networkInfo := generateNetworkInfos(o.Config.Hardware.Device)
|
||||||
assert.Len(networkInfo, 1, "expected to find only 1 item in the networkInfo")
|
assert.Len(networkInfo, 1, "expected to find only 1 item in the networkInfo")
|
||||||
networkMapping := []migration.NetworkMapping{
|
networkMapping := []migration.NetworkMapping{
|
||||||
{
|
{
|
||||||
@ -373,15 +374,17 @@ func Test_identifyNetworkCards(t *testing.T) {
|
|||||||
DestinationNetwork: "harvester1",
|
DestinationNetwork: "harvester1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
SourceNetwork: "DVSwitch: fea97929-4b2d-5972-b146-930c6d0b4014",
|
SourceNetwork: "DVSwitch: fea97929-4b2d-5972-b146-930c6d0b4014",
|
||||||
DestinationNetwork: "pod-network",
|
DestinationNetwork: "pod-network",
|
||||||
|
NetworkInterfaceModel: pointer.String(migration.NetworkInterfaceModelRtl8139),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedInfo := mapNetworkCards(networkInfo, networkMapping)
|
mappedInfo := source.MapNetworks(networkInfo, networkMapping)
|
||||||
assert.Len(mappedInfo, 1, "expected to find only 1 item in the mapped networkinfo")
|
assert.Len(mappedInfo, 1, "expected to find only 1 item in the mapped networkinfo")
|
||||||
|
assert.Equal(mappedInfo[0].Model, "rtl8139", "expected to have a NIC with rtl8139 model")
|
||||||
|
|
||||||
noNetworkMapping := []migration.NetworkMapping{}
|
noNetworkMapping := []migration.NetworkMapping{}
|
||||||
noMappedInfo := mapNetworkCards(networkInfo, noNetworkMapping)
|
noMappedInfo := source.MapNetworks(networkInfo, noNetworkMapping)
|
||||||
assert.Len(noMappedInfo, 0, "expected to find no item in the mapped networkinfo")
|
assert.Len(noMappedInfo, 0, "expected to find no item in the mapped networkinfo")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user