Merge pull request #4812 from jtopjian/openstack-per-network-floating-ip

provider/openstack: Per-network Floating IPs
This commit is contained in:
Joe Topjian 2016-01-31 15:24:59 -07:00
commit 1ccd0491ff
3 changed files with 376 additions and 110 deletions

View File

@ -136,10 +136,20 @@ func resourceComputeInstanceV2() *schema.Resource {
Optional: true, Optional: true,
Computed: true, Computed: true,
}, },
"floating_ip": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"mac": &schema.Schema{ "mac": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"access_network": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
}, },
}, },
}, },
@ -320,11 +330,6 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
return err return err
} }
networkDetails, err := resourceInstanceNetworks(computeClient, d)
if err != nil {
return err
}
// determine if volume/block_device configuration is correct // determine if volume/block_device configuration is correct
// this includes ensuring volume_ids are set // this includes ensuring volume_ids are set
// and if only one block_device was specified. // and if only one block_device was specified.
@ -332,6 +337,18 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
return err return err
} }
// check if floating IP configuration is correct
if err := checkInstanceFloatingIPs(d); err != nil {
return err
}
// Build a list of networks with the information given upon creation.
// Error out if an invalid network configuration was used.
networkDetails, err := getInstanceNetworks(computeClient, d)
if err != nil {
return err
}
networks := make([]servers.Network, len(networkDetails)) networks := make([]servers.Network, len(networkDetails))
for i, net := range networkDetails { for i, net := range networkDetails {
networks[i] = servers.Network{ networks[i] = servers.Network{
@ -424,11 +441,15 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e
"Error waiting for instance (%s) to become ready: %s", "Error waiting for instance (%s) to become ready: %s",
server.ID, err) server.ID, err)
} }
floatingIP := d.Get("floating_ip").(string)
if floatingIP != "" { // Now that the instance has been created, we need to do an early read on the
if err := floatingip.Associate(computeClient, server.ID, floatingIP).ExtractErr(); err != nil { // networks in order to associate floating IPs
return fmt.Errorf("Error associating floating IP: %s", err) _, err = getInstanceNetworksAndAddresses(computeClient, d)
}
// If floating IPs were specified, associate them after the instance has launched.
err = associateFloatingIPsToInstance(computeClient, d)
if err != nil {
return err
} }
// if volumes were specified, attach them after the instance has launched. // if volumes were specified, attach them after the instance has launched.
@ -462,99 +483,35 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err
d.Set("name", server.Name) d.Set("name", server.Name)
// begin reading the network configuration // Get the instance network and address information
d.Set("access_ip_v4", server.AccessIPv4) networks, err := getInstanceNetworksAndAddresses(computeClient, d)
d.Set("access_ip_v6", server.AccessIPv6)
hostv4 := server.AccessIPv4
hostv6 := server.AccessIPv6
networkDetails, err := resourceInstanceNetworks(computeClient, d)
addresses := resourceInstanceAddresses(server.Addresses)
if err != nil { if err != nil {
return err return err
} }
// if there are no networkDetails, make networks at least a length of 1 // Determine the best IPv4 and IPv6 addresses to access the instance with
networkLength := 1 hostv4, hostv6 := getInstanceAccessAddresses(d, networks)
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"],
}
}
}
log.Printf("[DEBUG] new networks: %+v", networks)
d.Set("network", networks) d.Set("network", networks)
d.Set("access_ip_v4", hostv4) d.Set("access_ip_v4", hostv4)
d.Set("access_ip_v6", hostv6) 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. // Determine the best IP address to use for SSH connectivity.
preferredv := "" // Prefer IPv4 over IPv6.
preferredSSHAddress := ""
if hostv4 != "" { if hostv4 != "" {
preferredv = hostv4 preferredSSHAddress = hostv4
} else if hostv6 != "" { } else if hostv6 != "" {
preferredv = hostv6 preferredSSHAddress = hostv6
} }
if preferredv != "" { if preferredSSHAddress != "" {
// Initialize the connection info // Initialize the connection info
d.SetConnInfo(map[string]string{ d.SetConnInfo(map[string]string{
"type": "ssh", "type": "ssh",
"host": preferredv, "host": preferredSSHAddress,
}) })
} }
// end network configuration
d.Set("metadata", server.Metadata) d.Set("metadata", server.Metadata)
@ -600,12 +557,6 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e
if d.HasChange("name") { if d.HasChange("name") {
updateOpts.Name = d.Get("name").(string) updateOpts.Name = d.Get("name").(string)
} }
if d.HasChange("access_ip_v4") {
updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string)
}
if d.HasChange("access_ip_v6") {
updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string)
}
if updateOpts != (servers.UpdateOpts{}) { if updateOpts != (servers.UpdateOpts{}) {
_, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract()
@ -679,20 +630,48 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e
log.Printf("[DEBUG] Old Floating IP: %v", oldFIP) log.Printf("[DEBUG] Old Floating IP: %v", oldFIP)
log.Printf("[DEBUG] New Floating IP: %v", newFIP) log.Printf("[DEBUG] New Floating IP: %v", newFIP)
if oldFIP.(string) != "" { if oldFIP.(string) != "" {
log.Printf("[DEBUG] Attemping to disassociate %s from %s", oldFIP, d.Id()) log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id())
if err := floatingip.Disassociate(computeClient, d.Id(), oldFIP.(string)).ExtractErr(); err != nil { if err := disassociateFloatingIPFromInstance(computeClient, oldFIP.(string), d.Id(), ""); err != nil {
return fmt.Errorf("Error disassociating Floating IP during update: %s", err) return fmt.Errorf("Error disassociating Floating IP during update: %s", err)
} }
} }
if newFIP.(string) != "" { if newFIP.(string) != "" {
log.Printf("[DEBUG] Attemping to associate %s to %s", newFIP, d.Id()) log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id())
if err := floatingip.Associate(computeClient, d.Id(), newFIP.(string)).ExtractErr(); err != nil { if err := associateFloatingIPToInstance(computeClient, newFIP.(string), d.Id(), ""); err != nil {
return fmt.Errorf("Error associating Floating IP during update: %s", err) return fmt.Errorf("Error associating Floating IP during update: %s", err)
} }
} }
} }
if d.HasChange("network") {
oldNetworks, newNetworks := d.GetChange("network")
oldNetworkList := oldNetworks.([]interface{})
newNetworkList := newNetworks.([]interface{})
for i, oldNet := range oldNetworkList {
oldNetRaw := oldNet.(map[string]interface{})
oldFIP := oldNetRaw["floating_ip"].(string)
oldFixedIP := oldNetRaw["fixed_ip_v4"].(string)
newNetRaw := newNetworkList[i].(map[string]interface{})
newFIP := newNetRaw["floating_ip"].(string)
newFixedIP := newNetRaw["fixed_ip_v4"].(string)
// Only changes to the floating IP are supported
if oldFIP != newFIP {
log.Printf("[DEBUG] Attempting to disassociate %s from %s", oldFIP, d.Id())
if err := disassociateFloatingIPFromInstance(computeClient, oldFIP, d.Id(), oldFixedIP); err != nil {
return fmt.Errorf("Error disassociating Floating IP during update: %s", err)
}
log.Printf("[DEBUG] Attempting to associate %s to %s", newFIP, d.Id())
if err := associateFloatingIPToInstance(computeClient, newFIP, d.Id(), newFixedIP); err != nil {
return fmt.Errorf("Error associating Floating IP during update: %s", err)
}
}
}
}
if d.HasChange("volume") { if d.HasChange("volume") {
// ensure the volume configuration is correct // ensure the volume configuration is correct
if err := checkVolumeConfig(d); err != nil { if err := checkVolumeConfig(d); err != nil {
@ -845,7 +824,62 @@ func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string {
return secgroups return secgroups
} }
func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) { // getInstanceNetworks collects instance network information from different sources
// and aggregates it all together.
func getInstanceNetworksAndAddresses(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) {
server, err := servers.Get(computeClient, d.Id()).Extract()
if err != nil {
return nil, CheckDeleted(d, err, "server")
}
networkDetails, err := getInstanceNetworks(computeClient, d)
addresses := getInstanceAddresses(server.Addresses)
if err != nil {
return nil, err
}
// 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 {
networks[0] = map[string]interface{}{
"name": netName,
"fixed_ip_v4": n["fixed_ip_v4"],
"fixed_ip_v6": n["fixed_ip_v6"],
"floating_ip": n["floating_ip"],
"mac": n["mac"],
}
}
} else {
for i, net := range networkDetails {
n := addresses[net["name"].(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"],
"floating_ip": n["floating_ip"],
"mac": n["mac"],
"access_network": networkDetails[i]["access_network"],
}
}
}
log.Printf("[DEBUG] networks: %+v", networks)
return networks, nil
}
func getInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) ([]map[string]interface{}, error) {
rawNetworks := d.Get("network").([]interface{}) rawNetworks := d.Get("network").([]interface{})
newNetworks := make([]map[string]interface{}, 0, len(rawNetworks)) newNetworks := make([]map[string]interface{}, 0, len(rawNetworks))
var tenantnet tenantnetworks.Network var tenantnet tenantnetworks.Network
@ -860,6 +894,7 @@ func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schem
} }
rawMap := raw.(map[string]interface{}) rawMap := raw.(map[string]interface{})
allPages, err := tenantnetworks.List(computeClient).AllPages() allPages, err := tenantnetworks.List(computeClient).AllPages()
if err != nil { if err != nil {
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
@ -899,10 +934,11 @@ func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schem
} }
newNetworks = append(newNetworks, map[string]interface{}{ newNetworks = append(newNetworks, map[string]interface{}{
"uuid": networkID, "uuid": networkID,
"name": networkName, "name": networkName,
"port": rawMap["port"].(string), "port": rawMap["port"].(string),
"fixed_ip_v4": rawMap["fixed_ip_v4"].(string), "fixed_ip_v4": rawMap["fixed_ip_v4"].(string),
"access_network": rawMap["access_network"].(bool),
}) })
} }
@ -910,8 +946,7 @@ func resourceInstanceNetworks(computeClient *gophercloud.ServiceClient, d *schem
return newNetworks, nil return newNetworks, nil
} }
func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} { func getInstanceAddresses(addresses map[string]interface{}) map[string]map[string]interface{} {
addrs := make(map[string]map[string]interface{}) addrs := make(map[string]map[string]interface{})
for n, networkAddresses := range addresses { for n, networkAddresses := range addresses {
addrs[n] = make(map[string]interface{}) addrs[n] = make(map[string]interface{})
@ -937,6 +972,117 @@ func resourceInstanceAddresses(addresses map[string]interface{}) map[string]map[
return addrs return addrs
} }
func getInstanceAccessAddresses(d *schema.ResourceData, networks []map[string]interface{}) (string, string) {
var hostv4, hostv6 string
// Start with a global floating IP
floatingIP := d.Get("floating_ip").(string)
if floatingIP != "" {
hostv4 = floatingIP
}
// Loop through all networks and check for the following:
// * If the network is set as an access network.
// * If the network has a floating IP.
// * If the network has a v4/v6 fixed IP.
for _, n := range networks {
if n["floating_ip"] != nil {
hostv4 = n["floating_ip"].(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)
}
if n["access_network"].(bool) {
break
}
}
log.Printf("[DEBUG] OpenStack Instance Network Access Addresses: %s, %s", hostv4, hostv6)
return hostv4, hostv6
}
func checkInstanceFloatingIPs(d *schema.ResourceData) error {
rawNetworks := d.Get("network").([]interface{})
floatingIP := d.Get("floating_ip").(string)
for _, raw := range rawNetworks {
if raw == nil {
continue
}
rawMap := raw.(map[string]interface{})
// Error if a floating IP was specified both globally and in the network block.
if floatingIP != "" && rawMap["floating_ip"] != "" {
return fmt.Errorf("Cannot specify a floating IP both globally and in a network block.")
}
}
return nil
}
func associateFloatingIPsToInstance(computeClient *gophercloud.ServiceClient, d *schema.ResourceData) error {
floatingIP := d.Get("floating_ip").(string)
rawNetworks := d.Get("network").([]interface{})
instanceID := d.Id()
if floatingIP != "" {
if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, ""); err != nil {
return err
}
} else {
for _, raw := range rawNetworks {
if raw == nil {
continue
}
rawMap := raw.(map[string]interface{})
if rawMap["floating_ip"].(string) != "" {
floatingIP := rawMap["floating_ip"].(string)
fixedIP := rawMap["fixed_ip_v4"].(string)
if err := associateFloatingIPToInstance(computeClient, floatingIP, instanceID, fixedIP); err != nil {
return err
}
}
}
}
return nil
}
func associateFloatingIPToInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error {
associateOpts := floatingip.AssociateOpts{
ServerID: instanceID,
FloatingIP: floatingIP,
FixedIP: fixedIP,
}
if err := floatingip.AssociateInstance(computeClient, associateOpts).ExtractErr(); err != nil {
return fmt.Errorf("Error associating floating IP: %s", err)
}
return nil
}
func disassociateFloatingIPFromInstance(computeClient *gophercloud.ServiceClient, floatingIP string, instanceID string, fixedIP string) error {
associateOpts := floatingip.AssociateOpts{
ServerID: instanceID,
FloatingIP: floatingIP,
FixedIP: fixedIP,
}
if err := floatingip.DisassociateInstance(computeClient, associateOpts).ExtractErr(); err != nil {
return fmt.Errorf("Error disassociating floating IP: %s", err)
}
return nil
}
func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string {
m := make(map[string]string) m := make(map[string]string)
for key, val := range d.Get("metadata").(map[string]interface{}) { for key, val := range d.Get("metadata").(map[string]interface{}) {

View File

@ -178,10 +178,10 @@ func TestAccComputeV2Instance_volumeDetachPostCreation(t *testing.T) {
}) })
} }
func TestAccComputeV2Instance_floatingIPAttach(t *testing.T) { func TestAccComputeV2Instance_floatingIPAttachGlobally(t *testing.T) {
var instance servers.Server var instance servers.Server
var fip floatingip.FloatingIP var fip floatingip.FloatingIP
var testAccComputeV2Instance_floatingIPAttach = fmt.Sprintf(` var testAccComputeV2Instance_floatingIPAttachGlobally = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip" { resource "openstack_compute_floatingip_v2" "myip" {
} }
@ -202,7 +202,7 @@ func TestAccComputeV2Instance_floatingIPAttach(t *testing.T) {
CheckDestroy: testAccCheckComputeV2InstanceDestroy, CheckDestroy: testAccCheckComputeV2InstanceDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccComputeV2Instance_floatingIPAttach, Config: testAccComputeV2Instance_floatingIPAttachGlobally,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip", &fip), testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip", &fip),
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
@ -213,6 +213,108 @@ func TestAccComputeV2Instance_floatingIPAttach(t *testing.T) {
}) })
} }
func TestAccComputeV2Instance_floatingIPAttachToNetwork(t *testing.T) {
var instance servers.Server
var fip floatingip.FloatingIP
var testAccComputeV2Instance_floatingIPAttachToNetwork = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip" {
}
resource "openstack_compute_instance_v2" "foo" {
name = "terraform-test"
security_groups = ["default"]
network {
uuid = "%s"
floating_ip = "${openstack_compute_floatingip_v2.myip.address}"
access_network = true
}
}`,
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_floatingIPAttachToNetwork,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip", &fip),
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
testAccCheckComputeV2InstanceFloatingIPAttach(&instance, &fip),
),
},
},
})
}
func TestAccComputeV2Instance_floatingIPAttachAndChange(t *testing.T) {
var instance servers.Server
var fip floatingip.FloatingIP
var testAccComputeV2Instance_floatingIPAttachToNetwork_1 = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip_1" {
}
resource "openstack_compute_floatingip_v2" "myip_2" {
}
resource "openstack_compute_instance_v2" "foo" {
name = "terraform-test"
security_groups = ["default"]
network {
uuid = "%s"
floating_ip = "${openstack_compute_floatingip_v2.myip_1.address}"
access_network = true
}
}`,
os.Getenv("OS_NETWORK_ID"))
var testAccComputeV2Instance_floatingIPAttachToNetwork_2 = fmt.Sprintf(`
resource "openstack_compute_floatingip_v2" "myip_1" {
}
resource "openstack_compute_floatingip_v2" "myip_2" {
}
resource "openstack_compute_instance_v2" "foo" {
name = "terraform-test"
security_groups = ["default"]
network {
uuid = "%s"
floating_ip = "${openstack_compute_floatingip_v2.myip_2.address}"
access_network = true
}
}`,
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_floatingIPAttachToNetwork_1,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip_1", &fip),
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
testAccCheckComputeV2InstanceFloatingIPAttach(&instance, &fip),
),
},
resource.TestStep{
Config: testAccComputeV2Instance_floatingIPAttachToNetwork_2,
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.myip_2", &fip),
testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance),
testAccCheckComputeV2InstanceFloatingIPAttach(&instance, &fip),
),
},
},
})
}
func TestAccComputeV2Instance_multi_secgroups(t *testing.T) { func TestAccComputeV2Instance_multi_secgroups(t *testing.T) {
var instance servers.Server var instance servers.Server
var secgroup secgroups.SecurityGroup var secgroup secgroups.SecurityGroup

View File

@ -50,7 +50,8 @@ The following arguments are supported:
desired flavor for the server. Changing this resizes the existing server. desired flavor for the server. Changing this resizes the existing server.
* `floating_ip` - (Optional) A *Compute* Floating IP that will be associated * `floating_ip` - (Optional) A *Compute* Floating IP that will be associated
with the Instance. The Floating IP must be provisioned already. with the Instance. The Floating IP must be provisioned already. See *Notes*
for more information about Floating IPs.
* `user_data` - (Optional) The user data to provide when launching the instance. * `user_data` - (Optional) The user data to provide when launching the instance.
Changing this creates a new server. Changing this creates a new server.
@ -106,6 +107,13 @@ The `network` block supports:
* `fixed_ip_v4` - (Optional) Specifies a fixed IPv4 address to be used on this * `fixed_ip_v4` - (Optional) Specifies a fixed IPv4 address to be used on this
network. network.
* `floating_ip` - (Optional) Specifies a floating IP address to be associated
with this network. Cannot be combined with a top-level floating IP. See
*Notes* for more information about Floating IPs.
* `access_network` - (Optional) Specifies if this network should be used for
provisioning access. Accepts true or false. Defaults to false.
The `block_device` block supports: The `block_device` block supports:
* `uuid` - (Required) The UUID of the image, volume, or snapshot. * `uuid` - (Required) The UUID of the image, volume, or snapshot.
@ -173,11 +181,21 @@ The following attributes are exported:
network. network.
* `network/fixed_ip_v6` - The Fixed IPv6 address of the Instance on that * `network/fixed_ip_v6` - The Fixed IPv6 address of the Instance on that
network. network.
* `network/floating_ip` - The Floating IP address of the Instance on that
network.
* `network/mac` - The MAC address of the NIC on that network. * `network/mac` - The MAC address of the NIC on that network.
## Notes ## Notes
If you configure the instance to have multiple networks, be aware that only Floating IPs can be associated in one of two ways:
the first network can be associated with a Floating IP. So the first network
in the instance resource _must_ be the network that you have configured to * You can specify a Floating IP address by using the top-level `floating_ip`
communicate with your floating IP / public network via a Neutron Router. attribute. This floating IP will be associated with either the network defined
in the first `network` block or the default network if no `network` blocks are
defined.
* You can specify a Floating IP address by using the `floating_ip` attribute
defined in the `network` block. Each `network` block can have its own floating
IP address.
Only one of the above methods can be used.