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