0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21:10 +00:00

fix(vm): do not overwrite cpu attributes with defaults when cloning

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-04-15 22:07:33 -04:00
parent 163a773088
commit 9c72e584de
No known key found for this signature in database
GPG Key ID: 02A24794ADAC7455
12 changed files with 860 additions and 561 deletions

View File

@ -0,0 +1,122 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tests
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceVMCloneCPU(t *testing.T) {
te := initTestEnvironment(t)
tests := []struct {
name string
step []resource.TestStep
}{
{"copy cpu from template in full", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm" "test_cpu_clone_template" {
node_name = "{{.NodeName}}"
started = false
name = "test-cpu-clone-template"
template = "true"
cpu {
cores = 2
type = "host"
}
}
resource "proxmox_virtual_environment_vm" "test_cpu_clone" {
node_name = "{{.NodeName}}"
started = false
name = "test-cpu-clone"
clone {
vm_id = proxmox_virtual_environment_vm.test_cpu_clone_template.id
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_cpu_clone", map[string]string{
"cpu.0.cores": "2",
"cpu.0.type": "host",
}),
),
}}},
{"merge cpu attributes", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm" "test_cpu_clone_template" {
node_name = "{{.NodeName}}"
started = false
name = "test-cpu-clone-template"
template = "true"
cpu {
cores = 2
}
}
resource "proxmox_virtual_environment_vm" "test_cpu_clone" {
node_name = "{{.NodeName}}"
started = false
name = "test-cpu-clone"
cpu {
type = "host"
}
clone {
vm_id = proxmox_virtual_environment_vm.test_cpu_clone_template.id
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_cpu_clone", map[string]string{
"cpu.0.cores": "2",
"cpu.0.type": "host",
}),
),
}}},
{"overwrite cpu attributes in full", []resource.TestStep{{
Config: te.renderConfig(`
resource "proxmox_virtual_environment_vm" "test_cpu_clone_template" {
node_name = "{{.NodeName}}"
started = false
name = "test-cpu-clone-template"
template = "true"
}
resource "proxmox_virtual_environment_vm" "test_cpu_clone" {
node_name = "{{.NodeName}}"
started = false
name = "test-cpu-clone"
cpu {
cores = 2
type = "host"
}
clone {
vm_id = proxmox_virtual_environment_vm.test_cpu_clone_template.id
}
}`),
Check: resource.ComposeTestCheckFunc(
testResourceAttributes("proxmox_virtual_environment_vm.test_cpu_clone", map[string]string{
"cpu.0.cores": "2",
"cpu.0.type": "host",
}),
),
}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: te.accProviders,
Steps: tt.step,
})
})
}
}

View File

@ -77,7 +77,7 @@ type CustomCPUEmulation struct {
Flags *[]string `json:"flags,omitempty" url:"flags,omitempty,semicolon"`
Hidden *types.CustomBool `json:"hidden,omitempty" url:"hidden,omitempty,int"`
HVVendorID *string `json:"hv-vendor-id,omitempty" url:"hv-vendor-id,omitempty"`
Type string `json:"cputype,omitempty" url:"cputype,omitempty"`
Type *string `json:"cputype,omitempty" url:"cputype,omitempty"`
}
// CustomEFIDisk handles QEMU EFI disk parameters.
@ -820,7 +820,7 @@ func (r CustomCloudInitConfig) EncodeValues(_ string, v *url.Values) error {
// EncodeValues converts a CustomCPUEmulation struct to a URL vlaue.
func (r CustomCPUEmulation) EncodeValues(key string, v *url.Values) error {
values := []string{
fmt.Sprintf("cputype=%s", r.Type),
fmt.Sprintf("cputype=%s", *r.Type),
}
if r.Flags != nil && len(*r.Flags) > 0 {
@ -1464,11 +1464,11 @@ func (r *CustomCPUEmulation) UnmarshalJSON(b []byte) error {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Type = v[0]
r.Type = &v[0]
} else if len(v) == 2 {
switch v[0] {
case "cputype":
r.Type = v[1]
r.Type = &v[1]
case "flags":
if v[1] != "" {
f := strings.Split(v[1], ";")

View File

@ -0,0 +1,107 @@
package validators
import (
"regexp"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
// CPUArchitectureValidator returns a schema validation function for a CPU architecture.
func CPUArchitectureValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"aarch64",
"x86_64",
}, false))
}
// CPUTypeValidator returns a schema validation function for a CPU type.
func CPUTypeValidator() schema.SchemaValidateDiagFunc {
standardTypes := []string{
"486",
"Broadwell",
"Broadwell-IBRS",
"Broadwell-noTSX",
"Broadwell-noTSX-IBRS",
"Cascadelake-Server",
"Cascadelake-Server-noTSX",
"Cascadelake-Server-v2",
"Cascadelake-Server-v4",
"Cascadelake-Server-v5",
"Conroe",
"Cooperlake",
"Cooperlake-v2",
"EPYC",
"EPYC-IBPB",
"EPYC-Milan",
"EPYC-Rome",
"EPYC-Rome-v2",
"EPYC-v3",
"Haswell",
"Haswell-IBRS",
"Haswell-noTSX",
"Haswell-noTSX-IBRS",
"Icelake-Client",
"Icelake-Client-noTSX",
"Icelake-Server",
"Icelake-Server-noTSX",
"Icelake-Server-v3",
"Icelake-Server-v4",
"Icelake-Server-v5",
"Icelake-Server-v6",
"IvyBridge",
"IvyBridge-IBRS",
"KnightsMill",
"Nehalem",
"Nehalem-IBRS",
"Opteron_G1",
"Opteron_G2",
"Opteron_G3",
"Opteron_G4",
"Opteron_G5",
"Penryn",
"SandyBridge",
"SandyBridge-IBRS",
"SapphireRapids",
"Skylake-Client",
"Skylake-Client-IBRS",
"Skylake-Client-noTSX-IBRS",
"Skylake-Client-v4",
"Skylake-Server",
"Skylake-Server-IBRS",
"Skylake-Server-noTSX-IBRS",
"Skylake-Server-v4",
"Skylake-Server-v5",
"Westmere",
"Westmere-IBRS",
"athlon",
"core2duo",
"coreduo",
"host",
"kvm32",
"kvm64",
"max",
"pentium",
"pentium2",
"pentium3",
"phenom",
"qemu32",
"qemu64",
"x86-64-v2",
"x86-64-v2-AES",
"x86-64-v3",
"x86-64-v4",
}
return validation.ToDiagFunc(validation.Any(
validation.StringInSlice(standardTypes, false),
validation.StringMatch(regexp.MustCompile(`^custom-.+$`), "must be a valid custom CPU type"),
))
}
// 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 or number ranges separated by ','"),
)
}

View File

@ -0,0 +1,38 @@
package validators
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCPUType(t *testing.T) {
t.Parallel()
tests := []struct {
name string
value string
valid bool
}{
{"empty", "", false},
{"invalid", "invalid", false},
{"valid", "host", true},
{"valid", "qemu64", true},
{"valid", "custom-abc", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
f := CPUTypeValidator()
res := f(tt.value, nil)
if tt.valid {
require.Empty(t, res, "validate: '%s'", tt.value)
} else {
require.NotEmpty(t, res, "validate: '%s'", tt.value)
}
})
}
}

