github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/qemu_test.go (about) 1 package driver 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 "syscall" 9 "testing" 10 "time" 11 12 "github.com/hashicorp/nomad/client/config" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "github.com/hashicorp/nomad/testutil" 15 16 ctestutils "github.com/hashicorp/nomad/client/testutil" 17 ) 18 19 // The fingerprinter test should always pass, even if QEMU is not installed. 20 func TestQemuDriver_Fingerprint(t *testing.T) { 21 if !testutil.IsTravis() { 22 t.Parallel() 23 } 24 ctestutils.QemuCompatible(t) 25 task := &structs.Task{ 26 Name: "foo", 27 Driver: "qemu", 28 Resources: structs.DefaultResources(), 29 } 30 ctx := testDriverContexts(t, task) 31 defer ctx.AllocDir.Destroy() 32 d := NewQemuDriver(ctx.DriverCtx) 33 34 node := &structs.Node{ 35 Attributes: make(map[string]string), 36 } 37 apply, err := d.Fingerprint(&config.Config{}, node) 38 if err != nil { 39 t.Fatalf("err: %v", err) 40 } 41 if !apply { 42 t.Fatalf("should apply") 43 } 44 if node.Attributes[qemuDriverAttr] == "" { 45 t.Fatalf("Missing Qemu driver") 46 } 47 if node.Attributes[qemuDriverVersionAttr] == "" { 48 t.Fatalf("Missing Qemu driver version") 49 } 50 } 51 52 func TestQemuDriver_StartOpen_Wait(t *testing.T) { 53 logger := testLogger() 54 if !testutil.IsTravis() { 55 t.Parallel() 56 } 57 ctestutils.QemuCompatible(t) 58 task := &structs.Task{ 59 Name: "linux", 60 Driver: "qemu", 61 Config: map[string]interface{}{ 62 "image_path": "linux-0.2.img", 63 "accelerator": "tcg", 64 "graceful_shutdown": false, 65 "port_map": []map[string]int{{ 66 "main": 22, 67 "web": 8080, 68 }}, 69 "args": []string{"-nodefconfig", "-nodefaults"}, 70 }, 71 LogConfig: &structs.LogConfig{ 72 MaxFiles: 10, 73 MaxFileSizeMB: 10, 74 }, 75 Resources: &structs.Resources{ 76 CPU: 500, 77 MemoryMB: 512, 78 Networks: []*structs.NetworkResource{ 79 { 80 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 81 }, 82 }, 83 }, 84 } 85 86 ctx := testDriverContexts(t, task) 87 defer ctx.AllocDir.Destroy() 88 d := NewQemuDriver(ctx.DriverCtx) 89 90 // Copy the test image into the task's directory 91 dst := ctx.ExecCtx.TaskDir.Dir 92 93 copyFile("./test-resources/qemu/linux-0.2.img", filepath.Join(dst, "linux-0.2.img"), t) 94 95 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 96 t.Fatalf("Prestart failed: %v", err) 97 } 98 99 resp, err := d.Start(ctx.ExecCtx, task) 100 if err != nil { 101 t.Fatalf("err: %v", err) 102 } 103 104 // Ensure that sending a Signal returns an error 105 if err := resp.Handle.Signal(syscall.SIGINT); err == nil { 106 t.Fatalf("Expect an error when signalling") 107 } 108 109 // Attempt to open 110 handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID()) 111 if err != nil { 112 t.Fatalf("err: %v", err) 113 } 114 if handle2 == nil { 115 t.Fatalf("missing handle") 116 } 117 118 // Clean up 119 if err := resp.Handle.Kill(); err != nil { 120 logger.Printf("Error killing Qemu test: %s", err) 121 } 122 } 123 124 func TestQemuDriver_GracefulShutdown(t *testing.T) { 125 logger := testLogger() 126 if !testutil.IsTravis() { 127 t.Parallel() 128 } 129 ctestutils.QemuCompatible(t) 130 ctestutils.RequireRoot(t) 131 task := &structs.Task{ 132 Name: "linux", 133 Driver: "qemu", 134 Config: map[string]interface{}{ 135 "image_path": "linux-0.2.img", 136 "accelerator": "tcg", 137 "graceful_shutdown": true, 138 "port_map": []map[string]int{{ 139 "main": 22, 140 "web": 8080, 141 }}, 142 "args": []string{"-nodefconfig", "-nodefaults"}, 143 }, 144 // With the use of tcg acceleration, it's very unlikely a qemu instance 145 // will boot (and gracefully halt) in a reasonable amount of time, so 146 // this timeout is kept low to reduce test execution time. 147 KillTimeout: time.Duration(1 * time.Second), 148 LogConfig: &structs.LogConfig{ 149 MaxFiles: 10, 150 MaxFileSizeMB: 10, 151 }, 152 Resources: &structs.Resources{ 153 CPU: 500, 154 MemoryMB: 512, 155 Networks: []*structs.NetworkResource{ 156 { 157 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 158 }, 159 }, 160 }, 161 } 162 163 ctx := testDriverContexts(t, task) 164 defer ctx.AllocDir.Destroy() 165 d := NewQemuDriver(ctx.DriverCtx) 166 167 apply, err := d.Fingerprint(&config.Config{}, ctx.DriverCtx.node) 168 if err != nil { 169 t.Fatalf("err: %v", err) 170 } 171 if !apply { 172 t.Fatalf("should apply") 173 } 174 175 dst := ctx.ExecCtx.TaskDir.Dir 176 177 copyFile("./test-resources/qemu/linux-0.2.img", filepath.Join(dst, "linux-0.2.img"), t) 178 179 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 180 t.Fatalf("Prestart failed: %v", err) 181 } 182 183 resp, err := d.Start(ctx.ExecCtx, task) 184 if err != nil { 185 t.Fatalf("err: %v", err) 186 } 187 188 // Clean up 189 defer func() { 190 if err := resp.Handle.Kill(); err != nil { 191 logger.Printf("Error killing Qemu test: %s", err) 192 } 193 }() 194 195 // The monitor socket will not exist immediately, so we'll wait up to 196 // 5 seconds for it to become available. 197 monitorPath := fmt.Sprintf("%s/linux/%s", ctx.AllocDir.AllocDir, qemuMonitorSocketName) 198 monitorPathExists := false 199 for i := 0; i < 100; i++ { 200 if _, err := os.Stat(monitorPath); !os.IsNotExist(err) { 201 logger.Printf("monitor socket exists at %q\n", monitorPath) 202 monitorPathExists = true 203 break 204 } 205 time.Sleep(200 * time.Millisecond) 206 } 207 if monitorPathExists == false { 208 t.Fatalf("monitor socket did not exist after waiting 20 seconds") 209 } 210 211 // userPid supplied in sendQemuShutdown calls is bogus (it's used only 212 // for log output) 213 if err := sendQemuShutdown(ctx.DriverCtx.logger, "", 0); err == nil { 214 t.Fatalf("sendQemuShutdown should return an error if monitorPath parameter is empty") 215 } 216 217 if err := sendQemuShutdown(ctx.DriverCtx.logger, "/path/that/does/not/exist", 0); err == nil { 218 t.Fatalf("sendQemuShutdown should return an error if file does not exist at monitorPath") 219 } 220 221 if err := sendQemuShutdown(ctx.DriverCtx.logger, monitorPath, 0); err != nil { 222 t.Fatalf("unexpected error from sendQemuShutdown: %s", err) 223 } 224 } 225 226 func TestQemuDriverUser(t *testing.T) { 227 if !testutil.IsTravis() { 228 t.Parallel() 229 } 230 ctestutils.QemuCompatible(t) 231 tasks := []*structs.Task{ 232 { 233 Name: "linux", 234 Driver: "qemu", 235 User: "alice", 236 Config: map[string]interface{}{ 237 "image_path": "linux-0.2.img", 238 "accelerator": "tcg", 239 "graceful_shutdown": false, 240 "port_map": []map[string]int{{ 241 "main": 22, 242 "web": 8080, 243 }}, 244 "args": []string{"-nodefconfig", "-nodefaults"}, 245 "msg": "unknown user alice", 246 }, 247 LogConfig: &structs.LogConfig{ 248 MaxFiles: 10, 249 MaxFileSizeMB: 10, 250 }, 251 Resources: &structs.Resources{ 252 CPU: 500, 253 MemoryMB: 512, 254 Networks: []*structs.NetworkResource{ 255 { 256 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 257 }, 258 }, 259 }, 260 }, 261 { 262 Name: "linux", 263 Driver: "qemu", 264 User: "alice", 265 Config: map[string]interface{}{ 266 "image_path": "linux-0.2.img", 267 "accelerator": "tcg", 268 "port_map": []map[string]int{{ 269 "main": 22, 270 "web": 8080, 271 }}, 272 "args": []string{"-nodefconfig", "-nodefaults"}, 273 "msg": "Qemu memory assignment out of bounds", 274 }, 275 LogConfig: &structs.LogConfig{ 276 MaxFiles: 10, 277 MaxFileSizeMB: 10, 278 }, 279 Resources: &structs.Resources{ 280 CPU: 500, 281 MemoryMB: -1, 282 Networks: []*structs.NetworkResource{ 283 { 284 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 285 }, 286 }, 287 }, 288 }, 289 } 290 291 for _, task := range tasks { 292 ctx := testDriverContexts(t, task) 293 defer ctx.AllocDir.Destroy() 294 d := NewQemuDriver(ctx.DriverCtx) 295 296 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 297 t.Fatalf("Prestart faild: %v", err) 298 } 299 300 resp, err := d.Start(ctx.ExecCtx, task) 301 if err == nil { 302 resp.Handle.Kill() 303 t.Fatalf("Should've failed") 304 } 305 306 msg := task.Config["msg"].(string) 307 if !strings.Contains(err.Error(), msg) { 308 t.Fatalf("Expecting '%v' in '%v'", msg, err) 309 } 310 } 311 } 312 313 func TestQemuDriverGetMonitorPathOldQemu(t *testing.T) { 314 task := &structs.Task{ 315 Name: "linux", 316 Driver: "qemu", 317 Config: map[string]interface{}{ 318 "image_path": "linux-0.2.img", 319 "accelerator": "tcg", 320 "graceful_shutdown": true, 321 "port_map": []map[string]int{{ 322 "main": 22, 323 "web": 8080, 324 }}, 325 "args": []string{"-nodefconfig", "-nodefaults"}, 326 }, 327 KillTimeout: time.Duration(1 * time.Second), 328 LogConfig: &structs.LogConfig{ 329 MaxFiles: 10, 330 MaxFileSizeMB: 10, 331 }, 332 Resources: &structs.Resources{ 333 CPU: 500, 334 MemoryMB: 512, 335 Networks: []*structs.NetworkResource{ 336 { 337 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 338 }, 339 }, 340 }, 341 } 342 343 ctx := testDriverContexts(t, task) 344 defer ctx.AllocDir.Destroy() 345 346 // Simulate an older version of qemu which does not support long monitor socket paths 347 ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.0.0" 348 349 d := &QemuDriver{DriverContext: *ctx.DriverCtx} 350 351 shortPath := strings.Repeat("x", 10) 352 _, err := d.getMonitorPath(shortPath) 353 if err != nil { 354 t.Fatal("Should not have returned an error") 355 } 356 357 longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100) 358 _, err = d.getMonitorPath(longPath) 359 if err == nil { 360 t.Fatal("Should have returned an error") 361 } 362 363 // Max length includes the '/' separator and socket name 364 maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1 365 maxLengthLegacyPath := strings.Repeat("x", maxLengthCount) 366 _, err = d.getMonitorPath(maxLengthLegacyPath) 367 if err != nil { 368 t.Fatalf("Should not have returned an error: %s", err) 369 } 370 } 371 372 func TestQemuDriverGetMonitorPathNewQemu(t *testing.T) { 373 task := &structs.Task{ 374 Name: "linux", 375 Driver: "qemu", 376 Config: map[string]interface{}{ 377 "image_path": "linux-0.2.img", 378 "accelerator": "tcg", 379 "graceful_shutdown": true, 380 "port_map": []map[string]int{{ 381 "main": 22, 382 "web": 8080, 383 }}, 384 "args": []string{"-nodefconfig", "-nodefaults"}, 385 }, 386 KillTimeout: time.Duration(1 * time.Second), 387 LogConfig: &structs.LogConfig{ 388 MaxFiles: 10, 389 MaxFileSizeMB: 10, 390 }, 391 Resources: &structs.Resources{ 392 CPU: 500, 393 MemoryMB: 512, 394 Networks: []*structs.NetworkResource{ 395 { 396 ReservedPorts: []structs.Port{{Label: "main", Value: 22000}, {Label: "web", Value: 80}}, 397 }, 398 }, 399 }, 400 } 401 402 ctx := testDriverContexts(t, task) 403 defer ctx.AllocDir.Destroy() 404 405 // Simulate a version of qemu which supports long monitor socket paths 406 ctx.DriverCtx.node.Attributes[qemuDriverVersionAttr] = "2.99.99" 407 408 d := &QemuDriver{DriverContext: *ctx.DriverCtx} 409 410 shortPath := strings.Repeat("x", 10) 411 _, err := d.getMonitorPath(shortPath) 412 if err != nil { 413 t.Fatal("Should not have returned an error") 414 } 415 416 longPath := strings.Repeat("x", qemuLegacyMaxMonitorPathLen+100) 417 _, err = d.getMonitorPath(longPath) 418 if err != nil { 419 t.Fatal("Should not have returned an error") 420 } 421 422 maxLengthCount := qemuLegacyMaxMonitorPathLen - len(qemuMonitorSocketName) - 1 423 maxLengthLegacyPath := strings.Repeat("x", maxLengthCount) 424 _, err = d.getMonitorPath(maxLengthLegacyPath) 425 if err != nil { 426 t.Fatal("Should not have returned an error") 427 } 428 }