|
|
|
|
@ -3,7 +3,6 @@ package qemu
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"bytes"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/mitchellh/multistep"
|
|
|
|
|
"io"
|
|
|
|
|
@ -11,7 +10,8 @@ import (
|
|
|
|
|
"os/exec"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
"sync"
|
|
|
|
|
"syscall"
|
|
|
|
|
"unicode"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -25,21 +25,14 @@ type Driver interface {
|
|
|
|
|
// qemuImgPath - string value for the qemu-img executable
|
|
|
|
|
Initialize(string, string)
|
|
|
|
|
|
|
|
|
|
// Checks if the VM with the given name is running.
|
|
|
|
|
IsRunning(string) (bool, error)
|
|
|
|
|
|
|
|
|
|
// Stop stops a running machine, forcefully.
|
|
|
|
|
Stop(string) error
|
|
|
|
|
Stop() error
|
|
|
|
|
|
|
|
|
|
// Qemu executes the given command via qemu-system-x86_64
|
|
|
|
|
Qemu(vmName string, qemuArgs ...string) error
|
|
|
|
|
Qemu(qemuArgs ...string) error
|
|
|
|
|
|
|
|
|
|
// wait on shutdown of the VM with option to cancel
|
|
|
|
|
WaitForShutdown(
|
|
|
|
|
vmName string,
|
|
|
|
|
block bool,
|
|
|
|
|
state multistep.StateBag,
|
|
|
|
|
cancellCallback DriverCancelCallback) error
|
|
|
|
|
WaitForShutdown(<-chan struct{}) bool
|
|
|
|
|
|
|
|
|
|
// Qemu executes the given command via qemu-img
|
|
|
|
|
QemuImg(...string) error
|
|
|
|
|
@ -53,156 +46,108 @@ type Driver interface {
|
|
|
|
|
Version() (string, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type driverState struct {
|
|
|
|
|
cmd *exec.Cmd
|
|
|
|
|
cancelChan chan struct{}
|
|
|
|
|
waitDone chan error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type QemuDriver struct {
|
|
|
|
|
qemuPath string
|
|
|
|
|
qemuImgPath string
|
|
|
|
|
state map[string]*driverState
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) getDriverState(name string) *driverState {
|
|
|
|
|
if _, ok := d.state[name]; !ok {
|
|
|
|
|
d.state[name] = &driverState{}
|
|
|
|
|
}
|
|
|
|
|
return d.state[name]
|
|
|
|
|
vmCmd *exec.Cmd
|
|
|
|
|
vmEndCh <-chan int
|
|
|
|
|
lock sync.Mutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) Initialize(qemuPath string, qemuImgPath string) {
|
|
|
|
|
d.qemuPath = qemuPath
|
|
|
|
|
d.qemuImgPath = qemuImgPath
|
|
|
|
|
d.state = make(map[string]*driverState)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) IsRunning(name string) (bool, error) {
|
|
|
|
|
ds := d.getDriverState(name)
|
|
|
|
|
return ds.cancelChan != nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) Stop(name string) error {
|
|
|
|
|
ds := d.getDriverState(name)
|
|
|
|
|
func (d *QemuDriver) Stop() error {
|
|
|
|
|
d.lock.Lock()
|
|
|
|
|
defer d.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
// signal to the command 'wait' to kill the process
|
|
|
|
|
if ds.cancelChan != nil {
|
|
|
|
|
close(ds.cancelChan)
|
|
|
|
|
ds.cancelChan = nil
|
|
|
|
|
if d.vmCmd != nil {
|
|
|
|
|
if err := d.vmCmd.Process.Kill(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) Qemu(vmName string, qemuArgs ...string) error {
|
|
|
|
|
func (d *QemuDriver) Qemu(qemuArgs ...string) error {
|
|
|
|
|
d.lock.Lock()
|
|
|
|
|
defer d.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
if d.vmCmd != nil {
|
|
|
|
|
panic("Existing VM state found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stdout_r, stdout_w := io.Pipe()
|
|
|
|
|
stderr_r, stderr_w := io.Pipe()
|
|
|
|
|
|
|
|
|
|
log.Printf("Executing %s: %#v", d.qemuPath, qemuArgs)
|
|
|
|
|
ds := d.getDriverState(vmName)
|
|
|
|
|
ds.cmd = exec.Command(d.qemuPath, qemuArgs...)
|
|
|
|
|
ds.cmd.Stdout = stdout_w
|
|
|
|
|
ds.cmd.Stderr = stderr_w
|
|
|
|
|
cmd := exec.Command(d.qemuPath, qemuArgs...)
|
|
|
|
|
cmd.Stdout = stdout_w
|
|
|
|
|
cmd.Stderr = stderr_w
|
|
|
|
|
|
|
|
|
|
err := cmd.Start()
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = fmt.Errorf("Error starting VM: %s", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
go logReader("Qemu stdout", stdout_r)
|
|
|
|
|
go logReader("Qemu stderr", stderr_r)
|
|
|
|
|
|
|
|
|
|
err := ds.cmd.Start()
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
err = fmt.Errorf("Error starting VM: %s", err)
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("---- Started Qemu ------- PID = %d", ds.cmd.Process.Pid)
|
|
|
|
|
log.Printf("Started Qemu. Pid: %d", cmd.Process.Pid)
|
|
|
|
|
|
|
|
|
|
// Wait for Qemu to complete in the background, and mark when its done
|
|
|
|
|
endCh := make(chan int, 1)
|
|
|
|
|
go func() {
|
|
|
|
|
defer stderr_w.Close()
|
|
|
|
|
defer stdout_w.Close()
|
|
|
|
|
|
|
|
|
|
var exitCode int = 0
|
|
|
|
|
if err := cmd.Wait(); err != nil {
|
|
|
|
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
|
|
|
// The program has exited with an exit code != 0
|
|
|
|
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
|
|
|
|
exitCode = status.ExitStatus()
|
|
|
|
|
} else {
|
|
|
|
|
exitCode = 254
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ds.cancelChan = make(chan struct{})
|
|
|
|
|
endCh <- exitCode
|
|
|
|
|
|
|
|
|
|
// make the channel to watch the process
|
|
|
|
|
ds.waitDone = make(chan error)
|
|
|
|
|
d.lock.Lock()
|
|
|
|
|
defer d.lock.Unlock()
|
|
|
|
|
d.vmCmd = nil
|
|
|
|
|
d.vmEndCh = nil
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// start the virtual machine in the background
|
|
|
|
|
go func() {
|
|
|
|
|
defer stderr_w.Close()
|
|
|
|
|
defer stdout_w.Close()
|
|
|
|
|
ds.waitDone <- ds.cmd.Wait()
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
// Setup our state so we know we are running
|
|
|
|
|
d.vmCmd = cmd
|
|
|
|
|
d.vmEndCh = endCh
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) WaitForShutdown(vmName string,
|
|
|
|
|
block bool,
|
|
|
|
|
state multistep.StateBag,
|
|
|
|
|
cancelCallback DriverCancelCallback) error {
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
ds := d.getDriverState(vmName)
|
|
|
|
|
|
|
|
|
|
if block {
|
|
|
|
|
// wait in the background for completion or caller cancel
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ds.cancelChan:
|
|
|
|
|
log.Println("Qemu process request to cancel -- killing Qemu process.")
|
|
|
|
|
if err = ds.cmd.Process.Kill(); err != nil {
|
|
|
|
|
log.Printf("Failed to kill qemu: %v", err)
|
|
|
|
|
}
|
|
|
|
|
func (d *QemuDriver) WaitForShutdown(cancelCh <-chan struct{}) bool {
|
|
|
|
|
d.lock.Lock()
|
|
|
|
|
endCh := d.vmEndCh
|
|
|
|
|
d.lock.Unlock()
|
|
|
|
|
|
|
|
|
|
// clear out the error channel since it's just a cancel
|
|
|
|
|
// and therefore the reason for failure is clear
|
|
|
|
|
log.Println("Empytying waitDone channel.")
|
|
|
|
|
<-ds.waitDone
|
|
|
|
|
|
|
|
|
|
// this gig is over -- assure calls to IsRunning see the nil
|
|
|
|
|
log.Println("'Nil'ing out cancelChan.")
|
|
|
|
|
ds.cancelChan = nil
|
|
|
|
|
return errors.New("WaitForShutdown cancelled")
|
|
|
|
|
case err = <-ds.waitDone:
|
|
|
|
|
log.Printf("Qemu Process done with output = %v", err)
|
|
|
|
|
// assure calls to IsRunning see the nil
|
|
|
|
|
log.Println("'Nil'ing out cancelChan.")
|
|
|
|
|
ds.cancelChan = nil
|
|
|
|
|
return nil
|
|
|
|
|
case <-time.After(1 * time.Second):
|
|
|
|
|
cancel := cancelCallback(state)
|
|
|
|
|
if cancel {
|
|
|
|
|
log.Println("Qemu process request to cancel -- killing Qemu process.")
|
|
|
|
|
|
|
|
|
|
// The step sequence was cancelled, so cancel waiting for SSH
|
|
|
|
|
// and just start the halting process.
|
|
|
|
|
close(ds.cancelChan)
|
|
|
|
|
|
|
|
|
|
log.Println("Cancel request made, quitting waiting for Qemu.")
|
|
|
|
|
return errors.New("WaitForShutdown cancelled by interrupt.")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
go func() {
|
|
|
|
|
select {
|
|
|
|
|
case <-ds.cancelChan:
|
|
|
|
|
log.Println("Qemu process request to cancel -- killing Qemu process.")
|
|
|
|
|
if err = ds.cmd.Process.Kill(); err != nil {
|
|
|
|
|
log.Printf("Failed to kill qemu: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clear out the error channel since it's just a cancel
|
|
|
|
|
// and therefore the reason for failure is clear
|
|
|
|
|
log.Println("Empytying waitDone channel.")
|
|
|
|
|
<-ds.waitDone
|
|
|
|
|
log.Println("'Nil'ing out cancelChan.")
|
|
|
|
|
ds.cancelChan = nil
|
|
|
|
|
|
|
|
|
|
case err = <-ds.waitDone:
|
|
|
|
|
log.Printf("Qemu Process done with output = %v", err)
|
|
|
|
|
log.Println("'Nil'ing out cancelChan.")
|
|
|
|
|
ds.cancelChan = nil
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
if endCh == nil {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ds.cancelChan = nil
|
|
|
|
|
return err
|
|
|
|
|
select {
|
|
|
|
|
case <-endCh:
|
|
|
|
|
return true
|
|
|
|
|
case <-cancelCh:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *QemuDriver) QemuImg(args ...string) error {
|
|
|
|
|
|