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 }