Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/resources/virtual_machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,51 @@ In the example above, the first interface is assigned to the `routable` network

On some Linux distributions, the first interface may be presented as `eth0` and the second may be presented as `eth1`.

#### Using external network management plane

A virtual machine's network interface can be bound to an external network management plane using `external_port_id` attribute. While using an external network management plane, such as NSX-T, the network management plane will allocate a port id for the VM, which will be placed automatically within the `external_port_id` attribute.
When using a pre-allocated port on the network management plane, the external port attachment id should be assigned to the `external_port_id` to bind the network interface with the external port.

**Example**:

```hcl
# Create a port on NSX-T using NSX-T provider resource. Set the port attachment to be referenced later by vSphere VM
resource "nsxt_policy_segment_port" "port" {
display_name = "port"
segment_path = nsxt_policy_segment.segment.path
attachment {
id = "port-attachment"
}
}

# Locate the NSX-T segment's ID in vSphere
data "vsphere_network" "nsx_segment" {
datacenter_id = data.vsphere_datacenter.datacenter.id
name = nsxt_policy_segment.segment.display_name
}

resource "vsphere_virtual_machine" "vm" {
name = "vm"

datacenter_id = data.vsphere_datacenter.datacenter.id
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
datastore_id = data.vsphere_datastore.datastore.id
wait_for_guest_net_timeout = 0

ovf_deploy {
remote_ovf_url = "https://example.com/foo.ova"
}

network_interface {
# Reference the NSX-T segment ID in vSphere
network_id = data.vsphere_network.nsx_segment.id

# Reference the user-configured attachment id, which will identify the NSX-T created port
external_port_id = nsxt_policy_segment_port.port.attachment[0].id
}
}
```

The options are:

* `network_id` - (Required) The [managed object reference ID][docs-about-morefs] of the network on which to connect the virtual machine network interface.
Expand All @@ -1027,6 +1072,8 @@ The options are:

* `ovf_mapping` - (Optional) Specifies which NIC in an OVF/OVA the `network_interface` should be associated. Only applies at creation when deploying from an OVF/OVA.

* `external_port_id` - (Optional) The external port id to be bound to the VM port. This attribute will contain the port ID which was set by external network management plane. A user can choose to force a specific predefined port id which has been configured on the external network management plane.

### Video Card Options

