Skip to content

[ENHANCEMENT] Improve auto-detection of the disk bus type of the VMware importer #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/apis/migration.harvesterhci.io/v1beta1/virtualmachines.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/rancher/wrangler/pkg/condition"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
kubevirtv1 "kubevirt.io/api/core/v1"

"github.com/harvester/vm-import-controller/pkg/apis/common"
Expand Down Expand Up @@ -33,6 +34,12 @@ type VirtualMachineImportSpec struct {
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"`

// The bus type that is used for imported disks if auto-detection fails.
// Note, the OpenStack source client does not support auto-detection,
// therefore it always makes use of this field.
// Defaults to "virtio".
DefaultDiskBusType *kubevirtv1.DiskBus `json:"defaultDiskBusType,omitempty"`
}

// VirtualMachineImportStatus tracks the status of the VirtualMachineImport export from migration and import into the Harvester cluster
Expand Down Expand Up @@ -93,3 +100,7 @@ const (
VirtualMachineExportFailed condition.Cond = "VMExportFailed"
VirtualMachineMigrationFailed ImportStatus = "VMMigrationFailed"
)

func (in *VirtualMachineImport) GetDefaultDiskBusType() kubevirtv1.DiskBus {
return ptr.Deref[kubevirtv1.DiskBus](in.Spec.DefaultDiskBusType, kubevirtv1.DiskBusVirtio)
}
2 changes: 1 addition & 1 deletion pkg/source/openstack/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
Name: rawImageFileName,
DiskSize: int64(volObj.Size),
DiskLocalPath: server.TempDir(),
BusType: kubevirt.DiskBusVirtio,
BusType: vm.GetDefaultDiskBusType(),
})
}

Expand Down
54 changes: 47 additions & 7 deletions pkg/source/vmware/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
i.Path = vm.Name + "-" + vm.Namespace + "-" + i.Path
}

busType := detectDiskBusType(i.DeviceId, vm.GetDefaultDiskBusType())

logrus.WithFields(logrus.Fields{
"name": vm.Name,
"namespace": vm.Namespace,
Expand All @@ -197,6 +199,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
"deviceId": i.DeviceId,
"path": i.Path,
"busType": busType,
"size": i.Size,
}).Info("Downloading an image")

