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

Additional CPU arguments

This commit is contained in:
Dan Petersen 2019-12-31 05:25:19 +01:00
parent daa7102a26
commit a9349cfbd0
6 changed files with 314 additions and 6 deletions

View File

@ -2,7 +2,7 @@
ENHANCEMENTS:
resource/virtual_environment_vm: Add `vga` argument
resource/virtual_environment_vm: Add `cpu.flags`, `cpu.type` and `vga` arguments
## 0.1.0

View File

@ -359,8 +359,52 @@ This resource doesn't expose any additional attributes.
* `user_data_file_id` - (Optional) The ID of a file containing custom user data (conflicts with `user_account`)
* `cpu` - (Optional) The CPU configuration
* `cores` - (Optional) The number of CPU cores (defaults to `1`)
* `flags` - (Optional) The CPU flags
* `+aes`/`-aes` - Activate AES instruction set for HW acceleration
* `+amd-no-ssb`/`-amd-no-ssb` - Notifies guest OS that host is not vulnerable for Spectre on AMD CPUs
* `+amd-ssbd`/`-amd-ssbd` - Improves Spectre mitigation performance with AMD CPUs, best used with "virt-ssbd"
* `+hv-evmcs`/`-hv-evmcs` - Improve performance for nested virtualization (only supported on Intel CPUs)
* `+hv-tlbflush`/`-hv-tlbflush` - Improve performance in overcommitted Windows guests (may lead to guest BSOD on old CPUs)
* `+ibpb`/`-ibpb` - Allows improved Spectre mitigation on AMD CPUs
* `+md-clear`/`-md-clear` - Required to let the guest OS know if MDS is mitigated correctly
* `+pcid`/`-pcid` - Meltdown fix cost reduction on Westmere, Sandy- and Ivy Bridge Intel CPUs
* `+pdpe1gb`/`-pdpe1gb` - Allows guest OS to use 1 GB size pages, if host HW supports it
* `+spec-ctrl`/`-spec-ctrl` - Allows improved Spectre mitigation with Intel CPUs
* `+ssbd`/`-ssbd` - Protection for "Speculative Store Bypass" for Intel models
* `+virt-ssbd`/`-virt-ssbd` - Basis for "Speculative Store Bypass" protection for AMD models
* `hotplugged` - (Optional) The number of hotplugged vCPUs (defaults to `0`)
* `sockets` - (Optional) The number of CPU sockets (defaults to `1`)
* `type` - (Optional) The emulated CPU type (defaults to `qemu64`)
* `486` - Intel 486
* `Broadwell`/`Broadwell-IBRS`/`Broadwell-noTSX`/`Broadwell-noTSX-IBRS` - Intel Core Processor (Broadwell, 2014)
* `Cascadelake-Server` - Intel Xeon 32xx/42xx/52xx/62xx/82xx/92xx (2019)
* `Conroe` - Intel Celeron_4x0 (Conroe/Merom Class Core 2, 2006)
* `EPYC`/`EPYC-IBPB` - AMD EPYC Processor (2017)
* `Haswell`/`Haswell-IBRS`/`Haswell-noTSX`/`Haswell-noTSX-IBRS` - Intel Core Processor (Haswell, 2013)
* `IvyBridge`/`IvyBridge-IBRS` - Intel Xeon E3-12xx v2 (Ivy Bridge, 2012)
* `KnightsMill` - Intel Xeon Phi 72xx (2017)
* `Nehalem`/`Nehalem-IBRS` - Intel Core i7 9xx (Nehalem Class Core i7, 2008)
* `Opteron_G1` - AMD Opteron 240 (Gen 1 Class Opteron, 2004)
* `Opteron_G2` - AMD Opteron 22xx (Gen 2 Class Opteron, 2006)
* `Opteron_G3` - AMD Opteron 23xx (Gen 3 Class Opteron, 2009)
* `Opteron_G4` - AMD Opteron 62xx class CPU (2011)
* `Opteron_G5` - AMD Opteron 63xx class CPU (2012)
* `Penryn` - Intel Core 2 Duo P9xxx (Penryn Class Core 2, 2007)
* `SandyBridge`/`SandyBridge-IBRS` - Intel Xeon E312xx (Sandy Bridge, 2011)
* `Skylake-Client`/`Skylake-Client-IBRS` - Intel Core Processor (Skylake, 2015)
* `Skylake-Server`/`Skylake-Server-IBRS` - Intel Xeon Processor (Skylake, 2016)
* `Westmere`/`Westmere-IBRS` - Intel Westmere E56xx/L56xx/X56xx (Nehalem-C, 2010)
* `athlon` - AMD Athlon
* `core2duo` - Intel Core 2 Duo
* `coreduo` - Intel Core Duo
* `host` - Host passthrough
* `kvm32`/`kvm64` - Common KVM processor (32 & 64 bit variants)
* `max` - Maximum amount of features from host CPU
* `pentium` - Intel Pentium (1993)
* `pentium2` - Intel Pentium 2 (1997-1999)
* `pentium3` - Intel Pentium 3 (1999-2001)
* `phenom` - AMD Phenom (2010)
* `qemu32`/`qemu64` - QEMU Virtual CPU version 2.5+ (32 & 64 bit variants)
* `description` - (Optional) The description
* `disk` - (Optional) The disk configuration (multiple blocks supported)
* `datastore_id` - (Optional) The ID of the datastore to create the disk in (defaults to `local-lvm`)

