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

Commit

Permalink
clh: Add memory hotplug
Browse files Browse the repository at this point in the history
Request memory to resize memory to hypervisor.

Depends-on: github.com/kata-containers/tests#2413

Fixes: #2526

Signed-off-by: Jose Carlos Venegas Munoz <[email protected]>
  • Loading branch information
jcvenegas committed Mar 26, 2020
1 parent 2f94873 commit aab82f6
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 2 deletions.
71 changes: 69 additions & 2 deletions virtcontainers/clh.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ func (clh *cloudHypervisor) createSandbox(ctx context.Context, id string, networ
// Convert to int64 openApiClient only support int64
clh.vmconfig.Memory.Size = int64((utils.MemUnit(clh.config.MemorySize) * utils.MiB).ToBytes())
clh.vmconfig.Memory.File = "/dev/shm"
hostMemKb, err := getHostMemorySizeKb(procMemInfo)
if err != nil {
return nil
}

// OpenAPI only supports int64 values
clh.vmconfig.Memory.HotplugSize = int64((utils.MemUnit(hostMemKb) * utils.KiB).ToBytes())
// Set initial amount of cpu's for the virtual machine
clh.vmconfig.Cpus = chclient.CpusConfig{
// cast to int32, as openAPI has a limitation that it does not support unsigned values
Expand Down Expand Up @@ -425,8 +432,68 @@ func (clh *cloudHypervisor) hypervisorConfig() HypervisorConfig {
}

func (clh *cloudHypervisor) resizeMemory(reqMemMB uint32, memoryBlockSizeMB uint32, probe bool) (uint32, memoryDevice, error) {
clh.Logger().WithField("function", "resizeMemory").Warn("not supported")
return 0, memoryDevice{}, nil

// TODO: Add support for virtio-mem

if probe {
return 0, memoryDevice{}, errors.New("probe memory is not supported for cloud-hypervisor")
}

if reqMemMB == 0 {
// This is a corner case if requested to resize to 0 means something went really wrong.
return 0, memoryDevice{}, errors.New("Can not resize memory to 0")
}

info, err := clh.vmInfo()
if err != nil {
return 0, memoryDevice{}, err
}

currentMem := utils.MemUnit(info.Config.Memory.Size) * utils.Byte
newMem := utils.MemUnit(reqMemMB) * utils.MiB

// Early check to verify if boot memory is the same as requested
if currentMem == newMem {
clh.Logger().WithField("memory", reqMemMB).Debugf("VM already has requested memory")
return uint32(currentMem.ToMiB()), memoryDevice{}, nil
}

if currentMem > newMem {
clh.Logger().Warn("Remove memory is not supported, nothing to do")
return uint32(currentMem.ToMiB()), memoryDevice{}, nil
}

blockSize := utils.MemUnit(memoryBlockSizeMB) * utils.MiB
hotplugSize := (newMem - currentMem).AlignMem(blockSize)

// Update memory request to increase memory aligned block
alignedRequest := currentMem + hotplugSize
if newMem != alignedRequest {
clh.Logger().WithFields(log.Fields{"request": newMem, "aligned-request": alignedRequest}).Debug("aligning VM memory request")
newMem = alignedRequest
}

// Check if memory is the same as requested, a second check is done
// to consider the memory request now that is updated to be memory aligned
if currentMem == newMem {
clh.Logger().WithFields(log.Fields{"current-memory": currentMem, "new-memory": newMem}).Debug("VM already has requested memory(after alignment)")
return uint32(currentMem.ToMiB()), memoryDevice{}, nil
}

cl := clh.client()
ctx, cancelResize := context.WithTimeout(context.Background(), clhAPITimeout*time.Second)
defer cancelResize()

// OpenApi does not support uint64, convert to int64
resize := chclient.VmResize{DesiredRam: int64(newMem.ToBytes())}
clh.Logger().WithFields(log.Fields{"current-memory": currentMem, "new-memory": newMem}).Debug("updating VM memory")
if _, err = cl.VmResizePut(ctx, resize); err != nil {
clh.Logger().WithFields(log.Fields{"current-memory": currentMem, "new-memory": newMem}).Warnf("failed to update memory %s", openAPIClientError(err))
err = fmt.Errorf("Failed to resize memory from %d to %d: %s", currentMem, newMem, openAPIClientError(err))
return uint32(currentMem.ToMiB()), memoryDevice{}, openAPIClientError(err)
}

return uint32(newMem.ToMiB()), memoryDevice{sizeMB: int(hotplugSize.ToMiB())}, nil
}

func (clh *cloudHypervisor) resizeVCPUs(reqVCPUs uint32) (currentVCPUs uint32, newVCPUs uint32, err error) {
Expand Down
61 changes: 61 additions & 0 deletions virtcontainers/clh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ import (
"net/http"
"os"
"path/filepath"
"reflect"
"testing"

"github.com/kata-containers/runtime/virtcontainers/device/config"
"github.com/kata-containers/runtime/virtcontainers/persist"
chclient "github.com/kata-containers/runtime/virtcontainers/pkg/cloud-hypervisor/client"
"github.com/kata-containers/runtime/virtcontainers/utils"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

const (
FAIL = true
PASS = !FAIL
)

func newClhConfig() (HypervisorConfig, error) {

setupClh()
Expand Down Expand Up @@ -255,3 +262,57 @@ func TestClooudHypervisorStartSandbox(t *testing.T) {
err = clh.startSandbox(10)
assert.NoError(err)
}

func TestCloudHypervisorResizeMemory(t *testing.T) {
assert := assert.New(t)
clhConfig, err := newClhConfig()
type args struct {
reqMemMB uint32
memoryBlockSizeMB uint32
}
tests := []struct {
name string
args args
expectedMemDev memoryDevice
wantErr bool
}{
{"Resize to zero", args{0, 128}, memoryDevice{probe: false, sizeMB: 0}, FAIL},
{"Resize to aligned size", args{clhConfig.MemorySize + 128, 128}, memoryDevice{probe: false, sizeMB: 128}, PASS},
{"Resize to aligned size", args{clhConfig.MemorySize + 129, 128}, memoryDevice{probe: false, sizeMB: 256}, PASS},
{"Resize to NOT aligned size", args{clhConfig.MemorySize + 125, 128}, memoryDevice{probe: false, sizeMB: 128}, PASS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.NoError(err)
clh := cloudHypervisor{}

mockClient := &clhClientMock{}
mockClient.vmInfo.Config.Memory.Size = int64(utils.MemUnit(clhConfig.MemorySize) * utils.MiB)
mockClient.vmInfo.Config.Memory.HotplugSize = int64(40 * utils.GiB.ToBytes())

clh.APIClient = mockClient
clh.config = clhConfig

newMem, memDev, err := clh.resizeMemory(tt.args.reqMemMB, tt.args.memoryBlockSizeMB, false)

if (err != nil) != tt.wantErr {
t.Errorf("cloudHypervisor.resizeMemory() error = %v, expected to fail = %v", err, tt.wantErr)
return
}

if err != nil {
return
}

expectedMem := clhConfig.MemorySize + uint32(tt.expectedMemDev.sizeMB)

if newMem != expectedMem {
t.Errorf("cloudHypervisor.resizeMemory() got = %+v, want %+v", newMem, expectedMem)
}

if !reflect.DeepEqual(memDev, tt.expectedMemDev) {
t.Errorf("cloudHypervisor.resizeMemory() got = %+v, want %+v", memDev, tt.expectedMemDev)
}
})
}
}

0 comments on commit aab82f6

Please sign in to comment.