provider/vsphere: IPv6 support. (#6457)

IPv6 support added.

We support 1 IPv6 address per interface. It seems like the vSphere SDK supports more than one, since it's provided as a list.
I can change it to support more than one address. I decided to stick with one for now since that's how the configuration parameters
had been set up by other developers.

The global gateway configuration option has been removed. Instead the user should specify a gateway on NIC level (ipv4_gateway and ipv6_gateway).

For now, the global gateway will be used as a fallback for every NICs ipv4_gateway.
The global gateway configuration option has been marked as deprecated.
This commit is contained in:
Paul Stack 2016-05-03 18:27:24 +02:00
parent ab9d78f99b
commit 501c05d0f7
4 changed files with 184 additions and 41 deletions

View File

@ -33,8 +33,10 @@ type networkInterface struct {
label string label string
ipv4Address string ipv4Address string
ipv4PrefixLength int ipv4PrefixLength int
ipv4Gateway string
ipv6Address string ipv6Address string
ipv6PrefixLength int ipv6PrefixLength int
ipv6Gateway string
adapterType string // TODO: Make "adapter_type" argument adapterType string // TODO: Make "adapter_type" argument
} }
@ -77,7 +79,6 @@ type virtualMachine struct {
networkInterfaces []networkInterface networkInterfaces []networkInterface
hardDisks []hardDisk hardDisks []hardDisk
cdroms []cdrom cdroms []cdrom
gateway string
domain string domain string
timeZone string timeZone string
dnsSuffixes []string dnsSuffixes []string
@ -163,9 +164,10 @@ func resourceVSphereVirtualMachine() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"gateway": &schema.Schema{ "gateway": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
Deprecated: "Please use network_interface.ipv4_gateway",
}, },
"domain": &schema.Schema{ "domain": &schema.Schema{
@ -285,16 +287,27 @@ func resourceVSphereVirtualMachine() *schema.Resource {
Computed: true, Computed: true,
}, },
// TODO: Imprement ipv6 parameters to be optional "ipv4_gateway": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ipv6_address": &schema.Schema{ "ipv6_address": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Optional: true,
ForceNew: true, ForceNew: true,
}, },
"ipv6_prefix_length": &schema.Schema{ "ipv6_prefix_length": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Computed: true, Optional: true,
ForceNew: true,
},
"ipv6_gateway": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true, ForceNew: true,
}, },
@ -515,10 +528,6 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
vm.resourcePool = v.(string) vm.resourcePool = v.(string)
} }
if v, ok := d.GetOk("gateway"); ok {
vm.gateway = v.(string)
}
if v, ok := d.GetOk("domain"); ok { if v, ok := d.GetOk("domain"); ok {
vm.domain = v.(string) vm.domain = v.(string)
} }
@ -570,6 +579,9 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
if v, ok := network["ip_address"].(string); ok && v != "" { if v, ok := network["ip_address"].(string); ok && v != "" {
networks[i].ipv4Address = v networks[i].ipv4Address = v
} }
if v, ok := d.GetOk("gateway"); ok {
networks[i].ipv4Gateway = v.(string)
}
if v, ok := network["subnet_mask"].(string); ok && v != "" { if v, ok := network["subnet_mask"].(string); ok && v != "" {
ip := net.ParseIP(v).To4() ip := net.ParseIP(v).To4()
if ip != nil { if ip != nil {
@ -586,6 +598,18 @@ func resourceVSphereVirtualMachineCreate(d *schema.ResourceData, meta interface{
if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 { if v, ok := network["ipv4_prefix_length"].(int); ok && v != 0 {
networks[i].ipv4PrefixLength = v networks[i].ipv4PrefixLength = v
} }
if v, ok := network["ipv4_gateway"].(string); ok && v != "" {
networks[i].ipv4Gateway = v
}
if v, ok := network["ipv6_address"].(string); ok && v != "" {
networks[i].ipv6Address = v
}
if v, ok := network["ipv6_prefix_length"].(int); ok && v != 0 {
networks[i].ipv6PrefixLength = v
}
if v, ok := network["ipv6_gateway"].(string); ok && v != "" {
networks[i].ipv6Gateway = v
}
} }
vm.networkInterfaces = networks vm.networkInterfaces = networks
log.Printf("[DEBUG] network_interface init: %v", networks) log.Printf("[DEBUG] network_interface init: %v", networks)
@ -1473,12 +1497,9 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
} }
networkDevices = append(networkDevices, nd) networkDevices = append(networkDevices, nd)
// TODO: IPv6 support
var ipSetting types.CustomizationIPSettings var ipSetting types.CustomizationIPSettings
if network.ipv4Address == "" { if network.ipv4Address == "" {
ipSetting = types.CustomizationIPSettings{ ipSetting.Ip = &types.CustomizationDhcpIpGenerator{}
Ip: &types.CustomizationDhcpIpGenerator{},
}
} else { } else {
if network.ipv4PrefixLength == 0 { if network.ipv4PrefixLength == 0 {
return fmt.Errorf("Error: ipv4_prefix_length argument is empty.") return fmt.Errorf("Error: ipv4_prefix_length argument is empty.")
@ -1486,21 +1507,39 @@ func (vm *virtualMachine) deployVirtualMachine(c *govmomi.Client) error {
m := net.CIDRMask(network.ipv4PrefixLength, 32) m := net.CIDRMask(network.ipv4PrefixLength, 32)
sm := net.IPv4(m[0], m[1], m[2], m[3]) sm := net.IPv4(m[0], m[1], m[2], m[3])
subnetMask := sm.String() subnetMask := sm.String()
log.Printf("[DEBUG] gateway: %v", vm.gateway) log.Printf("[DEBUG] ipv4 gateway: %v\n", network.ipv4Gateway)
log.Printf("[DEBUG] ipv4 address: %v", network.ipv4Address) log.Printf("[DEBUG] ipv4 address: %v\n", network.ipv4Address)
log.Printf("[DEBUG] ipv4 prefix length: %v", network.ipv4PrefixLength) log.Printf("[DEBUG] ipv4 prefix length: %v\n", network.ipv4PrefixLength)
log.Printf("[DEBUG] ipv4 subnet mask: %v", subnetMask) log.Printf("[DEBUG] ipv4 subnet mask: %v\n", subnetMask)
ipSetting = types.CustomizationIPSettings{ ipSetting.Gateway = []string{
Gateway: []string{ network.ipv4Gateway,
vm.gateway,
},
Ip: &types.CustomizationFixedIp{
IpAddress: network.ipv4Address,
},
SubnetMask: subnetMask,
} }
ipSetting.Ip = &types.CustomizationFixedIp{
IpAddress: network.ipv4Address,
}
ipSetting.SubnetMask = subnetMask
} }
ipv6Spec := &types.CustomizationIPSettingsIpV6AddressSpec{}
if network.ipv6Address == "" {
ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
&types.CustomizationDhcpIpV6Generator{},
}
} else {
log.Printf("[DEBUG] ipv6 gateway: %v\n", network.ipv6Gateway)
log.Printf("[DEBUG] ipv6 address: %v\n", network.ipv6Address)
log.Printf("[DEBUG] ipv6 prefix length: %v\n", network.ipv6PrefixLength)
ipv6Spec.Ip = []types.BaseCustomizationIpV6Generator{
&types.CustomizationFixedIpV6{
IpAddress: network.ipv6Address,
SubnetMask: network.ipv6PrefixLength,
},
}
ipv6Spec.Gateway = []string{network.ipv6Gateway}
}
ipSetting.IpV6Spec = ipv6Spec
// network config // network config
config := types.CustomizationAdapterMapping{ config := types.CustomizationAdapterMapping{
Adapter: ipSetting, Adapter: ipSetting,

View File

@ -34,9 +34,9 @@ func TestAccVSphereVirtualMachine_basic(t *testing.T) {
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v) datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
} }
template := os.Getenv("VSPHERE_TEMPLATE") template := os.Getenv("VSPHERE_TEMPLATE")
gateway := os.Getenv("VSPHERE_NETWORK_GATEWAY") gateway := os.Getenv("VSPHERE_IPV4_GATEWAY")
label := os.Getenv("VSPHERE_NETWORK_LABEL") label := os.Getenv("VSPHERE_NETWORK_LABEL")
ip_address := os.Getenv("VSPHERE_NETWORK_IP_ADDRESS") ip_address := os.Getenv("VSPHERE_IPV4_ADDRESS")
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -95,9 +95,9 @@ func TestAccVSphereVirtualMachine_diskInitType(t *testing.T) {
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v) datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
} }
template := os.Getenv("VSPHERE_TEMPLATE") template := os.Getenv("VSPHERE_TEMPLATE")
gateway := os.Getenv("VSPHERE_NETWORK_GATEWAY") gateway := os.Getenv("VSPHERE_IPV4_GATEWAY")
label := os.Getenv("VSPHERE_NETWORK_LABEL") label := os.Getenv("VSPHERE_NETWORK_LABEL")
ip_address := os.Getenv("VSPHERE_NETWORK_IP_ADDRESS") ip_address := os.Getenv("VSPHERE_IPV4_ADDRESS")
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
@ -457,9 +457,9 @@ func TestAccVSphereVirtualMachine_createWithCdrom(t *testing.T) {
func TestAccVSphereVirtualMachine_createWithExistingVmdk(t *testing.T) { func TestAccVSphereVirtualMachine_createWithExistingVmdk(t *testing.T) {
vmdk_path := os.Getenv("VSPHERE_VMDK_PATH") vmdk_path := os.Getenv("VSPHERE_VMDK_PATH")
gateway := os.Getenv("VSPHERE_NETWORK_GATEWAY") gateway := os.Getenv("VSPHERE_IPV4_GATEWAY")
label := os.Getenv("VSPHERE_NETWORK_LABEL") label := os.Getenv("VSPHERE_NETWORK_LABEL")
ip_address := os.Getenv("VSPHERE_NETWORK_IP_ADDRESS") ip_address := os.Getenv("VSPHERE_IPV4_ADDRESS")
var vm virtualMachine var vm virtualMachine
var locationOpt string var locationOpt string
@ -679,6 +679,77 @@ func TestAccVSphereVirtualMachine_updateVcpu(t *testing.T) {
}) })
} }
func TestAccVSphereVirtualMachine_ipv4Andipv6(t *testing.T) {
var vm virtualMachine
var locationOpt string
var datastoreOpt string
if v := os.Getenv("VSPHERE_DATACENTER"); v != "" {
locationOpt += fmt.Sprintf(" datacenter = \"%s\"\n", v)
}
if v := os.Getenv("VSPHERE_CLUSTER"); v != "" {
locationOpt += fmt.Sprintf(" cluster = \"%s\"\n", v)
}
if v := os.Getenv("VSPHERE_RESOURCE_POOL"); v != "" {
locationOpt += fmt.Sprintf(" resource_pool = \"%s\"\n", v)
}
if v := os.Getenv("VSPHERE_DATASTORE"); v != "" {
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
}
template := os.Getenv("VSPHERE_TEMPLATE")
label := os.Getenv("VSPHERE_NETWORK_LABEL")
ipv4Address := os.Getenv("VSPHERE_IPV4_ADDRESS")
ipv4Gateway := os.Getenv("VSPHERE_IPV4_GATEWAY")
ipv6Address := os.Getenv("VSPHERE_IPV6_ADDRESS")
ipv6Gateway := os.Getenv("VSPHERE_IPV6_GATEWAY")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckVSphereVirtualMachineDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: fmt.Sprintf(
testAccCheckVSphereVirtualMachineConfig_ipv4Andipv6,
locationOpt,
label,
ipv4Address,
ipv4Gateway,
ipv6Address,
ipv6Gateway,
datastoreOpt,
template,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckVSphereVirtualMachineExists("vsphere_virtual_machine.ipv4ipv6", &vm),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "name", "terraform-test-ipv4-ipv6"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "vcpu", "2"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "memory", "4096"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "disk.#", "2"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "disk.0.template", template),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "network_interface.#", "1"),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "network_interface.0.label", label),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "network_interface.0.ipv4_address", ipv4Address),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "network_interface.0.ipv4_gateway", ipv4Gateway),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "network_interface.0.ipv6_address", ipv6Address),
resource.TestCheckResourceAttr(
"vsphere_virtual_machine.ipv4ipv6", "network_interface.0.ipv6_gateway", ipv6Gateway),
),
},
},
})
}
func testAccCheckVSphereVirtualMachineDestroy(s *terraform.State) error { func testAccCheckVSphereVirtualMachineDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*govmomi.Client) client := testAccProvider.Meta().(*govmomi.Client)
finder := find.NewFinder(client.Client, true) finder := find.NewFinder(client.Client, true)
@ -1079,3 +1150,30 @@ resource "vsphere_virtual_machine" "bar" {
} }
} }
` `
const testAccCheckVSphereVirtualMachineConfig_ipv4Andipv6 = `
resource "vsphere_virtual_machine" "ipv4ipv6" {
name = "terraform-test-ipv4-ipv6"
%s
vcpu = 2
memory = 4096
network_interface {
label = "%s"
ipv4_address = "%s"
ipv4_prefix_length = 24
ipv4_gateway = "%s"
ipv6_address = "%s"
ipv6_prefix_length = 64
ipv6_gateway = "%s"
}
disk {
%s
template = "%s"
iops = 500
}
disk {
size = 1
iops = 500
}
}
`

