vorteil/vorteil

View on GitHub
pkg/virtualizers/qemu/virtualizer_notwindows.go

Summary

Maintainability
B
4 hrs
Test Coverage
F
0%
//go:build linux || darwin
// +build linux darwin

package qemu

/**
 * SPDX-License-Identifier: Apache-2.0
 * Copyright 2020 vorteil.io Pty Ltd
 */

import (
    "errors"
    "fmt"
    "net"
    "os"
    "os/exec"
    "path/filepath"
    "strings"
    "syscall"
    "time"

    "github.com/mattn/go-shellwords"
    "github.com/vorteil/vorteil/pkg/vcfg"
    "github.com/vorteil/vorteil/pkg/virtualizers"
)

// Start creates the virtualmachine and runs it
func (v *Virtualizer) Start() error {
    v.logger.Debugf("Starting VM")
    v.command = exec.Command(v.command.Args[0], v.command.Args[1:]...)
    v.command.SysProcAttr = &syscall.SysProcAttr{
        Setpgid: true,
    }

    switch v.State() {

    case "ready":
        v.state = virtualizers.Changing

        err := v.initLogging()
        if err != nil {
            return err
        }

        go func() {
            err = v.command.Start()
            if err != nil {
                v.logger.Errorf("Error Executing Start: %s", err.Error())
            }

            polling := true
            count := 0
            for {
                if !polling {
                    break
                }
                if count == 10 {
                    v.logger.Errorf("Error: unable to start QEMU as socket wasn't created in time")
                }
                v.sock, err = net.Dial("unix", filepath.ToSlash(filepath.Join(v.folder, "monitor.sock")))
                if err == nil {
                    polling = false
                    v.logger.Infof("Connected to socket at '%s'", filepath.ToSlash(filepath.Join(v.folder, "monitor.sock")))
                } else {
                    v.logger.Infof("Attempting to dial socket at '%s'", filepath.ToSlash(filepath.Join(v.folder, "monitor.sock")))
                }
                count++
                time.Sleep(time.Second * 1)
            }

            v.state = virtualizers.Alive

            _, err := v.command.Process.Wait()
            if err == nil || err.Error() != fmt.Errorf("wait: no child processes").Error() {
                if err != nil {
                    v.logger.Errorf("Error Wait Command: %s", err.Error())
                }
            }

            v.state = virtualizers.Ready
            if v.sock != nil {
                v.sock.Close()

                // vm should be stopped by now so close the pipes
                v.errPipe.Close()
                v.outPipe.Close()
                v.disk.Close()

                v.Close(false)

            }

        }()
    }

    return nil
}

// prepare sets the fields and arguments to spawn the virtual machine
func (o *operation) prepare(args *virtualizers.PrepareArgs) {
    var returnErr error
    progress := o.logger.NewProgress("Preparing QEMU machine", "", 0)
    defer progress.Finish(false)
    o.updateStatus(fmt.Sprintf("Building qemu command..."))

    defer func() {
        o.finished(returnErr)
    }()

    executable, err := virtualizers.GetExecutable(VirtualizerID)
    if err != nil {
        returnErr = err
        return
    }

    o.networkType = "nat"
    o.state = "initializing"
    o.name = args.Name
    o.folder = filepath.Dir(args.ImagePath)
    o.id = strings.Split(filepath.Base(o.folder), "-")[1]

    diskpath := filepath.ToSlash(args.ImagePath)
    diskformat := "raw"

    argsCommand := createArgs(o.config.VM.CPUs, o.config.VM.RAM.Units(vcfg.MiB), o.headless, diskpath, diskformat)
    argsCommand += fmt.Sprintf(" -monitor unix:%s,server,nowait", filepath.ToSlash(filepath.Join(o.folder, "monitor.sock")))

    params, err := shellwords.Parse(argsCommand)
    if err != nil {
        returnErr = err
        return
    }

    command := exec.Command(executable, params...)
    o.command = command

    netArgs, err := o.initializeNetworkCards()
    if err != nil {
        returnErr = err
    }
    o.command.Args = append(o.command.Args, netArgs...)

    o.logger.Infof("Creating QEMU VM with Args: %s", strings.Join(o.command.Args, " "))

    o.state = "ready"

    if args.Start {
        err = o.Start()
        if err != nil {
            returnErr = err
        }
    }

    _, loaded := virtualizers.ActiveVMs.LoadOrStore(o.name, o.Virtualizer)
    if loaded {
        returnErr = errors.New("virtual machine already exists")
    }
}

// GenerateScript generates a .sh file to be able to run the disk with qemu standalone
func (v *Virtualizer) GenerateScript(source string) error {
    var err error

    name := filepath.Base(v.folder)
    err = os.MkdirAll(filepath.Join(source), 0777)
    if err != nil {
        return err
    }
    // copy disk contents to source directory
    err = os.Rename(v.folder, filepath.Join(source, name))
    if err != nil {
        return err
    }

    args := strings.Join(v.command.Args, " ")
    //replace diskpath
    args = strings.ReplaceAll(args, filepath.ToSlash(filepath.Join(v.folder, fmt.Sprintf("%s.raw", v.name))), fmt.Sprintf("\"%s\"", filepath.ToSlash(filepath.Join(source, name, fmt.Sprintf("%s.raw", v.name)))))
    // replace monitor with nothing
    args = strings.ReplaceAll(args, fmt.Sprintf("-monitor unix:%s/monitor.sock,server,nowait", v.folder), "")

    f, err := os.Create(filepath.Join(source, name, "start.sh"))
    if err != nil {
        return err
    }

    f.Write([]byte(args))

    // chmod start.sh
    err = os.Chmod(f.Name(), 0777)
    if err != nil {
        return err
    }
    defer f.Close()
    return nil
}