Expand All @@ -208,7 +211,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
vm.Status.DiskImportStatus = append(vm.Status.DiskImportStatus, migration.DiskInfo{
Name: i.Path,
DiskSize: i.Size,
BusType: adapterType(i.DeviceId),
BusType: busType,
})
} else {
logrus.WithFields(logrus.Fields{
Expand Down Expand Up @@ -490,14 +493,51 @@ func mapNetworkCards(networkCards []networkInfo, mapping []migration.NetworkMapp
return retNetwork
}

// adapterType tries to identify the disk bus type from vmware
// to attempt and set correct bus types in kubevirt
// default is to switch to SATA to ensure device boots
func adapterType(deviceID string) kubevirt.DiskBus {
if strings.Contains(deviceID, "SCSI") {
// detectDiskBusType tries to identify the disk bus type from VMware to attempt and
// set correct bus types in KubeVirt. Defaults to the specified bus type in `def`
// if auto-detection fails.
// Examples:
// .--------------------------------------------------.
// | Bus | Device ID |
// |------|-------------------------------------------|
// | SCSI | /vm-13010/ParaVirtualSCSIController0:0 |
// | SCSI | /vm-13011/VirtualBusLogicController0:0 |
// | SCSI | /vm-13012/VirtualLsiLogicController0:0 |
// | SCSI | /vm-13013/VirtualLsiLogicSASController0:0 |
// | SATA | /vm-13767/VirtualAHCIController0:1 |
// | IDE | /vm-5678/VirtualIDEController1:0 |
// | NVMe | /vm-2468/VirtualNVMEController0:0 |
// | USB | /vm-54321/VirtualUSBController0:0 |
// '--------------------------------------------------'
// References:
// - https://github.com/vmware/pyvmomi/tree/master/pyVmomi/vim/vm/device
// - https://vdc-download.vmware.com/vmwb-repository/dcr-public/d1902b0e-d479-46bf-8ac9-cee0e31e8ec0/07ce8dbd-db48-4261-9b8f-c6d3ad8ba472/vim.vm.device.VirtualSCSIController.html
// - https://libvirt.org/formatdomain.html#controllers
// - https://kubevirt.io/api-reference/v1.1.0/definitions.html#_v1_disktarget
func detectDiskBusType(deviceID string, def kubevirt.DiskBus) kubevirt.DiskBus {
deviceID = strings.ToLower(deviceID)
switch {
case strings.Contains(deviceID, "paravirtualscsi"):
// The pvscsi (Paravirtual SCSI) controller cannot be mapped to
// SCSI bus type because in KubeVirt it is not possible to specify
// the exact model (pvscsi, lsilogic, ...) of the disk via the
// VirtualMachine API. Attempting to map pvscsi to SCSI prevents
// the VM from booting.
// As a workaround, the SATA bus type is utilized in such case.
// Note, VirtIO would be better, but it is not said that the
// required drivers are installed in the VM.
return kubevirt.DiskBusSATA
case strings.Contains(deviceID, "scsi"), strings.Contains(deviceID, "buslogic"), strings.Contains(deviceID, "lsilogic"):
return kubevirt.DiskBusSCSI
case strings.Contains(deviceID, "ahci"), strings.Contains(deviceID, "sata"), strings.Contains(deviceID, "ide"):
return kubevirt.DiskBusSATA
case strings.Contains(deviceID, "nvme"):
return kubevirt.DiskBusVirtio
case strings.Contains(deviceID, "usb"):
return kubevirt.DiskBusUSB
default:
return def
}
return kubevirt.DiskBusSATA
}

// SanitizeVirtualMachineImport is used to sanitize the VirtualMachineImport object.
Expand Down
77 changes: 77 additions & 0 deletions pkg/source/vmware/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
kubevirt "kubevirt.io/api/core/v1"

migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1"
"github.com/harvester/vm-import-controller/pkg/server"
Expand Down Expand Up @@ -385,3 +386,79 @@ func Test_identifyNetworkCards(t *testing.T) {
noMappedInfo := mapNetworkCards(networkInfo, noNetworkMapping)
assert.Len(noMappedInfo, 0, "expected to find no item in the mapped networkinfo")
}

func Test_detectDiskBusType(t *testing.T) {
assert := require.New(t)
testCases := []struct {
desc string
deviceID string
def kubevirt.DiskBus
expected kubevirt.DiskBus
}{
{
desc: "SCSI disk - VMware Paravirtual",
deviceID: "/vm-13010/ParaVirtualSCSIController0:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusSATA,
},
{
desc: "SCSI disk - BusLogic Parallel",
deviceID: "/vm-13011/VirtualBusLogicController0:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusSCSI,
},
{
desc: "SCSI disk - LSI Logic Parallel",
deviceID: "/vm-13012/VirtualLsiLogicController0:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusSCSI,
},
{
desc: "SCSI disk - LSI Logic SAS",
deviceID: "/vm-13013/VirtualLsiLogicSASController0:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusSCSI,
},
{
desc: "NVMe disk",
deviceID: "/vm-2468/VirtualNVMEController0:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusVirtio,
},
{
desc: "USB disk",
deviceID: "/vm-54321/VirtualUSBController0:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusUSB,
},
{
desc: "SATA disk",
deviceID: "/vm-13767/VirtualAHCIController0:1",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusSATA,
},
{
desc: "IDE disk",
deviceID: "/vm-5678/VirtualIDEController1:0",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusSATA,
},
{
desc: "Unknown disk 1",
deviceID: "foo",
def: kubevirt.DiskBusVirtio,
expected: kubevirt.DiskBusVirtio,
},
{
desc: "Unknown disk 2",
deviceID: "bar",
def: kubevirt.DiskBusSATA,
expected: kubevirt.DiskBusSATA,
},
}

for _, tc := range testCases {
busType := detectDiskBusType(tc.deviceID, tc.def)
assert.Equal(tc.expected, busType)
}
}