View File

@ -0,0 +1,386 @@
package cpu
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/structure"
)
func UpdateWhenClone(d *schema.ResourceData, api proxmox.Client, updateBody *vms.UpdateRequestBody) {
cpu := d.Get(MkCPU).([]interface{})
if len(cpu) > 0 && cpu[0] != nil {
cpuBlock := cpu[0].(map[string]interface{})
cpuArchitecture := cpuBlock[mkCPUArchitecture].(string)
cpuCores := cpuBlock[mkCPUCores].(int)
cpuFlags := cpuBlock[mkCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkCPUHotplugged].(int)
cpuLimit := cpuBlock[mkCPULimit].(int)
cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool))
cpuSockets := cpuBlock[mkCPUSockets].(int)
cpuType := cpuBlock[mkCPUType].(string)
cpuUnits := cpuBlock[mkCPUUnits].(int)
cpuAffinity := cpuBlock[mkCPUAffinity].(string)
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvCPUArchitecture {
updateBody.CPUArchitecture = &cpuArchitecture
}
if cpuCores != dvCPUCores && cpuCores > 0 {
// update only if we have non-default & non-empty value
updateBody.CPUCores = &cpuCores
}
updateBody.NUMAEnabled = &cpuNUMA
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
if cpuType != dvCPUType && cpuType != "" {
// update only if we have non-default & non-empty value
if updateBody.CPUEmulation == nil {
updateBody.CPUEmulation = &vms.CustomCPUEmulation{}
}
updateBody.CPUEmulation.Type = &cpuType
}
if len(cpuFlagsConverted) > 0 {
if updateBody.CPUEmulation == nil {
updateBody.CPUEmulation = &vms.CustomCPUEmulation{}
}
updateBody.CPUEmulation.Flags = &cpuFlagsConverted
}
if cpuAffinity != "" {
updateBody.CPUAffinity = &cpuAffinity
}
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
}
if cpuLimit > 0 {
updateBody.CPULimit = &cpuLimit
}
}
}
func Create(resource *schema.Resource, d *schema.ResourceData, api proxmox.Client, createBody *vms.CreateRequestBody) error {
cpuBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{MkCPU},
0,
true,
)
if err != nil {
return fmt.Errorf("error reading CPU block: %w", err)
}
cpuArchitecture := cpuBlock[mkCPUArchitecture].(string)
cpuCores := cpuBlock[mkCPUCores].(int)
cpuFlags := cpuBlock[mkCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkCPUHotplugged].(int)
cpuLimit := cpuBlock[mkCPULimit].(int)
cpuSockets := cpuBlock[mkCPUSockets].(int)
cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool))
cpuType := cpuBlock[mkCPUType].(string)
cpuUnits := cpuBlock[mkCPUUnits].(int)
cpuAffinity := cpuBlock[mkCPUAffinity].(string)
if cpuCores != dvCPUCores && cpuCores > 0 {
// set only if we have non-default & non-empty value
createBody.CPUCores = &cpuCores
}
if cpuSockets != dvCPUSockets && cpuSockets > 0 {
// set only if we have non-default & non-empty value
createBody.CPUSockets = &cpuSockets
}
if cpuUnits != dvCPUUnits && cpuUnits > 0 {
// set only if we have non-default & non-empty value
createBody.CPUUnits = &cpuUnits
}
if cpuNUMA != dvCPUNUMA && cpuNUMA {
// set only if we have non-default & non-empty value
createBody.NUMAEnabled = &cpuNUMA
}
if cpuLimit > 0 {
createBody.CPULimit = &cpuLimit
}
if cpuAffinity != "" {
createBody.CPUAffinity = &cpuAffinity
}
if cpuHotplugged > 0 {
createBody.VirtualCPUCount = &cpuHotplugged
}
if cpuType != dvCPUType && cpuType != "" {
// set only if we have non-default & non-empty value
if createBody.CPUEmulation == nil {
createBody.CPUEmulation = &vms.CustomCPUEmulation{}
}
createBody.CPUEmulation.Type = &cpuType
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
if len(cpuFlagsConverted) > 0 {
// set only if we have non-default & non-empty value
if createBody.CPUEmulation == nil {
createBody.CPUEmulation = &vms.CustomCPUEmulation{}
}
createBody.CPUEmulation.Flags = &cpuFlagsConverted
}
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvCPUArchitecture {
createBody.CPUArchitecture = &cpuArchitecture
}
return nil
}
func Read(vmConfig *vms.GetResponseData, api proxmox.Client, d *schema.ResourceData, clone bool) error {
// Compare the CPU configuration to the one stored in the state.
cpu := map[string]interface{}{}
if vmConfig.CPUArchitecture != nil {
cpu[mkCPUArchitecture] = *vmConfig.CPUArchitecture
} else {
// Default value of "arch" is "" according to the API documentation.
// However, assume the provider's default value as a workaround when the root account is not being used.
if !api.API().IsRootTicket() {
cpu[mkCPUArchitecture] = dvCPUArchitecture
} else {
cpu[mkCPUArchitecture] = ""
}
}
if vmConfig.CPUCores != nil {
cpu[mkCPUCores] = *vmConfig.CPUCores
//} else {
// // Default value of "cores" is "1" according to the API documentation.
// cpu[mkCPUCores] = 1
}
if vmConfig.VirtualCPUCount != nil {
cpu[mkCPUHotplugged] = *vmConfig.VirtualCPUCount
} else {
// Default value of "vcpus" is "1" according to the API documentation.
cpu[mkCPUHotplugged] = 0
}
if vmConfig.CPULimit != nil {
cpu[mkCPULimit] = *vmConfig.CPULimit
} else {
// Default value of "cpulimit" is "0" according to the API documentation.
cpu[mkCPULimit] = 0
}
if vmConfig.NUMAEnabled != nil {
cpu[mkCPUNUMA] = *vmConfig.NUMAEnabled
} else {
// Default value of "numa" is "false" according to the API documentation.
cpu[mkCPUNUMA] = false
}
if vmConfig.CPUSockets != nil {
cpu[mkCPUSockets] = *vmConfig.CPUSockets
} else {
// Default value of "sockets" is "1" according to the API documentation.
cpu[mkCPUSockets] = 1
}
if vmConfig.CPUEmulation != nil {
if vmConfig.CPUEmulation.Flags != nil {
convertedFlags := make([]interface{}, len(*vmConfig.CPUEmulation.Flags))
for fi, fv := range *vmConfig.CPUEmulation.Flags {
convertedFlags[fi] = fv
}
cpu[mkCPUFlags] = convertedFlags
//} else {
// cpu[mkCPUFlags] = []interface{}{}
}
cpu[mkCPUType] = vmConfig.CPUEmulation.Type
//} else {
// cpu[mkCPUFlags] = []interface{}{}
// // Default value of "cputype" is "qemu64" according to the QEMU documentation.
// cpu[mkCPUType] = dvCPUType
}
if vmConfig.CPUUnits != nil {
cpu[mkCPUUnits] = *vmConfig.CPUUnits
} else {
// Default value of "cpuunits" is "1024" according to the API documentation.
cpu[mkCPUUnits] = 1024
}
if vmConfig.CPUAffinity != nil {
cpu[mkCPUAffinity] = *vmConfig.CPUAffinity
} else {
cpu[mkCPUAffinity] = ""
}
currentCPU := d.Get(MkCPU).([]interface{})
//if len(clone) > 0 {
// if len(currentCPU) > 0 {
// err := d.Set(mkCPU, []interface{}{cpu})
// diags = append(diags, diag.FromErr(err)...)
// }
//} else if len(currentCPU) > 0 ||
// cpu[mkCPUArchitecture] != dvCPUArchitecture ||
// cpu[mkCPUCores] != dvCPUCores ||
// len(cpu[mkCPUFlags].([]interface{})) > 0 ||
// cpu[mkCPUHotplugged] != dvCPUHotplugged ||
// cpu[mkCPULimit] != dvCPULimit ||
// cpu[mkCPUSockets] != dvCPUSockets ||
// cpu[mkCPUType] != dvCPUType ||
// cpu[mkCPUUnits] != dvCPUUnits {
// err := d.Set(mkCPU, []interface{}{cpu})
// diags = append(diags, diag.FromErr(err)...)
//}
if clone || len(currentCPU) > 0 ||
cpu[mkCPUArchitecture] != dvCPUArchitecture ||
//cpu[mkCPUCores] != dvCPUCores ||
//len(cpu[mkCPUFlags].([]interface{})) > 0 ||
cpu[mkCPUHotplugged] != dvCPUHotplugged ||
cpu[mkCPULimit] != dvCPULimit ||
cpu[mkCPUSockets] != dvCPUSockets ||
//cpu[mkCPUType] != dvCPUType ||
cpu[mkCPUUnits] != dvCPUUnits {
err := d.Set(MkCPU, []interface{}{cpu})
if err != nil {
return fmt.Errorf("error setting CPU: %w", err)
}
}
return nil
}
func Update(d *schema.ResourceData, resource *schema.Resource, api proxmox.Client, updateBody *vms.UpdateRequestBody) ([]string, bool, error) {
// Prepare the new CPU configuration.
if !d.HasChange(MkCPU) {
return nil, false, nil
}
del := []string{}
cpuBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{MkCPU},
0,
true,
)
if err != nil {
return nil, false, err
}
cpuArchitecture := cpuBlock[mkCPUArchitecture].(string)
cpuCores := cpuBlock[mkCPUCores].(int)
cpuFlags := cpuBlock[mkCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkCPUHotplugged].(int)
cpuLimit := cpuBlock[mkCPULimit].(int)
cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool))
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() ||
cpuArchitecture != dvCPUArchitecture {
updateBody.CPUArchitecture = &cpuArchitecture
}
if cpuCores != dvCPUCores && cpuCores > 0 {
// set only if we have non-default & non-empty value
updateBody.CPUCores = &cpuCores
}
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
updateBody.NUMAEnabled = &cpuNUMA
// CPU affinity is a special case, only root can change it.
// we can't even have it in the delete list, as PVE will return an error for non-root.
// Hence, checking explicitly if it has changed.
if d.HasChange(MkCPU + ".0." + mkCPUAffinity) {
if cpuAffinity != "" {
updateBody.CPUAffinity = &cpuAffinity
} else {
del = append(del, "affinity")
}
}
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
} else {
del = append(del, "vcpus")
}
if cpuLimit > 0 {
updateBody.CPULimit = &cpuLimit
} else {
del = append(del, "cpulimit")
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
if cpuType != dvCPUType && cpuType != "" {
// set only if we have non-default & non-empty value
if updateBody.CPUEmulation == nil {
updateBody.CPUEmulation = &vms.CustomCPUEmulation{}
}
updateBody.CPUEmulation.Type = &cpuType
}
if len(cpuFlagsConverted) > 0 {
// set only if we have non-default & non-empty value
if updateBody.CPUEmulation == nil {
updateBody.CPUEmulation = &vms.CustomCPUEmulation{}
}
updateBody.CPUEmulation.Flags = &cpuFlagsConverted
}
// TODO: this may not be true
rebootRequired := true
return del, rebootRequired, nil
}

