diff --git a/builder/virtualbox/common/shutdown_config.go b/builder/virtualbox/common/shutdown_config.go index 83d2224c3..ec3051701 100644 --- a/builder/virtualbox/common/shutdown_config.go +++ b/builder/virtualbox/common/shutdown_config.go @@ -10,8 +10,10 @@ import ( type ShutdownConfig struct { ShutdownCommand string `mapstructure:"shutdown_command"` RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + RawPostShutdownDelay string `mapstructure:"post_shutdown_delay"` ShutdownTimeout time.Duration `` + PostShutdownDelay time.Duration `` } func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { @@ -19,6 +21,10 @@ func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { c.RawShutdownTimeout = "5m" } + if c.RawPostShutdownDelay == "" { + c.RawPostShutdownDelay = "0s" + } + var errs []error var err error c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) @@ -26,5 +32,10 @@ func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) } + c.PostShutdownDelay, err = time.ParseDuration(c.RawPostShutdownDelay) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing post_shutdown_delay: %s", err)) + } + return errs } diff --git a/builder/virtualbox/common/shutdown_config_test.go b/builder/virtualbox/common/shutdown_config_test.go index 5da613a19..3fd3a9c95 100644 --- a/builder/virtualbox/common/shutdown_config_test.go +++ b/builder/virtualbox/common/shutdown_config_test.go @@ -43,3 +43,38 @@ func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { t.Fatalf("bad: %s", c.ShutdownTimeout) } } + +func TestShutdownConfigPrepare_PostShutdownDelay(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with a bad value + c = testShutdownConfig() + c.RawPostShutdownDelay = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + // Test with default value + c = testShutdownConfig() + c.RawPostShutdownDelay = "" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if c.PostShutdownDelay.Nanoseconds() != 0 { + t.Fatalf("bad: %s", c.PostShutdownDelay) + } + + // Test with a good one + c = testShutdownConfig() + c.RawPostShutdownDelay = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if c.PostShutdownDelay != 5*time.Second { + t.Fatalf("bad: %s", c.PostShutdownDelay) + } +} diff --git a/builder/virtualbox/common/step_shutdown.go b/builder/virtualbox/common/step_shutdown.go index 0f33692ce..8d163afed 100644 --- a/builder/virtualbox/common/step_shutdown.go +++ b/builder/virtualbox/common/step_shutdown.go @@ -23,6 +23,7 @@ import ( type StepShutdown struct { Command string Timeout time.Duration + Delay time.Duration } func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { @@ -48,6 +49,12 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { for { running, _ := driver.IsRunning(vmName) if !running { + + if s.Delay.Nanoseconds() > 0 { + log.Printf("Delay for %s after shutdown to allow locks to clear...", s.Delay) + time.Sleep(s.Delay) + } + break } diff --git a/builder/virtualbox/common/step_shutdown_test.go b/builder/virtualbox/common/step_shutdown_test.go index 215eefd30..a80dec7cd 100644 --- a/builder/virtualbox/common/step_shutdown_test.go +++ b/builder/virtualbox/common/step_shutdown_test.go @@ -103,3 +103,66 @@ func TestStepShutdown_shutdownTimeout(t *testing.T) { t.Fatal("should have error") } } + +func TestStepShutdown_shutdownDelay(t *testing.T) { + state := testState(t) + step := new(StepShutdown) + step.Command = "poweroff" + step.Timeout = 5 * time.Second + step.Delay = 2 * time.Second + + comm := new(packer.MockCommunicator) + state.Put("communicator", comm) + state.Put("vmName", "foo") + + driver := state.Get("driver").(*DriverMock) + driver.IsRunningReturn = true + start := time.Now() + + go func() { + time.Sleep(10 * time.Millisecond) + driver.Lock() + defer driver.Unlock() + driver.IsRunningReturn = false + }() + + // Test the run + + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + testDuration := time.Since(start).Seconds() + if testDuration < 2.5 || testDuration > 2.6 { + t.Fatal("incorrect duration") + } + + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + + step.Delay = 0 + + driver.IsRunningReturn = true + start = time.Now() + + go func() { + time.Sleep(10 * time.Millisecond) + driver.Lock() + defer driver.Unlock() + driver.IsRunningReturn = false + }() + + // Test the run + if action := step.Run(state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + testDuration = time.Since(start).Seconds() + if testDuration > 0.6 { + t.Fatal("incorrect duration") + } + + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + +} diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 4b3958968..f51e68d5f 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -252,6 +252,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vboxcommon.StepShutdown{ Command: b.config.ShutdownCommand, Timeout: b.config.ShutdownTimeout, + Delay: b.config.PostShutdownDelay, }, new(vboxcommon.StepRemoveDevices), &vboxcommon.StepVBoxManage{ diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index 56e349b83..9145859b2 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -120,6 +120,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &vboxcommon.StepShutdown{ Command: b.config.ShutdownCommand, Timeout: b.config.ShutdownTimeout, + Delay: b.config.PostShutdownDelay, }, new(vboxcommon.StepRemoveDevices), &vboxcommon.StepVBoxManage{