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

Add support for "G", "M" etc. disk size unit spec #86

Closed
bpg opened this issue Jul 9, 2022 · 8 comments
Closed

Add support for "G", "M" etc. disk size unit spec #86

bpg opened this issue Jul 9, 2022 · 8 comments
Labels
✨ enhancement New feature or request stale

Comments

@bpg
Copy link
Owner

bpg commented Jul 9, 2022

Currently the unit of virtual_environment_vm.disk.size parameter is "gigabytes". Add ability to specify different units using G/GB, M/MB notation.

│ Error: Incorrect attribute value type
│ 
│   on main.tf line 117, in resource "proxmox_virtual_environment_vm" "example_vm":
│  117:     size         = "300G"
│ 
│ Inappropriate value for attribute "size": a number is required.

@bpg bpg added the ✨ enhancement New feature or request label Jul 9, 2022
@bpg
Copy link
Owner Author

bpg commented Jul 9, 2022

Hmmmm... The parsing code already exists:

func parseDiskSize(size *string) (int, error) {
var diskSize int
var err error
if size != nil {
if strings.HasSuffix(*size, "T") {
diskSize, err = strconv.Atoi(strings.TrimSuffix(*size, "T"))
if err != nil {
return -1, err
}
diskSize = int(math.Ceil(float64(diskSize) * 1024))
} else if strings.HasSuffix(*size, "G") {
diskSize, err = strconv.Atoi(strings.TrimSuffix(*size, "G"))
if err != nil {
return -1, err
}
} else if strings.HasSuffix(*size, "M") {
diskSize, err = strconv.Atoi(strings.TrimSuffix(*size, "M"))
if err != nil {
return -1, err
}
diskSize = int(math.Ceil(float64(diskSize) / 1024))
} else {
return -1, fmt.Errorf("Cannot parse storage size \"%s\"", *size)
}
}
return diskSize, err
}

@github-actions
Copy link
Contributor

github-actions bot commented Jan 6, 2023

Marking this issue as stale due to inactivity in the past 180 days. This helps us focus on the active issues. If this issue is reproducible with the latest version of the provider, please comment. If this issue receives no comments in the next 30 days it will automatically be closed. If this issue was automatically closed and you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thank you!

@github-actions github-actions bot added the stale label Jan 6, 2023
@bpg bpg added the 👶 good first issue Good for newcomers label Jan 6, 2023
@bpg
Copy link
Owner Author

bpg commented Jan 6, 2023

If anyone would like to contribute, this seems to be an easy one.

@github-actions github-actions bot removed the stale label Jan 7, 2023
@bpg bpg removed the 👶 good first issue Good for newcomers label Jan 18, 2023
@zeddD1abl0
Copy link
Contributor

@bpg I'm happy to pick this one up, but I have a question regarding this code; why is the size stored in gigabytes? I understand that most HDDs would be of gigabyte size, but this function fails when you try to clone a VM with an efi disk that is measured in kilobytes.

Specifically, I have a number of templates that have a 528K EFI disk associated with them, and the provider chokes while trying to decode this drive because "K" is not a supported size extension. I attempted to quickly submit a pull request for this, but the tests for this function appear to expect the size to be gigabytes:

func TestParseDiskSize(t *testing.T) {
t.Parallel()
tests := []struct {
name string
size *string
want int
wantErr bool
}{
{"handle null size", nil, 0, false},
{"parse terabytes", strPtr("2T"), 2048, false},
{"parse gigabytes", strPtr("2G"), 2, false},
{"parse megabytes", strPtr("2048M"), 2, false},
{"error on arbitrary string", strPtr("something"), -1, true},
{"error on missing unit", strPtr("12345"), -1, true},
}
for _, test := range tests {
tt := test
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
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 {
t.Errorf("ParseDiskSize() got = %v, want %v", got, tt.want)
}
})
}
}

