// Copyright (c) 2018 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // package virtcontainers import ( "fmt" "io/ioutil" "net" "path/filepath" "testing" govmmQemu "github.com/intel/govmm/qemu" "github.com/stretchr/testify/assert" "github.com/kata-containers/runtime/virtcontainers/device/config" "github.com/kata-containers/runtime/virtcontainers/store" "github.com/kata-containers/runtime/virtcontainers/types" "github.com/pkg/errors" ) const ( qemuArchBaseMachineType = "pc" qemuArchBaseQemuPath = "/usr/bin/qemu-system-x86_64" ) var qemuArchBaseQemuPaths = map[string]string{ qemuArchBaseMachineType: 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"}, } var qemuArchBaseSupportedQemuMachines = []govmmQemu.Machine{ { Type: qemuArchBaseMachineType, }, } func newQemuArchBase() *qemuArchBase { return &qemuArchBase{ machineType: qemuArchBaseMachineType, nestedRun: false, qemuPaths: qemuArchBaseQemuPaths, supportedQemuMachines: qemuArchBaseSupportedQemuMachines, 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, err := qemuArchBase.machine() assert.NoError(err) assert.Equal(m.Type, qemuArchBaseMachineType) machines := []govmmQemu.Machine{ { Type: "bad", }, } qemuArchBase.supportedQemuMachines = machines m, err = qemuArchBase.machine() assert.Error(err) assert.Equal("", m.Type) } func TestQemuArchBaseQemuPath(t *testing.T) { assert := assert.New(t) qemuArchBase := newQemuArchBase() p, err := qemuArchBase.qemuPath() assert.NoError(err) assert.Equal(p, qemuArchBaseQemuPath) paths := map[string]string{ "bad": qemuArchBaseQemuPath, } qemuArchBase.qemuPaths = paths p, err = qemuArchBase.qemuPath() assert.Error(err) assert.Equal("", p) } 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() c := qemuArchBase.capabilities() assert.True(c.IsBlockDeviceHotplugSupported()) } 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.machineType = QemuPC q.bridges(1) for i := uint32(1); i <= types.PCIBridgeMaxCapacity; i++ { _, _, err := q.addDeviceToBridge(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("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.machineType = QemuPCLite q.bridges(0) _, _, err = q.addDeviceToBridge("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: defaultMaxQemuVCPUs, Cores: defaultCores, Threads: defaultThreads, MaxCPUs: defaultMaxQemuVCPUs, } smp := qemuArchBase.cpuTopology(vcpus, defaultMaxQemuVCPUs) 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(devices, s) case types.Socket: devices = qemuArchBase.appendSocket(devices, s) case config.BlockDrive: devices, err = qemuArchBase.appendBlockDevice(devices, s) case config.VFIODev: devices = qemuArchBase.appendVFIODevice(devices, s) case config.VhostUserDeviceAttrs: devices, err = qemuArchBase.appendVhostUserDevice(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(store.SandboxRuntimeRootPath(sandboxID), consoleSocket) expectedOut := []govmmQemu.Device{ govmmQemu.SerialDevice{ Driver: govmmQemu.VirtioSerial, ID: "serial0", }, govmmQemu.CharDevice{ Driver: govmmQemu.Console, Backend: govmmQemu.Socket, DeviceID: "console0", ID: "charconsole0", Path: path, }, } devices, err = qemuArchBase.appendConsole(devices, path) assert.NoError(err) assert.Equal(expectedOut, devices) } func TestQemuArchBaseAppendImage(t *testing.T) { var devices []govmmQemu.Device assert := assert.New(t) qemuArchBase := newQemuArchBase() image, err := ioutil.TempFile("", "img") assert.NoError(err) err = image.Close() assert.NoError(err) devices, err = qemuArchBase.appendImage(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", }, } 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: true, Addr: "2", }, } 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, }, } 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: config.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(devices, false) assert.Equal(expectedOut, devices) assert.Nil(ioThread) assert.NoError(err) _, ioThread, err = qemuArchBase.appendSCSIController(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 := &BridgedMacvlanEndpoint{ 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: BridgedMacvlanEndpointType, } 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(devices, macvlanEp) assert.NoError(err) devices, err = qemuArchBase.appendNetwork(devices, macvtapEp) assert.NoError(err) assert.Equal(expectedOut, devices) }