Skip to content

Commit 65c36a7

Browse files
committed
add disk resize command
Signed-off-by: Nikita Vasilchenko <[email protected]>
1 parent cac97ed commit 65c36a7

File tree

5 files changed

+109
-15
lines changed

5 files changed

+109
-15
lines changed

cmd/limactl/disk.go

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ func newDiskCommand() *cobra.Command {
2626
$ limactl disk ls
2727
2828
Delete a disk:
29-
$ limactl disk delete DISK`,
29+
$ limactl disk delete DISK
30+
31+
Resize a disk:
32+
$ limactl disk resize DISK --size SIZE`,
3033
SilenceUsage: true,
3134
SilenceErrors: true,
3235
}
@@ -35,6 +38,7 @@ func newDiskCommand() *cobra.Command {
3538
newDiskListCommand(),
3639
newDiskDeleteCommand(),
3740
newDiskUnlockCommand(),
41+
newDiskResizeCommand(),
3842
)
3943
return diskCommand
4044
}
@@ -171,7 +175,7 @@ func diskListAction(cmd *cobra.Command, args []string) error {
171175
}
172176

173177
w := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
174-
fmt.Fprintln(w, "NAME\tSIZE\tDIR\tIN-USE-BY")
178+
fmt.Fprintln(w, "NAME\tSIZE\tFORMAT\tDIR\tIN-USE-BY")
175179

176180
if len(disks) == 0 {
177181
logrus.Warn("No disk found. Run `limactl disk create DISK --size SIZE` to create a disk.")
@@ -183,7 +187,7 @@ func diskListAction(cmd *cobra.Command, args []string) error {
183187
logrus.WithError(err).Errorf("disk %q does not exist?", diskName)
184188
continue
185189
}
186-
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", disk.Name, units.BytesSize(float64(disk.Size)), disk.Dir, disk.Instance)
190+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", disk.Name, units.BytesSize(float64(disk.Size)), disk.Format, disk.Dir, disk.Instance)
187191
}
188192

189193
return w.Flush()
@@ -331,3 +335,58 @@ func diskUnlockAction(_ *cobra.Command, args []string) error {
331335
}
332336
return nil
333337
}
338+
339+
func newDiskResizeCommand() *cobra.Command {
340+
diskResizeCommand := &cobra.Command{
341+
Use: "resize DISK",
342+
Example: `
343+
Resize a disk:
344+
$ limactl disk resize DISK --size SIZE`,
345+
Short: "Resize existing Lima disk",
346+
Args: WrapArgsError(cobra.ExactArgs(1)),
347+
RunE: diskResizeAction,
348+
}
349+
diskResizeCommand.Flags().String("size", "", "Disk size")
350+
_ = diskResizeCommand.MarkFlagRequired("size")
351+
return diskResizeCommand
352+
}
353+
354+
func diskResizeAction(cmd *cobra.Command, args []string) error {
355+
size, err := cmd.Flags().GetString("size")
356+
if err != nil {
357+
return err
358+
}
359+
360+
diskSize, err := units.RAMInBytes(size)
361+
if err != nil {
362+
return err
363+
}
364+
365+
diskName := args[0]
366+
disk, err := store.InspectDisk(diskName)
367+
if err != nil {
368+
if errors.Is(err, fs.ErrNotExist) {
369+
return fmt.Errorf("disk %q does not exists", diskName)
370+
}
371+
return err
372+
}
373+
374+
// Shrinking can cause a disk failure
375+
if diskSize < disk.Size {
376+
return fmt.Errorf("specified size %q is less than the current disk size %q. Disk shrinking is currently unavailable", units.BytesSize(float64(diskSize)), units.BytesSize(float64(disk.Size)))
377+
}
378+
379+
if disk.Instance != "" {
380+
inst, err := store.Inspect(disk.Instance)
381+
if err == nil {
382+
if inst.Status == store.StatusRunning {
383+
return fmt.Errorf("cannot resize disk %q used by running instance %q. Please stop the VM instance", diskName, disk.Instance)
384+
}
385+
}
386+
}
387+
if err := qemu.ResizeDataDisk(disk.Dir, disk.Format, int(diskSize)); err != nil {
388+
return fmt.Errorf("failed to resize disk %q: %w", diskName, err)
389+
}
390+
logrus.Infof("Resized disk %q (%q)", diskName, disk.Dir)
391+
return nil
392+
}

hack/test-templates.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,11 @@ if [[ -n ${CHECKS["restart"]} ]]; then
301301
limactl stop "$NAME"
302302
sleep 3
303303

304+
if [[ -n ${CHECKS["disk"]} ]]; then
305+
INFO "Resize disk and verify that partition and fs size are increased"
306+
limactl disk resize data --size 11G
307+
fi
308+
304309
export ftp_proxy=my.proxy:8021
305310
INFO "Restarting \"$NAME\""
306311
limactl start "$NAME"
@@ -325,6 +330,10 @@ if [[ -n ${CHECKS["restart"]} ]]; then
325330
ERROR "Disk does not persist across restarts"
326331
exit 1
327332
fi
333+
if ! limactl shell "$NAME" sh -c 'df -h /mnt/lima-data/ --output=size | grep -q 11G'; then
334+
ERROR "Disk FS does not resized after restart"
335+
exit 1
336+
fi
328337
fi
329338
fi
330339

