Merge pull request #1347 from jtopjian/compute-network-refactor

provider/openstack Compute Network Refactor
This commit is contained in:
Paul Hinze 2015-04-02 08:46:03 -05:00
commit e0cdadfc55
5 changed files with 355 additions and 172 deletions

View File

@ -63,4 +63,9 @@ func testAccPreCheck(t *testing.T) {
if v1 == "" && v2 == "" { if v1 == "" && v2 == "" {
t.Fatal("OS_FLAVOR_ID or OS_FLAVOR_NAME must be set for acceptance tests") 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")
}
} }

View File

@ -2,12 +2,14 @@ package openstack
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
) )
func TestAccComputeV2FloatingIP_basic(t *testing.T) { 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 { func testAccCheckComputeV2FloatingIPDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config) config := testAccProvider.Meta().(*Config)
computeClient, err := config.computeV2Client(OS_REGION_NAME) computeClient, err := config.computeV2Client(OS_REGION_NAME)
@ -83,9 +119,4 @@ func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingi
var testAccComputeV2FloatingIP_basic = ` var testAccComputeV2FloatingIP_basic = `
resource "openstack_compute_floatingip_v2" "foo" { 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}"
}` }`

View File

@ -13,14 +13,14 @@ import (
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "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/keypairs"
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "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/extensions/volumeattach"
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors" "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
"github.com/rackspace/gophercloud/openstack/compute/v2/images" "github.com/rackspace/gophercloud/openstack/compute/v2/images"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "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" "github.com/rackspace/gophercloud/pagination"
) )
@ -76,7 +76,7 @@ func resourceComputeInstanceV2() *schema.Resource {
Optional: true, Optional: true,
ForceNew: false, ForceNew: false,
}, },
"user_data": &schema.Schema{ "user_data": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
@ -106,19 +106,37 @@ func resourceComputeInstanceV2() *schema.Resource {
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Computed: true,
Elem: &schema.Resource{ Elem: &schema.Resource{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"uuid": &schema.Schema{ "uuid": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
}, },
"port": &schema.Schema{ "port": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
}, },
"fixed_ip": &schema.Schema{ "fixed_ip_v4": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
},
"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 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{ createOpts = &servers.CreateOpts{
Name: d.Get("name").(string), Name: d.Get("name").(string),
ImageRef: imageId, ImageRef: imageId,
FlavorRef: flavorId, FlavorRef: flavorId,
SecurityGroups: resourceInstanceSecGroupsV2(d), SecurityGroups: resourceInstanceSecGroupsV2(d),
AvailabilityZone: d.Get("availability_zone").(string), AvailabilityZone: d.Get("availability_zone").(string),
Networks: resourceInstanceNetworksV2(d), Networks: networks,
Metadata: resourceInstanceMetadataV2(d), Metadata: resourceInstanceMetadataV2(d),
ConfigDrive: d.Get("config_drive").(bool), ConfigDrive: d.Get("config_drive").(bool),
AdminPass: d.Get("admin_pass").(string), AdminPass: d.Get("admin_pass").(string),
@ -291,18 +323,8 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
} }
floatingIP := d.Get("floating_ip").(string) floatingIP := d.Get("floating_ip").(string)
if floatingIP != "" { if floatingIP != "" {
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err := floatingip.Associate(computeClient, server.ID, floatingIP).ExtractErr(); err != nil {
if err != nil { return fmt.Errorf("Error associating floating IP: %s", err)
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)
} }
} }
@ -338,67 +360,85 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err
log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server)
d.Set("name", server.Name) d.Set("name", server.Name)
// begin reading the network configuration
d.Set("access_ip_v4", server.AccessIPv4) d.Set("access_ip_v4", server.AccessIPv4)
d.Set("access_ip_v6", server.AccessIPv6) d.Set("access_ip_v6", server.AccessIPv6)
hostv4 := server.AccessIPv4 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
}
}
}
}
// 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
}
}
}
}
d.Set("access_ip_v4", hostv4)
log.Printf("hostv4: %s", hostv4)
hostv6 := server.AccessIPv6 hostv6 := server.AccessIPv6
if hostv6 == "" {
if publicAddressesRaw, ok := server.Addresses["public"]; ok { networkDetails, err := resourceInstanceNetworks(computeClient, d)
publicAddresses := publicAddressesRaw.([]interface{}) addresses := resourceInstanceAddresses(server.Addresses)
for _, paRaw := range publicAddresses { if err != nil {
pa := paRaw.(map[string]interface{}) return err
if pa["version"].(float64) == 6 { }
hostv6 = fmt.Sprintf("[%s]", pa["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)
} }
} }
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 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"],
}
} }
} }
// If no hostv6 found, just get the first IPv6 we find log.Printf("[DEBUG] new networks: %+v", networks)
if hostv6 == "" {
for _, networkAddresses := range server.Addresses { d.Set("network", networks)
for _, element := range networkAddresses.([]interface{}) { d.Set("access_ip_v4", hostv4)
address := element.(map[string]interface{})
if address["version"].(float64) == 6 {
hostv6 = fmt.Sprintf("[%s]", address["addr"].(string))
break
}
}
}
}
d.Set("access_ip_v6", hostv6) d.Set("access_ip_v6", hostv6)
log.Printf("hostv4: %s", hostv4)
log.Printf("hostv6: %s", hostv6) log.Printf("hostv6: %s", hostv6)
// prefer the v6 address if no v4 address exists.
preferredv := "" preferredv := ""
if hostv4 != "" { if hostv4 != "" {
preferredv = hostv4 preferredv = hostv4
@ -413,6 +453,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err
"host": preferredv, "host": preferredv,
}) })
} }
// end network configuration
d.Set("metadata", server.Metadata) d.Set("metadata", server.Metadata)
@ -553,20 +594,20 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e
} }
if d.HasChange("floating_ip") { if d.HasChange("floating_ip") {
floatingIP := d.Get("floating_ip").(string) oldFIP, newFIP := d.GetChange("floating_ip")
if floatingIP != "" { log.Printf("[DEBUG] Old Floating IP: %v", oldFIP)
networkingClient, err := config.networkingV2Client(d.Get("region").(string)) log.Printf("[DEBUG] New Floating IP: %v", newFIP)
if err != nil { if oldFIP.(string) != "" {
return fmt.Errorf("Error creating OpenStack compute client: %s", err) 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 newFIP.(string) != "" {
if err != nil { log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id())
return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil {
} return fmt.Errorf("Error associating Floating IP during update: %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)
} }
} }
} }
@ -722,18 +763,91 @@ func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string {
return secgroups 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{}) 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 { for i, raw := range rawNetworks {
rawMap := raw.(map[string]interface{}) rawMap := raw.(map[string]interface{})
networks[i] = servers.Network{
UUID: rawMap["uuid"].(string), allPages, err := tenantnetworks.List(computeClient).AllPages()
Port: rawMap["port"].(string), if err != nil {
FixedIP: rawMap["fixed_ip"].(string), 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 { func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
@ -759,75 +873,6 @@ func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interfa
return bfvOpts 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) { func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) {
imageId := d.Get("image_id").(string) imageId := d.Get("image_id").(string)

View File

@ -2,12 +2,14 @@ package openstack
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" "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/extensions/volumeattach"
"github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
"github.com/rackspace/gophercloud/pagination" "github.com/rackspace/gophercloud/pagination"
@ -15,6 +17,17 @@ import (
func TestAccComputeV2Instance_basic(t *testing.T) { func TestAccComputeV2Instance_basic(t *testing.T) {
var instance servers.Server 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{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, 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 { func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config) config := testAccProvider.Meta().(*Config)
computeClient, err := config.computeV2Client(OS_REGION_NAME) computeClient, err := config.computeV2Client(OS_REGION_NAME)
@ -159,15 +206,17 @@ func testAccCheckComputeV2InstanceVolumeAttachment(
} }
} }
var testAccComputeV2Instance_basic = fmt.Sprintf(` func testAccCheckComputeV2InstanceFloatingIPAttach(
resource "openstack_compute_instance_v2" "foo" { instance *servers.Server, fip *floatingip.FloatingIP) resource.TestCheckFunc {
region = "%s" return func(s *terraform.State) error {
name = "terraform-test" if fip.InstanceID == instance.ID {
metadata { return nil
foo = "bar"
} }
}`,
OS_REGION_NAME) return fmt.Errorf("Floating IP %s was not attached to instance %s", fip.ID, instance.ID)
}
}
var testAccComputeV2Instance_volumeAttach = fmt.Sprintf(` var testAccComputeV2Instance_volumeAttach = fmt.Sprintf(`
resource "openstack_blockstorage_volume_v1" "myvol" { resource "openstack_blockstorage_volume_v1" "myvol" {

View File

@ -2,11 +2,13 @@ package openstack
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"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/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 { func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config) config := testAccProvider.Meta().(*Config)
networkClient, err := config.networkingV2Client(OS_REGION_NAME) 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 = ` var testAccNetworkingV2FloatingIP_basic = `
resource "openstack_networking_floatingip_v2" "foo" { 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}"
}` }`