From 312883e667492a5fc7967ef9af5a15ef9e283879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristinn=20=C3=96rn=20Sigur=C3=B0sson?= Date: Wed, 3 Feb 2016 18:37:51 +0100 Subject: [PATCH] provider/vsphere: IPv6 support. 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. --- .../resource_vsphere_virtual_machine.go | 91 ++++++++++----- .../resource_vsphere_virtual_machine_test.go | 104 +++++++++++++++++- .../providers/vsphere/index.html.markdown | 6 +- .../vsphere/r/virtual_machine.html.markdown | 10 +- 4 files changed, 177 insertions(+), 34 deletions(-) diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go index c89c0b8dddc4..805e5f2cb9e8 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine.go @@ -32,8 +32,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 } @@ -54,7 +56,6 @@ type virtualMachine struct { template string networkInterfaces []networkInterface hardDisks []hardDisk - gateway string domain string timeZone string dnsSuffixes []string @@ -124,9 +125,10 @@ func resourceVSphereVirtualMachine() *schema.Resource { }, "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{ @@ -201,16 +203,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, }, @@ -290,10 +303,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) } @@ -337,6 +346,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 { @@ -353,6 +365,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) @@ -1085,12 +1109,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.") @@ -1098,20 +1119,38 @@ 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, + 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, }, - SubnetMask: subnetMask, } + ipv6Spec.Gateway = []string{network.ipv6Gateway} } + ipSetting.IpV6Spec = ipv6Spec // network config config := types.CustomizationAdapterMapping{ diff --git a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go index 97973efb5233..897b72e92430 100644 --- a/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go +++ b/builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go @@ -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) }, @@ -325,6 +325,77 @@ func TestAccVSphereVirtualMachine_createWithFolder(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) @@ -577,7 +648,7 @@ resource "vsphere_virtual_machine" "folder" { const testAccCheckVSphereVirtualMachineConfig_createWithFolder = ` resource "vsphere_folder" "with_folder" { - path = "%s" + path = "%s" %s } resource "vsphere_virtual_machine" "with_folder" { @@ -595,3 +666,30 @@ resource "vsphere_virtual_machine" "with_folder" { } } ` + +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 + } +} +` diff --git a/website/source/docs/providers/vsphere/index.html.markdown b/website/source/docs/providers/vsphere/index.html.markdown index a1b479c4e940..d0f86a4ae23c 100644 --- a/website/source/docs/providers/vsphere/index.html.markdown +++ b/website/source/docs/providers/vsphere/index.html.markdown @@ -76,8 +76,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 diff --git a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown index 008a0aed99c0..961019a38ccc 100644 --- a/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown +++ b/website/source/docs/providers/vsphere/r/virtual_machine.html.markdown @@ -39,7 +39,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 -* `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 [time zone](https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/timezone.html) to set on the virtual machine. Defaults to "Etc/UTC" * `dns_suffixes` - (Optional) List of name resolution suffixes for the virtual network adapter @@ -52,8 +52,12 @@ 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: