Skip to content

Commit 82e1f2b

Browse files
authored
Merge pull request #116 from UpCloudLtd/feat/storage-autoresize
Feat/storage autoresize
2 parents 7aaa08e + 32ce1cf commit 82e1f2b

4 files changed

Lines changed: 90 additions & 10 deletions

File tree

docs/upctl_storage_modify.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ upctl storage modify "My Storage" --size 25
1616
### Options
1717

1818
```
19-
--title string A short, informational description.
20-
--size int Size of the storage (GiB).
21-
--backup-time string The time when to create a backup in HH:MM. Empty value means no backups.
22-
--backup-interval string The interval of the backup.
23-
Available: daily,mon,tue,wed,thu,fri,sat,sun
24-
--backup-retention int How long to store the backups in days. The accepted range is 1-1095.
25-
-h, --help help for modify
19+
--title string A short, informational description.
20+
--size int Size of the storage (GiB).
21+
--backup-time string The time when to create a backup in HH:MM. Empty value means no backups.
22+
--backup-interval string The interval of the backup.
23+
Available: daily,mon,tue,wed,thu,fri,sat,sun
24+
--backup-retention int How long to store the backups in days. The accepted range is 1-1095.
25+
--enable-filesystem-autoresize[=true] Enable automatic resize of partition and filesystem when modifying storage size. Note that before the resize attempt is made, backup of the storage will be taken. If the resize attempt fails, the backup will be used to restore the storage and then deleted. If the resize attempt succeeds, backup will be kept. Taking and keeping backups incure costs..
26+
-h, --help help for modify
2627
```
2728

2829
### Options inherited from parent commands

internal/commands/storage/modify.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/UpCloudLtd/upcloud-cli/internal/commands"
88
"github.com/UpCloudLtd/upcloud-cli/internal/completion"
9+
"github.com/UpCloudLtd/upcloud-cli/internal/config"
910
"github.com/UpCloudLtd/upcloud-cli/internal/output"
1011
"github.com/UpCloudLtd/upcloud-cli/internal/resolver"
1112
"github.com/UpCloudLtd/upcloud-cli/internal/ui"
@@ -20,7 +21,8 @@ type modifyCommand struct {
2021
*commands.BaseCommand
2122
completion.Storage
2223
resolver.CachingStorage
23-
params modifyParams
24+
params modifyParams
25+
autoresizePartitionFilesystem config.OptionalBoolean
2426
}
2527

