github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/client/driver/qemu_test.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "fmt" 6 "net" 7 "path/filepath" 8 "strconv" 9 "strings" 10 "syscall" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/consul/lib/freeport" 15 "github.com/hashicorp/nomad/client/config" 16 cstructs "github.com/hashicorp/nomad/client/structs" 17 "github.com/hashicorp/nomad/helper/testlog" 18 "github.com/hashicorp/nomad/nomad/structs" 19 "github.com/hashicorp/nomad/testutil" 20 21 ctestutils "github.com/hashicorp/nomad/client/testutil" 22 ) 23 24 // The fingerprinter test should always pass, even if QEMU is not installed. 25 func TestQemuDriver_Fingerprint(t *testing.T) { 26 if !testutil.IsTravis() { 27 t.Parallel() 28 } 29 ctestutils.QemuCompatible(t) 30 task := &structs.Task{ 31 Name: "foo", 32 Driver: "qemu", 33 Resources: structs.DefaultResources(), 34 } 35 ctx := testDriverContexts(t, task) 36 defer ctx.AllocDir.Destroy() 37 d := NewQemuDriver(ctx.DriverCtx) 38 39 node := &structs.Node{ 40 Attributes: make(map[string]string), 41 } 42 43 request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node} 44 var response cstructs.FingerprintResponse 45 err := d.Fingerprint(request, &response) 46 if err != nil { 47 t.Fatalf("err: %v", err) 48 } 49 50 if !response.Detected { 51 t.Fatalf("expected response to be applicable") 52 } 53 54 attributes := response.Attributes 55 if attributes == nil { 56 t.Fatalf("attributes should not be nil") 57 } 58 59 if attributes[qemuDriverAttr] == "" { 60 t.Fatalf("Missing Qemu driver") 61 } 62 63 if attributes[qemuDriverVersionAttr] == "" { 64 t.Fatalf("Missing Qemu driver version") 65 } 66 } 67 68 func TestQemuDriver_StartOpen_Wait(t *testing.T) { 69 logger := testlog.Logger(t) 70 if !testutil.IsTravis() { 71 t.Parallel() 72 } 73 ctestutils.QemuCompatible(t) 74 task := &structs.Task{ 75 Name: "linux", 76 Driver: "qemu", 77 Config: map[string]interface{}{ 78 "image_path": "linux-0.2.img", 79 "accelerator": "tcg", 80 "graceful_shutdown": false, 81 "port_map": []map[string]int{{ 82 "main": 22, 83 "web": 8080, 84 }}, 85 "args": []string{"-nodefconfig", "-nodefaults"}, 86 }, 87 LogConfig: &structs.LogConfig{ 88 MaxFiles: 10, 89 MaxFileSizeMB: 10, 90 }, 91 Resources: &structs.Resources{ 92 CPU: 500, 93 MemoryMB: 512, 94 Networks: []*structs.NetworkResource{ 95 { 96 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 97 }, 98 }, 99 }, 100 } 101 102 ctx := testDriverContexts(t, task) 103 defer ctx.AllocDir.Destroy() 104 d := NewQemuDriver(ctx.DriverCtx) 105 106 // Copy the test image into the task's directory 107 dst := ctx.ExecCtx.TaskDir.Dir 108 109 copyFile("./test-resources/qemu/linux-0.2.img", filepath.Join(dst, "linux-0.2.img"), t) 110 111 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 112 t.Fatalf("Prestart failed: %v", err) 113 } 114 115 resp, err := d.Start(ctx.ExecCtx, task) 116 if err != nil { 117 t.Fatalf("err: %v", err) 118 } 119 120 // Ensure that sending a Signal returns an error 121 if err := resp.Handle.Signal(syscall.SIGINT); err == nil { 122 t.Fatalf("Expect an error when signalling") 123 } 124 125 // Attempt to open 126 handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID()) 127 if err != nil { 128 t.Fatalf("err: %v", err) 129 } 130 if handle2 == nil { 131 t.Fatalf("missing handle") 132 } 133 134 // Clean up 135 if err := resp.Handle.Kill(); err != nil { 136 logger.Printf("Error killing Qemu test: %s", err) 137 } 138 } 139 140 func TestQemuDriver_GracefulShutdown(t *testing.T) { 141 testutil.SkipSlow(t) 142 if !testutil.IsTravis() { 143 t.Parallel() 144 } 145 ctestutils.QemuCompatible(t) 146 147 logger := testlog.Logger(t) 148 149 // Graceful shutdown may be really slow unfortunately 150 killTimeout := 3 * time.Minute 151 152 // Grab a free port so we can tell when the image has started 153 port := freeport.GetT(t, 1)[0] 154 155 task := &structs.Task{ 156 Name: "alpine-shutdown-test", 157 Driver: "qemu", 158 Config: map[string]interface{}{ 159 "image_path": "alpine.qcow2", 160 "graceful_shutdown": true, 161 "args": []string{"-nodefconfig", "-nodefaults"}, 162 "port_map": []map[string]int{{ 163 "ssh": 22, 164 }}, 165 }, 166 LogConfig: &structs.LogConfig{ 167 MaxFiles: 10, 168 MaxFileSizeMB: 10, 169 }, 170 Resources: &structs.Resources{ 171 CPU: 1000, 172 MemoryMB: 256, 173 Networks: []*structs.NetworkResource{ 174 { 175 ReservedPorts: []structs.Port{{Label: "ssh", Value: port}}, 176 }, 177 }, 178 }, 179 KillTimeout: killTimeout, 180 } 181 182 ctx := testDriverContexts(t, task) 183 ctx.DriverCtx.config.MaxKillTimeout = killTimeout 184 defer ctx.AllocDir.Destroy() 185 d := NewQemuDriver(ctx.DriverCtx) 186 187 request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: ctx.DriverCtx.node} 188 var response cstructs.FingerprintResponse 189 err := d.Fingerprint(request, &response) 190 if err != nil { 191 t.Fatalf("err: %v", err) 192 } 193 194 for name, value := range response.Attributes { 195 ctx.DriverCtx.node.Attributes[name] = value 196 } 197 198 dst := ctx.ExecCtx.TaskDir.Dir 199 200 copyFile("./test-resources/qemu/alpine.qcow2", filepath.Join(dst, "alpine.qcow2"), t) 201 202 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 203 t.Fatalf("Prestart failed: %v", err) 204 } 205 206 resp, err := d.Start(ctx.ExecCtx, task) 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 211 // Clean up 212 defer func() { 213 select { 214 case <-resp.Handle.WaitCh(): 215 // Already exited 216 return 217 default: 218 } 219 220 if err := resp.Handle.Kill(); err != nil { 221 logger.Printf("[TEST] Error killing Qemu test: %s", err) 222 } 223 }() 224 225 // Wait until sshd starts before attempting to do a graceful shutdown 226 testutil.WaitForResult(func() (bool, error) { 227 conn, err := net.Dial("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port))) 228 if err != nil { 229 return false, err 230 } 231 232 // Since the connection will be accepted by the QEMU process 233 // before sshd actually starts, we need to block until we can 234 // read the "SSH" magic bytes 235 header := make([]byte, 3) 236 conn.SetReadDeadline(time.Now().Add(10 * time.Second)) 237 _, err = conn.Read(header) 238 if err != nil { 239 return false, err 240 } 241 if !bytes.Equal(header, []byte{'S', 'S', 'H'}) { 242 return false, fmt.Errorf("expected 'SSH' but received: %q %v", string(header), header) 243 } 244 245 logger.Printf("[TEST] connected to sshd in VM") 246 conn.Close() 247 return true, nil 248 }, func(err error) { 249 t.Fatalf("failed to connect to sshd in VM: %v", err) 250 }) 251 252 monitorPath := filepath.Join(ctx.AllocDir.AllocDir, task.Name, qemuMonitorSocketName) 253 254 // userPid supplied in sendQemuShutdown calls is bogus (it's used only 255 // for log output) 256 if err := sendQemuShutdown(ctx.DriverCtx.logger, "", 0); err == nil { 257 t.Fatalf("sendQemuShutdown should return an error if monitorPath parameter is empty") 258 } 259 260 if err := sendQemuShutdown(ctx.DriverCtx.logger, "/path/that/does/not/exist", 0); err == nil { 261 t.Fatalf("sendQemuShutdown should return an error if file does not exist at monitorPath") 262 } 263 264 if err := sendQemuShutdown(ctx.DriverCtx.logger, monitorPath, 0); err != nil { 265 t.Fatalf("unexpected error from sendQemuShutdown: %s", err) 266 } 267 268 select { 269 case <-resp.Handle.WaitCh(): 270 logger.Printf("[TEST] VM exited gracefully as expected") 271 case <-time.After(killTimeout): 272 t.Fatalf("VM did not exit gracefully exit before timeout: %s", killTimeout) 273 } 274 } 275 276 func TestQemuDriverUser(t *testing.T) { 277 if !testutil.IsTravis() { 278 t.Parallel() 279 } 280 ctestutils.QemuCompatible(t) 281 tasks := []*structs.Task{ 282 { 283 Name: "linux", 284 Driver: "qemu", 285 User: "alice", 286 Config: map[string]interface{}{ 287 "image_path": "linux-0.2.img", 288 "accelerator": "tcg", 289 "graceful_shutdown": false, 290 "port_map": []map[string]int{{ 291 "main": 22, 292 "web": 8080, 293 }}, 294 "args": []string{"-nodefconfig", "-nodefaults"}, 295 "msg": "unknown user alice", 296 }, 297 LogConfig: &structs.LogConfig{ 298 MaxFiles: 10, 299 MaxFileSizeMB: 10, 300 }, 301 Resources: &structs.Resources{ 302 CPU: 500, 303 MemoryMB: 512, 304 Networks: []*structs.NetworkResource{ 305 { 306 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 307 }, 308 }, 309 }, 310 }, 311 { 312 Name: "linux", 313 Driver: "qemu", 314 User: "alice", 315 Config: map[string]interface{}{ 316 "image_path": "linux-0.2.img", 317 "accelerator": "tcg", 318 "port_map": []map[string]int{{ 319 "main": 22, 320 "web": 8080, 321 }}, 322 "args": []string{"-nodefconfig", "-nodefaults"}, 323 "msg": "Qemu memory assignment out of bounds", 324 }, 325 LogConfig: &structs.LogConfig{ 326 MaxFiles: 10, 327 MaxFileSizeMB: 10, 328 }, 329 Resources: &structs.Resources{ 330 CPU: 500, 331 MemoryMB: -1, 332 Networks: []*structs.NetworkResource{ 333 { 334 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 335 }, 336 }, 337 }, 338 }, 339 } 340 341 for _, task := range tasks { 342 ctx := testDriverContexts(t, task) 343 defer ctx.AllocDir.Destroy() 344 d := NewQemuDriver(ctx.DriverCtx) 345 346 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 347 t.Fatalf("Prestart faild: %v", err) 348 } 349 350 resp, err := d.Start(ctx.ExecCtx, task) 351 if err == nil { 352 resp.Handle.Kill() 353 t.Fatalf("Should've failed") 354 } 355 356 msg := task.Config["msg"].(string) 357 if !strings.Contains(err.Error(), msg) { 358 t.Fatalf("Expecting '%v' in '%v'", msg, err) 359 } 360 } 361 } 362 363 func TestQemuDriverGetMonitorPathOldQemu(t *testing.T) { 364 task := &structs.Task{ 365 Name: "linux", 366 Driver: "qemu", 367 Config: map[string]interface{}{ 368 "image_path": "linux-0.2.img", 369 "accelerator": "tcg", 370 "graceful_shutdown": true, 371 "port_map": []map[string]int{{ 372 "main": 22, 373 "web": 8080, 374 }}, 375 "args": []string{"-nodefconfig", "-nodefaults"}, 376 }, 377 KillTimeout: time.Duration(1 * time.Second), 378 LogConfig: &structs.LogConfig{ 379 MaxFiles: 10, 380 MaxFileSizeMB: 10, 381 }, 382 Resources: &structs.Resources{ 383 CPU: 500, 384 MemoryMB: 512, 385 Networks: []*structs.NetworkResource{ 386 { 387 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 388 }, 389 }, 390 }, 391 } 392 393 ctx := testDriverContexts(t, task) 394 defer ctx.AllocDir.Destroy() 395 396 // Simulate an older version of qemu which does not support long monitor socket paths 397 ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.0.0" 398 399 d := &QemuDriver{DriverContext: *ctx.DriverCtx} 400 401 shortPath := strings.Repeat("x", 10) 402 _, err := d.getMonitorPath(shortPath) 403 if err != nil { 404 t.Fatal("Should not have returned an error") 405 } 406 407 longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100) 408 _, err = d.getMonitorPath(longPath) 409 if err == nil { 410 t.Fatal("Should have returned an error") 411 } 412 413 // Max length includes the '/' separator and socket name 414 maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1 415 maxLengthLegacyPath := strings.Repeat("x", maxLengthCount) 416 _, err = d.getMonitorPath(maxLengthLegacyPath) 417 if err != nil { 418 t.Fatalf("Should not have returned an error: %s", err) 419 } 420 } 421 422 func TestQemuDriverGetMonitorPathNewQemu(t *testing.T) { 423 task := &structs.Task{ 424 Name: "linux", 425 Driver: "qemu", 426 Config: map[string]interface{}{ 427 "image_path": "linux-0.2.img", 428 "accelerator": "tcg", 429 "graceful_shutdown": true, 430 "port_map": []map[string]int{{ 431 "main": 22, 432 "web": 8080, 433 }}, 434 "args": []string{"-nodefconfig", "-nodefaults"}, 435 }, 436 KillTimeout: time.Duration(1 * time.Second), 437 LogConfig: &structs.LogConfig{ 438 MaxFiles: 10, 439 MaxFileSizeMB: 10, 440 }, 441 Resources: &structs.Resources{ 442 CPU: 500, 443 MemoryMB: 512, 444 Networks: []*structs.NetworkResource{ 445 { 446 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 447 }, 448 }, 449 }, 450 } 451 452 ctx := testDriverContexts(t, task) 453 defer ctx.AllocDir.Destroy() 454 455 // Simulate a version of qemu which supports long monitor socket paths 456 ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.99.99" 457 458 d := &QemuDriver{DriverContext: *ctx.DriverCtx} 459 460 shortPath := strings.Repeat("x", 10) 461 _, err := d.getMonitorPath(shortPath) 462 if err != nil { 463 t.Fatal("Should not have returned an error") 464 } 465 466 longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100) 467 _, err = d.getMonitorPath(longPath) 468 if err != nil { 469 t.Fatal("Should not have returned an error") 470 } 471 472 maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1 473 maxLengthLegacyPath := strings.Repeat("x", maxLengthCount) 474 _, err = d.getMonitorPath(maxLengthLegacyPath) 475 if err != nil { 476 t.Fatal("Should not have returned an error") 477 } 478 }