github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/qemu_test.go (about)

     1  // Copyright (c) 2016 Intel Corporation
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package virtcontainers
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"testing"
    16  
    17  	govmmQemu "github.com/kata-containers/govmm/qemu"
    18  	"github.com/kata-containers/runtime/virtcontainers/device/config"
    19  	"github.com/kata-containers/runtime/virtcontainers/persist"
    20  	"github.com/kata-containers/runtime/virtcontainers/types"
    21  	"github.com/kata-containers/runtime/virtcontainers/utils"
    22  	"github.com/pkg/errors"
    23  	"github.com/stretchr/testify/assert"
    24  )
    25  
    26  func newQemuConfig() HypervisorConfig {
    27  	return HypervisorConfig{
    28  		KernelPath:        testQemuKernelPath,
    29  		ImagePath:         testQemuImagePath,
    30  		InitrdPath:        testQemuInitrdPath,
    31  		HypervisorPath:    testQemuPath,
    32  		NumVCPUs:          defaultVCPUs,
    33  		MemorySize:        defaultMemSzMiB,
    34  		DefaultBridges:    defaultBridges,
    35  		BlockDeviceDriver: defaultBlockDriver,
    36  		DefaultMaxVCPUs:   defaultMaxQemuVCPUs,
    37  		Msize9p:           defaultMsize9p,
    38  	}
    39  }
    40  
    41  func testQemuKernelParameters(t *testing.T, kernelParams []Param, expected string, debug bool) {
    42  	qemuConfig := newQemuConfig()
    43  	qemuConfig.KernelParams = kernelParams
    44  	assert := assert.New(t)
    45  
    46  	if debug == true {
    47  		qemuConfig.Debug = true
    48  	}
    49  
    50  	q := &qemu{
    51  		config: qemuConfig,
    52  		arch:   &qemuArchBase{},
    53  	}
    54  
    55  	params := q.kernelParameters()
    56  	assert.Equal(params, expected)
    57  }
    58  
    59  func TestQemuKernelParameters(t *testing.T) {
    60  	expectedOut := fmt.Sprintf("panic=1 nr_cpus=%d agent.use_vsock=false foo=foo bar=bar", MaxQemuVCPUs())
    61  	params := []Param{
    62  		{
    63  			Key:   "foo",
    64  			Value: "foo",
    65  		},
    66  		{
    67  			Key:   "bar",
    68  			Value: "bar",
    69  		},
    70  	}
    71  
    72  	testQemuKernelParameters(t, params, expectedOut, true)
    73  	testQemuKernelParameters(t, params, expectedOut, false)
    74  }
    75  
    76  func TestQemuCreateSandbox(t *testing.T) {
    77  	qemuConfig := newQemuConfig()
    78  	assert := assert.New(t)
    79  
    80  	store, err := persist.GetDriver()
    81  	assert.NoError(err)
    82  	q := &qemu{
    83  		store: store,
    84  	}
    85  	sandbox := &Sandbox{
    86  		ctx: context.Background(),
    87  		id:  "testSandbox",
    88  		config: &SandboxConfig{
    89  			HypervisorConfig: qemuConfig,
    90  		},
    91  	}
    92  
    93  	// Create the hypervisor fake binary
    94  	testQemuPath := filepath.Join(testDir, testHypervisor)
    95  	_, err = os.Create(testQemuPath)
    96  	assert.NoError(err)
    97  
    98  	// Create parent dir path for hypervisor.json
    99  	parentDir := filepath.Join(q.store.RunStoragePath(), sandbox.id)
   100  	assert.NoError(os.MkdirAll(parentDir, DirMode))
   101  
   102  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   103  	assert.NoError(err)
   104  	assert.NoError(os.RemoveAll(parentDir))
   105  	assert.Exactly(qemuConfig, q.config)
   106  }
   107  
   108  func TestQemuCreateSandboxMissingParentDirFail(t *testing.T) {
   109  	qemuConfig := newQemuConfig()
   110  	assert := assert.New(t)
   111  
   112  	store, err := persist.GetDriver()
   113  	assert.NoError(err)
   114  	q := &qemu{
   115  		store: store,
   116  	}
   117  	sandbox := &Sandbox{
   118  		ctx: context.Background(),
   119  		id:  "testSandbox",
   120  		config: &SandboxConfig{
   121  			HypervisorConfig: qemuConfig,
   122  		},
   123  	}
   124  
   125  	// Create the hypervisor fake binary
   126  	testQemuPath := filepath.Join(testDir, testHypervisor)
   127  	_, err = os.Create(testQemuPath)
   128  	assert.NoError(err)
   129  
   130  	// Ensure parent dir path for hypervisor.json does not exist.
   131  	parentDir := filepath.Join(q.store.RunStoragePath(), sandbox.id)
   132  	assert.NoError(os.RemoveAll(parentDir))
   133  
   134  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   135  	assert.NoError(err)
   136  }
   137  
   138  func TestQemuCPUTopology(t *testing.T) {
   139  	assert := assert.New(t)
   140  	vcpus := 1
   141  
   142  	q := &qemu{
   143  		arch: &qemuArchBase{},
   144  		config: HypervisorConfig{
   145  			NumVCPUs:        uint32(vcpus),
   146  			DefaultMaxVCPUs: uint32(vcpus),
   147  		},
   148  	}
   149  
   150  	expectedOut := govmmQemu.SMP{
   151  		CPUs:    uint32(vcpus),
   152  		Sockets: uint32(vcpus),
   153  		Cores:   defaultCores,
   154  		Threads: defaultThreads,
   155  		MaxCPUs: uint32(vcpus),
   156  	}
   157  
   158  	smp := q.cpuTopology()
   159  	assert.Exactly(smp, expectedOut)
   160  }
   161  
   162  func TestQemuMemoryTopology(t *testing.T) {
   163  	mem := uint32(1000)
   164  	slots := uint32(8)
   165  	assert := assert.New(t)
   166  
   167  	q := &qemu{
   168  		arch: &qemuArchBase{},
   169  		config: HypervisorConfig{
   170  			MemorySize: mem,
   171  			MemSlots:   slots,
   172  		},
   173  	}
   174  
   175  	hostMemKb, err := getHostMemorySizeKb(procMemInfo)
   176  	assert.NoError(err)
   177  	memMax := fmt.Sprintf("%dM", int(float64(hostMemKb)/1024))
   178  
   179  	expectedOut := govmmQemu.Memory{
   180  		Size:   fmt.Sprintf("%dM", mem),
   181  		Slots:  uint8(slots),
   182  		MaxMem: memMax,
   183  	}
   184  
   185  	memory, err := q.memoryTopology()
   186  	assert.NoError(err)
   187  	assert.Exactly(memory, expectedOut)
   188  }
   189  
   190  func TestQemuKnobs(t *testing.T) {
   191  	assert := assert.New(t)
   192  
   193  	sandbox, err := createQemuSandboxConfig()
   194  	assert.NoError(err)
   195  
   196  	q := &qemu{
   197  		store: sandbox.newStore,
   198  	}
   199  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   200  	assert.NoError(err)
   201  
   202  	assert.Equal(q.qemuConfig.Knobs.NoUserConfig, true)
   203  	assert.Equal(q.qemuConfig.Knobs.NoDefaults, true)
   204  	assert.Equal(q.qemuConfig.Knobs.NoGraphic, true)
   205  	assert.Equal(q.qemuConfig.Knobs.NoReboot, true)
   206  }
   207  
   208  func testQemuAddDevice(t *testing.T, devInfo interface{}, devType deviceType, expected []govmmQemu.Device) {
   209  	assert := assert.New(t)
   210  	q := &qemu{
   211  		ctx:  context.Background(),
   212  		arch: &qemuArchBase{},
   213  	}
   214  
   215  	err := q.addDevice(devInfo, devType)
   216  	assert.NoError(err)
   217  	assert.Exactly(q.qemuConfig.Devices, expected)
   218  }
   219  
   220  func TestQemuAddDeviceFsDev(t *testing.T) {
   221  	mountTag := "testMountTag"
   222  	hostPath := "testHostPath"
   223  
   224  	expectedOut := []govmmQemu.Device{
   225  		govmmQemu.FSDevice{
   226  			Driver:        govmmQemu.Virtio9P,
   227  			FSDriver:      govmmQemu.Local,
   228  			ID:            fmt.Sprintf("extra-9p-%s", mountTag),
   229  			Path:          hostPath,
   230  			MountTag:      mountTag,
   231  			SecurityModel: govmmQemu.None,
   232  			Multidev:      govmmQemu.Remap,
   233  		},
   234  	}
   235  
   236  	volume := types.Volume{
   237  		MountTag: mountTag,
   238  		HostPath: hostPath,
   239  	}
   240  
   241  	testQemuAddDevice(t, volume, fsDev, expectedOut)
   242  }
   243  
   244  func TestQemuAddDeviceVhostUserBlk(t *testing.T) {
   245  	socketPath := "/test/socket/path"
   246  	devID := "testDevID"
   247  
   248  	expectedOut := []govmmQemu.Device{
   249  		govmmQemu.VhostUserDevice{
   250  			SocketPath:    socketPath,
   251  			CharDevID:     utils.MakeNameID("char", devID, maxDevIDSize),
   252  			VhostUserType: govmmQemu.VhostUserBlk,
   253  		},
   254  	}
   255  
   256  	vDevice := config.VhostUserDeviceAttrs{
   257  		DevID:      devID,
   258  		SocketPath: socketPath,
   259  		Type:       config.VhostUserBlk,
   260  	}
   261  
   262  	testQemuAddDevice(t, vDevice, vhostuserDev, expectedOut)
   263  }
   264  
   265  func TestQemuAddDeviceSerialPortDev(t *testing.T) {
   266  	deviceID := "channelTest"
   267  	id := "charchTest"
   268  	hostPath := "/tmp/hyper_test.sock"
   269  	name := "sh.hyper.channel.test"
   270  
   271  	expectedOut := []govmmQemu.Device{
   272  		govmmQemu.CharDevice{
   273  			Driver:   govmmQemu.VirtioSerialPort,
   274  			Backend:  govmmQemu.Socket,
   275  			DeviceID: deviceID,
   276  			ID:       id,
   277  			Path:     hostPath,
   278  			Name:     name,
   279  		},
   280  	}
   281  
   282  	socket := types.Socket{
   283  		DeviceID: deviceID,
   284  		ID:       id,
   285  		HostPath: hostPath,
   286  		Name:     name,
   287  	}
   288  
   289  	testQemuAddDevice(t, socket, serialPortDev, expectedOut)
   290  }
   291  
   292  func TestQemuAddDeviceKataVSOCK(t *testing.T) {
   293  	assert := assert.New(t)
   294  
   295  	dir, err := ioutil.TempDir("", "")
   296  	assert.NoError(err)
   297  	defer os.RemoveAll(dir)
   298  
   299  	vsockFilename := filepath.Join(dir, "vsock")
   300  
   301  	contextID := uint64(3)
   302  	port := uint32(1024)
   303  
   304  	vsockFile, err := os.Create(vsockFilename)
   305  	assert.NoError(err)
   306  	defer vsockFile.Close()
   307  
   308  	expectedOut := []govmmQemu.Device{
   309  		govmmQemu.VSOCKDevice{
   310  			ID:        fmt.Sprintf("vsock-%d", contextID),
   311  			ContextID: contextID,
   312  			VHostFD:   vsockFile,
   313  		},
   314  	}
   315  
   316  	vsock := types.VSock{
   317  		ContextID: contextID,
   318  		Port:      port,
   319  		VhostFd:   vsockFile,
   320  	}
   321  
   322  	testQemuAddDevice(t, vsock, vSockPCIDev, expectedOut)
   323  }
   324  
   325  func TestQemuGetSandboxConsole(t *testing.T) {
   326  	assert := assert.New(t)
   327  	store, err := persist.GetDriver()
   328  	assert.NoError(err)
   329  	q := &qemu{
   330  		ctx:   context.Background(),
   331  		store: store,
   332  	}
   333  	sandboxID := "testSandboxID"
   334  	expected := filepath.Join(q.store.RunVMStoragePath(), sandboxID, consoleSocket)
   335  
   336  	result, err := q.getSandboxConsole(sandboxID)
   337  	assert.NoError(err)
   338  	assert.Equal(result, expected)
   339  }
   340  
   341  func TestQemuCapabilities(t *testing.T) {
   342  	assert := assert.New(t)
   343  	q := &qemu{
   344  		ctx:  context.Background(),
   345  		arch: &qemuArchBase{},
   346  	}
   347  
   348  	caps := q.capabilities()
   349  	assert.True(caps.IsBlockDeviceHotplugSupported())
   350  }
   351  
   352  func TestQemuQemuPath(t *testing.T) {
   353  	assert := assert.New(t)
   354  
   355  	f, err := ioutil.TempFile("", "qemu")
   356  	assert.NoError(err)
   357  	defer func() { _ = f.Close() }()
   358  	defer func() { _ = os.Remove(f.Name()) }()
   359  
   360  	expectedPath := f.Name()
   361  	qemuConfig := newQemuConfig()
   362  	qemuConfig.HypervisorPath = expectedPath
   363  	qkvm := &qemuArchBase{
   364  		machineType: "pc",
   365  		qemuPaths: map[string]string{
   366  			"pc": expectedPath,
   367  		},
   368  	}
   369  
   370  	q := &qemu{
   371  		config: qemuConfig,
   372  		arch:   qkvm,
   373  	}
   374  
   375  	// get config hypervisor path
   376  	path, err := q.qemuPath()
   377  	assert.NoError(err)
   378  	assert.Equal(path, expectedPath)
   379  
   380  	// config hypervisor path does not exist
   381  	q.config.HypervisorPath = "/abc/rgb/123"
   382  	path, err = q.qemuPath()
   383  	assert.Error(err)
   384  	assert.Equal(path, "")
   385  
   386  	// get arch hypervisor path
   387  	q.config.HypervisorPath = ""
   388  	path, err = q.qemuPath()
   389  	assert.NoError(err)
   390  	assert.Equal(path, expectedPath)
   391  
   392  	// bad machine type, arch should fail
   393  	qkvm.machineType = "rgb"
   394  	q.arch = qkvm
   395  	path, err = q.qemuPath()
   396  	assert.Error(err)
   397  	assert.Equal(path, "")
   398  }
   399  
   400  func TestHotplugUnsupportedDeviceType(t *testing.T) {
   401  	assert := assert.New(t)
   402  
   403  	qemuConfig := newQemuConfig()
   404  	q := &qemu{
   405  		ctx:    context.Background(),
   406  		id:     "qemuTest",
   407  		config: qemuConfig,
   408  	}
   409  
   410  	_, err := q.hotplugAddDevice(&memoryDevice{0, 128, uint64(0), false}, fsDev)
   411  	assert.Error(err)
   412  	_, err = q.hotplugRemoveDevice(&memoryDevice{0, 128, uint64(0), false}, fsDev)
   413  	assert.Error(err)
   414  }
   415  
   416  func TestQMPSetupShutdown(t *testing.T) {
   417  	assert := assert.New(t)
   418  
   419  	qemuConfig := newQemuConfig()
   420  	q := &qemu{
   421  		config: qemuConfig,
   422  	}
   423  
   424  	q.qmpShutdown()
   425  
   426  	q.qmpMonitorCh.qmp = &govmmQemu.QMP{}
   427  	err := q.qmpSetup()
   428  	assert.Nil(err)
   429  }
   430  
   431  func TestQemuCleanup(t *testing.T) {
   432  	assert := assert.New(t)
   433  
   434  	q := &qemu{
   435  		ctx:    context.Background(),
   436  		config: newQemuConfig(),
   437  	}
   438  
   439  	err := q.cleanup()
   440  	assert.Nil(err)
   441  }
   442  
   443  func TestQemuGrpc(t *testing.T) {
   444  	assert := assert.New(t)
   445  
   446  	config := newQemuConfig()
   447  	q := &qemu{
   448  		id:     "testqemu",
   449  		config: config,
   450  	}
   451  
   452  	json, err := q.toGrpc()
   453  	assert.Nil(err)
   454  
   455  	var q2 qemu
   456  	err = q2.fromGrpc(context.Background(), &config, json)
   457  	assert.Nil(err)
   458  
   459  	assert.True(q.id == q2.id)
   460  }
   461  
   462  func TestQemuFileBackedMem(t *testing.T) {
   463  	assert := assert.New(t)
   464  
   465  	// Check default Filebackedmem location for virtio-fs
   466  	sandbox, err := createQemuSandboxConfig()
   467  	assert.NoError(err)
   468  
   469  	q := &qemu{
   470  		store: sandbox.newStore,
   471  	}
   472  	sandbox.config.HypervisorConfig.SharedFS = config.VirtioFS
   473  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   474  	assert.NoError(err)
   475  
   476  	assert.Equal(q.qemuConfig.Knobs.FileBackedMem, true)
   477  	assert.Equal(q.qemuConfig.Knobs.MemShared, true)
   478  	assert.Equal(q.qemuConfig.Memory.Path, fallbackFileBackedMemDir)
   479  
   480  	// Check failure for VM templating
   481  	sandbox, err = createQemuSandboxConfig()
   482  	assert.NoError(err)
   483  
   484  	q = &qemu{
   485  		store: sandbox.newStore,
   486  	}
   487  	sandbox.config.HypervisorConfig.BootToBeTemplate = true
   488  	sandbox.config.HypervisorConfig.SharedFS = config.VirtioFS
   489  	sandbox.config.HypervisorConfig.MemoryPath = fallbackFileBackedMemDir
   490  
   491  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   492  
   493  	expectErr := errors.New("VM templating has been enabled with either virtio-fs or file backed memory and this configuration will not work")
   494  	assert.Equal(expectErr.Error(), err.Error())
   495  
   496  	// Check Setting of non-existent shared-mem path
   497  	sandbox, err = createQemuSandboxConfig()
   498  	assert.NoError(err)
   499  
   500  	q = &qemu{
   501  		store: sandbox.newStore,
   502  	}
   503  	sandbox.config.HypervisorConfig.FileBackedMemRootDir = "/tmp/xyzabc"
   504  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   505  	assert.NoError(err)
   506  	assert.Equal(q.qemuConfig.Knobs.FileBackedMem, false)
   507  	assert.Equal(q.qemuConfig.Knobs.MemShared, false)
   508  	assert.Equal(q.qemuConfig.Memory.Path, "")
   509  
   510  	// Check setting vhost-user storage with Hugepages
   511  	sandbox, err = createQemuSandboxConfig()
   512  	assert.NoError(err)
   513  
   514  	q = &qemu{
   515  		store: sandbox.newStore,
   516  	}
   517  	sandbox.config.HypervisorConfig.EnableVhostUserStore = true
   518  	sandbox.config.HypervisorConfig.HugePages = true
   519  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   520  	assert.NoError(err)
   521  	assert.Equal(q.qemuConfig.Knobs.MemShared, true)
   522  
   523  	// Check failure for vhost-user storage
   524  	sandbox, err = createQemuSandboxConfig()
   525  	assert.NoError(err)
   526  
   527  	q = &qemu{
   528  		store: sandbox.newStore,
   529  	}
   530  	sandbox.config.HypervisorConfig.EnableVhostUserStore = true
   531  	sandbox.config.HypervisorConfig.HugePages = false
   532  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   533  
   534  	expectErr = errors.New("Vhost-user-blk/scsi is enabled without HugePages. This configuration will not work")
   535  	assert.Equal(expectErr.Error(), err.Error())
   536  }
   537  
   538  func createQemuSandboxConfig() (*Sandbox, error) {
   539  
   540  	qemuConfig := newQemuConfig()
   541  	sandbox := Sandbox{
   542  		ctx: context.Background(),
   543  		id:  "testSandbox",
   544  		config: &SandboxConfig{
   545  			HypervisorConfig: qemuConfig,
   546  		},
   547  	}
   548  
   549  	newStore, err := persist.GetDriver()
   550  	if err != nil {
   551  		return &Sandbox{}, err
   552  	}
   553  	sandbox.newStore = newStore
   554  
   555  	return &sandbox, nil
   556  }
   557  
   558  func TestQemuVirtiofsdArgs(t *testing.T) {
   559  	assert := assert.New(t)
   560  
   561  	q := &qemu{
   562  		id: "foo",
   563  		config: HypervisorConfig{
   564  			VirtioFSCache: "none",
   565  			Debug:         true,
   566  		},
   567  	}
   568  
   569  	savedKataHostSharedDir := kataHostSharedDir
   570  	kataHostSharedDir = func() string {
   571  		return "test-share-dir"
   572  	}
   573  	defer func() {
   574  		kataHostSharedDir = savedKataHostSharedDir
   575  	}()
   576  
   577  	result := "--fd=123 -o source=test-share-dir/foo/shared -o cache=none --syslog -o no_posix_lock -d"
   578  	args := q.virtiofsdArgs(123)
   579  	assert.Equal(strings.Join(args, " "), result)
   580  
   581  	q.config.Debug = false
   582  	result = "--fd=123 -o source=test-share-dir/foo/shared -o cache=none --syslog -o no_posix_lock -f"
   583  	args = q.virtiofsdArgs(123)
   584  	assert.Equal(strings.Join(args, " "), result)
   585  }
   586  
   587  func TestQemuGetpids(t *testing.T) {
   588  	assert := assert.New(t)
   589  
   590  	qemuConfig := newQemuConfig()
   591  	q := &qemu{}
   592  	pids := q.getPids()
   593  	assert.NotNil(pids)
   594  	assert.True(len(pids) == 1)
   595  	assert.True(pids[0] == 0)
   596  
   597  	q = &qemu{
   598  		config: qemuConfig,
   599  	}
   600  	f, err := ioutil.TempFile("", "qemu-test-")
   601  	assert.Nil(err)
   602  	tmpfile := f.Name()
   603  	f.Close()
   604  	defer os.Remove(tmpfile)
   605  
   606  	q.qemuConfig.PidFile = tmpfile
   607  	pids = q.getPids()
   608  	assert.True(len(pids) == 1)
   609  	assert.True(pids[0] == 0)
   610  
   611  	err = ioutil.WriteFile(tmpfile, []byte("100"), 0)
   612  	assert.Nil(err)
   613  	pids = q.getPids()
   614  	assert.True(len(pids) == 1)
   615  	assert.True(pids[0] == 100)
   616  
   617  	q.state.VirtiofsdPid = 200
   618  	pids = q.getPids()
   619  	assert.True(len(pids) == 2)
   620  	assert.True(pids[0] == 100)
   621  	assert.True(pids[1] == 200)
   622  }