mirror of
https://github.com/harvester/vm-import-controller.git
synced 2025-06-03 01:44:51 +00:00
Enhance importer to handle several issue (#42)
- Import OpenStack server by UUID - Import OpenStack server with upper case characters in its name The following improvements have been done: - Sanitize the configured `VirtualMachineName` field, e.g. convert upper case to lower case to make it RFC 1123 compliant. - Convert UUID to real name for OpenStack imports - Reduce waiting time to recheck if created VM is running from 5min to 2min - Rename variable `uuid` to `serverUUID` in the OpenStack client code to do not collide with the imported uuid module - Improve error and log messages - Fix typos - Add comments Related to: https://github.com/harvester/harvester/issues/6500 Related to: https://github.com/harvester/harvester/issues/6505 Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
parent
51ae21800b
commit
29629c75f8
@ -21,11 +21,18 @@ type VirtualMachineImport struct {
|
|||||||
|
|
||||||
// VirtualMachineImportSpec is used to create kubevirt VirtualMachines by exporting VM's from migration clusters.
|
// VirtualMachineImportSpec is used to create kubevirt VirtualMachines by exporting VM's from migration clusters.
|
||||||
type VirtualMachineImportSpec struct {
|
type VirtualMachineImportSpec struct {
|
||||||
SourceCluster corev1.ObjectReference `json:"sourceCluster"`
|
SourceCluster corev1.ObjectReference `json:"sourceCluster"`
|
||||||
VirtualMachineName string `json:"virtualMachineName"`
|
|
||||||
Folder string `json:"folder,omitempty"`
|
// VirtualMachineName is the name of the virtual machine that will be
|
||||||
Mapping []NetworkMapping `json:"networkMapping,omitempty"` //If empty new VirtualMachineImport will be mapped to Management Network
|
// imported. It contains the name or ID of the source virtual machine.
|
||||||
StorageClass string `json:"storageClass,omitempty"`
|
// Note that these names may not be DNS1123 compliant and will therefore
|
||||||
|
// be sanitized later.
|
||||||
|
// Examples: "vm-1234", "my-VM" or "5649cac7-3871-4bb5-aab6-c72b8c18d0a2"
|
||||||
|
VirtualMachineName string `json:"virtualMachineName"`
|
||||||
|
|
||||||
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@ -34,6 +41,11 @@ type VirtualMachineImportStatus struct {
|
|||||||
DiskImportStatus []DiskInfo `json:"diskImportStatus,omitempty"`
|
DiskImportStatus []DiskInfo `json:"diskImportStatus,omitempty"`
|
||||||
ImportConditions []common.Condition `json:"importConditions,omitempty"`
|
ImportConditions []common.Condition `json:"importConditions,omitempty"`
|
||||||
NewVirtualMachine string `json:"newVirtualMachine,omitempty"`
|
NewVirtualMachine string `json:"newVirtualMachine,omitempty"`
|
||||||
|
|
||||||
|
// ImportedVirtualMachineName is the sanitized and definite name of the
|
||||||
|
// target virtual machine that will be created in the Harvester cluster.
|
||||||
|
// The name is DNS1123 compliant.
|
||||||
|
ImportedVirtualMachineName string `json:"importedVirtualMachineName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiskInfo contains the information about associated Disk in the Import migration.
|
// DiskInfo contains the information about associated Disk in the Import migration.
|
||||||
@ -70,14 +82,14 @@ const (
|
|||||||
DiskImagesFailed ImportStatus = "diskImageFailed"
|
DiskImagesFailed ImportStatus = "diskImageFailed"
|
||||||
VirtualMachineCreated ImportStatus = "virtualMachineCreated"
|
VirtualMachineCreated ImportStatus = "virtualMachineCreated"
|
||||||
VirtualMachineRunning ImportStatus = "virtualMachineRunning"
|
VirtualMachineRunning ImportStatus = "virtualMachineRunning"
|
||||||
VirtualMachineInvalid ImportStatus = "virtualMachineInvalid"
|
VirtualMachineImportValid ImportStatus = "virtualMachineImportValid"
|
||||||
|
VirtualMachineImportInvalid ImportStatus = "virtualMachineImportInvalid"
|
||||||
VirtualMachinePoweringOff condition.Cond = "VMPoweringOff"
|
VirtualMachinePoweringOff condition.Cond = "VMPoweringOff"
|
||||||
VirtualMachinePoweredOff condition.Cond = "VMPoweredOff"
|
VirtualMachinePoweredOff condition.Cond = "VMPoweredOff"
|
||||||
VirtualMachineExported condition.Cond = "VMExported"
|
VirtualMachineExported condition.Cond = "VMExported"
|
||||||
VirtualMachineImageSubmitted condition.Cond = "VirtualMachineImageSubmitted"
|
VirtualMachineImageSubmitted condition.Cond = "VirtualMachineImageSubmitted"
|
||||||
VirtualMachineImageReady condition.Cond = "VirtualMachineImageReady"
|
VirtualMachineImageReady condition.Cond = "VirtualMachineImageReady"
|
||||||
VirtualMachineImageFailed condition.Cond = "VirtualMachineImageFailed"
|
VirtualMachineImageFailed condition.Cond = "VirtualMachineImageFailed"
|
||||||
NotValidDNS1123Label string = "not a valid DNS1123 label"
|
|
||||||
VirtualMachineExportFailed condition.Cond = "VMExportFailed"
|
VirtualMachineExportFailed condition.Cond = "VMExportFailed"
|
||||||
VirtualMachineMigrationFailed ImportStatus = "VMMigrationFailed"
|
VirtualMachineMigrationFailed ImportStatus = "VMMigrationFailed"
|
||||||
)
|
)
|
||||||
|
@ -48,7 +48,7 @@ func (h *virtualMachineHandler) reconcileDiskImageStatus(vm *migration.VirtualMa
|
|||||||
// If VM has no disks associated ignore the VM
|
// If VM has no disks associated ignore the VM
|
||||||
if len(orgStatus.DiskImportStatus) == 0 {
|
if len(orgStatus.DiskImportStatus) == 0 {
|
||||||
logrus.Errorf("Imported VM %s in namespace %s, has no disks, being marked as invalid and will be ignored", vm.Name, vm.Namespace)
|
logrus.Errorf("Imported VM %s in namespace %s, has no disks, being marked as invalid and will be ignored", vm.Name, vm.Namespace)
|
||||||
vm.Status.Status = migration.VirtualMachineInvalid
|
vm.Status.Status = migration.VirtualMachineImportInvalid
|
||||||
return h.importVM.UpdateStatus(vm)
|
return h.importVM.UpdateStatus(vm)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -86,8 +86,8 @@ func (h *virtualMachineHandler) reconcileVirtualMachineStatus(vm *migration.Virt
|
|||||||
return vm, err
|
return vm, err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
// VM not running, requeue and check after 5mins
|
// VM not running, requeue and check after 2mins
|
||||||
h.importVM.EnqueueAfter(vm.Namespace, vm.Name, 5*time.Minute)
|
h.importVM.EnqueueAfter(vm.Namespace, vm.Name, 2*time.Minute)
|
||||||
return vm, nil
|
return vm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,13 +98,15 @@ func (h *virtualMachineHandler) reconcileVirtualMachineStatus(vm *migration.Virt
|
|||||||
func (h *virtualMachineHandler) reconcilePreFlightChecks(vm *migration.VirtualMachineImport) (*migration.VirtualMachineImport, error) {
|
func (h *virtualMachineHandler) reconcilePreFlightChecks(vm *migration.VirtualMachineImport) (*migration.VirtualMachineImport, error) {
|
||||||
err := h.preFlightChecks(vm)
|
err := h.preFlightChecks(vm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() != migration.NotValidDNS1123Label {
|
logrus.WithFields(logrus.Fields{
|
||||||
return vm, err
|
"name": vm.Name,
|
||||||
}
|
"namespace": vm.Namespace,
|
||||||
logrus.Errorf("vm migration target %s in VM %s in namespace %s is not RFC 1123 compliant", vm.Spec.VirtualMachineName, vm.Name, vm.Namespace)
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
vm.Status.Status = migration.VirtualMachineInvalid
|
}).Errorf("The preflight checks failed: %v", err)
|
||||||
|
// Stop the reconciling for good as the checks failed.
|
||||||
|
vm.Status.Status = migration.VirtualMachineImportInvalid
|
||||||
} else {
|
} else {
|
||||||
vm.Status.Status = migration.SourceReady
|
vm.Status.Status = migration.VirtualMachineImportValid
|
||||||
}
|
}
|
||||||
return h.importVM.UpdateStatus(vm)
|
return h.importVM.UpdateStatus(vm)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,8 @@ func (h *openstackHandler) OnSourceChange(_ string, o *migration.OpenstackSource
|
|||||||
"kind": o.Kind,
|
"kind": o.Kind,
|
||||||
"name": o.Name,
|
"name": o.Name,
|
||||||
"namespace": o.Namespace,
|
"namespace": o.Namespace,
|
||||||
}).Info("Reconciling migration source")
|
}).Info("Reconciling source")
|
||||||
|
|
||||||
if o.Status.Status != migration.ClusterReady {
|
if o.Status.Status != migration.ClusterReady {
|
||||||
// process migration logic
|
// process migration logic
|
||||||
secretObj, err := h.secret.Get(o.Spec.Credentials.Namespace, o.Spec.Credentials.Name, metav1.GetOptions{})
|
secretObj, err := h.secret.Get(o.Spec.Credentials.Namespace, o.Spec.Credentials.Name, metav1.GetOptions{})
|
||||||
@ -52,7 +53,7 @@ func (h *openstackHandler) OnSourceChange(_ string, o *migration.OpenstackSource
|
|||||||
|
|
||||||
client, err := openstack.NewClient(h.ctx, o.Spec.EndpointAddress, o.Spec.Region, secretObj, o.GetOptions().(migration.OpenstackSourceOptions))
|
client, err := openstack.NewClient(h.ctx, o.Spec.EndpointAddress, o.Spec.Region, secretObj, o.GetOptions().(migration.OpenstackSourceOptions))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return o, fmt.Errorf("error generating openstack client for openstack migration: %s: %v", o.Name, err)
|
return o, fmt.Errorf("error generating openstack client for openstack migration '%s': %v", o.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Verify()
|
err = client.Verify()
|
||||||
|
@ -38,11 +38,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
vmiAnnotation = "migration.harvesterhci.io/virtualmachineimport"
|
annotationVirtualMachineImport = "migration.harvesterhci.io/virtualmachineimport"
|
||||||
imageDisplayName = "harvesterhci.io/imageDisplayName"
|
labelImageDisplayName = "harvesterhci.io/imageDisplayName"
|
||||||
|
expectedAPIVersion = "migration.harvesterhci.io/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VirtualMachineOperations interface {
|
type VirtualMachineOperations interface {
|
||||||
|
// SanitizeVirtualMachineImport is responsible for sanitizing the VirtualMachineImport object.
|
||||||
|
SanitizeVirtualMachineImport(vm *migration.VirtualMachineImport) error
|
||||||
|
|
||||||
// ExportVirtualMachine is responsible for generating the raw images for each disk associated with the VirtualMachineImport
|
// ExportVirtualMachine is responsible for generating the raw images for each disk associated with the VirtualMachineImport
|
||||||
// Any image format conversion will be performed by the VM Operation
|
// Any image format conversion will be performed by the VM Operation
|
||||||
ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||||
@ -93,12 +97,12 @@ func RegisterVMImportController(ctx context.Context, vmware migrationController.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migration.VirtualMachineImport) (*migration.VirtualMachineImport, error) {
|
func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migration.VirtualMachineImport) (*migration.VirtualMachineImport, error) {
|
||||||
|
|
||||||
if vmObj == nil || vmObj.DeletionTimestamp != nil {
|
if vmObj == nil || vmObj.DeletionTimestamp != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
vm := vmObj.DeepCopy()
|
vm := vmObj.DeepCopy()
|
||||||
|
|
||||||
switch vm.Status.Status {
|
switch vm.Status.Status {
|
||||||
case "":
|
case "":
|
||||||
// run preflight checks and make vm ready for import
|
// run preflight checks and make vm ready for import
|
||||||
@ -108,6 +112,13 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
|||||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
}).Info("Running preflight checks ...")
|
}).Info("Running preflight checks ...")
|
||||||
return h.reconcilePreFlightChecks(vm)
|
return h.reconcilePreFlightChecks(vm)
|
||||||
|
case migration.VirtualMachineImportValid:
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
}).Info("Sanitizing the import spec ...")
|
||||||
|
return h.sanitizeVirtualMachineImport(vm)
|
||||||
case migration.SourceReady:
|
case migration.SourceReady:
|
||||||
// vm migration is valid and ready. trigger migration specific import
|
// vm migration is valid and ready. trigger migration specific import
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
@ -139,7 +150,9 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
|||||||
if newStatus == nil {
|
if newStatus == nil {
|
||||||
return vm, nil
|
return vm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.Status.Status = *newStatus
|
vm.Status.Status = *newStatus
|
||||||
|
|
||||||
return h.importVM.UpdateStatus(vm)
|
return h.importVM.UpdateStatus(vm)
|
||||||
case migration.DiskImagesFailed:
|
case migration.DiskImagesFailed:
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
@ -159,7 +172,9 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return vm, err
|
return vm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.Status.Status = migration.VirtualMachineCreated
|
vm.Status.Status = migration.VirtualMachineCreated
|
||||||
|
|
||||||
return h.importVM.UpdateStatus(vm)
|
return h.importVM.UpdateStatus(vm)
|
||||||
case migration.VirtualMachineCreated:
|
case migration.VirtualMachineCreated:
|
||||||
// wait for VM to be running using a watch on VM's
|
// wait for VM to be running using a watch on VM's
|
||||||
@ -176,7 +191,7 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
|||||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
}).Info("The VM was imported successfully")
|
}).Info("The VM was imported successfully")
|
||||||
return vm, h.tidyUpObjects(vm)
|
return vm, h.tidyUpObjects(vm)
|
||||||
case migration.VirtualMachineInvalid:
|
case migration.VirtualMachineImportInvalid:
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"name": vm.Name,
|
"name": vm.Name,
|
||||||
"namespace": vm.Namespace,
|
"namespace": vm.Namespace,
|
||||||
@ -197,13 +212,8 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
|||||||
|
|
||||||
// preFlightChecks is used to validate that the associate sources and VM migration references are valid
|
// preFlightChecks is used to validate that the associate sources and VM migration references are valid
|
||||||
func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImport) error {
|
func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImport) error {
|
||||||
|
if vm.Spec.SourceCluster.APIVersion != expectedAPIVersion {
|
||||||
if errs := validation.IsDNS1123Label(vm.Spec.VirtualMachineName); len(errs) != 0 {
|
return fmt.Errorf("expected migration cluster apiversion to be '%s' but got '%s'", expectedAPIVersion, vm.Spec.SourceCluster.APIVersion)
|
||||||
return fmt.Errorf(migration.NotValidDNS1123Label)
|
|
||||||
}
|
|
||||||
|
|
||||||
if vm.Spec.SourceCluster.APIVersion != "migration.harvesterhci.io/v1beta1" {
|
|
||||||
return fmt.Errorf("expected migration cluster apiversion to be migration.harvesterhci.io/v1beta1 but got %s", vm.Spec.SourceCluster.APIVersion)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ss migration.SourceInterface
|
var ss migration.SourceInterface
|
||||||
@ -213,21 +223,21 @@ func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImpo
|
|||||||
case "vmwaresource", "openstacksource":
|
case "vmwaresource", "openstacksource":
|
||||||
ss, err = h.generateSource(vm)
|
ss, err = h.generateSource(vm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating migration in preflight checks :%v", err)
|
return fmt.Errorf("error generating migration in preflight checks: %v", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported migration kind. Currently supported values are vmware/openstack but got %s", strings.ToLower(vm.Spec.SourceCluster.Kind))
|
return fmt.Errorf("unsupported migration kind. Currently supported values are vmware/openstack but got '%s'", strings.ToLower(vm.Spec.SourceCluster.Kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
if ss.ClusterStatus() != migration.ClusterReady {
|
if ss.ClusterStatus() != migration.ClusterReady {
|
||||||
return fmt.Errorf("migration not yet ready. current status is %s", ss.ClusterStatus())
|
return fmt.Errorf("migration not yet ready. Current status is '%s'", ss.ClusterStatus())
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify specified storage class exists. Empty storage class means default storage class
|
// verify specified storage class exists. Empty storage class means default storage class
|
||||||
if vm.Spec.StorageClass != "" {
|
if vm.Spec.StorageClass != "" {
|
||||||
_, err := h.sc.Get(vm.Spec.StorageClass)
|
_, err := h.sc.Get(vm.Spec.StorageClass)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("error looking up storageclass %s: %v", vm.Spec.StorageClass, err)
|
logrus.Errorf("error looking up storageclass '%s': %v", vm.Spec.StorageClass, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,13 +314,16 @@ func (h *virtualMachineHandler) triggerExport(vm *migration.VirtualMachineImport
|
|||||||
// power off machine
|
// power off machine
|
||||||
if !util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachinePoweringOff, v1.ConditionTrue) {
|
if !util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachinePoweringOff, v1.ConditionTrue) {
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"name": vm.Name,
|
"name": vm.Name,
|
||||||
"namespace": vm.Namespace,
|
"namespace": vm.Namespace,
|
||||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
}).Info("Powering off client VM ...")
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"status.importedVirtualMachineName": vm.Status.ImportedVirtualMachineName,
|
||||||
|
}).Info("Power off the source VM")
|
||||||
err = vmo.PowerOffVirtualMachine(vm)
|
err = vmo.PowerOffVirtualMachine(vm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error in poweroff call: %v", err)
|
return fmt.Errorf("failed to power off the source VM: %v", err)
|
||||||
}
|
}
|
||||||
conds := []common.Condition{
|
conds := []common.Condition{
|
||||||
{
|
{
|
||||||
@ -352,6 +365,14 @@ func (h *virtualMachineHandler) triggerExport(vm *migration.VirtualMachineImport
|
|||||||
util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachinePoweringOff, v1.ConditionTrue) &&
|
util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachinePoweringOff, v1.ConditionTrue) &&
|
||||||
!util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachineExported, v1.ConditionTrue) &&
|
!util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachineExported, v1.ConditionTrue) &&
|
||||||
!util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachineExportFailed, v1.ConditionTrue) {
|
!util.ConditionExists(vm.Status.ImportConditions, migration.VirtualMachineExportFailed, v1.ConditionTrue) {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"status.importedVirtualMachineName": vm.Status.ImportedVirtualMachineName,
|
||||||
|
}).Info("Exporting source VM")
|
||||||
err := vmo.ExportVirtualMachine(vm)
|
err := vmo.ExportVirtualMachine(vm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// avoid retrying if vm export fails
|
// avoid retrying if vm export fails
|
||||||
@ -365,7 +386,14 @@ func (h *virtualMachineHandler) triggerExport(vm *migration.VirtualMachineImport
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
vm.Status.ImportConditions = util.MergeConditions(vm.Status.ImportConditions, conds)
|
vm.Status.ImportConditions = util.MergeConditions(vm.Status.ImportConditions, conds)
|
||||||
logrus.Errorf("error exporting virtualmachine %s for virtualmachineimport %s-%s: %v", vm.Spec.VirtualMachineName, vm.Namespace, vm.Name, err)
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"status.importedVirtualMachineName": vm.Status.ImportedVirtualMachineName,
|
||||||
|
}).Errorf("Failed to export source VM: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conds := []common.Condition{
|
conds := []common.Condition{
|
||||||
@ -503,8 +531,9 @@ func (h *virtualMachineHandler) reconcileVMIStatus(vm *migration.VirtualMachineI
|
|||||||
func (h *virtualMachineHandler) createVirtualMachine(vm *migration.VirtualMachineImport) error {
|
func (h *virtualMachineHandler) createVirtualMachine(vm *migration.VirtualMachineImport) error {
|
||||||
vmo, err := h.generateVMO(vm)
|
vmo, err := h.generateVMO(vm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating VMO in createVirtualMachine :%v", err)
|
return fmt.Errorf("error generating VMO in createVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runVM, err := vmo.GenerateVirtualMachine(vm)
|
runVM, err := vmo.GenerateVirtualMachine(vm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating Kubevirt VM: %v", err)
|
return fmt.Errorf("error generating Kubevirt VM: %v", err)
|
||||||
@ -546,37 +575,37 @@ func (h *virtualMachineHandler) createVirtualMachine(vm *migration.VirtualMachin
|
|||||||
|
|
||||||
runVM.Spec.Template.Spec.Volumes = vmVols
|
runVM.Spec.Template.Spec.Volumes = vmVols
|
||||||
runVM.Spec.Template.Spec.Domain.Devices.Disks = disks
|
runVM.Spec.Template.Spec.Domain.Devices.Disks = disks
|
||||||
// apply virtualmachineimport annotation
|
|
||||||
|
|
||||||
|
// Apply annotations to the `VirtualMachine` object to make the newly
|
||||||
|
// created VM identifiable.
|
||||||
if runVM.GetAnnotations() == nil {
|
if runVM.GetAnnotations() == nil {
|
||||||
runVM.Annotations = make(map[string]string)
|
runVM.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
runVM.Annotations[vmiAnnotation] = fmt.Sprintf("%s-%s", vm.Name, vm.Namespace)
|
runVM.Annotations[annotationVirtualMachineImport] = fmt.Sprintf("%s-%s", vm.Name, vm.Namespace)
|
||||||
|
|
||||||
|
// Make sure the new VM is created only if it does not exist.
|
||||||
found := false
|
found := false
|
||||||
existingVMO, err := h.kubevirt.Get(runVM.Namespace, runVM.Name, metav1.GetOptions{})
|
existingVM, err := h.kubevirt.Get(runVM.Namespace, runVM.Name, metav1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if existingVMO.Annotations[vmiAnnotation] == fmt.Sprintf("%s-%s", vm.Name, vm.Namespace) {
|
if existingVM.Annotations[annotationVirtualMachineImport] == fmt.Sprintf("%s-%s", vm.Name, vm.Namespace) {
|
||||||
found = true
|
found = true
|
||||||
vm.Status.NewVirtualMachine = existingVMO.Name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
runVMObj, err := h.kubevirt.Create(runVM)
|
_, err := h.kubevirt.Create(runVM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating kubevirt VM in createVirtualMachine :%v", err)
|
return fmt.Errorf("error creating kubevirt VM in createVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
vm.Status.NewVirtualMachine = runVMObj.Name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *virtualMachineHandler) checkVirtualMachine(vm *migration.VirtualMachineImport) (bool, error) {
|
func (h *virtualMachineHandler) checkVirtualMachine(vm *migration.VirtualMachineImport) (bool, error) {
|
||||||
vmObj, err := h.kubevirt.Get(vm.Namespace, vm.Status.NewVirtualMachine, metav1.GetOptions{})
|
vmObj, err := h.kubevirt.Get(vm.Namespace, vm.Status.ImportedVirtualMachineName, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error querying kubevirt vm in checkVirtualMachine :%v", err)
|
return false, fmt.Errorf("error querying kubevirt vm in checkVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return vmObj.Status.Ready, nil
|
return vmObj.Status.Ready, nil
|
||||||
@ -707,7 +736,7 @@ func generateAnnotations(vm *migration.VirtualMachineImport, vmi *harvesterv1bet
|
|||||||
annotationSchemaOwners := ref.AnnotationSchemaOwners{}
|
annotationSchemaOwners := ref.AnnotationSchemaOwners{}
|
||||||
_ = annotationSchemaOwners.Add(kubevirt.VirtualMachineGroupVersionKind.GroupKind(), vm)
|
_ = annotationSchemaOwners.Add(kubevirt.VirtualMachineGroupVersionKind.GroupKind(), vm)
|
||||||
var schemaID = ref.GroupKindToSchemaID(kubevirt.VirtualMachineGroupVersionKind.GroupKind())
|
var schemaID = ref.GroupKindToSchemaID(kubevirt.VirtualMachineGroupVersionKind.GroupKind())
|
||||||
var ownerRef = ref.Construct(vm.GetNamespace(), vm.Spec.VirtualMachineName)
|
var ownerRef = ref.Construct(vm.GetNamespace(), vm.Status.ImportedVirtualMachineName)
|
||||||
schemaRef := ref.AnnotationSchemaReference{SchemaID: schemaID, References: ref.NewAnnotationSchemaOwnerReferences()}
|
schemaRef := ref.AnnotationSchemaReference{SchemaID: schemaID, References: ref.NewAnnotationSchemaOwnerReferences()}
|
||||||
schemaRef.References.Insert(ownerRef)
|
schemaRef.References.Insert(ownerRef)
|
||||||
annotationSchemaOwners[schemaID] = schemaRef
|
annotationSchemaOwners[schemaID] = schemaRef
|
||||||
@ -724,15 +753,14 @@ func generateAnnotations(vm *migration.VirtualMachineImport, vmi *harvesterv1bet
|
|||||||
|
|
||||||
func (h *virtualMachineHandler) checkAndCreateVirtualMachineImage(vm *migration.VirtualMachineImport, d migration.DiskInfo) (*harvesterv1beta1.VirtualMachineImage, error) {
|
func (h *virtualMachineHandler) checkAndCreateVirtualMachineImage(vm *migration.VirtualMachineImport, d migration.DiskInfo) (*harvesterv1beta1.VirtualMachineImage, error) {
|
||||||
imageList, err := h.vmi.Cache().List(vm.Namespace, labels.SelectorFromSet(map[string]string{
|
imageList, err := h.vmi.Cache().List(vm.Namespace, labels.SelectorFromSet(map[string]string{
|
||||||
imageDisplayName: fmt.Sprintf("vm-import-%s-%s", vm.Name, d.Name),
|
labelImageDisplayName: fmt.Sprintf("vm-import-%s-%s", vm.Name, d.Name),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(imageList) > 1 {
|
if len(imageList) > 1 {
|
||||||
return nil, fmt.Errorf("unexpected error: found %d images with label %s=%s, only expected to find one", len(imageList), imageDisplayName, fmt.Sprintf("vm-import-%s-%s", vm.Name, d.Name))
|
return nil, fmt.Errorf("unexpected error: found %d images with label %s=%s, only expected to find one", len(imageList), labelImageDisplayName, fmt.Sprintf("vm-import-%s-%s", vm.Name, d.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(imageList) == 1 {
|
if len(imageList) == 1 {
|
||||||
@ -753,7 +781,7 @@ func (h *virtualMachineHandler) checkAndCreateVirtualMachineImage(vm *migration.
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
imageDisplayName: fmt.Sprintf("vm-import-%s-%s", vm.Name, d.Name),
|
labelImageDisplayName: fmt.Sprintf("vm-import-%s-%s", vm.Name, d.Name),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: harvesterv1beta1.VirtualMachineImageSpec{
|
Spec: harvesterv1beta1.VirtualMachineImageSpec{
|
||||||
@ -770,5 +798,52 @@ func (h *virtualMachineHandler) checkAndCreateVirtualMachineImage(vm *migration.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.vmi.Create(vmi)
|
vmiObj, err := h.vmi.Create(vmi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Harvester VirtualMachineImage (namespace=%s spec.displayName=%s): %v", vmi.Namespace, vmi.Spec.DisplayName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return vmiObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *virtualMachineHandler) sanitizeVirtualMachineImport(vm *migration.VirtualMachineImport) (*migration.VirtualMachineImport, error) {
|
||||||
|
vmo, err := h.generateVMO(vm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating VMO in sanitizeVirtualMachineImport: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vmo.SanitizeVirtualMachineImport(vm)
|
||||||
|
if err != nil {
|
||||||
|
vm.Status.Status = migration.VirtualMachineImportInvalid
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"kind": vm.Kind,
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"status.importedVirtualMachineName": vm.Status.ImportedVirtualMachineName,
|
||||||
|
}).Errorf("Failed to sanitize the import spec: %v", err)
|
||||||
|
} else {
|
||||||
|
// Make sure the ImportedVirtualMachineName is RFC 1123 compliant.
|
||||||
|
if errs := validation.IsDNS1123Label(vm.Status.ImportedVirtualMachineName); len(errs) != 0 {
|
||||||
|
vm.Status.Status = migration.VirtualMachineImportInvalid
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"kind": vm.Kind,
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"status.importedVirtualMachineName": vm.Status.ImportedVirtualMachineName,
|
||||||
|
}).Error("The definitive name of the imported VM is not RFC 1123 compliant")
|
||||||
|
} else {
|
||||||
|
vm.Status.Status = migration.SourceReady
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"kind": vm.Kind,
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"status.importedVirtualMachineName": vm.Status.ImportedVirtualMachineName,
|
||||||
|
}).Info("The sanitization of the import spec was successful")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.importVM.UpdateStatus(vm)
|
||||||
}
|
}
|
||||||
|
@ -42,15 +42,17 @@ func (h *vmwareHandler) OnSourceChange(_ string, v *migration.VmwareSource) (*mi
|
|||||||
"kind": v.Kind,
|
"kind": v.Kind,
|
||||||
"name": v.Name,
|
"name": v.Name,
|
||||||
"namespace": v.Namespace,
|
"namespace": v.Namespace,
|
||||||
}).Info("Reconciling migration source")
|
}).Info("Reconciling source")
|
||||||
|
|
||||||
if v.Status.Status != migration.ClusterReady {
|
if v.Status.Status != migration.ClusterReady {
|
||||||
secretObj, err := h.secret.Get(v.Spec.Credentials.Namespace, v.Spec.Credentials.Name, metav1.GetOptions{})
|
secretObj, err := h.secret.Get(v.Spec.Credentials.Namespace, v.Spec.Credentials.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v, fmt.Errorf("error looking up secret for vmware migration: %v", err)
|
return v, fmt.Errorf("error looking up secret for vmware migration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := vmware.NewClient(h.ctx, v.Spec.EndpointAddress, v.Spec.Datacenter, secretObj)
|
client, err := vmware.NewClient(h.ctx, v.Spec.EndpointAddress, v.Spec.Datacenter, secretObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return v, fmt.Errorf("error generating vmware client for vmware migration: %s: %v", v.Name, err)
|
return v, fmt.Errorf("error generating vmware client for vmware migration '%s': %v", v.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Verify()
|
err = client.Verify()
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -154,15 +155,10 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Verify() error {
|
func (c *Client) Verify() error {
|
||||||
computeClient, err := openstack.NewComputeV2(c.pClient, c.opts)
|
pg := servers.List(c.computeClient, servers.ListOpts{})
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error generating compute client during verify phase :%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pg := servers.List(computeClient, servers.ListOpts{})
|
|
||||||
allPg, err := pg.AllPages()
|
allPg, err := pg.AllPages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating all pages :%v", err)
|
return fmt.Errorf("error generating all pages: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := allPg.IsEmpty()
|
ok, err := allPg.IsEmpty()
|
||||||
@ -176,7 +172,7 @@ func (c *Client) Verify() error {
|
|||||||
|
|
||||||
allServers, err := servers.ExtractServers(allPg)
|
allServers, err := servers.ExtractServers(allPg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error extracting servers :%v", err)
|
return fmt.Errorf("error extracting servers: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("found %d servers", len(allServers))
|
logrus.Infof("found %d servers", len(allServers))
|
||||||
@ -269,6 +265,15 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
|||||||
return fmt.Errorf("timeout waiting for volume %s to be available: %v", volObj.ID, err)
|
return fmt.Errorf("timeout waiting for volume %s to be available: %v", volObj.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"attachedVolumeID": v.ID,
|
||||||
|
}).Info("Creating a new image from a volume")
|
||||||
|
|
||||||
volImage, err := volumeactions.UploadImage(c.storageClient, volObj.ID, volumeactions.UploadImageOpts{
|
volImage, err := volumeactions.UploadImage(c.storageClient, volObj.ID, volumeactions.UploadImageOpts{
|
||||||
ImageName: imageName,
|
ImageName: imageName,
|
||||||
DiskFormat: "raw",
|
DiskFormat: "raw",
|
||||||
@ -296,6 +301,15 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
|||||||
time.Sleep(time.Duration(c.options.UploadImageRetryDelay) * time.Second)
|
time.Sleep(time.Duration(c.options.UploadImageRetryDelay) * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"imageID": volImage.ImageID,
|
||||||
|
}).Info("Downloading an image")
|
||||||
|
|
||||||
contents, err := imagedata.Download(c.imageClient, volImage.ImageID).Extract()
|
contents, err := imagedata.Download(c.imageClient, volImage.ImageID).Extract()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -338,11 +352,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error {
|
func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error {
|
||||||
computeClient, err := openstack.NewComputeV2(c.pClient, c.opts)
|
serverUUID, err := c.checkOrGetUUID(vm.Spec.VirtualMachineName)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error generating compute client during poweroffvirtualmachine: %v", err)
|
|
||||||
}
|
|
||||||
uuid, err := c.checkOrGetUUID(vm.Spec.VirtualMachineName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -352,13 +362,12 @@ func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return startstop.Stop(computeClient, uuid).ExtractErr()
|
return startstop.Stop(c.computeClient, serverUUID).ExtractErr()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error) {
|
func (c *Client) IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error) {
|
||||||
|
|
||||||
s, err := c.findVM(vm.Spec.VirtualMachineName)
|
s, err := c.findVM(vm.Spec.VirtualMachineName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -376,7 +385,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
var boolTrue = true
|
var boolTrue = true
|
||||||
vmObj, err := c.findVM(vm.Spec.VirtualMachineName)
|
vmObj, err := c.findVM(vm.Spec.VirtualMachineName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error finding vm in generatevirtualmachine: %v", err)
|
return nil, fmt.Errorf("error finding VM in GenerateVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorObj, err := flavors.Get(c.computeClient, vmObj.Flavor["id"].(string)).Extract()
|
flavorObj, err := flavors.Get(c.computeClient, vmObj.Flavor["id"].(string)).Extract()
|
||||||
@ -396,7 +405,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
|
|
||||||
newVM := &kubevirt.VirtualMachine{
|
newVM := &kubevirt.VirtualMachine{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: vm.Spec.VirtualMachineName,
|
Name: vm.Status.ImportedVirtualMachineName,
|
||||||
Namespace: vm.Namespace,
|
Namespace: vm.Namespace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -413,7 +422,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
Template: &kubevirt.VirtualMachineInstanceTemplateSpec{
|
Template: &kubevirt.VirtualMachineInstanceTemplateSpec{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"harvesterhci.io/vmName": vm.Spec.VirtualMachineName,
|
"harvesterhci.io/vmName": vm.Status.ImportedVirtualMachineName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: kubevirt.VirtualMachineInstanceSpec{
|
Spec: kubevirt.VirtualMachineInstanceSpec{
|
||||||
@ -722,6 +731,28 @@ func generateNetworkInfo(info map[string]interface{}) ([]networkInfo, error) {
|
|||||||
return uniqueNetworks, nil
|
return uniqueNetworks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizeVirtualMachineImport is used to sanitize the VirtualMachineImport object.
|
||||||
|
func (c *Client) SanitizeVirtualMachineImport(vm *migration.VirtualMachineImport) error {
|
||||||
|
// If the given `spec.virtualMachineName` is a UUID, then we need to
|
||||||
|
// get the name from the OpenStack server object.
|
||||||
|
parsedUUID, err := uuid.Parse(vm.Spec.VirtualMachineName)
|
||||||
|
if err == nil {
|
||||||
|
vmObj, err := c.findVM(parsedUUID.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
vm.Status.ImportedVirtualMachineName = vmObj.Name
|
||||||
|
} else {
|
||||||
|
vm.Status.ImportedVirtualMachineName = vm.Spec.VirtualMachineName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note, server objects might have upper case characters in OpenStack,
|
||||||
|
// so we need to convert them to lower case to be RFC 1123 compliant.
|
||||||
|
vm.Status.ImportedVirtualMachineName = strings.ToLower(vm.Status.ImportedVirtualMachineName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// writeRawImageFile Download and write the raw image file to the specified path in chunks of 32KiB.
|
// writeRawImageFile Download and write the raw image file to the specified path in chunks of 32KiB.
|
||||||
func writeRawImageFile(name string, src io.ReadCloser) error {
|
func writeRawImageFile(name string, src io.ReadCloser) error {
|
||||||
dst, err := os.Create(name)
|
dst, err := os.Create(name)
|
||||||
|
@ -119,7 +119,7 @@ func (c *Client) Verify() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Infof("found dc :%v", dcObj)
|
logrus.Infof("found dc: %v", dcObj)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,12 +158,12 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
|||||||
tmpPath, err = os.MkdirTemp("/tmp", fmt.Sprintf("%s-%s-", vm.Name, vm.Namespace))
|
tmpPath, err = os.MkdirTemp("/tmp", fmt.Sprintf("%s-%s-", vm.Name, vm.Namespace))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating tmp dir for vmexport: %v", err)
|
return fmt.Errorf("error creating tmp dir in ExportVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vmObj, err = c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
vmObj, err = c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding vm in ExportVirtualMacine: %v", err)
|
return fmt.Errorf("error finding vm in ExportVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lease, err = vmObj.Export(c.ctx)
|
lease, err = vmObj.Export(c.ctx)
|
||||||
@ -186,6 +186,17 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
|||||||
i.Path = vm.Name + "-" + vm.Namespace + "-" + i.Path
|
i.Path = vm.Name + "-" + vm.Namespace + "-" + i.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"deviceId": i.DeviceId,
|
||||||
|
"path": i.Path,
|
||||||
|
"size": i.Size,
|
||||||
|
}).Info("Downloading an image")
|
||||||
|
|
||||||
exportPath := filepath.Join(tmpPath, i.Path)
|
exportPath := filepath.Join(tmpPath, i.Path)
|
||||||
err = lease.DownloadFile(c.ctx, exportPath, i, soap.DefaultDownload)
|
err = lease.DownloadFile(c.ctx, exportPath, i, soap.DefaultDownload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,6 +207,17 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
|||||||
DiskSize: i.Size,
|
DiskSize: i.Size,
|
||||||
BusType: adapterType(i.DeviceId),
|
BusType: adapterType(i.DeviceId),
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
logrus.WithFields(logrus.Fields{
|
||||||
|
"name": vm.Name,
|
||||||
|
"namespace": vm.Namespace,
|
||||||
|
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||||
|
"spec.sourceCluster.name": vm.Spec.SourceCluster.Name,
|
||||||
|
"spec.sourceCluster.kind": vm.Spec.SourceCluster.Kind,
|
||||||
|
"deviceId": i.DeviceId,
|
||||||
|
"path": i.Path,
|
||||||
|
"size": i.Size,
|
||||||
|
}).Info("Skipping an image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +246,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
|||||||
destFile := filepath.Join(server.TempDir(), rawDiskName)
|
destFile := filepath.Join(server.TempDir(), rawDiskName)
|
||||||
err = qemu.ConvertVMDKtoRAW(sourceFile, destFile)
|
err = qemu.ConvertVMDKtoRAW(sourceFile, destFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error during conversion of vmdk to raw disk %v", err)
|
return fmt.Errorf("error during conversion of VMDK to RAW disk: %v", err)
|
||||||
}
|
}
|
||||||
// update fields to reflect final location of raw image file
|
// update fields to reflect final location of raw image file
|
||||||
vm.Status.DiskImportStatus[i].DiskLocalPath = server.TempDir()
|
vm.Status.DiskImportStatus[i].DiskLocalPath = server.TempDir()
|
||||||
@ -236,7 +258,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
|||||||
func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error {
|
func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error {
|
||||||
vmObj, err := c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
vmObj, err := c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding vm in PowerOffVirtualMachine: %v", err)
|
return fmt.Errorf("error finding VM in PowerOffVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err := c.IsPoweredOff(vm)
|
ok, err := c.IsPoweredOff(vm)
|
||||||
@ -255,7 +277,7 @@ func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) erro
|
|||||||
func (c *Client) IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error) {
|
func (c *Client) IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error) {
|
||||||
vmObj, err := c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
vmObj, err := c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("error find VM in IsPoweredOff :%v", err)
|
return false, fmt.Errorf("error find VM in IsPoweredOff: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := vmObj.PowerState(c.ctx)
|
state, err := vmObj.PowerState(c.ctx)
|
||||||
@ -278,9 +300,10 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error quering vm in GenerateVirtualMachine: %v", err)
|
return nil, fmt.Errorf("error quering vm in GenerateVirtualMachine: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newVM := &kubevirt.VirtualMachine{
|
newVM := &kubevirt.VirtualMachine{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: vm.Spec.VirtualMachineName,
|
Name: vm.Status.ImportedVirtualMachineName,
|
||||||
Namespace: vm.Namespace,
|
Namespace: vm.Namespace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -300,7 +323,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
|||||||
Template: &kubevirt.VirtualMachineInstanceTemplateSpec{
|
Template: &kubevirt.VirtualMachineInstanceTemplateSpec{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"harvesterhci.io/vmName": vm.Spec.VirtualMachineName,
|
"harvesterhci.io/vmName": vm.Status.ImportedVirtualMachineName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Spec: kubevirt.VirtualMachineInstanceSpec{
|
Spec: kubevirt.VirtualMachineInstanceSpec{
|
||||||
@ -478,3 +501,12 @@ func adapterType(deviceID string) kubevirt.DiskBus {
|
|||||||
}
|
}
|
||||||
return kubevirt.DiskBusSATA
|
return kubevirt.DiskBusSATA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SanitizeVirtualMachineImport is used to sanitize the VirtualMachineImport object.
|
||||||
|
func (c *Client) SanitizeVirtualMachineImport(vm *migration.VirtualMachineImport) error {
|
||||||
|
// Note, VMware allows upper case characters in virtual machine names,
|
||||||
|
// so we need to convert them to lower case to be RFC 1123 compliant.
|
||||||
|
vm.Status.ImportedVirtualMachineName = strings.ToLower(vm.Spec.VirtualMachineName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -78,11 +78,11 @@ var _ = Describe("perform valid dns names", func() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if vmObj.Status.Status == migration.VirtualMachineInvalid {
|
if vmObj.Status.Status == migration.VirtualMachineImportInvalid {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("waiiting for vm obj to be marked invalid")
|
return fmt.Errorf("waiting for vm obj to be marked invalid")
|
||||||
}, "30s", "5s").ShouldNot(HaveOccurred())
|
}, "30s", "5s").ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user