github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/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 "testing" 14 "time" 15 16 docker "github.com/fsouza/go-dockerclient" 17 "github.com/hashicorp/nomad/client/allocdir" 18 "github.com/hashicorp/nomad/client/config" 19 "github.com/hashicorp/nomad/client/driver/env" 20 cstructs "github.com/hashicorp/nomad/client/structs" 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 Driver: "docker", 55 Config: map[string]interface{}{ 56 "image": "busybox", 57 "load": "busybox.tar", 58 "command": "/bin/nc", 59 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 60 }, 61 LogConfig: &structs.LogConfig{ 62 MaxFiles: 10, 63 MaxFileSizeMB: 10, 64 }, 65 Resources: &structs.Resources{ 66 MemoryMB: 256, 67 CPU: 512, 68 Networks: []*structs.NetworkResource{ 69 &structs.NetworkResource{ 70 IP: "127.0.0.1", 71 ReservedPorts: []structs.Port{{Label: "main", Value: docker_reserved}}, 72 DynamicPorts: []structs.Port{{Label: "REDIS", Value: docker_dynamic}}, 73 }, 74 }, 75 }, 76 }, docker_reserved, docker_dynamic 77 } 78 79 // dockerSetup does all of the basic setup you need to get a running docker 80 // process up and running for testing. Use like: 81 // 82 // task := taskTemplate() 83 // // do custom task configuration 84 // client, handle, cleanup := dockerSetup(t, task) 85 // defer cleanup() 86 // // do test stuff 87 // 88 // If there is a problem during setup this function will abort or skip the test 89 // and indicate the reason. 90 func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) { 91 client := newTestDockerClient(t) 92 return dockerSetupWithClient(t, task, client) 93 } 94 95 func testDockerDriverContexts(t *testing.T, task *structs.Task) *testContext { 96 tctx := testDriverContexts(t, task) 97 98 // Drop the delay 99 tctx.DriverCtx.config.Options = make(map[string]string) 100 tctx.DriverCtx.config.Options[dockerImageRemoveDelayConfigOption] = "1s" 101 102 return tctx 103 } 104 105 func dockerSetupWithClient(t *testing.T, task *structs.Task, client *docker.Client) (*docker.Client, DriverHandle, func()) { 106 tctx := testDockerDriverContexts(t, task) 107 //tctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 108 driver := NewDockerDriver(tctx.DriverCtx) 109 copyImage(t, tctx.ExecCtx.TaskDir, "busybox.tar") 110 111 res, err := driver.Prestart(tctx.ExecCtx, task) 112 if err != nil { 113 tctx.AllocDir.Destroy() 114 t.Fatalf("error in prestart: %v", err) 115 } 116 117 handle, err := driver.Start(tctx.ExecCtx, task) 118 if err != nil { 119 tctx.AllocDir.Destroy() 120 t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack()) 121 } 122 123 if handle == nil { 124 tctx.AllocDir.Destroy() 125 t.Fatalf("handle is nil\nStack\n%s", debug.Stack()) 126 } 127 128 cleanup := func() { 129 driver.Cleanup(tctx.ExecCtx, res) 130 handle.Kill() 131 tctx.AllocDir.Destroy() 132 } 133 134 return client, handle, cleanup 135 } 136 137 func newTestDockerClient(t *testing.T) *docker.Client { 138 if !testutil.DockerIsConnected(t) { 139 t.SkipNow() 140 } 141 142 client, err := docker.NewClientFromEnv() 143 if err != nil { 144 t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack()) 145 } 146 return client 147 } 148 149 // This test should always pass, even if docker daemon is not available 150 func TestDockerDriver_Fingerprint(t *testing.T) { 151 ctx := testDockerDriverContexts(t, &structs.Task{Name: "foo", Driver: "docker", Resources: basicResources}) 152 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 153 defer ctx.AllocDir.Destroy() 154 d := NewDockerDriver(ctx.DriverCtx) 155 node := &structs.Node{ 156 Attributes: make(map[string]string), 157 } 158 apply, err := d.Fingerprint(&config.Config{}, node) 159 if err != nil { 160 t.Fatalf("err: %v", err) 161 } 162 if apply != testutil.DockerIsConnected(t) { 163 t.Fatalf("Fingerprinter should detect when docker is available") 164 } 165 if node.Attributes["driver.docker"] != "1" { 166 t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.") 167 } 168 t.Logf("Found docker version %s", node.Attributes["driver.docker.version"]) 169 } 170 171 func TestDockerDriver_StartOpen_Wait(t *testing.T) { 172 if !testutil.DockerIsConnected(t) { 173 t.SkipNow() 174 } 175 176 task := &structs.Task{ 177 Name: "nc-demo", 178 Driver: "docker", 179 Config: map[string]interface{}{ 180 "load": "busybox.tar", 181 "image": "busybox", 182 "command": "/bin/nc", 183 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 184 }, 185 LogConfig: &structs.LogConfig{ 186 MaxFiles: 10, 187 MaxFileSizeMB: 10, 188 }, 189 Resources: basicResources, 190 } 191 192 ctx := testDockerDriverContexts(t, task) 193 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 194 defer ctx.AllocDir.Destroy() 195 d := NewDockerDriver(ctx.DriverCtx) 196 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 197 198 _, err := d.Prestart(ctx.ExecCtx, task) 199 if err != nil { 200 t.Fatalf("error in prestart: %v", err) 201 } 202 203 handle, err := d.Start(ctx.ExecCtx, task) 204 if err != nil { 205 t.Fatalf("err: %v", err) 206 } 207 if handle == nil { 208 t.Fatalf("missing handle") 209 } 210 defer handle.Kill() 211 212 // Attempt to open 213 handle2, err := d.Open(ctx.ExecCtx, handle.ID()) 214 if err != nil { 215 t.Fatalf("err: %v", err) 216 } 217 if handle2 == nil { 218 t.Fatalf("missing handle") 219 } 220 } 221 222 func TestDockerDriver_Start_Wait(t *testing.T) { 223 task := &structs.Task{ 224 Name: "nc-demo", 225 Driver: "docker", 226 Config: map[string]interface{}{ 227 "load": "busybox.tar", 228 "image": "busybox", 229 "command": "/bin/echo", 230 "args": []string{"hello"}, 231 }, 232 Resources: &structs.Resources{ 233 MemoryMB: 256, 234 CPU: 512, 235 }, 236 LogConfig: &structs.LogConfig{ 237 MaxFiles: 10, 238 MaxFileSizeMB: 10, 239 }, 240 } 241 242 _, handle, cleanup := dockerSetup(t, task) 243 defer cleanup() 244 245 // Update should be a no-op 246 err := handle.Update(task) 247 if err != nil { 248 t.Fatalf("err: %v", err) 249 } 250 251 select { 252 case res := <-handle.WaitCh(): 253 if !res.Successful() { 254 t.Fatalf("err: %v", res) 255 } 256 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 257 t.Fatalf("timeout") 258 } 259 } 260 261 func TestDockerDriver_Start_LoadImage(t *testing.T) { 262 if !testutil.DockerIsConnected(t) { 263 t.SkipNow() 264 } 265 task := &structs.Task{ 266 Name: "busybox-demo", 267 Driver: "docker", 268 Config: map[string]interface{}{ 269 "image": "busybox", 270 "load": "busybox.tar", 271 "command": "/bin/echo", 272 "args": []string{ 273 "hello", 274 }, 275 }, 276 LogConfig: &structs.LogConfig{ 277 MaxFiles: 10, 278 MaxFileSizeMB: 10, 279 }, 280 Resources: &structs.Resources{ 281 MemoryMB: 256, 282 CPU: 512, 283 }, 284 } 285 286 ctx := testDockerDriverContexts(t, task) 287 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 288 defer ctx.AllocDir.Destroy() 289 d := NewDockerDriver(ctx.DriverCtx) 290 291 // Copy the image into the task's directory 292 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 293 294 _, err := d.Prestart(ctx.ExecCtx, task) 295 if err != nil { 296 t.Fatalf("error in prestart: %v", err) 297 } 298 handle, err := d.Start(ctx.ExecCtx, task) 299 if err != nil { 300 t.Fatalf("err: %v", err) 301 } 302 if handle == nil { 303 t.Fatalf("missing handle") 304 } 305 defer handle.Kill() 306 307 select { 308 case res := <-handle.WaitCh(): 309 if !res.Successful() { 310 t.Fatalf("err: %v", res) 311 } 312 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 313 t.Fatalf("timeout") 314 } 315 316 // Check that data was written to the shared alloc directory. 317 outputFile := filepath.Join(ctx.ExecCtx.TaskDir.LogDir, "busybox-demo.stdout.0") 318 act, err := ioutil.ReadFile(outputFile) 319 if err != nil { 320 t.Fatalf("Couldn't read expected output: %v", err) 321 } 322 323 exp := "hello" 324 if strings.TrimSpace(string(act)) != exp { 325 t.Fatalf("Command outputted %v; want %v", act, exp) 326 } 327 328 } 329 330 func TestDockerDriver_Start_BadPull_Recoverable(t *testing.T) { 331 if !testutil.DockerIsConnected(t) { 332 t.SkipNow() 333 } 334 task := &structs.Task{ 335 Name: "busybox-demo", 336 Driver: "docker", 337 Config: map[string]interface{}{ 338 "image": "127.0.1.1:32121/foo", // bad path 339 "command": "/bin/echo", 340 "args": []string{ 341 "hello", 342 }, 343 }, 344 LogConfig: &structs.LogConfig{ 345 MaxFiles: 10, 346 MaxFileSizeMB: 10, 347 }, 348 Resources: &structs.Resources{ 349 MemoryMB: 256, 350 CPU: 512, 351 }, 352 } 353 354 ctx := testDockerDriverContexts(t, task) 355 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 356 defer ctx.AllocDir.Destroy() 357 d := NewDockerDriver(ctx.DriverCtx) 358 359 _, err := d.Prestart(ctx.ExecCtx, task) 360 if err == nil { 361 t.Fatalf("want error in prestart: %v", err) 362 } 363 364 if rerr, ok := err.(*structs.RecoverableError); !ok { 365 t.Fatalf("want recoverable error: %+v", err) 366 } else if !rerr.IsRecoverable() { 367 t.Fatalf("error not recoverable: %+v", err) 368 } 369 } 370 371 func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) { 372 // This test requires that the alloc dir be mounted into docker as a volume. 373 // Because this cannot happen when docker is run remotely, e.g. when running 374 // docker in a VM, we skip this when we detect Docker is being run remotely. 375 if !testutil.DockerIsConnected(t) || dockerIsRemote(t) { 376 t.SkipNow() 377 } 378 379 exp := []byte{'w', 'i', 'n'} 380 file := "output.txt" 381 task := &structs.Task{ 382 Name: "nc-demo", 383 Driver: "docker", 384 Config: map[string]interface{}{ 385 "image": "busybox", 386 "load": "busybox.tar", 387 "command": "/bin/sh", 388 "args": []string{ 389 "-c", 390 fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`, 391 string(exp), env.AllocDir, file), 392 }, 393 }, 394 LogConfig: &structs.LogConfig{ 395 MaxFiles: 10, 396 MaxFileSizeMB: 10, 397 }, 398 Resources: &structs.Resources{ 399 MemoryMB: 256, 400 CPU: 512, 401 }, 402 } 403 404 ctx := testDockerDriverContexts(t, task) 405 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 406 defer ctx.AllocDir.Destroy() 407 d := NewDockerDriver(ctx.DriverCtx) 408 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 409 410 _, err := d.Prestart(ctx.ExecCtx, task) 411 if err != nil { 412 t.Fatalf("error in prestart: %v", err) 413 } 414 handle, err := d.Start(ctx.ExecCtx, task) 415 if err != nil { 416 t.Fatalf("err: %v", err) 417 } 418 if handle == nil { 419 t.Fatalf("missing handle") 420 } 421 defer handle.Kill() 422 423 select { 424 case res := <-handle.WaitCh(): 425 if !res.Successful() { 426 t.Fatalf("err: %v", res) 427 } 428 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 429 t.Fatalf("timeout") 430 } 431 432 // Check that data was written to the shared alloc directory. 433 outputFile := filepath.Join(ctx.AllocDir.SharedDir, file) 434 act, err := ioutil.ReadFile(outputFile) 435 if err != nil { 436 t.Fatalf("Couldn't read expected output: %v", err) 437 } 438 439 if !reflect.DeepEqual(act, exp) { 440 t.Fatalf("Command outputted %v; want %v", act, exp) 441 } 442 } 443 444 func TestDockerDriver_Start_Kill_Wait(t *testing.T) { 445 task := &structs.Task{ 446 Name: "nc-demo", 447 Driver: "docker", 448 Config: map[string]interface{}{ 449 "image": "busybox", 450 "load": "busybox.tar", 451 "command": "/bin/sleep", 452 "args": []string{"10"}, 453 }, 454 LogConfig: &structs.LogConfig{ 455 MaxFiles: 10, 456 MaxFileSizeMB: 10, 457 }, 458 Resources: basicResources, 459 } 460 461 _, handle, cleanup := dockerSetup(t, task) 462 defer cleanup() 463 464 go func() { 465 time.Sleep(100 * time.Millisecond) 466 err := handle.Kill() 467 if err != nil { 468 t.Fatalf("err: %v", err) 469 } 470 }() 471 472 select { 473 case res := <-handle.WaitCh(): 474 if res.Successful() { 475 t.Fatalf("should err: %v", res) 476 } 477 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 478 t.Fatalf("timeout") 479 } 480 } 481 482 func TestDockerDriver_StartN(t *testing.T) { 483 if !testutil.DockerIsConnected(t) { 484 t.SkipNow() 485 } 486 487 task1, _, _ := dockerTask() 488 task2, _, _ := dockerTask() 489 task3, _, _ := dockerTask() 490 taskList := []*structs.Task{task1, task2, task3} 491 492 handles := make([]DriverHandle, len(taskList)) 493 494 t.Logf("Starting %d tasks", len(taskList)) 495 496 // Let's spin up a bunch of things 497 for idx, task := range taskList { 498 ctx := testDockerDriverContexts(t, task) 499 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 500 defer ctx.AllocDir.Destroy() 501 d := NewDockerDriver(ctx.DriverCtx) 502 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 503 504 _, err := d.Prestart(ctx.ExecCtx, task) 505 if err != nil { 506 t.Fatalf("error in prestart #%d: %v", idx+1, err) 507 } 508 handles[idx], err = d.Start(ctx.ExecCtx, task) 509 if err != nil { 510 t.Errorf("Failed starting task #%d: %s", idx+1, err) 511 } 512 } 513 514 t.Log("All tasks are started. Terminating...") 515 516 for idx, handle := range handles { 517 if handle == nil { 518 t.Errorf("Bad handle for task #%d", idx+1) 519 continue 520 } 521 522 err := handle.Kill() 523 if err != nil { 524 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 525 } 526 } 527 528 t.Log("Test complete!") 529 } 530 531 func TestDockerDriver_StartNVersions(t *testing.T) { 532 if !testutil.DockerIsConnected(t) { 533 t.SkipNow() 534 } 535 536 task1, _, _ := dockerTask() 537 task1.Config["image"] = "busybox" 538 task1.Config["load"] = "busybox.tar" 539 540 task2, _, _ := dockerTask() 541 task2.Config["image"] = "busybox:musl" 542 task2.Config["load"] = "busybox_musl.tar" 543 544 task3, _, _ := dockerTask() 545 task3.Config["image"] = "busybox:glibc" 546 task3.Config["load"] = "busybox_glibc.tar" 547 548 taskList := []*structs.Task{task1, task2, task3} 549 550 handles := make([]DriverHandle, len(taskList)) 551 552 t.Logf("Starting %d tasks", len(taskList)) 553 554 // Let's spin up a bunch of things 555 for idx, task := range taskList { 556 ctx := testDockerDriverContexts(t, task) 557 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 558 defer ctx.AllocDir.Destroy() 559 d := NewDockerDriver(ctx.DriverCtx) 560 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 561 copyImage(t, ctx.ExecCtx.TaskDir, "busybox_musl.tar") 562 copyImage(t, ctx.ExecCtx.TaskDir, "busybox_glibc.tar") 563 564 _, err := d.Prestart(ctx.ExecCtx, task) 565 if err != nil { 566 t.Fatalf("error in prestart #%d: %v", idx+1, err) 567 } 568 handles[idx], err = d.Start(ctx.ExecCtx, task) 569 if err != nil { 570 t.Errorf("Failed starting task #%d: %s", idx+1, err) 571 } 572 } 573 574 t.Log("All tasks are started. Terminating...") 575 576 for idx, handle := range handles { 577 if handle == nil { 578 t.Errorf("Bad handle for task #%d", idx+1) 579 continue 580 } 581 582 err := handle.Kill() 583 if err != nil { 584 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 585 } 586 } 587 588 t.Log("Test complete!") 589 } 590 591 func waitForExist(t *testing.T, client *docker.Client, handle *DockerHandle) { 592 tu.WaitForResult(func() (bool, error) { 593 container, err := client.InspectContainer(handle.ContainerID()) 594 if err != nil { 595 if _, ok := err.(*docker.NoSuchContainer); !ok { 596 return false, err 597 } 598 } 599 600 return container != nil, nil 601 }, func(err error) { 602 t.Fatalf("err: %v", err) 603 }) 604 } 605 606 func TestDockerDriver_NetworkMode_Host(t *testing.T) { 607 expected := "host" 608 609 task := &structs.Task{ 610 Name: "nc-demo", 611 Driver: "docker", 612 Config: map[string]interface{}{ 613 "image": "busybox", 614 "load": "busybox.tar", 615 "command": "/bin/nc", 616 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 617 "network_mode": expected, 618 }, 619 Resources: &structs.Resources{ 620 MemoryMB: 256, 621 CPU: 512, 622 }, 623 LogConfig: &structs.LogConfig{ 624 MaxFiles: 10, 625 MaxFileSizeMB: 10, 626 }, 627 } 628 629 client, handle, cleanup := dockerSetup(t, task) 630 defer cleanup() 631 632 waitForExist(t, client, handle.(*DockerHandle)) 633 634 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 635 if err != nil { 636 t.Fatalf("err: %v", err) 637 } 638 639 actual := container.HostConfig.NetworkMode 640 if actual != expected { 641 t.Fatalf("Got network mode %q; want %q", expected, actual) 642 } 643 } 644 645 func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) { 646 // Because go-dockerclient doesn't provide api for query network aliases, just check that 647 // a container can be created with a 'network_aliases' property 648 649 // Create network, network-scoped alias is supported only for containers in user defined networks 650 client := newTestDockerClient(t) 651 networkOpts := docker.CreateNetworkOptions{Name: "foobar", Driver: "bridge"} 652 network, err := client.CreateNetwork(networkOpts) 653 if err != nil { 654 t.Fatalf("err: %v", err) 655 } 656 defer client.RemoveNetwork(network.ID) 657 658 expected := []string{"foobar"} 659 task := &structs.Task{ 660 Name: "nc-demo", 661 Driver: "docker", 662 Config: map[string]interface{}{ 663 "image": "busybox", 664 "load": "busybox.tar", 665 "command": "/bin/nc", 666 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 667 "network_mode": network.Name, 668 "network_aliases": expected, 669 }, 670 Resources: &structs.Resources{ 671 MemoryMB: 256, 672 CPU: 512, 673 }, 674 LogConfig: &structs.LogConfig{ 675 MaxFiles: 10, 676 MaxFileSizeMB: 10, 677 }, 678 } 679 680 client, handle, cleanup := dockerSetupWithClient(t, task, client) 681 defer cleanup() 682 683 waitForExist(t, client, handle.(*DockerHandle)) 684 685 _, err = client.InspectContainer(handle.(*DockerHandle).ContainerID()) 686 if err != nil { 687 t.Fatalf("err: %v", err) 688 } 689 } 690 691 func TestDockerDriver_Labels(t *testing.T) { 692 task, _, _ := dockerTask() 693 task.Config["labels"] = []map[string]string{ 694 map[string]string{ 695 "label1": "value1", 696 "label2": "value2", 697 }, 698 } 699 700 client, handle, cleanup := dockerSetup(t, task) 701 defer cleanup() 702 703 waitForExist(t, client, handle.(*DockerHandle)) 704 705 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 706 if err != nil { 707 t.Fatalf("err: %v", err) 708 } 709 710 if want, got := 2, len(container.Config.Labels); want != got { 711 t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got) 712 } 713 714 if want, got := "value1", container.Config.Labels["label1"]; want != got { 715 t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got) 716 } 717 } 718 719 func TestDockerDriver_ForcePull_IsInvalidConfig(t *testing.T) { 720 task, _, _ := dockerTask() 721 task.Config["force_pull"] = "nothing" 722 723 ctx := testDockerDriverContexts(t, task) 724 defer ctx.AllocDir.Destroy() 725 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 726 driver := NewDockerDriver(ctx.DriverCtx) 727 728 if _, err := driver.Prestart(ctx.ExecCtx, task); err == nil { 729 t.Fatalf("error expected in prestart") 730 } 731 } 732 733 func TestDockerDriver_ForcePull(t *testing.T) { 734 task, _, _ := dockerTask() 735 task.Config["force_pull"] = "true" 736 737 client, handle, cleanup := dockerSetup(t, task) 738 defer cleanup() 739 740 waitForExist(t, client, handle.(*DockerHandle)) 741 742 _, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 743 if err != nil { 744 t.Fatalf("err: %v", err) 745 } 746 } 747 748 func TestDockerDriver_DNS(t *testing.T) { 749 task, _, _ := dockerTask() 750 task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"} 751 task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"} 752 753 client, handle, cleanup := dockerSetup(t, task) 754 defer cleanup() 755 756 waitForExist(t, client, handle.(*DockerHandle)) 757 758 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 759 if err != nil { 760 t.Fatalf("err: %v", err) 761 } 762 763 if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) { 764 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS) 765 } 766 767 if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) { 768 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch) 769 } 770 } 771 772 func TestDockerWorkDir(t *testing.T) { 773 task, _, _ := dockerTask() 774 task.Config["work_dir"] = "/some/path" 775 776 client, handle, cleanup := dockerSetup(t, task) 777 defer cleanup() 778 779 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 780 if err != nil { 781 t.Fatalf("err: %v", err) 782 } 783 784 if want, got := "/some/path", container.Config.WorkingDir; want != got { 785 t.Errorf("Wrong working directory for docker job. Expect: %s, got: %s", want, got) 786 } 787 } 788 789 func inSlice(needle string, haystack []string) bool { 790 for _, h := range haystack { 791 if h == needle { 792 return true 793 } 794 } 795 return false 796 } 797 798 func TestDockerDriver_PortsNoMap(t *testing.T) { 799 task, res, dyn := dockerTask() 800 801 client, handle, cleanup := dockerSetup(t, task) 802 defer cleanup() 803 804 waitForExist(t, client, handle.(*DockerHandle)) 805 806 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 807 if err != nil { 808 t.Fatalf("err: %v", err) 809 } 810 811 // Verify that the correct ports are EXPOSED 812 expectedExposedPorts := map[docker.Port]struct{}{ 813 docker.Port(fmt.Sprintf("%d/tcp", res)): struct{}{}, 814 docker.Port(fmt.Sprintf("%d/udp", res)): struct{}{}, 815 docker.Port(fmt.Sprintf("%d/tcp", dyn)): struct{}{}, 816 docker.Port(fmt.Sprintf("%d/udp", dyn)): struct{}{}, 817 } 818 819 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 820 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 821 } 822 823 // Verify that the correct ports are FORWARDED 824 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 825 docker.Port(fmt.Sprintf("%d/tcp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 826 docker.Port(fmt.Sprintf("%d/udp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 827 docker.Port(fmt.Sprintf("%d/tcp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 828 docker.Port(fmt.Sprintf("%d/udp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 829 } 830 831 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 832 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 833 } 834 835 expectedEnvironment := map[string]string{ 836 "NOMAD_ADDR_main": fmt.Sprintf("127.0.0.1:%d", res), 837 "NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn), 838 } 839 840 for key, val := range expectedEnvironment { 841 search := fmt.Sprintf("%s=%s", key, val) 842 if !inSlice(search, container.Config.Env) { 843 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 844 } 845 } 846 } 847 848 func TestDockerDriver_PortsMapping(t *testing.T) { 849 task, res, dyn := dockerTask() 850 task.Config["port_map"] = []map[string]string{ 851 map[string]string{ 852 "main": "8080", 853 "REDIS": "6379", 854 }, 855 } 856 857 client, handle, cleanup := dockerSetup(t, task) 858 defer cleanup() 859 860 waitForExist(t, client, handle.(*DockerHandle)) 861 862 container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID()) 863 if err != nil { 864 t.Fatalf("err: %v", err) 865 } 866 867 // Verify that the correct ports are EXPOSED 868 expectedExposedPorts := map[docker.Port]struct{}{ 869 docker.Port("8080/tcp"): struct{}{}, 870 docker.Port("8080/udp"): struct{}{}, 871 docker.Port("6379/tcp"): struct{}{}, 872 docker.Port("6379/udp"): struct{}{}, 873 } 874 875 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 876 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 877 } 878 879 // Verify that the correct ports are FORWARDED 880 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 881 docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 882 docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 883 docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 884 docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 885 } 886 887 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 888 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 889 } 890 891 expectedEnvironment := map[string]string{ 892 "NOMAD_ADDR_main": "127.0.0.1:8080", 893 "NOMAD_ADDR_REDIS": "127.0.0.1:6379", 894 "NOMAD_HOST_PORT_main": strconv.Itoa(docker_reserved), 895 } 896 897 for key, val := range expectedEnvironment { 898 search := fmt.Sprintf("%s=%s", key, val) 899 if !inSlice(search, container.Config.Env) { 900 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 901 } 902 } 903 } 904 905 func TestDockerDriver_User(t *testing.T) { 906 task := &structs.Task{ 907 Name: "redis-demo", 908 User: "alice", 909 Driver: "docker", 910 Config: map[string]interface{}{ 911 "image": "busybox", 912 "load": "busybox.tar", 913 "command": "/bin/sleep", 914 "args": []string{"10000"}, 915 }, 916 Resources: &structs.Resources{ 917 MemoryMB: 256, 918 CPU: 512, 919 }, 920 LogConfig: &structs.LogConfig{ 921 MaxFiles: 10, 922 MaxFileSizeMB: 10, 923 }, 924 } 925 926 if !testutil.DockerIsConnected(t) { 927 t.SkipNow() 928 } 929 930 ctx := testDockerDriverContexts(t, task) 931 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 932 driver := NewDockerDriver(ctx.DriverCtx) 933 defer ctx.AllocDir.Destroy() 934 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 935 936 _, err := driver.Prestart(ctx.ExecCtx, task) 937 if err != nil { 938 t.Fatalf("error in prestart: %v", err) 939 } 940 941 // It should fail because the user "alice" does not exist on the given 942 // image. 943 handle, err := driver.Start(ctx.ExecCtx, task) 944 if err == nil { 945 handle.Kill() 946 t.Fatalf("Should've failed") 947 } 948 949 if !strings.Contains(err.Error(), "alice") { 950 t.Fatalf("Expected failure string not found, found %q instead", err.Error()) 951 } 952 } 953 954 func TestDockerDriver_CleanupContainer(t *testing.T) { 955 task := &structs.Task{ 956 Name: "redis-demo", 957 Driver: "docker", 958 Config: map[string]interface{}{ 959 "image": "busybox", 960 "load": "busybox.tar", 961 "command": "/bin/echo", 962 "args": []string{"hello"}, 963 }, 964 Resources: &structs.Resources{ 965 MemoryMB: 256, 966 CPU: 512, 967 }, 968 LogConfig: &structs.LogConfig{ 969 MaxFiles: 10, 970 MaxFileSizeMB: 10, 971 }, 972 } 973 974 _, handle, cleanup := dockerSetup(t, task) 975 defer cleanup() 976 977 // Update should be a no-op 978 err := handle.Update(task) 979 if err != nil { 980 t.Fatalf("err: %v", err) 981 } 982 983 select { 984 case res := <-handle.WaitCh(): 985 if !res.Successful() { 986 t.Fatalf("err: %v", res) 987 } 988 989 time.Sleep(3 * time.Second) 990 991 // Ensure that the container isn't present 992 _, err := client.InspectContainer(handle.(*DockerHandle).containerID) 993 if err == nil { 994 t.Fatalf("expected to not get container") 995 } 996 997 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 998 t.Fatalf("timeout") 999 } 1000 } 1001 1002 func TestDockerDriver_Stats(t *testing.T) { 1003 task := &structs.Task{ 1004 Name: "sleep", 1005 Driver: "docker", 1006 Config: map[string]interface{}{ 1007 "image": "busybox", 1008 "load": "busybox.tar", 1009 "command": "/bin/sleep", 1010 "args": []string{"100"}, 1011 }, 1012 LogConfig: &structs.LogConfig{ 1013 MaxFiles: 10, 1014 MaxFileSizeMB: 10, 1015 }, 1016 Resources: basicResources, 1017 } 1018 1019 _, handle, cleanup := dockerSetup(t, task) 1020 defer cleanup() 1021 1022 waitForExist(t, client, handle.(*DockerHandle)) 1023 1024 go func() { 1025 time.Sleep(3 * time.Second) 1026 ru, err := handle.Stats() 1027 if err != nil { 1028 t.Fatalf("err: %v", err) 1029 } 1030 if ru.ResourceUsage == nil { 1031 handle.Kill() 1032 t.Fatalf("expected resource usage") 1033 } 1034 err = handle.Kill() 1035 if err != nil { 1036 t.Fatalf("err: %v", err) 1037 } 1038 }() 1039 1040 select { 1041 case res := <-handle.WaitCh(): 1042 if res.Successful() { 1043 t.Fatalf("should err: %v", res) 1044 } 1045 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1046 t.Fatalf("timeout") 1047 } 1048 } 1049 1050 func setupDockerVolumes(t *testing.T, cfg *config.Config, hostpath string) (*structs.Task, Driver, *ExecContext, string, func()) { 1051 if !testutil.DockerIsConnected(t) { 1052 t.SkipNow() 1053 } 1054 1055 randfn := fmt.Sprintf("test-%d", rand.Int()) 1056 hostfile := filepath.Join(hostpath, randfn) 1057 containerPath := "/mnt/vol" 1058 containerFile := filepath.Join(containerPath, randfn) 1059 1060 task := &structs.Task{ 1061 Name: "ls", 1062 Env: map[string]string{"VOL_PATH": containerPath}, 1063 Driver: "docker", 1064 Config: map[string]interface{}{ 1065 "image": "busybox", 1066 "load": "busybox.tar", 1067 "command": "touch", 1068 "args": []string{containerFile}, 1069 "volumes": []string{fmt.Sprintf("%s:${VOL_PATH}", hostpath)}, 1070 }, 1071 LogConfig: &structs.LogConfig{ 1072 MaxFiles: 10, 1073 MaxFileSizeMB: 10, 1074 }, 1075 Resources: basicResources, 1076 } 1077 1078 // Build alloc and task directory structure 1079 allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(cfg.AllocDir, structs.GenerateUUID())) 1080 if err := allocDir.Build(); err != nil { 1081 t.Fatalf("failed to build alloc dir: %v", err) 1082 } 1083 taskDir := allocDir.NewTaskDir(task.Name) 1084 if err := taskDir.Build(false, nil, cstructs.FSIsolationImage); err != nil { 1085 allocDir.Destroy() 1086 t.Fatalf("failed to build task dir: %v", err) 1087 } 1088 1089 alloc := mock.Alloc() 1090 execCtx := NewExecContext(taskDir) 1091 cleanup := func() { 1092 allocDir.Destroy() 1093 if filepath.IsAbs(hostpath) { 1094 os.RemoveAll(hostpath) 1095 } 1096 } 1097 1098 taskEnv, err := GetTaskEnv(taskDir, cfg.Node, task, alloc, cfg, "") 1099 if err != nil { 1100 cleanup() 1101 t.Fatalf("Failed to get task env: %v", err) 1102 } 1103 1104 logger := testLogger() 1105 emitter := func(m string, args ...interface{}) { 1106 logger.Printf("[EVENT] "+m, args...) 1107 } 1108 driverCtx := NewDriverContext(task.Name, alloc.ID, cfg, cfg.Node, testLogger(), taskEnv, emitter) 1109 driver := NewDockerDriver(driverCtx) 1110 copyImage(t, taskDir, "busybox.tar") 1111 1112 return task, driver, execCtx, hostfile, cleanup 1113 } 1114 1115 func TestDockerDriver_VolumesDisabled(t *testing.T) { 1116 cfg := testConfig() 1117 cfg.Options = map[string]string{ 1118 dockerVolumesConfigOption: "false", 1119 "docker.cleanup.image": "false", 1120 } 1121 1122 { 1123 tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesdisabled") 1124 if err != nil { 1125 t.Fatalf("error creating temporary dir: %v", err) 1126 } 1127 1128 task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, tmpvol) 1129 defer cleanup() 1130 1131 _, err = driver.Prestart(execCtx, task) 1132 if err != nil { 1133 t.Fatalf("error in prestart: %v", err) 1134 } 1135 if _, err := driver.Start(execCtx, task); err == nil { 1136 t.Fatalf("Started driver successfully when volumes should have been disabled.") 1137 } 1138 } 1139 1140 // Relative paths should still be allowed 1141 { 1142 task, driver, execCtx, fn, cleanup := setupDockerVolumes(t, cfg, ".") 1143 defer cleanup() 1144 1145 _, err := driver.Prestart(execCtx, task) 1146 if err != nil { 1147 t.Fatalf("error in prestart: %v", err) 1148 } 1149 handle, err := driver.Start(execCtx, task) 1150 if err != nil { 1151 t.Fatalf("err: %v", err) 1152 } 1153 defer handle.Kill() 1154 1155 select { 1156 case res := <-handle.WaitCh(): 1157 if !res.Successful() { 1158 t.Fatalf("unexpected err: %v", res) 1159 } 1160 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1161 t.Fatalf("timeout") 1162 } 1163 1164 if _, err := ioutil.ReadFile(filepath.Join(execCtx.TaskDir.Dir, fn)); err != nil { 1165 t.Fatalf("unexpected error reading %s: %v", fn, err) 1166 } 1167 } 1168 1169 // Volume Drivers should be rejected (error) 1170 { 1171 task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, "fake_flocker_vol") 1172 defer cleanup() 1173 task.Config["volume_driver"] = "flocker" 1174 1175 if _, err := driver.Prestart(execCtx, task); err != nil { 1176 t.Fatalf("error in prestart: %v", err) 1177 } 1178 if _, err := driver.Start(execCtx, task); err == nil { 1179 t.Fatalf("Started driver successfully when volume drivers should have been disabled.") 1180 } 1181 } 1182 1183 } 1184 1185 func TestDockerDriver_VolumesEnabled(t *testing.T) { 1186 cfg := testConfig() 1187 1188 tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesenabled") 1189 if err != nil { 1190 t.Fatalf("error creating temporary dir: %v", err) 1191 } 1192 1193 task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg, tmpvol) 1194 defer cleanup() 1195 1196 _, err = driver.Prestart(execCtx, task) 1197 if err != nil { 1198 t.Fatalf("error in prestart: %v", err) 1199 } 1200 handle, err := driver.Start(execCtx, task) 1201 if err != nil { 1202 t.Fatalf("Failed to start docker driver: %v", err) 1203 } 1204 defer handle.Kill() 1205 1206 select { 1207 case res := <-handle.WaitCh(): 1208 if !res.Successful() { 1209 t.Fatalf("unexpected err: %v", res) 1210 } 1211 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1212 t.Fatalf("timeout") 1213 } 1214 1215 if _, err := ioutil.ReadFile(hostpath); err != nil { 1216 t.Fatalf("unexpected error reading %s: %v", hostpath, err) 1217 } 1218 } 1219 1220 // TestDockerDriver_Cleanup ensures Cleanup removes only downloaded images. 1221 func TestDockerDriver_Cleanup(t *testing.T) { 1222 if !testutil.DockerIsConnected(t) { 1223 t.SkipNow() 1224 } 1225 1226 imageName := "hello-world:latest" 1227 task := &structs.Task{ 1228 Name: "cleanup_test", 1229 Driver: "docker", 1230 Config: map[string]interface{}{ 1231 "image": imageName, 1232 }, 1233 } 1234 tctx := testDockerDriverContexts(t, task) 1235 defer tctx.AllocDir.Destroy() 1236 1237 // Run Prestart 1238 driver := NewDockerDriver(tctx.DriverCtx).(*DockerDriver) 1239 res, err := driver.Prestart(tctx.ExecCtx, task) 1240 if err != nil { 1241 t.Fatalf("error in prestart: %v", err) 1242 } 1243 if len(res.Resources) == 0 || len(res.Resources[dockerImageResKey]) == 0 { 1244 t.Fatalf("no created resources: %#v", res) 1245 } 1246 1247 // Cleanup 1248 rescopy := res.Copy() 1249 if err := driver.Cleanup(tctx.ExecCtx, rescopy); err != nil { 1250 t.Fatalf("Cleanup failed: %v", err) 1251 } 1252 1253 // Make sure rescopy is updated 1254 if len(rescopy.Resources) > 0 { 1255 t.Errorf("Cleanup should have cleared resource map: %#v", rescopy.Resources) 1256 } 1257 1258 // Ensure image was removed 1259 tu.WaitForResult(func() (bool, error) { 1260 if _, err := client.InspectImage(driver.driverConfig.ImageName); err == nil { 1261 return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", imageName) 1262 } 1263 1264 return true, nil 1265 }, func(err error) { 1266 t.Fatalf("err: %v", err) 1267 }) 1268 1269 // The image doesn't exist which shouldn't be an error when calling 1270 // Cleanup, so call it again to make sure. 1271 if err := driver.Cleanup(tctx.ExecCtx, res.Copy()); err != nil { 1272 t.Fatalf("Cleanup failed: %v", err) 1273 } 1274 } 1275 1276 func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) { 1277 dst := filepath.Join(taskDir.LocalDir, image) 1278 copyFile(filepath.Join("./test-resources/docker", image), dst, t) 1279 } 1280 1281 func TestDockerDriver_AuthConfiguration(t *testing.T) { 1282 path := "./test-resources/docker/auth.json" 1283 cases := []struct { 1284 Repo string 1285 AuthConfig *docker.AuthConfiguration 1286 }{ 1287 { 1288 Repo: "lolwhat.com/what:1337", 1289 AuthConfig: &docker.AuthConfiguration{}, 1290 }, 1291 { 1292 Repo: "redis:3.2", 1293 AuthConfig: &docker.AuthConfiguration{ 1294 Username: "test", 1295 Password: "1234", 1296 Email: "", 1297 ServerAddress: "https://index.docker.io/v1/", 1298 }, 1299 }, 1300 { 1301 Repo: "quay.io/redis:3.2", 1302 AuthConfig: &docker.AuthConfiguration{ 1303 Username: "test", 1304 Password: "5678", 1305 Email: "", 1306 ServerAddress: "quay.io", 1307 }, 1308 }, 1309 { 1310 Repo: "other.io/redis:3.2", 1311 AuthConfig: &docker.AuthConfiguration{ 1312 Username: "test", 1313 Password: "abcd", 1314 Email: "", 1315 ServerAddress: "https://other.io/v1/", 1316 }, 1317 }, 1318 } 1319 1320 for i, c := range cases { 1321 act, err := authOptionFrom(path, c.Repo) 1322 if err != nil { 1323 t.Fatalf("Test %d failed: %v", i+1, err) 1324 } 1325 1326 if !reflect.DeepEqual(act, c.AuthConfig) { 1327 t.Fatalf("Test %d failed: Unexpected auth config: got %+v; want %+v", i+1, act, c.AuthConfig) 1328 } 1329 } 1330 }