mirror of
https://github.com/harvester/vm-import-controller.git
synced 2025-06-03 01:44:51 +00:00
VM Import Controller uses too much memory during QCOW2 conversion phase
- Create the volume image using RAW disk format instead of QCOW2, so no conversion is required after downloading. This will reduce memory consumption. - Download and write the image file in chunks (32KiB by default), so the whole file doesn't need to be downloaded completely and stored in memory before it is written to disk. - Fix a variable name shadowing issue. - Improve logging. Related to: https://github.com/harvester/harvester/issues/6674 Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
parent
ea161c2fc1
commit
ebf60d5ec9
@ -17,11 +17,6 @@ func ConvertVMDKtoRAW(source, target string) error {
|
||||
return runCommand(defaultCommand, args...)
|
||||
}
|
||||
|
||||
func ConvertQCOW2toRAW(source, target string) error {
|
||||
args := []string{"convert", "-f", "qcow2", "-O", "raw", source, target}
|
||||
return runCommand(defaultCommand, args...)
|
||||
}
|
||||
|
||||
func createVMDK(path string, size string) error {
|
||||
args := []string{"create", "-f", "vmdk", path, size}
|
||||
return runCommand(defaultCommand, args...)
|
||||
|
@ -30,7 +30,6 @@ import (
|
||||
kubevirt "kubevirt.io/api/core/v1"
|
||||
|
||||
migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1"
|
||||
"github.com/harvester/vm-import-controller/pkg/qemu"
|
||||
"github.com/harvester/vm-import-controller/pkg/server"
|
||||
)
|
||||
|
||||
@ -193,27 +192,32 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||
return err
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp("/tmp", "openstack-image-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating tmp image directory: %v", err)
|
||||
}
|
||||
for vIndex, v := range vmObj.AttachedVolumes {
|
||||
imageName := fmt.Sprintf("import-controller-%s-%d", vm.Spec.VirtualMachineName, vIndex)
|
||||
|
||||
for i, v := range vmObj.AttachedVolumes {
|
||||
// create snapshot for volume
|
||||
snapInfo, err := snapshots.Create(c.storageClient, snapshots.CreateOpts{
|
||||
Name: fmt.Sprintf("import-controller-%v-%d", vm.Spec.VirtualMachineName, i),
|
||||
Name: imageName,
|
||||
VolumeID: v.ID,
|
||||
Force: true,
|
||||
}).Extract()
|
||||
|
||||
// snapshot creation is async, so call returns a 202 error when successful.
|
||||
// this is ignored
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"name": vm.Name,
|
||||
"namespace": vm.Namespace,
|
||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||
"snapshot.id": snapInfo.ID,
|
||||
"snapshot.name": snapInfo.Name,
|
||||
"snapshot.size": snapInfo.Size,
|
||||
}).Info("Waiting for snapshot to be available")
|
||||
|
||||
if err := snapshots.WaitForStatus(c.storageClient, snapInfo.ID, "available", pollingTimeout); err != nil {
|
||||
return fmt.Errorf("timeout waiting for snapshot %v to become available: %v", snapInfo.ID, err)
|
||||
return fmt.Errorf("timeout waiting for snapshot %s to be available: %v", snapInfo.ID, err)
|
||||
}
|
||||
|
||||
volObj, err := volumes.Create(c.storageClient, volumes.CreateOpts{
|
||||
@ -228,30 +232,41 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||
"name": vm.Name,
|
||||
"namespace": vm.Namespace,
|
||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||
"volume": volObj,
|
||||
}).Info("Attempting to create new image from volume")
|
||||
"volume.id": volObj.ID,
|
||||
"volume.createdAt": volObj.CreatedAt,
|
||||
"volume.snapshotID": volObj.SnapshotID,
|
||||
"volume.size": volObj.Size,
|
||||
"volume.status": volObj.Status,
|
||||
}).Info("Waiting for volume to be available")
|
||||
|
||||
if err := volumes.WaitForStatus(c.storageClient, volObj.ID, "available", pollingTimeout); err != nil {
|
||||
return fmt.Errorf("timeout waiting for volumes %v to become available: %v", volObj.ID, err)
|
||||
return fmt.Errorf("timeout waiting for volume %s to be available: %v", volObj.ID, err)
|
||||
}
|
||||
|
||||
volImage, err := volumeactions.UploadImage(c.storageClient, volObj.ID, volumeactions.UploadImageOpts{
|
||||
ImageName: fmt.Sprintf("import-controller-%s-%d", vm.Spec.VirtualMachineName, i),
|
||||
DiskFormat: "qcow2",
|
||||
ImageName: imageName,
|
||||
DiskFormat: "raw",
|
||||
}).Extract()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for image to be ready
|
||||
for i := 0; i < defaultCount; i++ {
|
||||
imgObj, err := images.Get(c.imageClient, volImage.ImageID).Extract()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking status of volume image: %v", err)
|
||||
return fmt.Errorf("error checking status of volume image %s: %v", volImage.ImageID, err)
|
||||
}
|
||||
if imgObj.Status == "active" {
|
||||
break
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"name": vm.Name,
|
||||
"namespace": vm.Namespace,
|
||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||
"image.id": imgObj.ID,
|
||||
"image.status": imgObj.Status,
|
||||
}).Info("Waiting for raw image to be available")
|
||||
time.Sleep(defaultInterval)
|
||||
}
|
||||
|
||||
@ -260,30 +275,19 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||
return err
|
||||
}
|
||||
|
||||
imageContents, err := io.ReadAll(contents)
|
||||
rawImageFileName := fmt.Sprintf("%s-%d.img", vmObj.Name, vIndex)
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"name": vm.Name,
|
||||
"namespace": vm.Namespace,
|
||||
"spec.virtualMachineName": vm.Spec.VirtualMachineName,
|
||||
"volume.imageID": volImage.ImageID,
|
||||
}).Info("Downloading raw image")
|
||||
err = writeRawImageFile(filepath.Join(server.TempDir(), rawImageFileName), contents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qcowFileName := filepath.Join(tmpDir, fmt.Sprintf("%s-%d", vm.Spec.VirtualMachineName, i))
|
||||
imgFile, err := os.Create(qcowFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating disk file: %v", err)
|
||||
}
|
||||
|
||||
_, err = imgFile.Write(imageContents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imgFile.Close()
|
||||
|
||||
// downloaded image is qcow2. Convert to raw file
|
||||
rawFileName := filepath.Join(server.TempDir(), fmt.Sprintf("%s-%d.img", vmObj.Name, i))
|
||||
err = qemu.ConvertQCOW2toRAW(qcowFileName, rawFileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting qcow2 to raw file: %v", err)
|
||||
}
|
||||
|
||||
if err := volumes.Delete(c.storageClient, volObj.ID, volumes.DeleteOpts{}).ExtractErr(); err != nil {
|
||||
return fmt.Errorf("error deleting volume %s: %v", volObj.ID, err)
|
||||
}
|
||||
@ -297,13 +301,14 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
|
||||
}
|
||||
|
||||
vm.Status.DiskImportStatus = append(vm.Status.DiskImportStatus, migration.DiskInfo{
|
||||
Name: fmt.Sprintf("%s-%d.img", vmObj.Name, i),
|
||||
Name: rawImageFileName,
|
||||
DiskSize: int64(volObj.Size),
|
||||
DiskLocalPath: server.TempDir(),
|
||||
BusType: kubevirt.DiskBusVirtio,
|
||||
})
|
||||
}
|
||||
return os.RemoveAll(tmpDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error {
|
||||
@ -690,3 +695,16 @@ func generateNetworkInfo(info map[string]interface{}) ([]networkInfo, error) {
|
||||
}
|
||||
return uniqueNetworks, 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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating raw image file: %v", err)
|
||||
}
|
||||
|
||||
defer dst.Close()
|
||||
|
||||
_, err = io.Copy(dst, src)
|
||||
return err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user