View File

@ -0,0 +1,141 @@
package cpu
import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/validators"
)
const (
dvCPUArchitecture = "x86_64"
dvCPUCores = 1
dvCPUHotplugged = 0
dvCPULimit = 0
dvCPUNUMA = false
dvCPUSockets = 1
dvCPUType = "qemu64"
dvCPUUnits = 1024
dvCPUAffinity = ""
MkCPU = "cpu"
mkCPUArchitecture = "architecture"
mkCPUCores = "cores"
mkCPUFlags = "flags"
mkCPUHotplugged = "hotplugged"
mkCPULimit = "limit"
mkCPUNUMA = "numa"
mkCPUSockets = "sockets"
mkCPUType = "type"
mkCPUUnits = "units"
mkCPUAffinity = "affinity"
)
func Schema() map[string]*schema.Schema {
return map[string]*schema.Schema{
MkCPU: {
Type: schema.TypeList,
Description: "The CPU allocation",
Optional: true,
Computed: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkCPUArchitecture: dvCPUArchitecture,
mkCPUCores: dvCPUCores,
mkCPUFlags: []interface{}{},
mkCPUHotplugged: dvCPUHotplugged,
mkCPULimit: dvCPULimit,
mkCPUNUMA: dvCPUNUMA,
mkCPUSockets: dvCPUSockets,
mkCPUType: dvCPUType,
mkCPUUnits: dvCPUUnits,
mkCPUAffinity: dvCPUAffinity,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkCPUArchitecture: {
Type: schema.TypeString,
Description: "The CPU architecture",
Optional: true,
Default: dvCPUArchitecture,
ValidateDiagFunc: validators.CPUArchitectureValidator(),
},
mkCPUCores: {
Type: schema.TypeInt,
Description: "The number of CPU cores",
Optional: true,
Computed: true,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 2304)),
},
mkCPUFlags: {
Type: schema.TypeList,
Description: "The CPU flags",
Optional: true,
Computed: true,
//DefaultFunc: func() (interface{}, error) {
// return []interface{}{}, nil
//},
Elem: &schema.Schema{Type: schema.TypeString},
},
mkCPUHotplugged: {
Type: schema.TypeInt,
Description: "The number of hotplugged vCPUs",
Optional: true,
Default: dvCPUHotplugged,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 2304)),
},
mkCPULimit: {
Type: schema.TypeInt,
Description: "Limit of CPU usage",
Optional: true,
Default: dvCPULimit,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(0, 128),
),
},
mkCPUNUMA: {
Type: schema.TypeBool,
Description: "Enable/disable NUMA.",
Optional: true,
Default: dvCPUNUMA,
},
mkCPUSockets: {
Type: schema.TypeInt,
Description: "The number of CPU sockets",
Optional: true,
Default: dvCPUSockets,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 16)),
},
mkCPUType: {
Type: schema.TypeString,
Description: "The emulated CPU type",
Optional: true,
Computed: true,
ValidateDiagFunc: validators.CPUTypeValidator(),
},
mkCPUUnits: {
Type: schema.TypeInt,
Description: "The CPU units",
Optional: true,
Default: dvCPUUnits,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(2, 262144),
),
},
mkCPUAffinity: {
Type: schema.TypeString,
Description: "The CPU affinity",
Optional: true,
Default: dvCPUAffinity,
ValidateDiagFunc: validators.CPUAffinityValidator(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
}
}

