mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-07-14 22:06:46 +00:00
First of all, this is a controversial piece, and I know that. In this commit we're trying to make a less greedy approach regards the amount of vCPUs we allocate for the VMM, which will be advantageous mainly when using the `static_sandbox_resource_mgmt` feature, which is used by the confidential guests. The current approach we have basically does: * Gets the amount of vCPUs set in the config (an integer) * Gets the amount of vCPUs set as limit (an integer) * Sum those up * Starts / Updates the VMM to use that total amount of vCPUs The fact we're dealing with integers is logical, as we cannot request 500m vCPUs to the VMMs. However, it leads us to, in several cases, be wasting one vCPU. Let's take the example that we know the VMM requires 500m vCPUs to be running, and the workload sets 250m vCPUs as a resource limit. In that case, we'd do: * Gets the amount of vCPUs set in the config: 1 * Gets the amount of vCPUs set as limit: ceil(0.25) * 1 + ceil(0.25) = 1 + 1 = 2 vCPUs * Starts / Updates the VMM to use 2 vCPUs With the logic changed here, what we're doing is considering everything as float till just before we start / update the VMM. So, the flow describe above would be: * Gets the amount of vCPUs set in the config: 0.5 * Gets the amount of vCPUs set as limit: 0.25 * ceil(0.5 + 0.25) = 1 vCPUs * Starts / Updates the VMM to use 1 vCPUs In the way I've written this patch we introduce zero regressions, as the default values set are still the same, and those will only be changed for the TEE use cases (although I can see firecracker, or any other user of `static_sandbox_resource_mgmt=true` taking advantage of this). There's, though, an implicit assumption in this patch that we'd need to make explicit, and that's that the default_vcpus / default_memory is the amount of vcpus / memory required by the VMM, and absolutely nothing else. Also, the amount set there should be reflected in the podOverhead for the specific runtime class. One other possible approach, which I am not that much in favour of taking as I think it's **less clear**, is that we could actually get the podOverhead amount, subtract it from the default_vcpus (treating the result as a float), then sum up what the user set as limit (as a float), and finally ceil the result. It could work, but IMHO this is **less clear**, and **less explicit** on what we're actually doing, and how the default_vcpus / default_memory should be used. Fixes: #6909 Signed-off-by: Fabiano Fidêncio <fabiano.fidencio@intel.com> Signed-off-by: Christophe de Dinechin <dinechin@redhat.com>
760 lines
20 KiB
Go
760 lines
20 KiB
Go
//go:build linux
|
|
|
|
// Copyright (c) 2019 Ericsson Eurolab Deutschland G.m.b.H.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package virtcontainers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kata-containers/kata-containers/src/runtime/pkg/device/config"
|
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/persist"
|
|
chclient "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/cloud-hypervisor/client"
|
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types"
|
|
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
const (
|
|
FAIL = true
|
|
PASS = !FAIL
|
|
)
|
|
|
|
func newClhConfig() (HypervisorConfig, error) {
|
|
|
|
setupClh()
|
|
|
|
if testClhPath == "" {
|
|
return HypervisorConfig{}, errors.New("hypervisor fake path is empty")
|
|
}
|
|
|
|
if testVirtiofsdPath == "" {
|
|
return HypervisorConfig{}, errors.New("virtiofsd fake path is empty")
|
|
}
|
|
|
|
if _, err := os.Stat(testClhPath); os.IsNotExist(err) {
|
|
return HypervisorConfig{}, err
|
|
}
|
|
|
|
if _, err := os.Stat(testVirtiofsdPath); os.IsNotExist(err) {
|
|
return HypervisorConfig{}, err
|
|
}
|
|
|
|
return HypervisorConfig{
|
|
KernelPath: testClhKernelPath,
|
|
ImagePath: testClhImagePath,
|
|
RootfsType: string(EXT4),
|
|
HypervisorPath: testClhPath,
|
|
NumVCPUsF: defaultVCPUs,
|
|
BlockDeviceDriver: config.VirtioBlock,
|
|
MemorySize: defaultMemSzMiB,
|
|
DefaultBridges: defaultBridges,
|
|
DefaultMaxVCPUs: uint32(64),
|
|
SharedFS: config.VirtioFS,
|
|
VirtioFSCache: typeVirtioFSCacheModeAlways,
|
|
VirtioFSDaemon: testVirtiofsdPath,
|
|
NetRateLimiterBwMaxRate: int64(0),
|
|
NetRateLimiterBwOneTimeBurst: int64(0),
|
|
NetRateLimiterOpsMaxRate: int64(0),
|
|
NetRateLimiterOpsOneTimeBurst: int64(0),
|
|
HotPlugVFIO: config.NoPort,
|
|
}, nil
|
|
}
|
|
|
|
type clhClientMock struct {
|
|
vmInfo chclient.VmInfo
|
|
}
|
|
|
|
func (c *clhClientMock) VmmPingGet(ctx context.Context) (chclient.VmmPingResponse, *http.Response, error) {
|
|
return chclient.VmmPingResponse{}, nil, nil
|
|
}
|
|
|
|
func (c *clhClientMock) ShutdownVMM(ctx context.Context) (*http.Response, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (c *clhClientMock) CreateVM(ctx context.Context, vmConfig chclient.VmConfig) (*http.Response, error) {
|
|
c.vmInfo.State = clhStateCreated
|
|
return nil, nil
|
|
}
|
|
|
|
//nolint:golint
|
|
func (c *clhClientMock) VmInfoGet(ctx context.Context) (chclient.VmInfo, *http.Response, error) {
|
|
return c.vmInfo, nil, nil
|
|
}
|
|
|
|
func (c *clhClientMock) BootVM(ctx context.Context) (*http.Response, error) {
|
|
c.vmInfo.State = clhStateRunning
|
|
return nil, nil
|
|
}
|
|
|
|
//nolint:golint
|
|
func (c *clhClientMock) VmResizePut(ctx context.Context, vmResize chclient.VmResize) (*http.Response, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
//nolint:golint
|
|
func (c *clhClientMock) VmAddDevicePut(ctx context.Context, deviceConfig chclient.DeviceConfig) (chclient.PciDeviceInfo, *http.Response, error) {
|
|
return chclient.PciDeviceInfo{}, nil, nil
|
|
}
|
|
|
|
//nolint:golint
|
|
func (c *clhClientMock) VmAddDiskPut(ctx context.Context, diskConfig chclient.DiskConfig) (chclient.PciDeviceInfo, *http.Response, error) {
|
|
return chclient.PciDeviceInfo{Bdf: "0000:00:0a.0"}, nil, nil
|
|
}
|
|
|
|
//nolint:golint
|
|
func (c *clhClientMock) VmRemoveDevicePut(ctx context.Context, vmRemoveDevice chclient.VmRemoveDevice) (*http.Response, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestCloudHypervisorAddVSock(t *testing.T) {
|
|
assert := assert.New(t)
|
|
clh := cloudHypervisor{}
|
|
|
|
clh.addVSock(1, "path")
|
|
assert.Equal(clh.vmconfig.Vsock.Cid, int64(1))
|
|
assert.Equal(clh.vmconfig.Vsock.Socket, "path")
|
|
}
|
|
|
|
// Check addNet appends to the network config list new configurations.
|
|
// Check that the elements in the list has the correct values
|
|
func TestCloudHypervisorAddNetCheckNetConfigListValues(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
macTest := "00:00:00:00:00"
|
|
|
|
file, err := os.CreateTemp("", "netFd")
|
|
assert.Nil(err)
|
|
defer os.Remove(file.Name())
|
|
|
|
vmFds := make([]*os.File, 1)
|
|
vmFds = append(vmFds, file)
|
|
|
|
clh := cloudHypervisor{}
|
|
clh.netDevicesFiles = make(map[string][]*os.File)
|
|
|
|
e := &VethEndpoint{}
|
|
e.NetPair.TAPIface.HardAddr = macTest
|
|
e.NetPair.TapInterface.VMFds = vmFds
|
|
|
|
err = clh.addNet(e)
|
|
assert.Nil(err)
|
|
|
|
assert.Equal(len(*clh.netDevices), 1)
|
|
if err == nil {
|
|
assert.Equal(*(*clh.netDevices)[0].Mac, macTest)
|
|
}
|
|
|
|
err = clh.addNet(e)
|
|
assert.Nil(err)
|
|
|
|
assert.Equal(len(*clh.netDevices), 2)
|
|
if err == nil {
|
|
assert.Equal(*(*clh.netDevices)[1].Mac, macTest)
|
|
}
|
|
}
|
|
|
|
// Check addNet with valid values, and fail with invalid values
|
|
// For Cloud Hypervisor only tap is be required
|
|
func TestCloudHypervisorAddNetCheckEnpointTypes(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
macTest := "00:00:00:00:00"
|
|
|
|
file, err := os.CreateTemp("", "netFd")
|
|
assert.Nil(err)
|
|
defer os.Remove(file.Name())
|
|
|
|
vmFds := make([]*os.File, 1)
|
|
vmFds = append(vmFds, file)
|
|
|
|
validVeth := &VethEndpoint{}
|
|
validVeth.NetPair.TAPIface.HardAddr = macTest
|
|
validVeth.NetPair.TapInterface.VMFds = vmFds
|
|
|
|
type args struct {
|
|
e Endpoint
|
|
}
|
|
// nolint: govet
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{"TapEndpoint", args{e: &TapEndpoint{}}, true},
|
|
{"Empty VethEndpoint", args{e: &VethEndpoint{}}, true},
|
|
{"Valid VethEndpoint", args{e: validVeth}, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
clh := &cloudHypervisor{}
|
|
clh.netDevicesFiles = make(map[string][]*os.File)
|
|
if err := clh.addNet(tt.args.e); (err != nil) != tt.wantErr {
|
|
t.Errorf("cloudHypervisor.addNet() error = %v, wantErr %v", err, tt.wantErr)
|
|
} else if err == nil {
|
|
files := clh.netDevicesFiles[macTest]
|
|
assert.Equal(files, vmFds)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Check AddNet properly sets up the network rate limiter
|
|
func TestCloudHypervisorNetRateLimiter(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
file, err := os.CreateTemp("", "netFd")
|
|
assert.Nil(err)
|
|
defer os.Remove(file.Name())
|
|
|
|
vmFds := make([]*os.File, 1)
|
|
vmFds = append(vmFds, file)
|
|
|
|
validVeth := &VethEndpoint{}
|
|
validVeth.NetPair.TapInterface.VMFds = vmFds
|
|
|
|
type args struct {
|
|
bwMaxRate int64
|
|
bwOneTimeBurst int64
|
|
opsMaxRate int64
|
|
opsOneTimeBurst int64
|
|
}
|
|
|
|
//nolint: govet
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
expectsRateLimiter bool
|
|
expectsBwBucketToken bool
|
|
expectsOpsBucketToken bool
|
|
}{
|
|
// Bandwidth
|
|
{
|
|
"Bandwidth | max rate with one time burst",
|
|
args{
|
|
bwMaxRate: int64(1000),
|
|
bwOneTimeBurst: int64(10000),
|
|
},
|
|
true, // expectsRateLimiter
|
|
true, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Bandwidth | max rate without one time burst",
|
|
args{
|
|
bwMaxRate: int64(1000),
|
|
},
|
|
true, // expectsRateLimiter
|
|
true, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Bandwidth | no max rate with one time burst",
|
|
args{
|
|
bwOneTimeBurst: int64(10000),
|
|
},
|
|
false, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Bandwidth | no max rate and no one time burst",
|
|
args{},
|
|
false, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
|
|
// Operations
|
|
{
|
|
"Operations | max rate with one time burst",
|
|
args{
|
|
opsMaxRate: int64(1000),
|
|
opsOneTimeBurst: int64(10000),
|
|
},
|
|
true, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
true, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Operations | max rate without one time burst",
|
|
args{
|
|
opsMaxRate: int64(1000),
|
|
},
|
|
true, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
true, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Operations | no max rate with one time burst",
|
|
args{
|
|
opsOneTimeBurst: int64(10000),
|
|
},
|
|
false, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Operations | no max rate and no one time burst",
|
|
args{},
|
|
false, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
|
|
// Bandwidth and Operations
|
|
{
|
|
"Bandwidth and Operations | max rate with one time burst",
|
|
args{
|
|
bwMaxRate: int64(1000),
|
|
bwOneTimeBurst: int64(10000),
|
|
opsMaxRate: int64(1000),
|
|
opsOneTimeBurst: int64(10000),
|
|
},
|
|
true, // expectsRateLimiter
|
|
true, // expectsBwBucketToken
|
|
true, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Bandwidth and Operations | max rate without one time burst",
|
|
args{
|
|
bwMaxRate: int64(1000),
|
|
opsMaxRate: int64(1000),
|
|
},
|
|
true, // expectsRateLimiter
|
|
true, // expectsBwBucketToken
|
|
true, // expectsOpsBucketToken
|
|
},
|
|
{
|
|
"Bandwidth and Operations | no max rate with one time burst",
|
|
args{
|
|
bwOneTimeBurst: int64(10000),
|
|
opsOneTimeBurst: int64(10000),
|
|
},
|
|
false, // expectsRateLimiter
|
|
false, // expectsBwBucketToken
|
|
false, // expectsOpsBucketToken
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
clhConfig, err := newClhConfig()
|
|
assert.NoError(err)
|
|
|
|
clhConfig.NetRateLimiterBwMaxRate = tt.args.bwMaxRate
|
|
clhConfig.NetRateLimiterBwOneTimeBurst = tt.args.bwOneTimeBurst
|
|
clhConfig.NetRateLimiterOpsMaxRate = tt.args.opsMaxRate
|
|
clhConfig.NetRateLimiterOpsOneTimeBurst = tt.args.opsOneTimeBurst
|
|
|
|
clh := &cloudHypervisor{}
|
|
clh.netDevicesFiles = make(map[string][]*os.File)
|
|
clh.config = clhConfig
|
|
clh.APIClient = &clhClientMock{}
|
|
|
|
if err := clh.addNet(validVeth); err != nil {
|
|
t.Errorf("cloudHypervisor.addNet() error = %v", err)
|
|
} else {
|
|
netConfig := (*clh.netDevices)[0]
|
|
|
|
assert.Equal(netConfig.HasRateLimiterConfig(), tt.expectsRateLimiter)
|
|
if tt.expectsRateLimiter {
|
|
rateLimiterConfig := netConfig.GetRateLimiterConfig()
|
|
assert.Equal(rateLimiterConfig.HasBandwidth(), tt.expectsBwBucketToken)
|
|
assert.Equal(rateLimiterConfig.HasOps(), tt.expectsOpsBucketToken)
|
|
|
|
if tt.expectsBwBucketToken {
|
|
bwBucketToken := rateLimiterConfig.GetBandwidth()
|
|
assert.Equal(bwBucketToken.GetSize(), int64(utils.RevertBytes(uint64(tt.args.bwMaxRate/8))))
|
|
assert.Equal(bwBucketToken.GetOneTimeBurst(), int64(utils.RevertBytes(uint64(tt.args.bwOneTimeBurst/8))))
|
|
}
|
|
|
|
if tt.expectsOpsBucketToken {
|
|
opsBucketToken := rateLimiterConfig.GetOps()
|
|
assert.Equal(opsBucketToken.GetSize(), int64(tt.args.opsMaxRate))
|
|
assert.Equal(opsBucketToken.GetOneTimeBurst(), int64(tt.args.opsOneTimeBurst))
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCloudHypervisorBootVM(t *testing.T) {
|
|
clh := &cloudHypervisor{}
|
|
clh.APIClient = &clhClientMock{}
|
|
|
|
savedVmAddNetPutRequestFunc := vmAddNetPutRequest
|
|
vmAddNetPutRequest = func(clh *cloudHypervisor) error { return nil }
|
|
defer func() {
|
|
vmAddNetPutRequest = savedVmAddNetPutRequestFunc
|
|
}()
|
|
|
|
var ctx context.Context
|
|
if err := clh.bootVM(ctx); err != nil {
|
|
t.Errorf("cloudHypervisor.bootVM() error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCloudHypervisorCleanupVM(t *testing.T) {
|
|
assert := assert.New(t)
|
|
store, err := persist.GetDriver()
|
|
assert.NoError(err, "persist.GetDriver() unexpected error")
|
|
|
|
clh := &cloudHypervisor{
|
|
config: HypervisorConfig{
|
|
VMStorePath: store.RunVMStoragePath(),
|
|
RunStorePath: store.RunStoragePath(),
|
|
},
|
|
}
|
|
|
|
err = clh.cleanupVM(true)
|
|
assert.Error(err, "persist.GetDriver() expected error")
|
|
|
|
clh.id = "cleanVMID"
|
|
clh.config.VMid = "cleanVMID"
|
|
|
|
err = clh.cleanupVM(true)
|
|
assert.NoError(err, "persist.GetDriver() unexpected error")
|
|
|
|
dir := filepath.Join(store.RunVMStoragePath(), clh.id)
|
|
os.MkdirAll(dir, os.ModePerm)
|
|
|
|
err = clh.cleanupVM(false)
|
|
assert.NoError(err, "persist.GetDriver() unexpected error")
|
|
|
|
_, err = os.Stat(dir)
|
|
assert.Error(err, "dir should not exist %s", dir)
|
|
|
|
assert.True(os.IsNotExist(err), "persist.GetDriver() unexpected error")
|
|
}
|
|
|
|
func TestClhCreateVM(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
store, err := persist.GetDriver()
|
|
assert.NoError(err)
|
|
|
|
network, err := NewNetwork()
|
|
assert.NoError(err)
|
|
|
|
clh := &cloudHypervisor{
|
|
config: HypervisorConfig{
|
|
VMStorePath: store.RunVMStoragePath(),
|
|
RunStorePath: store.RunStoragePath(),
|
|
},
|
|
}
|
|
|
|
config0, err := newClhConfig()
|
|
assert.NoError(err)
|
|
|
|
config1, err := newClhConfig()
|
|
assert.NoError(err)
|
|
config1.ImagePath = ""
|
|
config1.InitrdPath = testClhInitrdPath
|
|
|
|
config2, err := newClhConfig()
|
|
assert.NoError(err)
|
|
config2.Debug = true
|
|
|
|
config3, err := newClhConfig()
|
|
assert.NoError(err)
|
|
config3.Debug = true
|
|
config3.ConfidentialGuest = true
|
|
|
|
config4, err := newClhConfig()
|
|
assert.NoError(err)
|
|
config4.SGXEPCSize = 1
|
|
|
|
config5, err := newClhConfig()
|
|
assert.NoError(err)
|
|
config5.SharedFS = config.VirtioFSNydus
|
|
|
|
type testData struct {
|
|
config HypervisorConfig
|
|
expectError bool
|
|
configMatch bool
|
|
}
|
|
|
|
data := []testData{
|
|
{config0, false, true},
|
|
{config1, false, true},
|
|
{config2, false, true},
|
|
{config3, true, false},
|
|
{config4, false, true},
|
|
{config5, false, true},
|
|
}
|
|
|
|
for i, d := range data {
|
|
msg := fmt.Sprintf("test[%d]", i)
|
|
|
|
err = clh.CreateVM(context.Background(), "testSandbox", network, &d.config)
|
|
|
|
if d.expectError {
|
|
assert.Error(err, msg)
|
|
continue
|
|
}
|
|
|
|
assert.NoError(err, msg)
|
|
|
|
if d.configMatch {
|
|
assert.Exactly(d.config, clh.config, msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCloudHypervisorStartSandbox(t *testing.T) {
|
|
assert := assert.New(t)
|
|
clhConfig, err := newClhConfig()
|
|
assert.NoError(err)
|
|
clhConfig.Debug = true
|
|
clhConfig.DisableSeccomp = true
|
|
|
|
store, err := persist.GetDriver()
|
|
assert.NoError(err)
|
|
|
|
savedVmAddNetPutRequestFunc := vmAddNetPutRequest
|
|
vmAddNetPutRequest = func(clh *cloudHypervisor) error { return nil }
|
|
defer func() {
|
|
vmAddNetPutRequest = savedVmAddNetPutRequestFunc
|
|
}()
|
|
|
|
clhConfig.VMStorePath = store.RunVMStoragePath()
|
|
clhConfig.RunStorePath = store.RunStoragePath()
|
|
|
|
clh := &cloudHypervisor{
|
|
config: clhConfig,
|
|
APIClient: &clhClientMock{},
|
|
virtiofsDaemon: &virtiofsdMock{},
|
|
}
|
|
|
|
err = clh.StartVM(context.Background(), 10)
|
|
assert.NoError(err)
|
|
|
|
_, err = clh.loadVirtiofsDaemon("/tmp/xyzabc")
|
|
assert.NoError(err)
|
|
|
|
err = clh.stopVirtiofsDaemon(context.Background())
|
|
assert.NoError(err)
|
|
|
|
_, _, err = clh.GetVMConsole(context.Background(), "test")
|
|
assert.NoError(err)
|
|
|
|
_, err = clh.GetThreadIDs(context.Background())
|
|
assert.NoError(err)
|
|
|
|
assert.True(clh.getClhStopSandboxTimeout().Nanoseconds() != 0)
|
|
|
|
pid := clh.GetPids()
|
|
assert.True(pid[0] != 0)
|
|
|
|
pid2 := *clh.GetVirtioFsPid()
|
|
assert.True(pid2 == 0)
|
|
|
|
mem := clh.GetTotalMemoryMB(context.Background())
|
|
assert.True(mem == 0)
|
|
|
|
err = clh.PauseVM(context.Background())
|
|
assert.NoError(err)
|
|
|
|
err = clh.SaveVM()
|
|
assert.NoError(err)
|
|
|
|
err = clh.ResumeVM(context.Background())
|
|
assert.NoError(err)
|
|
|
|
err = clh.Check()
|
|
assert.NoError(err)
|
|
|
|
err = clh.Cleanup(context.Background())
|
|
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 = *chclient.NewVmConfig(*chclient.NewPayloadConfig())
|
|
mockClient.vmInfo.Config.Memory = chclient.NewMemoryConfig(int64(utils.MemUnit(clhConfig.MemorySize) * utils.MiB))
|
|
mockClient.vmInfo.Config.Memory.HotplugSize = func(i int64) *int64 { return &i }(int64(40 * utils.GiB.ToBytes()))
|
|
|
|
clh.APIClient = mockClient
|
|
clh.config = clhConfig
|
|
|
|
newMem, memDev, err := clh.ResizeMemory(context.Background(), 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCloudHypervisorHotplugAddBlockDevice(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
clhConfig, err := newClhConfig()
|
|
assert.NoError(err)
|
|
|
|
clh := &cloudHypervisor{}
|
|
clh.config = clhConfig
|
|
clh.APIClient = &clhClientMock{}
|
|
clh.devicesIds = make(map[string]string)
|
|
|
|
clh.config.BlockDeviceDriver = config.VirtioBlock
|
|
err = clh.hotplugAddBlockDevice(&config.BlockDrive{Pmem: false})
|
|
assert.NoError(err, "Hotplug disk block device expected no error")
|
|
|
|
err = clh.hotplugAddBlockDevice(&config.BlockDrive{Pmem: true})
|
|
assert.Error(err, "Hotplug pmem block device expected error")
|
|
|
|
clh.config.BlockDeviceDriver = config.VirtioSCSI
|
|
err = clh.hotplugAddBlockDevice(&config.BlockDrive{Pmem: false})
|
|
assert.Error(err, "Hotplug block device not using 'virtio-blk' expected error")
|
|
}
|
|
|
|
func TestCloudHypervisorHotplugRemoveDevice(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
clhConfig, err := newClhConfig()
|
|
assert.NoError(err)
|
|
|
|
clh := &cloudHypervisor{}
|
|
clh.config = clhConfig
|
|
clh.APIClient = &clhClientMock{}
|
|
clh.devicesIds = make(map[string]string)
|
|
|
|
_, err = clh.HotplugRemoveDevice(context.Background(), &config.BlockDrive{}, BlockDev)
|
|
assert.NoError(err, "Hotplug remove block device expected no error")
|
|
|
|
_, err = clh.HotplugRemoveDevice(context.Background(), &config.VFIODev{}, VfioDev)
|
|
assert.NoError(err, "Hotplug remove vfio block device expected no error")
|
|
|
|
_, err = clh.HotplugRemoveDevice(context.Background(), nil, NetDev)
|
|
assert.Error(err, "Hotplug remove pmem block device expected error")
|
|
}
|
|
|
|
func TestClhGenerateSocket(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
// Ensure the type is fully constructed
|
|
hypervisor, err := NewHypervisor("clh")
|
|
assert.NoError(err)
|
|
|
|
clh, ok := hypervisor.(*cloudHypervisor)
|
|
assert.True(ok)
|
|
|
|
clh.config = HypervisorConfig{
|
|
VMStorePath: "/foo",
|
|
RunStorePath: "/bar",
|
|
}
|
|
|
|
clh.addVSock(1, "path")
|
|
|
|
s, err := clh.GenerateSocket("c")
|
|
|
|
assert.NoError(err)
|
|
assert.NotNil(s)
|
|
|
|
hvsock, ok := s.(types.HybridVSock)
|
|
assert.True(ok)
|
|
assert.NotEmpty(hvsock.UdsPath)
|
|
|
|
// Path must be absolute
|
|
assert.True(strings.HasPrefix(hvsock.UdsPath, "/"), "failed: socket path: %s", hvsock.UdsPath)
|
|
|
|
assert.NotZero(hvsock.Port)
|
|
}
|
|
|
|
func TestClhSetConfig(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
config, err := newClhConfig()
|
|
assert.NoError(err)
|
|
|
|
clh := &cloudHypervisor{}
|
|
assert.Equal(clh.config, HypervisorConfig{})
|
|
|
|
err = clh.setConfig(&config)
|
|
assert.NoError(err)
|
|
|
|
assert.Equal(clh.config, config)
|
|
}
|
|
|
|
func TestClhCapabilities(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
hConfig, err := newClhConfig()
|
|
assert.NoError(err)
|
|
|
|
clh := &cloudHypervisor{}
|
|
assert.Equal(clh.config, HypervisorConfig{})
|
|
|
|
hConfig.SharedFS = config.VirtioFS
|
|
|
|
err = clh.setConfig(&hConfig)
|
|
assert.NoError(err)
|
|
|
|
var ctx context.Context
|
|
c := clh.Capabilities(ctx)
|
|
assert.True(c.IsFsSharingSupported())
|
|
|
|
hConfig.SharedFS = config.NoSharedFS
|
|
|
|
err = clh.setConfig(&hConfig)
|
|
assert.NoError(err)
|
|
|
|
c = clh.Capabilities(ctx)
|
|
assert.False(c.IsFsSharingSupported())
|
|
|
|
assert.True(c.IsNetworkDeviceHotplugSupported())
|
|
assert.True(c.IsBlockDeviceHotplugSupported())
|
|
}
|