-
-
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 all commits
467d3dd
d5297c0
feb605f
534edff
2c2f804
cdbf94f
79ddf1a
dd7326a
a6fa6bf
08f9f9d
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -10,7 +10,9 @@ | |||||
| "context" | ||||||
| "errors" | ||||||
| "fmt" | ||||||
| "reflect" | ||||||
| "regexp" | ||||||
| "slices" | ||||||
| "sort" | ||||||
| "strconv" | ||||||
| "strings" | ||||||
|
|
@@ -336,7 +338,6 @@ | |||||
| Type: schema.TypeList, | ||||||
| Description: "The disks", | ||||||
| Optional: true, | ||||||
| ForceNew: true, | ||||||
| DefaultFunc: func() (any, error) { | ||||||
| return []any{ | ||||||
| map[string]any{ | ||||||
|
|
@@ -377,7 +378,6 @@ | |||||
| Type: schema.TypeInt, | ||||||
| Description: "The rootfs size in gigabytes", | ||||||
| Optional: true, | ||||||
| ForceNew: true, | ||||||
| Default: dvDiskSize, | ||||||
| ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)), | ||||||
| }, | ||||||
|
|
@@ -666,6 +666,7 @@ | |||||
| Type: schema.TypeList, | ||||||
| Description: "A mount point", | ||||||
| Optional: true, | ||||||
| ForceNew: true, | ||||||
| Elem: &schema.Resource{ | ||||||
| Schema: map[string]*schema.Schema{ | ||||||
| mkMountPointACL: { | ||||||
|
|
@@ -725,12 +726,14 @@ | |||||
| Description: "Volume size (only used for volume mount points)", | ||||||
| Optional: true, | ||||||
| Default: dvMountPointSize, | ||||||
| ForceNew: true, | ||||||
| ValidateDiagFunc: validators.FileSize(), | ||||||
| }, | ||||||
| mkMountPointVolume: { | ||||||
| Type: schema.TypeString, | ||||||
| Description: "Volume, device or directory to mount into the container", | ||||||
| Required: true, | ||||||
| ForceNew: true, | ||||||
| DiffSuppressFunc: func(_, oldVal, newVal string, _ *schema.ResourceData) bool { | ||||||
| // For *new* volume mounts PVE returns an actual volume ID which is saved in the stare, | ||||||
| // so on reapply the provider will try override it:" | ||||||
|
|
@@ -1053,6 +1056,96 @@ | |||||
| return strconv.Itoa(newValue.(int)) != d.Id() | ||||||
| }, | ||||||
| ), | ||||||
| // create a customdiff that checks each mount point | ||||||
| customdiff.ForceNewIf( | ||||||
| mkMountPoint, | ||||||
| func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { | ||||||
| oldRaw, newRaw := d.GetChange(mkMountPoint) | ||||||
| // compare all oldRaw and newRaw entries | ||||||
| oldList, _ := oldRaw.([]interface{}) | ||||||
| newList, _ := newRaw.([]interface{}) | ||||||
|
|
||||||
| if oldList == nil { | ||||||
| oldList = []interface{}{} | ||||||
| } | ||||||
| if newList == nil { | ||||||
| newList = []interface{}{} | ||||||
| } | ||||||
|
|
||||||
| for i := 0; i < len(oldList); i++ { | ||||||
| if len(newList)-1 < i { | ||||||
| return true | ||||||
| } | ||||||
| // compare old and new list entries and call ForceNew on the correspondig string | ||||||
| // make a deep comparison | ||||||
| oldMap, _ := oldList[i].(map[string]interface{}) | ||||||
| // the volume entry of oldMap does containe the storage volume PLUS the identifier, which we have to strip | ||||||
| volumeEntry, ok := oldMap["volume"] | ||||||
| if ok { | ||||||
| volumeParts := strings.Split(volumeEntry.(string), ":") | ||||||
| if len(volumeParts) >= 1 { | ||||||
| oldMap["volume"] = volumeParts[0] | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| newMap, _ := newList[i].(map[string]interface{}) | ||||||
| // deep compare | ||||||
| if !reflect.DeepEqual(oldMap, newMap) { | ||||||
| // get key that is different and call ForceNew | ||||||
| for _, v := range oldMap { | ||||||
| d.ForceNew(fmt.Sprintf("%s.%d.%s", mkMountPoint, i, v)) | ||||||
| } | ||||||
| return true | ||||||
| } | ||||||
| } | ||||||
| return false | ||||||
|
|
||||||
| }, | ||||||
| ), | ||||||
| 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{}{} | ||||||
| } | ||||||
|
|
||||||
| minDrives := min(len(oldList), len(newList)) | ||||||
|
|
||||||
| for i := range minDrives { | ||||||
| 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 | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if oldSize > newSize { | ||||||
| _ = d.ForceNew(fmt.Sprintf("%s.%d.%s", mkDisk, i, mkDiskSize)) | ||||||
| return true | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return false | ||||||
| }, | ||||||
| ), | ||||||
| ), | ||||||
| Importer: &schema.ResourceImporter{ | ||||||
| StateContext: func(_ context.Context, d *schema.ResourceData, _ any) ([]*schema.ResourceData, error) { | ||||||
|
|
@@ -3019,7 +3112,23 @@ | |||||
| mountOptions := diskBlock[mkDiskMountOptions].([]any) | ||||||
| quota := types.CustomBool(diskBlock[mkDiskQuota].(bool)) | ||||||
| replicate := types.CustomBool(diskBlock[mkDiskReplicate].(bool)) | ||||||
|
|
||||||
| oldSize := containerConfig.RootFS.Size | ||||||
| size := types.DiskSizeFromGigabytes(int64(diskBlock[mkDiskSize].(int))) | ||||||
| // we should never reach this point. The `plan` should recreate the container, not update it, if the old size is larger. | ||||||
| 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
|
||||||
| return diag.Errorf("New disk size (%s) has to be greater the current disk (%s)!", oldSize, size) | ||||||
| } | ||||||
|
|
||||||
| if !ptr.Eq(oldSize, size) { | ||||||
| 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 | ||||||
|
|
@@ -3029,15 +3138,26 @@ | |||||
| 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) { | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.