From 94d369f5580b8e4cb91a3d204353863afd3bba50 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Sep 2013 22:51:28 -0700 Subject: [PATCH] builder/digitalocean: give up on graceful shutdown more quickly --- builder/digitalocean/step_power_off.go | 2 +- builder/digitalocean/step_shutdown.go | 58 ++++++++++++++++++-------- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index 06f05c52f..9aa5e30b8 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -38,6 +38,7 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + log.Println("Waiting for poweroff event to complete...") err = waitForDropletState("off", dropletId, client, c.stateTimeout) if err != nil { state.Put("error", err) @@ -45,7 +46,6 @@ func (s *stepPowerOff) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - log.Println("Waiting for poweroff event to complete...") return multistep.ActionContinue } diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go index c50dc0b95..7ef71bd7c 100644 --- a/builder/digitalocean/step_shutdown.go +++ b/builder/digitalocean/step_shutdown.go @@ -12,6 +12,7 @@ type stepShutdown struct{} func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { client := state.Get("client").(*DigitalOceanClient) + c := state.Get("config").(config) ui := state.Get("ui").(packer.Ui) dropletId := state.Get("droplet_id").(uint) @@ -19,30 +20,53 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction { // of times because sometimes it says it completed when it actually // did absolutely nothing (*ALAKAZAM!* magic!). We give up after // a pretty arbitrary amount of time. - var err error ui.Say("Gracefully shutting down droplet...") - for attempts := 1; attempts <= 10; attempts++ { - log.Printf("ShutdownDroplet attempt #%d...", attempts) - err := client.ShutdownDroplet(dropletId) - if err != nil { - err := fmt.Errorf("Error shutting down droplet: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - err = waitForDropletState("off", dropletId, client, 20*time.Second) - if err == nil { - break - } - } - + err := client.ShutdownDroplet(dropletId) if err != nil { + // If we get an error the first time, actually report it + err := fmt.Errorf("Error shutting down droplet: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } + // A channel we use as a flag to end our goroutines + done := make(chan struct{}) + shutdownRetryDone := make(chan struct{}) + + // Make sure we wait for the shutdown retry goroutine to end + // before moving on. + defer func() { + close(done) + <-shutdownRetryDone + }() + + // Start a goroutine that just keeps trying to shut down the + // droplet. + go func() { + defer close(shutdownRetryDone) + + for attempts := 2; attempts > 0; attempts++ { + log.Printf("ShutdownDroplet attempt #%d...", attempts) + err := client.ShutdownDroplet(dropletId) + if err != nil { + log.Printf("Shutdown retry error: %s", err) + } + + select { + case <-done: + return + case <-time.After(20 * time.Second): + // Retry! + } + } + }() + + err = waitForDropletState("off", dropletId, client, 2 * time.Minute) + if err != nil { + log.Printf("Error waiting for graceful off: %s", err) + } + return multistep.ActionContinue }