From 32616948778ecdfcebe7e3155deed658d4885e34 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Wed, 4 Feb 2015 22:14:36 +0100 Subject: [PATCH 1/5] Just try the first IP available if none found before Some cloud don't implement correctly IP addresses. Instead of failing during the provisionning, we just take the first IP available and try with this one. --- .../resource_openstack_compute_instance_v2.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 7b3db0c6c01a..2d9a0cdb8405 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -246,11 +246,25 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err pa := paRaw.(map[string]interface{}) if pa["version"].(float64) == 4 { host = pa["addr"].(string) - d.Set("access_ip_v4", host) + break } } } } + + // If no host found, just get the first IP we find + if host == "" { + for _, networkAddresses := range server.Addresses { + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + if address["version"].(float64) == 4 { + host = address["addr"].(string) + break + } + } + } + } + d.Set("access_ip_v4", host) d.Set("host", host) log.Printf("host: %s", host) From a9b7b784202498ae39291de61dc85d4b135f4376 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Wed, 4 Feb 2015 22:15:11 +0100 Subject: [PATCH 2/5] add ACTIVE as pending state when deleting instance --- .../openstack/resource_openstack_compute_instance_v2.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 2d9a0cdb8405..65c2b45da7b1 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -442,6 +442,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"}, Target: "DELETED", Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 10 * time.Minute, From f0ed1e7ef87c8214aa4ba564ec766ef85e093a9a Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Thu, 5 Feb 2015 08:23:46 +0100 Subject: [PATCH 3/5] Add floating IP resource --- builtin/providers/openstack/provider.go | 1 + ...urce_openstack_networking_floatingip_v2.go | 160 ++++++++++++++++++ ...openstack_networking_floatingip_v2_test.go | 89 ++++++++++ 3 files changed, 250 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 180b5c3a7a79..955101a441a8 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -68,6 +68,7 @@ func Provider() terraform.ResourceProvider { "openstack_lb_vip_v1": resourceLBVipV1(), "openstack_networking_network_v2": resourceNetworkingNetworkV2(), "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), }, diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go new file mode 100644 index 000000000000..410fb0e39701 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go @@ -0,0 +1,160 @@ +package openstack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" + "github.com/rackspace/gophercloud/pagination" +) + +func resourceNetworkingFloatingIPV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkFloatingIPV2Create, + Read: resourceNetworkFloatingIPV2Read, + Delete: resourceNetworkFloatingIPV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "pool": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + poolID, err := getNetworkID(d, meta, d.Get("pool").(string)) + if err != nil { + return fmt.Errorf("Error retrieving floating IP pool name: %s", err) + } + if len(poolID) == 0 { + return fmt.Errorf("No network found with name: %s", d.Get("pool").(string)) + } + floatingIP, err := floatingips.Create(networkClient, floatingips.CreateOpts{ + FloatingNetworkID: poolID, + }).Extract() + if err != nil { + return fmt.Errorf("Error allocating floating IP: %s", err) + } + + d.SetId(floatingIP.ID) + + return resourceNetworkFloatingIPV2Read(d, meta) +} + +func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkClient, 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() + if err != nil { + return fmt.Errorf("Error retrieving floatingIP: %s", err) + } + + d.Set("region", d.Get("region").(string)) + d.Set("address", floatingIP.FloatingIP) + poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID) + if err != nil { + return fmt.Errorf("Error retrieving floating IP pool name: %s", err) + } + d.Set("pool", poolName) + + return nil +} + +func resourceNetworkFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkClient, 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) + } + 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)) + if err != nil { + return "", fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + opts := networks.ListOpts{Name: networkName} + pager := networks.List(networkClient, opts) + networkID := "" + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.Name == networkName { + networkID = n.ID + return false, nil + } + } + + return true, nil + }) + + return networkID, err +} + +func getNetworkName(d *schema.ResourceData, meta interface{}, networkID string) (string, error) { + config := meta.(*Config) + networkClient, 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) + networkName := "" + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.ID == networkID { + networkName = n.Name + return false, nil + } + } + + return true, nil + }) + + return networkName, err +} diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go new file mode 100644 index 000000000000..390275928420 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go @@ -0,0 +1,89 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" +) + +func TestAccNetworkingV2FloatingIP_basic(t *testing.T) { + var floatingIP floatingips.FloatingIP + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2FloatingIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2FloatingIP_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2FloatingIPExists(t, "openstack_networking_floatingip_v2.foo", &floatingIP), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2FloatingIPDestroy) Error creating OpenStack floating IP: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_floatingip_v2" { + continue + } + + _, err := floatingips.Get(networkClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("FloatingIP still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floatingips.FloatingIP) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + networkClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2FloatingIPExists) Error creating OpenStack networking client: %s", err) + } + + found, err := floatingips.Get(networkClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("FloatingIP not found") + } + + *kp = *found + + return nil + } +} + +var testAccNetworkingV2FloatingIP_basic = fmt.Sprintf(` + resource "openstack_networking_floatingip_v2" "foo" { + region = "%s" + pool = "PublicNetwork-01" + }`, + OS_REGION_NAME) From 21549b71ae31f4b75ecd8f8b709db6bcec541f17 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Fri, 6 Feb 2015 14:34:11 +0100 Subject: [PATCH 4/5] Manage floating IP in compute instances --- .../resource_openstack_compute_instance_v2.go | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 65c2b45da7b1..a5807100f2c6 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -14,6 +14,9 @@ import ( "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/servers" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" + "github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/rackspace/gophercloud/pagination" ) @@ -48,6 +51,11 @@ func resourceComputeInstanceV2() *schema.Resource { ForceNew: false, DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), }, + "floating_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, "security_groups": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -215,6 +223,22 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e "Error waiting for instance (%s) to become ready: %s", server.ID, err) } + floatingIP := d.Get("floating_ip").(string) + if len(floatingIP) > 0 { + 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 { + fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + } + } return resourceComputeInstanceV2Read(d, meta) } @@ -375,6 +399,25 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e } } + if d.HasChange("floating_ip") { + floatingIP := d.Get("floating_ip").(string) + if len(floatingIP) > 0 { + 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), d.Id()) + if err != nil { + fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + } + } + } + if d.HasChange("flavor_ref") { resizeOpts := &servers.ResizeOpts{ FlavorRef: d.Get("flavor_ref").(string), @@ -526,3 +569,93 @@ 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 { + networkID, err := getFirstNetworkID(networkingClient, instanceID) + if err != nil { + return err + } + portID, err := getInstancePortID(networkingClient, instanceID, networkID) + _, err = floatingips.Update(networkingClient, floatingIP.ID, floatingips.UpdateOpts{ + PortID: portID, + }).Extract() + return err +} + +func getFirstNetworkID(networkingClient *gophercloud.ServiceClient, instanceID string) (string, error) { + pager := networks.List(networkingClient, networks.ListOpts{}) + + var networkdID string + err := pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + if len(networkList) > 0 { + networkdID = networkList[0].ID + return false, nil + } + return false, fmt.Errorf("No network found for the instance %s", instanceID) + }) + if err != nil { + return "", err + } + return networkdID, nil + +} + +func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID, networkID string) (string, error) { + pager := ports.List(networkingClient, ports.ListOpts{ + DeviceID: instanceID, + NetworkID: networkID, + }) + + 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 + } + 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 +} From 7d75f1051aaa008f072bbdd3c9131adaffd416b1 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Mon, 9 Feb 2015 13:27:30 +0100 Subject: [PATCH 5/5] Make pool name configurable in tests --- builtin/providers/openstack/provider_test.go | 7 +++++++ .../openstack/resource_openstack_compute_instance_v2.go | 4 ++-- .../resource_openstack_networking_floatingip_v2_test.go | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 9e90bb4ea703..d98ec1820a72 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -10,6 +10,7 @@ import ( var ( OS_REGION_NAME = "" + OS_POOL_NAME = "" ) var testAccProviders map[string]terraform.ResourceProvider @@ -49,6 +50,12 @@ func testAccPreCheck(t *testing.T) { t.Fatal("OS_IMAGE_ID must be set for acceptance tests") } + v = os.Getenv("OS_POOL_NAME") + if v == "" { + t.Fatal("OS_POOL_NAME must be set for acceptance tests") + } + OS_POOL_NAME = v + v = os.Getenv("OS_FLAVOR_ID") if v == "" { t.Fatal("OS_FLAVOR_ID must be set for acceptance tests") diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index a5807100f2c6..bcf30a6ee772 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -224,7 +224,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e server.ID, err) } floatingIP := d.Get("floating_ip").(string) - if len(floatingIP) > 0 { + if floatingIP != "" { networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) @@ -401,7 +401,7 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e if d.HasChange("floating_ip") { floatingIP := d.Get("floating_ip").(string) - if len(floatingIP) > 0 { + if floatingIP != "" { networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) 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 390275928420..cd08ea512169 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go @@ -84,6 +84,6 @@ func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floati var testAccNetworkingV2FloatingIP_basic = fmt.Sprintf(` resource "openstack_networking_floatingip_v2" "foo" { region = "%s" - pool = "PublicNetwork-01" + pool = "%s" }`, - OS_REGION_NAME) + OS_REGION_NAME, OS_POOL_NAME)