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

View File

@ -34,9 +34,9 @@ func TestAccVSphereVirtualMachine_basic(t *testing.T) {
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
}
template := os.Getenv("VSPHERE_TEMPLATE")
gateway := os.Getenv("VSPHERE_NETWORK_GATEWAY")
gateway := os.Getenv("VSPHERE_IPV4_GATEWAY")
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{
PreCheck: func() { testAccPreCheck(t) },
@ -95,9 +95,9 @@ func TestAccVSphereVirtualMachine_diskInitType(t *testing.T) {
datastoreOpt = fmt.Sprintf(" datastore = \"%s\"\n", v)
}
template := os.Getenv("VSPHERE_TEMPLATE")
gateway := os.Getenv("VSPHERE_NETWORK_GATEWAY")
gateway := os.Getenv("VSPHERE_IPV4_GATEWAY")
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{
PreCheck: func() { testAccPreCheck(t) },
@ -457,9 +457,9 @@ func TestAccVSphereVirtualMachine_createWithCdrom(t *testing.T) {
func TestAccVSphereVirtualMachine_createWithExistingVmdk(t *testing.T) {
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")
ip_address := os.Getenv("VSPHERE_NETWORK_IP_ADDRESS")
ip_address := os.Getenv("VSPHERE_IPV4_ADDRESS")
var vm virtualMachine
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 {
client := testAccProvider.Meta().(*govmomi.Client)
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
set to valid values for your VMware vSphere environment:
* VSPHERE\_NETWORK\_GATEWAY
* VSPHERE\_NETWORK\_IP\_ADDRESS
* VSPHERE\_IPV4\_GATEWAY
* VSPHERE\_IPV4\_ADDRESS
* VSPHERE\_IPV6\_GATEWAY
* VSPHERE\_IPV6\_ADDRESS
* VSPHERE\_NETWORK\_LABEL
* VSPHERE\_NETWORK\_LABEL\_DHCP
* 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
* `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).
* `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"
* `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
@ -85,14 +85,18 @@ The following arguments are supported:
The `network_interface` block supports:
* `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_prefix_length` - (Optional) prefix length to use when statically assigning an IP.
* `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 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
removed in a future version:
* `ip_address` - __Deprecated, please use `ipv4_address` instead_.
* `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` instead_.
* `ip_address` - __Deprecated, please use `ipv4_address` instead__.
* `subnet_mask` - __Deprecated, please use `ipv4_prefix_length` instead__.
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).
* `iops` - (Optional) Number of virtual iops to allocate for this disk.
* `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.
<a id="cdrom"></a>