diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go index 5c7575bf1..5d393c9ad 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go @@ -3,8 +3,12 @@ package openstack import ( "fmt" "log" + "time" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/rackspace/gophercloud/pagination" @@ -45,7 +49,7 @@ func resourceNetworkingFloatingIPV2() *schema.Resource { func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkClient, err := config.networkingV2Client(d.Get("region").(string)) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack network client: %s", err) } @@ -62,11 +66,23 @@ func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) PortID: d.Get("port_id").(string), } log.Printf("[DEBUG] Create Options: %#v", createOpts) - floatingIP, err := floatingips.Create(networkClient, createOpts).Extract() + floatingIP, err := floatingips.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error allocating floating IP: %s", err) } + log.Printf("[DEBUG] Waiting for OpenStack Neutron Floating IP (%s) to become available.", floatingIP.ID) + + stateConf := &resource.StateChangeConf{ + Target: "ACTIVE", + Refresh: waitForFloatingIPActive(networkingClient, floatingIP.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + d.SetId(floatingIP.ID) return resourceNetworkFloatingIPV2Read(d, meta) @@ -74,12 +90,12 @@ func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkClient, err := config.networkingV2Client(d.Get("region").(string)) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack network client: %s", err) } - floatingIP, err := floatingips.Get(networkClient, d.Id()).Extract() + floatingIP, err := floatingips.Get(networkingClient, d.Id()).Extract() if err != nil { return CheckDeleted(d, err, "floating IP") } @@ -97,7 +113,7 @@ func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e func resourceNetworkFloatingIPV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkClient, err := config.networkingV2Client(d.Get("region").(string)) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack network client: %s", err) } @@ -110,7 +126,7 @@ func resourceNetworkFloatingIPV2Update(d *schema.ResourceData, meta interface{}) log.Printf("[DEBUG] Update Options: %#v", updateOpts) - _, err = floatingips.Update(networkClient, d.Id(), updateOpts).Extract() + _, err = floatingips.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating floating IP: %s", err) } @@ -120,28 +136,38 @@ func resourceNetworkFloatingIPV2Update(d *schema.ResourceData, meta interface{}) func resourceNetworkFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkClient, err := config.networkingV2Client(d.Get("region").(string)) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack network client: %s", err) } - err = floatingips.Delete(networkClient, d.Id()).ExtractErr() - if err != nil { - return fmt.Errorf("Error deleting floating IP: %s", err) + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: "DELETED", + Refresh: waitForFloatingIPDelete(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Floating IP: %s", err) + } + d.SetId("") return nil } func getNetworkID(d *schema.ResourceData, meta interface{}, networkName string) (string, error) { config := meta.(*Config) - networkClient, err := config.networkingV2Client(d.Get("region").(string)) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return "", fmt.Errorf("Error creating OpenStack network client: %s", err) } opts := networks.ListOpts{Name: networkName} - pager := networks.List(networkClient, opts) + pager := networks.List(networkingClient, opts) networkID := "" err = pager.EachPage(func(page pagination.Page) (bool, error) { @@ -165,13 +191,13 @@ func getNetworkID(d *schema.ResourceData, meta interface{}, networkName string) func getNetworkName(d *schema.ResourceData, meta interface{}, networkID string) (string, error) { config := meta.(*Config) - networkClient, err := config.networkingV2Client(d.Get("region").(string)) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return "", fmt.Errorf("Error creating OpenStack network client: %s", err) } opts := networks.ListOpts{ID: networkID} - pager := networks.List(networkClient, opts) + pager := networks.List(networkingClient, opts) networkName := "" err = pager.EachPage(func(page pagination.Page) (bool, error) { @@ -192,3 +218,52 @@ func getNetworkName(d *schema.ResourceData, meta interface{}, networkID string) return networkName, err } + +func waitForFloatingIPActive(networkingClient *gophercloud.ServiceClient, fId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + f, err := floatingips.Get(networkingClient, fId).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack Neutron Floating IP: %+v", f) + if f.Status == "DOWN" || f.Status == "ACTIVE" { + return f, "ACTIVE", nil + } + + return f, "", nil + } +} + +func waitForFloatingIPDelete(networkingClient *gophercloud.ServiceClient, fId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack Floating IP %s.\n", fId) + + f, err := floatingips.Get(networkingClient, fId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return f, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId) + return f, "DELETED", nil + } + } + + err = floatingips.Delete(networkingClient, fId).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return f, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Floating IP %s", fId) + return f, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Floating IP %s still active.\n", fId) + return f, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index b4a3db7ef..4073a7612 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -4,8 +4,12 @@ import ( "fmt" "log" "strconv" + "time" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/networks" ) @@ -87,6 +91,19 @@ func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) } log.Printf("[INFO] Network ID: %s", n.ID) + log.Printf("[DEBUG] Waiting for Network (%s) to become available", n.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD"}, + Target: "ACTIVE", + Refresh: waitForNetworkActive(networkingClient, n.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + d.SetId(n.ID) return resourceNetworkingNetworkV2Read(d, meta) @@ -163,7 +180,16 @@ func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - err = networks.Delete(networkingClient, d.Id()).ExtractErr() + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: "DELETED", + Refresh: waitForNetworkDelete(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err) } @@ -171,3 +197,52 @@ func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) d.SetId("") return nil } + +func waitForNetworkActive(networkingClient *gophercloud.ServiceClient, networkId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + n, err := networks.Get(networkingClient, networkId).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack Neutron Network: %+v", n) + if n.Status == "DOWN" || n.Status == "ACTIVE" { + return n, "ACTIVE", nil + } + + return n, n.Status, nil + } +} + +func waitForNetworkDelete(networkingClient *gophercloud.ServiceClient, networkId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack Network %s.\n", networkId) + + n, err := networks.Get(networkingClient, networkId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return n, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId) + return n, "DELETED", nil + } + } + + err = networks.Delete(networkingClient, networkId).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return n, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Network %s", networkId) + return n, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Network %s still active.\n", networkId) + return n, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go index 5bff60532..ca09c6b98 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go @@ -2,17 +2,36 @@ package openstack import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/rackspace/gophercloud/openstack/networking/v2/networks" + "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" ) func TestAccNetworkingV2Network_basic(t *testing.T) { + region := os.Getenv(OS_REGION_NAME) + var network networks.Network + var testAccNetworkingV2Network_basic = fmt.Sprintf(` + resource "openstack_networking_network_v2" "foo" { + region = "%s" + name = "network_1" + admin_state_up = "true" + }`, region) + + var testAccNetworkingV2Network_update = fmt.Sprintf(` + resource "openstack_networking_network_v2" "foo" { + region = "%s" + name = "network_2" + admin_state_up = "true" + }`, region) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -34,6 +53,57 @@ func TestAccNetworkingV2Network_basic(t *testing.T) { }) } +func TestAccNetworkingV2Network_netstack(t *testing.T) { + region := os.Getenv(OS_REGION_NAME) + + var network networks.Network + var subnet subnets.Subnet + var router routers.Router + + var testAccNetworkingV2Network_netstack = fmt.Sprintf(` + resource "openstack_networking_network_v2" "foo" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "foo" { + region = "%s" + name = "subnet_1" + network_id = "${openstack_networking_network_v2.foo.id}" + cidr = "192.168.10.0/24" + ip_version = 4 + } + + resource "openstack_networking_router_v2" "foo" { + region = "%s" + name = "router_1" + } + + resource "openstack_networking_router_interface_v2" "foo" { + region = "%s" + router_id = "${openstack_networking_router_v2.foo.id}" + subnet_id = "${openstack_networking_subnet_v2.foo.id}" + }`, region, region, region, region) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2NetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2Network_netstack, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2NetworkExists(t, "openstack_networking_network_v2.foo", &network), + testAccCheckNetworkingV2SubnetExists(t, "openstack_networking_subnet_v2.foo", &subnet), + testAccCheckNetworkingV2RouterExists(t, "openstack_networking_router_v2.foo", &router), + testAccCheckNetworkingV2RouterInterfaceExists(t, "openstack_networking_router_interface_v2.foo"), + ), + }, + }, + }) +} + func testAccCheckNetworkingV2NetworkDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkingClient, err := config.networkingV2Client(OS_REGION_NAME) diff --git a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go index 1e60c30ef..2fc0b4bbb 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go @@ -3,8 +3,11 @@ package openstack import ( "fmt" "log" + "time" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/rackspace/gophercloud/openstack/networking/v2/ports" @@ -55,6 +58,19 @@ func resourceNetworkingRouterInterfaceV2Create(d *schema.ResourceData, meta inte } log.Printf("[INFO] Router interface Port ID: %s", n.PortID) + log.Printf("[DEBUG] Waiting for Router Interface (%s) to become available", n.PortID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD", "PENDING_CREATE", "PENDING_UPDATE"}, + Target: "ACTIVE", + Refresh: waitForRouterInterfaceActive(networkingClient, n.PortID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + d.SetId(n.PortID) return resourceNetworkingRouterInterfaceV2Read(d, meta) @@ -93,11 +109,16 @@ func resourceNetworkingRouterInterfaceV2Delete(d *schema.ResourceData, meta inte return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - removeOpts := routers.InterfaceOpts{ - SubnetID: d.Get("subnet_id").(string), + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: "DELETED", + Refresh: waitForRouterInterfaceDelete(networkingClient, d), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, } - _, err = routers.RemoveInterface(networkingClient, d.Get("router_id").(string), removeOpts).Extract() + _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf("Error deleting OpenStack Neutron Router Interface: %s", err) } @@ -105,3 +126,55 @@ func resourceNetworkingRouterInterfaceV2Delete(d *schema.ResourceData, meta inte d.SetId("") return nil } + +func waitForRouterInterfaceActive(networkingClient *gophercloud.ServiceClient, rId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + r, err := ports.Get(networkingClient, rId).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack Neutron Router Interface: %+v", r) + return r, r.Status, nil + } +} + +func waitForRouterInterfaceDelete(networkingClient *gophercloud.ServiceClient, d *schema.ResourceData) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + routerId := d.Get("router_id").(string) + routerInterfaceId := d.Id() + + log.Printf("[DEBUG] Attempting to delete OpenStack Router Interface %s.\n", routerInterfaceId) + + removeOpts := routers.InterfaceOpts{ + SubnetID: d.Get("subnet_id").(string), + } + + r, err := ports.Get(networkingClient, routerInterfaceId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s", routerInterfaceId) + return r, "DELETED", nil + } + } + + _, err = routers.RemoveInterface(networkingClient, routerId, removeOpts).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Router Interface %s", routerInterfaceId) + return r, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Router Interface %s still active.\n", routerInterfaceId) + return r, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_v2.go index aefc910bf..9c030eafb 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2.go @@ -3,8 +3,11 @@ package openstack import ( "fmt" "log" + "time" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" ) @@ -81,6 +84,18 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) } log.Printf("[INFO] Router ID: %s", n.ID) + log.Printf("[DEBUG] Waiting for OpenStack Neutron Router (%s) to become available", n.ID) + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD", "PENDING_CREATE", "PENDING_UPDATE"}, + Target: "ACTIVE", + Refresh: waitForRouterActive(networkingClient, n.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + d.SetId(n.ID) return resourceNetworkingRouterV2Read(d, meta) @@ -150,7 +165,16 @@ func resourceNetworkingRouterV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - err = routers.Delete(networkingClient, d.Id()).ExtractErr() + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: "DELETED", + Refresh: waitForRouterDelete(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf("Error deleting OpenStack Neutron Router: %s", err) } @@ -158,3 +182,48 @@ func resourceNetworkingRouterV2Delete(d *schema.ResourceData, meta interface{}) d.SetId("") return nil } + +func waitForRouterActive(networkingClient *gophercloud.ServiceClient, routerId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + r, err := routers.Get(networkingClient, routerId).Extract() + if err != nil { + return nil, r.Status, err + } + + log.Printf("[DEBUG] OpenStack Neutron Router: %+v", r) + return r, r.Status, nil + } +} + +func waitForRouterDelete(networkingClient *gophercloud.ServiceClient, routerId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack Router %s.\n", routerId) + + r, err := routers.Get(networkingClient, routerId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Router %s", routerId) + return r, "DELETED", nil + } + } + + err = routers.Delete(networkingClient, routerId).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return r, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Router %s", routerId) + return r, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Router %s still active.\n", routerId) + return r, "ACTIVE", nil + } +} diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index ab40ae804..74ac91a34 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -3,9 +3,13 @@ package openstack import ( "fmt" "log" + "time" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" ) @@ -140,6 +144,17 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) } log.Printf("[INFO] Subnet ID: %s", s.ID) + log.Printf("[DEBUG] Waiting for Subnet (%s) to become available", s.ID) + stateConf := &resource.StateChangeConf{ + Target: "ACTIVE", + Refresh: waitForSubnetActive(networkingClient, s.ID), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + d.SetId(s.ID) return resourceNetworkingSubnetV2Read(d, meta) @@ -220,7 +235,16 @@ func resourceNetworkingSubnetV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - err = subnets.Delete(networkingClient, d.Id()).ExtractErr() + stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, + Target: "DELETED", + Refresh: waitForSubnetDelete(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf("Error deleting OpenStack Neutron Subnet: %s", err) } @@ -263,3 +287,48 @@ func resourceSubnetHostRoutesV2(d *schema.ResourceData) []subnets.HostRoute { } return hr } + +func waitForSubnetActive(networkingClient *gophercloud.ServiceClient, subnetId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + s, err := subnets.Get(networkingClient, subnetId).Extract() + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] OpenStack Neutron Subnet: %+v", s) + return s, "ACTIVE", nil + } +} + +func waitForSubnetDelete(networkingClient *gophercloud.ServiceClient, subnetId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Attempting to delete OpenStack Subnet %s.\n", subnetId) + + s, err := subnets.Get(networkingClient, subnetId).Extract() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return s, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId) + return s, "DELETED", nil + } + } + + err = subnets.Delete(networkingClient, subnetId).ExtractErr() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return s, "ACTIVE", err + } + if errCode.Actual == 404 { + log.Printf("[DEBUG] Successfully deleted OpenStack Subnet %s", subnetId) + return s, "DELETED", nil + } + } + + log.Printf("[DEBUG] OpenStack Subnet %s still active.\n", subnetId) + return s, "ACTIVE", nil + } +}