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 // CPUDevice is CPU device type
cpuDev cpuDev
// memoryDevice is memory device type
memoryDev
) )
type memoryDevice struct {
slot int
sizeMB int
}
// Set sets an hypervisor type based on the input string. // Set sets an hypervisor type based on the input string.
func (hType *HypervisorType) Set(value string) error { func (hType *HypervisorType) Set(value string) error {
switch value { switch value {

View File

@ -7,6 +7,7 @@ package virtcontainers
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -39,12 +40,15 @@ type CPUDevice struct {
type QemuState struct { type QemuState struct {
Bridges []Bridge Bridges []Bridge
// HotpluggedCPUs is the list of CPUs that were hot-added // HotpluggedCPUs is the list of CPUs that were hot-added
HotpluggedVCPUs []CPUDevice HotpluggedVCPUs []CPUDevice
UUID string HotpluggedMemory int
UUID string
} }
// qemu is an Hypervisor interface implementation for the Linux qemu hypervisor. // qemu is an Hypervisor interface implementation for the Linux qemu hypervisor.
type qemu struct { type qemu struct {
vmConfig Resources
config HypervisorConfig config HypervisorConfig
qmpMonitorCh qmpChannel qmpMonitorCh qmpChannel
@ -169,6 +173,7 @@ func (q *qemu) init(sandbox *Sandbox) error {
return err return err
} }
q.vmConfig = sandbox.config.VMConfig
q.config = sandbox.config.HypervisorConfig q.config = sandbox.config.HypervisorConfig
q.sandbox = sandbox q.sandbox = sandbox
q.arch = newQemuArch(q.config) 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) 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) hostMemKb, err := getHostMemorySizeKb(procMemInfo)
if err != nil { 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 { 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) memMb := uint64(q.config.DefaultMemSz)
if sandboxConfig.VMConfig.Memory > 0 { if q.vmConfig.Memory > 0 {
memMb = uint64(sandboxConfig.VMConfig.Memory) memMb = uint64(q.vmConfig.Memory)
} }
return q.arch.memoryTopology(memMb, hostMemMb), nil return q.arch.memoryTopology(memMb, hostMemMb), nil
@ -271,7 +283,7 @@ func (q *qemu) createSandbox(sandboxConfig SandboxConfig) error {
smp := q.cpuTopology() smp := q.cpuTopology()
memory, err := q.memoryTopology(sandboxConfig) memory, err := q.memoryTopology()
if err != nil { if err != nil {
return err 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 // TODO: find a way to remove dependency of deviceDrivers lib @weizhang555
device := devInfo.(deviceDrivers.VFIODevice) device := devInfo.(deviceDrivers.VFIODevice)
return nil, q.hotplugVFIODevice(device, op) return nil, q.hotplugVFIODevice(device, op)
case memoryDev:
memdev := devInfo.(*memoryDevice)
return nil, q.hotplugMemory(memdev, op)
default: default:
return nil, fmt.Errorf("cannot hotplug device: unsupported device type '%v'", devType) 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) 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 { func (q *qemu) pauseSandbox() error {
return q.togglePauseSandbox(true) return q.togglePauseSandbox(true)
} }

View File

@ -167,15 +167,11 @@ func TestQemuMemoryTopology(t *testing.T) {
MaxMem: memMax, MaxMem: memMax,
} }
vmConfig := Resources{ q.vmConfig = Resources{
Memory: uint(mem), Memory: uint(mem),
} }
sandboxConfig := SandboxConfig{ memory, err := q.memoryTopology()
VMConfig: vmConfig,
}
memory, err := q.memoryTopology(sandboxConfig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -323,3 +319,29 @@ func TestQemuQemuPath(t *testing.T) {
assert.Error(err) assert.Error(err)
assert.Equal(path, "") 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)
}