builder/qemu: simplify driver, make things more Go-like
This commit is contained in:
parent
d78787e182
commit
3bc0c4aa25
@ -372,7 +372,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||||||
new(stepHTTPServer),
|
new(stepHTTPServer),
|
||||||
new(stepForwardSSH),
|
new(stepForwardSSH),
|
||||||
new(stepConfigureVNC),
|
new(stepConfigureVNC),
|
||||||
new(stepRun),
|
&stepRun{
|
||||||
|
BootDrive: "d",
|
||||||
|
Message: "Starting VM, booting from CD-ROM",
|
||||||
|
},
|
||||||
|
&stepBootWait{},
|
||||||
|
&stepTypeBootCommand{},
|
||||||
|
&stepWaitForShutdown{
|
||||||
|
Message: "Waiting for initial VM boot to shut down",
|
||||||
|
},
|
||||||
|
&stepRun{
|
||||||
|
BootDrive: "c",
|
||||||
|
Message: "Starting VM, booting from hard disk",
|
||||||
|
},
|
||||||
&common.StepConnectSSH{
|
&common.StepConnectSSH{
|
||||||
SSHAddress: sshAddress,
|
SSHAddress: sshAddress,
|
||||||
SSHConfig: sshConfig,
|
SSHConfig: sshConfig,
|
||||||
|
@ -3,7 +3,6 @@ package qemu
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
"io"
|
"io"
|
||||||
@ -11,7 +10,8 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,21 +25,14 @@ type Driver interface {
|
|||||||
// qemuImgPath - string value for the qemu-img executable
|
// qemuImgPath - string value for the qemu-img executable
|
||||||
Initialize(string, string)
|
Initialize(string, string)
|
||||||
|
|
||||||
// Checks if the VM with the given name is running.
|
|
||||||
IsRunning(string) (bool, error)
|
|
||||||
|
|
||||||
// Stop stops a running machine, forcefully.
|
// Stop stops a running machine, forcefully.
|
||||||
Stop(string) error
|
Stop() error
|
||||||
|
|
||||||
// Qemu executes the given command via qemu-system-x86_64
|
// 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
|
// wait on shutdown of the VM with option to cancel
|
||||||
WaitForShutdown(
|
WaitForShutdown(<-chan struct{}) bool
|
||||||
vmName string,
|
|
||||||
block bool,
|
|
||||||
state multistep.StateBag,
|
|
||||||
cancellCallback DriverCancelCallback) error
|
|
||||||
|
|
||||||
// Qemu executes the given command via qemu-img
|
// Qemu executes the given command via qemu-img
|
||||||
QemuImg(...string) error
|
QemuImg(...string) error
|
||||||
@ -53,156 +46,108 @@ type Driver interface {
|
|||||||
Version() (string, error)
|
Version() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type driverState struct {
|
|
||||||
cmd *exec.Cmd
|
|
||||||
cancelChan chan struct{}
|
|
||||||
waitDone chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
type QemuDriver struct {
|
type QemuDriver struct {
|
||||||
qemuPath string
|
qemuPath string
|
||||||
qemuImgPath string
|
qemuImgPath string
|
||||||
state map[string]*driverState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QemuDriver) getDriverState(name string) *driverState {
|
vmCmd *exec.Cmd
|
||||||
if _, ok := d.state[name]; !ok {
|
vmEndCh <-chan int
|
||||||
d.state[name] = &driverState{}
|
lock sync.Mutex
|
||||||
}
|
|
||||||
return d.state[name]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QemuDriver) Initialize(qemuPath string, qemuImgPath string) {
|
func (d *QemuDriver) Initialize(qemuPath string, qemuImgPath string) {
|
||||||
d.qemuPath = qemuPath
|
d.qemuPath = qemuPath
|
||||||
d.qemuImgPath = qemuImgPath
|
d.qemuImgPath = qemuImgPath
|
||||||
d.state = make(map[string]*driverState)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QemuDriver) IsRunning(name string) (bool, error) {
|
func (d *QemuDriver) Stop() error {
|
||||||
ds := d.getDriverState(name)
|
d.lock.Lock()
|
||||||
return ds.cancelChan != nil, nil
|
defer d.lock.Unlock()
|
||||||
}
|
|
||||||
|
|
||||||
func (d *QemuDriver) Stop(name string) error {
|
if d.vmCmd != nil {
|
||||||
ds := d.getDriverState(name)
|
if err := d.vmCmd.Process.Kill(); err != nil {
|
||||||
|
return err
|
||||||
// signal to the command 'wait' to kill the process
|
}
|
||||||
if ds.cancelChan != nil {
|
|
||||||
close(ds.cancelChan)
|
|
||||||
ds.cancelChan = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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()
|
stdout_r, stdout_w := io.Pipe()
|
||||||
stderr_r, stderr_w := io.Pipe()
|
stderr_r, stderr_w := io.Pipe()
|
||||||
|
|
||||||
log.Printf("Executing %s: %#v", d.qemuPath, qemuArgs)
|
log.Printf("Executing %s: %#v", d.qemuPath, qemuArgs)
|
||||||
ds := d.getDriverState(vmName)
|
cmd := exec.Command(d.qemuPath, qemuArgs...)
|
||||||
ds.cmd = exec.Command(d.qemuPath, qemuArgs...)
|
cmd.Stdout = stdout_w
|
||||||
ds.cmd.Stdout = stdout_w
|
cmd.Stderr = stderr_w
|
||||||
ds.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 stdout", stdout_r)
|
||||||
go logReader("Qemu stderr", stderr_r)
|
go logReader("Qemu stderr", stderr_r)
|
||||||
|
|
||||||
err := ds.cmd.Start()
|
log.Printf("Started Qemu. Pid: %d", cmd.Process.Pid)
|
||||||
|
|
||||||
if err != nil {
|
// Wait for Qemu to complete in the background, and mark when its done
|
||||||
err = fmt.Errorf("Error starting VM: %s", err)
|
endCh := make(chan int, 1)
|
||||||
} else {
|
go func() {
|
||||||
log.Printf("---- Started Qemu ------- PID = %d", ds.cmd.Process.Pid)
|
defer stderr_w.Close()
|
||||||
|
defer stdout_w.Close()
|
||||||
|
|
||||||
ds.cancelChan = make(chan struct{})
|
var exitCode int = 0
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
// make the channel to watch the process
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
ds.waitDone = make(chan error)
|
// The program has exited with an exit code != 0
|
||||||
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
// start the virtual machine in the background
|
exitCode = status.ExitStatus()
|
||||||
go func() {
|
} else {
|
||||||
defer stderr_w.Close()
|
exitCode = 254
|
||||||
defer stdout_w.Close()
|
|
||||||
ds.waitDone <- ds.cmd.Wait()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
endCh <- exitCode
|
||||||
// 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:
|
d.lock.Lock()
|
||||||
log.Printf("Qemu Process done with output = %v", err)
|
defer d.lock.Unlock()
|
||||||
log.Println("'Nil'ing out cancelChan.")
|
d.vmCmd = nil
|
||||||
ds.cancelChan = nil
|
d.vmEndCh = nil
|
||||||
}
|
}()
|
||||||
}()
|
|
||||||
|
// Setup our state so we know we are running
|
||||||
|
d.vmCmd = cmd
|
||||||
|
d.vmEndCh = endCh
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *QemuDriver) WaitForShutdown(cancelCh <-chan struct{}) bool {
|
||||||
|
d.lock.Lock()
|
||||||
|
endCh := d.vmEndCh
|
||||||
|
d.lock.Unlock()
|
||||||
|
|
||||||
|
if endCh == nil {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ds.cancelChan = nil
|
select {
|
||||||
return err
|
case <-endCh:
|
||||||
|
return true
|
||||||
|
case <-cancelCh:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *QemuDriver) QemuImg(args ...string) error {
|
func (d *QemuDriver) QemuImg(args ...string) error {
|
||||||
|
25
builder/qemu/step_boot_wait.go
Normal file
25
builder/qemu/step_boot_wait.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package qemu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stepBootWait waits the configured time period.
|
||||||
|
type stepBootWait struct{}
|
||||||
|
|
||||||
|
func (s *stepBootWait) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
config := state.Get("config").(*config)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if int64(config.bootWait) > 0 {
|
||||||
|
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
|
||||||
|
time.Sleep(config.bootWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepBootWait) Cleanup(state multistep.StateBag) {}
|
@ -6,49 +6,51 @@ import (
|
|||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// stepRun runs the virtual machine
|
||||||
type stepRun struct {
|
type stepRun struct {
|
||||||
vmName string
|
BootDrive string
|
||||||
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBootCommand(state multistep.StateBag,
|
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
actionChannel chan multistep.StepAction) {
|
driver := state.Get("driver").(Driver)
|
||||||
config := state.Get("config").(*config)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
bootCmd := stepTypeBootCommand{}
|
|
||||||
|
|
||||||
if int64(config.bootWait) > 0 {
|
ui.Say(s.Message)
|
||||||
ui.Say(fmt.Sprintf("Waiting %s for boot...", config.bootWait))
|
|
||||||
time.Sleep(config.bootWait)
|
command := getCommandArgs(s.BootDrive, state)
|
||||||
|
if err := driver.Qemu(command...); err != nil {
|
||||||
|
err := fmt.Errorf("Error launching VM: %s", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
actionChannel <- bootCmd.Run(state)
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelCallback(state multistep.StateBag) bool {
|
func (s *stepRun) Cleanup(state multistep.StateBag) {
|
||||||
cancel := false
|
driver := state.Get("driver").(Driver)
|
||||||
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
|
||||||
cancel = true
|
|
||||||
}
|
|
||||||
return cancel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRun) getCommandArgs(
|
|
||||||
bootDrive string,
|
|
||||||
state multistep.StateBag) []string {
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
if err := driver.Stop(); err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommandArgs(bootDrive string, state multistep.StateBag) []string {
|
||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
|
isoPath := state.Get("iso_path").(string)
|
||||||
|
vncPort := state.Get("vnc_port").(uint)
|
||||||
|
sshHostPort := state.Get("sshHostPort").(uint)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
guiArgument := "sdl"
|
||||||
|
vnc := fmt.Sprintf("0.0.0.0:%d", vncPort-5900)
|
||||||
vmName := config.VMName
|
vmName := config.VMName
|
||||||
imgPath := filepath.Join(config.OutputDir,
|
imgPath := filepath.Join(config.OutputDir,
|
||||||
fmt.Sprintf("%s.%s", vmName, strings.ToLower(config.Format)))
|
fmt.Sprintf("%s.%s", vmName, strings.ToLower(config.Format)))
|
||||||
isoPath := state.Get("iso_path").(string)
|
|
||||||
vncPort := state.Get("vnc_port").(uint)
|
|
||||||
guiArgument := "sdl"
|
|
||||||
sshHostPort := state.Get("sshHostPort").(uint)
|
|
||||||
vnc := fmt.Sprintf("0.0.0.0:%d", vncPort-5900)
|
|
||||||
|
|
||||||
if config.Headless == true {
|
if config.Headless == true {
|
||||||
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
|
ui.Message("WARNING: The VM will be started in headless mode, as configured.\n" +
|
||||||
@ -112,74 +114,3 @@ func (s *stepRun) getCommandArgs(
|
|||||||
|
|
||||||
return outArgs
|
return outArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stepRun) runVM(
|
|
||||||
sendBootCommands bool,
|
|
||||||
bootDrive string,
|
|
||||||
state multistep.StateBag) multistep.StepAction {
|
|
||||||
|
|
||||||
config := state.Get("config").(*config)
|
|
||||||
driver := state.Get("driver").(Driver)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
|
||||||
vmName := config.VMName
|
|
||||||
|
|
||||||
ui.Say("Starting the virtual machine for OS Install...")
|
|
||||||
command := s.getCommandArgs(bootDrive, state)
|
|
||||||
if err := driver.Qemu(vmName, command...); err != nil {
|
|
||||||
err := fmt.Errorf("Error launching VM: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
s.vmName = vmName
|
|
||||||
|
|
||||||
// run the boot command after its own timeout
|
|
||||||
if sendBootCommands {
|
|
||||||
waitDone := make(chan multistep.StepAction, 1)
|
|
||||||
go runBootCommand(state, waitDone)
|
|
||||||
select {
|
|
||||||
case action := <-waitDone:
|
|
||||||
if action != multistep.ActionContinue {
|
|
||||||
// stop the VM in its tracks
|
|
||||||
driver.Stop(vmName)
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.Say("Waiting for VM to shutdown...")
|
|
||||||
if err := driver.WaitForShutdown(vmName, sendBootCommands, state, cancelCallback); err != nil {
|
|
||||||
err := fmt.Errorf("Error waiting for initial VM install to shutdown: %s", err)
|
|
||||||
ui.Error(err.Error())
|
|
||||||
return multistep.ActionHalt
|
|
||||||
}
|
|
||||||
|
|
||||||
return multistep.ActionContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRun) Run(state multistep.StateBag) multistep.StepAction {
|
|
||||||
// First, the OS install boot
|
|
||||||
action := s.runVM(true, "d", state)
|
|
||||||
|
|
||||||
if action == multistep.ActionContinue {
|
|
||||||
// Then the provisioning install
|
|
||||||
action = s.runVM(false, "c", state)
|
|
||||||
}
|
|
||||||
|
|
||||||
return action
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stepRun) Cleanup(state multistep.StateBag) {
|
|
||||||
if s.vmName == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
driver := state.Get("driver").(Driver)
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
|
||||||
|
|
||||||
if running, _ := driver.IsRunning(s.vmName); running {
|
|
||||||
if err := driver.Stop(s.vmName); err != nil {
|
|
||||||
ui.Error(fmt.Sprintf("Error shutting down VM: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
// config *config
|
// config *config
|
||||||
// driver Driver
|
// driver Driver
|
||||||
// ui packer.Ui
|
// ui packer.Ui
|
||||||
// vmName string
|
|
||||||
//
|
//
|
||||||
// Produces:
|
// Produces:
|
||||||
// <nothing>
|
// <nothing>
|
||||||
@ -28,7 +27,6 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
config := state.Get("config").(*config)
|
config := state.Get("config").(*config)
|
||||||
driver := state.Get("driver").(Driver)
|
driver := state.Get("driver").(Driver)
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
vmName := config.VMName
|
|
||||||
|
|
||||||
if config.ShutdownCommand != "" {
|
if config.ShutdownCommand != "" {
|
||||||
ui.Say("Gracefully halting virtual machine...")
|
ui.Say("Gracefully halting virtual machine...")
|
||||||
@ -41,28 +39,23 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
|||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the machine to actually shut down
|
// Start the goroutine that will time out our graceful attempt
|
||||||
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
|
cancelCh := make(chan struct{}, 1)
|
||||||
shutdownTimer := time.After(config.shutdownTimeout)
|
go func() {
|
||||||
for {
|
defer close(cancelCh)
|
||||||
running, _ := driver.IsRunning(vmName)
|
<-time.After(config.shutdownTimeout)
|
||||||
if !running {
|
}()
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
|
||||||
case <-shutdownTimer:
|
if ok := driver.WaitForShutdown(cancelCh); !ok {
|
||||||
err := errors.New("Timeout while waiting for machine to shut down.")
|
err := errors.New("Timeout while waiting for machine to shut down.")
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
default:
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ui.Say("Halting the virtual machine...")
|
ui.Say("Halting the virtual machine...")
|
||||||
if err := driver.Stop(vmName); err != nil {
|
if err := driver.Stop(); err != nil {
|
||||||
err := fmt.Errorf("Error stopping VM: %s", err)
|
err := fmt.Errorf("Error stopping VM: %s", err)
|
||||||
state.Put("error", err)
|
state.Put("error", err)
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
|
43
builder/qemu/step_wait_for_shutdown.go
Normal file
43
builder/qemu/step_wait_for_shutdown.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package qemu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stepWaitForShutdown waits for the shutdown of the currently running
|
||||||
|
// qemu VM.
|
||||||
|
type stepWaitForShutdown struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepWaitForShutdown) Run(state multistep.StateBag) multistep.StepAction {
|
||||||
|
driver := state.Get("driver").(Driver)
|
||||||
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
defer close(stopCh)
|
||||||
|
|
||||||
|
cancelCh := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if _, ok := state.GetOk(multistep.StateCancelled); ok {
|
||||||
|
close(cancelCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
return
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ui.Say(s.Message)
|
||||||
|
driver.WaitForShutdown(cancelCh)
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepWaitForShutdown) Cleanup(state multistep.StateBag) {}
|
Loading…
x
Reference in New Issue
Block a user