diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 57d346dc6..7f1b8f9bc 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -18,6 +18,7 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/schedulerhints" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" @@ -318,6 +319,11 @@ func resourceComputeInstanceV2() *schema.Resource { }, Set: resourceComputeInstancePersonalityHash, }, + "stop_before_destroy": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, } } @@ -808,6 +814,27 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack compute client: %s", err) } + if d.Get("stop_before_destroy").(bool) { + err = startstop.Stop(computeClient, d.Id()).ExtractErr() + if err != nil { + log.Printf("[WARN] Error stopping OpenStack instance: %s", err) + } else { + stopStateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: []string{"SHUTOFF"}, + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), + Timeout: 3 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + log.Printf("[DEBUG] Waiting for instance (%s) to stop", d.Id()) + _, err = stopStateConf.WaitForState() + if err != nil { + log.Printf("[WARN] Error waiting for instance (%s) to stop: %s, proceeding to delete", d.Id(), err) + } + } + } + err = servers.Delete(computeClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack server: %s", err) @@ -817,7 +844,7 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) stateConf := &resource.StateChangeConf{ - Pending: []string{"ACTIVE"}, + Pending: []string{"ACTIVE", "SHUTOFF"}, Target: []string{"DELETED"}, Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 30 * time.Minute, diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index fd08536a7..cc87bf707 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -840,3 +840,31 @@ func testAccCheckComputeV2InstanceInstanceIDsDoNotMatch( return nil } } + +func TestAccComputeV2Instance_stop_before_destroy(t *testing.T) { + var instance servers.Server + var testAccComputeV2Instance_stop_before_destroy = fmt.Sprintf(` + resource "openstack_compute_instance_v2" "foo" { + name = "terraform-test" + security_groups = ["default"] + network { + uuid = "%s" + } + stop_before_destroy = true + }`, + os.Getenv("OS_NETWORK_ID")) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2InstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2Instance_stop_before_destroy, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), + ), + }, + }, + }) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/doc.go b/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/doc.go new file mode 100644 index 000000000..d2729f874 --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/doc.go @@ -0,0 +1,5 @@ +/* +Package startstop provides functionality to start and stop servers that have +been provisioned by the OpenStack Compute service. +*/ +package startstop diff --git a/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/fixtures.go b/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/fixtures.go new file mode 100644 index 000000000..e2c33fe2d --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/fixtures.go @@ -0,0 +1,29 @@ +// +build fixtures + +package startstop + +import ( + "net/http" + "testing" + + th "github.com/rackspace/gophercloud/testhelper" + "github.com/rackspace/gophercloud/testhelper/client" +) + +func mockStartServerResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{"os-start": null}`) + w.WriteHeader(http.StatusAccepted) + }) +} + +func mockStopServerResponse(t *testing.T, id string) { + th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Auth-Token", client.TokenID) + th.TestJSONRequest(t, r, `{"os-stop": null}`) + w.WriteHeader(http.StatusAccepted) + }) +} diff --git a/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go b/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go new file mode 100644 index 000000000..0e090e69f --- /dev/null +++ b/vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop/requests.go @@ -0,0 +1,23 @@ +package startstop + +import "github.com/rackspace/gophercloud" + +func actionURL(client *gophercloud.ServiceClient, id string) string { + return client.ServiceURL("servers", id, "action") +} + +// Start is the operation responsible for starting a Compute server. +func Start(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { + var res gophercloud.ErrResult + reqBody := map[string]interface{}{"os-start": nil} + _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil) + return res +} + +// Stop is the operation responsible for stopping a Compute server. +func Stop(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult { + var res gophercloud.ErrResult + reqBody := map[string]interface{}{"os-stop": nil} + _, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil) + return res +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 67b82fc3c..4668171d3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1184,6 +1184,12 @@ "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", "revisionTime": "2016-06-03T22:34:01Z" }, + { + "checksumSHA1": "jqSmOJoNKVtGwDhuoilAF7iftqA=", + "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop", + "revision": "d47105ce4ef90cea9a14b85c8dd172b760085828", + "revisionTime": "2016-06-03T22:34:01Z" + }, { "checksumSHA1": "rHEOEAm10HDsfBLU8FqKSUdwqFY=", "path": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/tenantnetworks", diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index af7783122..a14d84ecf 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -264,6 +264,11 @@ The following arguments are supported: defining one or more files and their contents. The personality structure is described below. +* `stop_before_destroy` - (Optional) Whether to try stop instance gracefully + before destroying it, thus giving chance for guest OS daemons to stop correctly. + If instance doesn't stop within timeout, it will be destroyed anyway. + + The `network` block supports: * `uuid` - (Required unless `port` or `name` is provided) The network UUID to