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:
parent
163a773088
commit
9c72e584de
122
fwprovider/tests/resource_vm_clone_test.go
Normal file
122
fwprovider/tests/resource_vm_clone_test.go
Normal 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,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -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], ";")
|
||||
|
107
proxmoxtf/resource/validators/cpu.go
Normal file
107
proxmoxtf/resource/validators/cpu.go
Normal 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 ','"),
|
||||
)
|
||||
}
|
38
proxmoxtf/resource/validators/cpu_test.go
Normal file
38
proxmoxtf/resource/validators/cpu_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
386
proxmoxtf/resource/vm/cpu/cpu.go
Normal file
386
proxmoxtf/resource/vm/cpu/cpu.go
Normal 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
|
||||
}
|
141
proxmoxtf/resource/vm/cpu/schema.go
Normal file
141
proxmoxtf/resource/vm/cpu/schema.go
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
40
proxmoxtf/resource/vm/cpu/schema_test.go
Normal file
40
proxmoxtf/resource/vm/cpu/schema_test.go
Normal 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,
|
||||
})
|
||||
|
||||
}
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{
|
||||
|
Loading…
Reference in New Issue
Block a user