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

Ongoing work on importfiles route.

Reworked exec package a bit.
parent 745e0215
Branches
No related tags found
No related merge requests found
package exec package exec
import ( import (
"fmt"
"os/exec" "os/exec"
"strings" "strings"
"errors" "errors"
"strconv" "strconv"
"nexus-server/logger"
) )
const ( const (
...@@ -12,6 +14,8 @@ const ( ...@@ -12,6 +14,8 @@ const (
qemuimgBinaryMinVersion = 4 qemuimgBinaryMinVersion = 4
) )
var log = logger.GetInstance()
// Check qemu-img is available and the version is recent enough. // Check qemu-img is available and the version is recent enough.
func CheckQemuImg() error { func CheckQemuImg() error {
output, err := exec.Command(qemuimgBinary, "--version").Output() output, err := exec.Command(qemuimgBinary, "--version").Output()
...@@ -36,11 +40,25 @@ func CheckQemuImg() error { ...@@ -36,11 +40,25 @@ func CheckQemuImg() error {
} }
// Creates a qemu-img command that creates a disk image (VM) baked by a base image (template). // Creates a qemu-img command that creates a disk image (VM) baked by a base image (template).
func NewQemuImgCreate(templateDiskFile string, vmDiskFile string) *exec.Cmd { func QemuImgCreate(templateDiskFile, vmDiskFile string) error {
return exec.Command(qemuimgBinary, "create", "-F", "qcow2", "-b", templateDiskFile, "-f", "qcow2", vmDiskFile) cmd := exec.Command(qemuimgBinary, "create", "-F", "qcow2", "-b", templateDiskFile, "-f", "qcow2", vmDiskFile)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
log.Error("Failed creating VM disk image: "+output)
return errors.New("Failed creating VM disk image")
}
return nil
} }
// Creates a qemu-img command that rebase an overlay image in order to become a standalone image. // Creates a qemu-img command that rebase an overlay image in order to become a standalone image.
func NewQemuImgRebase(overlayFile string) *exec.Cmd { func QemuImgRebase(overlayFile string) error {
return exec.Command(qemuimgBinary, "rebase", "-b", "", overlayFile) cmd := exec.Command(qemuimgBinary, "rebase", "-b", "", overlayFile)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
log.Error("Failed rebasing template disk from VM: "+": "+output)
return errors.New("Failed rebasing template disk from VM")
}
return nil
} }
...@@ -38,7 +38,7 @@ func CheckQemuSystem() error { ...@@ -38,7 +38,7 @@ func CheckQemuSystem() error {
// Creates a qemu-system command. // Creates a qemu-system command.
// The nic argument must be either "none" or "user". // The nic argument must be either "none" or "user".
func NewQemuSystem(qgaSock string, cpus int, ram int, nic string, diskFile string, spicePort int, secretPwdFile string, certDir string) (*exec.Cmd, error) { func NewQemuSystem(qgaSock string, cpus, ram int, nic, diskFile string, spicePort int, secretPwdFile, certDir string) (*exec.Cmd, error) {
nicArgs := "none" nicArgs := "none"
if nic == "user" { if nic == "user" {
addr, err := utils.RandMacAddress() addr, err := utils.RandMacAddress()
......
package exec package exec
import ( import (
"fmt"
"os/exec" "os/exec"
"strings" "strings"
"errors" "errors"
...@@ -29,9 +30,17 @@ func CheckVirtCopyOut() error { ...@@ -29,9 +30,17 @@ func CheckVirtCopyOut() error {
return nil return nil
} }
// Creates a virt-copy-out command that recursively extracts (i.e. copy out) a directory from a VM's disk. // Creates a virt-copy-out command that recursively copies a directory from the VM's
// Note: localDir must already exist, otherwise the command will fail. // filesystem (vmDir) into a directory on the host (localDir).
func NewVirtCopyOut(vmDiskFile string, vmDir string, localDir string) *exec.Cmd { // Note: both directories must exist, otherwise the command will fail.
// mkdir tmp && virt-copy-out -a disk.qcow /home tmp func CopyFromVM(vmDiskFile, vmDir, localDir string) error {
return exec.Command(virtcopyoutBinary, "-a", vmDiskFile, vmDir, localDir) cmd := exec.Command(virtcopyoutBinary, "-a", vmDiskFile, vmDir, localDir)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
msg := "Failed reading \""+vmDir+"\" in qcow ("+vmDiskFile+"): "+output
log.Error(msg)
return errors.New(msg)
}
return nil
} }
...@@ -37,6 +37,9 @@ func main() { ...@@ -37,6 +37,9 @@ func main() {
if err := exec.CheckVirtCopyOut(); err != nil { if err := exec.CheckVirtCopyOut(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if err := exec.CheckVirtCopyIn(); err != nil {
log.Fatal(err)
}
usage := func() { usage := func() {
fmt.Println("Usage of "+path.Base(os.Args[0])+":") fmt.Println("Usage of "+path.Base(os.Args[0])+":")
......
...@@ -3,7 +3,6 @@ package router ...@@ -3,7 +3,6 @@ package router
import ( import (
"io" "io"
"os" "os"
"fmt"
"net/http" "net/http"
"path/filepath" "path/filepath"
"nexus-server/vms" "nexus-server/vms"
...@@ -11,7 +10,6 @@ import ( ...@@ -11,7 +10,6 @@ import (
"nexus-server/users" "nexus-server/users"
"nexus-server/paths" "nexus-server/paths"
"nexus-server/utils" "nexus-server/utils"
"nexus-server/exec"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
...@@ -355,7 +353,7 @@ func (r *RouterVMs)DeleteVMAccessForUser(c echo.Context) error { ...@@ -355,7 +353,7 @@ func (r *RouterVMs)DeleteVMAccessForUser(c echo.Context) error {
// - the logged user has the userCapabilityAny capability. // - the logged user has the userCapabilityAny capability.
// - the VM access for the logged user matches the vmAccessCapability capability. // - the VM access for the logged user matches the vmAccessCapability capability.
// Also, VMs for which cond is false are filtered out. // Also, VMs for which cond is false are filtered out.
func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny string, vmAccessCapability string, cond vms.VMKeeperFn) error { func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny, vmAccessCapability string, cond vms.VMKeeperFn) error {
// Retrieves logged user from context. // Retrieves logged user from context.
user, err := getLoggedUser(r.users, c) user, err := getLoggedUser(r.users, c)
if err != nil { if err != nil {
...@@ -384,7 +382,7 @@ func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny string, vmAc ...@@ -384,7 +382,7 @@ func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny string, vmAc
// Helper function that performs an action on a VM based either on: // Helper function that performs an action on a VM based either on:
// - the logged user has the userCapabilityAny capability. // - the logged user has the userCapabilityAny capability.
// - the VM access for the logged user matches the vmAccessCapability capability. // - the VM access for the logged user matches the vmAccessCapability capability.
func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny string, vmAccessCapability string, action vmActionFn) error { func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny, vmAccessCapability string, action vmActionFn) error {
// Retrieves the VM on which to perform the action. // Retrieves the VM on which to perform the action.
id, err := uuid.Parse(c.Param("id")) id, err := uuid.Parse(c.Param("id"))
if err != nil { if err != nil {
...@@ -449,13 +447,8 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error { ...@@ -449,13 +447,8 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error {
} }
}(uniqTmpDir) }(uniqTmpDir)
// Extracts the VM's directory inside the temporary directory above. // Extracts VM's p.Dir directory inside uniqTmpDir directory on the host.
vmDisk := vm.GetDiskPath() if err := r.vms.ExportVMFiles(vm, p.Dir, uniqTmpDir); err != nil {
cmd := exec.NewVirtCopyOut(vmDisk, p.Dir, uniqTmpDir)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
log.Error("Failed reading \""+p.Dir+"\" in qcow "+vmDisk+": "+output)
return echo.NewHTTPError(http.StatusBadRequest, "Failed reading VM's dir") return echo.NewHTTPError(http.StatusBadRequest, "Failed reading VM's dir")
} }
...@@ -483,11 +476,10 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error { ...@@ -483,11 +476,10 @@ func (r *RouterVMs)ExportVMDir(c echo.Context) error {
// curl --cacert ca.pem -X POST https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/importfiles -H 'Content-Type: multipart/form-data' -F dir="/home/nexus" -F file=@files.tar -H "Authorization: Bearer <AccessToken>" // curl --cacert ca.pem -X POST https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/importfiles -H 'Content-Type: multipart/form-data' -F dir="/home/nexus" -F file=@files.tar -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { 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 { return r.performVMAction(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(c echo.Context, vm *vms.VM) error {
// Retrieve "dir" argument // Retrieve the "dir" argument
dir := c.FormValue("dir") vmDir := c.FormValue("dir")
log.Info("ImportFilesToVM: dir=",dir)
// Retrieve the tar archive: uploadedFile // Retrieve the tar archive (uploadedTarFile)
tarFile, err := c.FormFile("file") tarFile, err := c.FormFile("file")
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
...@@ -505,22 +497,37 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error { ...@@ -505,22 +497,37 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
log.Error("Failed creating random UUID: "+err.Error()) log.Error("Failed creating random UUID: "+err.Error())
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
uploadedFile := filepath.Join(tmpDir, "upload_"+uuid.String()+".tar") uploadedTarFile := filepath.Join(tmpDir, "upload_"+uuid.String()+".tar")
dst, err := os.Create(uploadedFile) dst, err := os.Create(uploadedTarFile)
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
defer dst.Close() defer dst.Close()
//defer os.Remove(uploadedFile) //defer os.Remove(uploadedTarFile)
if _, err = io.Copy(dst, src); err != nil { if _, err = io.Copy(dst, src); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
} }
// Extract the archive // Extract the archive into tmpExtractDir
tmpExtractDir := filepath.Join(tmpDir, "extract_"+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)
}
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 // Copy the archive's files into the VM
// IMPORTANT: check the VM is NOT running! // IMPORTANT: check the VM is NOT running!
if err = r.vms.ImportFilesToVM(vm, tmpExtractDir, vmDir); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Remove the files and the archive // Remove the files and the archive
......
...@@ -50,11 +50,11 @@ func RandInit() { ...@@ -50,11 +50,11 @@ func RandInit() {
} }
// Returns an int in the range [min,max] (both inclusive). // Returns an int in the range [min,max] (both inclusive).
func Rand(min int, max int) int { func Rand(min, max int) int {
return rand.Intn(max-min+1)+min return rand.Intn(max-min+1)+min
} }
func CopyFiles(source string, dest string) error { func CopyFiles(source, dest string) error {
src, err := os.Open(source) src, err := os.Open(source)
if err != nil { if err != nil {
return err return err
...@@ -115,10 +115,11 @@ func IsPortAvailable(port int) bool { ...@@ -115,10 +115,11 @@ func IsPortAvailable(port int) bool {
return true return true
} }
// Creates the destFile .tar archive of srcDir and all its files and subdirectories. // Creates the destTarball .tar archive of srcDir and all its files and subdirectories.
// This implementation fails if a symlink points to an absolute path that doesn't exist on the host // This implementation fails if a symlink points to an absolute path that doesn't exist on the host.
func TarDir(srcDir, destFile string) error { // Source code taken from: https://golangdocs.com/tar-gzip-in-golang
tarFile, err := os.Create(destFile) func TarDir(srcDir, destTarball string) error {
tarFile, err := os.Create(destTarball)
if err != nil { if err != nil {
return err return err
} }
...@@ -174,3 +175,61 @@ func TarDir(srcDir, destFile string) error { ...@@ -174,3 +175,61 @@ func TarDir(srcDir, destFile string) error {
return err return err
}) })
} }
// Unarchive srcTarball into destDir.
func Untar(srcTarball, destDir string) error {
reader, err := os.Open(srcTarball)
if err != nil {
return err
}
defer reader.Close()
// First pass: reads and creates directories first.
tarDirReader := tar.NewReader(reader)
for {
header, err := tarDirReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
path := filepath.Join(destDir, header.Name)
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(path, info.Mode()); err != nil {
return err
}
continue
}
}
// Seeks to the beginning of the file.
whence := 0
reader.Seek(0, whence)
// Second pass: reads and creates files.
tarFileReader := tar.NewReader(reader)
for {
header, err := tarFileReader.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
path := filepath.Join(destDir, header.Name)
info := header.FileInfo()
if !info.IsDir() {
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, tarFileReader)
if err != nil {
return err
}
}
}
return nil
}
...@@ -2,7 +2,6 @@ package vms ...@@ -2,7 +2,6 @@ package vms
import( import(
"os" "os"
"fmt"
"errors" "errors"
"path/filepath" "path/filepath"
"encoding/json" "encoding/json"
...@@ -30,7 +29,7 @@ const( ...@@ -30,7 +29,7 @@ const(
var dummyTemplate = Template{} var dummyTemplate = Template{}
// Creates a template from a VM's disk. // Creates a template from a VM's disk.
func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Template, error) { func NewTemplateFromVM(name, owner, access string, vm *VM) (*Template, error) {
// Marks the VM to copy from as being busy. // Marks the VM to copy from as being busy.
if err := vms.setDiskBusy(vm); err != nil { if err := vms.setDiskBusy(vm); err != nil {
return nil, errors.New("Failed setting disk busy flag during template creation: "+err.Error()) return nil, errors.New("Failed setting disk busy flag during template creation: "+err.Error())
...@@ -47,19 +46,17 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ ...@@ -47,19 +46,17 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ
// Creates the template directory. // Creates the template directory.
templatesDir := GetTemplatesInstance().getDir() templatesDir := GetTemplatesInstance().getDir()
templateDir := filepath.Join(templatesDir, template.ID.String()) templateDir := filepath.Join(templatesDir, template.ID.String())
// log.Info("(0) create tpl dir: "+templateDir)
if err := os.Mkdir(templateDir, 0750); err != nil { if err := os.Mkdir(templateDir, 0750); err != nil {
log.Error("Failed creating template dir: "+err.Error()) msg := "Failed creating template dir: "+err.Error()
return nil, errors.New("Failed creating template dir: "+err.Error()) log.Error(msg)
return nil, errors.New(msg)
} }
// 1) Copy VM's overlay file into a new one (in the VM's directory!): // 1) Copy VM's overlay file into a new one (in the VM's directory!):
// cp disk.qcow disk.tpl.qcow // cp disk.qcow disk.tpl.qcow
vmDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile)) vmDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile))
tplDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, "/disk.tpl.qcow")) tplDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, "/disk.tpl.qcow"))
// log.Info("(1) copy: "+vmDiskFile+" to "+tplDiskFile) if err := utils.CopyFiles(vmDiskFile, tplDiskFile); err != nil {
err = utils.CopyFiles(vmDiskFile, tplDiskFile)
if err != nil {
template.delete() template.delete()
GetVMsInstance().updateVM(vm) GetVMsInstance().updateVM(vm)
log.Error("Failed copying VM overlay disk: "+err.Error()) log.Error("Failed copying VM overlay disk: "+err.Error())
...@@ -68,26 +65,17 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ ...@@ -68,26 +65,17 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ
// 2) Rebase the new template overlay in order to become a standalone disk file: // 2) Rebase the new template overlay in order to become a standalone disk file:
// qemu-img rebase -b "" disk.tpl.qcow // qemu-img rebase -b "" disk.tpl.qcow
cmd := exec.NewQemuImgRebase(tplDiskFile) if err := exec.QemuImgRebase(tplDiskFile); err != nil {
// log.Info("(2) rebase: "+cmd.String())
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
log.Debug("(2) rebase: "+cmd.String())
template.delete() template.delete()
os.Remove(tplDiskFile) os.Remove(tplDiskFile)
GetVMsInstance().updateVM(vm) GetVMsInstance().updateVM(vm)
output := fmt.Sprintf("[%s]", stdoutStderr) return nil, err
log.Error("Failed rebasing template disk from VM: "+": "+output)
return nil, errors.New("Failed rebasing template disk from VM")
} }
// 3) Move the template disk file into the template directory: // 3) Move the template disk file into the template directory:
// mv disk.tpl.qcow new_template_dir/ // mv disk.tpl.qcow new_template_dir/
destFile, _ := filepath.Abs(filepath.Join(templateDir, templateDiskFile)) destFile, _ := filepath.Abs(filepath.Join(templateDir, templateDiskFile))
// log.Info("(3) move "+tplDiskFile+" to "+destFile) if err := os.Rename(tplDiskFile, destFile); err != nil {
err = os.Rename(tplDiskFile, destFile)
if err != nil {
log.Debug("(3) move "+tplDiskFile+" -> "+destFile)
template.delete() template.delete()
os.Remove(tplDiskFile) os.Remove(tplDiskFile)
GetVMsInstance().updateVM(vm) GetVMsInstance().updateVM(vm)
...@@ -104,7 +92,7 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ ...@@ -104,7 +92,7 @@ func NewTemplateFromVM(name string, owner string, access string, vm *VM) (*Templ
} }
// Creates a template from a qcow file. // Creates a template from a qcow file.
func NewTemplateFromQCOW(name string, owner string, access string, qcowFile string) (*Template, error) { func NewTemplateFromQCOW(name, owner, access, qcowFile string) (*Template, error) {
// Creates the template. // Creates the template.
template, err := newTemplate(name, owner, access) template, err := newTemplate(name, owner, access)
if err != nil { if err != nil {
...@@ -150,7 +138,7 @@ func newEmptyTemplate() *Template { ...@@ -150,7 +138,7 @@ func newEmptyTemplate() *Template {
} }
// Creates a template. // Creates a template.
func newTemplate(name string, owner string, access string) (*Template, error) { func newTemplate(name, owner, access string) (*Template, error) {
id, err := uuid.NewRandom() id, err := uuid.NewRandom()
if err != nil { if err != nil {
log.Error("Failed creating template: "+err.Error()) log.Error("Failed creating template: "+err.Error())
......
...@@ -2,7 +2,6 @@ package vms ...@@ -2,7 +2,6 @@ package vms
import ( import (
"os" "os"
"fmt"
"sync" "sync"
"path" "path"
"errors" "errors"
...@@ -31,10 +30,11 @@ type ( ...@@ -31,10 +30,11 @@ type (
TemplateID uuid.UUID `json:"templateID" validate:"required"` TemplateID uuid.UUID `json:"templateID" validate:"required"`
Access map[string]caps.Capabilities `json:"access" validate:"required"` Access map[string]caps.Capabilities `json:"access" validate:"required"`
// None of the fields below are serialized to disk.
dir string // VM directory dir string // VM directory
qgaSock string // QEMU Guest Agent (QGA) UNIX socket qgaSock string // QEMU Guest Agent (QGA) UNIX socket
Run runStates Run runStates
DiskBusy bool // If true the VM's disk is busy (cannot be modified or deleted). DiskBusy bool // If true the VM's disk is busy (cannot be modified or deleted)
mutex *sync.Mutex mutex *sync.Mutex
} }
...@@ -67,7 +67,7 @@ const ( ...@@ -67,7 +67,7 @@ const (
var dummyVM = VM{} var dummyVM = VM{}
// Creates a VM. // Creates a VM.
func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus int, ram int, nic NicType, templateID uuid.UUID) (*VM, error) { func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus, ram int, nic NicType, templateID uuid.UUID) (*VM, error) {
vmID, err := uuid.NewRandom() vmID, err := uuid.NewRandom()
if err != nil { if err != nil {
log.Error("Failed creating VM: "+err.Error()) log.Error("Failed creating VM: "+err.Error())
...@@ -92,7 +92,7 @@ func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus int, r ...@@ -92,7 +92,7 @@ func NewVM(creatorEmail string, caps caps.Capabilities, name string, cpus int, r
return vm, nil return vm, nil
} }
func (vm *VM)GetDiskPath() string { func (vm *VM)getDiskPath() string {
vmDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile)) vmDiskFile, _ := filepath.Abs(filepath.Join(vm.dir, vmDiskFile))
return vmDiskFile return vmDiskFile
} }
...@@ -200,13 +200,10 @@ func (vm *VM)writeFiles() error { ...@@ -200,13 +200,10 @@ func (vm *VM)writeFiles() error {
// Creates vmDiskFile as an overlay on top of the template disk. // Creates vmDiskFile as an overlay on top of the template disk.
// NOTE: template and output file must be both specified as absolute paths. // NOTE: template and output file must be both specified as absolute paths.
templateDiskFile, _ := filepath.Abs(filepath.Join(GetTemplatesInstance().getDir(), template.ID.String(), templateDiskFile)) templateDiskFile, _ := filepath.Abs(filepath.Join(GetTemplatesInstance().getDir(), template.ID.String(), templateDiskFile))
cmd := exec.NewQemuImgCreate(templateDiskFile, vm.GetDiskPath())
stdoutStderr, err := cmd.CombinedOutput() if err := exec.QemuImgCreate(templateDiskFile, vm.getDiskPath()); err != nil {
if err != nil {
vm.delete() vm.delete()
output := fmt.Sprintf("[%s]", stdoutStderr) return err
log.Error("Failed creating VM disk image: "+output)
return errors.New("Failed creating VM disk image")
} }
return nil return nil
...@@ -329,10 +326,6 @@ func (vm *VM)stop() error { ...@@ -329,10 +326,6 @@ func (vm *VM)stop() error {
return errors.New("Failed stopping VM: VM is not running") return errors.New("Failed stopping VM: VM is not running")
} }
if vm.DiskBusy {
return errors.New("Failed stopping VM: VM disk is busy")
}
// Sends a SIGINT signal to terminate the QEMU process. // Sends a SIGINT signal to terminate the QEMU process.
// Note that QEMU terminates with status code 0 in this case (i.e. no error). // Note that QEMU terminates with status code 0 in this case (i.e. no error).
if err := syscall.Kill(vm.Run.Pid, syscall.SIGINT); err != nil { if err := syscall.Kill(vm.Run.Pid, syscall.SIGINT); err != nil {
...@@ -344,8 +337,8 @@ func (vm *VM)stop() error { ...@@ -344,8 +337,8 @@ func (vm *VM)stop() error {
} }
// Gracefully stops a running VM. // Gracefully stops a running VM.
// Uses QMP commands to talk to the VM, which means QEMU Guest Agent must be running in the VM // Uses QGA commands to talk to the VM, which means QEMU Guest Agent must be running
// otherwise the VM won't shutdown. // in the VM, otherwise the VM won't shutdown.
func (vm *VM)shutdown() error { func (vm *VM)shutdown() error {
if !vm.IsRunning() { if !vm.IsRunning() {
return errors.New("Shutdown failed: VM is not running") return errors.New("Shutdown failed: VM is not running")
...@@ -355,7 +348,7 @@ func (vm *VM)shutdown() error { ...@@ -355,7 +348,7 @@ func (vm *VM)shutdown() error {
return errors.New("Shutdown failed: VM disk is busy") return errors.New("Shutdown failed: VM disk is busy")
} }
// Sends a QMP command to order the VM to shutdown. // Sends a QGA command to order the VM to shutdown.
con := qga.New() con := qga.New()
if err := con.Open(vm.qgaSock); err != nil { if err := con.Open(vm.qgaSock); err != nil {
log.Error("VM shutdown failed (open): "+err.Error()) log.Error("VM shutdown failed (open): "+err.Error())
...@@ -377,7 +370,7 @@ func (vm *VM)shutdown() error { ...@@ -377,7 +370,7 @@ func (vm *VM)shutdown() error {
} }
// Executes the VM in QEMU using the specified spice port and password. // Executes the VM in QEMU using the specified spice port and password.
func (vm *VM)runQEMU(port int, pwd string, pwdFile string, endofExecFn endOfExecCallback) error { func (vm *VM)runQEMU(port int, pwd, pwdFile string, endofExecFn endOfExecCallback) error {
pkiDir := paths.GetInstance().NexusPkiDir pkiDir := paths.GetInstance().NexusPkiDir
cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.Cpus, vm.Ram, string(vm.Nic), filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir) cmd, err := exec.NewQemuSystem(vm.qgaSock, vm.Cpus, vm.Ram, string(vm.Nic), filepath.Join(vm.dir, vmDiskFile), port, pwdFile, pkiDir)
if err != nil { if err != nil {
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"sync" "sync"
"errors" "errors"
"path/filepath" "path/filepath"
"nexus-server/exec"
"nexus-server/caps" "nexus-server/caps"
"nexus-server/paths" "nexus-server/paths"
"nexus-server/utils" "nexus-server/utils"
...@@ -259,7 +260,7 @@ func (vms *VMs)IsTemplateUsed(templateID string) bool { ...@@ -259,7 +260,7 @@ func (vms *VMs)IsTemplateUsed(templateID string) bool {
} }
// Edit a VM' specs: name, cpus, ram, nic // Edit a VM' specs: name, cpus, ram, nic
func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus int, ram int, nic NicType) error { func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus, ram int, nic NicType) error {
// Retrieves the VM to be edited. // Retrieves the VM to be edited.
vm, err := vms.GetVM(vmID) vm, err := vms.GetVM(vmID)
if err != nil { if err != nil {
...@@ -299,7 +300,7 @@ func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus int, ram int, nic NicTyp ...@@ -299,7 +300,7 @@ func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus int, ram int, nic NicTyp
// Set a VM's Access for a given user (email). // Set a VM's Access for a given user (email).
// loggedUserEmail is the email of the currently logged user // loggedUserEmail is the email of the currently logged user
// userMail is the email of the user for which to modify the access // userMail is the email of the user for which to modify the access
func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail string, newAccess caps.Capabilities) error { func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string, newAccess caps.Capabilities) error {
if !caps.AreVMAccessCapsValid(newAccess) { if !caps.AreVMAccessCapsValid(newAccess) {
return errors.New("Invalid capability") return errors.New("Invalid capability")
} }
...@@ -334,7 +335,7 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail str ...@@ -334,7 +335,7 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail str
// Remove a VM's Access for a given user (email). // Remove a VM's Access for a given user (email).
// loggedUserEmail is the email of the currently logged user // loggedUserEmail is the email of the currently logged user
// userMail is the email of the user for which to remove the access // userMail is the email of the user for which to remove the access
func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail string) error { func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string) error {
// Retrieves the VM for which the access caps must be changed. // Retrieves the VM for which the access caps must be changed.
vm, err := vms.GetVM(vmID) vm, err := vms.GetVM(vmID)
if err != nil { if err != nil {
...@@ -363,6 +364,25 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail ...@@ -363,6 +364,25 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail string, userEmail
return nil 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 {
vmDisk := vm.getDiskPath()
return exec.CopyFromVM(vmDisk, vmDir, hostDir)
}
// Import files into a VM's filesystem, in a specified directory.
func (vms *VMs)ImportFilesToVM(vm *VM, hostDir, vmDir string) error {
// Marks the VM to copy from as being busy.
if err := vms.setDiskBusy(vm); err != nil {
return errors.New("Failed setting disk busy flag during VM files import: "+err.Error())
}
// Clears the VM from being busy.
defer vms.clearDiskBusy(vm)
vmDisk := vm.getDiskPath()
return exec.CopyToVM(vmDisk, hostDir, vmDir)
}
// Marks a VM as "busy", meaning its disk file is being accessed for a possibly long time. // Marks a VM as "busy", meaning its disk file is being accessed for a possibly long time.
func (vms *VMs)setDiskBusy(vm *VM) error { func (vms *VMs)setDiskBusy(vm *VM) error {
vm.mutex.Lock() vm.mutex.Lock()
...@@ -377,7 +397,7 @@ func (vms *VMs)setDiskBusy(vm *VM) error { ...@@ -377,7 +397,7 @@ func (vms *VMs)setDiskBusy(vm *VM) error {
vms.rwlock.Lock() vms.rwlock.Lock()
defer vms.rwlock.Unlock() defer vms.rwlock.Unlock()
vms.updateVM(vm) vms.updateVMMap(vm)
return nil return nil
} }
...@@ -395,7 +415,7 @@ func (vms *VMs)clearDiskBusy(vm *VM) error { ...@@ -395,7 +415,7 @@ func (vms *VMs)clearDiskBusy(vm *VM) error {
vms.rwlock.Lock() vms.rwlock.Lock()
defer vms.rwlock.Unlock() defer vms.rwlock.Unlock()
vms.updateVM(vm) vms.updateVMMap(vm)
return nil return nil
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment