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

server:

default upload file size changed to 256M
exec/guestfish: added function to delete a file in a VM
routerVMs: updated ImportFilesToVM to test if import would work before actually performing it. This allow to get an error early on, before the file has been transfered (which can take a while)
added config log messages at init time
parent 1ae0b65c
No related branches found
No related tags found
No related merge requests found
......@@ -27,7 +27,7 @@ locked_time_hours = 4.0
[limits]
max_upload_size = 16M
max_upload_size = 128M
# We estimate that KVM allows for this amount of RAM saving in % (due to page sharing across VMs).
# 30% seems to be a pretty conservative estimate.
......
......@@ -2,6 +2,7 @@ package cmdVM
import (
u "nexus-client/utils"
"nexus-common/utils"
libclient "nexus-libclient/vm"
"os"
......@@ -59,7 +60,7 @@ func (cmd *ImportDir) Run(args []string) int {
statusCode := 0
tmpTarGzFile, err := u.GetRandomTempFilename()
tmpTarGzFile, err := utils.GetRandomTempFilename()
if err != nil {
u.PrintlnErr(err)
return 1
......
......@@ -10,8 +10,6 @@ import (
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
)
// Creates a tar.gz archive of dir and all its files and subdirectories.
......@@ -72,13 +70,3 @@ func TarGzDir(dir, archive string) error {
return nil
}
func GetRandomTempFilename() (string, error) {
tempDir := os.TempDir()
uuid, err := uuid.NewRandom()
if err != nil {
return "", errors.New("Failed creating random UUID: " + err.Error())
}
randName := "temp_" + uuid.String()
return filepath.Join(tempDir, randName), nil
}
......@@ -3,6 +3,9 @@ package utils
import (
"errors"
"os"
"path/filepath"
"github.com/google/uuid"
)
// Returns true if the specified file exists, false otherwise.
......@@ -10,3 +13,28 @@ func FileExists(filename string) bool {
_, err := os.Stat(filename)
return !errors.Is(err, os.ErrNotExist)
}
func GetRandomTempFilename() (string, error) {
tempDir := os.TempDir()
uuid, err := uuid.NewRandom()
if err != nil {
return "", errors.New("Failed creating random UUID: " + err.Error())
}
randName := "temp_" + uuid.String()
return filepath.Join(tempDir, randName), nil
}
func CreateRandomEmptyFile() (string, error) {
filePath, err := GetRandomTempFilename()
if err != nil {
return "", err
}
file, err := os.Create(filePath)
if err != nil {
return "", err
}
file.Close()
return filePath, nil
}
......@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os/exec"
"path/filepath"
"strings"
)
......@@ -22,27 +23,55 @@ func CheckGuestfish() error {
return nil
}
// Copies and unarchives a local tar.gz archive into a directory (vmDir) inside the VM's filesystem.
func CopyToVM(vmDiskFile, tarGzFile, vmDir string) error {
cmd := exec.Command(conf.Tools.Guestfish, "--rw", "-i", "tar-in", "-a", vmDiskFile, tarGzFile, vmDir, "compress:gzip")
// Uploads and unarchives a local tar.gz archive into a directory (vmDir) inside the VM's filesystem.
func DearchiveToVM(vmDiskFile, localTarGzFile, vmDir string) error {
cmd := exec.Command(conf.Tools.Guestfish, "--rw", "-i", "tar-in", "-a", vmDiskFile, localTarGzFile, vmDir, "compress:gzip")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
msg := "Failed writing to \"" + vmDir + "\" in qcow (" + vmDiskFile + ")"
msg := "Failed dearchiving to \"" + vmDir + "\" in VM"
log.Error(msg + ": " + output)
return errors.New(msg)
}
return nil
}
// Recursively copies a directory in the VM's filesystem (vmDir) into a tar.gz archive.
func CopyFromVM(vmDiskFile, vmDir, tarGzFile string) error {
cmd := exec.Command(conf.Tools.Guestfish, "--ro", "-i", "tar-out", "-a", vmDiskFile, vmDir, tarGzFile, "compress:gzip")
// Uploads a local file into a directory (vmDir) inside the VM's filesystem.
func CopyToVM(vmDiskFile, localFile, vmDir string) error {
filename := filepath.Base(localFile)
cmd := exec.Command(conf.Tools.Guestfish, "--rw", "-i", "upload", "-a", vmDiskFile, localFile, filepath.Join(vmDir, filename))
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
msg := "Failed reading \"" + vmDir + "\" in qcow (" + vmDiskFile + "): " + output
log.Error(msg)
msg := "Failed copying file to \"" + vmDir + "\" in VM"
log.Error(msg + ": " + output)
return errors.New(msg)
}
return nil
}
// Recursively downloads a directory from the VM's filesystem (vmDir) into a local tar.gz archive.
func ArchiveFromVM(vmDiskFile, vmDir, localTarGzFile string) error {
cmd := exec.Command(conf.Tools.Guestfish, "--ro", "-i", "tar-out", "-a", vmDiskFile, vmDir, localTarGzFile, "compress:gzip")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
msg := "Failed archiving \"" + vmDir + "\" in VM"
log.Error(msg + ": " + output)
return errors.New(msg)
}
return nil
}
// Delete a file from a VM's filesystem.
// filePath is the full path in the VM to the file to delete.
func DeleteFromVM(vmDiskFile, filePath string) error {
cmd := exec.Command(conf.Tools.Guestfish, "--rw", "-a", vmDiskFile, "-i", "rm", filePath)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
output := fmt.Sprintf("[%s]", stdoutStderr)
msg := "Failed deleting \"" + filePath + "\" in VM"
log.Error(msg + ": " + output)
return errors.New(msg)
}
return nil
......
......@@ -47,8 +47,17 @@ func main() {
cleaner.Start()
log.Info("Using port range [", conf.Core.VMSpiceMinPort, "-", conf.Core.VMSpiceMaxPort, "]")
log.Info("API port: ", conf.Core.APIDefaultPort)
log.Info("Spice port range [", conf.Core.VMSpiceMinPort, "-", conf.Core.VMSpiceMaxPort, "]")
log.Info("Tmp directory: ", conf.Core.TmpDir)
log.Info("Max upload size: ", conf.Limits.MaxUploadSize)
log.Info("KSM RAM saving: ", conf.Limits.KsmRamSaving*100, "%")
log.Info("RAM usage limit: ", conf.Limits.RamUsageLimit*100, "%")
log.Info("Max login attemps: ", conf.Auth.MaxLoginAttempts)
log.Info("Account lock time: ", conf.Auth.LockedTimeInHours, " hours")
log.Info("Access token expiration time: ", conf.Auth.AccessTokenExpirationTimeInHours, " hours")
router.New().Start(conf.Core.APIDefaultPort)
}
......@@ -6,8 +6,11 @@ import (
"net/http"
"nexus-common/caps"
"nexus-common/params"
"nexus-common/utils"
"nexus-server/exec"
"nexus-server/users"
"nexus-server/vms"
"os"
"path/filepath"
......@@ -595,7 +598,7 @@ func (r *RouterVMs) ExportVMDir(c echo.Context) error {
tarGzFile := filepath.Join(conf.Core.TmpDir, "exportdir_"+vm.GetID().String()+".tar.gz")
// Extracts VM's p.Dir directory into tarGzFile on the host.
if err := r.vms.ExportVMFiles(vm, p.Dir, tarGzFile); err != nil {
if err := r.vms.ExportFilesFromVM(vm, p.Dir, tarGzFile); err != nil {
msg := "Failed extracting VM " + vmID + "'s dir: " + err.Error()
log.Error(msg)
return echo.NewHTTPError(http.StatusBadRequest, msg)
......@@ -621,6 +624,31 @@ func (r *RouterVMs) ImportFilesToVM(c echo.Context) error {
// Retrieves the various client arguments.
vmDir := c.FormValue("vmDir")
// Checks if importing files into the VM would be successfull.
// The purpose of this function is to quickly checks if the import would succeed
// without having to wait for the archive to be uploaded from the client (which can take a while).
// It attempts to create an empty file inside the VM directory.
// Create a local file on the host
emptyLocalFile, err := utils.CreateRandomEmptyFile()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
// Remove the local file on the host when exiting the function
defer os.Remove(emptyLocalFile)
// Copy the local file from the host to the VM in vmDir
if err := r.vms.ImportFilesToVM(vm, exec.CopyToVM, emptyLocalFile, vmDir); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
// Remove the file we just copied in the VM
filename := filepath.Base(emptyLocalFile)
if err := r.vms.DeleteFileFromVM(vm, filepath.Join(vmDir, filename)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
// Retrieves the tar.gz archive (uploadedtarGzFile).
tarGzFile, err := c.FormFile("file")
if err != nil {
......@@ -652,7 +680,7 @@ func (r *RouterVMs) ImportFilesToVM(c echo.Context) error {
// Copy the archive's files into the VM
// IMPORTANT: the VM cannot have its disk busy
if err = r.vms.ImportFilesToVM(vm, uploadedtarGzFile, vmDir); err != nil {
if err = r.vms.ImportFilesToVM(vm, exec.DearchiveToVM, uploadedtarGzFile, vmDir); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
......
......@@ -777,7 +777,7 @@ func (vms *VMs) DeleteVMAccess(vmID uuid.UUID, user *users.User, destUserEmail s
// Technically, extracting files from a running VM should work, but some files might be inconsistent.
// In consequence, we forbid this action on a running VM.
// Concurrency: safe
func (vms *VMs) ExportVMFiles(vm *VM, vmDir, tarGzFile string) error {
func (vms *VMs) ExportFilesFromVM(vm *VM, vmDir, localTarGzFile string) error {
prefix := "Failed exporting files from VM: "
vm.mutex.Lock()
......@@ -797,7 +797,7 @@ func (vms *VMs) ExportVMFiles(vm *VM, vmDir, tarGzFile string) error {
vmDisk := vm.getDiskPath()
if err := exec.CopyFromVM(vmDisk, vmDir, tarGzFile); err != nil {
if err := exec.ArchiveFromVM(vmDisk, vmDir, localTarGzFile); err != nil {
vm.mutex.Lock()
vm.DiskBusy = false
vm.mutex.Unlock()
......@@ -815,7 +815,11 @@ func (vms *VMs) ExportVMFiles(vm *VM, vmDir, tarGzFile string) error {
// Imports files from a tar.gz archive into a VM disk image (guest's filesystem), in a specified directory.
// Concurrency: safe
func (vms *VMs) ImportFilesToVM(vm *VM, tarGzFile, vmDir string) error {
//func (vms *VMs) ImportFilesToVM(vm *VM, localTarGzFile, vmDir string) error {
type ImportFileFunc func(vmDiskFile, localFile, vmDir string) error
func (vms *VMs) ImportFilesToVM(vm *VM, fn ImportFileFunc, localFile, vmDir string) error {
prefix := "Failed importing files into VM: "
vm.mutex.Lock()
......@@ -835,7 +839,7 @@ func (vms *VMs) ImportFilesToVM(vm *VM, tarGzFile, vmDir string) error {
vmDisk := vm.getDiskPath()
if err := exec.CopyToVM(vmDisk, tarGzFile, vmDir); err != nil {
if err := fn(vmDisk, localFile, vmDir); err != nil {
vm.mutex.Lock()
vm.DiskBusy = false
vm.mutex.Unlock()
......@@ -850,3 +854,32 @@ func (vms *VMs) ImportFilesToVM(vm *VM, tarGzFile, vmDir string) error {
return nil
}
// Delete a file from a VM's filesystem.
// filePath is the full path in the VM to the file to delete.
func (vms *VMs) DeleteFileFromVM(vm *VM, filePath string) error {
prefix := "Failed deleting file from VM: "
vm.mutex.Lock()
defer vm.mutex.Unlock()
if vm.IsRunning() {
return errors.New(prefix + "VM must be stopped")
}
if vm.IsDiskBusy() {
return errors.New(prefix + "disk in use (busy)")
}
vm.DiskBusy = true
defer func(vm *VM) {
vm.DiskBusy = false
}(vm)
if err := exec.DeleteFromVM(vm.getDiskPath(), filePath); err != nil {
msg := prefix + err.Error()
log.Error(msg)
return errors.New(msg)
}
return nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment