gitee.com/leisunstar/runtime@v0.0.0-20200521203717-5cef3e7b53f9/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/intel/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 testQemuAddDevice(t *testing.T, devInfo interface{}, devType deviceType, expected []govmmQemu.Device) {
   191  	assert := assert.New(t)
   192  	q := &qemu{
   193  		ctx:  context.Background(),
   194  		arch: &qemuArchBase{},
   195  	}
   196  
   197  	err := q.addDevice(devInfo, devType)
   198  	assert.NoError(err)
   199  	assert.Exactly(q.qemuConfig.Devices, expected)
   200  }
   201  
   202  func TestQemuAddDeviceFsDev(t *testing.T) {
   203  	mountTag := "testMountTag"
   204  	hostPath := "testHostPath"
   205  
   206  	expectedOut := []govmmQemu.Device{
   207  		govmmQemu.FSDevice{
   208  			Driver:        govmmQemu.Virtio9P,
   209  			FSDriver:      govmmQemu.Local,
   210  			ID:            fmt.Sprintf("extra-9p-%s", mountTag),
   211  			Path:          hostPath,
   212  			MountTag:      mountTag,
   213  			SecurityModel: govmmQemu.None,
   214  		},
   215  	}
   216  
   217  	volume := types.Volume{
   218  		MountTag: mountTag,
   219  		HostPath: hostPath,
   220  	}
   221  
   222  	testQemuAddDevice(t, volume, fsDev, expectedOut)
   223  }
   224  
   225  func TestQemuAddDeviceVhostUserBlk(t *testing.T) {
   226  	socketPath := "/test/socket/path"
   227  	devID := "testDevID"
   228  
   229  	expectedOut := []govmmQemu.Device{
   230  		govmmQemu.VhostUserDevice{
   231  			SocketPath:    socketPath,
   232  			CharDevID:     utils.MakeNameID("char", devID, maxDevIDSize),
   233  			VhostUserType: govmmQemu.VhostUserBlk,
   234  		},
   235  	}
   236  
   237  	vDevice := config.VhostUserDeviceAttrs{
   238  		DevID:      devID,
   239  		SocketPath: socketPath,
   240  		Type:       config.VhostUserBlk,
   241  	}
   242  
   243  	testQemuAddDevice(t, vDevice, vhostuserDev, expectedOut)
   244  }
   245  
   246  func TestQemuAddDeviceSerialPortDev(t *testing.T) {
   247  	deviceID := "channelTest"
   248  	id := "charchTest"
   249  	hostPath := "/tmp/hyper_test.sock"
   250  	name := "sh.hyper.channel.test"
   251  
   252  	expectedOut := []govmmQemu.Device{
   253  		govmmQemu.CharDevice{
   254  			Driver:   govmmQemu.VirtioSerialPort,
   255  			Backend:  govmmQemu.Socket,
   256  			DeviceID: deviceID,
   257  			ID:       id,
   258  			Path:     hostPath,
   259  			Name:     name,
   260  		},
   261  	}
   262  
   263  	socket := types.Socket{
   264  		DeviceID: deviceID,
   265  		ID:       id,
   266  		HostPath: hostPath,
   267  		Name:     name,
   268  	}
   269  
   270  	testQemuAddDevice(t, socket, serialPortDev, expectedOut)
   271  }
   272  
   273  func TestQemuAddDeviceKataVSOCK(t *testing.T) {
   274  	assert := assert.New(t)
   275  
   276  	dir, err := ioutil.TempDir("", "")
   277  	assert.NoError(err)
   278  	defer os.RemoveAll(dir)
   279  
   280  	vsockFilename := filepath.Join(dir, "vsock")
   281  
   282  	contextID := uint64(3)
   283  	port := uint32(1024)
   284  
   285  	vsockFile, err := os.Create(vsockFilename)
   286  	assert.NoError(err)
   287  	defer vsockFile.Close()
   288  
   289  	expectedOut := []govmmQemu.Device{
   290  		govmmQemu.VSOCKDevice{
   291  			ID:        fmt.Sprintf("vsock-%d", contextID),
   292  			ContextID: contextID,
   293  			VHostFD:   vsockFile,
   294  		},
   295  	}
   296  
   297  	vsock := types.VSock{
   298  		ContextID: contextID,
   299  		Port:      port,
   300  		VhostFd:   vsockFile,
   301  	}
   302  
   303  	testQemuAddDevice(t, vsock, vSockPCIDev, expectedOut)
   304  }
   305  
   306  func TestQemuGetSandboxConsole(t *testing.T) {
   307  	assert := assert.New(t)
   308  	store, err := persist.GetDriver()
   309  	assert.NoError(err)
   310  	q := &qemu{
   311  		ctx:   context.Background(),
   312  		store: store,
   313  	}
   314  	sandboxID := "testSandboxID"
   315  	expected := filepath.Join(q.store.RunVMStoragePath(), sandboxID, consoleSocket)
   316  
   317  	result, err := q.getSandboxConsole(sandboxID)
   318  	assert.NoError(err)
   319  	assert.Equal(result, expected)
   320  }
   321  
   322  func TestQemuCapabilities(t *testing.T) {
   323  	assert := assert.New(t)
   324  	q := &qemu{
   325  		ctx:  context.Background(),
   326  		arch: &qemuArchBase{},
   327  	}
   328  
   329  	caps := q.capabilities()
   330  	assert.True(caps.IsBlockDeviceHotplugSupported())
   331  }
   332  
   333  func TestQemuQemuPath(t *testing.T) {
   334  	assert := assert.New(t)
   335  
   336  	f, err := ioutil.TempFile("", "qemu")
   337  	assert.NoError(err)
   338  	defer func() { _ = f.Close() }()
   339  	defer func() { _ = os.Remove(f.Name()) }()
   340  
   341  	expectedPath := f.Name()
   342  	qemuConfig := newQemuConfig()
   343  	qemuConfig.HypervisorPath = expectedPath
   344  	qkvm := &qemuArchBase{
   345  		machineType: "pc",
   346  		qemuPaths: map[string]string{
   347  			"pc": expectedPath,
   348  		},
   349  	}
   350  
   351  	q := &qemu{
   352  		config: qemuConfig,
   353  		arch:   qkvm,
   354  	}
   355  
   356  	// get config hypervisor path
   357  	path, err := q.qemuPath()
   358  	assert.NoError(err)
   359  	assert.Equal(path, expectedPath)
   360  
   361  	// config hypervisor path does not exist
   362  	q.config.HypervisorPath = "/abc/rgb/123"
   363  	path, err = q.qemuPath()
   364  	assert.Error(err)
   365  	assert.Equal(path, "")
   366  
   367  	// get arch hypervisor path
   368  	q.config.HypervisorPath = ""
   369  	path, err = q.qemuPath()
   370  	assert.NoError(err)
   371  	assert.Equal(path, expectedPath)
   372  
   373  	// bad machine type, arch should fail
   374  	qkvm.machineType = "rgb"
   375  	q.arch = qkvm
   376  	path, err = q.qemuPath()
   377  	assert.Error(err)
   378  	assert.Equal(path, "")
   379  }
   380  
   381  func TestHotplugUnsupportedDeviceType(t *testing.T) {
   382  	assert := assert.New(t)
   383  
   384  	qemuConfig := newQemuConfig()
   385  	q := &qemu{
   386  		ctx:    context.Background(),
   387  		id:     "qemuTest",
   388  		config: qemuConfig,
   389  	}
   390  
   391  	_, err := q.hotplugAddDevice(&memoryDevice{0, 128, uint64(0), false}, fsDev)
   392  	assert.Error(err)
   393  	_, err = q.hotplugRemoveDevice(&memoryDevice{0, 128, uint64(0), false}, fsDev)
   394  	assert.Error(err)
   395  }
   396  
   397  func TestQMPSetupShutdown(t *testing.T) {
   398  	assert := assert.New(t)
   399  
   400  	qemuConfig := newQemuConfig()
   401  	q := &qemu{
   402  		config: qemuConfig,
   403  	}
   404  
   405  	q.qmpShutdown()
   406  
   407  	q.qmpMonitorCh.qmp = &govmmQemu.QMP{}
   408  	err := q.qmpSetup()
   409  	assert.Nil(err)
   410  }
   411  
   412  func TestQemuCleanup(t *testing.T) {
   413  	assert := assert.New(t)
   414  
   415  	q := &qemu{
   416  		ctx:    context.Background(),
   417  		config: newQemuConfig(),
   418  	}
   419  
   420  	err := q.cleanup()
   421  	assert.Nil(err)
   422  }
   423  
   424  func TestQemuGrpc(t *testing.T) {
   425  	assert := assert.New(t)
   426  
   427  	config := newQemuConfig()
   428  	q := &qemu{
   429  		id:     "testqemu",
   430  		config: config,
   431  	}
   432  
   433  	json, err := q.toGrpc()
   434  	assert.Nil(err)
   435  
   436  	var q2 qemu
   437  	err = q2.fromGrpc(context.Background(), &config, json)
   438  	assert.Nil(err)
   439  
   440  	assert.True(q.id == q2.id)
   441  }
   442  
   443  func TestQemuFileBackedMem(t *testing.T) {
   444  	assert := assert.New(t)
   445  
   446  	// Check default Filebackedmem location for virtio-fs
   447  	sandbox, err := createQemuSandboxConfig()
   448  	assert.NoError(err)
   449  
   450  	q := &qemu{
   451  		store: sandbox.newStore,
   452  	}
   453  	sandbox.config.HypervisorConfig.SharedFS = config.VirtioFS
   454  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   455  	assert.NoError(err)
   456  
   457  	assert.Equal(q.qemuConfig.Knobs.FileBackedMem, true)
   458  	assert.Equal(q.qemuConfig.Knobs.MemShared, true)
   459  	assert.Equal(q.qemuConfig.Memory.Path, fallbackFileBackedMemDir)
   460  
   461  	// Check failure for VM templating
   462  	sandbox, err = createQemuSandboxConfig()
   463  	assert.NoError(err)
   464  
   465  	q = &qemu{
   466  		store: sandbox.newStore,
   467  	}
   468  	sandbox.config.HypervisorConfig.BootToBeTemplate = true
   469  	sandbox.config.HypervisorConfig.SharedFS = config.VirtioFS
   470  	sandbox.config.HypervisorConfig.MemoryPath = fallbackFileBackedMemDir
   471  
   472  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   473  
   474  	expectErr := errors.New("VM templating has been enabled with either virtio-fs or file backed memory and this configuration will not work")
   475  	assert.Equal(expectErr.Error(), err.Error())
   476  
   477  	// Check Setting of non-existent shared-mem path
   478  	sandbox, err = createQemuSandboxConfig()
   479  	assert.NoError(err)
   480  
   481  	q = &qemu{
   482  		store: sandbox.newStore,
   483  	}
   484  	sandbox.config.HypervisorConfig.FileBackedMemRootDir = "/tmp/xyzabc"
   485  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   486  	assert.NoError(err)
   487  	assert.Equal(q.qemuConfig.Knobs.FileBackedMem, false)
   488  	assert.Equal(q.qemuConfig.Knobs.MemShared, false)
   489  	assert.Equal(q.qemuConfig.Memory.Path, "")
   490  
   491  	// Check setting vhost-user storage with Hugepages
   492  	sandbox, err = createQemuSandboxConfig()
   493  	assert.NoError(err)
   494  
   495  	q = &qemu{
   496  		store: sandbox.newStore,
   497  	}
   498  	sandbox.config.HypervisorConfig.EnableVhostUserStore = true
   499  	sandbox.config.HypervisorConfig.HugePages = true
   500  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   501  	assert.NoError(err)
   502  	assert.Equal(q.qemuConfig.Knobs.MemShared, true)
   503  
   504  	// Check failure for vhost-user storage
   505  	sandbox, err = createQemuSandboxConfig()
   506  	assert.NoError(err)
   507  
   508  	q = &qemu{
   509  		store: sandbox.newStore,
   510  	}
   511  	sandbox.config.HypervisorConfig.EnableVhostUserStore = true
   512  	sandbox.config.HypervisorConfig.HugePages = false
   513  	err = q.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   514  
   515  	expectErr = errors.New("Vhost-user-blk/scsi is enabled without HugePages. This configuration will not work")
   516  	assert.Equal(expectErr.Error(), err.Error())
   517  }
   518  
   519  func createQemuSandboxConfig() (*Sandbox, error) {
   520  
   521  	qemuConfig := newQemuConfig()
   522  	sandbox := Sandbox{
   523  		ctx: context.Background(),
   524  		id:  "testSandbox",
   525  		config: &SandboxConfig{
   526  			HypervisorConfig: qemuConfig,
   527  		},
   528  	}
   529  
   530  	newStore, err := persist.GetDriver()
   531  	if err != nil {
   532  		return &Sandbox{}, err
   533  	}
   534  	sandbox.newStore = newStore
   535  
   536  	return &sandbox, nil
   537  }
   538  
   539  func TestQemuVirtiofsdArgs(t *testing.T) {
   540  	assert := assert.New(t)
   541  
   542  	q := &qemu{
   543  		id: "foo",
   544  		config: HypervisorConfig{
   545  			VirtioFSCache: "none",
   546  			Debug:         true,
   547  		},
   548  	}
   549  
   550  	savedKataHostSharedDir := kataHostSharedDir
   551  	kataHostSharedDir = func() string {
   552  		return "test-share-dir"
   553  	}
   554  	defer func() {
   555  		kataHostSharedDir = savedKataHostSharedDir
   556  	}()
   557  
   558  	result := "--fd=123 -o source=test-share-dir/foo -o cache=none --syslog -o no_posix_lock -d"
   559  	args := q.virtiofsdArgs(123)
   560  	assert.Equal(strings.Join(args, " "), result)
   561  
   562  	q.config.Debug = false
   563  	result = "--fd=123 -o source=test-share-dir/foo -o cache=none --syslog -o no_posix_lock -f"
   564  	args = q.virtiofsdArgs(123)
   565  	assert.Equal(strings.Join(args, " "), result)
   566  }
   567  
   568  func TestQemuGetpids(t *testing.T) {
   569  	assert := assert.New(t)
   570  
   571  	qemuConfig := newQemuConfig()
   572  	q := &qemu{}
   573  	pids := q.getPids()
   574  	assert.NotNil(pids)
   575  	assert.True(len(pids) == 1)
   576  	assert.True(pids[0] == 0)
   577  
   578  	q = &qemu{
   579  		config: qemuConfig,
   580  	}
   581  	f, err := ioutil.TempFile("", "qemu-test-")
   582  	assert.Nil(err)
   583  	tmpfile := f.Name()
   584  	f.Close()
   585  	defer os.Remove(tmpfile)
   586  
   587  	q.qemuConfig.PidFile = tmpfile
   588  	pids = q.getPids()
   589  	assert.True(len(pids) == 1)
   590  	assert.True(pids[0] == 0)
   591  
   592  	err = ioutil.WriteFile(tmpfile, []byte("100"), 0)
   593  	assert.Nil(err)
   594  	pids = q.getPids()
   595  	assert.True(len(pids) == 1)
   596  	assert.True(pids[0] == 100)
   597  
   598  	q.state.VirtiofsdPid = 200
   599  	pids = q.getPids()
   600  	assert.True(len(pids) == 2)
   601  	assert.True(pids[0] == 100)
   602  	assert.True(pids[1] == 200)
   603  }