diff --git a/docs/resources/virtual_environment_vm.md b/docs/resources/virtual_environment_vm.md index a2d2e53b..55f7a857 100644 --- a/docs/resources/virtual_environment_vm.md +++ b/docs/resources/virtual_environment_vm.md @@ -231,11 +231,15 @@ output "ubuntu_vm_public_key" { - `custom-` - Custom CPU model. All `custom-` values should be defined in `/etc/pve/virtual-guest/cpu-models.conf` file. - `units` - (Optional) The CPU units (defaults to `1024`). + - `affinity` - (Optional) The CPU cores that are used to run the VM’s vCPU. The + value is a list of CPU IDs, separated by commas. The CPU IDs are zero-based. + For example, `0,1,2,3` (which also can be shortened to `0-3`) means that the VM’s vCPUs are run on the first four + CPU cores. Setting `affinity` is only allowed for `root@pam` authenticated user. - `description` - (Optional) The description. - `disk` - (Optional) A disk (multiple blocks supported). - `aio` - (Optional) The disk AIO mode (defaults to `io_uring`). - `io_uring` - Use io_uring. - - `native` - Use native AIO. Should be used with to unbuffered, O_DIRECT, raw block storage only, + - `native` - Use native AIO. Should be used with to unbuffered, O_DIRECT, raw block storage only, with the disk `cache` must be set to `none`. Raw block storage types include iSCSI, CEPH/RBD, and NVMe. - `threads` - Use thread-based AIO. - `backup` - (Optional) Whether the drive should be included when making backups (defaults to `true`). @@ -422,7 +426,7 @@ output "ubuntu_vm_public_key" { - `queues` - (Optional) The number of queues for VirtIO (1..64). - `rate_limit` - (Optional) The rate limit in megabytes per second. - `vlan_id` - (Optional) The VLAN identifier. - - `trunks` - (Optional) String containing a `;` separated list of VLAN trunks + - `trunks` - (Optional) String containing a `;` separated list of VLAN trunks ("10;20;30"). Note that the VLAN-aware feature need to be enabled on the PVE Linux Bridge to use trunks. - `node_name` - (Required) The name of the node to assign the virtual machine diff --git a/example/resource_virtual_environment_vm.tf b/example/resource_virtual_environment_vm.tf index a36089ba..f4cb61ee 100644 --- a/example/resource_virtual_environment_vm.tf +++ b/example/resource_virtual_environment_vm.tf @@ -14,6 +14,7 @@ resource "proxmox_virtual_environment_vm" "example_template" { cores = 2 numa = true limit = 64 + # affinity = "0-1" } smbios { @@ -75,7 +76,7 @@ resource "proxmox_virtual_environment_vm" "example_template" { address = "dhcp" } # ipv6 { - # address = "dhcp" + # address = "dhcp" #} } diff --git a/proxmox/nodes/vms/vms_types.go b/proxmox/nodes/vms/vms_types.go index 10086d94..0a74f87a 100644 --- a/proxmox/nodes/vms/vms_types.go +++ b/proxmox/nodes/vms/vms_types.go @@ -236,6 +236,7 @@ type CreateRequestBody struct { CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"` CPUSockets *int `json:"sockets,omitempty" url:"sockets,omitempty"` CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"` + CPUAffinity *string `json:"affinity,omitempty" url:"affinity,omitempty"` DedicatedMemory *int `json:"memory,omitempty" url:"memory,omitempty"` Delete []string `json:"delete,omitempty" url:"delete,omitempty,comma"` DeletionProtection *types.CustomBool `json:"protection,omitempty" url:"protection,omitempty,int"` @@ -364,6 +365,7 @@ type GetResponseData struct { CPULimit *types.CustomInt `json:"cpulimit,omitempty"` CPUSockets *int `json:"sockets,omitempty"` CPUUnits *int `json:"cpuunits,omitempty"` + CPUAffinity *string `json:"affinity,omitempty"` DedicatedMemory *types.CustomInt64 `json:"memory,omitempty"` DeletionProtection *types.CustomBool `json:"protection,omitempty"` Description *string `json:"description,omitempty"` diff --git a/proxmoxtf/resource/vm/validators.go b/proxmoxtf/resource/vm/validators.go index 61a0b0d3..a4ea0ee6 100644 --- a/proxmoxtf/resource/vm/validators.go +++ b/proxmoxtf/resource/vm/validators.go @@ -144,6 +144,13 @@ func CPUTypeValidator() schema.SchemaValidateDiagFunc { )) } +// CPUAffinityValidator returns a schema validation function for a CPU affinity. +func CPUAffinityValidator() schema.SchemaValidateDiagFunc { + return validation.ToDiagFunc( + validation.StringMatch(regexp.MustCompile(`^\d+[\d-,]*$`), "must contain numbers but also number ranges"), + ) +} + // QEMUAgentTypeValidator is a schema validation function for QEMU agent types. func QEMUAgentTypeValidator() schema.SchemaValidateDiagFunc { return validation.ToDiagFunc(validation.StringInSlice([]string{"isa", "virtio"}, false)) diff --git a/proxmoxtf/resource/vm/vm.go b/proxmoxtf/resource/vm/vm.go index c999638b..47d5b8c2 100644 --- a/proxmoxtf/resource/vm/vm.go +++ b/proxmoxtf/resource/vm/vm.go @@ -64,6 +64,7 @@ const ( dvCPUSockets = 1 dvCPUType = "qemu64" dvCPUUnits = 1024 + dvCPUAffinity = "" dvDescription = "" dvEFIDiskDatastoreID = "local-lvm" @@ -165,6 +166,7 @@ const ( mkCPUSockets = "sockets" mkCPUType = "type" mkCPUUnits = "units" + mkCPUAffinity = "affinity" mkDescription = "description" mkEFIDisk = "efi_disk" @@ -491,6 +493,7 @@ func VM() *schema.Resource { mkCPUSockets: dvCPUSockets, mkCPUType: dvCPUType, mkCPUUnits: dvCPUUnits, + mkCPUAffinity: dvCPUAffinity, }, }, nil }, @@ -564,6 +567,13 @@ func VM() *schema.Resource { validation.IntBetween(2, 262144), ), }, + mkCPUAffinity: { + Type: schema.TypeString, + Description: "The CPU affinity", + Optional: true, + Default: dvCPUAffinity, + ValidateDiagFunc: CPUAffinityValidator(), + }, }, }, MaxItems: 1, @@ -1776,6 +1786,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d cpuSockets := cpuBlock[mkCPUSockets].(int) cpuType := cpuBlock[mkCPUType].(string) cpuUnits := cpuBlock[mkCPUUnits].(int) + cpuAffinity := cpuBlock[mkCPUAffinity].(string) cpuFlagsConverted := make([]string, len(cpuFlags)) @@ -1798,6 +1809,10 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d updateBody.CPUSockets = &cpuSockets updateBody.CPUUnits = &cpuUnits + if cpuAffinity != "" { + updateBody.CPUAffinity = &cpuAffinity + } + if cpuHotplugged > 0 { updateBody.VirtualCPUCount = &cpuHotplugged } @@ -2183,6 +2198,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool)) cpuType := cpuBlock[mkCPUType].(string) cpuUnits := cpuBlock[mkCPUUnits].(int) + cpuAffinity := cpuBlock[mkCPUAffinity].(string) description := d.Get(mkDescription).(string) @@ -2465,6 +2481,10 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) createBody.CPULimit = &cpuLimit } + if cpuAffinity != "" { + createBody.CPUAffinity = &cpuAffinity + } + if description != "" { createBody.Description = &description } @@ -3379,6 +3399,12 @@ func vmReadCustom( cpu[mkCPUUnits] = 1024 } + if vmConfig.CPUAffinity != nil { + cpu[mkCPUAffinity] = *vmConfig.CPUAffinity + } else { + cpu[mkCPUAffinity] = "" + } + currentCPU := d.Get(mkCPU).([]interface{}) if len(clone) > 0 { @@ -4643,6 +4669,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D cpuSockets := cpuBlock[mkCPUSockets].(int) cpuType := cpuBlock[mkCPUType].(string) cpuUnits := cpuBlock[mkCPUUnits].(int) + cpuAffinity := cpuBlock[mkCPUAffinity].(string) // Only the root account is allowed to change the CPU architecture, which makes this check necessary. if api.API().IsRootTicket() || @@ -4653,8 +4680,15 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D updateBody.CPUCores = &cpuCores updateBody.CPUSockets = &cpuSockets updateBody.CPUUnits = &cpuUnits + updateBody.CPUAffinity = &cpuAffinity updateBody.NUMAEnabled = &cpuNUMA + if cpuAffinity != "" { + updateBody.CPUAffinity = &cpuAffinity + } else { + del = append(del, "affinity") + } + if cpuHotplugged > 0 { updateBody.VirtualCPUCount = &cpuHotplugged } else {