Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vm): efi disk, cpu numa #384

Merged
merged 8 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/resources/virtual_environment_vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ output "ubuntu_vm_public_key" {
protection for AMD models.
- `hotplugged` - (Optional) The number of hotplugged vCPUs (defaults
to `0`).
- `numa` - (Boolean) Enable/disable NUMA. (default to `false`)
- `sockets` - (Optional) The number of CPU sockets (defaults to `1`).
- `type` - (Optional) The emulated CPU type (defaults to `qemu64`).
- `486` - Intel 486.
Expand Down Expand Up @@ -241,6 +242,19 @@ output "ubuntu_vm_public_key" {
- `ssd` - (Optional) Whether to use an SSD emulation option for this disk (
defaults to `false`). Note that SSD emulation is not supported on VirtIO
Block drives.
- `efi_disk` - (Optional) The efi disk device (required if `bios` is set
to `ovmf`)
- `datastore_id` (Optional) The identifier for the datastore to create
the disk in (defaults to `local-lvm`).
- `file_format` (Optional) The file format.
- `type` (Optional) Size and type of the OVMF EFI disk. `4m` is newer and
recommended, and required for Secure Boot. For backwards compatibility
use `2m`. Ignored for VMs with cpu.architecture=`aarch64` (defaults
to `2m`).
- `pre_enrolled_keys` (Optional) Use am EFI vars template with
distribution-specific and Microsoft Standard keys enrolled, if used with
EFI type=`4m`. Ignored for VMs with cpu.architecture=`aarch64` (defaults
to `false`).
- `hostpci` - (Optional) A host PCI device mapping (multiple blocks supported).
- `device` - (Required) The PCI device name for Proxmox, in form
of `hostpciX` where `X` is a sequential number from 0 to 3.
Expand Down Expand Up @@ -284,7 +298,7 @@ output "ubuntu_vm_public_key" {
- `user_data_file_id` - (Optional) The identifier for a file containing
custom user data (conflicts with `user_account`).
- `vendor_data_file_id` - (Optional) The identifier for a file containing
all vendor data passed to the VM via cloud-init.
all vendor data passed to the VM via cloud-init.
- `meta_data_file_id` - (Optional) The identifier for a file containing
all meta data passed to the VM via cloud-init.
- `keyboard_layout` - (Optional) The keyboard layout (defaults to `en-us`).
Expand Down
10 changes: 10 additions & 0 deletions example/resource_virtual_environment_vm.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ resource "proxmox_virtual_environment_vm" "example_template" {

description = "Managed by Terraform"

cpu {
numa = true
}

efi_disk {
datastore_id = local.datastore_id
file_format = "raw"
type = "4m"
}

# disk {
# datastore_id = local.datastore_id
# file_id = proxmox_virtual_environment_file.ubuntu_cloud_image.id
Expand Down
42 changes: 27 additions & 15 deletions proxmox/nodes/vms/vms_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ type CustomCPUEmulation struct {

// CustomEFIDisk handles QEMU EFI disk parameters.
type CustomEFIDisk struct {
Size *types.DiskSize `json:"size,omitempty" url:"size,omitempty"`
FileVolume string `json:"file" url:"file"`
Format *string `json:"format,omitempty" url:"format,omitempty"`
FileVolume string `json:"file" url:"file"`
Format *string `json:"format,omitempty" url:"format,omitempty"`
Type *string `json:"efitype,omitempty" url:"efitype,omitempty"`
PreEnrolledKeys *types2.CustomBool `json:"pre-enrolled-keys,omitempty" url:"pre-enrolled-keys,omitempty,int"`
}

// CustomNetworkDevice handles QEMU network device parameters.
Expand Down Expand Up @@ -784,8 +785,16 @@ func (r CustomEFIDisk) EncodeValues(key string, v *url.Values) error {
values = append(values, fmt.Sprintf("format=%s", *r.Format))
}

if r.Size != nil {
values = append(values, fmt.Sprintf("size=%s", *r.Size))
if r.Type != nil {
values = append(values, fmt.Sprintf("efitype=%s", *r.Type))
}

if r.PreEnrolledKeys != nil {
if *r.PreEnrolledKeys {
values = append(values, "pre-enrolled-keys=1")
} else {
values = append(values, "pre-enrolled-keys=0")
}
}

v.Add(key, strings.Join(values, ","))
Expand Down Expand Up @@ -1472,22 +1481,25 @@ func (r *CustomEFIDisk) UnmarshalJSON(b []byte) error {

pairs := strings.Split(s, ",")

for _, p := range pairs {
for i, p := range pairs {
v := strings.Split(strings.TrimSpace(p), "=")

if len(v) == 1 && i == 0 {
r.FileVolume = v[0]
}

if len(v) == 2 {
switch v[0] {
case "format":
r.Format = &v[1]
case "file":
r.FileVolume = v[1]
case "size":
r.Size = new(types.DiskSize)

err := r.Size.UnmarshalJSON([]byte(v[1]))
if err != nil {
return fmt.Errorf("failed to unmarshal disk size: %w", err)
}
case "format":
r.Format = &v[1]
case "efitype":
t := strings.ToLower(v[1])
r.Type = &t
case "pre-enrolled-keys":
bv := types2.CustomBool(v[1] == "1")
r.PreEnrolledKeys = &bv
}
}
}
Expand Down
32 changes: 17 additions & 15 deletions proxmox/types/disk_size.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ type DiskSize int64

// String returns the string representation of the disk size.
func (r DiskSize) String() string {
return formatDiskSize(int64(r))
return FormatDiskSize(r)
}

// InMegabytes returns the disk size in megabytes.
func (r DiskSize) InMegabytes() int {
return int(int64(r) / 1024 / 1024)
}

// InGigabytes returns the disk size in gigabytes.
Expand All @@ -39,7 +44,7 @@ func DiskSizeFromGigabytes(size int) DiskSize {

// MarshalJSON marshals a disk size into a Proxmox API `<DiskSize>` string.
func (r DiskSize) MarshalJSON() ([]byte, error) {
bytes, err := json.Marshal(formatDiskSize(int64(r)))
bytes, err := json.Marshal(FormatDiskSize(r))
if err != nil {
return nil, fmt.Errorf("cannot marshal disk size: %w", err)
}
Expand All @@ -51,27 +56,23 @@ func (r DiskSize) MarshalJSON() ([]byte, error) {
func (r *DiskSize) UnmarshalJSON(b []byte) error {
s := string(b)

size, err := parseDiskSize(&s)
size, err := ParseDiskSize(s)
if err != nil {
return err
}

*r = DiskSize(size)
*r = size

return nil
}

// parseDiskSize parses a disk size string into a number of bytes.
func parseDiskSize(size *string) (int64, error) {
if size == nil {
return 0, nil
}

matches := sizeRegex.FindStringSubmatch(*size)
// ParseDiskSize parses a disk size string into a number of bytes.
func ParseDiskSize(size string) (DiskSize, error) {
matches := sizeRegex.FindStringSubmatch(size)
if len(matches) > 0 {
fsize, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return -1, fmt.Errorf("cannot parse disk size \"%s\": %w", *size, err)
return -1, fmt.Errorf("cannot parse disk size \"%s\": %w", size, err)
}

switch strings.ToLower(matches[3]) {
Expand All @@ -85,13 +86,14 @@ func parseDiskSize(size *string) (int64, error) {
fsize = fsize * 1024 * 1024 * 1024 * 1024
}

return int64(math.Ceil(fsize)), nil
return DiskSize(math.Ceil(fsize)), nil
}

return -1, fmt.Errorf("cannot parse disk size \"%s\"", *size)
return -1, fmt.Errorf("cannot parse disk size \"%s\"", size)
}

func formatDiskSize(size int64) string {
// FormatDiskSize turns a number of bytes into a disk size string.
func FormatDiskSize(size DiskSize) string {
if size < 0 {
return ""
}
Expand Down
39 changes: 19 additions & 20 deletions proxmox/types/disk_size_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,36 @@ func TestParseDiskSize(t *testing.T) {

tests := []struct {
name string
size *string
size string
want int64
wantErr bool
}{
{"handle null size", nil, 0, false},
{"parse TB", StrPtr("2TB"), 2199023255552, false},
{"parse T", StrPtr("2T"), 2199023255552, false},
{"parse fraction T", StrPtr("2.2T"), 2418925581108, false},
{"parse GB", StrPtr("2GB"), 2147483648, false},
{"parse G", StrPtr("2G"), 2147483648, false},
{"parse M", StrPtr("2048M"), 2147483648, false},
{"parse MB", StrPtr("2048MB"), 2147483648, false},
{"parse MiB", StrPtr("2048MiB"), 2147483648, false},
{"parse K", StrPtr("1K"), 1024, false},
{"parse KB", StrPtr("2KB"), 2048, false},
{"parse KiB", StrPtr("4KiB"), 4096, false},
{"parse no units as bytes", StrPtr("12345"), 12345, false},
{"error on bad format string", StrPtr("20l8G"), -1, true},
{"error on unknown unit string", StrPtr("2048W"), -1, true},
{"error on arbitrary string", StrPtr("something"), -1, true},
{"parse TB", "2TB", 2199023255552, false},
{"parse T", "2T", 2199023255552, false},
{"parse fraction T", "2.2T", 2418925581108, false},
{"parse GB", "2GB", 2147483648, false},
{"parse G", "2G", 2147483648, false},
{"parse M", "2048M", 2147483648, false},
{"parse MB", "2048MB", 2147483648, false},
{"parse MiB", "2048MiB", 2147483648, false},
{"parse K", "1K", 1024, false},
{"parse KB", "2KB", 2048, false},
{"parse KiB", "4KiB", 4096, false},
{"parse no units as bytes", "12345", 12345, false},
{"error on bad format string", "20l8G", -1, true},
{"error on unknown unit string", "2048W", -1, true},
{"error on arbitrary string", "something", -1, true},
}
for _, test := range tests {
tt := test
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := parseDiskSize(tt.size)
got, err := ParseDiskSize(tt.size)
if (err != nil) != tt.wantErr {
t.Errorf("parseDiskSize() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
if int64(got) != tt.want {
t.Errorf("parseDiskSize() got = %v, want %v", got, tt.want)
}
})
Expand All @@ -73,7 +72,7 @@ func TestFormatDiskSize(t *testing.T) {
tt := test
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := formatDiskSize(tt.size); got != tt.want {
if got := FormatDiskSize(DiskSize(tt.size)); got != tt.want {
t.Errorf("formatDiskSize() = %v, want %v", got, tt.want)
}
})
Expand Down
29 changes: 29 additions & 0 deletions proxmoxtf/resource/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"

"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/vms"
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
)

func getBIOSValidator() schema.SchemaValidateDiagFunc {
Expand Down Expand Up @@ -186,6 +187,29 @@ func getFileIDValidator() schema.SchemaValidateDiagFunc {
})
}

//nolint:unused
func getFileSizeValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(func(i interface{}, k string) ([]string, []error) {
v, ok := i.(string)
var es []error

if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return nil, es
}

if v != "" {
_, err := types.ParseDiskSize(v)
if err != nil {
es = append(es, fmt.Errorf("expected %s to be a valid file size (100, 1M, 1G), got %s", k, v))
return nil, es
}
}

return []string{}, es
})
}

func getKeyboardLayoutValidator() schema.SchemaValidateDiagFunc {
return validation.ToDiagFunc(validation.StringInSlice([]string{
"da",
Expand Down Expand Up @@ -506,6 +530,11 @@ func getDiskDatastores(vm *vms.GetResponseData, d *schema.ResourceData) []string
datastoresSet[fileIDParts[0]] = 1
}

if vm.EFIDisk != nil {
fileIDParts := strings.Split(vm.EFIDisk.FileVolume, ":")
datastoresSet[fileIDParts[0]] = 1
}

datastores := []string{}
for datastore := range datastoresSet {
datastores = append(datastores, datastore)
Expand Down
Loading