View File

@ -6,6 +6,7 @@ package proxmox
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
@ -55,6 +56,14 @@ type CustomCloudInitIPConfig struct {
// CustomCloudInitSSHKeys handles QEMU cloud-init SSH keys parameters.
type CustomCloudInitSSHKeys []string
// CustomCPUEmulation handles QEMU CPU emulation parameters.
type CustomCPUEmulation struct {
Flags *[]string `json:"flags,omitempty" url:"flags,omitempty,semicolon"`
Hidden *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"`
}
// CustomEFIDisk handles QEMU EFI disk parameters.
type CustomEFIDisk struct {
DiskSize *int `json:"size,omitempty" url:"size,omitempty"`
@ -202,6 +211,7 @@ type VirtualEnvironmentVMCreateRequestBody struct {
CloudInitConfig *CustomCloudInitConfig `json:"cloudinit,omitempty" url:"cloudinit,omitempty"`
CPUArchitecture *string `json:"arch,omitempty" url:"arch,omitempty"`
CPUCores *int `json:"cores,omitempty" url:"cores,omitempty"`
CPUEmulation *CustomCPUEmulation `json:"cpu,omitempty" url:"cpu,omitempty"`
CPULimit *int `json:"cpulimit,omitempty" url:"cpulimit,omitempty"`
CPUSockets *int `json:"sockets,omitempty" url:"sockets,omitempty"`
CPUUnits *int `json:"cpuunits,omitempty" url:"cpuunits,omitempty"`
@ -322,6 +332,7 @@ type VirtualEnvironmentVMGetResponseData struct {
CloudInitUsername *string `json:"ciuser,omitempty"`
CPUArchitecture *string `json:"arch,omitempty"`
CPUCores *int `json:"cores,omitempty"`
CPUEmulation *CustomCPUEmulation `json:"cpu,omitempty"`
CPULimit *int `json:"cpulimit,omitempty"`
CPUSockets *int `json:"sockets,omitempty"`
CPUUnits *int `json:"cpuunits,omitempty"`
@ -574,6 +585,33 @@ func (r CustomCloudInitConfig) EncodeValues(key string, v *url.Values) error {
return nil
}
// 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),
}
if r.Flags != nil && len(*r.Flags) > 0 {
values = append(values, fmt.Sprintf("flags=%s", strings.Join(*r.Flags, ";")))
}
if r.Hidden != nil {
if *r.Hidden {
values = append(values, "hidden=1")
} else {
values = append(values, "hidden=0")
}
}
if r.HVVendorID != nil {
values = append(values, fmt.Sprintf("hv-vendor-id=%s", *r.HVVendorID))
}
v.Add(key, strings.Join(values, ","))
return nil
}
// EncodeValues converts a CustomEFIDisk struct to a URL vlaue.
func (r CustomEFIDisk) EncodeValues(key string, v *url.Values) error {
values := []string{
@ -1136,6 +1174,51 @@ func (r *CustomCloudInitSSHKeys) UnmarshalJSON(b []byte) error {
return nil
}
// UnmarshalJSON converts a CustomCPUEmulation string to an object.
func (r *CustomCPUEmulation) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
if s == "" {
return errors.New("Unexpected empty string")
}
pairs := strings.Split(s, ",")
for _, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")
if len(v) == 1 {
r.Type = v[0]
} else if len(v) == 2 {
switch v[0] {
case "cputype":
r.Type = v[1]
case "flags":
if v[1] != "" {
f := strings.Split(v[1], ";")
r.Flags = &f
} else {
f := []string{}
r.Flags = &f
}
case "hidden":
bv := CustomBool(v[1] == "1")
r.Hidden = &bv
case "hv-vendor-id":
r.HVVendorID = &v[1]
}
}
}
return nil
}
// UnmarshalJSON converts a CustomNetworkDevice string to an object.
func (r *CustomNetworkDevice) UnmarshalJSON(b []byte) error {
var s string

View File

@ -28,6 +28,7 @@ const (
dvResourceVirtualEnvironmentVMCPUCores = 1
dvResourceVirtualEnvironmentVMCPUHotplugged = 0
dvResourceVirtualEnvironmentVMCPUSockets = 1
dvResourceVirtualEnvironmentVMCPUType = "qemu64"
dvResourceVirtualEnvironmentVMDescription = ""
dvResourceVirtualEnvironmentVMDiskDatastoreID = "local-lvm"
dvResourceVirtualEnvironmentVMDiskFileFormat = "qcow2"
@ -81,8 +82,10 @@ const (
mkResourceVirtualEnvironmentVMCloudInitUserDataFileID = "user_data_file_id"
mkResourceVirtualEnvironmentVMCPU = "cpu"
mkResourceVirtualEnvironmentVMCPUCores = "cores"
mkResourceVirtualEnvironmentVMCPUFlags = "flags"
mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged"
mkResourceVirtualEnvironmentVMCPUSockets = "sockets"
mkResourceVirtualEnvironmentVMCPUType = "type"
mkResourceVirtualEnvironmentVMDescription = "description"
mkResourceVirtualEnvironmentVMDisk = "disk"
mkResourceVirtualEnvironmentVMDiskDatastoreID = "datastore_id"
@ -363,8 +366,10 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
defaultMap := map[string]interface{}{}
defaultMap[mkResourceVirtualEnvironmentVMCPUCores] = dvResourceVirtualEnvironmentVMCPUCores
defaultMap[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{}
defaultMap[mkResourceVirtualEnvironmentVMCPUHotplugged] = dvResourceVirtualEnvironmentVMCPUHotplugged
defaultMap[mkResourceVirtualEnvironmentVMCPUSockets] = dvResourceVirtualEnvironmentVMCPUSockets
defaultMap[mkResourceVirtualEnvironmentVMCPUType] = dvResourceVirtualEnvironmentVMCPUType
defaultList[0] = defaultMap
@ -379,6 +384,15 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
Default: dvResourceVirtualEnvironmentVMCPUCores,
ValidateFunc: validation.IntBetween(1, 2304),
},
mkResourceVirtualEnvironmentVMCPUFlags: {
Type: schema.TypeList,
Description: "The CPU flags",
Optional: true,
DefaultFunc: func() (interface{}, error) {
return []interface{}{}, nil
},
Elem: &schema.Schema{Type: schema.TypeString},
},
mkResourceVirtualEnvironmentVMCPUHotplugged: {
Type: schema.TypeInt,
Description: "The number of hotplugged vCPUs",
@ -393,6 +407,13 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
Default: dvResourceVirtualEnvironmentVMCPUSockets,
ValidateFunc: validation.IntBetween(1, 16),
},
mkResourceVirtualEnvironmentVMCPUType: {
Type: schema.TypeString,
Description: "The emulated CPU type",
Optional: true,
Default: dvResourceVirtualEnvironmentVMCPUType,
ValidateFunc: getCPUTypeValidator(),
},
},
},
MaxItems: 1,
@ -786,8 +807,10 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
}
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
description := d.Get(mkResourceVirtualEnvironmentVMDescription).(string)
diskDeviceObjects, err := resourceVirtualEnvironmentVMGetDiskDeviceObjects(d, m)
@ -847,6 +870,12 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
bootOrder = "cd"
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
ideDevice2Media := "cdrom"
ideDevices := proxmox.CustomStorageDevices{
proxmox.CustomStorageDevice{
@ -883,6 +912,10 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
BootOrder: &bootOrder,
CloudInitConfig: cloudInitConfig,
CPUCores: &cpuCores,
CPUEmulation: &proxmox.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
},
CPUSockets: &cpuSockets,
DedicatedMemory: &memoryDedicated,
FloatingMemory: &memoryFloating,
@ -1557,12 +1590,33 @@ func resourceVirtualEnvironmentVMRead(d *schema.ResourceData, m interface{}) err
cpu[mkResourceVirtualEnvironmentVMCPUSockets] = 0
}
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[mkResourceVirtualEnvironmentVMCPUFlags] = convertedFlags
} else {
cpu[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{}
}
cpu[mkResourceVirtualEnvironmentVMCPUType] = vmConfig.CPUEmulation.Type
} else {
cpu[mkResourceVirtualEnvironmentVMCPUFlags] = []interface{}{}
cpu[mkResourceVirtualEnvironmentVMCPUType] = ""
}
currentCPU := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{})
if len(currentCPU) > 0 ||
cpu[mkResourceVirtualEnvironmentVMCPUCores] != dvResourceVirtualEnvironmentVMCPUCores ||
len(cpu[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})) > 0 ||
cpu[mkResourceVirtualEnvironmentVMCPUHotplugged] != dvResourceVirtualEnvironmentVMCPUHotplugged ||
cpu[mkResourceVirtualEnvironmentVMCPUSockets] != dvResourceVirtualEnvironmentVMCPUSockets {
cpu[mkResourceVirtualEnvironmentVMCPUSockets] != dvResourceVirtualEnvironmentVMCPUSockets ||
cpu[mkResourceVirtualEnvironmentVMCPUType] != dvResourceVirtualEnvironmentVMCPUType {
d.Set(mkResourceVirtualEnvironmentVMCPU, []interface{}{cpu})
}
@ -2040,8 +2094,10 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e
}
cpuCores := cpuBlock[mkResourceVirtualEnvironmentVMCPUCores].(int)
cpuFlags := cpuBlock[mkResourceVirtualEnvironmentVMCPUFlags].([]interface{})
cpuHotplugged := cpuBlock[mkResourceVirtualEnvironmentVMCPUHotplugged].(int)
cpuSockets := cpuBlock[mkResourceVirtualEnvironmentVMCPUSockets].(int)
cpuType := cpuBlock[mkResourceVirtualEnvironmentVMCPUType].(string)
if cpuCores > 0 {
body.CPUCores = &cpuCores
@ -2055,6 +2111,17 @@ func resourceVirtualEnvironmentVMUpdate(d *schema.ResourceData, m interface{}) e
body.CPUSockets = &cpuSockets
}
cpuFlagsConverted := make([]string, len(cpuFlags))
for fi, flag := range cpuFlags {
cpuFlagsConverted[fi] = flag.(string)
}
body.CPUEmulation = &proxmox.CustomCPUEmulation{
Flags: &cpuFlagsConverted,
Type: cpuType,
}
rebootRequired = true
}

