github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/client/driver/docker_test.go (about) 1 package driver 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "math/rand" 7 "path/filepath" 8 "reflect" 9 "runtime/debug" 10 "strconv" 11 "strings" 12 "testing" 13 "time" 14 15 docker "github.com/fsouza/go-dockerclient" 16 "github.com/hashicorp/nomad/client/allocdir" 17 "github.com/hashicorp/nomad/client/config" 18 "github.com/hashicorp/nomad/client/driver/env" 19 "github.com/hashicorp/nomad/client/testutil" 20 "github.com/hashicorp/nomad/nomad/structs" 21 tu "github.com/hashicorp/nomad/testutil" 22 ) 23 24 func dockerIsRemote(t *testing.T) bool { 25 client, err := docker.NewClientFromEnv() 26 if err != nil { 27 return false 28 } 29 30 // Technically this could be a local tcp socket but for testing purposes 31 // we'll just assume that tcp is only used for remote connections. 32 if client.Endpoint()[0:3] == "tcp" { 33 return true 34 } 35 return false 36 } 37 38 // Ports used by tests 39 var ( 40 docker_reserved = 32768 + int(rand.Int31n(25000)) 41 docker_dynamic = 32768 + int(rand.Int31n(25000)) 42 ) 43 44 // Returns a task with a reserved and dynamic port. The ports are returned 45 // respectively. 46 func dockerTask() (*structs.Task, int, int) { 47 docker_reserved += 1 48 docker_dynamic += 1 49 return &structs.Task{ 50 Name: "redis-demo", 51 Config: map[string]interface{}{ 52 "image": "busybox", 53 "load": []string{"busybox.tar"}, 54 "command": "/bin/nc", 55 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 56 }, 57 LogConfig: &structs.LogConfig{ 58 MaxFiles: 10, 59 MaxFileSizeMB: 10, 60 }, 61 Resources: &structs.Resources{ 62 MemoryMB: 256, 63 CPU: 512, 64 Networks: []*structs.NetworkResource{ 65 &structs.NetworkResource{ 66 IP: "127.0.0.1", 67 ReservedPorts: []structs.Port{{"main", docker_reserved}}, 68 DynamicPorts: []structs.Port{{"REDIS", docker_dynamic}}, 69 }, 70 }, 71 }, 72 }, docker_reserved, docker_dynamic 73 } 74 75 // dockerSetup does all of the basic setup you need to get a running docker 76 // process up and running for testing. Use like: 77 // 78 // task := taskTemplate() 79 // // do custom task configuration 80 // client, handle, cleanup := dockerSetup(t, task) 81 // defer cleanup() 82 // // do test stuff 83 // 84 // If there is a problem during setup this function will abort or skip the test 85 // and indicate the reason. 86 func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) { 87 if !testutil.DockerIsConnected(t) { 88 t.SkipNow() 89 } 90 91 client, err := docker.NewClientFromEnv() 92 if err != nil { 93 t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack()) 94 } 95 96 driverCtx, execCtx := testDriverContexts(task) 97 driver := NewDockerDriver(driverCtx) 98 copyImage(execCtx, task, "busybox.tar", t) 99 100 handle, err := driver.Start(execCtx, task) 101 if err != nil { 102 execCtx.AllocDir.Destroy() 103 t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack()) 104 } 105 if handle == nil { 106 execCtx.AllocDir.Destroy() 107 t.Fatalf("handle is nil\nStack\n%s", debug.Stack()) 108 } 109 110 cleanup := func() { 111 handle.Kill() 112 execCtx.AllocDir.Destroy() 113 } 114 115 return client, handle, cleanup 116 } 117 118 // This test should always pass, even if docker daemon is not available 119 func TestDockerDriver_Fingerprint(t *testing.T) { 120 driverCtx, execCtx := testDriverContexts(&structs.Task{Name: "foo", Resources: basicResources}) 121 defer execCtx.AllocDir.Destroy() 122 d := NewDockerDriver(driverCtx) 123 node := &structs.Node{ 124 Attributes: make(map[string]string), 125 } 126 apply, err := d.Fingerprint(&config.Config{}, node) 127 if err != nil { 128 t.Fatalf("err: %v", err) 129 } 130 if apply != testutil.DockerIsConnected(t) { 131 t.Fatalf("Fingerprinter should detect when docker is available") 132 } 133 if node.Attributes["driver.docker"] != "1" { 134 t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.") 135 } 136 t.Logf("Found docker version %s", node.Attributes["driver.docker.version"]) 137 } 138 139 func TestDockerDriver_StartOpen_Wait(t *testing.T) { 140 if !testutil.DockerIsConnected(t) { 141 t.SkipNow() 142 } 143 144 task := &structs.Task{ 145 Name: "nc-demo", 146 Config: map[string]interface{}{ 147 "load": []string{"busybox.tar"}, 148 "image": "busybox", 149 "command": "/bin/nc", 150 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 151 }, 152 LogConfig: &structs.LogConfig{ 153 MaxFiles: 10, 154 MaxFileSizeMB: 10, 155 }, 156 Resources: basicResources, 157 } 158 159 driverCtx, execCtx := testDriverContexts(task) 160 defer execCtx.AllocDir.Destroy() 161 d := NewDockerDriver(driverCtx) 162 copyImage(execCtx, task, "busybox.tar", t) 163 164 handle, err := d.Start(execCtx, task) 165 if err != nil { 166 t.Fatalf("err: %v", err) 167 } 168 if handle == nil { 169 t.Fatalf("missing handle") 170 } 171 defer handle.Kill() 172 173 // Attempt to open 174 handle2, err := d.Open(execCtx, handle.ID()) 175 if err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 if handle2 == nil { 179 t.Fatalf("missing handle") 180 } 181 } 182 183 func TestDockerDriver_Start_Wait(t *testing.T) { 184 task := &structs.Task{ 185 Name: "nc-demo", 186 Config: map[string]interface{}{ 187 "load": []string{"busybox.tar"}, 188 "image": "busybox", 189 "command": "/bin/echo", 190 "args": []string{"hello"}, 191 }, 192 Resources: &structs.Resources{ 193 MemoryMB: 256, 194 CPU: 512, 195 }, 196 LogConfig: &structs.LogConfig{ 197 MaxFiles: 10, 198 MaxFileSizeMB: 10, 199 }, 200 } 201 202 _, handle, cleanup := dockerSetup(t, task) 203 defer cleanup() 204 205 // Update should be a no-op 206 err := handle.Update(task) 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 211 select { 212 case res := <-handle.WaitCh(): 213 if !res.Successful() { 214 t.Fatalf("err: %v", res) 215 } 216 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 217 t.Fatalf("timeout") 218 } 219 } 220 221 func TestDockerDriver_Start_LoadImage(t *testing.T) { 222 if !testutil.DockerIsConnected(t) { 223 t.SkipNow() 224 } 225 task := &structs.Task{ 226 Name: "busybox-demo", 227 Config: map[string]interface{}{ 228 "image": "busybox", 229 "load": []string{"busybox.tar"}, 230 "command": "/bin/echo", 231 "args": []string{ 232 "hello", 233 }, 234 }, 235 LogConfig: &structs.LogConfig{ 236 MaxFiles: 10, 237 MaxFileSizeMB: 10, 238 }, 239 Resources: &structs.Resources{ 240 MemoryMB: 256, 241 CPU: 512, 242 }, 243 } 244 245 driverCtx, execCtx := testDriverContexts(task) 246 defer execCtx.AllocDir.Destroy() 247 d := NewDockerDriver(driverCtx) 248 249 // Copy the image into the task's directory 250 copyImage(execCtx, task, "busybox.tar", t) 251 252 handle, err := d.Start(execCtx, task) 253 if err != nil { 254 t.Fatalf("err: %v", err) 255 } 256 if handle == nil { 257 t.Fatalf("missing handle") 258 } 259 defer handle.Kill() 260 261 select { 262 case res := <-handle.WaitCh(): 263 if !res.Successful() { 264 t.Fatalf("err: %v", res) 265 } 266 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 267 t.Fatalf("timeout") 268 } 269 270 // Check that data was written to the shared alloc directory. 271 outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "busybox-demo.stdout.0") 272 act, err := ioutil.ReadFile(outputFile) 273 if err != nil { 274 t.Fatalf("Couldn't read expected output: %v", err) 275 } 276 277 exp := "hello" 278 if strings.TrimSpace(string(act)) != exp { 279 t.Fatalf("Command outputted %v; want %v", act, exp) 280 } 281 282 } 283 284 func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) { 285 // This test requires that the alloc dir be mounted into docker as a volume. 286 // Because this cannot happen when docker is run remotely, e.g. when running 287 // docker in a VM, we skip this when we detect Docker is being run remotely. 288 if !testutil.DockerIsConnected(t) || dockerIsRemote(t) { 289 t.SkipNow() 290 } 291 292 exp := []byte{'w', 'i', 'n'} 293 file := "output.txt" 294 task := &structs.Task{ 295 Name: "nc-demo", 296 Config: map[string]interface{}{ 297 "image": "busybox", 298 "load": []string{"busybox.tar"}, 299 "command": "/bin/sh", 300 "args": []string{ 301 "-c", 302 fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`, 303 string(exp), env.AllocDir, file), 304 }, 305 }, 306 LogConfig: &structs.LogConfig{ 307 MaxFiles: 10, 308 MaxFileSizeMB: 10, 309 }, 310 Resources: &structs.Resources{ 311 MemoryMB: 256, 312 CPU: 512, 313 }, 314 } 315 316 driverCtx, execCtx := testDriverContexts(task) 317 defer execCtx.AllocDir.Destroy() 318 d := NewDockerDriver(driverCtx) 319 copyImage(execCtx, task, "busybox.tar", t) 320 321 handle, err := d.Start(execCtx, task) 322 if err != nil { 323 t.Fatalf("err: %v", err) 324 } 325 if handle == nil { 326 t.Fatalf("missing handle") 327 } 328 defer handle.Kill() 329 330 select { 331 case res := <-handle.WaitCh(): 332 if !res.Successful() { 333 t.Fatalf("err: %v", res) 334 } 335 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 336 t.Fatalf("timeout") 337 } 338 339 // Check that data was written to the shared alloc directory. 340 outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file) 341 act, err := ioutil.ReadFile(outputFile) 342 if err != nil { 343 t.Fatalf("Couldn't read expected output: %v", err) 344 } 345 346 if !reflect.DeepEqual(act, exp) { 347 t.Fatalf("Command outputted %v; want %v", act, exp) 348 } 349 } 350 351 func TestDockerDriver_Start_Kill_Wait(t *testing.T) { 352 task := &structs.Task{ 353 Name: "nc-demo", 354 Config: map[string]interface{}{ 355 "image": "busybox", 356 "load": []string{"busybox.tar"}, 357 "command": "/bin/sleep", 358 "args": []string{"10"}, 359 }, 360 LogConfig: &structs.LogConfig{ 361 MaxFiles: 10, 362 MaxFileSizeMB: 10, 363 }, 364 Resources: basicResources, 365 } 366 367 _, handle, cleanup := dockerSetup(t, task) 368 defer cleanup() 369 370 go func() { 371 time.Sleep(100 * time.Millisecond) 372 err := handle.Kill() 373 if err != nil { 374 t.Fatalf("err: %v", err) 375 } 376 }() 377 378 select { 379 case res := <-handle.WaitCh(): 380 if res.Successful() { 381 t.Fatalf("should err: %v", res) 382 } 383 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 384 t.Fatalf("timeout") 385 } 386 } 387 388 func TestDockerDriver_StartN(t *testing.T) { 389 if !testutil.DockerIsConnected(t) { 390 t.SkipNow() 391 } 392 393 task1, _, _ := dockerTask() 394 task2, _, _ := dockerTask() 395 task3, _, _ := dockerTask() 396 taskList := []*structs.Task{task1, task2, task3} 397 398 handles := make([]DriverHandle, len(taskList)) 399 400 t.Logf("Starting %d tasks", len(taskList)) 401 402 // Let's spin up a bunch of things 403 var err error 404 for idx, task := range taskList { 405 driverCtx, execCtx := testDriverContexts(task) 406 defer execCtx.AllocDir.Destroy() 407 d := NewDockerDriver(driverCtx) 408 copyImage(execCtx, task, "busybox.tar", t) 409 410 handles[idx], err = d.Start(execCtx, task) 411 if err != nil { 412 t.Errorf("Failed starting task #%d: %s", idx+1, err) 413 } 414 } 415 416 t.Log("All tasks are started. Terminating...") 417 418 for idx, handle := range handles { 419 if handle == nil { 420 t.Errorf("Bad handle for task #%d", idx+1) 421 continue 422 } 423 424 err := handle.Kill() 425 if err != nil { 426 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 427 } 428 } 429 430 t.Log("Test complete!") 431 } 432 433 func TestDockerDriver_StartNVersions(t *testing.T) { 434 if !testutil.DockerIsConnected(t) { 435 t.SkipNow() 436 } 437 438 task1, _, _ := dockerTask() 439 task1.Config["image"] = "busybox" 440 task1.Config["load"] = []string{"busybox.tar"} 441 442 task2, _, _ := dockerTask() 443 task2.Config["image"] = "busybox:musl" 444 task2.Config["load"] = []string{"busybox_musl.tar"} 445 446 task3, _, _ := dockerTask() 447 task3.Config["image"] = "busybox:glibc" 448 task3.Config["load"] = []string{"busybox_glibc.tar"} 449 450 taskList := []*structs.Task{task1, task2, task3} 451 452 handles := make([]DriverHandle, len(taskList)) 453 454 t.Logf("Starting %d tasks", len(taskList)) 455 456 // Let's spin up a bunch of things 457 var err error 458 for idx, task := range taskList { 459 driverCtx, execCtx := testDriverContexts(task) 460 defer execCtx.AllocDir.Destroy() 461 d := NewDockerDriver(driverCtx) 462 copyImage(execCtx, task, "busybox.tar", t) 463 copyImage(execCtx, task, "busybox_musl.tar", t) 464 copyImage(execCtx, task, "busybox_glibc.tar", t) 465 466 handles[idx], err = d.Start(execCtx, task) 467 if err != nil { 468 t.Errorf("Failed starting task #%d: %s", idx+1, err) 469 } 470 } 471 472 t.Log("All tasks are started. Terminating...") 473 474 for idx, handle := range handles { 475 if handle == nil { 476 t.Errorf("Bad handle for task #%d", idx+1) 477 continue 478 } 479 480 err := handle.Kill() 481 if err != nil { 482 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 483 } 484 } 485 486 t.Log("Test complete!") 487 } 488 489 func waitForExist(t *testing.T, client *docker.Client, handle *DockerHandle) { 490 tu.WaitForResult(func() (bool, error) { 491 container, err := client.InspectContainer(handle.ContainerID()) 492 if err != nil { 493 if _, ok := err.(*docker.NoSuchContainer); !ok { 494 return false, err 495 } 496 } 497 498 return container != nil, nil 499 }, func(err error) { 500 t.Fatalf("err: %v", err) 501 }) 502 } 503 504 func TestDockerDriver_NetworkMode_Host(t *testing.T) { 505 expected := "host" 506 507 task := &structs.Task{ 508 Name: "nc-demo", 509 Config: map[string]interface{}{ 510 "image": "busybox", 511 "load": []string{"busybox.tar"}, 512 "command": "/bin/nc", 513 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 514 "network_mode": expected, 515 }, 516 Resources: &structs.Resources{ 517 MemoryMB: 256, 518 CPU: 512, 519 }, 520 LogConfig: &structs.LogConfig{ 521 MaxFiles: 10, 522 MaxFileSizeMB: 10, 523 }, 524 } 525 526 client, handle, cleanup := dockerSetup(t, task) 527 defer cleanup() 528 529 waitForExist(t, client, handle.(*DockerHandle)) 530 531 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 532 if err != nil { 533 t.Fatalf("err: %v", err) 534 } 535 536 actual := container.HostConfig.NetworkMode 537 if actual != expected { 538 t.Fatalf("Got network mode %q; want %q", expected, actual) 539 } 540 } 541 542 func TestDockerDriver_Labels(t *testing.T) { 543 task, _, _ := dockerTask() 544 task.Config["labels"] = []map[string]string{ 545 map[string]string{ 546 "label1": "value1", 547 "label2": "value2", 548 }, 549 } 550 551 client, handle, cleanup := dockerSetup(t, task) 552 defer cleanup() 553 554 waitForExist(t, client, handle.(*DockerHandle)) 555 556 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 557 if err != nil { 558 t.Fatalf("err: %v", err) 559 } 560 561 if want, got := 2, len(container.Config.Labels); want != got { 562 t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got) 563 } 564 565 if want, got := "value1", container.Config.Labels["label1"]; want != got { 566 t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got) 567 } 568 } 569 570 func TestDockerDriver_DNS(t *testing.T) { 571 task, _, _ := dockerTask() 572 task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"} 573 task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"} 574 575 client, handle, cleanup := dockerSetup(t, task) 576 defer cleanup() 577 578 waitForExist(t, client, handle.(*DockerHandle)) 579 580 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 581 if err != nil { 582 t.Fatalf("err: %v", err) 583 } 584 585 if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) { 586 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS) 587 } 588 589 if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) { 590 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch) 591 } 592 } 593 594 func TestDockerWorkDir(t *testing.T) { 595 task, _, _ := dockerTask() 596 task.Config["work_dir"] = "/some/path" 597 598 client, handle, cleanup := dockerSetup(t, task) 599 defer cleanup() 600 601 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 602 if err != nil { 603 t.Fatalf("err: %v", err) 604 } 605 606 if want, got := "/some/path", container.Config.WorkingDir; want != got { 607 t.Errorf("Wrong working directory for docker job. Expect: %d, got: %d", want, got) 608 } 609 } 610 611 func inSlice(needle string, haystack []string) bool { 612 for _, h := range haystack { 613 if h == needle { 614 return true 615 } 616 } 617 return false 618 } 619 620 func TestDockerDriver_PortsNoMap(t *testing.T) { 621 task, res, dyn := dockerTask() 622 623 client, handle, cleanup := dockerSetup(t, task) 624 defer cleanup() 625 626 waitForExist(t, client, handle.(*DockerHandle)) 627 628 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 629 if err != nil { 630 t.Fatalf("err: %v", err) 631 } 632 633 // Verify that the correct ports are EXPOSED 634 expectedExposedPorts := map[docker.Port]struct{}{ 635 docker.Port(fmt.Sprintf("%d/tcp", res)): struct{}{}, 636 docker.Port(fmt.Sprintf("%d/udp", res)): struct{}{}, 637 docker.Port(fmt.Sprintf("%d/tcp", dyn)): struct{}{}, 638 docker.Port(fmt.Sprintf("%d/udp", dyn)): struct{}{}, 639 } 640 641 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 642 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 643 } 644 645 // Verify that the correct ports are FORWARDED 646 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 647 docker.Port(fmt.Sprintf("%d/tcp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 648 docker.Port(fmt.Sprintf("%d/udp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 649 docker.Port(fmt.Sprintf("%d/tcp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 650 docker.Port(fmt.Sprintf("%d/udp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 651 } 652 653 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 654 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 655 } 656 657 expectedEnvironment := map[string]string{ 658 "NOMAD_ADDR_main": fmt.Sprintf("127.0.0.1:%d", res), 659 "NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn), 660 } 661 662 for key, val := range expectedEnvironment { 663 search := fmt.Sprintf("%s=%s", key, val) 664 if !inSlice(search, container.Config.Env) { 665 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 666 } 667 } 668 } 669 670 func TestDockerDriver_PortsMapping(t *testing.T) { 671 task, res, dyn := dockerTask() 672 task.Config["port_map"] = []map[string]string{ 673 map[string]string{ 674 "main": "8080", 675 "REDIS": "6379", 676 }, 677 } 678 679 client, handle, cleanup := dockerSetup(t, task) 680 defer cleanup() 681 682 waitForExist(t, client, handle.(*DockerHandle)) 683 684 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 685 if err != nil { 686 t.Fatalf("err: %v", err) 687 } 688 689 // Verify that the correct ports are EXPOSED 690 expectedExposedPorts := map[docker.Port]struct{}{ 691 docker.Port("8080/tcp"): struct{}{}, 692 docker.Port("8080/udp"): struct{}{}, 693 docker.Port("6379/tcp"): struct{}{}, 694 docker.Port("6379/udp"): struct{}{}, 695 } 696 697 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 698 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 699 } 700 701 // Verify that the correct ports are FORWARDED 702 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 703 docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 704 docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 705 docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 706 docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 707 } 708 709 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 710 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 711 } 712 713 expectedEnvironment := map[string]string{ 714 "NOMAD_ADDR_main": "127.0.0.1:8080", 715 "NOMAD_ADDR_REDIS": "127.0.0.1:6379", 716 "NOMAD_HOST_PORT_main": strconv.Itoa(docker_reserved), 717 } 718 719 for key, val := range expectedEnvironment { 720 search := fmt.Sprintf("%s=%s", key, val) 721 if !inSlice(search, container.Config.Env) { 722 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 723 } 724 } 725 } 726 727 func TestDockerDriver_User(t *testing.T) { 728 task := &structs.Task{ 729 Name: "redis-demo", 730 User: "alice", 731 Config: map[string]interface{}{ 732 "image": "busybox", 733 "load": []string{"busybox.tar"}, 734 "command": "/bin/sleep", 735 "args": []string{"10000"}, 736 }, 737 Resources: &structs.Resources{ 738 MemoryMB: 256, 739 CPU: 512, 740 }, 741 LogConfig: &structs.LogConfig{ 742 MaxFiles: 10, 743 MaxFileSizeMB: 10, 744 }, 745 } 746 747 if !testutil.DockerIsConnected(t) { 748 t.SkipNow() 749 } 750 751 driverCtx, execCtx := testDriverContexts(task) 752 driver := NewDockerDriver(driverCtx) 753 defer execCtx.AllocDir.Destroy() 754 copyImage(execCtx, task, "busybox.tar", t) 755 756 // It should fail because the user "alice" does not exist on the given 757 // image. 758 handle, err := driver.Start(execCtx, task) 759 if err == nil { 760 handle.Kill() 761 t.Fatalf("Should've failed") 762 } 763 764 if !strings.Contains(err.Error(), "alice") { 765 t.Fatalf("Expected failure string not found, found %q instead", err.Error()) 766 } 767 } 768 769 func TestDockerDriver_CleanupContainer(t *testing.T) { 770 task := &structs.Task{ 771 Name: "redis-demo", 772 Config: map[string]interface{}{ 773 "image": "busybox", 774 "load": []string{"busybox.tar"}, 775 "command": "/bin/echo", 776 "args": []string{"hello"}, 777 }, 778 Resources: &structs.Resources{ 779 MemoryMB: 256, 780 CPU: 512, 781 }, 782 LogConfig: &structs.LogConfig{ 783 MaxFiles: 10, 784 MaxFileSizeMB: 10, 785 }, 786 } 787 788 _, handle, cleanup := dockerSetup(t, task) 789 defer cleanup() 790 791 // Update should be a no-op 792 err := handle.Update(task) 793 if err != nil { 794 t.Fatalf("err: %v", err) 795 } 796 797 select { 798 case res := <-handle.WaitCh(): 799 if !res.Successful() { 800 t.Fatalf("err: %v", res) 801 } 802 803 time.Sleep(3 * time.Second) 804 805 // Ensure that the container isn't present 806 _, err := client.InspectContainer(handle.(*DockerHandle).containerID) 807 if err == nil { 808 t.Fatalf("expected to not get container") 809 } 810 811 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 812 t.Fatalf("timeout") 813 } 814 } 815 816 func TestDockerDriver_Stats(t *testing.T) { 817 task := &structs.Task{ 818 Name: "sleep", 819 Config: map[string]interface{}{ 820 "image": "busybox", 821 "load": []string{"busybox.tar"}, 822 "command": "/bin/sleep", 823 "args": []string{"100"}, 824 }, 825 LogConfig: &structs.LogConfig{ 826 MaxFiles: 10, 827 MaxFileSizeMB: 10, 828 }, 829 Resources: basicResources, 830 } 831 832 _, handle, cleanup := dockerSetup(t, task) 833 defer cleanup() 834 835 waitForExist(t, client, handle.(*DockerHandle)) 836 837 go func() { 838 time.Sleep(3 * time.Second) 839 ru, err := handle.Stats() 840 if err != nil { 841 t.Fatalf("err: %v", err) 842 } 843 if ru.ResourceUsage == nil { 844 handle.Kill() 845 t.Fatalf("expected resource usage") 846 } 847 err = handle.Kill() 848 if err != nil { 849 t.Fatalf("err: %v", err) 850 } 851 }() 852 853 select { 854 case res := <-handle.WaitCh(): 855 if res.Successful() { 856 t.Fatalf("should err: %v", res) 857 } 858 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 859 t.Fatalf("timeout") 860 } 861 862 } 863 864 func copyImage(execCtx *ExecContext, task *structs.Task, image string, t *testing.T) { 865 taskDir, _ := execCtx.AllocDir.TaskDirs[task.Name] 866 dst := filepath.Join(taskDir, allocdir.TaskLocal, image) 867 copyFile(filepath.Join("./test-resources/docker", image), dst, t) 868 }