The virtual video card is managed by adding a `video_card` block.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ func NetworkInterfaceSubresourceSchema() map[string]*schema.Schema {
ForceNew: true,
Description: "Mapping of network interface to OVF network.",
},
"external_port_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "The external port id to be bound to the VM port.",
},
}
structure.MergeSchema(s, subresourceSchema())
return s
Expand Down Expand Up @@ -714,6 +720,7 @@ func ReadNetworkInterfaces(l object.VirtualDeviceList) ([]map[string]interface{}

m["adapter_type"] = virtualEthernetCardString(device.(types.BaseVirtualEthernetCard))
m["mac_address"] = ethernetCard.MacAddress
m["external_port_id"] = ethernetCard.ExternalId
m["network_id"] = networkID

out = append(out, m)
Expand Down Expand Up @@ -832,6 +839,11 @@ func (r *NetworkInterfaceSubresource) Create(l object.VirtualDeviceList) ([]type
card.MacAddress = r.Get("mac_address").(string)
}

externalID := r.Get("external_port_id").(string)
if externalID != "" {
card.ExternalId = externalID
}

if r.Get("adapter_type") != networkInterfaceSubresourceTypeSriov {
bandwidthLimit := structure.Int64Ptr(-1)
bandwidthReservation := structure.Int64Ptr(0)
Expand Down Expand Up @@ -940,6 +952,7 @@ func (r *NetworkInterfaceSubresource) Read(l object.VirtualDeviceList) error {
r.Set("network_id", netID)
r.Set("use_static_mac", card.AddressType == string(types.VirtualEthernetCardMacTypeManual))
r.Set("mac_address", card.MacAddress)
r.Set("external_port_id", card.ExternalId)

if r.Get("adapter_type") != networkInterfaceSubresourceTypeSriov {
if card.ResourceAllocation != nil {
Expand Down Expand Up @@ -1054,6 +1067,14 @@ func (r *NetworkInterfaceSubresource) Update(l object.VirtualDeviceList) ([]type
card.Backing = backing
}

oldExternalID, newExternalID := r.GetChange("external_port_id")
if oldExternalID.(string) != "" && newExternalID.(string) == "" {
// Once a VM has been attached to a pre-allocated port, it cannot be reattached to an ephemeral port,
// so only allow replacement with another pre-allocated port.
return nil, fmt.Errorf("cannot reset external_port_id to use an ephemeral port")
}
card.ExternalId = newExternalID.(string)

if r.Get("use_static_mac").(bool) {
card.AddressType = string(types.VirtualEthernetCardMacTypeManual)
card.MacAddress = r.Get("mac_address").(string)
Expand Down
64 changes: 64 additions & 0 deletions vsphere/resource_vsphere_virtual_machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3010,6 +3010,42 @@ func TestAccResourceVSphereVirtualMachine_cpuTopology(t *testing.T) {
})
}

func TestAccResourceVSphereVirtualMachine_vAppExternalPort(t *testing.T) {
// Prerequisites for this test:
// * Create a segment and a segment port on NSX-T.
// * Assign TF_VAR_VSPHERE_PG_NAME the value of the segment name
// * Assign TF_VAR_VSPHERE_EXT_PORT_ID the attachment id of the segment port
// * TF_VAR_VSPHERE_OVF_URL should reference some lightweight OVF URL
testAccSkipUnstable(t)
resource.Test(t, resource.TestCase{
PreCheck: func() {
RunSweepers()
testAccPreCheck(t)
testAccCheckEnvVariables(t, []string{
"TF_VAR_VSPHERE_DATACENTER",
"TF_VAR_VSPHERE_ESXI1",
"TF_VAR_VSPHERE_ESXI2",
"TF_VAR_VSPHERE_NFS_DS_NAME",
"TF_VAR_VSPHERE_CLUSTER",
"TF_VAR_VSPHERE_PG_NAME",
"TF_VAR_VSPHERE_OVF_URL",
"TF_VAR_VSPHERE_EXT_PORT_ID"})
},
Providers: testAccProviders,
CheckDestroy: testAccResourceVSphereVirtualMachineCheckExists(false),
Steps: []resource.TestStep{
{
Config: testAccResourceVSphereVirtualMachineVAppExternalPort(),
Check: resource.ComposeTestCheckFunc(
testAccResourceVSphereVirtualMachineCheckExists(true),
resource.TestCheckResourceAttrSet("vsphere_virtual_machine.vm", "network_interface.0.network_id"),
resource.TestCheckResourceAttrSet("vsphere_virtual_machine.vm", "network_interface.0.external_port_id"),
),
},
},
})
}

func testAccResourceVSphereVirtualMachinePreCheck(t *testing.T) {
// Note that TF_VAR_VSPHERE_USE_LINKED_CLONE is also a variable and its presence
// speeds up tests greatly, but it's not a necessary variable, so we don't
Expand Down Expand Up @@ -8761,6 +8797,34 @@ resource "vsphere_virtual_machine" "vm" {
)
}

func testAccResourceVSphereVirtualMachineVAppExternalPort() string {
return fmt.Sprintf(`
%s // Mix and match config

resource "vsphere_virtual_machine" "vm" {
name = "testacc-test"
datacenter_id = data.vsphere_datacenter.rootdc1.id
resource_pool_id = vsphere_resource_pool.pool1.id
datastore_id = data.vsphere_datastore.rootds1.id

wait_for_guest_net_timeout = 0

ovf_deploy {
remote_ovf_url = "%s"
}

network_interface {
network_id = data.vsphere_network.network1.id
external_port_id = "%s"
}
}
`,
testAccResourceVSphereVirtualMachineConfigBase(),
os.Getenv("TF_VAR_VSPHERE_OVF_URL"),
os.Getenv("TF_VAR_VSPHERE_EXT_PORT_ID"),
)
}

// Tests to skip until new features are developed.

// Needs storage policy resource
Expand Down