2628
type modifyParams struct {
@@ -66,6 +68,7 @@ func (s *modifyCommand) InitCommand() {
6668
flagSet.StringVar(&s.params.backupTime, "backup-time", s.params.backupTime, "The time when to create a backup in HH:MM. Empty value means no backups.")
6769
flagSet.StringVar(&s.params.backupInterval, "backup-interval", "", "The interval of the backup.\nAvailable: daily,mon,tue,wed,thu,fri,sat,sun")
6870
flagSet.IntVar(&s.params.backupRetention, "backup-retention", 0, "How long to store the backups in days. The accepted range is 1-1095.")
71+
config.AddEnableOrDisableFlag(flagSet, &s.autoresizePartitionFilesystem, false, "filesystem-autoresize", "automatic resize of partition and filesystem when modifying storage size. Note that before the resize attempt is made, backup of the storage will be taken. If the resize attempt fails, the backup will be used to restore the storage and then deleted. If the resize attempt succeeds, backup will be kept. Taking and keeping backups incure costs.")
6972

7073
s.AddFlags(flagSet)
7174
}
@@ -129,6 +132,10 @@ func setBackupFields(storageUUID string, p modifyParams, service service.Storage
129132

130133
// Execute implements commands.MultipleArgumentCommand
131134
func (s *modifyCommand) Execute(exec commands.Executor, uuid string) (output.Output, error) {
135+
if s.autoresizePartitionFilesystem.Value() && s.params.Size == 0 {
136+
return nil, fmt.Errorf("filesystem autoresize is enabled, but new size is not specified")
137+
}
138+
132139
svc := exec.Storage()
133140
msg := fmt.Sprintf("modifing storage %v", uuid)
134141
logline := exec.NewLogEntry(msg)
@@ -149,8 +156,35 @@ func (s *modifyCommand) Execute(exec commands.Executor, uuid string) (output.Out
149156
return nil, err
150157
}
151158

159+
// If autoresize is not enabled, then just consider the whole operation done and output the modify API call response
160+
if !s.autoresizePartitionFilesystem.Value() {
161+
logline.SetMessage(fmt.Sprintf("%s: done", msg))
162+
logline.MarkDone()
163+
164+
return output.OnlyMarshaled{Value: res}, nil
165+
}
166+
167+
logline.SetMessage(fmt.Sprintf("%s: resizing partition and filesystem", msg))
168+
backup, err := svc.ResizeStorageFilesystem(&request.ResizeStorageFilesystemRequest{UUID: uuid})
169+
170+
// If there was an error during resize attempt, we consider the overall modify operation successful and just log warning about failed resize
171+
if err != nil {
172+
logline.SetMessage(ui.LiveLogEntryWarningColours.Sprintf("%s: done, but partition and filesystem resize failed; storage was restored using backed taken right before resize attempt", msg))
173+
logline.SetDetails(err.Error(), "error: ")
174+
logline.MarkDone()
175+
return output.OnlyMarshaled{Value: res}, nil
176+
}
177+
152178
logline.SetMessage(fmt.Sprintf("%s: done", msg))
153179
logline.MarkDone()
154180

155-
return output.OnlyMarshaled{Value: res}, nil
181+
out := struct {
182+
upcloud.StorageDetails
183+
LatestResizeBackup string `json:"latest_resize_backup,omitempty"`
184+
}{
185+
StorageDetails: *res,
186+
LatestResizeBackup: backup.UUID,
187+
}
188+
189+
return output.OnlyMarshaled{Value: out}, nil
156190
}

internal/commands/storage/modify_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,46 @@ func TestModifyCommandExistingBackupRule(t *testing.T) {
230230
})
231231
}
232232
}
233+
234+
func TestModifyCommandAutoresize(t *testing.T) {
235+
t.Run("modifying storage size with filesystem autoresize enabled", func(t *testing.T) {
236+
conf := config.New()
237+
testCmd := ModifyCommand()
238+
mService := new(smock.Service)
239+
UUID := "some_storage_id"
240+
241+
mGetDetailsResponse := upcloud.StorageDetails{
242+
Storage: upcloud.Storage{Size: 45},
243+
BackupRule: &upcloud.BackupRule{},
244+
}
245+
246+
mModifyResponse := upcloud.StorageDetails{
247+
Storage: upcloud.Storage{
248+
Size: 50,
249+
},
250+
}
251+
252+
mResizeResponse := upcloud.ResizeStorageFilesystemBackup{
253+
UUID: "resize_backup",
254+
}
255+
256+
conf.Service = internal.Wrapper{Service: mService}
257+
mService.On("ModifyStorage", &request.ModifyStorageRequest{UUID: UUID, Size: 50}).Return(&mModifyResponse, nil)
258+
mService.On("ResizeStorageFilesystem", &request.ResizeStorageFilesystemRequest{UUID: UUID}).Return(&mResizeResponse, nil)
259+
mService.On("GetStorageDetails", &request.GetStorageDetailsRequest{UUID: UUID}).Return(&mGetDetailsResponse, nil)
260+
261+
c := commands.BuildCommand(testCmd, nil, conf)
262+
err := c.Cobra().Flags().Parse([]string{"--size", "50", "--enable-filesystem-autoresize"})
263+
assert.NoError(t, err)
264+
265+
output, err := c.(commands.MultipleArgumentCommand).Execute(commands.NewExecutor(conf, mService, flume.New("test")), UUID)
266+
assert.NoError(t, err)
267+
mService.AssertNumberOfCalls(t, "ModifyStorage", 1)
268+
mService.AssertNumberOfCalls(t, "ResizeStorageFilesystem", 1)
269+
270+
json, err := output.MarshalJSON()
271+
assert.NoError(t, err)
272+
assert.Contains(t, string(json), "latest_resize_backup")
273+
assert.Contains(t, string(json), "resize_backup")
274+
})
275+
}

internal/ui/log.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ var (
2727
DisableLiveRendering: !terminal.IsStdoutTerminal(),
2828
Colours: liveLogDefaultColours,
2929
}
30+
// LiveLogEntryWarningColours specifies the colour used for warnings in LiveLog
31+
// TODO: remove cross-package dependency and make those private
32+
LiveLogEntryWarningColours = text.FgHiYellow
3033
// LiveLogEntryErrorColours specifies the colour used for errors in LiveLog
31-
// TODO: remove cross-package dependency and make this private
3234
LiveLogEntryErrorColours = text.FgHiRed
3335
)
3436

0 commit comments

Comments
 (0)