mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 10:33:46 +00:00
fix(vm): handle more than 4 hostpci
devices (#1543)
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
13ceab8205
commit
569db6df3b
@ -312,7 +312,7 @@ output "ubuntu_vm_public_key" {
|
||||
(defaults to `v2.0`).
|
||||
- `hostpci` - (Optional) A host PCI device mapping (multiple blocks supported).
|
||||
- `device` - (Required) The PCI device name for Proxmox, in form
|
||||
of `hostpciX` where `X` is a sequential number from 0 to 3.
|
||||
of `hostpciX` where `X` is a sequential number from 0 to 15.
|
||||
- `id` - (Optional) The PCI device ID. This parameter is not compatible
|
||||
with `api_token` and requires the root `username` and `password`
|
||||
configured in the proxmox provider. Use either this or `mapping`.
|
||||
|
@ -22,7 +22,7 @@ type Value = types.Map
|
||||
// NewValue returns a new Value with the given CD-ROM settings from the PVE API.
|
||||
func NewValue(ctx context.Context, config *vms.GetResponseData, diags *diag.Diagnostics) Value {
|
||||
// find storage devices with media=cdrom
|
||||
cdroms := config.CustomStorageDevices.Filter(func(device *vms.CustomStorageDevice) bool {
|
||||
cdroms := config.StorageDevices.Filter(func(device *vms.CustomStorageDevice) bool {
|
||||
return device.Media != nil && *device.Media == "cdrom"
|
||||
})
|
||||
|
||||
|
@ -27,50 +27,50 @@ type CustomPCIDevice struct {
|
||||
}
|
||||
|
||||
// CustomPCIDevices handles QEMU host PCI device mapping parameters.
|
||||
type CustomPCIDevices []CustomPCIDevice
|
||||
type CustomPCIDevices map[string]*CustomPCIDevice
|
||||
|
||||
// EncodeValues converts a CustomPCIDevice struct to a URL value.
|
||||
func (r *CustomPCIDevice) EncodeValues(key string, v *url.Values) error {
|
||||
func (d *CustomPCIDevice) EncodeValues(key string, v *url.Values) error {
|
||||
var values []string
|
||||
|
||||
if r.DeviceIDs == nil && r.Mapping == nil {
|
||||
if d.DeviceIDs == nil && d.Mapping == nil {
|
||||
return fmt.Errorf("either device ID or resource mapping must be set")
|
||||
}
|
||||
|
||||
if r.DeviceIDs != nil {
|
||||
values = append(values, fmt.Sprintf("host=%s", strings.Join(*r.DeviceIDs, ";")))
|
||||
if d.DeviceIDs != nil {
|
||||
values = append(values, fmt.Sprintf("host=%s", strings.Join(*d.DeviceIDs, ";")))
|
||||
}
|
||||
|
||||
if r.Mapping != nil {
|
||||
values = append(values, fmt.Sprintf("mapping=%s", *r.Mapping))
|
||||
if d.Mapping != nil {
|
||||
values = append(values, fmt.Sprintf("mapping=%s", *d.Mapping))
|
||||
}
|
||||
|
||||
if r.MDev != nil {
|
||||
values = append(values, fmt.Sprintf("mdev=%s", *r.MDev))
|
||||
if d.MDev != nil {
|
||||
values = append(values, fmt.Sprintf("mdev=%s", *d.MDev))
|
||||
}
|
||||
|
||||
if r.PCIExpress != nil {
|
||||
if *r.PCIExpress {
|
||||
if d.PCIExpress != nil {
|
||||
if *d.PCIExpress {
|
||||
values = append(values, "pcie=1")
|
||||
} else {
|
||||
values = append(values, "pcie=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.ROMBAR != nil {
|
||||
if *r.ROMBAR {
|
||||
if d.ROMBAR != nil {
|
||||
if *d.ROMBAR {
|
||||
values = append(values, "rombar=1")
|
||||
} else {
|
||||
values = append(values, "rombar=0")
|
||||
}
|
||||
}
|
||||
|
||||
if r.ROMFile != nil {
|
||||
values = append(values, fmt.Sprintf("romfile=%s", *r.ROMFile))
|
||||
if d.ROMFile != nil {
|
||||
values = append(values, fmt.Sprintf("romfile=%s", *d.ROMFile))
|
||||
}
|
||||
|
||||
if r.XVGA != nil {
|
||||
if *r.XVGA {
|
||||
if d.XVGA != nil {
|
||||
if *d.XVGA {
|
||||
values = append(values, "x-vga=1")
|
||||
} else {
|
||||
values = append(values, "x-vga=0")
|
||||
@ -83,10 +83,10 @@ func (r *CustomPCIDevice) EncodeValues(key string, v *url.Values) error {
|
||||
}
|
||||
|
||||
// EncodeValues converts a CustomPCIDevices array to multiple URL values.
|
||||
func (r CustomPCIDevices) EncodeValues(key string, v *url.Values) error {
|
||||
for i, d := range r {
|
||||
if err := d.EncodeValues(fmt.Sprintf("%s%d", key, i), v); err != nil {
|
||||
return fmt.Errorf("failed to encode PCI device %d: %w", i, err)
|
||||
func (r CustomPCIDevices) EncodeValues(_ string, v *url.Values) error {
|
||||
for s, d := range r {
|
||||
if err := d.EncodeValues(s, v); err != nil {
|
||||
return fmt.Errorf("failed to encode PCI device %s: %w", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ func (r CustomPCIDevices) EncodeValues(key string, v *url.Values) error {
|
||||
}
|
||||
|
||||
// UnmarshalJSON converts a CustomPCIDevice string to an object.
|
||||
func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
|
||||
func (d *CustomPCIDevice) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
@ -107,27 +107,27 @@ func (r *CustomPCIDevice) UnmarshalJSON(b []byte) error {
|
||||
v := strings.Split(strings.TrimSpace(p), "=")
|
||||
if len(v) == 1 {
|
||||
dIDs := strings.Split(v[0], ";")
|
||||
r.DeviceIDs = &dIDs
|
||||
d.DeviceIDs = &dIDs
|
||||
} else if len(v) == 2 {
|
||||
switch v[0] {
|
||||
case "host":
|
||||
dIDs := strings.Split(v[1], ";")
|
||||
r.DeviceIDs = &dIDs
|
||||
d.DeviceIDs = &dIDs
|
||||
case "mapping":
|
||||
r.Mapping = &v[1]
|
||||
d.Mapping = &v[1]
|
||||
case "mdev":
|
||||
r.MDev = &v[1]
|
||||
d.MDev = &v[1]
|
||||
case "pcie":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.PCIExpress = &bv
|
||||
d.PCIExpress = &bv
|
||||
case "rombar":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.ROMBAR = &bv
|
||||
d.ROMBAR = &bv
|
||||
case "romfile":
|
||||
r.ROMFile = &v[1]
|
||||
d.ROMFile = &v[1]
|
||||
case "x-vga":
|
||||
bv := types.CustomBool(v[1] == "1")
|
||||
r.XVGA = &bv
|
||||
d.XVGA = &bv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,10 +290,6 @@ type GetResponseData struct {
|
||||
NUMADevices7 *CustomNUMADevice `json:"numa7,omitempty"`
|
||||
OSType *string `json:"ostype,omitempty"`
|
||||
Overwrite *types.CustomBool `json:"force,omitempty"`
|
||||
PCIDevice0 *CustomPCIDevice `json:"hostpci0,omitempty"`
|
||||
PCIDevice1 *CustomPCIDevice `json:"hostpci1,omitempty"`
|
||||
PCIDevice2 *CustomPCIDevice `json:"hostpci2,omitempty"`
|
||||
PCIDevice3 *CustomPCIDevice `json:"hostpci3,omitempty"`
|
||||
PoolID *string `json:"pool,omitempty"`
|
||||
Revert *string `json:"revert,omitempty"`
|
||||
SCSIHardware *string `json:"scsihw,omitempty"`
|
||||
@ -322,7 +318,8 @@ type GetResponseData struct {
|
||||
VMGenerationID *string `json:"vmgenid,omitempty"`
|
||||
VMStateDatastoreID *string `json:"vmstatestorage,omitempty"`
|
||||
WatchdogDevice *CustomWatchdogDevice `json:"watchdog,omitempty"`
|
||||
CustomStorageDevices CustomStorageDevices `json:"-"`
|
||||
StorageDevices CustomStorageDevices `json:"-"`
|
||||
PCIDevices CustomPCIDevices `json:"-"`
|
||||
}
|
||||
|
||||
// GetStatusResponseBody contains the body from a VM get status response.
|
||||
@ -451,7 +448,7 @@ type UpdateAsyncResponseBody struct {
|
||||
// UpdateRequestBody contains the data for an virtual machine update request.
|
||||
type UpdateRequestBody = CreateRequestBody
|
||||
|
||||
// UnmarshalJSON unmarshals the custom storage devices from the JSON data.
|
||||
// UnmarshalJSON unmarshals the data from the JSON response, populating the CustomStorageDevices field.
|
||||
func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
||||
type Alias GetResponseData
|
||||
|
||||
@ -470,10 +467,13 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
||||
return fmt.Errorf("failed to unmarshal data: %w", err)
|
||||
}
|
||||
|
||||
allDevices := make(CustomStorageDevices)
|
||||
data.StorageDevices = make(CustomStorageDevices)
|
||||
data.PCIDevices = make(CustomPCIDevices)
|
||||
|
||||
for key, value := range byAttr {
|
||||
for _, prefix := range StorageInterfaces {
|
||||
// the device names can overlap with other fields, for example`scsi0` and `scsihw`, so just checking
|
||||
// the prefix is not enough
|
||||
r := regexp.MustCompile(`^` + prefix + `\d+$`)
|
||||
if r.MatchString(key) {
|
||||
var device CustomStorageDevice
|
||||
@ -481,12 +481,19 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
|
||||
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
||||
}
|
||||
|
||||
allDevices[key] = &device
|
||||
data.StorageDevices[key] = &device
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.CustomStorageDevices = allDevices
|
||||
if r := regexp.MustCompile(`^hostpci\d+$`); r.MatchString(key) {
|
||||
var device CustomPCIDevice
|
||||
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
|
||||
}
|
||||
|
||||
data.PCIDevices[key] = &device
|
||||
}
|
||||
}
|
||||
|
||||
*d = GetResponseData(data)
|
||||
|
||||
|
@ -25,7 +25,10 @@ func TestUnmarshalGetResponseData(t *testing.T) {
|
||||
"ide2": "%[1]s",
|
||||
"ide3": "%[1]s",
|
||||
"virtio13": "%[1]s",
|
||||
"scsi22": "%[1]s"
|
||||
"scsi22": "%[1]s",
|
||||
"hostpci0": "0000:81:00.2",
|
||||
"hostpci1": "host=81:00.4,pcie=0,rombar=1,x-vga=0",
|
||||
"hostpci12": "mapping=mappeddevice,pcie=0,rombar=1,x-vga=0"
|
||||
}`, "local-lvm:vm-100-disk-0,aio=io_uring,backup=1,cache=none,discard=ignore,replicate=1,size=8G,ssd=1")
|
||||
|
||||
var data GetResponseData
|
||||
@ -34,20 +37,26 @@ func TestUnmarshalGetResponseData(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "test", *data.BackupFile)
|
||||
|
||||
assert.NotNil(t, data.CustomStorageDevices)
|
||||
assert.Len(t, data.CustomStorageDevices, 6)
|
||||
assert.NotNil(t, data.CustomStorageDevices["ide0"])
|
||||
assertDevice(t, data.CustomStorageDevices["ide0"])
|
||||
assert.NotNil(t, data.CustomStorageDevices["ide1"])
|
||||
assertDevice(t, data.CustomStorageDevices["ide1"])
|
||||
assert.NotNil(t, data.CustomStorageDevices["ide2"])
|
||||
assertDevice(t, data.CustomStorageDevices["ide2"])
|
||||
assert.NotNil(t, data.CustomStorageDevices["ide3"])
|
||||
assertDevice(t, data.CustomStorageDevices["ide3"])
|
||||
assert.NotNil(t, data.CustomStorageDevices["virtio13"])
|
||||
assertDevice(t, data.CustomStorageDevices["virtio13"])
|
||||
assert.NotNil(t, data.CustomStorageDevices["scsi22"])
|
||||
assertDevice(t, data.CustomStorageDevices["scsi22"])
|
||||
assert.NotNil(t, data.StorageDevices)
|
||||
assert.Len(t, data.StorageDevices, 6)
|
||||
assert.NotNil(t, data.StorageDevices["ide0"])
|
||||
assertDevice(t, data.StorageDevices["ide0"])
|
||||
assert.NotNil(t, data.StorageDevices["ide1"])
|
||||
assertDevice(t, data.StorageDevices["ide1"])
|
||||
assert.NotNil(t, data.StorageDevices["ide2"])
|
||||
assertDevice(t, data.StorageDevices["ide2"])
|
||||
assert.NotNil(t, data.StorageDevices["ide3"])
|
||||
assertDevice(t, data.StorageDevices["ide3"])
|
||||
assert.NotNil(t, data.StorageDevices["virtio13"])
|
||||
assertDevice(t, data.StorageDevices["virtio13"])
|
||||
assert.NotNil(t, data.StorageDevices["scsi22"])
|
||||
assertDevice(t, data.StorageDevices["scsi22"])
|
||||
|
||||
assert.NotNil(t, data.PCIDevices)
|
||||
assert.Len(t, data.PCIDevices, 3)
|
||||
assert.NotNil(t, data.PCIDevices["hostpci0"])
|
||||
assert.NotNil(t, data.PCIDevices["hostpci1"])
|
||||
assert.NotNil(t, data.PCIDevices["hostpci12"])
|
||||
}
|
||||
|
||||
func assertDevice(t *testing.T, dev *CustomStorageDevice) {
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
// GetInfo returns the disk information for a VM.
|
||||
// Deprecated: use vms.MapCustomStorageDevices from proxmox/nodes/vms instead.
|
||||
func GetInfo(resp *vms.GetResponseData, d *schema.ResourceData) vms.CustomStorageDevices {
|
||||
storageDevices := resp.CustomStorageDevices
|
||||
storageDevices := resp.StorageDevices
|
||||
|
||||
currentDisk := d.Get(MkDisk)
|
||||
|
||||
|
@ -130,9 +130,10 @@ const (
|
||||
dvStopOnDestroy = false
|
||||
dvHookScript = ""
|
||||
|
||||
maxResourceVirtualEnvironmentVMAudioDevices = 1
|
||||
maxResourceVirtualEnvironmentVMSerialDevices = 4
|
||||
maxResourceVirtualEnvironmentVMHostPCIDevices = 8
|
||||
maxResourceVirtualEnvironmentVMAudioDevices = 1
|
||||
maxResourceVirtualEnvironmentVMSerialDevices = 4
|
||||
// see /usr/share/perl5/PVE/QemuServer/PCI.pm.
|
||||
maxResourceVirtualEnvironmentVMHostPCIDevices = 16
|
||||
maxResourceVirtualEnvironmentVMHostUSBDevices = 4
|
||||
// hardcoded /usr/share/perl5/PVE/QemuServer/Memory.pm: "our $MAX_NUMA = 8".
|
||||
maxResourceVirtualEnvironmentVMNUMADevices = 8
|
||||
@ -966,6 +967,7 @@ func VM() *schema.Resource {
|
||||
},
|
||||
},
|
||||
},
|
||||
MaxItems: maxResourceVirtualEnvironmentVMHostPCIDevices,
|
||||
},
|
||||
mkHostUSB: {
|
||||
Type: schema.TypeList,
|
||||
@ -1516,7 +1518,7 @@ func vmCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
||||
|
||||
// Check for an existing CloudInit IDE drive. If no such drive is found, return the specified `defaultValue`.
|
||||
func findExistingCloudInitDrive(vmConfig *vms.GetResponseData, vmID int, defaultValue string) string {
|
||||
devs := vmConfig.CustomStorageDevices.Filter(func(device *vms.CustomStorageDevice) bool {
|
||||
devs := vmConfig.StorageDevices.Filter(func(device *vms.CustomStorageDevice) bool {
|
||||
return device.IsCloudInitDrive(vmID)
|
||||
})
|
||||
|
||||
@ -1530,7 +1532,7 @@ func findExistingCloudInitDrive(vmConfig *vms.GetResponseData, vmID int, default
|
||||
// Return a pointer to the storage device configuration based on a name. The device name is assumed to be a
|
||||
// valid ide, sata, or scsi interface name.
|
||||
func getStorageDevice(vmConfig *vms.GetResponseData, deviceName string) *vms.CustomStorageDevice {
|
||||
if dev, ok := vmConfig.CustomStorageDevices[deviceName]; ok {
|
||||
if dev, ok := vmConfig.StorageDevices[deviceName]; ok {
|
||||
return dev
|
||||
}
|
||||
|
||||
@ -2949,9 +2951,10 @@ func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
|
||||
pciDevice := d.Get(mkHostPCI).([]interface{})
|
||||
pciDeviceObjects := make(vms.CustomPCIDevices, len(pciDevice))
|
||||
|
||||
for i, pciDeviceEntry := range pciDevice {
|
||||
for _, pciDeviceEntry := range pciDevice {
|
||||
block := pciDeviceEntry.(map[string]interface{})
|
||||
|
||||
deviceName := block[mkHostPCIDevice].(string)
|
||||
ids, _ := block[mkHostPCIDeviceID].(string)
|
||||
mdev, _ := block[mkHostPCIDeviceMDev].(string)
|
||||
pcie := types.CustomBool(block[mkHostPCIDevicePCIE].(bool))
|
||||
@ -2985,7 +2988,7 @@ func vmGetHostPCIDeviceObjects(d *schema.ResourceData) vms.CustomPCIDevices {
|
||||
device.Mapping = &mapping
|
||||
}
|
||||
|
||||
pciDeviceObjects[i] = device
|
||||
pciDeviceObjects[deviceName] = &device
|
||||
}
|
||||
|
||||
return pciDeviceObjects
|
||||
@ -3673,8 +3676,7 @@ func vmReadCustom(
|
||||
currentPCIList := d.Get(mkHostPCI).([]interface{})
|
||||
pciMap := map[string]interface{}{}
|
||||
|
||||
pciDevices := getPCIInfo(vmConfig, d)
|
||||
for pi, pp := range pciDevices {
|
||||
for pi, pp := range vmConfig.PCIDevices {
|
||||
if (pp == nil) || (pp.DeviceIDs == nil && pp.Mapping == nil) {
|
||||
continue
|
||||
}
|
||||
@ -5524,17 +5526,6 @@ func getNUMAInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*
|
||||
return numaDevices
|
||||
}
|
||||
|
||||
func getPCIInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomPCIDevice {
|
||||
pciDevices := map[string]*vms.CustomPCIDevice{}
|
||||
|
||||
pciDevices["hostpci0"] = resp.PCIDevice0
|
||||
pciDevices["hostpci1"] = resp.PCIDevice1
|
||||
pciDevices["hostpci2"] = resp.PCIDevice2
|
||||
pciDevices["hostpci3"] = resp.PCIDevice3
|
||||
|
||||
return pciDevices
|
||||
}
|
||||
|
||||
func getUSBInfo(resp *vms.GetResponseData, _ *schema.ResourceData) map[string]*vms.CustomUSBDevice {
|
||||
usbDevices := map[string]*vms.CustomUSBDevice{}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user