0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-08-22 19:38:35 +00:00

feat(vm): add support for USB devices passthrough (#666)

* feat: support usb devices for vm; fixes #665

Signed-off-by: Daniel Muehlbachler-Pietrzykowski <daniel@muehlbachler.io>

* chore: fix linter errors

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

---------

Signed-off-by: Daniel Muehlbachler-Pietrzykowski <daniel@muehlbachler.io>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Daniel Mühlbachler-Pietrzykowski 2023-10-31 02:41:44 +01:00 committed by GitHub
parent db842a2399
commit cec4e65868
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 263 additions and 12 deletions

View File

@ -301,6 +301,12 @@ output "ubuntu_vm_public_key" {
is a relative path under `/usr/share/kvm/`. is a relative path under `/usr/share/kvm/`.
- `xvga` - (Optional) Marks the PCI(e) device as the primary GPU of the VM. - `xvga` - (Optional) Marks the PCI(e) device as the primary GPU of the VM.
With this enabled the `vga` configuration argument will be ignored. With this enabled the `vga` configuration argument will be ignored.
- `usb` - (Optional) A host USB device mapping (multiple blocks supported).
- `host` - (Optional) The USB device ID. Use either this or `mapping`.
- `mapping` - (Optional) The resource mapping name of the device, for
example usbdevice. Use either this or `id`.
- `usb3` - (Optional) Makes the USB device a USB3 device for the VM (defaults
to `false`).
- `initialization` - (Optional) The cloud-init configuration. - `initialization` - (Optional) The cloud-init configuration.
- `datastore_id` - (Optional) The identifier for the datastore to create the - `datastore_id` - (Optional) The identifier for the datastore to create the
cloud-init disk in (defaults to `local-lvm`). cloud-init disk in (defaults to `local-lvm`).

View File

@ -165,9 +165,21 @@ resource "proxmox_virtual_environment_vm" "example" {
# pcie = true # pcie = true
#} #}
#usb {
# host = "0000:1234"
# mapping = "usbdevice1"
# usb3 = false
#}
#usb {
# host = "0000:5678"
# mapping = "usbdevice2"
# usb3 = false
#}
# attached disks from data_vm # attached disks from data_vm
dynamic "disk" { dynamic "disk" {
for_each = {for idx, val in proxmox_virtual_environment_vm.data_vm.disk : idx => val} for_each = { for idx, val in proxmox_virtual_environment_vm.data_vm.disk : idx => val }
iterator = data_disk iterator = data_disk
content { content {
datastore_id = data_disk.value["datastore_id"] datastore_id = data_disk.value["datastore_id"]
@ -175,7 +187,7 @@ resource "proxmox_virtual_environment_vm" "example" {
file_format = data_disk.value["file_format"] file_format = data_disk.value["file_format"]
size = data_disk.value["size"] size = data_disk.value["size"]
# assign from scsi1 and up # assign from scsi1 and up
interface = "scsi${data_disk.key + 1}" interface = "scsi${data_disk.key + 1}"
} }
} }
} }

View File

@ -242,8 +242,9 @@ type CustomStorageDevices map[string]CustomStorageDevice
// CustomUSBDevice handles QEMU USB device parameters. // CustomUSBDevice handles QEMU USB device parameters.
type CustomUSBDevice struct { type CustomUSBDevice struct {
HostDevice string `json:"host" url:"host"` HostDevice *string `json:"host" url:"host"`
USB3 *types.CustomBool `json:"usb3,omitempty" url:"usb3,omitempty,int"` Mapping *string `json:"mapping,omitempty" url:"mapping,omitempty"`
USB3 *types.CustomBool `json:"usb3,omitempty" url:"usb3,omitempty,int"`
} }
// CustomUSBDevices handles QEMU USB device parameters. // CustomUSBDevices handles QEMU USB device parameters.
@ -517,7 +518,10 @@ type GetResponseData struct {
Tags *string `json:"tags,omitempty"` Tags *string `json:"tags,omitempty"`
Template *types.CustomBool `json:"template,omitempty"` Template *types.CustomBool `json:"template,omitempty"`
TimeDriftFixEnabled *types.CustomBool `json:"tdf,omitempty"` TimeDriftFixEnabled *types.CustomBool `json:"tdf,omitempty"`
USBDevices *CustomUSBDevices `json:"usb,omitempty"` USBDevice0 *CustomUSBDevice `json:"usb0,omitempty"`
USBDevice1 *CustomUSBDevice `json:"usb1,omitempty"`
USBDevice2 *CustomUSBDevice `json:"usb2,omitempty"`
USBDevice3 *CustomUSBDevice `json:"usb3,omitempty"`
VGADevice *CustomVGADevice `json:"vga,omitempty"` VGADevice *CustomVGADevice `json:"vga,omitempty"`
VirtualCPUCount *int `json:"vcpus,omitempty"` VirtualCPUCount *int `json:"vcpus,omitempty"`
VirtualIODevice0 *CustomStorageDevice `json:"virtio0,omitempty"` VirtualIODevice0 *CustomStorageDevice `json:"virtio0,omitempty"`
@ -1232,8 +1236,16 @@ func (r CustomStorageDevices) EncodeValues(_ string, v *url.Values) error {
// EncodeValues converts a CustomUSBDevice struct to a URL vlaue. // EncodeValues converts a CustomUSBDevice struct to a URL vlaue.
func (r CustomUSBDevice) EncodeValues(key string, v *url.Values) error { func (r CustomUSBDevice) EncodeValues(key string, v *url.Values) error {
if r.HostDevice == nil && r.Mapping == nil {
return fmt.Errorf("either device ID or resource mapping must be set")
}
values := []string{ values := []string{
fmt.Sprintf("host=%s", r.HostDevice), fmt.Sprintf("host=%s", *(r.HostDevice)),
}
if r.Mapping != nil {
values = append(values, fmt.Sprintf("mapping=%s", *r.Mapping))
} }
if r.USB3 != nil { if r.USB3 != nil {
@ -1696,6 +1708,36 @@ func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
return nil return nil
} }
// UnmarshalJSON converts a CustomUSBDevice string to an object.
func (r *CustomUSBDevice) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("failed to unmarshal CustomUSBDevice: %w", err)
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.HostDevice = &v[1]
} else if len(v) == 2 {
switch v[0] {
case "host":
r.HostDevice = &v[1]
case "mapping":
r.Mapping = &v[1]
case "usb3":
bv := types.CustomBool(v[1] == "1")
r.USB3 = &bv
}
}
}
return nil
}
// UnmarshalJSON converts a CustomSharedMemory string to an object. // UnmarshalJSON converts a CustomSharedMemory string to an object.
func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error { func (r *CustomSharedMemory) UnmarshalJSON(b []byte) error {
var s string var s string

View File

@ -124,3 +124,50 @@ func TestCustomPCIDevice_UnmarshalJSON(t *testing.T) {
}) })
} }
} }
func TestCustomUSBDevice_UnmarshalJSON(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line string
want *CustomUSBDevice
wantErr bool
}{
{
name: "id only usb device",
line: `"host=0000:81"`,
want: &CustomUSBDevice{
HostDevice: types.StrPtr("0000:81"),
},
},
{
name: "usb device with more details",
line: `"host=81:00,usb3=0"`,
want: &CustomUSBDevice{
HostDevice: types.StrPtr("81:00"),
USB3: types.BoolPtr(false),
},
},
{
name: "usb device with mapping",
line: `"mapping=mappeddevice,usb=0"`,
want: &CustomUSBDevice{
HostDevice: nil,
Mapping: types.StrPtr("mappeddevice"),
USB3: types.BoolPtr(false),
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &CustomUSBDevice{}
if err := r.UnmarshalJSON([]byte(tt.line)); (err != nil) != tt.wantErr {
t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -140,6 +140,7 @@ const (
maxResourceVirtualEnvironmentVMNetworkDevices = 8 maxResourceVirtualEnvironmentVMNetworkDevices = 8
maxResourceVirtualEnvironmentVMSerialDevices = 4 maxResourceVirtualEnvironmentVMSerialDevices = 4
maxResourceVirtualEnvironmentVMHostPCIDevices = 8 maxResourceVirtualEnvironmentVMHostPCIDevices = 8
maxResourceVirtualEnvironmentVMHostUSBDevices = 4
mkResourceVirtualEnvironmentVMRebootAfterCreation = "reboot" mkResourceVirtualEnvironmentVMRebootAfterCreation = "reboot"
mkResourceVirtualEnvironmentVMOnBoot = "on_boot" mkResourceVirtualEnvironmentVMOnBoot = "on_boot"
@ -280,6 +281,10 @@ const (
mkResourceVirtualEnvironmentVMTimeoutShutdownVM = "timeout_shutdown_vm" mkResourceVirtualEnvironmentVMTimeoutShutdownVM = "timeout_shutdown_vm"
mkResourceVirtualEnvironmentVMTimeoutStartVM = "timeout_start_vm" mkResourceVirtualEnvironmentVMTimeoutStartVM = "timeout_start_vm"
mkResourceVirtualEnvironmentVMTimeoutStopVM = "timeout_stop_vm" mkResourceVirtualEnvironmentVMTimeoutStopVM = "timeout_stop_vm"
mkResourceVirtualEnvironmentVMHostUSB = "usb"
mkResourceVirtualEnvironmentVMHostUSBDevice = "host"
mkResourceVirtualEnvironmentVMHostUSBDeviceMapping = "mapping"
mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3 = "usb3"
mkResourceVirtualEnvironmentVMVGA = "vga" mkResourceVirtualEnvironmentVMVGA = "vga"
mkResourceVirtualEnvironmentVMVGAEnabled = "enabled" mkResourceVirtualEnvironmentVMVGAEnabled = "enabled"
mkResourceVirtualEnvironmentVMVGAMemory = "memory" mkResourceVirtualEnvironmentVMVGAMemory = "memory"
@ -1061,6 +1066,34 @@ func VM() *schema.Resource {
}, },
}, },
}, },
mkResourceVirtualEnvironmentVMHostUSB: {
Type: schema.TypeList,
Description: "The Host USB devices mapped to the VM",
Optional: true,
ForceNew: false,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkResourceVirtualEnvironmentVMHostUSBDevice: {
Type: schema.TypeString,
Description: "The USB device ID for Proxmox, in form of '<MANUFACTURER>:<ID>'",
Required: true,
},
mkResourceVirtualEnvironmentVMHostUSBDeviceMapping: {
Type: schema.TypeString,
Description: "The resource mapping name of the device, for example usbdisk. Use either this or id.",
Optional: true,
},
mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3: {
Type: schema.TypeBool,
Description: "Makes the USB device a USB3 device for the machine. Default is false",
Optional: true,
},
},
},
},
mkResourceVirtualEnvironmentVMKeyboardLayout: { mkResourceVirtualEnvironmentVMKeyboardLayout: {
Type: schema.TypeString, Type: schema.TypeString,
Description: "The keyboard layout", Description: "The keyboard layout",
@ -1831,6 +1864,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
cpu := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{}) cpu := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{})
initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{}) initialization := d.Get(mkResourceVirtualEnvironmentVMInitialization).([]interface{})
hostPCI := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{}) hostPCI := d.Get(mkResourceVirtualEnvironmentVMHostPCI).([]interface{})
hostUSB := d.Get(mkResourceVirtualEnvironmentVMHostUSB).([]interface{})
keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string) keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string)
memory := d.Get(mkResourceVirtualEnvironmentVMMemory).([]interface{}) memory := d.Get(mkResourceVirtualEnvironmentVMMemory).([]interface{})
networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{}) networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{})
@ -1997,6 +2031,10 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
updateBody.PCIDevices = vmGetHostPCIDeviceObjects(d) updateBody.PCIDevices = vmGetHostPCIDeviceObjects(d)
} }
if len(hostUSB) > 0 {
updateBody.USBDevices = vmGetHostUSBDeviceObjects(d)
}
if len(cdrom) > 0 || len(initialization) > 0 { if len(cdrom) > 0 || len(initialization) > 0 {
updateBody.IDEDevices = ideDevices updateBody.IDEDevices = ideDevices
} }
@ -2393,6 +2431,8 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
pciDeviceObjects := vmGetHostPCIDeviceObjects(d) pciDeviceObjects := vmGetHostPCIDeviceObjects(d)
usbDeviceObjects := vmGetHostUSBDeviceObjects(d)
keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string) keyboardLayout := d.Get(mkResourceVirtualEnvironmentVMKeyboardLayout).(string)
memoryBlock, err := structure.GetSchemaBlock( memoryBlock, err := structure.GetSchemaBlock(
resource, resource,
@ -2562,6 +2602,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
StartupOrder: startupOrder, StartupOrder: startupOrder,
TabletDeviceEnabled: &tabletDevice, TabletDeviceEnabled: &tabletDevice,
Template: &template, Template: &template,
USBDevices: usbDeviceObjects,
VGADevice: vgaDevice, VGADevice: vgaDevice,
VMID: &vmID, VMID: &vmID,
} }
@ -3241,6 +3282,31 @@ func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
return pciDeviceObjects return pciDeviceObjects
} }
func vmGetHostUSBDeviceObjects(d *schema.ResourceData) vms.CustomUSBDevices {
usbDevice := d.Get(mkResourceVirtualEnvironmentVMHostUSB).([]interface{})
usbDeviceObjects := make(vms.CustomUSBDevices, len(usbDevice))
for i, usbDeviceEntry := range usbDevice {
block := usbDeviceEntry.(map[string]interface{})
host, _ := block[mkResourceVirtualEnvironmentVMHostUSBDevice].(string)
usb3 := types.CustomBool(block[mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3].(bool))
mapping, _ := block[mkResourceVirtualEnvironmentVMHostUSBDeviceMapping].(string)
device := vms.CustomUSBDevice{
HostDevice: &host,
USB3: &usb3,
}
if mapping != "" {
device.Mapping = &mapping
}
usbDeviceObjects[i] = device
}
return usbDeviceObjects
}
func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices { func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices {
networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{}) networkDevice := d.Get(mkResourceVirtualEnvironmentVMNetworkDevice).([]interface{})
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice)) networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))
@ -3417,33 +3483,38 @@ func vmGetStartupOrder(d *schema.ResourceData) *vms.CustomStartupOrder {
} }
func vmGetTagsString(d *schema.ResourceData) string { func vmGetTagsString(d *schema.ResourceData) string {
tags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
var sanitizedTags []string var sanitizedTags []string
tags := d.Get(mkResourceVirtualEnvironmentVMTags).([]interface{})
for i := 0; i < len(tags); i++ { for i := 0; i < len(tags); i++ {
tag := strings.TrimSpace(tags[i].(string)) tag := strings.TrimSpace(tags[i].(string))
if len(tag) > 0 { if len(tag) > 0 {
sanitizedTags = append(sanitizedTags, tag) sanitizedTags = append(sanitizedTags, tag)
} }
} }
sort.Strings(sanitizedTags) sort.Strings(sanitizedTags)
return strings.Join(sanitizedTags, ";") return strings.Join(sanitizedTags, ";")
} }
func vmGetSerialDeviceValidator() schema.SchemaValidateDiagFunc { func vmGetSerialDeviceValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(func(i interface{}, k string) (s []string, es []error) { return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) {
v, ok := i.(string) v, ok := i.(string)
var es []error
if !ok { if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k)) es = append(es, fmt.Errorf("expected type of %s to be string", k))
return return nil, es
} }
if !strings.HasPrefix(v, "/dev/") && v != "socket" { if !strings.HasPrefix(v, "/dev/") && v != "socket" {
es = append(es, fmt.Errorf("expected %s to be '/dev/*' or 'socket'", k)) es = append(es, fmt.Errorf("expected %s to be '/dev/*' or 'socket'", k))
return return nil, es
} }
return return nil, es
}) })
} }
@ -4068,12 +4139,50 @@ func vmReadCustom(
} }
if len(currentPCIList) > 0 { if len(currentPCIList) > 0 {
// todo: reordering of devices by PVE may cause an issue here
orderedPCIList := orderedListFromMap(pciMap) orderedPCIList := orderedListFromMap(pciMap)
err := d.Set(mkResourceVirtualEnvironmentVMHostPCI, orderedPCIList) err := d.Set(mkResourceVirtualEnvironmentVMHostPCI, orderedPCIList)
diags = append(diags, diag.FromErr(err)...) diags = append(diags, diag.FromErr(err)...)
} }
currentUSBList := d.Get(mkResourceVirtualEnvironmentVMHostUSB).([]interface{})
usbMap := map[string]interface{}{}
usbDevices := getUSBInfo(vmConfig, d)
for pi, pp := range usbDevices {
if (pp == nil) || (pp.HostDevice == nil && pp.Mapping == nil) {
continue
}
usb := map[string]interface{}{}
if pp.HostDevice != nil {
usb[mkResourceVirtualEnvironmentVMHostUSBDevice] = *pp.HostDevice
} else {
usb[mkResourceVirtualEnvironmentVMHostUSBDevice] = ""
}
if pp.USB3 != nil {
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3] = *pp.USB3
} else {
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3] = false
}
if pp.Mapping != nil {
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceMapping] = *pp.Mapping
} else {
usb[mkResourceVirtualEnvironmentVMHostUSBDeviceMapping] = ""
}
usbMap[pi] = usb
}
if len(currentUSBList) > 0 {
// todo: reordering of devices by PVE may cause an issue here
orderedUSBList := orderedListFromMap(usbMap)
err := d.Set(mkResourceVirtualEnvironmentVMHostUSB, orderedUSBList)
diags = append(diags, diag.FromErr(err)...)
}
// Compare the initialization configuration to the one stored in the state. // Compare the initialization configuration to the one stored in the state.
initialization := map[string]interface{}{} initialization := map[string]interface{}{}
@ -5410,6 +5519,17 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
rebootRequired = true rebootRequired = true
} }
// Prepare the new usb devices configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMHostUSB) {
updateBody.USBDevices = vmGetHostUSBDeviceObjects(d)
for i := len(updateBody.USBDevices); i < maxResourceVirtualEnvironmentVMHostUSBDevices; i++ {
del = append(del, fmt.Sprintf("usb%d", i))
}
rebootRequired = true
}
// Prepare the new memory configuration. // Prepare the new memory configuration.
if d.HasChange(mkResourceVirtualEnvironmentVMMemory) { if d.HasChange(mkResourceVirtualEnvironmentVMMemory) {
memoryBlock, err := structure.GetSchemaBlock( memoryBlock, err := structure.GetSchemaBlock(
@ -5922,6 +6042,17 @@ func getPCIInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*v
return pciDevices return pciDevices
} }
func getUSBInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomUSBDevice {
usbDevices := map[string]*vms.CustomUSBDevice{}
usbDevices["usb0"] = resp.USBDevice0
usbDevices["usb1"] = resp.USBDevice1
usbDevices["usb2"] = resp.USBDevice2
usbDevices["usb3"] = resp.USBDevice3
return usbDevices
}
func parseImportIDWithNodeName(id string) (string, string, error) { func parseImportIDWithNodeName(id string) (string, string, error) {
nodeName, id, found := strings.Cut(id, "/") nodeName, id, found := strings.Cut(id, "/")

View File

@ -49,6 +49,7 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMEFIDisk, mkResourceVirtualEnvironmentVMEFIDisk,
mkResourceVirtualEnvironmentVMInitialization, mkResourceVirtualEnvironmentVMInitialization,
mkResourceVirtualEnvironmentVMHostPCI, mkResourceVirtualEnvironmentVMHostPCI,
mkResourceVirtualEnvironmentVMHostUSB,
mkResourceVirtualEnvironmentVMKeyboardLayout, mkResourceVirtualEnvironmentVMKeyboardLayout,
mkResourceVirtualEnvironmentVMKVMArguments, mkResourceVirtualEnvironmentVMKVMArguments,
mkResourceVirtualEnvironmentVMMachine, mkResourceVirtualEnvironmentVMMachine,
@ -84,6 +85,7 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMDisk: schema.TypeList, mkResourceVirtualEnvironmentVMDisk: schema.TypeList,
mkResourceVirtualEnvironmentVMEFIDisk: schema.TypeList, mkResourceVirtualEnvironmentVMEFIDisk: schema.TypeList,
mkResourceVirtualEnvironmentVMHostPCI: schema.TypeList, mkResourceVirtualEnvironmentVMHostPCI: schema.TypeList,
mkResourceVirtualEnvironmentVMHostUSB: schema.TypeList,
mkResourceVirtualEnvironmentVMInitialization: schema.TypeList, mkResourceVirtualEnvironmentVMInitialization: schema.TypeList,
mkResourceVirtualEnvironmentVMIPv4Addresses: schema.TypeList, mkResourceVirtualEnvironmentVMIPv4Addresses: schema.TypeList,
mkResourceVirtualEnvironmentVMIPv6Addresses: schema.TypeList, mkResourceVirtualEnvironmentVMIPv6Addresses: schema.TypeList,
@ -278,6 +280,17 @@ func TestVMSchema(t *testing.T) {
mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA: schema.TypeBool, mkResourceVirtualEnvironmentVMHostPCIDeviceXVGA: schema.TypeBool,
}) })
hostUSBSchema := test.AssertNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMHostUSB)
test.AssertOptionalArguments(t, hostUSBSchema, []string{
mkResourceVirtualEnvironmentVMHostUSBDeviceMapping,
})
test.AssertValueTypes(t, hostUSBSchema, map[string]schema.ValueType{
mkResourceVirtualEnvironmentVMHostUSBDevice: schema.TypeString,
mkResourceVirtualEnvironmentVMHostUSBDeviceUSB3: schema.TypeBool,
})
initializationDNSSchema := test.AssertNestedSchemaExistence( initializationDNSSchema := test.AssertNestedSchemaExistence(
t, t,
initializationSchema, initializationSchema,