View File

@ -227,18 +227,24 @@ func TestResourceVirtualEnvironmentVMSchema(t *testing.T) {
testOptionalArguments(t, cpuSchema, []string{
mkResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUFlags,
mkResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUSockets,
mkResourceVirtualEnvironmentVMCPUType,
})
testSchemaValueTypes(t, cpuSchema, []string{
mkResourceVirtualEnvironmentVMCPUCores,
mkResourceVirtualEnvironmentVMCPUFlags,
mkResourceVirtualEnvironmentVMCPUHotplugged,
mkResourceVirtualEnvironmentVMCPUSockets,
mkResourceVirtualEnvironmentVMCPUType,
}, []schema.ValueType{
schema.TypeInt,
schema.TypeList,
schema.TypeInt,
schema.TypeInt,
schema.TypeString,
})
diskSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentVMDisk)

View File

@ -22,6 +22,114 @@ func getContentTypeValidator() schema.SchemaValidateFunc {
}, false)
}
func getCPUFlagsValidator() schema.SchemaValidateFunc {
return func(i interface{}, k string) (ws []string, es []error) {
list, ok := i.([]interface{})
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be []interface{}", k))
return
}
validator := validation.StringInSlice([]string{
"+aes",
"-aes",
"+amd-no-ssb",
"-amd-no-ssb",
"+amd-ssbd",
"-amd-ssbd",
"+hv-evmcs",
"-hv-evmcs",
"+hv-tlbflush",
"-hv-tlbflush",
"+ibpb",
"-ibpb",
"+md-clear",
"-md-clear",
"+pcid",
"-pcid",
"+pdpe1gb",
"-pdpe1gb",
"+spec-ctrl",
"-spec-ctrl",
"+ssbd",
"-ssbd",
"+virt-ssbd",
"-virt-ssbd",
}, false)
for li, lv := range list {
v, ok := lv.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s[%d] to be string", k, li))
return
}
warns, errs := validator(v, k)
ws = append(ws, warns...)
es = append(es, errs...)
if len(es) > 0 {
return
}
}
return
}
}
func getCPUTypeValidator() schema.SchemaValidateFunc {
return validation.StringInSlice([]string{
"486",
"Broadwell",
"Broadwell-IBRS",
"Broadwell-noTSX",
"Broadwell-noTSX-IBRS",
"Cascadelake-Server",
"Conroe",
"EPYC",
"EPYC-IBPB",
"Haswell",
"Haswell-IBRS",
"Haswell-noTSX",
"Haswell-noTSX-IBRS",
"IvyBridge",
"IvyBridge-IBRS",
"KnightsMill",
"Nehalem",
"Nehalem-IBRS",
"Opteron_G1",
"Opteron_G2",
"Opteron_G3",
"Opteron_G4",
"Opteron_G5",
"Penryn",
"SandyBridge",
"SandyBridge-IBRS",
"Skylake-Client",
"Skylake-Client-IBRS",
"Skylake-Server",
"Skylake-Server-IBRS",
"Westmere",
"Westmere-IBRS",
"athlon",
"core2duo",
"coreduo",
"host",
"kvm32",
"kvm64",
"max",
"pentium",
"pentium2",
"pentium3",
"phenom",
"qemu32",
"qemu64",
}, false)
}
func getFileFormatValidator() schema.SchemaValidateFunc {
return validation.StringInSlice([]string{
"qcow2",