github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/docker_test.go (about) 1 package driver 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "math/rand" 7 "os" 8 "path/filepath" 9 "reflect" 10 "runtime/debug" 11 "strconv" 12 "strings" 13 "syscall" 14 "testing" 15 "time" 16 17 docker "github.com/fsouza/go-dockerclient" 18 "github.com/hashicorp/nomad/client/allocdir" 19 "github.com/hashicorp/nomad/client/config" 20 "github.com/hashicorp/nomad/client/driver/env" 21 "github.com/hashicorp/nomad/client/testutil" 22 "github.com/hashicorp/nomad/nomad/mock" 23 "github.com/hashicorp/nomad/nomad/structs" 24 tu "github.com/hashicorp/nomad/testutil" 25 ) 26 27 func dockerIsRemote(t *testing.T) bool { 28 client, err := docker.NewClientFromEnv() 29 if err != nil { 30 return false 31 } 32 33 // Technically this could be a local tcp socket but for testing purposes 34 // we'll just assume that tcp is only used for remote connections. 35 if client.Endpoint()[0:3] == "tcp" { 36 return true 37 } 38 return false 39 } 40 41 // Ports used by tests 42 var ( 43 docker_reserved = 32768 + int(rand.Int31n(25000)) 44 docker_dynamic = 32768 + int(rand.Int31n(25000)) 45 ) 46 47 // Returns a task with a reserved and dynamic port. The ports are returned 48 // respectively. 49 func dockerTask() (*structs.Task, int, int) { 50 docker_reserved += 1 51 docker_dynamic += 1 52 return &structs.Task{ 53 Name: "redis-demo", 54 Config: map[string]interface{}{ 55 "image": "busybox", 56 "load": []string{"busybox.tar"}, 57 "command": "/bin/nc", 58 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 59 }, 60 LogConfig: &structs.LogConfig{ 61 MaxFiles: 10, 62 MaxFileSizeMB: 10, 63 }, 64 Resources: &structs.Resources{ 65 MemoryMB: 256, 66 CPU: 512, 67 Networks: []*structs.NetworkResource{ 68 &structs.NetworkResource{ 69 IP: "127.0.0.1", 70 ReservedPorts: []structs.Port{{"main", docker_reserved}}, 71 DynamicPorts: []structs.Port{{"REDIS", docker_dynamic}}, 72 }, 73 }, 74 }, 75 }, docker_reserved, docker_dynamic 76 } 77 78 // dockerSetup does all of the basic setup you need to get a running docker 79 // process up and running for testing. Use like: 80 // 81 // task := taskTemplate() 82 // // do custom task configuration 83 // client, handle, cleanup := dockerSetup(t, task) 84 // defer cleanup() 85 // // do test stuff 86 // 87 // If there is a problem during setup this function will abort or skip the test 88 // and indicate the reason. 89 func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) { 90 if !testutil.DockerIsConnected(t) { 91 t.SkipNow() 92 } 93 94 client, err := docker.NewClientFromEnv() 95 if err != nil { 96 t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack()) 97 } 98 99 driverCtx, execCtx := testDriverContexts(task) 100 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 101 driver := NewDockerDriver(driverCtx) 102 copyImage(execCtx, task, "busybox.tar", t) 103 104 handle, err := driver.Start(execCtx, task) 105 if err != nil { 106 execCtx.AllocDir.Destroy() 107 t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack()) 108 } 109 if handle == nil { 110 execCtx.AllocDir.Destroy() 111 t.Fatalf("handle is nil\nStack\n%s", debug.Stack()) 112 } 113 114 cleanup := func() { 115 handle.Kill() 116 execCtx.AllocDir.Destroy() 117 } 118 119 return client, handle, cleanup 120 } 121 122 // This test should always pass, even if docker daemon is not available 123 func TestDockerDriver_Fingerprint(t *testing.T) { 124 driverCtx, execCtx := testDriverContexts(&structs.Task{Name: "foo", Resources: basicResources}) 125 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 126 defer execCtx.AllocDir.Destroy() 127 d := NewDockerDriver(driverCtx) 128 node := &structs.Node{ 129 Attributes: make(map[string]string), 130 } 131 apply, err := d.Fingerprint(&config.Config{}, node) 132 if err != nil { 133 t.Fatalf("err: %v", err) 134 } 135 if apply != testutil.DockerIsConnected(t) { 136 t.Fatalf("Fingerprinter should detect when docker is available") 137 } 138 if node.Attributes["driver.docker"] != "1" { 139 t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.") 140 } 141 t.Logf("Found docker version %s", node.Attributes["driver.docker.version"]) 142 } 143 144 func TestDockerDriver_StartOpen_Wait(t *testing.T) { 145 if !testutil.DockerIsConnected(t) { 146 t.SkipNow() 147 } 148 149 task := &structs.Task{ 150 Name: "nc-demo", 151 Config: map[string]interface{}{ 152 "load": []string{"busybox.tar"}, 153 "image": "busybox", 154 "command": "/bin/nc", 155 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 156 }, 157 LogConfig: &structs.LogConfig{ 158 MaxFiles: 10, 159 MaxFileSizeMB: 10, 160 }, 161 Resources: basicResources, 162 } 163 164 driverCtx, execCtx := testDriverContexts(task) 165 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 166 defer execCtx.AllocDir.Destroy() 167 d := NewDockerDriver(driverCtx) 168 copyImage(execCtx, task, "busybox.tar", t) 169 170 handle, err := d.Start(execCtx, task) 171 if err != nil { 172 t.Fatalf("err: %v", err) 173 } 174 if handle == nil { 175 t.Fatalf("missing handle") 176 } 177 defer handle.Kill() 178 179 // Attempt to open 180 handle2, err := d.Open(execCtx, handle.ID()) 181 if err != nil { 182 t.Fatalf("err: %v", err) 183 } 184 if handle2 == nil { 185 t.Fatalf("missing handle") 186 } 187 } 188 189 func TestDockerDriver_Start_Wait(t *testing.T) { 190 task := &structs.Task{ 191 Name: "nc-demo", 192 Config: map[string]interface{}{ 193 "load": []string{"busybox.tar"}, 194 "image": "busybox", 195 "command": "/bin/echo", 196 "args": []string{"hello"}, 197 }, 198 Resources: &structs.Resources{ 199 MemoryMB: 256, 200 CPU: 512, 201 }, 202 LogConfig: &structs.LogConfig{ 203 MaxFiles: 10, 204 MaxFileSizeMB: 10, 205 }, 206 } 207 208 _, handle, cleanup := dockerSetup(t, task) 209 defer cleanup() 210 211 // Update should be a no-op 212 err := handle.Update(task) 213 if err != nil { 214 t.Fatalf("err: %v", err) 215 } 216 217 select { 218 case res := <-handle.WaitCh(): 219 if !res.Successful() { 220 t.Fatalf("err: %v", res) 221 } 222 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 223 t.Fatalf("timeout") 224 } 225 } 226 227 func TestDockerDriver_Start_LoadImage(t *testing.T) { 228 if !testutil.DockerIsConnected(t) { 229 t.SkipNow() 230 } 231 task := &structs.Task{ 232 Name: "busybox-demo", 233 Config: map[string]interface{}{ 234 "image": "busybox", 235 "load": []string{"busybox.tar"}, 236 "command": "/bin/echo", 237 "args": []string{ 238 "hello", 239 }, 240 }, 241 LogConfig: &structs.LogConfig{ 242 MaxFiles: 10, 243 MaxFileSizeMB: 10, 244 }, 245 Resources: &structs.Resources{ 246 MemoryMB: 256, 247 CPU: 512, 248 }, 249 } 250 251 driverCtx, execCtx := testDriverContexts(task) 252 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 253 defer execCtx.AllocDir.Destroy() 254 d := NewDockerDriver(driverCtx) 255 256 // Copy the image into the task's directory 257 copyImage(execCtx, task, "busybox.tar", t) 258 259 handle, err := d.Start(execCtx, task) 260 if err != nil { 261 t.Fatalf("err: %v", err) 262 } 263 if handle == nil { 264 t.Fatalf("missing handle") 265 } 266 defer handle.Kill() 267 268 select { 269 case res := <-handle.WaitCh(): 270 if !res.Successful() { 271 t.Fatalf("err: %v", res) 272 } 273 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 274 t.Fatalf("timeout") 275 } 276 277 // Check that data was written to the shared alloc directory. 278 outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "busybox-demo.stdout.0") 279 act, err := ioutil.ReadFile(outputFile) 280 if err != nil { 281 t.Fatalf("Couldn't read expected output: %v", err) 282 } 283 284 exp := "hello" 285 if strings.TrimSpace(string(act)) != exp { 286 t.Fatalf("Command outputted %v; want %v", act, exp) 287 } 288 289 } 290 291 func TestDockerDriver_Start_BadPull_Recoverable(t *testing.T) { 292 if !testutil.DockerIsConnected(t) { 293 t.SkipNow() 294 } 295 task := &structs.Task{ 296 Name: "busybox-demo", 297 Config: map[string]interface{}{ 298 "image": "127.0.1.1:32121/foo", // bad path 299 "command": "/bin/echo", 300 "args": []string{ 301 "hello", 302 }, 303 }, 304 LogConfig: &structs.LogConfig{ 305 MaxFiles: 10, 306 MaxFileSizeMB: 10, 307 }, 308 Resources: &structs.Resources{ 309 MemoryMB: 256, 310 CPU: 512, 311 }, 312 } 313 314 driverCtx, execCtx := testDriverContexts(task) 315 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 316 defer execCtx.AllocDir.Destroy() 317 d := NewDockerDriver(driverCtx) 318 319 _, err := d.Start(execCtx, task) 320 if err == nil { 321 t.Fatalf("want err: %v", err) 322 } 323 324 if rerr, ok := err.(*structs.RecoverableError); !ok { 325 t.Fatalf("want recoverable error: %+v", err) 326 } else if !rerr.Recoverable { 327 t.Fatalf("error not recoverable: %+v", err) 328 } 329 } 330 331 func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) { 332 // This test requires that the alloc dir be mounted into docker as a volume. 333 // Because this cannot happen when docker is run remotely, e.g. when running 334 // docker in a VM, we skip this when we detect Docker is being run remotely. 335 if !testutil.DockerIsConnected(t) || dockerIsRemote(t) { 336 t.SkipNow() 337 } 338 339 exp := []byte{'w', 'i', 'n'} 340 file := "output.txt" 341 task := &structs.Task{ 342 Name: "nc-demo", 343 Config: map[string]interface{}{ 344 "image": "busybox", 345 "load": []string{"busybox.tar"}, 346 "command": "/bin/sh", 347 "args": []string{ 348 "-c", 349 fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`, 350 string(exp), env.AllocDir, file), 351 }, 352 }, 353 LogConfig: &structs.LogConfig{ 354 MaxFiles: 10, 355 MaxFileSizeMB: 10, 356 }, 357 Resources: &structs.Resources{ 358 MemoryMB: 256, 359 CPU: 512, 360 }, 361 } 362 363 driverCtx, execCtx := testDriverContexts(task) 364 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 365 defer execCtx.AllocDir.Destroy() 366 d := NewDockerDriver(driverCtx) 367 copyImage(execCtx, task, "busybox.tar", t) 368 369 handle, err := d.Start(execCtx, task) 370 if err != nil { 371 t.Fatalf("err: %v", err) 372 } 373 if handle == nil { 374 t.Fatalf("missing handle") 375 } 376 defer handle.Kill() 377 378 select { 379 case res := <-handle.WaitCh(): 380 if !res.Successful() { 381 t.Fatalf("err: %v", res) 382 } 383 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 384 t.Fatalf("timeout") 385 } 386 387 // Check that data was written to the shared alloc directory. 388 outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file) 389 act, err := ioutil.ReadFile(outputFile) 390 if err != nil { 391 t.Fatalf("Couldn't read expected output: %v", err) 392 } 393 394 if !reflect.DeepEqual(act, exp) { 395 t.Fatalf("Command outputted %v; want %v", act, exp) 396 } 397 } 398 399 func TestDockerDriver_Start_Kill_Wait(t *testing.T) { 400 task := &structs.Task{ 401 Name: "nc-demo", 402 Config: map[string]interface{}{ 403 "image": "busybox", 404 "load": []string{"busybox.tar"}, 405 "command": "/bin/sleep", 406 "args": []string{"10"}, 407 }, 408 LogConfig: &structs.LogConfig{ 409 MaxFiles: 10, 410 MaxFileSizeMB: 10, 411 }, 412 Resources: basicResources, 413 } 414 415 _, handle, cleanup := dockerSetup(t, task) 416 defer cleanup() 417 418 go func() { 419 time.Sleep(100 * time.Millisecond) 420 err := handle.Kill() 421 if err != nil { 422 t.Fatalf("err: %v", err) 423 } 424 }() 425 426 select { 427 case res := <-handle.WaitCh(): 428 if res.Successful() { 429 t.Fatalf("should err: %v", res) 430 } 431 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 432 t.Fatalf("timeout") 433 } 434 } 435 436 func TestDockerDriver_StartN(t *testing.T) { 437 if !testutil.DockerIsConnected(t) { 438 t.SkipNow() 439 } 440 441 task1, _, _ := dockerTask() 442 task2, _, _ := dockerTask() 443 task3, _, _ := dockerTask() 444 taskList := []*structs.Task{task1, task2, task3} 445 446 handles := make([]DriverHandle, len(taskList)) 447 448 t.Logf("Starting %d tasks", len(taskList)) 449 450 // Let's spin up a bunch of things 451 var err error 452 for idx, task := range taskList { 453 driverCtx, execCtx := testDriverContexts(task) 454 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 455 defer execCtx.AllocDir.Destroy() 456 d := NewDockerDriver(driverCtx) 457 copyImage(execCtx, task, "busybox.tar", t) 458 459 handles[idx], err = d.Start(execCtx, task) 460 if err != nil { 461 t.Errorf("Failed starting task #%d: %s", idx+1, err) 462 } 463 } 464 465 t.Log("All tasks are started. Terminating...") 466 467 for idx, handle := range handles { 468 if handle == nil { 469 t.Errorf("Bad handle for task #%d", idx+1) 470 continue 471 } 472 473 err := handle.Kill() 474 if err != nil { 475 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 476 } 477 } 478 479 t.Log("Test complete!") 480 } 481 482 func TestDockerDriver_StartNVersions(t *testing.T) { 483 if !testutil.DockerIsConnected(t) { 484 t.SkipNow() 485 } 486 487 task1, _, _ := dockerTask() 488 task1.Config["image"] = "busybox" 489 task1.Config["load"] = []string{"busybox.tar"} 490 491 task2, _, _ := dockerTask() 492 task2.Config["image"] = "busybox:musl" 493 task2.Config["load"] = []string{"busybox_musl.tar"} 494 495 task3, _, _ := dockerTask() 496 task3.Config["image"] = "busybox:glibc" 497 task3.Config["load"] = []string{"busybox_glibc.tar"} 498 499 taskList := []*structs.Task{task1, task2, task3} 500 501 handles := make([]DriverHandle, len(taskList)) 502 503 t.Logf("Starting %d tasks", len(taskList)) 504 505 // Let's spin up a bunch of things 506 var err error 507 for idx, task := range taskList { 508 driverCtx, execCtx := testDriverContexts(task) 509 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 510 defer execCtx.AllocDir.Destroy() 511 d := NewDockerDriver(driverCtx) 512 copyImage(execCtx, task, "busybox.tar", t) 513 copyImage(execCtx, task, "busybox_musl.tar", t) 514 copyImage(execCtx, task, "busybox_glibc.tar", t) 515 516 handles[idx], err = d.Start(execCtx, task) 517 if err != nil { 518 t.Errorf("Failed starting task #%d: %s", idx+1, err) 519 } 520 } 521 522 t.Log("All tasks are started. Terminating...") 523 524 for idx, handle := range handles { 525 if handle == nil { 526 t.Errorf("Bad handle for task #%d", idx+1) 527 continue 528 } 529 530 err := handle.Kill() 531 if err != nil { 532 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 533 } 534 } 535 536 t.Log("Test complete!") 537 } 538 539 func waitForExist(t *testing.T, client *docker.Client, handle *DockerHandle) { 540 tu.WaitForResult(func() (bool, error) { 541 container, err := client.InspectContainer(handle.ContainerID()) 542 if err != nil { 543 if _, ok := err.(*docker.NoSuchContainer); !ok { 544 return false, err 545 } 546 } 547 548 return container != nil, nil 549 }, func(err error) { 550 t.Fatalf("err: %v", err) 551 }) 552 } 553 554 func TestDockerDriver_NetworkMode_Host(t *testing.T) { 555 expected := "host" 556 557 task := &structs.Task{ 558 Name: "nc-demo", 559 Config: map[string]interface{}{ 560 "image": "busybox", 561 "load": []string{"busybox.tar"}, 562 "command": "/bin/nc", 563 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 564 "network_mode": expected, 565 }, 566 Resources: &structs.Resources{ 567 MemoryMB: 256, 568 CPU: 512, 569 }, 570 LogConfig: &structs.LogConfig{ 571 MaxFiles: 10, 572 MaxFileSizeMB: 10, 573 }, 574 } 575 576 client, handle, cleanup := dockerSetup(t, task) 577 defer cleanup() 578 579 waitForExist(t, client, handle.(*DockerHandle)) 580 581 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 582 if err != nil { 583 t.Fatalf("err: %v", err) 584 } 585 586 actual := container.HostConfig.NetworkMode 587 if actual != expected { 588 t.Fatalf("Got network mode %q; want %q", expected, actual) 589 } 590 } 591 592 func TestDockerDriver_Labels(t *testing.T) { 593 task, _, _ := dockerTask() 594 task.Config["labels"] = []map[string]string{ 595 map[string]string{ 596 "label1": "value1", 597 "label2": "value2", 598 }, 599 } 600 601 client, handle, cleanup := dockerSetup(t, task) 602 defer cleanup() 603 604 waitForExist(t, client, handle.(*DockerHandle)) 605 606 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 607 if err != nil { 608 t.Fatalf("err: %v", err) 609 } 610 611 if want, got := 2, len(container.Config.Labels); want != got { 612 t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got) 613 } 614 615 if want, got := "value1", container.Config.Labels["label1"]; want != got { 616 t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got) 617 } 618 } 619 620 func TestDockerDriver_DNS(t *testing.T) { 621 task, _, _ := dockerTask() 622 task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"} 623 task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"} 624 625 client, handle, cleanup := dockerSetup(t, task) 626 defer cleanup() 627 628 waitForExist(t, client, handle.(*DockerHandle)) 629 630 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 631 if err != nil { 632 t.Fatalf("err: %v", err) 633 } 634 635 if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) { 636 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS) 637 } 638 639 if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) { 640 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch) 641 } 642 } 643 644 func TestDockerWorkDir(t *testing.T) { 645 task, _, _ := dockerTask() 646 task.Config["work_dir"] = "/some/path" 647 648 client, handle, cleanup := dockerSetup(t, task) 649 defer cleanup() 650 651 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 652 if err != nil { 653 t.Fatalf("err: %v", err) 654 } 655 656 if want, got := "/some/path", container.Config.WorkingDir; want != got { 657 t.Errorf("Wrong working directory for docker job. Expect: %d, got: %d", want, got) 658 } 659 } 660 661 func inSlice(needle string, haystack []string) bool { 662 for _, h := range haystack { 663 if h == needle { 664 return true 665 } 666 } 667 return false 668 } 669 670 func TestDockerDriver_PortsNoMap(t *testing.T) { 671 task, res, dyn := dockerTask() 672 673 client, handle, cleanup := dockerSetup(t, task) 674 defer cleanup() 675 676 waitForExist(t, client, handle.(*DockerHandle)) 677 678 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 679 if err != nil { 680 t.Fatalf("err: %v", err) 681 } 682 683 // Verify that the correct ports are EXPOSED 684 expectedExposedPorts := map[docker.Port]struct{}{ 685 docker.Port(fmt.Sprintf("%d/tcp", res)): struct{}{}, 686 docker.Port(fmt.Sprintf("%d/udp", res)): struct{}{}, 687 docker.Port(fmt.Sprintf("%d/tcp", dyn)): struct{}{}, 688 docker.Port(fmt.Sprintf("%d/udp", dyn)): struct{}{}, 689 } 690 691 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 692 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 693 } 694 695 // Verify that the correct ports are FORWARDED 696 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 697 docker.Port(fmt.Sprintf("%d/tcp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 698 docker.Port(fmt.Sprintf("%d/udp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 699 docker.Port(fmt.Sprintf("%d/tcp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 700 docker.Port(fmt.Sprintf("%d/udp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 701 } 702 703 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 704 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 705 } 706 707 expectedEnvironment := map[string]string{ 708 "NOMAD_ADDR_main": fmt.Sprintf("127.0.0.1:%d", res), 709 "NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn), 710 } 711 712 for key, val := range expectedEnvironment { 713 search := fmt.Sprintf("%s=%s", key, val) 714 if !inSlice(search, container.Config.Env) { 715 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 716 } 717 } 718 } 719 720 func TestDockerDriver_PortsMapping(t *testing.T) { 721 task, res, dyn := dockerTask() 722 task.Config["port_map"] = []map[string]string{ 723 map[string]string{ 724 "main": "8080", 725 "REDIS": "6379", 726 }, 727 } 728 729 client, handle, cleanup := dockerSetup(t, task) 730 defer cleanup() 731 732 waitForExist(t, client, handle.(*DockerHandle)) 733 734 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 735 if err != nil { 736 t.Fatalf("err: %v", err) 737 } 738 739 // Verify that the correct ports are EXPOSED 740 expectedExposedPorts := map[docker.Port]struct{}{ 741 docker.Port("8080/tcp"): struct{}{}, 742 docker.Port("8080/udp"): struct{}{}, 743 docker.Port("6379/tcp"): struct{}{}, 744 docker.Port("6379/udp"): struct{}{}, 745 } 746 747 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 748 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 749 } 750 751 // Verify that the correct ports are FORWARDED 752 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 753 docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 754 docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 755 docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 756 docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 757 } 758 759 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 760 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 761 } 762 763 expectedEnvironment := map[string]string{ 764 "NOMAD_ADDR_main": "127.0.0.1:8080", 765 "NOMAD_ADDR_REDIS": "127.0.0.1:6379", 766 "NOMAD_HOST_PORT_main": strconv.Itoa(docker_reserved), 767 } 768 769 for key, val := range expectedEnvironment { 770 search := fmt.Sprintf("%s=%s", key, val) 771 if !inSlice(search, container.Config.Env) { 772 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 773 } 774 } 775 } 776 777 func TestDockerDriver_User(t *testing.T) { 778 task := &structs.Task{ 779 Name: "redis-demo", 780 User: "alice", 781 Config: map[string]interface{}{ 782 "image": "busybox", 783 "load": []string{"busybox.tar"}, 784 "command": "/bin/sleep", 785 "args": []string{"10000"}, 786 }, 787 Resources: &structs.Resources{ 788 MemoryMB: 256, 789 CPU: 512, 790 }, 791 LogConfig: &structs.LogConfig{ 792 MaxFiles: 10, 793 MaxFileSizeMB: 10, 794 }, 795 } 796 797 if !testutil.DockerIsConnected(t) { 798 t.SkipNow() 799 } 800 801 driverCtx, execCtx := testDriverContexts(task) 802 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 803 driver := NewDockerDriver(driverCtx) 804 defer execCtx.AllocDir.Destroy() 805 copyImage(execCtx, task, "busybox.tar", t) 806 807 // It should fail because the user "alice" does not exist on the given 808 // image. 809 handle, err := driver.Start(execCtx, task) 810 if err == nil { 811 handle.Kill() 812 t.Fatalf("Should've failed") 813 } 814 815 if !strings.Contains(err.Error(), "alice") { 816 t.Fatalf("Expected failure string not found, found %q instead", err.Error()) 817 } 818 } 819 820 func TestDockerDriver_CleanupContainer(t *testing.T) { 821 task := &structs.Task{ 822 Name: "redis-demo", 823 Config: map[string]interface{}{ 824 "image": "busybox", 825 "load": []string{"busybox.tar"}, 826 "command": "/bin/echo", 827 "args": []string{"hello"}, 828 }, 829 Resources: &structs.Resources{ 830 MemoryMB: 256, 831 CPU: 512, 832 }, 833 LogConfig: &structs.LogConfig{ 834 MaxFiles: 10, 835 MaxFileSizeMB: 10, 836 }, 837 } 838 839 _, handle, cleanup := dockerSetup(t, task) 840 defer cleanup() 841 842 // Update should be a no-op 843 err := handle.Update(task) 844 if err != nil { 845 t.Fatalf("err: %v", err) 846 } 847 848 select { 849 case res := <-handle.WaitCh(): 850 if !res.Successful() { 851 t.Fatalf("err: %v", res) 852 } 853 854 time.Sleep(3 * time.Second) 855 856 // Ensure that the container isn't present 857 _, err := client.InspectContainer(handle.(*DockerHandle).containerID) 858 if err == nil { 859 t.Fatalf("expected to not get container") 860 } 861 862 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 863 t.Fatalf("timeout") 864 } 865 } 866 867 func TestDockerDriver_Stats(t *testing.T) { 868 task := &structs.Task{ 869 Name: "sleep", 870 Config: map[string]interface{}{ 871 "image": "busybox", 872 "load": []string{"busybox.tar"}, 873 "command": "/bin/sleep", 874 "args": []string{"100"}, 875 }, 876 LogConfig: &structs.LogConfig{ 877 MaxFiles: 10, 878 MaxFileSizeMB: 10, 879 }, 880 Resources: basicResources, 881 } 882 883 _, handle, cleanup := dockerSetup(t, task) 884 defer cleanup() 885 886 waitForExist(t, client, handle.(*DockerHandle)) 887 888 go func() { 889 time.Sleep(3 * time.Second) 890 ru, err := handle.Stats() 891 if err != nil { 892 t.Fatalf("err: %v", err) 893 } 894 if ru.ResourceUsage == nil { 895 handle.Kill() 896 t.Fatalf("expected resource usage") 897 } 898 err = handle.Kill() 899 if err != nil { 900 t.Fatalf("err: %v", err) 901 } 902 }() 903 904 select { 905 case res := <-handle.WaitCh(): 906 if res.Successful() { 907 t.Fatalf("should err: %v", res) 908 } 909 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 910 t.Fatalf("timeout") 911 } 912 } 913 914 func TestDockerDriver_Signal(t *testing.T) { 915 task := &structs.Task{ 916 Name: "redis-demo", 917 Config: map[string]interface{}{ 918 "image": "busybox", 919 "load": []string{"busybox.tar"}, 920 "command": "/bin/sh", 921 "args": []string{"local/test.sh"}, 922 }, 923 Resources: &structs.Resources{ 924 MemoryMB: 256, 925 CPU: 512, 926 }, 927 LogConfig: &structs.LogConfig{ 928 MaxFiles: 10, 929 MaxFileSizeMB: 10, 930 }, 931 } 932 933 driverCtx, execCtx := testDriverContexts(task) 934 driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 935 defer execCtx.AllocDir.Destroy() 936 d := NewDockerDriver(driverCtx) 937 938 // Copy the image into the task's directory 939 copyImage(execCtx, task, "busybox.tar", t) 940 941 testFile := filepath.Join(execCtx.AllocDir.TaskDirs["redis-demo"], allocdir.TaskLocal, "test.sh") 942 testData := []byte(` 943 at_term() { 944 echo 'Terminated.' 945 exit 3 946 } 947 trap at_term USR1 948 while true; do 949 sleep 1 950 done 951 `) 952 if err := ioutil.WriteFile(testFile, testData, 0777); err != nil { 953 fmt.Errorf("Failed to write data") 954 } 955 956 handle, err := d.Start(execCtx, task) 957 if err != nil { 958 t.Fatalf("err: %v", err) 959 } 960 if handle == nil { 961 t.Fatalf("missing handle") 962 } 963 defer handle.Kill() 964 965 waitForExist(t, handle.(*DockerHandle).client, handle.(*DockerHandle)) 966 967 time.Sleep(1 * time.Second) 968 if err := handle.Signal(syscall.SIGUSR1); err != nil { 969 t.Fatalf("Signal returned an error: %v", err) 970 } 971 972 select { 973 case res := <-handle.WaitCh(): 974 if res.Successful() { 975 t.Fatalf("should err: %v", res) 976 } 977 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 978 t.Fatalf("timeout") 979 } 980 981 // Check the log file to see it exited because of the signal 982 outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "redis-demo.stdout.0") 983 act, err := ioutil.ReadFile(outputFile) 984 if err != nil { 985 t.Fatalf("Couldn't read expected output: %v", err) 986 } 987 988 exp := "Terminated." 989 if strings.TrimSpace(string(act)) != exp { 990 t.Fatalf("Command outputted %v; want %v", act, exp) 991 } 992 } 993 994 func setupDockerVolumes(t *testing.T, cfg *config.Config, hostpath string) (*structs.Task, Driver, *ExecContext, string, func()) { 995 if !testutil.DockerIsConnected(t) { 996 t.SkipNow() 997 } 998 999 randfn := fmt.Sprintf("test-%d", rand.Int()) 1000 hostfile := filepath.Join(hostpath, randfn) 1001 containerPath := "/mnt/vol" 1002 containerFile := filepath.Join(containerPath, randfn) 1003 1004 task := &structs.Task{ 1005 Name: "ls", 1006 Env: map[string]string{"VOL_PATH": containerPath}, 1007 Config: map[string]interface{}{ 1008 "image": "busybox", 1009 "load": []string{"busybox.tar"}, 1010 "command": "touch", 1011 "args": []string{containerFile}, 1012 "volumes": []string{fmt.Sprintf("%s:${VOL_PATH}", hostpath)}, 1013 }, 1014 LogConfig: &structs.LogConfig{ 1015 MaxFiles: 10, 1016 MaxFileSizeMB: 10, 1017 }, 1018 Resources: basicResources, 1019 } 1020 1021 allocDir := allocdir.NewAllocDir(filepath.Join(cfg.AllocDir, structs.GenerateUUID())) 1022 allocDir.Build([]*structs.Task{task}) 1023 alloc := mock.Alloc() 1024 execCtx := NewExecContext(allocDir, alloc.ID) 1025 cleanup := func() { 1026 execCtx.AllocDir.Destroy() 1027 if filepath.IsAbs(hostpath) { 1028 os.RemoveAll(hostpath) 1029 } 1030 } 1031 1032 taskEnv, err := GetTaskEnv(allocDir, cfg.Node, task, alloc, "") 1033 if err != nil { 1034 cleanup() 1035 t.Fatalf("Failed to get task env: %v", err) 1036 } 1037 1038 driverCtx := NewDriverContext(task.Name, cfg, cfg.Node, testLogger(), taskEnv) 1039 driver := NewDockerDriver(driverCtx) 1040 copyImage(execCtx, task, "busybox.tar", t) 1041 1042 return task, driver, execCtx, hostfile, cleanup 1043 } 1044 1045 func TestDockerDriver_VolumesDisabled(t *testing.T) { 1046 cfg := testConfig() 1047 cfg.Options = map[string]string{ 1048 dockerVolumesConfigOption: "false", 1049 "docker.cleanup.image": "false", 1050 } 1051 1052 { 1053 tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesdisabled") 1054 if err != nil { 1055 t.Fatalf("error creating temporary dir: %v", err) 1056 } 1057 1058 task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, tmpvol) 1059 defer cleanup() 1060 1061 if _, err := driver.Start(execCtx, task); err == nil { 1062 t.Fatalf("Started driver successfully when volumes should have been disabled.") 1063 } 1064 } 1065 1066 // Relative paths should still be allowed 1067 { 1068 task, driver, execCtx, fn, cleanup := setupDockerVolumes(t, cfg, ".") 1069 defer cleanup() 1070 1071 handle, err := driver.Start(execCtx, task) 1072 if err != nil { 1073 t.Fatalf("err: %v", err) 1074 } 1075 defer handle.Kill() 1076 1077 select { 1078 case res := <-handle.WaitCh(): 1079 if !res.Successful() { 1080 t.Fatalf("unexpected err: %v", res) 1081 } 1082 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1083 t.Fatalf("timeout") 1084 } 1085 1086 if _, err := ioutil.ReadFile(filepath.Join(execCtx.AllocDir.SharedDir, fn)); err != nil { 1087 t.Fatalf("unexpected error reading %s: %v", fn, err) 1088 } 1089 } 1090 1091 } 1092 1093 func TestDockerDriver_VolumesEnabled(t *testing.T) { 1094 cfg := testConfig() 1095 1096 tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesenabled") 1097 if err != nil { 1098 t.Fatalf("error creating temporary dir: %v", err) 1099 } 1100 1101 task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg, tmpvol) 1102 defer cleanup() 1103 1104 handle, err := driver.Start(execCtx, task) 1105 if err != nil { 1106 t.Fatalf("Failed to start docker driver: %v", err) 1107 } 1108 defer handle.Kill() 1109 1110 select { 1111 case res := <-handle.WaitCh(): 1112 if !res.Successful() { 1113 t.Fatalf("unexpected err: %v", res) 1114 } 1115 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1116 t.Fatalf("timeout") 1117 } 1118 1119 if _, err := ioutil.ReadFile(hostpath); err != nil { 1120 t.Fatalf("unexpected error reading %s: %v", hostpath, err) 1121 } 1122 } 1123 1124 func copyImage(execCtx *ExecContext, task *structs.Task, image string, t *testing.T) { 1125 taskDir, _ := execCtx.AllocDir.TaskDirs[task.Name] 1126 dst := filepath.Join(taskDir, allocdir.TaskLocal, image) 1127 copyFile(filepath.Join("./test-resources/docker", image), dst, t) 1128 }