View File

@ -0,0 +1,40 @@
package cpu
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
)
func TestCPUSchema(t *testing.T) {
t.Parallel()
s := Schema()
cpuSchema := test.AssertNestedSchemaExistence(t, s, MkCPU)
test.AssertOptionalArguments(t, cpuSchema, []string{
mkCPUArchitecture,
mkCPUCores,
mkCPUFlags,
mkCPUHotplugged,
mkCPUNUMA,
mkCPUSockets,
mkCPUType,
mkCPUUnits,
})
test.AssertValueTypes(t, cpuSchema, map[string]schema.ValueType{
mkCPUArchitecture: schema.TypeString,
mkCPUCores: schema.TypeInt,
mkCPUFlags: schema.TypeList,
mkCPUHotplugged: schema.TypeInt,
mkCPUNUMA: schema.TypeBool,
mkCPUSockets: schema.TypeInt,
mkCPUType: schema.TypeString,
mkCPUUnits: schema.TypeInt,
})
}

View File

@ -8,7 +8,7 @@ import (
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
)
func TestVMSchema(t *testing.T) {
func TestDiskSchema(t *testing.T) {
t.Parallel()
s := Schema()

View File

@ -52,105 +52,6 @@ func BIOSValidator() schema.SchemaValidateDiagFunc {
}, false))
}
// CPUArchitectureValidator returns a schema validation function for a CPU architecture.
func CPUArchitectureValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"aarch64",
"x86_64",
}, false))
}
// CPUTypeValidator returns a schema validation function for a CPU type.
func CPUTypeValidator() schema.SchemaValidateDiagFunc {
standardTypes := []string{
"486",
"Broadwell",
"Broadwell-IBRS",
"Broadwell-noTSX",
"Broadwell-noTSX-IBRS",
"Cascadelake-Server",
"Cascadelake-Server-noTSX",
"Cascadelake-Server-v2",
"Cascadelake-Server-v4",
"Cascadelake-Server-v5",
"Conroe",
"Cooperlake",
"Cooperlake-v2",
"EPYC",
"EPYC-IBPB",
"EPYC-Milan",
"EPYC-Rome",
"EPYC-Rome-v2",
"EPYC-v3",
"Haswell",
"Haswell-IBRS",
"Haswell-noTSX",
"Haswell-noTSX-IBRS",
"Icelake-Client",
"Icelake-Client-noTSX",
"Icelake-Server",
"Icelake-Server-noTSX",
"Icelake-Server-v3",
"Icelake-Server-v4",
"Icelake-Server-v5",
"Icelake-Server-v6",
"IvyBridge",
"IvyBridge-IBRS",
"KnightsMill",
"Nehalem",
"Nehalem-IBRS",
"Opteron_G1",
"Opteron_G2",
"Opteron_G3",
"Opteron_G4",
"Opteron_G5",
"Penryn",
"SandyBridge",
"SandyBridge-IBRS",
"SapphireRapids",
"Skylake-Client",
"Skylake-Client-IBRS",
"Skylake-Client-noTSX-IBRS",
"Skylake-Client-v4",
"Skylake-Server",
"Skylake-Server-IBRS",
"Skylake-Server-noTSX-IBRS",
"Skylake-Server-v4",
"Skylake-Server-v5",
"Westmere",
"Westmere-IBRS",
"athlon",
"core2duo",
"coreduo",
"host",
"kvm32",
"kvm64",
"max",
"pentium",
"pentium2",
"pentium3",
"phenom",
"qemu32",
"qemu64",
"x86-64-v2",
"x86-64-v2-AES",
"x86-64-v3",
"x86-64-v4",
}
return validation.ToDiagFunc(validation.Any(
validation.StringInSlice(standardTypes, false),
validation.StringMatch(regexp.MustCompile(`^custom-.+$`), "must be a valid custom CPU type"),
))
}
// 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 or number ranges separated by ','"),
)
}
// QEMUAgentTypeValidator is a schema validation function for QEMU agent types.
func QEMUAgentTypeValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{"isa", "virtio"}, false))