View File

@ -146,8 +146,10 @@ configuration fields to be set using the documented environment variables.
In addition, the following environment variables are used in tests, and must be In addition, the following environment variables are used in tests, and must be
set to valid values for your VMware vSphere environment: set to valid values for your VMware vSphere environment:
* VSPHERE\_NETWORK\_GATEWAY * VSPHERE\_IPV4\_GATEWAY
* VSPHERE\_NETWORK\_IP\_ADDRESS * VSPHERE\_IPV4\_ADDRESS
* VSPHERE\_IPV6\_GATEWAY
* VSPHERE\_IPV6\_ADDRESS
* VSPHERE\_NETWORK\_LABEL * VSPHERE\_NETWORK\_LABEL
* VSPHERE\_NETWORK\_LABEL\_DHCP * VSPHERE\_NETWORK\_LABEL\_DHCP
* VSPHERE\_TEMPLATE * VSPHERE\_TEMPLATE

View File

@ -68,7 +68,7 @@ The following arguments are supported:
* `datacenter` - (Optional) The name of a Datacenter in which to launch the virtual machine * `datacenter` - (Optional) The name of a Datacenter in which to launch the virtual machine
* `cluster` - (Optional) Name of a Cluster in which to launch the virtual machine * `cluster` - (Optional) Name of a Cluster in which to launch the virtual machine
* `resource_pool` (Optional) The name of a Resource Pool in which to launch the virtual machine. Requires full path (see cluster example). * `resource_pool` (Optional) The name of a Resource Pool in which to launch the virtual machine. Requires full path (see cluster example).
* `gateway` - (Optional) Gateway IP address to use for all network interfaces * `gateway` - __Deprecated, please use `network_interface.ipv4_gateway` instead__.
* `domain` - (Optional) A FQDN for the virtual machine; defaults to "vsphere.local" * `domain` - (Optional) A FQDN for the virtual machine; defaults to "vsphere.local"
* `time_zone` - (Optional) The [Linux](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) or [Windows](https://msdn.microsoft.com/en-us/library/ms912391.aspx) time zone to set on the virtual machine. Defaults to "Etc/UTC" * `time_zone` - (Optional) The [Linux](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) or [Windows](https://msdn.microsoft.com/en-us/library/ms912391.aspx) time zone to set on the virtual machine. Defaults to "Etc/UTC"
* `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual network adapter * `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual network adapter
@ -85,14 +85,18 @@ The following arguments are supported:
The `network_interface` block supports: The `network_interface` block supports:
* `label` - (Required) Label to assign to this network interface * `label` - (Required) Label to assign to this network interface
* `ipv4_address` - (Optional) Static IP to assign to this network interface. Interface will use DHCP if this is left blank. Currently only IPv4 IP addresses are supported. * `ipv4_address` - (Optional) Static IPv4 to assign to this network interface. Interface will use DHCP if this is left blank.
* `ipv4_prefix_length` - (Optional) prefix length to use when statically assigning an IP. * `ipv4_prefix_length` - (Optional) prefix length to use when statically assigning an IPv4 address.
* `ipv4_gateway` - (Optional) IPv4 gateway IP address to use.
* `ipv6_address` - (Optional) Static IPv6 to assign to this network interface. Interface will use DHCPv6 if this is left blank.
* `ipv6_prefix_length` - (Optional) prefix length to use when statically assigning an IPv6.
* `ipv6_gateway` - (Optional) IPv6 gateway IP address to use.
The following arguments are maintained for backwards compatibility and may be The following arguments are maintained for backwards compatibility and may be
removed in a future version: removed in a future version:
* `ip_address` - __Deprecated, please use `ipv4_address` instead_. * `ip_address` - __Deprecated, please use `ipv4_address` instead__.
* `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` instead_. * `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` instead__.
The `windows_opt_config` block supports: The `windows_opt_config` block supports:
@ -112,7 +116,7 @@ The `disk` block supports:
* `size` - (Required if template and bootable_vmdks_path not provided) Size of this disk (in GB). * `size` - (Required if template and bootable_vmdks_path not provided) Size of this disk (in GB).
* `iops` - (Optional) Number of virtual iops to allocate for this disk. * `iops` - (Optional) Number of virtual iops to allocate for this disk.
* `type` - (Optional) 'eager_zeroed' (the default), or 'thin' are supported options. * `type` - (Optional) 'eager_zeroed' (the default), or 'thin' are supported options.
* `vmdk` - (Required if template and size not provided) Path to a vmdk in a vSphere datastore. * `vmdk` - (Required if template and size not provided) Path to a vmdk in a vSphere datastore.
* `bootable` - (Optional) Set to 'true' if a vmdk was given and it should attempt to boot after creation. * `bootable` - (Optional) Set to 'true' if a vmdk was given and it should attempt to boot after creation.
<a id="cdrom"></a> <a id="cdrom"></a>