Skip to content

Commit

Permalink
Merge pull request #1158 from stgraber/vm
Browse files Browse the repository at this point in the history
Allow live resize of VM disks
  • Loading branch information
tych0 committed Aug 27, 2024
2 parents 5757692 + de3ea2e commit 73aa654
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 22 deletions.
1 change: 1 addition & 0 deletions internal/server/device/config/device_runconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type MountEntryItem struct {
PassNo int // Used by fsck(8) to determine the order in which filesystem checks are done at boot time. Defaults to zero (don't fsck) if not present.
OwnerShift string // Ownership shifting mode, use constants MountOwnerShiftNone, MountOwnerShiftStatic or MountOwnerShiftDynamic.
Limits *DiskLimits // Disk limits.
Size int64 // Expected disk size in bytes.
}

// RootFSEntryItem represents the root filesystem options for an Instance.
Expand Down
20 changes: 20 additions & 0 deletions internal/server/device/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,26 @@ func (d *disk) Update(oldDevices deviceConfig.Devices, isRunning bool) error {
d.logger.Warn("Could not apply quota because disk is in use, deferring until next start")
} else if err != nil {
return err
} else if d.inst.Type() == instancetype.VM && d.inst.IsRunning() {
// Get the disk size in bytes.
size, err := units.ParseByteSizeString(newRootDiskDeviceSize)
if err != nil {
return err
}

// Notify to reload disk size.
runConf := deviceConfig.RunConfig{}
runConf.Mounts = []deviceConfig.MountEntryItem{
{
DevName: d.name,
Size: size,
},
}

err = d.inst.DeviceEventHandler(&runConf)
if err != nil {
return err
}
}
}
}
Expand Down
20 changes: 15 additions & 5 deletions internal/server/instance/drivers/driver_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -8129,7 +8129,7 @@ func (d *qemu) DeviceEventHandler(runConf *deviceConfig.RunConfig) error {

// Handle disk reconfiguration.
for _, mount := range runConf.Mounts {
if mount.Limits == nil {
if mount.Limits == nil && mount.Size == 0 {
continue
}

Expand All @@ -8142,10 +8142,20 @@ func (d *qemu) DeviceEventHandler(runConf *deviceConfig.RunConfig) error {
// Figure out the QEMU device ID.
devID := fmt.Sprintf("%s%s", qemuDeviceIDPrefix, linux.PathNameEncode(mount.DevName))

// Apply the limits.
err = m.SetBlockThrottle(devID, int(mount.Limits.ReadBytes), int(mount.Limits.WriteBytes), int(mount.Limits.ReadIOps), int(mount.Limits.WriteIOps))
if err != nil {
return fmt.Errorf("Failed applying limits for disk device %q: %w", mount.DevName, err)
if mount.Limits != nil {
// Apply the limits.
err = m.SetBlockThrottle(devID, int(mount.Limits.ReadBytes), int(mount.Limits.WriteBytes), int(mount.Limits.ReadIOps), int(mount.Limits.WriteIOps))
if err != nil {
return fmt.Errorf("Failed applying limits for disk device %q: %w", mount.DevName, err)
}
}

if mount.Size > 0 {
// Update the size.
err = m.UpdateBlockSize(strings.SplitN(devID, "-", 2)[1])
if err != nil {
return fmt.Errorf("Failed updating disk size %q: %w", mount.DevName, err)
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions internal/server/instance/drivers/qmp/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,24 @@ func (m *Monitor) Eject(id string) error {
return nil
}

// UpdateBlockSize updates the size of a disk.
func (m *Monitor) UpdateBlockSize(id string) error {
var args struct {
NodeName string `json:"node-name"`
Size int64 `json:"size"`
}

args.NodeName = id
args.Size = 1

err := m.run("block_resize", args, nil)
if err != nil {
return err
}

return nil
}

// SetBlockThrottle applies an I/O limit on a disk.
func (m *Monitor) SetBlockThrottle(id string, bytesRead int, bytesWrite int, iopsRead int, iopsWrite int) error {
var args struct {
Expand Down
34 changes: 24 additions & 10 deletions internal/server/storage/drivers/driver_lvm_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,18 +435,14 @@ func (d *lvm) createLogicalVolumeSnapshot(vgName string, srcVol Volume, snapVol
// If clustered, we need to acquire exclusive write access for this operation.
if d.clustered && !makeThinLv {
parent, _, _ := api.GetParentAndSnapshotName(srcVol.Name())
volDevPath := d.lvmDevPath(d.config["lvm.vg_name"], srcVol.volType, srcVol.contentType, parent)
parentVol := NewVolume(d, d.Name(), srcVol.volType, srcVol.contentType, parent, srcVol.config, srcVol.poolConfig)

if util.PathExists(volDevPath) {
_, err := subprocess.TryRunCommand("lvchange", "--activate", "ey", "--ignoreactivationskip", volDevPath)
if err != nil {
return "", fmt.Errorf("Failed to acquire exclusive lock on LVM logical volume %q: %w", volDevPath, err)
}

defer func() {
_, _ = subprocess.TryRunCommand("lvchange", "--activate", "sy", "--ignoreactivationskip", volDevPath)
}()
release, err := d.acquireExclusive(parentVol)
if err != nil {
return "", err
}

defer release()
}

_, err = subprocess.TryRunCommand("lvcreate", args...)
Expand All @@ -466,6 +462,24 @@ func (d *lvm) createLogicalVolumeSnapshot(vgName string, srcVol Volume, snapVol
return targetVolDevPath, nil
}

// acquireExclusive switches a volume lock to exclusive mode.
func (d *lvm) acquireExclusive(vol Volume) (func(), error) {
volDevPath := d.lvmDevPath(d.config["lvm.vg_name"], vol.volType, vol.contentType, vol.name)

if !d.clustered || !util.PathExists(volDevPath) {
return func() {}, nil
}

_, err := subprocess.TryRunCommand("lvchange", "--activate", "ey", "--ignoreactivationskip", volDevPath)
if err != nil {
return nil, fmt.Errorf("Failed to acquire exclusive lock on LVM logical volume %q: %w", volDevPath, err)
}

return func() {
_, _ = subprocess.TryRunCommand("lvchange", "--activate", "sy", "--ignoreactivationskip", volDevPath)
}, nil
}

// removeLogicalVolume removes a logical volume.
func (d *lvm) removeLogicalVolume(volDevPath string) error {
_, err := subprocess.TryRunCommand("lvremove", "-f", volDevPath)
Expand Down
19 changes: 16 additions & 3 deletions internal/server/storage/drivers/driver_lvm_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,14 @@ func (d *lvm) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, op
return err
}
} else if sizeBytes > oldSizeBytes {
// Get exclusive mode if active.
release, err := d.acquireExclusive(vol)
if err != nil {
return err
}

defer release()

// Grow block device first.
err = d.resizeLogicalVolume(volDevPath, sizeBytes)
if err != nil {
Expand Down Expand Up @@ -528,12 +536,17 @@ func (d *lvm) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, op
if sizeBytes < oldSizeBytes {
return fmt.Errorf("Block volumes cannot be shrunk: %w", ErrCannotBeShrunk)
}
}

if inUse {
return ErrInUse // We don't allow online resizing of block volumes.
}
// Get exclusive mode.
release, err := d.acquireExclusive(vol)
if err != nil {
return err
}

defer release()

// Resize the block device.
err = d.resizeLogicalVolume(volDevPath, sizeBytes)
if err != nil {
return err
Expand Down
4 changes: 0 additions & 4 deletions internal/server/storage/drivers/driver_zfs_volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1792,10 +1792,6 @@ func (d *zfs) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, op
if sizeBytes < oldVolSizeBytes {
return fmt.Errorf("Block volumes cannot be shrunk: %w", ErrCannotBeShrunk)
}

if inUse {
return ErrInUse // We don't allow online resizing of block volumes.
}
}

err = d.setDatasetProperties(d.dataset(vol, false), fmt.Sprintf("volsize=%d", sizeBytes))
Expand Down

0 comments on commit 73aa654

Please sign in to comment.