hypervisor/qemu: add memory hotplug support

So that we can add more memory to an existing guest.

Fixes: #469

Signed-off-by: Peng Tao <bergwolf@gmail.com>
This commit is contained in:
Peng Tao 2018-07-05 16:45:13 +08:00
parent 0646a39ff0
commit 66a3e812f2
3 changed files with 117 additions and 15 deletions

View File

@ -76,8 +76,16 @@ const (
// CPUDevice is CPU device type
cpuDev
// memoryDevice is memory device type
memoryDev
)
type memoryDevice struct {
slot int
sizeMB int
}
// Set sets an hypervisor type based on the input string.
func (hType *HypervisorType) Set(value string) error {
switch value {

View File

@ -7,6 +7,7 @@ package virtcontainers
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
@ -39,12 +40,15 @@ type CPUDevice struct {
type QemuState struct {
Bridges []Bridge
// HotpluggedCPUs is the list of CPUs that were hot-added
HotpluggedVCPUs []CPUDevice
UUID string
HotpluggedVCPUs []CPUDevice
HotpluggedMemory int
UUID string
}
// qemu is an Hypervisor interface implementation for the Linux qemu hypervisor.
type qemu struct {
vmConfig Resources
config HypervisorConfig
qmpMonitorCh qmpChannel
@ -169,6 +173,7 @@ func (q *qemu) init(sandbox *Sandbox) error {
return err
}
q.vmConfig = sandbox.config.VMConfig
q.config = sandbox.config.HypervisorConfig
q.sandbox = sandbox
q.arch = newQemuArch(q.config)
@ -204,20 +209,27 @@ func (q *qemu) cpuTopology() govmmQemu.SMP {
return q.arch.cpuTopology(q.config.DefaultVCPUs, q.config.DefaultMaxVCPUs)
}
func (q *qemu) memoryTopology(sandboxConfig SandboxConfig) (govmmQemu.Memory, error) {
func (q *qemu) hostMemMB() (uint64, error) {
hostMemKb, err := getHostMemorySizeKb(procMemInfo)
if err != nil {
return govmmQemu.Memory{}, fmt.Errorf("Unable to read memory info: %s", err)
return 0, fmt.Errorf("Unable to read memory info: %s", err)
}
if hostMemKb == 0 {
return govmmQemu.Memory{}, fmt.Errorf("Error host memory size 0")
return 0, fmt.Errorf("Error host memory size 0")
}
hostMemMb := uint64(float64(hostMemKb / 1024))
return hostMemKb / 1024, nil
}
func (q *qemu) memoryTopology() (govmmQemu.Memory, error) {
hostMemMb, err := q.hostMemMB()
if err != nil {
return govmmQemu.Memory{}, err
}
memMb := uint64(q.config.DefaultMemSz)
if sandboxConfig.VMConfig.Memory > 0 {
memMb = uint64(sandboxConfig.VMConfig.Memory)
if q.vmConfig.Memory > 0 {
memMb = uint64(q.vmConfig.Memory)
}
return q.arch.memoryTopology(memMb, hostMemMb), nil
@ -271,7 +283,7 @@ func (q *qemu) createSandbox(sandboxConfig SandboxConfig) error {
smp := q.cpuTopology()
memory, err := q.memoryTopology(sandboxConfig)
memory, err := q.memoryTopology()
if err != nil {
return err
}
@ -705,6 +717,9 @@ func (q *qemu) hotplugDevice(devInfo interface{}, devType deviceType, op operati
// TODO: find a way to remove dependency of deviceDrivers lib @weizhang555
device := devInfo.(deviceDrivers.VFIODevice)
return nil, q.hotplugVFIODevice(device, op)
case memoryDev:
memdev := devInfo.(*memoryDevice)
return nil, q.hotplugMemory(memdev, op)
default:
return nil, fmt.Errorf("cannot hotplug device: unsupported device type '%v'", devType)
}
@ -837,6 +852,63 @@ func (q *qemu) hotplugRemoveCPUs(amount uint32) (uint32, error) {
return amount, q.sandbox.storage.storeHypervisorState(q.sandbox.id, q.state)
}
func (q *qemu) hotplugMemory(memDev *memoryDevice, op operation) error {
if memDev.sizeMB < 0 {
return fmt.Errorf("cannot hotplug negative size (%d) memory", memDev.sizeMB)
}
// We do not support memory hot unplug.
if op == removeDevice {
return errors.New("cannot hot unplug memory device")
}
maxMem, err := q.hostMemMB()
if err != nil {
return err
}
// calculate current memory
currentMemory := int(q.config.DefaultMemSz)
if q.vmConfig.Memory > 0 {
currentMemory = int(q.vmConfig.Memory)
}
currentMemory += q.state.HotpluggedMemory
// Don't exceed the maximum amount of memory
if currentMemory+memDev.sizeMB > int(maxMem) {
return fmt.Errorf("Unable to hotplug %d MiB memory, the SB has %d MiB and the maximum amount is %d MiB",
memDev.sizeMB, currentMemory, q.config.DefaultMemSz)
}
return q.hotplugAddMemory(memDev)
}
func (q *qemu) hotplugAddMemory(memDev *memoryDevice) error {
// setup qmp channel if necessary
if q.qmpMonitorCh.qmp == nil {
qmp, err := q.qmpSetup()
if err != nil {
return err
}
q.qmpMonitorCh.qmp = qmp
defer func() {
qmp.Shutdown()
q.qmpMonitorCh.qmp = nil
}()
}
err := q.qmpMonitorCh.qmp.ExecHotplugMemory(q.qmpMonitorCh.ctx, "memory-backend-ram", "mem"+strconv.Itoa(memDev.slot), "", memDev.sizeMB)
if err != nil {
q.Logger().WithError(err).Error("hotplug memory")
return err
}
q.state.HotpluggedMemory += memDev.sizeMB
return q.sandbox.storage.storeHypervisorState(q.sandbox.id, q.state)
}
func (q *qemu) pauseSandbox() error {
return q.togglePauseSandbox(true)
}

View File

@ -167,15 +167,11 @@ func TestQemuMemoryTopology(t *testing.T) {
MaxMem: memMax,
}
vmConfig := Resources{
q.vmConfig = Resources{
Memory: uint(mem),
}
sandboxConfig := SandboxConfig{
VMConfig: vmConfig,
}
memory, err := q.memoryTopology(sandboxConfig)
memory, err := q.memoryTopology()
if err != nil {
t.Fatal(err)
}
@ -323,3 +319,29 @@ func TestQemuQemuPath(t *testing.T) {
assert.Error(err)
assert.Equal(path, "")
}
func TestHotplugRemoveMemory(t *testing.T) {
assert := assert.New(t)
qemuConfig := newQemuConfig()
q := &qemu{
config: qemuConfig,
}
_, err := q.hotplugRemoveDevice(&memoryDevice{0, 128}, memoryDev)
assert.Error(err)
}
func TestHotplugUnsupportedDeviceType(t *testing.T) {
assert := assert.New(t)
qemuConfig := newQemuConfig()
q := &qemu{
config: qemuConfig,
}
_, err := q.hotplugAddDevice(&memoryDevice{0, 128}, fsDev)
assert.Error(err)
_, err = q.hotplugRemoveDevice(&memoryDevice{0, 128}, fsDev)
assert.Error(err)
}