diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 7b3e65dd4302..7e4b6d99fef2 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -63,4 +63,9 @@ func testAccPreCheck(t *testing.T) { if v1 == "" && v2 == "" { t.Fatal("OS_FLAVOR_ID or OS_FLAVOR_NAME must be set for acceptance tests") } + + v = os.Getenv("OS_NETWORK_ID") + if v == "" { + t.Fatal("OS_NETWORK_ID must be set for acceptance tests") + } } diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go index a298a87d1854..d6fe43b529a9 100644 --- a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go @@ -2,12 +2,14 @@ package openstack import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) func TestAccComputeV2FloatingIP_basic(t *testing.T) { @@ -28,6 +30,40 @@ func TestAccComputeV2FloatingIP_basic(t *testing.T) { }) } +func TestAccComputeV2FloatingIP_attach(t *testing.T) { + var instance servers.Server + var fip floatingip.FloatingIP + var testAccComputeV2FloatingIP_attach = fmt.Sprintf(` + resource "openstack_compute_floatingip_v2" "myip" { + } + + resource "openstack_compute_instance_v2" "foo" { + name = "terraform-test" + floating_ip = "${openstack_compute_floatingip_v2.myip.address}" + + network { + uuid = "%s" + } + }`, + os.Getenv("OS_NETWORK_ID")) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2FloatingIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2FloatingIP_attach, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip", &fip), + testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), + testAccCheckComputeV2InstanceFloatingIPAttach(&instance, &fip), + ), + }, + }, + }) +} + func testAccCheckComputeV2FloatingIPDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) computeClient, err := config.computeV2Client(OS_REGION_NAME) @@ -83,9 +119,4 @@ func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingi var testAccComputeV2FloatingIP_basic = ` resource "openstack_compute_floatingip_v2" "foo" { - } - - resource "openstack_compute_instance_v2" "bar" { - name = "terraform-acc-floating-ip-test" - floating_ip = "${openstack_compute_floatingip_v2.foo.address}" }` diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index b5fe36a10666..a6cf8a597860 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -13,14 +13,14 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" + "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" "github.com/rackspace/gophercloud/openstack/compute/v2/images" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" - "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/rackspace/gophercloud/pagination" ) @@ -76,7 +76,7 @@ func resourceComputeInstanceV2() *schema.Resource { Optional: true, ForceNew: false, }, - "user_data": &schema.Schema{ + "user_data": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, @@ -106,19 +106,37 @@ func resourceComputeInstanceV2() *schema.Resource { Type: schema.TypeList, Optional: true, ForceNew: true, + Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "uuid": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, }, "port": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, + }, + "fixed_ip_v4": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, }, - "fixed_ip": &schema.Schema{ + "fixed_ip_v6": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, + }, + "mac": &schema.Schema{ + Type: schema.TypeString, + Computed: true, }, }, }, @@ -230,13 +248,27 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e return err } + networkDetails, err := resourceInstanceNetworks(computeClient, d) + if err != nil { + return err + } + + networks := make([]servers.Network, len(networkDetails)) + for i, net := range networkDetails { + networks[i] = servers.Network{ + UUID: net["uuid"].(string), + Port: net["port"].(string), + FixedIP: net["fixed_ip_v4"].(string), + } + } + createOpts = &servers.CreateOpts{ Name: d.Get("name").(string), ImageRef: imageId, FlavorRef: flavorId, SecurityGroups: resourceInstanceSecGroupsV2(d), AvailabilityZone: d.Get("availability_zone").(string), - Networks: resourceInstanceNetworksV2(d), + Networks: networks, Metadata: resourceInstanceMetadataV2(d), ConfigDrive: d.Get("config_drive").(bool), AdminPass: d.Get("admin_pass").(string), @@ -291,18 +323,8 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e } floatingIP := d.Get("floating_ip").(string) if floatingIP != "" { - networkingClient, err := config.networkingV2Client(d.Get("region").(string)) - if err != nil { - return fmt.Errorf("Error creating OpenStack compute client: %s", err) - } - - allFloatingIPs, err := getFloatingIPs(networkingClient) - if err != nil { - return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) - } - err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), server.ID) - if err != nil { - return fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + if err := floatingip.Associate(computeClient, server.ID, floatingIP).ExtractErr(); err != nil { + return fmt.Errorf("Error associating floating IP: %s", err) } } @@ -338,67 +360,85 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) d.Set("name", server.Name) + + // begin reading the network configuration d.Set("access_ip_v4", server.AccessIPv4) d.Set("access_ip_v6", server.AccessIPv6) - hostv4 := server.AccessIPv4 - if hostv4 == "" { - if publicAddressesRaw, ok := server.Addresses["public"]; ok { - publicAddresses := publicAddressesRaw.([]interface{}) - for _, paRaw := range publicAddresses { - pa := paRaw.(map[string]interface{}) - if pa["version"].(float64) == 4 { - hostv4 = pa["addr"].(string) - break - } - } - } + hostv6 := server.AccessIPv6 + + networkDetails, err := resourceInstanceNetworks(computeClient, d) + addresses := resourceInstanceAddresses(server.Addresses) + if err != nil { + return err } - // If no host found, just get the first IPv4 we find - if hostv4 == "" { - for _, networkAddresses := range server.Addresses { - for _, element := range networkAddresses.([]interface{}) { - address := element.(map[string]interface{}) - if address["version"].(float64) == 4 { - hostv4 = address["addr"].(string) - break + // if there are no networkDetails, make networks at least a length of 1 + networkLength := 1 + if len(networkDetails) > 0 { + networkLength = len(networkDetails) + } + networks := make([]map[string]interface{}, networkLength) + + // Loop through all networks and addresses, + // merge relevant address details. + if len(networkDetails) == 0 { + for netName, n := range addresses { + if floatingIP, ok := n["floating_ip"]; ok { + hostv4 = floatingIP.(string) + } else { + if hostv4 == "" && n["fixed_ip_v4"] != nil { + hostv4 = n["fixed_ip_v4"].(string) } } - } - } - d.Set("access_ip_v4", hostv4) - log.Printf("hostv4: %s", hostv4) - hostv6 := server.AccessIPv6 - if hostv6 == "" { - if publicAddressesRaw, ok := server.Addresses["public"]; ok { - publicAddresses := publicAddressesRaw.([]interface{}) - for _, paRaw := range publicAddresses { - pa := paRaw.(map[string]interface{}) - if pa["version"].(float64) == 6 { - hostv6 = fmt.Sprintf("[%s]", pa["addr"].(string)) - break - } + if hostv6 == "" && n["fixed_ip_v6"] != nil { + hostv6 = n["fixed_ip_v6"].(string) + } + + networks[0] = map[string]interface{}{ + "name": netName, + "fixed_ip_v4": n["fixed_ip_v4"], + "fixed_ip_v6": n["fixed_ip_v6"], + "mac": n["mac"], } } - } + } else { + for i, net := range networkDetails { + n := addresses[net["name"].(string)] - // If no hostv6 found, just get the first IPv6 we find - if hostv6 == "" { - for _, networkAddresses := range server.Addresses { - for _, element := range networkAddresses.([]interface{}) { - address := element.(map[string]interface{}) - if address["version"].(float64) == 6 { - hostv6 = fmt.Sprintf("[%s]", address["addr"].(string)) - break + if floatingIP, ok := n["floating_ip"]; ok { + hostv4 = floatingIP.(string) + } else { + if hostv4 == "" && n["fixed_ip_v4"] != nil { + hostv4 = n["fixed_ip_v4"].(string) } } + + if hostv6 == "" && n["fixed_ip_v6"] != nil { + hostv6 = n["fixed_ip_v6"].(string) + } + + networks[i] = map[string]interface{}{ + "uuid": networkDetails[i]["uuid"], + "name": networkDetails[i]["name"], + "port": networkDetails[i]["port"], + "fixed_ip_v4": n["fixed_ip_v4"], + "fixed_ip_v6": n["fixed_ip_v6"], + "mac": n["mac"], + } } } + + log.Printf("[DEBUG] new networks: %+v", networks) + + d.Set("network", networks) + d.Set("access_ip_v4", hostv4) d.Set("access_ip_v6", hostv6) + log.Printf("hostv4: %s", hostv4) log.Printf("hostv6: %s", hostv6) + // prefer the v6 address if no v4 address exists. preferredv := "" if hostv4 != "" { preferredv = hostv4 @@ -413,6 +453,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err "host": preferredv, }) } + // end network configuration d.Set("metadata", server.Metadata) @@ -553,20 +594,20 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e } if d.HasChange("floating_ip") { - floatingIP := d.Get("floating_ip").(string) - if floatingIP != "" { - networkingClient, err := config.networkingV2Client(d.Get("region").(string)) - if err != nil { - return fmt.Errorf("Error creating OpenStack compute client: %s", err) + oldFIP, newFIP := d.GetChange("floating_ip") + log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) + log.Printf("[DEBUG] New Floating IP: %v", newFIP) + if oldFIP.(string) != "" { + log.Printf("[DEBUG] Attemping to disassociate %s from %s", oldFIP, d.Id()) + if err := floatingip.Disassociate(computeClient, d.Id(), oldFIP.(string)).ExtractErr(); err != nil { + return fmt.Errorf("Error disassociating Floating IP during update: %s", err) } + } - allFloatingIPs, err := getFloatingIPs(networkingClient) - if err != nil { - return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) - } - err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), d.Id()) - if err != nil { - fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + if newFIP.(string) != "" { + log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id()) + if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil { + return fmt.Errorf("Error associating Floating IP during update: %s", err) } } } @@ -722,18 +763,91 @@ func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { return secgroups } -func resourceInstanceNetworksV2(d *schema.ResourceData) []servers.Network { +func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { rawNetworks := d.Get("network").([]interface{}) - networks := make([]servers.Network, len(rawNetworks)) + newNetworks := make([]map[string]interface{}, len(rawNetworks)) + var tenantnet tenantnetworks.Network + + tenantNetworkExt := true for i, raw := range rawNetworks { rawMap := raw.(map[string]interface{}) - networks[i] = servers.Network{ - UUID: rawMap["uuid"].(string), - Port: rawMap["port"].(string), - FixedIP: rawMap["fixed_ip"].(string), + + allPages, err := tenantnetworks.List(computeClient).AllPages() + if err != nil { + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) + if !ok { + return nil, err + } + + if errCode.Actual == 404 { + tenantNetworkExt = false + } else { + return nil, err + } + } + + networkID := "" + networkName := "" + if tenantNetworkExt { + networkList, err := tenantnetworks.ExtractNetworks(allPages) + if err != nil { + return nil, err + } + + for _, network := range networkList { + if network.Name == rawMap["name"] { + tenantnet = network + } + if network.ID == rawMap["uuid"] { + tenantnet = network + } + } + + networkID = tenantnet.ID + networkName = tenantnet.Name + } else { + networkID = rawMap["uuid"].(string) + networkName = rawMap["name"].(string) + } + + newNetworks[i] = map[string]interface{}{ + "uuid": networkID, + "name": networkName, + "port": rawMap["port"].(string), + "fixed_ip_v4": rawMap["fixed_ip_v4"].(string), } } - return networks + + log.Printf("[DEBUG] networks: %+v", newNetworks) + + return newNetworks, nil +} + +func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { + + addrs := make(map[string]map[string]interface{}) + for n, networkAddresses := range addresses { + addrs[n] = make(map[string]interface{}) + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + if address["OS-EXT-IPS:type"] == "floating" { + addrs[n]["floating_ip"] = address["addr"] + } else { + if address["version"].(float64) == 4 { + addrs[n]["fixed_ip_v4"] = address["addr"].(string) + } else { + addrs[n]["fixed_ip_v6"] = fmt.Sprintf("[%s]", address["addr"].(string)) + } + } + if mac, ok := address["OS-EXT-IPS-MAC:mac_addr"]; ok { + addrs[n]["mac"] = mac.(string) + } + } + } + + log.Printf("[DEBUG] Addresses: %+v", addresses) + + return addrs } func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { @@ -759,75 +873,6 @@ func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interfa return bfvOpts } -func extractFloatingIPFromIP(ips []floatingips.FloatingIP, IP string) *floatingips.FloatingIP { - for _, floatingIP := range ips { - if floatingIP.FloatingIP == IP { - return &floatingIP - } - } - return nil -} - -func assignFloatingIP(networkingClient *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, instanceID string) error { - portID, err := getInstancePortID(networkingClient, instanceID) - if err != nil { - return err - } - return floatingips.Update(networkingClient, floatingIP.ID, floatingips.UpdateOpts{ - PortID: portID, - }).Err -} - -func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID string) (string, error) { - pager := ports.List(networkingClient, ports.ListOpts{ - DeviceID: instanceID, - }) - - var portID string - err := pager.EachPage(func(page pagination.Page) (bool, error) { - portList, err := ports.ExtractPorts(page) - if err != nil { - return false, err - } - for _, port := range portList { - portID = port.ID - return false, nil - } - return true, nil - }) - - if err != nil { - return "", err - } - - if portID == "" { - return "", fmt.Errorf("Cannot find port for instance %s", instanceID) - } - - return portID, nil -} - -func getFloatingIPs(networkingClient *gophercloud.ServiceClient) ([]floatingips.FloatingIP, error) { - pager := floatingips.List(networkingClient, floatingips.ListOpts{}) - - ips := []floatingips.FloatingIP{} - err := pager.EachPage(func(page pagination.Page) (bool, error) { - floatingipList, err := floatingips.ExtractFloatingIPs(page) - if err != nil { - return false, err - } - for _, f := range floatingipList { - ips = append(ips, f) - } - return true, nil - }) - - if err != nil { - return nil, err - } - return ips, nil -} - func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { imageId := d.Get("image_id").(string) 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 f4c6c8557da6..587df56520d5 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -2,12 +2,14 @@ package openstack import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/pagination" @@ -15,6 +17,17 @@ import ( func TestAccComputeV2Instance_basic(t *testing.T) { var instance servers.Server + var testAccComputeV2Instance_basic = fmt.Sprintf(` + resource "openstack_compute_instance_v2" "foo" { + name = "terraform-test" + network { + uuid = "%s" + } + metadata { + foo = "bar" + } + }`, + os.Getenv("OS_NETWORK_ID")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -53,6 +66,40 @@ func TestAccComputeV2Instance_volumeAttach(t *testing.T) { }) } +func TestAccComputeV2Instance_floatingIPAttach(t *testing.T) { + var instance servers.Server + var fip floatingip.FloatingIP + var testAccComputeV2Instance_floatingIPAttach = fmt.Sprintf(` + resource "openstack_compute_floatingip_v2" "myip" { + } + + resource "openstack_compute_instance_v2" "foo" { + name = "terraform-test" + floating_ip = "${openstack_compute_floatingip_v2.myip.address}" + + network { + uuid = "%s" + } + }`, + 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_floatingIPAttach, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip", &fip), + testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), + testAccCheckComputeV2InstanceFloatingIPAttach(&instance, &fip), + ), + }, + }, + }) +} + func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) computeClient, err := config.computeV2Client(OS_REGION_NAME) @@ -159,15 +206,17 @@ func testAccCheckComputeV2InstanceVolumeAttachment( } } -var testAccComputeV2Instance_basic = fmt.Sprintf(` - resource "openstack_compute_instance_v2" "foo" { - region = "%s" - name = "terraform-test" - metadata { - foo = "bar" +func testAccCheckComputeV2InstanceFloatingIPAttach( + instance *servers.Server, fip *floatingip.FloatingIP) resource.TestCheckFunc { + return func(s *terraform.State) error { + if fip.InstanceID == instance.ID { + return nil } - }`, - OS_REGION_NAME) + + return fmt.Errorf("Floating IP %s was not attached to instance %s", fip.ID, instance.ID) + + } +} var testAccComputeV2Instance_volumeAttach = fmt.Sprintf(` resource "openstack_blockstorage_volume_v1" "myvol" { diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go index 5c8ae38e3752..a989f2774dbe 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go @@ -2,11 +2,13 @@ package openstack import ( "fmt" + "os" "testing" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" ) @@ -28,6 +30,40 @@ func TestAccNetworkingV2FloatingIP_basic(t *testing.T) { }) } +func TestAccNetworkingV2FloatingIP_attach(t *testing.T) { + var instance servers.Server + var fip floatingips.FloatingIP + var testAccNetworkV2FloatingIP_attach = fmt.Sprintf(` + resource "openstack_networking_floatingip_v2" "myip" { + } + + resource "openstack_compute_instance_v2" "foo" { + name = "terraform-test" + floating_ip = "${openstack_networking_floatingip_v2.myip.address}" + + network { + uuid = "%s" + } + }`, + os.Getenv("OS_NETWORK_ID")) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2FloatingIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkV2FloatingIP_attach, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2FloatingIPExists(t, "openstack_networking_floatingip_v2.myip", &fip), + testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), + testAccCheckNetworkingV2InstanceFloatingIPAttach(&instance, &fip), + ), + }, + }, + }) +} + func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkClient, err := config.networkingV2Client(OS_REGION_NAME) @@ -81,11 +117,28 @@ func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floati } } +func testAccCheckNetworkingV2InstanceFloatingIPAttach( + instance *servers.Server, fip *floatingips.FloatingIP) resource.TestCheckFunc { + + // When Neutron is used, the Instance sometimes does not know its floating IP until some time + // after the attachment happened. This can be anywhere from 2-20 seconds. Because of that delay, + // the test usually completes with failure. + // However, the Fixed IP is known on both sides immediately, so that can be used as a bridge + // to ensure the two are now related. + // I think a better option is to introduce some state changing config in the actual resource. + return func(s *terraform.State) error { + for _, networkAddresses := range instance.Addresses { + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + if address["OS-EXT-IPS:type"] == "fixed" && address["addr"] == fip.FixedIP { + return nil + } + } + } + return fmt.Errorf("Floating IP %+v was not attached to instance %+v", fip, instance) + } +} + var testAccNetworkingV2FloatingIP_basic = ` resource "openstack_networking_floatingip_v2" "foo" { - } - - resource "openstack_compute_instance_v2" "bar" { - name = "terraform-acc-floating-ip-test" - floating_ip = "${openstack_networking_floatingip_v2.foo.address}" }`