Skip to content

Commit

Permalink
provider/vsphere: IPv6 support. (hashicorp#6457)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stack72 authored and Xavier Sellier committed May 17, 2016
1 parent 6d1e0ab commit ac6f427
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 40 deletions.
91 changes: 65 additions & 26 deletions builtin/providers/vsphere/resource_vsphere_virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -77,7 +79,6 @@ type virtualMachine struct {
networkInterfaces []networkInterface
hardDisks []hardDisk
cdroms []cdrom
gateway string
domain string
timeZone string
dnsSuffixes []string
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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,
},

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -1473,33 +1497,48 @@ 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.")
}
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{
Expand Down
110 changes: 104 additions & 6 deletions builtin/providers/vsphere/resource_vsphere_virtual_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down Expand Up @@ -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) },
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}
`
6 changes: 4 additions & 2 deletions website/source/docs/providers/vsphere/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand All @@ -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>
Expand Down

0 comments on commit ac6f427

Please sign in to comment.