View File

@ -12,37 +12,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestCPUType(t *testing.T) {
t.Parallel()
tests := []struct {
name string
value string
valid bool
}{
{"empty", "", false},
{"invalid", "invalid", false},
{"valid", "host", true},
{"valid", "qemu64", true},
{"valid", "custom-abc", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
f := CPUTypeValidator()
res := f(tt.value, nil)
if tt.valid {
require.Empty(t, res, "validate: '%s'", tt.value)
} else {
require.NotEmpty(t, res, "validate: '%s'", tt.value)
}
})
}
}
func TestMachineType(t *testing.T) {
t.Parallel()

View File

@ -19,6 +19,7 @@ import (
"golang.org/x/exp/maps"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/network"
"github.com/bpg/terraform-provider-proxmox/utils"
@ -59,15 +60,6 @@ const (
dvCloneNodeName = ""
dvCloneFull = true
dvCloneRetries = 1
dvCPUArchitecture = "x86_64"
dvCPUCores = 1
dvCPUHotplugged = 0
dvCPULimit = 0
dvCPUNUMA = false
dvCPUSockets = 1
dvCPUType = "qemu64"
dvCPUUnits = 1024
dvCPUAffinity = ""
dvDescription = ""
dvEFIDiskDatastoreID = "local-lvm"
@ -164,17 +156,6 @@ const (
mkCloneNodeName = "node_name"
mkCloneVMID = "vm_id"
mkCloneFull = "full"
mkCPU = "cpu"
mkCPUArchitecture = "architecture"
mkCPUCores = "cores"
mkCPUFlags = "flags"
mkCPUHotplugged = "hotplugged"
mkCPULimit = "limit"
mkCPUNUMA = "numa"
mkCPUSockets = "sockets"
mkCPUType = "type"
mkCPUUnits = "units"
mkCPUAffinity = "affinity"
mkDescription = "description"
mkNUMA = "numa"
@ -495,108 +476,6 @@ func VM() *schema.Resource {
MaxItems: 1,
MinItems: 0,
},
mkCPU: {
Type: schema.TypeList,
Description: "The CPU allocation",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{
map[string]interface{}{
mkCPUArchitecture: dvCPUArchitecture,
mkCPUCores: dvCPUCores,
mkCPUFlags: []interface{}{},
mkCPUHotplugged: dvCPUHotplugged,
mkCPULimit: dvCPULimit,
mkCPUNUMA: dvCPUNUMA,
mkCPUSockets: dvCPUSockets,
mkCPUType: dvCPUType,
mkCPUUnits: dvCPUUnits,
mkCPUAffinity: dvCPUAffinity,
},
}, nil
},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
mkCPUArchitecture: {
Type: schema.TypeString,
Description: "The CPU architecture",
Optional: true,
Default: dvCPUArchitecture,
ValidateDiagFunc: CPUArchitectureValidator(),
},
mkCPUCores: {
Type: schema.TypeInt,
Description: "The number of CPU cores",
Optional: true,
Default: dvCPUCores,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 2304)),
},
mkCPUFlags: {
Type: schema.TypeList,
Description: "The CPU flags",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Schema{Type: schema.TypeString},
},
mkCPUHotplugged: {
Type: schema.TypeInt,
Description: "The number of hotplugged vCPUs",
Optional: true,
Default: dvCPUHotplugged,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 2304)),
},
mkCPULimit: {
Type: schema.TypeInt,
Description: "Limit of CPU usage",
Optional: true,
Default: dvCPULimit,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(0, 128),
),
},
mkCPUNUMA: {
Type: schema.TypeBool,
Description: "Enable/disable NUMA.",
Optional: true,
Default: dvCPUNUMA,
},
mkCPUSockets: {
Type: schema.TypeInt,
Description: "The number of CPU sockets",
Optional: true,
Default: dvCPUSockets,
ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 16)),
},
mkCPUType: {
Type: schema.TypeString,
Description: "The emulated CPU type",
Optional: true,
Default: dvCPUType,
ValidateDiagFunc: CPUTypeValidator(),
},
mkCPUUnits: {
Type: schema.TypeInt,
Description: "The CPU units",
Optional: true,
Default: dvCPUUnits,
ValidateDiagFunc: validation.ToDiagFunc(
validation.IntBetween(2, 262144),
),
},
mkCPUAffinity: {
Type: schema.TypeString,
Description: "The CPU affinity",
Optional: true,
Default: dvCPUAffinity,
ValidateDiagFunc: CPUAffinityValidator(),
},
},
},
MaxItems: 1,
MinItems: 0,
},
mkDescription: {
Type: schema.TypeString,
Description: "The description",
@ -1450,6 +1329,7 @@ func VM() *schema.Resource {
},
}
structure.MergeSchema(s, cpu.Schema())
structure.MergeSchema(s, disk.Schema())
structure.MergeSchema(s, network.Schema())
@ -1871,7 +1751,6 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
kvmArguments := d.Get(mkKVMArguments).(string)
scsiHardware := d.Get(mkSCSIHardware).(string)
cdrom := d.Get(mkCDROM).([]interface{})
cpu := d.Get(mkCPU).([]interface{})
initialization := d.Get(mkInitialization).([]interface{})
hostPCI := d.Get(mkHostPCI).([]interface{})
hostUSB := d.Get(mkHostUSB).([]interface{})
@ -1964,53 +1843,7 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
}
}
if len(cpu) > 0 && cpu[0] != nil {
cpuBlock := cpu[0].(map[string]interface{})
cpuArchitecture := cpuBlock[mkCPUArchitecture].(string)
cpuCores := cpuBlock[mkCPUCores].(int)
cpuFlags := cpuBlock[mkCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkCPUHotplugged].(int)
cpuLimit := cpuBlock[mkCPULimit].(int)
cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool))
cpuSockets := cpuBlock[mkCPUSockets].(int)
cpuType := cpuBlock[mkCPUType].(string)
cpuUnits := cpuBlock[mkCPUUnits].(int)
cpuAffinity := cpuBlock[mkCPUAffinity].(string)
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvCPUArchitecture {
updateBody.CPUArchitecture = &cpuArchitecture
}
updateBody.CPUCores = &cpuCores
updateBody.CPUEmulation = &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
}
updateBody.NUMAEnabled = &cpuNUMA
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
if cpuAffinity != "" {
updateBody.CPUAffinity = &cpuAffinity
}
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
}
if cpuLimit > 0 {
updateBody.CPULimit = &cpuLimit
}
}
cpu.UpdateWhenClone(d, api, updateBody)
vmConfig, err := vmAPI.GetVM(ctx)
if err != nil {
@ -2194,8 +2027,6 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
return diag.FromErr(e)
}
/////////////////
allDiskInfo := disk.GetInfo(vmConfig, d) // from the cloned VM
planDisks, e := disk.GetDiskDeviceObjects(d, VM(), nil) // from the resource config
@ -2382,28 +2213,6 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
cdromFileID = "cdrom"
}
cpuBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkCPU},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cpuArchitecture := cpuBlock[mkCPUArchitecture].(string)
cpuCores := cpuBlock[mkCPUCores].(int)
cpuFlags := cpuBlock[mkCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkCPUHotplugged].(int)
cpuLimit := cpuBlock[mkCPULimit].(int)
cpuSockets := cpuBlock[mkCPUSockets].(int)
cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool))
cpuType := cpuBlock[mkCPUType].(string)
cpuUnits := cpuBlock[mkCPUUnits].(int)
cpuAffinity := cpuBlock[mkCPUAffinity].(string)
description := d.Get(mkDescription).(string)
var efiDisk *vms.CustomEFIDisk
@ -2590,11 +2399,6 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
}
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
ideDevice2Media := "cdrom"
ideDevices := vms.CustomStorageDevices{
cdromCloudInitInterface: &vms.CustomStorageDevice{
@ -2631,14 +2435,7 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
Boot: &vms.CustomBoot{
Order: &bootOrderConverted,
},
CloudInitConfig: initializationConfig,
CPUCores: &cpuCores,
CPUEmulation: &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
},
CPUSockets: &cpuSockets,
CPUUnits: &cpuUnits,
CloudInitConfig: initializationConfig,
DedicatedMemory: &memoryDedicated,
DeletionProtection: &protection,
EFIDisk: efiDisk,
@ -2647,7 +2444,6 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
IDEDevices: ideDevices,
KeyboardLayout: &keyboardLayout,
NetworkDevices: networkDeviceObjects,
NUMAEnabled: &cpuNUMA,
NUMADevices: numaDeviceObjects,
OSType: &operatingSystemType,
PCIDevices: pciDeviceObjects,
@ -2664,6 +2460,11 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
VMID: &vmID,
}
err = cpu.Create(resource, d, api, createBody)
if err != nil {
return diag.FromErr(err)
}
if sataDeviceObjects != nil {
createBody.SATADevices = sataDeviceObjects
}
@ -2676,24 +2477,6 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
createBody.VirtualIODevices = virtioDeviceObjects
}
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
if api.API().IsRootTicket() ||
cpuArchitecture != dvCPUArchitecture {
createBody.CPUArchitecture = &cpuArchitecture
}
if cpuHotplugged > 0 {
createBody.VirtualCPUCount = &cpuHotplugged
}
if cpuLimit > 0 {
createBody.CPULimit = &cpuLimit
}
if cpuAffinity != "" {
createBody.CPUAffinity = &cpuAffinity
}
if description != "" {
createBody.Description = &description
}
@ -3582,48 +3365,8 @@ func vmReadCustom(
diags = append(diags, diag.FromErr(err)...)
}
// Compare the CPU configuration to the one stored in the state.
cpu := map[string]interface{}{}
if vmConfig.CPUArchitecture != nil {
cpu[mkCPUArchitecture] = *vmConfig.CPUArchitecture
} else {
// Default value of "arch" is "" according to the API documentation.
// However, assume the provider's default value as a workaround when the root account is not being used.
if !api.API().IsRootTicket() {
cpu[mkCPUArchitecture] = dvCPUArchitecture
} else {
cpu[mkCPUArchitecture] = ""
}
}
if vmConfig.CPUCores != nil {
cpu[mkCPUCores] = *vmConfig.CPUCores
} else {
// Default value of "cores" is "1" according to the API documentation.
cpu[mkCPUCores] = 1
}
if vmConfig.VirtualCPUCount != nil {
cpu[mkCPUHotplugged] = *vmConfig.VirtualCPUCount
} else {
// Default value of "vcpus" is "1" according to the API documentation.
cpu[mkCPUHotplugged] = 0
}
if vmConfig.CPULimit != nil {
cpu[mkCPULimit] = *vmConfig.CPULimit
} else {
// Default value of "cpulimit" is "0" according to the API documentation.
cpu[mkCPULimit] = 0
}
if vmConfig.NUMAEnabled != nil {
cpu[mkCPUNUMA] = *vmConfig.NUMAEnabled
} else {
// Default value of "numa" is "false" according to the API documentation.
cpu[mkCPUNUMA] = false
}
err := cpu.Read(vmConfig, api, d, len(clone) > 0)
diags = append(diags, diag.FromErr(err)...)
currentNUMAList := d.Get(mkNUMA).([]interface{})
numaMap := map[string]interface{}{}
@ -3663,66 +3406,6 @@ func vmReadCustom(
diags = append(diags, diag.FromErr(err)...)
}
if vmConfig.CPUSockets != nil {
cpu[mkCPUSockets] = *vmConfig.CPUSockets
} else {
// Default value of "sockets" is "1" according to the API documentation.
cpu[mkCPUSockets] = 1
}
if vmConfig.CPUEmulation != nil {
if vmConfig.CPUEmulation.Flags != nil {
convertedFlags := make([]interface{}, len(*vmConfig.CPUEmulation.Flags))
for fi, fv := range *vmConfig.CPUEmulation.Flags {
convertedFlags[fi] = fv
}
cpu[mkCPUFlags] = convertedFlags
} else {
cpu[mkCPUFlags] = []interface{}{}
}
cpu[mkCPUType] = vmConfig.CPUEmulation.Type
} else {
cpu[mkCPUFlags] = []interface{}{}
// Default value of "cputype" is "qemu64" according to the QEMU documentation.
cpu[mkCPUType] = "qemu64"
}
if vmConfig.CPUUnits != nil {
cpu[mkCPUUnits] = *vmConfig.CPUUnits
} else {
// Default value of "cpuunits" is "1024" according to the API documentation.
cpu[mkCPUUnits] = 1024
}
if vmConfig.CPUAffinity != nil {
cpu[mkCPUAffinity] = *vmConfig.CPUAffinity
} else {
cpu[mkCPUAffinity] = ""
}
currentCPU := d.Get(mkCPU).([]interface{})
if len(clone) > 0 {
if len(currentCPU) > 0 {
err := d.Set(mkCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...)
}
} else if len(currentCPU) > 0 ||
cpu[mkCPUArchitecture] != dvCPUArchitecture ||
cpu[mkCPUCores] != dvCPUCores ||
len(cpu[mkCPUFlags].([]interface{})) > 0 ||
cpu[mkCPUHotplugged] != dvCPUHotplugged ||
cpu[mkCPULimit] != dvCPULimit ||
cpu[mkCPUSockets] != dvCPUSockets ||
cpu[mkCPUType] != dvCPUType ||
cpu[mkCPUUnits] != dvCPUUnits {
err := d.Set(mkCPU, []interface{}{cpu})
diags = append(diags, diag.FromErr(err)...)
}
allDiskInfo := disk.GetInfo(vmConfig, d)
diags = append(diags, disk.Read(ctx, d, allDiskInfo, vmID, api, nodeName, len(clone) > 0)...)
@ -4961,79 +4644,14 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
}
}
// Prepare the new CPU configuration.
if d.HasChange(mkCPU) {
cpuBlock, err := structure.GetSchemaBlock(
resource,
d,
[]string{mkCPU},
0,
true,
)
if err != nil {
return diag.FromErr(err)
}
cpuArchitecture := cpuBlock[mkCPUArchitecture].(string)
cpuCores := cpuBlock[mkCPUCores].(int)
cpuFlags := cpuBlock[mkCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkCPUHotplugged].(int)
cpuLimit := cpuBlock[mkCPULimit].(int)
cpuNUMA := types.CustomBool(cpuBlock[mkCPUNUMA].(bool))
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() ||
cpuArchitecture != dvCPUArchitecture {
updateBody.CPUArchitecture = &cpuArchitecture
}
updateBody.CPUCores = &cpuCores
updateBody.CPUSockets = &cpuSockets
updateBody.CPUUnits = &cpuUnits
updateBody.NUMAEnabled = &cpuNUMA
// CPU affinity is a special case, only root can change it.
// we can't even have it in the delete list, as PVE will return an error for non-root.
// Hence, checking explicitly if it has changed.
if d.HasChange(mkCPU + ".0." + mkCPUAffinity) {
if cpuAffinity != "" {
updateBody.CPUAffinity = &cpuAffinity
} else {
del = append(del, "affinity")
}
}
if cpuHotplugged > 0 {
updateBody.VirtualCPUCount = &cpuHotplugged
} else {
del = append(del, "vcpus")
}
if cpuLimit > 0 {
updateBody.CPULimit = &cpuLimit
} else {
del = append(del, "cpulimit")
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
updateBody.CPUEmulation = &vms.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
}
rebootRequired = true
dd, rr, err := cpu.Update(d, resource, api, updateBody)
if err != nil {
return diag.FromErr(err)
}
del = append(del, dd...)
rebootRequired = rebootRequired || rr
// Prepare the new disk device configuration.
allDiskInfo := disk.GetInfo(vmConfig, d)
@ -5042,7 +4660,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
return diag.FromErr(err)
}
rr, err := disk.Update(d, planDisks, allDiskInfo, updateBody)
rr, err = disk.Update(d, planDisks, allDiskInfo, updateBody)
if err != nil {
return diag.FromErr(err)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/stretchr/testify/require"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/cpu"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/disk"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/resource/vm/network"
"github.com/bpg/terraform-provider-proxmox/proxmoxtf/test"
@ -45,7 +46,7 @@ func TestVMSchema(t *testing.T) {
mkBootOrder,
mkCDROM,
mkClone,
mkCPU,
cpu.MkCPU,
mkDescription,
disk.MkDisk,
mkEFIDisk,
@ -75,7 +76,7 @@ func TestVMSchema(t *testing.T) {
mkBIOS: schema.TypeString,
mkBootOrder: schema.TypeList,
mkCDROM: schema.TypeList,
mkCPU: schema.TypeList,
cpu.MkCPU: schema.TypeList,
mkDescription: schema.TypeString,
disk.MkDisk: schema.TypeList,
mkEFIDisk: schema.TypeList,
@ -153,30 +154,6 @@ func TestVMSchema(t *testing.T) {
mkCloneVMID: schema.TypeInt,
})
cpuSchema := test.AssertNestedSchemaExistence(t, s, mkCPU)
test.AssertOptionalArguments(t, cpuSchema, []string{
mkCPUArchitecture,
mkCPUCores,
mkCPUFlags,
mkCPUHotplugged,
mkCPUNUMA,
mkCPUSockets,
mkCPUType,
mkCPUUnits,
})
test.AssertValueTypes(t, cpuSchema, map[string]schema.ValueType{
mkCPUArchitecture: schema.TypeString,
mkCPUCores: schema.TypeInt,
mkCPUFlags: schema.TypeList,
mkCPUHotplugged: schema.TypeInt,
mkCPUNUMA: schema.TypeBool,
mkCPUSockets: schema.TypeInt,
mkCPUType: schema.TypeString,
mkCPUUnits: schema.TypeInt,
})
efiDiskSchema := test.AssertNestedSchemaExistence(t, s, mkEFIDisk)
test.AssertOptionalArguments(t, efiDiskSchema, []string{