Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
hypervisor/qemu: add memory hotplug support
Browse files Browse the repository at this point in the history
So that we can add more memory to an existing guest.

Fixes: #469

Signed-off-by: Peng Tao <bergwolf@gmail.com>
  • Loading branch information
bergwolf committed Jul 9, 2018
1 parent 0646a39 commit 66a3e81
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 15 deletions.
8 changes: 8 additions & 0 deletions virtcontainers/hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
90 changes: 81 additions & 9 deletions virtcontainers/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package virtcontainers

import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
34 changes: 28 additions & 6 deletions virtcontainers/qemu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}

0 comments on commit 66a3e81

Please sign in to comment.