Skip to content
Snippets Groups Projects
Commit 8c1f7902 authored by Florent Gluck's avatar Florent Gluck
Browse files

Updated VM import/export to be simpler and more robust

parent f01c77e1
Branches
No related tags found
No related merge requests found
......@@ -8,33 +8,31 @@ import (
)
const (
virtcopyinBinary = "virt-copy-in"
virtTarInBinary = "virt-tar-in"
)
// Check virt-copy-in is available.
func CheckVirtCopyIn() error {
output, err := exec.Command(virtcopyinBinary, "--version").Output()
// Checks virt-tar-in is available.
func CheckVirtTarIn() error {
output, err := exec.Command(virtTarInBinary, "--version").Output()
if err != nil {
return errors.New(virtcopyinBinary+" is required but not found. On Ubuntu/Debian, it can be installed with \"sudo apt-get install guestfish\".")
return errors.New(virtTarInBinary+" is required but not found. On Ubuntu/Debian, it can be installed with \"sudo apt-get install guestfish\".")
}
out := string(output)
lines := strings.Split(out, "\n")
fields := strings.Split(lines[0], " ")
if len(fields) < 2 {
return errors.New("Failed extracting "+virtcopyinBinary+" version number!")
return errors.New("Failed extracting "+virtTarInBinary+" version number!")
}
cmd := fields[0]
if cmd != virtcopyinBinary {
return errors.New(virtcopyinBinary+" is required, but not found.")
if cmd != virtTarInBinary {
return errors.New(virtTarInBinary+" is required, but not found.")
}
return nil
}
// Creates a virt-copy-in command that recursively copy a directory on the host (localDir)
// into a directory on the VM's filesystem (vmDir).
// Note: both directories must exist, otherwise the command will fail.
func CopyToVM(vmDiskFile, localDir, vmDir string) error {
cmd := exec.Command(virtcopyinBinary, "-a", vmDiskFile, localDir, vmDir)
// Runs virt-tar-in which unarchive a local archive into a directory (vmDir) inside the VM disk.
func CopyToVM(vmDiskFile, tarFile, vmDir string) error {
cmd := exec.Command(virtTarInBinary, "-a", vmDiskFile, tarFile, vmDir)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
......
......@@ -8,33 +8,34 @@ import (
)
const (
virtcopyoutBinary = "virt-copy-out"
virtTarOutBinary = "virt-tar-out"
)
// Check virt-copy-out is available.
// Checks virt-tar-out is available.
func CheckVirtCopyOut() error {
output, err := exec.Command(virtcopyoutBinary, "--version").Output()
output, err := exec.Command(virtTarOutBinary, "--version").Output()
if err != nil {
return errors.New(virtcopyoutBinary+" is required but not found. On Ubuntu/Debian, it can be installed with \"sudo apt-get install guestfish\".")
return errors.New(virtTarOutBinary+" is required but not found. On Ubuntu/Debian, it can be installed with \"sudo apt-get install guestfish\".")
}
out := string(output)
lines := strings.Split(out, "\n")
fields := strings.Split(lines[0], " ")
if len(fields) < 2 {
return errors.New("Failed extracting "+virtcopyoutBinary+" version number!")
return errors.New("Failed extracting "+virtTarOutBinary+" version number!")
}
cmd := fields[0]
if cmd != virtcopyoutBinary {
return errors.New(virtcopyoutBinary+" is required, but not found.")
if cmd != virtTarOutBinary {
return errors.New(virtTarOutBinary+" is required, but not found.")
}
return nil
}
// Creates a virt-copy-out command that recursively copies a directory from the VM's
// filesystem (vmDir) into a directory on the host (localDir).
// Note: both directories must exist, otherwise the command will fail.
func CopyFromVM(vmDiskFile, vmDir, localDir string) error {
cmd := exec.Command(virtcopyoutBinary, "-a", vmDiskFile, vmDir, localDir)
// Runs virt-tar-in which unarchive a local archive into a directory (vmDir) inside the VM disk.
// Runs virt-copy-out which recursively extract a directory from the VM's
// filesystem (vmDir) into a tar archive.
func CopyFromVM(vmDiskFile, vmDir, tarFile string) error {
cmd := exec.Command(virtTarOutBinary, "-a", vmDiskFile, vmDir, tarFile)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
......
......@@ -9,7 +9,6 @@ import (
"nexus-server/caps"
"nexus-server/users"
"nexus-server/paths"
"nexus-server/utils"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/go-playground/validator/v10"
......@@ -445,32 +444,16 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Creates a unique temporary directory in which the VM's dir will be recursively copied.
// Creates a unique tar filename.
tmpDir := paths.GetInstance().TmpDir
uniqTmpDir := filepath.Join(tmpDir, "exportdir_"+vm.ID.String())
if err := os.Mkdir(uniqTmpDir, 0750); err != nil {
log.Error("Failed creating tmp dir for VM dir copy: "+err.Error())
return echo.NewHTTPError(http.StatusInternalServerError, "Failed creating tmp dir for VM dir copy")
}
defer func(dir string) {
if err := os.RemoveAll(dir); err != nil {
log.Error("Failed removing tmp dir for VM dir copy: "+err.Error())
}
}(uniqTmpDir)
// Extracts VM's p.Dir directory inside uniqTmpDir directory on the host.
if err := r.vms.ExportVMFiles(vm, p.Dir, uniqTmpDir); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Failed reading VM's dir")
}
// Creates a tar archive from the extracted VM directory.
tarFile := filepath.Join(tmpDir, "exportdir_"+vm.ID.String()+".tar")
if err := utils.TarDir(uniqTmpDir, tarFile); err != nil {
log.Error("Failed creating archive \""+tarFile+"\": "+err.Error())
return echo.NewHTTPError(http.StatusInternalServerError, "Failed creating VM's dir archive")
// Extracts VM's p.Dir directory into tarFile on the host.
if err := r.vms.ExportVMFiles(vm, p.Dir, tarFile); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Failed extracting VM's dir")
}
// Sends the archive back to the client and once done, deletes it.
// Sends the archive back to the client and once completed, deletes it.
defer func(file string) {
if err := os.Remove(file); err != nil {
log.Error("Failed removing archive of extracted VM dir: "+err.Error())
......@@ -489,7 +472,6 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(c echo.Context, vm *vms.VM) error {
// Retrieves the various client arguments.
vmDir := c.FormValue("vmDir")
localDir := c.FormValue("localDir")
// Retrieves the tar archive (uploadedTarFile).
tarFile, err := c.FormFile("file")
......@@ -521,24 +503,9 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
// Extracts the archive into tmpExtractDir.
tmpExtractDir := filepath.Join(tmpDir, "upload_"+uuid.String())
if err := os.Mkdir(tmpExtractDir, 0750); err != nil {
msg := "ImportFilesToVM: failed creating dir: "+err.Error()
log.Error(msg)
return echo.NewHTTPError(http.StatusInternalServerError, msg)
}
defer os.RemoveAll(tmpExtractDir)
if err := utils.Untar(uploadedTarFile, tmpExtractDir); err != nil {
msg := "ImportFilesToVM: failed unarchiving tarball: "+err.Error()
log.Error(msg)
return echo.NewHTTPError(http.StatusInternalServerError, msg)
}
// Copy the archive's files into the VM
// IMPORTANT: check the VM is NOT running!
if err = r.vms.ImportFilesToVM(vm, filepath.Join(tmpExtractDir, localDir), vmDir); err != nil {
if err = r.vms.ImportFilesToVM(vm, uploadedTarFile, vmDir); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
......
......@@ -364,10 +364,10 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string)
return nil
}
// Exports a VM's directory and its subdirectories into the specified directory on the host.
func (vms *VMs)ExportVMFiles(vm *VM, vmDir, hostDir string) error {
// Exports a VM's directory and its subdirectories into a tar archive on the host.
func (vms *VMs)ExportVMFiles(vm *VM, vmDir, tarFile string) error {
vmDisk := vm.getDiskPath()
return exec.CopyFromVM(vmDisk, vmDir, hostDir)
return exec.CopyFromVM(vmDisk, vmDir, tarFile)
}
// Import files into a VM's filesystem, in a specified directory.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment