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
@ -22,7 +22,14 @@ type VirtualMachineImport struct {
|
||||
// VirtualMachineImportSpec is used to create kubevirt VirtualMachines by exporting VM's from migration clusters.
|
||||
type VirtualMachineImportSpec struct {
|
||||
SourceCluster corev1.ObjectReference `json:"sourceCluster"`
|
||||
|
||||
// VirtualMachineName is the name of the virtual machine that will be
|
||||
// imported. It contains the name or ID of the source virtual machine.
|
||||
// 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"`
|
||||
@ -34,6 +41,11 @@ type VirtualMachineImportStatus struct {
|
||||
DiskImportStatus []DiskInfo `json:"diskImportStatus,omitempty"`
|
||||
ImportConditions []common.Condition `json:"importConditions,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.
|
||||
@ -70,14 +82,14 @@ const (
|
||||
DiskImagesFailed ImportStatus = "diskImageFailed"
|
||||
VirtualMachineCreated ImportStatus = "virtualMachineCreated"
|
||||
VirtualMachineRunning ImportStatus = "virtualMachineRunning"
|
||||
VirtualMachineInvalid ImportStatus = "virtualMachineInvalid"
|
||||
VirtualMachineImportValid ImportStatus = "virtualMachineImportValid"
|
||||
VirtualMachineImportInvalid ImportStatus = "virtualMachineImportInvalid"
|
||||
VirtualMachinePoweringOff condition.Cond = "VMPoweringOff"
|
||||
VirtualMachinePoweredOff condition.Cond = "VMPoweredOff"
|
||||
VirtualMachineExported condition.Cond = "VMExported"
|
||||
VirtualMachineImageSubmitted condition.Cond = "VirtualMachineImageSubmitted"
|
||||
VirtualMachineImageReady condition.Cond = "VirtualMachineImageReady"
|
||||
VirtualMachineImageFailed condition.Cond = "VirtualMachineImageFailed"
|
||||
NotValidDNS1123Label string = "not a valid DNS1123 label"
|
||||
VirtualMachineExportFailed condition.Cond = "VMExportFailed"
|
||||
VirtualMachineMigrationFailed ImportStatus = "VMMigrationFailed"
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ func (h *virtualMachineHandler) reconcileDiskImageStatus(vm *migration.VirtualMa
|
||||
// If VM has no disks associated ignore the VM
|
||||
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)
|
||||
vm.Status.Status = migration.VirtualMachineInvalid
|
||||
vm.Status.Status = migration.VirtualMachineImportInvalid
|
||||
return h.importVM.UpdateStatus(vm)
|
||||
|
||||
}
|
||||
@ -86,8 +86,8 @@ func (h *virtualMachineHandler) reconcileVirtualMachineStatus(vm *migration.Virt
|
||||
return vm, err
|
||||
}
|
||||
if !ok {
|
||||
// VM not running, requeue and check after 5mins
|
||||
h.importVM.EnqueueAfter(vm.Namespace, vm.Name, 5*time.Minute)
|
||||
// VM not running, requeue and check after 2mins
|
||||
h.importVM.EnqueueAfter(vm.Namespace, vm.Name, 2*time.Minute)
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
@ -98,13 +98,15 @@ func (h *virtualMachineHandler) reconcileVirtualMachineStatus(vm *migration.Virt
|
||||
func (h *virtualMachineHandler) reconcilePreFlightChecks(vm *migration.VirtualMachineImport) (*migration.VirtualMachineImport, error) {
|
||||
err := h.preFlightChecks(vm)
|
||||
if err != nil {
|
||||
if err.Error() != migration.NotValidDNS1123Label {
|
||||
return vm, err
|
||||
}
|
||||
logrus.Errorf("vm migration target %s in VM %s in namespace %s is not RFC 1123 compliant", vm.Spec.VirtualMachineName, vm.Name, vm.Namespace)
|
||||
vm.Status.Status = migration.VirtualMachineInvalid
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"name": vm.Name,
|
||||
"namespace": vm.Namespace,
|
||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||
}).Errorf("The preflight checks failed: %v", err)
|
||||
// Stop the reconciling for good as the checks failed.
|
||||
vm.Status.Status = migration.VirtualMachineImportInvalid
|
||||
} else {
|
||||
vm.Status.Status = migration.SourceReady
|
||||
vm.Status.Status = migration.VirtualMachineImportValid
|
||||
}
|
||||
return h.importVM.UpdateStatus(vm)
|
||||
}
|
||||
|
@ -42,7 +42,8 @@ func (h *openstackHandler) OnSourceChange(_ string, o *migration.OpenstackSource
|
||||
"kind": o.Kind,
|
||||
"name": o.Name,
|
||||
"namespace": o.Namespace,
|
||||
}).Info("Reconciling migration source")
|
||||
}).Info("Reconciling source")
|
||||
|
||||
if o.Status.Status != migration.ClusterReady {
|
||||
// process migration logic
|
||||
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))
|
||||
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()
|
||||
|
@ -38,11 +38,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
vmiAnnotation = "migration.harvesterhci.io/virtualmachineimport"
|
||||
imageDisplayName = "harvesterhci.io/imageDisplayName"
|
||||
annotationVirtualMachineImport = "migration.harvesterhci.io/virtualmachineimport"
|
||||
labelImageDisplayName = "harvesterhci.io/imageDisplayName"
|
||||
expectedAPIVersion = "migration.harvesterhci.io/v1beta1"
|
||||
)
|
||||
|
||||
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
|
||||
// Any image format conversion will be performed by the VM Operation
|
||||
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) {
|
||||
|
||||
if vmObj == nil || vmObj.DeletionTimestamp != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
vm := vmObj.DeepCopy()
|
||||
|
||||
switch vm.Status.Status {
|
||||
case "":
|
||||
// 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,
|
||||
}).Info("Running preflight checks ...")
|
||||
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:
|
||||
// vm migration is valid and ready. trigger migration specific import
|
||||
logrus.WithFields(logrus.Fields{
|
||||
@ -139,7 +150,9 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
||||
if newStatus == nil {
|
||||
return vm, nil
|
||||
}
|
||||
|
||||
vm.Status.Status = *newStatus
|
||||
|
||||
return h.importVM.UpdateStatus(vm)
|
||||
case migration.DiskImagesFailed:
|
||||
logrus.WithFields(logrus.Fields{
|
||||
@ -159,7 +172,9 @@ func (h *virtualMachineHandler) OnVirtualMachineChange(_ string, vmObj *migratio
|
||||
if err != nil {
|
||||
return vm, err
|
||||
}
|
||||
|
||||
vm.Status.Status = migration.VirtualMachineCreated
|
||||
|
||||
return h.importVM.UpdateStatus(vm)
|
||||
case migration.VirtualMachineCreated:
|
||||
// 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,
|
||||
}).Info("The VM was imported successfully")
|
||||
return vm, h.tidyUpObjects(vm)
|
||||
case migration.VirtualMachineInvalid:
|
||||
case migration.VirtualMachineImportInvalid:
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"name": vm.Name,
|
||||
"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
|
||||
func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImport) error {
|
||||
|
||||
if errs := validation.IsDNS1123Label(vm.Spec.VirtualMachineName); len(errs) != 0 {
|
||||
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)
|
||||
if vm.Spec.SourceCluster.APIVersion != expectedAPIVersion {
|
||||
return fmt.Errorf("expected migration cluster apiversion to be '%s' but got '%s'", expectedAPIVersion, vm.Spec.SourceCluster.APIVersion)
|
||||
}
|
||||
|
||||
var ss migration.SourceInterface
|
||||
@ -216,18 +226,18 @@ func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImpo
|
||||
return fmt.Errorf("error generating migration in preflight checks: %v", err)
|
||||
}
|
||||
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 {
|
||||
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
|
||||
if vm.Spec.StorageClass != "" {
|
||||
_, err := h.sc.Get(vm.Spec.StorageClass)
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -307,10 +317,13 @@ func (h *virtualMachineHandler) triggerExport(vm *migration.VirtualMachineImport
|
||||
"name": vm.Name,
|
||||
"namespace": vm.Namespace,
|
||||
"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)
|
||||
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{
|
||||
{
|
||||
@ -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.VirtualMachineExported, 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)
|
||||
if err != nil {
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
conds := []common.Condition{
|
||||
@ -505,6 +533,7 @@ func (h *virtualMachineHandler) createVirtualMachine(vm *migration.VirtualMachin
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating VMO in createVirtualMachine: %v", err)
|
||||
}
|
||||
|
||||
runVM, err := vmo.GenerateVirtualMachine(vm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating Kubevirt VM: %v", err)
|
||||
@ -546,35 +575,35 @@ func (h *virtualMachineHandler) createVirtualMachine(vm *migration.VirtualMachin
|
||||
|
||||
runVM.Spec.Template.Spec.Volumes = vmVols
|
||||
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 {
|
||||
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
|
||||
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 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
|
||||
vm.Status.NewVirtualMachine = existingVMO.Name
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
runVMObj, err := h.kubevirt.Create(runVM)
|
||||
_, err := h.kubevirt.Create(runVM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating kubevirt VM in createVirtualMachine: %v", err)
|
||||
}
|
||||
vm.Status.NewVirtualMachine = runVMObj.Name
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return false, fmt.Errorf("error querying kubevirt vm in checkVirtualMachine: %v", err)
|
||||
}
|
||||
@ -707,7 +736,7 @@ func generateAnnotations(vm *migration.VirtualMachineImport, vmi *harvesterv1bet
|
||||
annotationSchemaOwners := ref.AnnotationSchemaOwners{}
|
||||
_ = annotationSchemaOwners.Add(kubevirt.VirtualMachineGroupVersionKind.GroupKind(), vm)
|
||||
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.References.Insert(ownerRef)
|
||||
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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -753,7 +781,7 @@ func (h *virtualMachineHandler) checkAndCreateVirtualMachineImage(vm *migration.
|
||||
},
|
||||
},
|
||||
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{
|
||||
@ -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,
|
||||
"name": v.Name,
|
||||
"namespace": v.Namespace,
|
||||
}).Info("Reconciling migration source")
|
||||
}).Info("Reconciling source")
|
||||
|
||||
if v.Status.Status != migration.ClusterReady {
|
||||
secretObj, err := h.secret.Get(v.Spec.Credentials.Namespace, v.Spec.Credentials.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
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)
|
||||
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()
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@ -154,12 +155,7 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
|
||||
}
|
||||
|
||||
func (c *Client) Verify() error {
|
||||
computeClient, err := openstack.NewComputeV2(c.pClient, c.opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating compute client during verify phase :%v", err)
|
||||
}
|
||||
|
||||
pg := servers.List(computeClient, servers.ListOpts{})
|
||||
pg := servers.List(c.computeClient, servers.ListOpts{})
|
||||
allPg, err := pg.AllPages()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating all pages: %v", err)
|
||||
@ -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)
|
||||
}
|
||||
|
||||
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{
|
||||
ImageName: imageName,
|
||||
DiskFormat: "raw",
|
||||
@ -296,6 +301,15 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||
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()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -338,11 +352,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||
}
|
||||
|
||||
func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error {
|
||||
computeClient, err := openstack.NewComputeV2(c.pClient, c.opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating compute client during poweroffvirtualmachine: %v", err)
|
||||
}
|
||||
uuid, err := c.checkOrGetUUID(vm.Spec.VirtualMachineName)
|
||||
serverUUID, err := c.checkOrGetUUID(vm.Spec.VirtualMachineName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -352,13 +362,12 @@ func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) erro
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return startstop.Stop(computeClient, uuid).ExtractErr()
|
||||
return startstop.Stop(c.computeClient, serverUUID).ExtractErr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error) {
|
||||
|
||||
s, err := c.findVM(vm.Spec.VirtualMachineName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -376,7 +385,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
||||
var boolTrue = true
|
||||
vmObj, err := c.findVM(vm.Spec.VirtualMachineName)
|
||||
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()
|
||||
@ -396,7 +405,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
||||
|
||||
newVM := &kubevirt.VirtualMachine{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: vm.Spec.VirtualMachineName,
|
||||
Name: vm.Status.ImportedVirtualMachineName,
|
||||
Namespace: vm.Namespace,
|
||||
},
|
||||
}
|
||||
@ -413,7 +422,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
||||
Template: &kubevirt.VirtualMachineInstanceTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"harvesterhci.io/vmName": vm.Spec.VirtualMachineName,
|
||||
"harvesterhci.io/vmName": vm.Status.ImportedVirtualMachineName,
|
||||
},
|
||||
},
|
||||
Spec: kubevirt.VirtualMachineInstanceSpec{
|
||||
@ -722,6 +731,28 @@ func generateNetworkInfo(info map[string]interface{}) ([]networkInfo, error) {
|
||||
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.
|
||||
func writeRawImageFile(name string, src io.ReadCloser) error {
|
||||
dst, err := os.Create(name)
|
||||
|
@ -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))
|
||||
|
||||
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)
|
||||
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)
|
||||
@ -186,6 +186,17 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
||||
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)
|
||||
err = lease.DownloadFile(c.ctx, exportPath, i, soap.DefaultDownload)
|
||||
if err != nil {
|
||||
@ -196,6 +207,17 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e
|
||||
DiskSize: i.Size,
|
||||
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)
|
||||
err = qemu.ConvertVMDKtoRAW(sourceFile, destFile)
|
||||
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
|
||||
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 {
|
||||
vmObj, err := c.findVM(vm.Spec.Folder, vm.Spec.VirtualMachineName)
|
||||
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)
|
||||
@ -278,9 +300,10 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error quering vm in GenerateVirtualMachine: %v", err)
|
||||
}
|
||||
|
||||
newVM := &kubevirt.VirtualMachine{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: vm.Spec.VirtualMachineName,
|
||||
Name: vm.Status.ImportedVirtualMachineName,
|
||||
Namespace: vm.Namespace,
|
||||
},
|
||||
}
|
||||
@ -300,7 +323,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
|
||||
Template: &kubevirt.VirtualMachineInstanceTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"harvesterhci.io/vmName": vm.Spec.VirtualMachineName,
|
||||
"harvesterhci.io/vmName": vm.Status.ImportedVirtualMachineName,
|
||||
},
|
||||
},
|
||||
Spec: kubevirt.VirtualMachineInstanceSpec{
|
||||
@ -478,3 +501,12 @@ func adapterType(deviceID string) kubevirt.DiskBus {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
if vmObj.Status.Status == migration.VirtualMachineInvalid {
|
||||
if vmObj.Status.Status == migration.VirtualMachineImportInvalid {
|
||||
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())
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user