The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7955

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
Adds support for shrinking the memory limit of VM (when hugepages are not being used) using `lxc config set limits.memory`.
From ae4618cf9461a9e1def2fdf9196cc55f8cb3e841 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@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.parr...@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.parr...@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.parr...@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()
 
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to