I tried to investigate how and why you'd go with that as a standard, and I can see you use qemu-img to grow the HDD to the requested size in this line:

`qemu-img resize -f "$file_format" "$file_path_tmp" "${disk_size}G"`,

Is that it? Have I missed anything in the logic? This appears to prevent the creation of anything that's not at least 1 gigabyte in size. After following through the logic, it seems to round up to the nearest whole gigabyte if M, G, or T, is specified, and dies if the value is anything but this.

If the above is all correct, I'd propose to base off of bytes, as qemu-img uses bytes by default, and expanding to bytes is super simple. Then, it's simply a case of changing the line above to remove the "G", changing the util function to multiply out to bytes, and adding in the newly expected values ("MB", "GB", "TB", "KB", "B", etc).

You could plausibly go to Kilobytes as the base unit, extending your limit from 512 Petabytes per disk to 512 Exabytes per disk...

As an aside, I notice that there is no way to provide an EFI disk for a VM. If I get the chance, I'll probably include that in another pull request and get it added.

@bpg
Copy link
Owner Author

bpg commented Apr 11, 2023

Hey @zeddD1abl0 👋🏼 Thanks for taking a stab on this!

In addition to reference you've mentioned, there are also a few special cases in PVE where a size is expected to be set as an integer without any units. So my guess that was a main driving factor behind unification of all disk size values as "gigabytes" in the original provider.

For example, https://pve.proxmox.com/pve-docs/pve-admin-guide.html#qm_configuration, see "the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume" for params --ide[n], --sata[n], etc.
In provider we're using this approach in

diskDevice.FileVolume = fmt.Sprintf("%s:%d", datastoreID, size)
or in
Volume: fmt.Sprintf("%s:%d", diskDatastoreID, diskSize),

So I'm not entirely sure how to proceed with this story. It seems like using units less than 1G won't work in all use cases. I would probably do something similar to this:

  • if no units specified, then assume the value is in gigabytes;
  • if units are specified, then convert to gigabytes rounding up;
  • if the result value is less than 1G, then throw an error;

@zeddD1abl0
Copy link
Contributor

zeddD1abl0 commented Apr 11, 2023

Cool. I can work with that. How do you feel about using Regex to do the matching? The current check allows for 80TG, which will break the process. I'd propose the following Regex-based solution to prevent erroneous sizes from being provided. This forces the value to be in the format of "<numbers><possibly a space><size designation>"

re := regexp.MustCompile(`(?i)^([\d\.]+)\s?(m|mb|mib|g|gb|gib|t|tb|tib)?$`)
matches := re.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\"", *size)
}
switch strings.ToLower(matches[2]) {
case "m", "mb", "mib":
	return int(math.Ceil(fsize / 1024)) , nil
case "g", "gb", "gib":
	return int(math.Ceil(fsize)), nil
case "t", "tb", "tib":
	return int(math.Ceil(fsize * 1024)), nil
case "p", "pb", "pib":
	return int(math.Ceil(fsize * 1024 * 1024)), nil
default:
	return int(math.Ceil(fsize)), nil
}
}

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

If you'd prefer me to follow the current format, I can do that too. I just wanted to double-check before I got too far ahead of myself.

@bpg
Copy link
Owner Author

bpg commented Apr 11, 2023

I'm fine with regex :), just make sure it is cached and not compiled on each func invocation.

Also I'm not sure if we should go into the rabbit hole of MB vs MiB etc, as they mean different things. Have everything represented in power on 2 seems reasonable to me.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 9, 2023

Marking this issue as stale due to inactivity in the past 180 days. This helps us focus on the active issues. If this issue is reproducible with the latest version of the provider, please comment. If this issue receives no comments in the next 30 days it will automatically be closed. If this issue was automatically closed and you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thank you!

@github-actions github-actions bot added the stale label Oct 9, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Nov 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ enhancement New feature or request stale
Projects
Development

Successfully merging a pull request may close this issue.

2 participants