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:
Volker Theile 2025-02-04 07:42:42 +01:00 committed by GitHub
parent 51ae21800b
commit 29629c75f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 241 additions and 86 deletions

View File

@ -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"
)

View File

@ -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)
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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()

View File

@ -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)

View File

@ -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
}

View File

@ -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())
})
})