Skip to content

Commit

Permalink
Merge pull request #2034 from vasileknik76/disk-resize
Browse files Browse the repository at this point in the history
Disk resize command
  • Loading branch information
AkihiroSuda authored Dec 1, 2023
2 parents a8c703b + 65c36a7 commit b453ded
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 15 deletions.
65 changes: 62 additions & 3 deletions cmd/limactl/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ func newDiskCommand() *cobra.Command {
$ limactl disk ls
Delete a disk:
$ limactl disk delete DISK`,
$ limactl disk delete DISK
Resize a disk:
$ limactl disk resize DISK --size SIZE`,
SilenceUsage: true,
SilenceErrors: true,
}
Expand All @@ -35,6 +38,7 @@ func newDiskCommand() *cobra.Command {
newDiskListCommand(),
newDiskDeleteCommand(),
newDiskUnlockCommand(),
newDiskResizeCommand(),
)
return diskCommand
}
Expand Down Expand Up @@ -171,7 +175,7 @@ func diskListAction(cmd *cobra.Command, args []string) error {
}

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

if len(disks) == 0 {
logrus.Warn("No disk found. Run `limactl disk create DISK --size SIZE` to create a disk.")
Expand All @@ -183,7 +187,7 @@ func diskListAction(cmd *cobra.Command, args []string) error {
logrus.WithError(err).Errorf("disk %q does not exist?", diskName)
continue
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", disk.Name, units.BytesSize(float64(disk.Size)), disk.Dir, disk.Instance)
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)
}

return w.Flush()
Expand Down Expand Up @@ -331,3 +335,58 @@ func diskUnlockAction(_ *cobra.Command, args []string) error {
}
return nil
}

func newDiskResizeCommand() *cobra.Command {
diskResizeCommand := &cobra.Command{
Use: "resize DISK",
Example: `
Resize a disk:
$ limactl disk resize DISK --size SIZE`,
Short: "Resize existing Lima disk",
Args: WrapArgsError(cobra.ExactArgs(1)),
RunE: diskResizeAction,
}
diskResizeCommand.Flags().String("size", "", "Disk size")
_ = diskResizeCommand.MarkFlagRequired("size")
return diskResizeCommand
}

func diskResizeAction(cmd *cobra.Command, args []string) error {
size, err := cmd.Flags().GetString("size")
if err != nil {
return err
}

diskSize, err := units.RAMInBytes(size)
if err != nil {
return err
}

diskName := args[0]
disk, err := store.InspectDisk(diskName)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("disk %q does not exists", diskName)
}
return err
}

// Shrinking can cause a disk failure
if diskSize < disk.Size {
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)))
}

if disk.Instance != "" {
inst, err := store.Inspect(disk.Instance)
if err == nil {
if inst.Status == store.StatusRunning {
return fmt.Errorf("cannot resize disk %q used by running instance %q. Please stop the VM instance", diskName, disk.Instance)
}
}
}
if err := qemu.ResizeDataDisk(disk.Dir, disk.Format, int(diskSize)); err != nil {
return fmt.Errorf("failed to resize disk %q: %w", diskName, err)
}
logrus.Infof("Resized disk %q (%q)", diskName, disk.Dir)
return nil
}
9 changes: 9 additions & 0 deletions hack/test-templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ if [[ -n ${CHECKS["restart"]} ]]; then
limactl stop "$NAME"
sleep 3

if [[ -n ${CHECKS["disk"]} ]]; then
INFO "Resize disk and verify that partition and fs size are increased"
limactl disk resize data --size 11G
fi

export ftp_proxy=my.proxy:8021
INFO "Restarting \"$NAME\""
limactl start "$NAME"
Expand All @@ -325,6 +330,10 @@ if [[ -n ${CHECKS["restart"]} ]]; then
ERROR "Disk does not persist across restarts"
exit 1
fi
if ! limactl shell "$NAME" sh -c 'df -h /mnt/lima-data/ --output=size | grep -q 11G'; then
ERROR "Disk FS does not resized after restart"
exit 1
fi
fi
fi

Expand Down
13 changes: 13 additions & 0 deletions pkg/cidata/cidata.TEMPLATE.d/boot/05-lima-disks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,17 @@ for i in $(seq 0 $((LIMA_CIDATA_DISKS - 1))); do

mkdir -p "/mnt/lima-${DISK_NAME}"
mount -t $FORMAT_FSTYPE "/dev/${DEVICE_NAME}1" "/mnt/lima-${DISK_NAME}"
if command -v growpart >/dev/null 2>&1 && command -v resize2fs >/dev/null 2>&1; then
growpart "/dev/${DEVICE_NAME}" 1 || true
# Only resize when filesystem is in a healthy state
if command -v "fsck.$FORMAT_FSTYPE" -f -p "/dev/disk/by-label/lima-${DISK_NAME}"; then
if [[ $FORMAT_FSTYPE == "ext2" || $FORMAT_FSTYPE == "ext3" || $FORMAT_FSTYPE == "ext4" ]]; then
resize2fs "/dev/disk/by-label/lima-${DISK_NAME}" || true
elif [ "$FORMAT_FSTYPE" == "xfs" ]; then
xfs_growfs "/dev/disk/by-label/lima-${DISK_NAME}" || true
else
echo >&2 "WARNING: unknown fs '$FORMAT_FSTYPE'. FS will not be grew up automatically"
fi
fi
fi
done
11 changes: 11 additions & 0 deletions pkg/qemu/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ func CreateDataDisk(dir, format string, size int) error {
return nil
}

func ResizeDataDisk(dir, format string, size int) error {
dataDisk := filepath.Join(dir, filenames.DataDisk)

args := []string{"resize", "-f", format, dataDisk, strconv.Itoa(size)}
cmd := exec.Command("qemu-img", args...)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to run %v: %q: %w", cmd.Args, string(out), err)
}
return nil
}

func newQmpClient(cfg Config) (*qmp.SocketMonitor, error) {
qmpSock := filepath.Join(cfg.InstanceDir, filenames.QMPSock)
qmpClient, err := qmp.NewSocketMonitor("unix", qmpSock, 5*time.Second)
Expand Down
26 changes: 14 additions & 12 deletions pkg/store/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
type Disk struct {
Name string `json:"name"`
Size int64 `json:"size"`
Format string `json:"format"`
Dir string `json:"dir"`
Instance string `json:"instance"`
InstanceDir string `json:"instanceDir"`
Expand All @@ -37,7 +38,7 @@ func InspectDisk(diskName string) (*Disk, error) {
return nil, err
}

disk.Size, err = inspectDiskSize(dataDisk)
disk.Size, disk.Format, err = inspectDisk(dataDisk)
if err != nil {
return nil, err
}
Expand All @@ -57,32 +58,33 @@ func InspectDisk(diskName string) (*Disk, error) {
return disk, nil
}

// inspectDiskSize attempts to inspect the disk size by itself,
// and falls back to inspectDiskSizeWithQemuImg on an error.
func inspectDiskSize(fName string) (int64, error) {
// inspectDisk attempts to inspect the disk size and format by itself,
// and falls back to inspectDiskWithQemuImg on an error.
func inspectDisk(fName string) (int64, string, error) {
f, err := os.Open(fName)
if err != nil {
return inspectDiskSizeWithQemuImg(fName)
return inspectDiskWithQemuImg(fName)
}
defer f.Close()
img, err := qcow2reader.Open(f)
if err != nil {
return inspectDiskSizeWithQemuImg(fName)
return inspectDiskWithQemuImg(fName)
}
sz := img.Size()
if sz < 0 {
return inspectDiskSizeWithQemuImg(fName)
return inspectDiskWithQemuImg(fName)
}
return sz, nil

return sz, string(img.Type()), nil
}

// inspectDiskSizeWithQemuImg invokes `qemu-img` binary to inspect the disk size.
func inspectDiskSizeWithQemuImg(fName string) (int64, error) {
// inspectDiskSizeWithQemuImg invokes `qemu-img` binary to inspect the disk size and format.
func inspectDiskWithQemuImg(fName string) (int64, string, error) {
info, err := imgutil.GetInfo(fName)
if err != nil {
return -1, err
return -1, "", err
}
return info.VSize, nil
return info.VSize, info.Format, nil
}

func (d *Disk) Lock(instanceDir string) error {
Expand Down

0 comments on commit b453ded

Please sign in to comment.