pkg/cidata/cidata.TEMPLATE.d/boot/05-lima-disks.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,17 @@ for i in $(seq 0 $((LIMA_CIDATA_DISKS - 1))); do
3030

3131
mkdir -p "/mnt/lima-${DISK_NAME}"
3232
mount -t $FORMAT_FSTYPE "/dev/${DEVICE_NAME}1" "/mnt/lima-${DISK_NAME}"
33+
if command -v growpart >/dev/null 2>&1 && command -v resize2fs >/dev/null 2>&1; then
34+
growpart "/dev/${DEVICE_NAME}" 1 || true
35+
# Only resize when filesystem is in a healthy state
36+
if command -v "fsck.$FORMAT_FSTYPE" -f -p "/dev/disk/by-label/lima-${DISK_NAME}"; then
37+
if [[ $FORMAT_FSTYPE == "ext2" || $FORMAT_FSTYPE == "ext3" || $FORMAT_FSTYPE == "ext4" ]]; then
38+
resize2fs "/dev/disk/by-label/lima-${DISK_NAME}" || true
39+
elif [ "$FORMAT_FSTYPE" == "xfs" ]; then
40+
xfs_growfs "/dev/disk/by-label/lima-${DISK_NAME}" || true
41+
else
42+
echo >&2 "WARNING: unknown fs '$FORMAT_FSTYPE'. FS will not be grew up automatically"
43+
fi
44+
fi
45+
fi
3346
done

pkg/qemu/qemu.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ func CreateDataDisk(dir, format string, size int) error {
137137
return nil
138138
}
139139

140+
func ResizeDataDisk(dir, format string, size int) error {
141+
dataDisk := filepath.Join(dir, filenames.DataDisk)
142+
143+
args := []string{"resize", "-f", format, dataDisk, strconv.Itoa(size)}
144+
cmd := exec.Command("qemu-img", args...)
145+
if out, err := cmd.CombinedOutput(); err != nil {
146+
return fmt.Errorf("failed to run %v: %q: %w", cmd.Args, string(out), err)
147+
}
148+
return nil
149+
}
150+
140151
func newQmpClient(cfg Config) (*qmp.SocketMonitor, error) {
141152
qmpSock := filepath.Join(cfg.InstanceDir, filenames.QMPSock)
142153
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSock, 5*time.Second)

pkg/store/disk.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
type Disk struct {
1616
Name string `json:"name"`
1717
Size int64 `json:"size"`
18+
Format string `json:"format"`
1819
Dir string `json:"dir"`
1920
Instance string `json:"instance"`
2021
InstanceDir string `json:"instanceDir"`
@@ -37,7 +38,7 @@ func InspectDisk(diskName string) (*Disk, error) {
3738
return nil, err
3839
}
3940

40-
disk.Size, err = inspectDiskSize(dataDisk)
41+
disk.Size, disk.Format, err = inspectDisk(dataDisk)
4142
if err != nil {
4243
return nil, err
4344
}
@@ -57,32 +58,33 @@ func InspectDisk(diskName string) (*Disk, error) {
5758
return disk, nil
5859
}
5960

60-
// inspectDiskSize attempts to inspect the disk size by itself,
61-
// and falls back to inspectDiskSizeWithQemuImg on an error.
62-
func inspectDiskSize(fName string) (int64, error) {
61+
// inspectDisk attempts to inspect the disk size and format by itself,
62+
// and falls back to inspectDiskWithQemuImg on an error.
63+
func inspectDisk(fName string) (int64, string, error) {
6364
f, err := os.Open(fName)
6465
if err != nil {
65-
return inspectDiskSizeWithQemuImg(fName)
66+
return inspectDiskWithQemuImg(fName)
6667
}
6768
defer f.Close()
6869
img, err := qcow2reader.Open(f)
6970
if err != nil {
70-
return inspectDiskSizeWithQemuImg(fName)
71+
return inspectDiskWithQemuImg(fName)
7172
}
7273
sz := img.Size()
7374
if sz < 0 {
74-
return inspectDiskSizeWithQemuImg(fName)
75+
return inspectDiskWithQemuImg(fName)
7576
}
76-
return sz, nil
77+
78+
return sz, string(img.Type()), nil
7779
}
7880

79-
// inspectDiskSizeWithQemuImg invokes `qemu-img` binary to inspect the disk size.
80-
func inspectDiskSizeWithQemuImg(fName string) (int64, error) {
81+
// inspectDiskSizeWithQemuImg invokes `qemu-img` binary to inspect the disk size and format.
82+
func inspectDiskWithQemuImg(fName string) (int64, string, error) {
8183
info, err := imgutil.GetInfo(fName)
8284
if err != nil {
83-
return -1, err
85+
return -1, "", err
8486
}
85-
return info.VSize, nil
87+
return info.VSize, info.Format, nil
8688
}
8789

8890
func (d *Disk) Lock(instanceDir string) error {

0 commit comments

Comments
 (0)