[lxc-devel] [lxd/master] VM: Add support for live shrinking of limits.memory

tomponline on Github lxc-bot at linuxcontainers.org
Wed Sep 30 09:34:30 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 424 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200930/73438d44/attachment.bin>
-------------- next part --------------
From ae4618cf9461a9e1def2fdf9196cc55f8cb3e841 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 30 Sep 2020 10:28:29 +0100
Subject: [PATCH 1/2] lxd/instance/drivers/qmp/monitor: Adds
 GetBalloonSizeBytes and SetBalloonSizeBytes

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance/drivers/qmp/monitor.go | 38 +++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/lxd/instance/drivers/qmp/monitor.go b/lxd/instance/drivers/qmp/monitor.go
index fb4bacf2eb..11c011da38 100644
--- a/lxd/instance/drivers/qmp/monitor.go
+++ b/lxd/instance/drivers/qmp/monitor.go
@@ -322,3 +322,41 @@ func (m *Monitor) GetCPUs() ([]int, error) {
 
 	return pids, nil
 }
+
+// GetBalloonSizeBytes returns the current size of the memory balloon in bytes.
+func (m *Monitor) GetBalloonSizeBytes() (int64, error) {
+	respRaw, err := m.qmp.Run([]byte("{'execute': 'query-balloon'}"))
+	if err != nil {
+		m.Disconnect()
+		return -1, ErrMonitorDisconnect
+	}
+
+	// Process the response.
+	var respDecoded struct {
+		Return struct {
+			Actual int64 `json:"actual"`
+		} `json:"return"`
+	}
+
+	err = json.Unmarshal(respRaw, &respDecoded)
+	if err != nil {
+		return -1, ErrMonitorBadReturn
+	}
+
+	return respDecoded.Return.Actual, nil
+}
+
+// SetBalloonSizeBytes sets the size of the memory balloon in bytes.
+func (m *Monitor) SetBalloonSizeBytes(sizeBytes int64) error {
+	respRaw, err := m.qmp.Run([]byte(fmt.Sprintf("{'execute': 'balloon', 'arguments': {'value': %d}}", sizeBytes)))
+	if err != nil {
+		m.Disconnect()
+		return ErrMonitorDisconnect
+	}
+
+	if string(respRaw) != `{"return": {}}` {
+		return ErrMonitorBadReturn
+	}
+
+	return nil
+}

From 1023637b7bab9ac02540100ecb633bbbb12a4087 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 30 Sep 2020 10:29:02 +0100
Subject: [PATCH 2/2] lxd/instance/drivers/driver/qemu: Adds live shrinking of
 memory

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/instance/drivers/driver_qemu.go | 82 +++++++++++++++++++++++++++--
 1 file changed, 79 insertions(+), 3 deletions(-)

diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index 8572365fb2..c1cf5c91f2 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -2659,7 +2659,9 @@ func (vm *qemu) Rename(newName string) error {
 
 // Update the instance config.
 func (vm *qemu) Update(args db.InstanceArgs, userRequested bool) error {
-	// Only user.* keys can be changed on a running VM
+	// Only certain can be changed on a running VM.
+	liveUpdateKeys := []string{"limits.memory"}
+
 	if vm.IsRunning() {
 		if args.Config == nil {
 			args.Config = map[string]string{}
@@ -2720,8 +2722,8 @@ func (vm *qemu) Update(args db.InstanceArgs, userRequested bool) error {
 		}
 
 		for _, key := range changedConfig {
-			if !strings.HasPrefix(key, "user.") {
-				return fmt.Errorf("Only user.* keys can be updated on running VMs")
+			if !strings.HasPrefix(key, "user.") && !shared.StringInSlice(key, liveUpdateKeys) {
+				return fmt.Errorf("Key %q cannot be updated when VM is running", key)
 			}
 		}
 
@@ -2733,6 +2735,19 @@ func (vm *qemu) Update(args db.InstanceArgs, userRequested bool) error {
 			}
 		}
 
+		for _, key := range changedConfig {
+			value := vm.expandedConfig[key]
+
+			if key == "limits.memory" {
+				err = vm.updateMemoryLimit(value)
+				if err != nil {
+					if err != nil {
+						return errors.Wrapf(err, "Failed updating memory limit")
+					}
+				}
+			}
+		}
+
 		err = vm.state.Cluster.Transaction(func(tx *db.ClusterTx) error {
 			object, err := tx.GetInstance(vm.project, vm.name)
 			if err != nil {
@@ -3070,6 +3085,67 @@ func (vm *qemu) Update(args db.InstanceArgs, userRequested bool) error {
 	return nil
 }
 
+// updateMemoryLimit live updates the VM's memory limit by shrinking the balloon device.
+// Only memory shrinking is supported at this time.
+func (vm *qemu) updateMemoryLimit(newLimit string) error {
+	if shared.IsTrue(vm.expandedConfig["limits.memory.hugepages"]) {
+		return fmt.Errorf("Cannot live update memory limit when using huge pages")
+	}
+
+	// Check new size string is valid and convert to bytes.
+	newSizeBytes, err := units.ParseByteSizeString(newLimit)
+	if err != nil {
+		return errors.Wrapf(err, "Invalid memory size")
+	}
+
+	// Connect to the monitor.
+	monitor, err := qmp.Connect(vm.monitorPath(), qemuSerialChardevName, vm.getMonitorEventHandler())
+	if err != nil {
+		return err // The VM isn't running as no monitor socket available.
+	}
+
+	curSizeBytes, err := monitor.GetBalloonSizeBytes()
+	if err != nil {
+		return err
+	}
+
+	if curSizeBytes == newSizeBytes {
+		return nil
+	} else if curSizeBytes < newSizeBytes {
+		return fmt.Errorf("Cannot increase memory size when VM is running")
+	}
+
+	// Shrink balloon device.
+	err = monitor.SetBalloonSizeBytes(newSizeBytes)
+	if err != nil {
+		return err
+	}
+
+	// Shrinking the balloon can take time, so poll the actual balloon size to check it has shrunk within 1%
+	// of the target size, which we then take as success (it may still continue to shrink closer to target).
+	for i := 0; i < 5; i++ {
+		curSizeBytes, err = monitor.GetBalloonSizeBytes()
+		if err != nil {
+			return err
+		}
+
+		var diff int64
+		if curSizeBytes < newSizeBytes {
+			diff = newSizeBytes - curSizeBytes
+		} else {
+			diff = curSizeBytes - newSizeBytes
+		}
+
+		if diff <= (newSizeBytes / 100) {
+			return nil // We reached to within 1% of our target size.
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	return fmt.Errorf("Failed setting memory to %d bytes (currently %d bytes) as it was taking too long", newSizeBytes, curSizeBytes)
+}
+
 func (vm *qemu) updateDevices(removeDevices deviceConfig.Devices, addDevices deviceConfig.Devices, updateDevices deviceConfig.Devices, oldExpandedDevices deviceConfig.Devices) error {
 	isRunning := vm.IsRunning()
 


More information about the lxc-devel mailing list