diff --git a/vsphere/internal/virtualdevice/virtual_machine_device_subresource.go b/vsphere/internal/virtualdevice/virtual_machine_device_subresource.go index 3e71bb060..51dfbdb4e 100644 --- a/vsphere/internal/virtualdevice/virtual_machine_device_subresource.go +++ b/vsphere/internal/virtualdevice/virtual_machine_device_subresource.go @@ -1018,7 +1018,7 @@ func (c *pciApplyConfig) modifyVirtualPciDevices(devList *schema.Set, op types.V } // PciPassthroughApplyOperation checks for changes in a virtual machine's -// PCI passthrough devices and creates config specs to apply apply to the +// PCI passthrough devices and creates config specs to apply to the // virtual machine. func PciPassthroughApplyOperation(d *schema.ResourceData, c *govmomi.Client, l object.VirtualDeviceList) (object.VirtualDeviceList, []types.BaseVirtualDeviceConfigSpec, error) { old, newValue := d.GetChange("pci_device_id") @@ -1096,62 +1096,147 @@ func PciPassthroughPostCloneOperation(d *schema.ResourceData, c *govmomi.Client, return applyConfig.VirtualDevice, applyConfig.Spec, nil } -// SharedPciPassthroughApplyOperation checks for changes in a virtual machine's -// Shared PCI passthrough device and creates config specs to apply apply to the -// virtual machine. -func SharedPciPassthroughApplyOperation(d *schema.ResourceData, c *govmomi.Client, l object.VirtualDeviceList) (object.VirtualDeviceList, []types.BaseVirtualDeviceConfigSpec, error) { - old, new := d.GetChange("shared_pci_device_id") - oldDevId := old.(string) - newDevId := new.(string) +// modifyVirtualSharedPciDevices will take a list of devices and an operation and +// will create the appropriate config spec. +func (c *pciApplyConfig) modifyVirtualSharedPciDevices(devList *schema.Set, op types.VirtualDeviceConfigSpecOperation) error { + log.Printf("VirtualMachine: Creating Shared PCI device specs %v", op) + for _, devId := range devList.List() { + log.Printf("[DEBUG] modifyVirtualSharedPciDevices: Appending %v spec for %s", op, devId.(string)) - var specs []types.BaseVirtualDeviceConfigSpec - if oldDevId == newDevId { - return l, specs, nil - } + dev := &types.VirtualPCIPassthrough{ + VirtualDevice: types.VirtualDevice{ + DynamicData: types.DynamicData{}, + Backing: &types.VirtualPCIPassthroughVmiopBackingInfo{ + Vgpu: devId.(string), + }, + }, + } - if oldDevId != "" { - vm, err := virtualmachine.FromUUID(c, d.Id()) + vm, err := virtualmachine.FromUUID(c.Client, c.ResourceData.Id()) if err != nil { - return nil, nil, err + return err } + vprops, err := virtualmachine.Properties(vm) if err != nil { - return nil, nil, err + return err } + // This will only find a device for delete operations. for _, vmDevP := range vprops.Config.Hardware.Device { if vmDev, ok := vmDevP.(*types.VirtualPCIPassthrough); ok { - if vmDev.Backing.(*types.VirtualPCIPassthroughVmiopBackingInfo).Vgpu == oldDevId { - dspec, err := object.VirtualDeviceList{vmDev}.ConfigSpec(types.VirtualDeviceConfigSpecOperationRemove) - if err != nil { - return nil, nil, err - } - specs = append(specs, dspec...) - - l = applyDeviceChange(l, dspec) - d.Set("reboot_required", true) + if vmDev.Backing.(*types.VirtualPCIPassthroughVmiopBackingInfo).Vgpu == devId { + dev = vmDev } } } - } - if newDevId != "" { - dev := &types.VirtualPCIPassthrough{ - VirtualDevice: types.VirtualDevice{ - DynamicData: types.DynamicData{}, - Backing: &types.VirtualPCIPassthroughVmiopBackingInfo{ - Vgpu: newDevId, - }, - }, - } - dspec, err := object.VirtualDeviceList{dev}.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd) + dspec, err := object.VirtualDeviceList{dev}.ConfigSpec(op) if err != nil { - return nil, nil, err + return err } - specs = append(specs, dspec...) - l = applyDeviceChange(l, dspec) - d.Set("reboot_required", true) + + c.Spec = append(c.Spec, dspec...) + c.VirtualDevice = applyDeviceChange(c.VirtualDevice, dspec) + } + log.Printf("VirtualMachine: Shared PCI device specs created") + return nil +} + +// SharedPciApplyOperation checks for changes in a virtual machine's +// Shared PCI device and creates config specs to apply to the +// virtual machine. +func SharedPciApplyOperation(d *schema.ResourceData, c *govmomi.Client, l object.VirtualDeviceList) (object.VirtualDeviceList, []types.BaseVirtualDeviceConfigSpec, error) { + log.Printf("[DEBUG] SharedPciApplyOperation: Looking for shared PCI device changes") + + // Get current (old) and new devices + old, newValue := d.GetChange("shared_pci_device_id") + oldDevIds := old.(*schema.Set) + newDevIds := newValue.(*schema.Set) + + // Compare + delDevs := oldDevIds.Difference(newDevIds) + addDevs := newDevIds.Difference(oldDevIds) + + // Create base apply config + applyConfig := &pciApplyConfig{ + Client: c, + ResourceData: d, + Spec: []types.BaseVirtualDeviceConfigSpec{}, + VirtualDevice: l, + } + + // If there are no changes, return as is + if addDevs.Len() == 0 && delDevs.Len() == 0 { + log.Printf("[DEBUG] SharedPciApplyOperation: No shared PCI device additions/deletions") + return applyConfig.VirtualDevice, applyConfig.Spec, nil + } + + // Set reboot + _ = d.Set("reboot_required", true) + + // Add new Shared PCI devices + log.Printf("[DEBUG] SharedPciApplyOperation: Identified %d shared PCI device additions", + addDevs.Len()) + err := applyConfig.modifyVirtualSharedPciDevices(addDevs, types.VirtualDeviceConfigSpecOperationAdd) + if err != nil { + return nil, nil, err } - return l, specs, nil + // Remove deleted Shared PCI devices + log.Printf("[DEBUG] SharedPciApplyOperation: Identified %d shared PCI device deletions", + delDevs.Len()) + err = applyConfig.modifyVirtualSharedPciDevices(delDevs, types.VirtualDeviceConfigSpecOperationRemove) + if err != nil { + return nil, nil, err + } + + return applyConfig.VirtualDevice, applyConfig.Spec, nil +} + +// SharedPciPostCloneOperation normalizes the Shared PCI devices +// on a newly-cloned virtual machine and outputs any necessary device change +// operations. It also sets the state in advance of the post-create read. +func SharedPciPostCloneOperation(d *schema.ResourceData, c *govmomi.Client, l object.VirtualDeviceList) (object.VirtualDeviceList, []types.BaseVirtualDeviceConfigSpec, error) { + log.Printf("[DEBUG] SharedPCIPostCloneOperation: Looking for shared PCI device changes post-clone") + + // Get current (old) and new devices + old, newValue := d.GetChange("shared_pci_device_id") + oldDevIds := old.(*schema.Set) + newDevIds := newValue.(*schema.Set) + + // Compare + delDevs := oldDevIds.Difference(newDevIds) + addDevs := newDevIds.Difference(oldDevIds) + + // Create base apply config + applyConfig := &pciApplyConfig{ + Client: c, + ResourceData: d, + Spec: []types.BaseVirtualDeviceConfigSpec{}, + VirtualDevice: l, + } + + // If there are no changes, return as is + if addDevs.Len() <= 0 && delDevs.Len() <= 0 { + log.Printf("[DEBUG] SharedPCIPostCloneOperation: No shared PCI device additions/deletions post-clone") + return applyConfig.VirtualDevice, applyConfig.Spec, nil + } + + // Add new Shared PCI devices + log.Printf("[DEBUG] SharedPCIPostCloneOperation: Identified %d shared PCI device additions post-clone", + addDevs.Len()) + err := applyConfig.modifyVirtualSharedPciDevices(addDevs, types.VirtualDeviceConfigSpecOperationAdd) + if err != nil { + return nil, nil, err + } + + // Remove deleted Shared PCI devices + log.Printf("[DEBUG] SharedPCIPostCloneOperation: Identified %d shared PCI device deletions post-clone", + delDevs.Len()) + err = applyConfig.modifyVirtualSharedPciDevices(delDevs, types.VirtualDeviceConfigSpecOperationRemove) + if err != nil { + return nil, nil, err + } + return applyConfig.VirtualDevice, applyConfig.Spec, nil } diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 780f5cd94..d63f6b60c 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -246,9 +246,9 @@ func resourceVSphereVirtualMachine() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "shared_pci_device_id": { - Type: schema.TypeString, + Type: schema.TypeSet, Optional: true, - Description: "Id of Shared PCI passthrough device, 'grid_rtx8000-8q'", + Description: "A list of Shared PCI passthrough device, 'grid_rtx8000-8q'", Elem: &schema.Schema{Type: schema.TypeString}, }, "clone": { @@ -1706,6 +1706,17 @@ func resourceVSphereVirtualMachinePostDeployChanges(d *schema.ResourceData, meta ) } cfgSpec.DeviceChange = virtualdevice.AppendDeviceChangeSpec(cfgSpec.DeviceChange, delta...) + // Shared PCI devices + devices, delta, err = virtualdevice.SharedPciPostCloneOperation(d, client, devices) + if err != nil { + return resourceVSphereVirtualMachineRollbackCreate( + d, + meta, + vm, + fmt.Errorf("error processing shared PCI device changes post-clone: %s", err), + ) + } + cfgSpec.DeviceChange = virtualdevice.AppendDeviceChangeSpec(cfgSpec.DeviceChange, delta...) log.Printf("[DEBUG] %s: Final device list: %s", resourceVSphereVirtualMachineIDString(d), virtualdevice.DeviceListString(devices)) log.Printf("[DEBUG] %s: Final device change cfgSpec: %s", resourceVSphereVirtualMachineIDString(d), virtualdevice.DeviceChangeString(cfgSpec.DeviceChange)) @@ -2009,8 +2020,8 @@ func applyVirtualDevices(d *schema.ResourceData, c *govmomi.Client, l object.Vir return nil, err } spec = virtualdevice.AppendDeviceChangeSpec(spec, delta...) - // Shared PCI passthrough device - l, delta, err = virtualdevice.SharedPciPassthroughApplyOperation(d, c, l) + // Shared PCI device + l, delta, err = virtualdevice.SharedPciApplyOperation(d, c, l) if err != nil { return nil, err } diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 36ed0c521..1b1dc23c0 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -614,7 +614,9 @@ The following options are general virtual machine and provider workflow options: - `cdrom` - (Optional) A specification for a CD-ROM device on the virtual machine. See [CD-ROM options](#cd-rom-options) for more information. -- `clone` - (Optional) When specified, the virtual machine will be created as a clone of a specified template. Optional customization options can be submitted for the resource. See [creating a virtual machine from a template](#creating-a-virtual-machine-from-a-template) for more information. +* `clone` - (Optional) When specified, the virtual machine will be created as a clone of a specified template. Optional customization options can be submitted for the resource. See [creating a virtual machine from a template](#creating-a-virtual-machine-from-a-template) for more information. + +~> **NOTE:** Cloning requires vCenter Server and is not supported on direct ESXi host connections. - `extra_config_reboot_required` - (Optional) Allow the virtual machine to be rebooted when a change to `extra_config` occurs. Default: `true`. @@ -659,15 +661,17 @@ The following options are general virtual machine and provider workflow options: - `name` - (Required) The name of the virtual machine. -- `network_interface` - (Required) A specification for a virtual NIC on the virtual machine. See [network interface options](#network-interface-options) for more information. +* `network_interface` - (Required) A specification for a virtual NIC on the virtual machine. See [network interface options](#network-interface-options) for more information. + +* `pci_device_id` - (Optional) List of host PCI device IDs in which to create PCI passthroughs. -- `pci_device_id` - (Optional) List of host PCI device IDs in which to create PCI passthroughs. +* `shared_pci_device_id` - (Optional) A shared PCI device ID to create PCI passthrough. ~> **NOTE:** Cloning requires vCenter Server and is not supported on direct ESXi host connections. - `ovf_deploy` - (Optional) When specified, the virtual machine will be deployed from the provided OVF/OVA template. See [creating a virtual machine from an OVF/OVA template](#creating-a-virtual-machine-from-an-ovf-ova-template) for more information. -- `replace_trigger` - (Optional) Triggers replacement of resource whenever it changes. +* `replace_trigger` - (Optional) Triggers replacement of resource whenever it changes. For example, `replace_trigger = sha256(format("%s-%s",data.template_file.cloud_init_metadata.rendered,data.template_file.cloud_init_userdata.rendered))` will fingerprint the changes in cloud-init metadata and userdata templates. This will enable a replacement of the resource whenever the dependant template renders a new configuration. (Forces a replacement.) @@ -681,7 +685,7 @@ For example, `replace_trigger = sha256(format("%s-%s",data.template_file.cloud_i - `scsi_type` - (Optional) The SCSI controller type for the virtual machine. One of `lsilogic` (LSI Logic Parallel), `lsilogic-sas` (LSI Logic SAS) or `pvscsi` (VMware Paravirtual). Default: `pvscsi`. -- `scsi_bus_sharing` - (Optional) The type of SCSI bus sharing for the virtual machine SCSI controller. One of `physicalSharing`, `virtualSharing`, and `noSharing`. Default: `noSharing`. +* `scsi_bus_sharing` - (Optional) The type of SCSI bus sharing for the virtual machine SCSI controller. One of `physicalSharing`, `virtualSharing`, and `noSharing`. Default: `noSharing`. - `storage_policy_id` - (Optional) The ID of the storage policy to assign to the home directory of a virtual machine.