From 315d4ce5f568a426d823759a3e671f4c5a3f6cbd Mon Sep 17 00:00:00 2001 From: Jack Pearkes Date: Sat, 24 Aug 2013 13:04:51 +0200 Subject: [PATCH] builder/digitalocean: send a "shutdown" before snapshotting Instead of pulling the plug on a droplet with the "poweroff" command, we first send a soft shutdown to the API, then we poweroff to allow the snapshot to properly complete. Sending just a shutdown and then snapshotting wasn't as reliable as sending the poweroff manually, for reasons unknown to me. This fixes #332. --- builder/digitalocean/api.go | 9 +++++ builder/digitalocean/builder.go | 1 + builder/digitalocean/step_power_off.go | 15 ++++---- builder/digitalocean/step_shutdown.go | 49 ++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 builder/digitalocean/step_shutdown.go diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index a476f5efb..fbd1b7d50 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -115,6 +115,15 @@ func (d DigitalOceanClient) PowerOffDroplet(id uint) error { return err } +// Shutsdown a droplet. This is a "soft" shutdown. +func (d DigitalOceanClient) ShutdownDroplet(id uint) error { + path := fmt.Sprintf("droplets/%v/shutdown", id) + + _, err := NewRequest(d, path, url.Values{}) + + return err +} + // Creates a snaphot of a droplet by it's ID func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error { path := fmt.Sprintf("droplets/%v/snapshot", id) diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index 6ba6a5b87..cce9d7292 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -206,6 +206,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHWaitTimeout: 5 * time.Minute, }, new(common.StepProvision), + new(stepShutdown), new(stepPowerOff), new(stepSnapshot), } diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index 8d0bb340c..a0650a254 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -32,15 +32,14 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction { return multistep.ActionHalt } - ui.Say("Waiting for droplet to power off...") + log.Println("Waiting for poweroff event to complete...") - err = waitForDropletState("off", dropletId, client, c) - if err != nil { - err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) - state["error"] = err - ui.Error(err.Error()) - return multistep.ActionHalt - } + // This arbitrary sleep is because we can't wait for the state + // of the droplet to be 'off', as stepShutdown should already + // have accomplished that, and the state indicator is the same. + // We just have to assume that this event will process quickly. + log.Printf("Sleeping for %v, event_delay", c.RawEventDelay) + time.Sleep(c.eventDelay) return multistep.ActionContinue } diff --git a/builder/digitalocean/step_shutdown.go b/builder/digitalocean/step_shutdown.go new file mode 100644 index 000000000..9c516ef28 --- /dev/null +++ b/builder/digitalocean/step_shutdown.go @@ -0,0 +1,49 @@ +package digitalocean + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +type stepShutdown struct{} + +func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction { + client := state["client"].(*DigitalOceanClient) + c := state["config"].(config) + ui := state["ui"].(packer.Ui) + dropletId := state["droplet_id"].(uint) + + // Sleep arbitrarily before sending the request + // Otherwise we get "pending event" errors, even though there isn't + // one. + log.Printf("Sleeping for %v, event_delay", c.RawEventDelay) + time.Sleep(c.eventDelay) + + err := client.ShutdownDroplet(dropletId) + + if err != nil { + err := fmt.Errorf("Error shutting down droplet: %s", err) + state["error"] = err + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Waiting for droplet to shutdown...") + + err = waitForDropletState("off", dropletId, client, c) + if err != nil { + err := fmt.Errorf("Error waiting for droplet to become 'off': %s", err) + state["error"] = err + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *stepShutdown) Cleanup(state map[string]interface{}) { + // no cleanup +}