builder/virtualbox: graceful shutdown
This commit is contained in:
parent
52391bb1f3
commit
48a3892ce6
|
@ -32,6 +32,8 @@ type config struct {
|
|||
ISOMD5 string `mapstructure:"iso_md5"`
|
||||
ISOUrl string `mapstructure:"iso_url"`
|
||||
OutputDir string `mapstructure:"output_directory"`
|
||||
ShutdownCommand string `mapstructure:"shutdown_command"`
|
||||
ShutdownTimeout time.Duration ``
|
||||
SSHHostPortMin uint `mapstructure:"ssh_host_port_min"`
|
||||
SSHHostPortMax uint `mapstructure:"ssh_host_port_max"`
|
||||
SSHPassword string `mapstructure:"ssh_password"`
|
||||
|
@ -41,6 +43,7 @@ type config struct {
|
|||
VMName string `mapstructure:"vm_name"`
|
||||
|
||||
RawBootWait string `mapstructure:"boot_wait"`
|
||||
RawShutdownTimeout string `mapstructure:"shutdown_timeout"`
|
||||
RawSSHWaitTimeout string `mapstructure:"ssh_wait_timeout"`
|
||||
}
|
||||
|
||||
|
@ -140,6 +143,15 @@ func (b *Builder) Prepare(raw interface{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
if b.config.RawShutdownTimeout == "" {
|
||||
b.config.RawShutdownTimeout = "5m"
|
||||
}
|
||||
|
||||
b.config.ShutdownTimeout, err = time.ParseDuration(b.config.RawShutdownTimeout)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err))
|
||||
}
|
||||
|
||||
if b.config.SSHHostPortMin > b.config.SSHHostPortMax {
|
||||
errs = append(errs, errors.New("ssh_host_port_min must be less than ssh_host_port_max"))
|
||||
}
|
||||
|
@ -183,6 +195,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
new(stepTypeBootCommand),
|
||||
new(stepWaitForSSH),
|
||||
new(stepProvision),
|
||||
new(stepShutdown),
|
||||
}
|
||||
|
||||
// Setup the state bag
|
||||
|
|
|
@ -171,6 +171,25 @@ func TestBuilderPrepare_ISOUrl(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_ShutdownTimeout(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
||||
// Test with a bad value
|
||||
config["shutdown_timeout"] = "this is not good"
|
||||
err := b.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
config["shutdown_timeout"] = "5s"
|
||||
err = b.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("should not have error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderPrepare_SSHHostPort(t *testing.T) {
|
||||
var b Builder
|
||||
config := testConfig()
|
||||
|
|
|
@ -12,6 +12,12 @@ import (
|
|||
// A driver is able to talk to VirtualBox and perform certain
|
||||
// operations with it.
|
||||
type Driver interface {
|
||||
// Checks if the VM with the given name is running.
|
||||
IsRunning(string) (bool, error)
|
||||
|
||||
// Stop stops a running machine, forcefully.
|
||||
Stop(string) error
|
||||
|
||||
// SuppressMessages should do what needs to be done in order to
|
||||
// suppress any annoying popups from VirtualBox.
|
||||
SuppressMessages() error
|
||||
|
@ -30,6 +36,32 @@ type VBox42Driver struct {
|
|||
VBoxManagePath string
|
||||
}
|
||||
|
||||
func (d *VBox42Driver) IsRunning(name string) (bool, error) {
|
||||
var stdout bytes.Buffer
|
||||
|
||||
cmd := exec.Command(d.VBoxManagePath, "showvminfo", name, "--machinereadable")
|
||||
cmd.Stdout = &stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(stdout.String(), "\n") {
|
||||
if line == `VMState="running"` {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *VBox42Driver) Stop(name string) error {
|
||||
if err := d.VBoxManage("controlvm", name, "poweroff"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *VBox42Driver) SuppressMessages() error {
|
||||
extraData := map[string]string{
|
||||
"GUI/RegistrationData": "triesLeft=0",
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package virtualbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This step shuts down the machine. It first attempts to do so gracefully,
|
||||
// but ultimately forcefully shuts it down if that fails.
|
||||
//
|
||||
// Uses:
|
||||
// communicator packer.Communicator
|
||||
// config *config
|
||||
// driver Driver
|
||||
// ui packer.Ui
|
||||
// vmName string
|
||||
//
|
||||
// Produces:
|
||||
// <nothing>
|
||||
type stepShutdown struct{}
|
||||
|
||||
func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
|
||||
comm := state["communicator"].(packer.Communicator)
|
||||
config := state["config"].(*config)
|
||||
driver := state["driver"].(Driver)
|
||||
ui := state["ui"].(packer.Ui)
|
||||
vmName := state["vmName"].(string)
|
||||
|
||||
if config.ShutdownCommand != "" {
|
||||
ui.Say("Gracefully halting virtual machine...")
|
||||
log.Printf("Executing shutdown command: %s", config.ShutdownCommand)
|
||||
cmd := &packer.RemoteCmd{Command: config.ShutdownCommand}
|
||||
if err := comm.Start(cmd); err != nil {
|
||||
ui.Error(fmt.Sprintf("Failed to send shutdown command: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// Wait for the command to run
|
||||
cmd.Wait()
|
||||
|
||||
// Wait for the machine to actually shut down
|
||||
log.Printf("Waiting max %s for shutdown to complete", config.ShutdownTimeout)
|
||||
shutdownTimer := time.After(config.ShutdownTimeout)
|
||||
for {
|
||||
running, _ := driver.IsRunning(vmName)
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case <-shutdownTimer:
|
||||
ui.Error("Timeout while waiting for machine to shut down.")
|
||||
return multistep.ActionHalt
|
||||
default:
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := driver.Stop(vmName); err != nil {
|
||||
ui.Error(fmt.Sprintf("Error stopping VM: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("VM shut down.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
|
Loading…
Reference in New Issue