Skip to content

Commit

Permalink
Merge pull request hashicorp#1 from julienvey/IPmanagement
Browse files Browse the repository at this point in the history
Floating IP resource
  • Loading branch information
jrperritt committed Feb 9, 2015
2 parents 0330923 + 7d75f10 commit 15f65e0
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 1 deletion.
1 change: 1 addition & 0 deletions builtin/providers/openstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},

Expand Down
7 changes: 7 additions & 0 deletions builtin/providers/openstack/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

var (
OS_REGION_NAME = ""
OS_POOL_NAME = ""
)

var testAccProviders map[string]terraform.ResourceProvider
Expand Down Expand Up @@ -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")
Expand Down
150 changes: 149 additions & 1 deletion builtin/providers/openstack/resource_openstack_compute_instance_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 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 {
fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err)
}
}

return resourceComputeInstanceV2Read(d, meta)
}
Expand Down Expand Up @@ -246,11 +270,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)
Expand Down Expand Up @@ -361,6 +399,25 @@ 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)
}

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),
Expand Down Expand Up @@ -428,6 +485,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,
Expand Down Expand Up @@ -511,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
}
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 15f65e0

Please sign in to comment.