kata-containers/src/runtime/virtcontainers/qemu_arch_base_test.go
Peng Tao 32fd013716 runtime: run prestart hooks before starting VM for FC
Add a new hypervisor capability to tell if it supports device hotplug.
If not, we should run prestart hooks before starting new VMs as nerdctl
is using the prestart hooks to set up netns. To make nerdctl + FC
to work, we need to run the prestart hooks before starting new VMs.

Fixes: #6384
Signed-off-by: Peng Tao <bergwolf@hyper.sh>
2023-08-30 02:52:01 +00:00

610 lines
15 KiB
Go

//go:build linux
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"context"
"fmt"
"net"
"os"
"path/filepath"
"testing"
govmmQemu "github.com/kata-containers/kata-containers/src/runtime/pkg/govmm/qemu"
"github.com/stretchr/testify/assert"
"github.com/kata-containers/kata-containers/src/runtime/pkg/device/config"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/persist/fs"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types"
"github.com/pkg/errors"
)
const (
qemuArchBaseQemuPath = "/usr/bin/qemu-system-x86_64"
)
var qemuArchBaseMachine = govmmQemu.Machine{
Type: "q35",
}
var qemuArchBaseQemuPaths = map[string]string{
qemuArchBaseMachine.Type: qemuArchBaseQemuPath,
}
var qemuArchBaseKernelParamsNonDebug = []Param{
{"quiet", ""},
{"systemd.show_status", "false"},
}
var qemuArchBaseKernelParamsDebug = []Param{
{"debug", ""},
{"systemd.show_status", "true"},
{"systemd.log_level", "debug"},
}
var qemuArchBaseKernelParams = []Param{
{"root", "/dev/vda"},
{"rootfstype", "ext4"},
}
func newQemuArchBase() *qemuArchBase {
return &qemuArchBase{
qemuMachine: qemuArchBaseMachine,
qemuExePath: qemuArchBaseQemuPaths[qemuArchBaseMachine.Type],
nestedRun: false,
kernelParamsNonDebug: qemuArchBaseKernelParamsNonDebug,
kernelParamsDebug: qemuArchBaseKernelParamsDebug,
kernelParams: qemuArchBaseKernelParams,
}
}
func TestQemuArchBaseEnableNestingChecks(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
qemuArchBase.enableNestingChecks()
assert.True(qemuArchBase.nestedRun)
}
func TestQemuArchBaseDisableNestingChecks(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
qemuArchBase.disableNestingChecks()
assert.False(qemuArchBase.nestedRun)
}
func TestQemuArchBaseMachine(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
m := qemuArchBase.machine()
assert.Equal(m.Type, qemuArchBaseMachine.Type)
}
func TestQemuArchBaseQemuPath(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
p := qemuArchBase.qemuPath()
assert.Equal(p, qemuArchBaseQemuPath)
}
func TestQemuArchBaseKernelParameters(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
// with debug params
expectedParams := qemuArchBaseKernelParams
debugParams := qemuArchBaseKernelParamsDebug
expectedParams = append(expectedParams, debugParams...)
p := qemuArchBase.kernelParameters(true)
assert.Equal(expectedParams, p)
// with non-debug params
expectedParams = qemuArchBaseKernelParams
nonDebugParams := qemuArchBaseKernelParamsNonDebug
expectedParams = append(expectedParams, nonDebugParams...)
p = qemuArchBase.kernelParameters(false)
assert.Equal(expectedParams, p)
}
func TestQemuArchBaseCapabilities(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
hConfig := HypervisorConfig{}
hConfig.SharedFS = config.VirtioFS
c := qemuArchBase.capabilities(hConfig)
assert.True(c.IsBlockDeviceHotplugSupported())
assert.True(c.IsFsSharingSupported())
assert.True(c.IsNetworkDeviceHotplugSupported())
hConfig.SharedFS = config.NoSharedFS
c = qemuArchBase.capabilities(hConfig)
assert.False(c.IsFsSharingSupported())
}
func TestQemuArchBaseBridges(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
len := 5
qemuArchBase.bridges(uint32(len))
bridges := qemuArchBase.getBridges()
assert.Len(bridges, len)
for i, b := range bridges {
id := fmt.Sprintf("%s-bridge-%d", types.PCI, i)
assert.Equal(types.PCI, b.Type)
assert.Equal(id, b.ID)
assert.NotNil(b.Devices)
}
}
func TestQemuAddDeviceToBridge(t *testing.T) {
assert := assert.New(t)
// addDeviceToBridge successfully
q := newQemuArchBase()
q.qemuMachine.Type = QemuQ35
q.bridges(1)
for i := uint32(1); i <= types.PCIBridgeMaxCapacity; i++ {
_, _, err := q.addDeviceToBridge(context.Background(), fmt.Sprintf("qemu-bridge-%d", i), types.PCI)
assert.Nil(err)
}
// fail to add device to bridge cause no more available bridge slot
_, _, err := q.addDeviceToBridge(context.Background(), "qemu-bridge-31", types.PCI)
exceptErr := errors.New("no more bridge slots available")
assert.Equal(exceptErr.Error(), err.Error())
// addDeviceToBridge fails cause q.Bridges == 0
q = newQemuArchBase()
q.qemuMachine.Type = QemuQ35
q.bridges(0)
_, _, err = q.addDeviceToBridge(context.Background(), "qemu-bridge", types.PCI)
if assert.Error(err) {
exceptErr = errors.New("failed to get available address from bridges")
assert.Equal(exceptErr.Error(), err.Error())
}
}
func TestQemuArchBaseCPUTopology(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
vcpus := uint32(2)
expectedSMP := govmmQemu.SMP{
CPUs: vcpus,
Sockets: defaultMaxVCPUs,
Cores: defaultCores,
Threads: defaultThreads,
MaxCPUs: defaultMaxVCPUs,
}
smp := qemuArchBase.cpuTopology(vcpus, defaultMaxVCPUs)
assert.Equal(expectedSMP, smp)
}
func TestQemuArchBaseCPUModel(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
assert.Equal(defaultCPUModel, qemuArchBase.cpuModel())
}
func TestQemuArchBaseMemoryTopology(t *testing.T) {
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
hostMem := uint64(100)
mem := uint64(120)
slots := uint8(12)
expectedMemory := govmmQemu.Memory{
Size: fmt.Sprintf("%dM", mem),
Slots: slots,
MaxMem: fmt.Sprintf("%dM", hostMem),
}
m := qemuArchBase.memoryTopology(mem, hostMem, slots)
assert.Equal(expectedMemory, m)
}
func testQemuArchBaseAppend(t *testing.T, structure interface{}, expected []govmmQemu.Device) {
var devices []govmmQemu.Device
var err error
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
switch s := structure.(type) {
case types.Volume:
devices, err = qemuArchBase.append9PVolume(context.Background(), devices, s)
case types.Socket:
devices = qemuArchBase.appendSocket(devices, s)
case config.BlockDrive:
devices, err = qemuArchBase.appendBlockDevice(context.Background(), devices, s)
case config.VFIODev:
devices = qemuArchBase.appendVFIODevice(devices, s)
case config.VhostUserDeviceAttrs:
devices, err = qemuArchBase.appendVhostUserDevice(context.Background(), devices, s)
}
assert.NoError(err)
assert.Equal(devices, expected)
}
func TestQemuArchBaseAppendConsoles(t *testing.T) {
var devices []govmmQemu.Device
var err error
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
path := filepath.Join(filepath.Join(fs.MockRunStoragePath(), "test"), consoleSocket)
expectedOut := []govmmQemu.Device{
govmmQemu.SerialDevice{
Driver: govmmQemu.VirtioSerial,
ID: "serial0",
MaxPorts: uint(2),
},
govmmQemu.CharDevice{
Driver: govmmQemu.Console,
Backend: govmmQemu.Socket,
DeviceID: "console0",
ID: "charconsole0",
Path: path,
},
}
devices, err = qemuArchBase.appendConsole(context.Background(), devices, path)
assert.NoError(err)
assert.Equal(expectedOut, devices)
assert.Contains(qemuArchBase.kernelParams, Param{"console", "hvc0"})
assert.Contains(qemuArchBase.kernelParams, Param{"console", "hvc1"})
}
func TestQemuArchBaseAppendConsolesLegacy(t *testing.T) {
var devices []govmmQemu.Device
var err error
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
qemuArchBase.legacySerial = true
path := filepath.Join(filepath.Join(fs.MockRunStoragePath(), "test"), consoleSocket)
expectedOut := []govmmQemu.Device{
govmmQemu.LegacySerialDevice{
Chardev: "charconsole0",
},
govmmQemu.CharDevice{
Driver: govmmQemu.LegacySerial,
Backend: govmmQemu.Socket,
DeviceID: "console0",
ID: "charconsole0",
Path: path,
},
}
devices, err = qemuArchBase.appendConsole(context.Background(), devices, path)
assert.NoError(err)
assert.Equal(expectedOut, devices)
assert.Contains(qemuArchBase.kernelParams, Param{"console", "ttyS0"})
}
func TestQemuArchBaseAppendImage(t *testing.T) {
var devices []govmmQemu.Device
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
image, err := os.CreateTemp("", "img")
assert.NoError(err)
defer os.Remove(image.Name())
err = image.Close()
assert.NoError(err)
devices, err = qemuArchBase.appendImage(context.Background(), devices, image.Name())
assert.NoError(err)
assert.Len(devices, 1)
drive, ok := devices[0].(govmmQemu.BlockDevice)
assert.True(ok)
expectedOut := []govmmQemu.Device{
govmmQemu.BlockDevice{
Driver: govmmQemu.VirtioBlock,
ID: drive.ID,
File: image.Name(),
AIO: govmmQemu.Threads,
Format: "raw",
Interface: "none",
ShareRW: true,
ReadOnly: true,
},
}
assert.Equal(expectedOut, devices)
}
func TestQemuArchBaseAppendBridges(t *testing.T) {
var devices []govmmQemu.Device
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
qemuArchBase.bridges(1)
bridges := qemuArchBase.getBridges()
assert.Len(bridges, 1)
devices = qemuArchBase.appendBridges(devices)
assert.Len(devices, 1)
expectedOut := []govmmQemu.Device{
govmmQemu.BridgeDevice{
Type: govmmQemu.PCIBridge,
Bus: defaultBridgeBus,
ID: bridges[0].ID,
Chassis: 1,
SHPC: false,
Addr: "2",
IOReserve: "4k",
MemReserve: "1m",
Pref64Reserve: "1m",
},
}
assert.Equal(expectedOut, devices)
}
func TestQemuArchBaseAppend9PVolume(t *testing.T) {
mountTag := "testMountTag"
hostPath := "testHostPath"
expectedOut := []govmmQemu.Device{
govmmQemu.FSDevice{
Driver: govmmQemu.Virtio9P,
FSDriver: govmmQemu.Local,
ID: fmt.Sprintf("extra-9p-%s", mountTag),
Path: hostPath,
MountTag: mountTag,
SecurityModel: govmmQemu.None,
Multidev: govmmQemu.Remap,
},
}
volume := types.Volume{
MountTag: mountTag,
HostPath: hostPath,
}
testQemuArchBaseAppend(t, volume, expectedOut)
}
func TestQemuArchBaseAppendSocket(t *testing.T) {
deviceID := "channelTest"
id := "charchTest"
hostPath := "/tmp/hyper_test.sock"
name := "sh.hyper.channel.test"
expectedOut := []govmmQemu.Device{
govmmQemu.CharDevice{
Driver: govmmQemu.VirtioSerialPort,
Backend: govmmQemu.Socket,
DeviceID: deviceID,
ID: id,
Path: hostPath,
Name: name,
},
}
socket := types.Socket{
DeviceID: deviceID,
ID: id,
HostPath: hostPath,
Name: name,
}
testQemuArchBaseAppend(t, socket, expectedOut)
}
func TestQemuArchBaseAppendBlockDevice(t *testing.T) {
id := "blockDevTest"
file := "/root"
format := "raw"
expectedOut := []govmmQemu.Device{
govmmQemu.BlockDevice{
Driver: govmmQemu.VirtioBlock,
ID: id,
File: "/root",
AIO: govmmQemu.Threads,
Format: govmmQemu.BlockDeviceFormat(format),
Interface: "none",
},
}
drive := config.BlockDrive{
File: file,
Format: format,
ID: id,
}
testQemuArchBaseAppend(t, drive, expectedOut)
}
func TestQemuArchBaseAppendVhostUserDevice(t *testing.T) {
socketPath := "nonexistentpath.sock"
macAddress := "00:11:22:33:44:55:66"
id := "deadbeef"
expectedOut := []govmmQemu.Device{
govmmQemu.VhostUserDevice{
SocketPath: socketPath,
CharDevID: fmt.Sprintf("char-%s", id),
TypeDevID: fmt.Sprintf("net-%s", id),
Address: macAddress,
VhostUserType: govmmQemu.VhostUserNet,
},
}
vhostUserDevice := config.VhostUserDeviceAttrs{
Type: config.VhostUserNet,
MacAddress: macAddress,
}
vhostUserDevice.DevID = id
vhostUserDevice.SocketPath = socketPath
testQemuArchBaseAppend(t, vhostUserDevice, expectedOut)
}
func TestQemuArchBaseAppendVFIODevice(t *testing.T) {
bdf := "02:10.1"
expectedOut := []govmmQemu.Device{
govmmQemu.VFIODevice{
BDF: bdf,
},
}
vfDevice := config.VFIODev{
BDF: bdf,
}
testQemuArchBaseAppend(t, vfDevice, expectedOut)
}
func TestQemuArchBaseAppendVFIODeviceWithVendorDeviceID(t *testing.T) {
bdf := "02:10.1"
vendorID := "0x1234"
deviceID := "0x5678"
expectedOut := []govmmQemu.Device{
govmmQemu.VFIODevice{
BDF: bdf,
VendorID: vendorID,
DeviceID: deviceID,
},
}
vfDevice := config.VFIODev{
BDF: bdf,
VendorID: vendorID,
DeviceID: deviceID,
}
testQemuArchBaseAppend(t, vfDevice, expectedOut)
}
func TestQemuArchBaseAppendSCSIController(t *testing.T) {
var devices []govmmQemu.Device
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
expectedOut := []govmmQemu.Device{
govmmQemu.SCSIController{
ID: scsiControllerID,
},
}
devices, ioThread, err := qemuArchBase.appendSCSIController(context.Background(), devices, false)
assert.Equal(expectedOut, devices)
assert.Nil(ioThread)
assert.NoError(err)
_, ioThread, err = qemuArchBase.appendSCSIController(context.Background(), devices, true)
assert.NotNil(ioThread)
assert.NoError(err)
}
func TestQemuArchBaseAppendNetwork(t *testing.T) {
var devices []govmmQemu.Device
var err error
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
macAddr := net.HardwareAddr{0x02, 0x00, 0xCA, 0xFE, 0x00, 0x04}
macvlanEp := &MacvlanEndpoint{
NetPair: NetworkInterfacePair{
TapInterface: TapInterface{
ID: "uniqueTestID-4",
Name: "br4_kata",
TAPIface: NetworkInterface{
Name: "tap4_kata",
},
},
VirtIface: NetworkInterface{
Name: "eth4",
HardAddr: macAddr.String(),
},
NetInterworkingModel: DefaultNetInterworkingModel,
},
EndpointType: MacvlanEndpointType,
}
macvtapEp := &MacvtapEndpoint{
EndpointType: MacvtapEndpointType,
EndpointProperties: NetworkInfo{
Iface: NetlinkIface{
Type: "macvtap",
},
},
}
expectedOut := []govmmQemu.Device{
govmmQemu.NetDevice{
Type: networkModelToQemuType(macvlanEp.NetPair.NetInterworkingModel),
Driver: govmmQemu.VirtioNet,
ID: fmt.Sprintf("network-%d", 0),
IFName: macvlanEp.NetPair.TAPIface.Name,
MACAddress: macvlanEp.NetPair.TAPIface.HardAddr,
DownScript: "no",
Script: "no",
FDs: macvlanEp.NetPair.VMFds,
VhostFDs: macvlanEp.NetPair.VhostFds,
},
govmmQemu.NetDevice{
Type: govmmQemu.MACVTAP,
Driver: govmmQemu.VirtioNet,
ID: fmt.Sprintf("network-%d", 1),
IFName: macvtapEp.Name(),
MACAddress: macvtapEp.HardwareAddr(),
DownScript: "no",
Script: "no",
FDs: macvtapEp.VMFds,
VhostFDs: macvtapEp.VhostFds,
},
}
devices, err = qemuArchBase.appendNetwork(context.Background(), devices, macvlanEp)
assert.NoError(err)
devices, err = qemuArchBase.appendNetwork(context.Background(), devices, macvtapEp)
assert.NoError(err)
assert.Equal(expectedOut, devices)
}
func TestQemuArchBaseAppendIOMMU(t *testing.T) {
var devices []govmmQemu.Device
var err error
assert := assert.New(t)
qemuArchBase := newQemuArchBase()
expectedOut := []govmmQemu.Device{
govmmQemu.IommuDev{
Intremap: true,
DeviceIotlb: true,
CachingMode: true,
},
}
qemuArchBase.qemuMachine.Type = QemuQ35
devices, err = qemuArchBase.appendIOMMU(devices)
assert.NoError(err)
assert.Equal(expectedOut, devices)
}