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