-
-
Notifications
You must be signed in to change notification settings - Fork 231
feature(lxc): enable root disk resize without recreate. recreate container if mount points are added, deleted or changed in size #2321
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
467d3dd
d5297c0
feb605f
534edff
2c2f804
cdbf94f
79ddf1a
dd7326a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,7 @@ import ( | |||||
| "errors" | ||||||
| "fmt" | ||||||
| "regexp" | ||||||
| "slices" | ||||||
| "sort" | ||||||
| "strconv" | ||||||
| "strings" | ||||||
|
|
@@ -333,7 +334,6 @@ func Container() *schema.Resource { | |||||
| Type: schema.TypeList, | ||||||
| Description: "The disks", | ||||||
| Optional: true, | ||||||
| ForceNew: true, | ||||||
| DefaultFunc: func() (interface{}, error) { | ||||||
| return []interface{}{ | ||||||
| map[string]interface{}{ | ||||||
|
|
@@ -374,7 +374,6 @@ func Container() *schema.Resource { | |||||
| Type: schema.TypeInt, | ||||||
| Description: "The rootfs size in gigabytes", | ||||||
| Optional: true, | ||||||
| ForceNew: true, | ||||||
| Default: dvDiskSize, | ||||||
| ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)), | ||||||
| }, | ||||||
|
|
@@ -1025,6 +1024,55 @@ func Container() *schema.Resource { | |||||
| return strconv.Itoa(newValue.(int)) != d.Id() | ||||||
| }, | ||||||
| ), | ||||||
| customdiff.ForceNewIf( | ||||||
| mkDisk, | ||||||
| func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool { | ||||||
| oldRaw, newRaw := d.GetChange(mkDisk) | ||||||
| oldList, _ := oldRaw.([]interface{}) | ||||||
| newList, _ := newRaw.([]interface{}) | ||||||
|
|
||||||
| if oldList == nil { | ||||||
| oldList = []interface{}{} | ||||||
| } | ||||||
| if newList == nil { | ||||||
| newList = []interface{}{} | ||||||
| } | ||||||
|
|
||||||
| // fmt.Printf("ALEX: ALL DISK: old: %v ---- new: %v\n", old, new) | ||||||
|
|
||||||
| minDrives := min(len(oldList), len(newList)) | ||||||
|
|
||||||
| for i := range minDrives { | ||||||
bpg marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| oldSize := dvDiskSize | ||||||
| newSize := dvDiskSize | ||||||
| if i < len(oldList) && oldList[i] != nil { | ||||||
| if om, ok := oldList[i].(map[string]interface{}); ok { | ||||||
| if v, ok := om[mkDiskSize].(int); ok { | ||||||
| oldSize = v | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if i < len(newList) && newList[i] != nil { | ||||||
| if nm, ok := newList[i].(map[string]interface{}); ok { | ||||||
| if v, ok := nm[mkDiskSize].(int); ok { | ||||||
| newSize = v | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // fmt.Printf("ALEX: check DISK %v: %v vs %v\n", i, oldSize, newSize) | ||||||
| if oldSize > newSize { | ||||||
|
|
||||||
| // fmt.Print("ALEX: check DISK: new is smaller\n") | ||||||
| _ = d.ForceNew(fmt.Sprintf("%s.%d.%s", mkDisk, i, mkDiskSize)) | ||||||
| return true // <-- this is not working | ||||||
| } | ||||||
| } | ||||||
lexxxel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| return false | ||||||
| }, | ||||||
| ), | ||||||
lexxxel marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| ), | ||||||
| Importer: &schema.ResourceImporter{ | ||||||
| StateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { | ||||||
|
|
@@ -1765,7 +1813,7 @@ func containerCreateCustom(ctx context.Context, d *schema.ResourceData, m interf | |||||
| } | ||||||
|
|
||||||
| diskSize := diskBlock[mkDiskSize].(int) | ||||||
| if diskDatastoreID != "" && (diskSize != dvDiskSize || len(mountPoints) > 0) { | ||||||
| if diskDatastoreID != "" && (diskSize != dvDiskSize || len(mountPoints) > 0 || len(diskMountOptions) > 0) { | ||||||
| // This is a special case where the rootfs size is set to a non-default value at creation time. | ||||||
| // see https://pve.proxmox.com/pve-docs/chapter-pct.html#_storage_backed_mount_points | ||||||
| rootFS = &containers.CustomRootFS{ | ||||||
|
|
@@ -2985,17 +3033,42 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) | |||||
| } | ||||||
|
|
||||||
| rootFS := &containers.CustomRootFS{} | ||||||
| // Disk ID for the rootfs is always 0 | ||||||
| diskID := 0 | ||||||
| vmID := d.Get(mkVMID).(int) | ||||||
| rootFS.Volume = diskBlock[mkDiskDatastoreID].(string) | ||||||
| rootFS.Volume = getContainerDiskVolume(rootFS.Volume, vmID, diskID) | ||||||
| containerConfig, e := containerAPI.GetContainer(ctx) | ||||||
| if e != nil { | ||||||
| if errors.Is(e, api.ErrResourceDoesNotExist) { | ||||||
| d.SetId("") | ||||||
| return nil | ||||||
| } | ||||||
| return diag.FromErr(e) | ||||||
| } | ||||||
|
|
||||||
| if containerConfig.RootFS == nil { | ||||||
| return diag.Errorf("RootFS information of container malformed.") | ||||||
| } | ||||||
| rootFS.Volume = containerConfig.RootFS.Volume | ||||||
|
|
||||||
| acl := types.CustomBool(diskBlock[mkDiskACL].(bool)) | ||||||
| mountOptions := diskBlock[mkDiskMountOptions].([]interface{}) | ||||||
| quota := types.CustomBool(diskBlock[mkDiskQuota].(bool)) | ||||||
| replicate := types.CustomBool(diskBlock[mkDiskReplicate].(bool)) | ||||||
|
|
||||||
| oldSize := containerConfig.RootFS.Size | ||||||
| size := types.DiskSizeFromGigabytes(int64(diskBlock[mkDiskSize].(int))) | ||||||
| if *oldSize > *size { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a potential nil pointer dereference here.
Suggested change
|
||||||
| // TODO: we should never reach this point. The `plan` should recreate the container, not update it. | ||||||
| d.SetId("") | ||||||
| return diag.Errorf("New disk size (%s) has to be greater the current disk (%s)!", oldSize, size) | ||||||
lexxxel marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| } | ||||||
|
|
||||||
| if oldSize != size { | ||||||
lexxxel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
lexxxel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| err = containerAPI.ResizeContainerDisk(ctx, &containers.ResizeRequestBody{ | ||||||
| Disk: "rootfs", | ||||||
| Size: size.String(), | ||||||
| }) | ||||||
| if err != nil { | ||||||
| return diag.FromErr(err) | ||||||
lexxxel marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
| rootFS.ACL = &acl | ||||||
| rootFS.Quota = "a | ||||||
|
|
@@ -3005,15 +3078,26 @@ func containerUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) | |||||
| mountOptionsStrings := make([]string, 0, len(mountOptions)) | ||||||
|
|
||||||
| for _, option := range mountOptions { | ||||||
| mountOptionsStrings = append(mountOptionsStrings, option.(string)) | ||||||
| optionString := option.(string) | ||||||
| mountOptionsStrings = append(mountOptionsStrings, optionString) | ||||||
| } | ||||||
|
|
||||||
| // Always set, including empty, to allow clearing mount options | ||||||
| rootFS.MountOptions = &mountOptionsStrings | ||||||
|
|
||||||
| updateBody.RootFS = rootFS | ||||||
| // To compare contents regardless of order, we can sort them. | ||||||
| // The schema already uses a suppress func for order, so we should be consistent. | ||||||
| sort.Strings(mountOptionsStrings) | ||||||
| currentMountOptions := containerConfig.RootFS.MountOptions | ||||||
| currentMountOptionsSorted := []string{} | ||||||
| if currentMountOptions != nil { | ||||||
| currentMountOptionsSorted = append(currentMountOptionsSorted, *currentMountOptions...) | ||||||
| } | ||||||
| sort.Strings(currentMountOptionsSorted) | ||||||
| if !slices.Equal(mountOptionsStrings, currentMountOptionsSorted) { | ||||||
| rebootRequired = true | ||||||
| } | ||||||
|
|
||||||
| rebootRequired = true | ||||||
| updateBody.RootFS = rootFS | ||||||
| } | ||||||
|
|
||||||
| if d.HasChange(mkFeatures) { | ||||||
|
|
@@ -3534,10 +3618,6 @@ func parseImportIDWithNodeName(id string) (string, string, error) { | |||||
| return nodeName, id, nil | ||||||
| } | ||||||
|
|
||||||
| func getContainerDiskVolume(rawVolume string, vmID int, diskID int) string { | ||||||
| return fmt.Sprintf("%s:vm-%d-disk-%d", rawVolume, vmID, diskID) | ||||||
| } | ||||||
|
|
||||||
| func skipDnsDiffIfEmpty(k, oldValue, newValue string, d *schema.ResourceData) bool { | ||||||
| dnsDataKey := mkInitialization + ".0." + mkInitializationDNS | ||||||
| if k == dnsDataKey+".#" { | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.