github.com/emate/nomad@v0.8.2-wo-binpacking/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" 11 "runtime/debug" 12 "sort" 13 "strconv" 14 "strings" 15 "testing" 16 "time" 17 18 docker "github.com/fsouza/go-dockerclient" 19 "github.com/hashicorp/consul/lib/freeport" 20 sockaddr "github.com/hashicorp/go-sockaddr" 21 "github.com/hashicorp/nomad/client/allocdir" 22 "github.com/hashicorp/nomad/client/config" 23 "github.com/hashicorp/nomad/client/driver/env" 24 "github.com/hashicorp/nomad/client/fingerprint" 25 cstructs "github.com/hashicorp/nomad/client/structs" 26 "github.com/hashicorp/nomad/client/testutil" 27 "github.com/hashicorp/nomad/helper/uuid" 28 "github.com/hashicorp/nomad/nomad/mock" 29 "github.com/hashicorp/nomad/nomad/structs" 30 tu "github.com/hashicorp/nomad/testutil" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 ) 34 35 func dockerIsRemote(t *testing.T) bool { 36 client, err := docker.NewClientFromEnv() 37 if err != nil { 38 return false 39 } 40 41 // Technically this could be a local tcp socket but for testing purposes 42 // we'll just assume that tcp is only used for remote connections. 43 if client.Endpoint()[0:3] == "tcp" { 44 return true 45 } 46 return false 47 } 48 49 // Returns a task with a reserved and dynamic port. The ports are returned 50 // respectively. 51 func dockerTask(t *testing.T) (*structs.Task, int, int) { 52 ports := freeport.GetT(t, 2) 53 dockerReserved := ports[0] 54 dockerDynamic := ports[1] 55 return &structs.Task{ 56 Name: "redis-demo", 57 Driver: "docker", 58 Config: map[string]interface{}{ 59 "image": "busybox", 60 "load": "busybox.tar", 61 "command": "/bin/nc", 62 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 63 }, 64 LogConfig: &structs.LogConfig{ 65 MaxFiles: 10, 66 MaxFileSizeMB: 10, 67 }, 68 Resources: &structs.Resources{ 69 MemoryMB: 256, 70 CPU: 512, 71 Networks: []*structs.NetworkResource{ 72 { 73 IP: "127.0.0.1", 74 ReservedPorts: []structs.Port{{Label: "main", Value: dockerReserved}}, 75 DynamicPorts: []structs.Port{{Label: "REDIS", Value: dockerDynamic}}, 76 }, 77 }, 78 }, 79 }, dockerReserved, dockerDynamic 80 } 81 82 // dockerSetup does all of the basic setup you need to get a running docker 83 // process up and running for testing. Use like: 84 // 85 // task := taskTemplate() 86 // // do custom task configuration 87 // client, handle, cleanup := dockerSetup(t, task) 88 // defer cleanup() 89 // // do test stuff 90 // 91 // If there is a problem during setup this function will abort or skip the test 92 // and indicate the reason. 93 func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, *DockerHandle, func()) { 94 client := newTestDockerClient(t) 95 return dockerSetupWithClient(t, task, client) 96 } 97 98 func testDockerDriverContexts(t *testing.T, task *structs.Task) *testContext { 99 tctx := testDriverContexts(t, task) 100 101 // Drop the delay 102 tctx.DriverCtx.config.Options = make(map[string]string) 103 tctx.DriverCtx.config.Options[dockerImageRemoveDelayConfigOption] = "1s" 104 105 return tctx 106 } 107 108 func dockerSetupWithClient(t *testing.T, task *structs.Task, client *docker.Client) (*docker.Client, *DockerHandle, func()) { 109 t.Helper() 110 tctx := testDockerDriverContexts(t, task) 111 driver := NewDockerDriver(tctx.DriverCtx) 112 copyImage(t, tctx.ExecCtx.TaskDir, "busybox.tar") 113 114 presp, err := driver.Prestart(tctx.ExecCtx, task) 115 if err != nil { 116 if presp != nil && presp.CreatedResources != nil { 117 driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 118 } 119 tctx.AllocDir.Destroy() 120 t.Fatalf("error in prestart: %v", err) 121 } 122 // Update the exec ctx with the driver network env vars 123 tctx.ExecCtx.TaskEnv = tctx.EnvBuilder.SetDriverNetwork(presp.Network).Build() 124 125 sresp, err := driver.Start(tctx.ExecCtx, task) 126 if err != nil { 127 driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 128 tctx.AllocDir.Destroy() 129 t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack()) 130 } 131 132 if sresp.Handle == nil { 133 driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 134 tctx.AllocDir.Destroy() 135 t.Fatalf("handle is nil\nStack\n%s", debug.Stack()) 136 } 137 138 // At runtime this is handled by TaskRunner 139 tctx.ExecCtx.TaskEnv = tctx.EnvBuilder.SetDriverNetwork(sresp.Network).Build() 140 141 cleanup := func() { 142 driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 143 sresp.Handle.Kill() 144 tctx.AllocDir.Destroy() 145 } 146 147 return client, sresp.Handle.(*DockerHandle), cleanup 148 } 149 150 func newTestDockerClient(t *testing.T) *docker.Client { 151 t.Helper() 152 if !testutil.DockerIsConnected(t) { 153 t.Skip("Docker not connected") 154 } 155 156 client, err := docker.NewClientFromEnv() 157 if err != nil { 158 t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack()) 159 } 160 return client 161 } 162 163 // This test should always pass, even if docker daemon is not available 164 func TestDockerDriver_Fingerprint(t *testing.T) { 165 if !tu.IsTravis() { 166 t.Parallel() 167 } 168 169 ctx := testDockerDriverContexts(t, &structs.Task{Name: "foo", Driver: "docker", Resources: basicResources}) 170 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 171 defer ctx.AllocDir.Destroy() 172 d := NewDockerDriver(ctx.DriverCtx) 173 node := &structs.Node{ 174 Attributes: make(map[string]string), 175 } 176 177 request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node} 178 var response cstructs.FingerprintResponse 179 err := d.Fingerprint(request, &response) 180 if err != nil { 181 t.Fatalf("err: %v", err) 182 } 183 184 attributes := response.Attributes 185 if testutil.DockerIsConnected(t) && attributes["driver.docker"] == "" { 186 t.Fatalf("Fingerprinter should detect when docker is available") 187 } 188 189 if attributes["driver.docker"] != "1" { 190 t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.") 191 } else { 192 193 // if docker is available, make sure that the response is tagged as 194 // applicable 195 if !response.Detected { 196 t.Fatalf("expected response to be applicable") 197 } 198 } 199 200 t.Logf("Found docker version %s", attributes["driver.docker.version"]) 201 } 202 203 // TestDockerDriver_Fingerprint_Bridge asserts that if Docker is running we set 204 // the bridge network's IP as a node attribute. See #2785 205 func TestDockerDriver_Fingerprint_Bridge(t *testing.T) { 206 if !tu.IsTravis() { 207 t.Parallel() 208 } 209 if !testutil.DockerIsConnected(t) { 210 t.Skip("requires Docker") 211 } 212 if runtime.GOOS != "linux" { 213 t.Skip("expect only on linux") 214 } 215 216 // This seems fragile, so we might need to reconsider this test if it 217 // proves flaky 218 expectedAddr, err := sockaddr.GetInterfaceIP("docker0") 219 if err != nil { 220 t.Fatalf("unable to get ip for docker0: %v", err) 221 } 222 if expectedAddr == "" { 223 t.Fatalf("unable to get ip for docker bridge") 224 } 225 226 conf := testConfig(t) 227 conf.Node = mock.Node() 228 dd := NewDockerDriver(NewDriverContext("", "", "", "", conf, conf.Node, testLogger(), nil)) 229 230 request := &cstructs.FingerprintRequest{Config: conf, Node: conf.Node} 231 var response cstructs.FingerprintResponse 232 233 err = dd.Fingerprint(request, &response) 234 if err != nil { 235 t.Fatalf("error fingerprinting docker: %v", err) 236 } 237 238 if !response.Detected { 239 t.Fatalf("expected response to be applicable") 240 } 241 242 attributes := response.Attributes 243 if attributes == nil { 244 t.Fatalf("expected attributes to be set") 245 } 246 247 if attributes["driver.docker"] == "" { 248 t.Fatalf("expected Docker to be enabled but false was returned") 249 } 250 251 if found := attributes["driver.docker.bridge_ip"]; found != expectedAddr { 252 t.Fatalf("expected bridge ip %q but found: %q", expectedAddr, found) 253 } 254 t.Logf("docker bridge ip: %q", attributes["driver.docker.bridge_ip"]) 255 } 256 257 func TestDockerDriver_Check_DockerHealthStatus(t *testing.T) { 258 if !tu.IsTravis() { 259 t.Parallel() 260 } 261 if !testutil.DockerIsConnected(t) { 262 t.Skip("requires Docker") 263 } 264 if runtime.GOOS != "linux" { 265 t.Skip("expect only on linux") 266 } 267 268 require := require.New(t) 269 270 expectedAddr, err := sockaddr.GetInterfaceIP("docker0") 271 if err != nil { 272 t.Fatalf("unable to get ip for docker0: %v", err) 273 } 274 if expectedAddr == "" { 275 t.Fatalf("unable to get ip for docker bridge") 276 } 277 278 conf := testConfig(t) 279 conf.Node = mock.Node() 280 dd := NewDockerDriver(NewDriverContext("", "", "", "", conf, conf.Node, testLogger(), nil)) 281 282 request := &cstructs.HealthCheckRequest{} 283 var response cstructs.HealthCheckResponse 284 285 dc, ok := dd.(fingerprint.HealthCheck) 286 require.True(ok) 287 err = dc.HealthCheck(request, &response) 288 require.Nil(err) 289 290 driverInfo := response.Drivers["docker"] 291 require.NotNil(driverInfo) 292 require.True(driverInfo.Healthy) 293 } 294 295 func TestDockerDriver_StartOpen_Wait(t *testing.T) { 296 if !tu.IsTravis() { 297 t.Parallel() 298 } 299 if !testutil.DockerIsConnected(t) { 300 t.Skip("Docker not connected") 301 } 302 303 task := &structs.Task{ 304 Name: "nc-demo", 305 Driver: "docker", 306 Config: map[string]interface{}{ 307 "load": "busybox.tar", 308 "image": "busybox", 309 "command": "/bin/nc", 310 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 311 }, 312 LogConfig: &structs.LogConfig{ 313 MaxFiles: 10, 314 MaxFileSizeMB: 10, 315 }, 316 Resources: basicResources, 317 } 318 319 ctx := testDockerDriverContexts(t, task) 320 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 321 defer ctx.AllocDir.Destroy() 322 d := NewDockerDriver(ctx.DriverCtx) 323 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 324 325 _, err := d.Prestart(ctx.ExecCtx, task) 326 if err != nil { 327 t.Fatalf("error in prestart: %v", err) 328 } 329 330 resp, err := d.Start(ctx.ExecCtx, task) 331 if err != nil { 332 t.Fatalf("err: %v", err) 333 } 334 if resp.Handle == nil { 335 t.Fatalf("missing handle") 336 } 337 defer resp.Handle.Kill() 338 339 // Attempt to open 340 resp2, err := d.Open(ctx.ExecCtx, resp.Handle.ID()) 341 if err != nil { 342 t.Fatalf("err: %v", err) 343 } 344 if resp2 == nil { 345 t.Fatalf("missing handle") 346 } 347 } 348 349 func TestDockerDriver_Start_Wait(t *testing.T) { 350 if !tu.IsTravis() { 351 t.Parallel() 352 } 353 if !testutil.DockerIsConnected(t) { 354 t.Skip("Docker not connected") 355 } 356 task := &structs.Task{ 357 Name: "nc-demo", 358 Driver: "docker", 359 Config: map[string]interface{}{ 360 "load": "busybox.tar", 361 "image": "busybox", 362 "command": "/bin/echo", 363 "args": []string{"hello"}, 364 }, 365 Resources: &structs.Resources{ 366 MemoryMB: 256, 367 CPU: 512, 368 }, 369 LogConfig: &structs.LogConfig{ 370 MaxFiles: 10, 371 MaxFileSizeMB: 10, 372 }, 373 } 374 375 _, handle, cleanup := dockerSetup(t, task) 376 defer cleanup() 377 378 // Update should be a no-op 379 err := handle.Update(task) 380 if err != nil { 381 t.Fatalf("err: %v", err) 382 } 383 384 select { 385 case res := <-handle.WaitCh(): 386 if !res.Successful() { 387 t.Fatalf("err: %v", res) 388 } 389 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 390 t.Fatalf("timeout") 391 } 392 } 393 394 // TestDockerDriver_Start_StoppedContainer asserts that Nomad will detect a 395 // stopped task container, remove it, and start a new container. 396 // 397 // See https://github.com/hashicorp/nomad/issues/3419 398 func TestDockerDriver_Start_StoppedContainer(t *testing.T) { 399 if !tu.IsTravis() { 400 t.Parallel() 401 } 402 if !testutil.DockerIsConnected(t) { 403 t.Skip("Docker not connected") 404 } 405 task := &structs.Task{ 406 Name: "nc-demo", 407 Driver: "docker", 408 Config: map[string]interface{}{ 409 "load": "busybox.tar", 410 "image": "busybox", 411 "command": "sleep", 412 "args": []string{"9000"}, 413 }, 414 Resources: &structs.Resources{ 415 MemoryMB: 100, 416 CPU: 100, 417 }, 418 LogConfig: &structs.LogConfig{ 419 MaxFiles: 1, 420 MaxFileSizeMB: 10, 421 }, 422 } 423 424 tctx := testDockerDriverContexts(t, task) 425 defer tctx.AllocDir.Destroy() 426 427 copyImage(t, tctx.ExecCtx.TaskDir, "busybox.tar") 428 client := newTestDockerClient(t) 429 driver := NewDockerDriver(tctx.DriverCtx).(*DockerDriver) 430 driverConfig := &DockerDriverConfig{ImageName: "busybox", LoadImage: "busybox.tar"} 431 if _, err := driver.loadImage(driverConfig, client, tctx.ExecCtx.TaskDir); err != nil { 432 t.Fatalf("error loading image: %v", err) 433 } 434 435 // Create a container of the same name but don't start it. This mimics 436 // the case of dockerd getting restarted and stopping containers while 437 // Nomad is watching them. 438 opts := docker.CreateContainerOptions{ 439 Name: fmt.Sprintf("%s-%s", task.Name, tctx.DriverCtx.allocID), 440 Config: &docker.Config{ 441 Image: "busybox", 442 Cmd: []string{"sleep", "9000"}, 443 }, 444 } 445 if _, err := client.CreateContainer(opts); err != nil { 446 t.Fatalf("error creating initial container: %v", err) 447 } 448 449 // Now assert that the driver can still start normally 450 presp, err := driver.Prestart(tctx.ExecCtx, task) 451 if err != nil { 452 driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 453 t.Fatalf("error in prestart: %v", err) 454 } 455 defer driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 456 457 sresp, err := driver.Start(tctx.ExecCtx, task) 458 if err != nil { 459 t.Fatalf("failed to start driver: %s", err) 460 } 461 handle := sresp.Handle.(*DockerHandle) 462 waitForExist(t, client, handle) 463 handle.Kill() 464 } 465 466 func TestDockerDriver_Start_LoadImage(t *testing.T) { 467 if !tu.IsTravis() { 468 t.Parallel() 469 } 470 if !testutil.DockerIsConnected(t) { 471 t.Skip("Docker not connected") 472 } 473 task := &structs.Task{ 474 Name: "busybox-demo", 475 Driver: "docker", 476 Config: map[string]interface{}{ 477 "image": "busybox", 478 "load": "busybox.tar", 479 "command": "/bin/sh", 480 "args": []string{ 481 "-c", 482 "echo hello > $NOMAD_TASK_DIR/output", 483 }, 484 }, 485 LogConfig: &structs.LogConfig{ 486 MaxFiles: 10, 487 MaxFileSizeMB: 10, 488 }, 489 Resources: &structs.Resources{ 490 MemoryMB: 256, 491 CPU: 512, 492 }, 493 } 494 495 ctx := testDockerDriverContexts(t, task) 496 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 497 defer ctx.AllocDir.Destroy() 498 d := NewDockerDriver(ctx.DriverCtx) 499 500 // Copy the image into the task's directory 501 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 502 503 _, err := d.Prestart(ctx.ExecCtx, task) 504 if err != nil { 505 t.Fatalf("error in prestart: %v", err) 506 } 507 resp, err := d.Start(ctx.ExecCtx, task) 508 if err != nil { 509 t.Fatalf("err: %v", err) 510 } 511 defer resp.Handle.Kill() 512 513 select { 514 case res := <-resp.Handle.WaitCh(): 515 if !res.Successful() { 516 t.Fatalf("err: %v", res) 517 } 518 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 519 t.Fatalf("timeout") 520 } 521 522 // Check that data was written to the shared alloc directory. 523 outputFile := filepath.Join(ctx.ExecCtx.TaskDir.LocalDir, "output") 524 act, err := ioutil.ReadFile(outputFile) 525 if err != nil { 526 t.Fatalf("Couldn't read expected output: %v", err) 527 } 528 529 exp := "hello" 530 if strings.TrimSpace(string(act)) != exp { 531 t.Fatalf("Command outputted %v; want %v", act, exp) 532 } 533 534 } 535 536 func TestDockerDriver_Start_BadPull_Recoverable(t *testing.T) { 537 if !tu.IsTravis() { 538 t.Parallel() 539 } 540 if !testutil.DockerIsConnected(t) { 541 t.Skip("Docker not connected") 542 } 543 task := &structs.Task{ 544 Name: "busybox-demo", 545 Driver: "docker", 546 Config: map[string]interface{}{ 547 "image": "127.0.1.1:32121/foo", // bad path 548 "command": "/bin/echo", 549 "args": []string{ 550 "hello", 551 }, 552 }, 553 LogConfig: &structs.LogConfig{ 554 MaxFiles: 10, 555 MaxFileSizeMB: 10, 556 }, 557 Resources: &structs.Resources{ 558 MemoryMB: 256, 559 CPU: 512, 560 }, 561 } 562 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 568 _, err := d.Prestart(ctx.ExecCtx, task) 569 if err == nil { 570 t.Fatalf("want error in prestart: %v", err) 571 } 572 573 if rerr, ok := err.(*structs.RecoverableError); !ok { 574 t.Fatalf("want recoverable error: %+v", err) 575 } else if !rerr.IsRecoverable() { 576 t.Fatalf("error not recoverable: %+v", err) 577 } 578 } 579 580 func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) { 581 if !tu.IsTravis() { 582 t.Parallel() 583 } 584 // This test requires that the alloc dir be mounted into docker as a volume. 585 // Because this cannot happen when docker is run remotely, e.g. when running 586 // docker in a VM, we skip this when we detect Docker is being run remotely. 587 if !testutil.DockerIsConnected(t) || dockerIsRemote(t) { 588 t.Skip("Docker not connected") 589 } 590 591 exp := []byte{'w', 'i', 'n'} 592 file := "output.txt" 593 task := &structs.Task{ 594 Name: "nc-demo", 595 Driver: "docker", 596 Config: map[string]interface{}{ 597 "image": "busybox", 598 "load": "busybox.tar", 599 "command": "/bin/sh", 600 "args": []string{ 601 "-c", 602 fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`, 603 string(exp), env.AllocDir, file), 604 }, 605 }, 606 LogConfig: &structs.LogConfig{ 607 MaxFiles: 10, 608 MaxFileSizeMB: 10, 609 }, 610 Resources: &structs.Resources{ 611 MemoryMB: 256, 612 CPU: 512, 613 }, 614 } 615 616 ctx := testDockerDriverContexts(t, task) 617 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 618 defer ctx.AllocDir.Destroy() 619 d := NewDockerDriver(ctx.DriverCtx) 620 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 621 622 _, err := d.Prestart(ctx.ExecCtx, task) 623 if err != nil { 624 t.Fatalf("error in prestart: %v", err) 625 } 626 resp, err := d.Start(ctx.ExecCtx, task) 627 if err != nil { 628 t.Fatalf("err: %v", err) 629 } 630 defer resp.Handle.Kill() 631 632 select { 633 case res := <-resp.Handle.WaitCh(): 634 if !res.Successful() { 635 t.Fatalf("err: %v", res) 636 } 637 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 638 t.Fatalf("timeout") 639 } 640 641 // Check that data was written to the shared alloc directory. 642 outputFile := filepath.Join(ctx.AllocDir.SharedDir, file) 643 act, err := ioutil.ReadFile(outputFile) 644 if err != nil { 645 t.Fatalf("Couldn't read expected output: %v", err) 646 } 647 648 if !reflect.DeepEqual(act, exp) { 649 t.Fatalf("Command outputted %v; want %v", act, exp) 650 } 651 } 652 653 func TestDockerDriver_Start_Kill_Wait(t *testing.T) { 654 if !tu.IsTravis() { 655 t.Parallel() 656 } 657 if !testutil.DockerIsConnected(t) { 658 t.Skip("Docker not connected") 659 } 660 task := &structs.Task{ 661 Name: "nc-demo", 662 Driver: "docker", 663 Config: map[string]interface{}{ 664 "image": "busybox", 665 "load": "busybox.tar", 666 "command": "/bin/sleep", 667 "args": []string{"10"}, 668 }, 669 LogConfig: &structs.LogConfig{ 670 MaxFiles: 10, 671 MaxFileSizeMB: 10, 672 }, 673 Resources: basicResources, 674 } 675 676 _, handle, cleanup := dockerSetup(t, task) 677 defer cleanup() 678 679 go func() { 680 time.Sleep(100 * time.Millisecond) 681 err := handle.Kill() 682 if err != nil { 683 t.Fatalf("err: %v", err) 684 } 685 }() 686 687 select { 688 case res := <-handle.WaitCh(): 689 if res.Successful() { 690 t.Fatalf("should err: %v", res) 691 } 692 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 693 t.Fatalf("timeout") 694 } 695 } 696 697 func TestDockerDriver_StartN(t *testing.T) { 698 if !tu.IsTravis() { 699 t.Parallel() 700 } 701 if !testutil.DockerIsConnected(t) { 702 t.Skip("Docker not connected") 703 } 704 705 task1, _, _ := dockerTask(t) 706 task2, _, _ := dockerTask(t) 707 task3, _, _ := dockerTask(t) 708 taskList := []*structs.Task{task1, task2, task3} 709 710 handles := make([]DriverHandle, len(taskList)) 711 712 t.Logf("Starting %d tasks", len(taskList)) 713 714 // Let's spin up a bunch of things 715 for idx, task := range taskList { 716 ctx := testDockerDriverContexts(t, task) 717 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 718 defer ctx.AllocDir.Destroy() 719 d := NewDockerDriver(ctx.DriverCtx) 720 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 721 722 _, err := d.Prestart(ctx.ExecCtx, task) 723 if err != nil { 724 t.Fatalf("error in prestart #%d: %v", idx+1, err) 725 } 726 resp, err := d.Start(ctx.ExecCtx, task) 727 if err != nil { 728 t.Errorf("Failed starting task #%d: %s", idx+1, err) 729 continue 730 } 731 handles[idx] = resp.Handle 732 } 733 734 t.Log("All tasks are started. Terminating...") 735 736 for idx, handle := range handles { 737 if handle == nil { 738 t.Errorf("Bad handle for task #%d", idx+1) 739 continue 740 } 741 742 err := handle.Kill() 743 if err != nil { 744 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 745 } 746 } 747 748 t.Log("Test complete!") 749 } 750 751 func TestDockerDriver_StartNVersions(t *testing.T) { 752 if !tu.IsTravis() { 753 t.Parallel() 754 } 755 if !testutil.DockerIsConnected(t) { 756 t.Skip("Docker not connected") 757 } 758 759 task1, _, _ := dockerTask(t) 760 task1.Config["image"] = "busybox" 761 task1.Config["load"] = "busybox.tar" 762 763 task2, _, _ := dockerTask(t) 764 task2.Config["image"] = "busybox:musl" 765 task2.Config["load"] = "busybox_musl.tar" 766 767 task3, _, _ := dockerTask(t) 768 task3.Config["image"] = "busybox:glibc" 769 task3.Config["load"] = "busybox_glibc.tar" 770 771 taskList := []*structs.Task{task1, task2, task3} 772 773 handles := make([]DriverHandle, len(taskList)) 774 775 t.Logf("Starting %d tasks", len(taskList)) 776 777 // Let's spin up a bunch of things 778 for idx, task := range taskList { 779 ctx := testDockerDriverContexts(t, task) 780 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 781 defer ctx.AllocDir.Destroy() 782 d := NewDockerDriver(ctx.DriverCtx) 783 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 784 copyImage(t, ctx.ExecCtx.TaskDir, "busybox_musl.tar") 785 copyImage(t, ctx.ExecCtx.TaskDir, "busybox_glibc.tar") 786 787 _, err := d.Prestart(ctx.ExecCtx, task) 788 if err != nil { 789 t.Fatalf("error in prestart #%d: %v", idx+1, err) 790 } 791 resp, err := d.Start(ctx.ExecCtx, task) 792 if err != nil { 793 t.Errorf("Failed starting task #%d: %s", idx+1, err) 794 continue 795 } 796 handles[idx] = resp.Handle 797 } 798 799 t.Log("All tasks are started. Terminating...") 800 801 for idx, handle := range handles { 802 if handle == nil { 803 t.Errorf("Bad handle for task #%d", idx+1) 804 continue 805 } 806 807 err := handle.Kill() 808 if err != nil { 809 t.Errorf("Failed stopping task #%d: %s", idx+1, err) 810 } 811 } 812 813 t.Log("Test complete!") 814 } 815 816 func waitForExist(t *testing.T, client *docker.Client, handle *DockerHandle) { 817 handle.logger.Printf("[DEBUG] docker.test: waiting for container %s to exist...", handle.ContainerID()) 818 tu.WaitForResult(func() (bool, error) { 819 container, err := client.InspectContainer(handle.ContainerID()) 820 if err != nil { 821 if _, ok := err.(*docker.NoSuchContainer); !ok { 822 return false, err 823 } 824 } 825 826 return container != nil, nil 827 }, func(err error) { 828 t.Fatalf("err: %v", err) 829 }) 830 handle.logger.Printf("[DEBUG] docker.test: ...container %s exists!", handle.ContainerID()) 831 } 832 833 func TestDockerDriver_NetworkMode_Host(t *testing.T) { 834 if !tu.IsTravis() { 835 t.Parallel() 836 } 837 if !testutil.DockerIsConnected(t) { 838 t.Skip("Docker not connected") 839 } 840 expected := "host" 841 842 task := &structs.Task{ 843 Name: "nc-demo", 844 Driver: "docker", 845 Config: map[string]interface{}{ 846 "image": "busybox", 847 "load": "busybox.tar", 848 "command": "/bin/nc", 849 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 850 "network_mode": expected, 851 }, 852 Resources: &structs.Resources{ 853 MemoryMB: 256, 854 CPU: 512, 855 }, 856 LogConfig: &structs.LogConfig{ 857 MaxFiles: 10, 858 MaxFileSizeMB: 10, 859 }, 860 } 861 862 client, handle, cleanup := dockerSetup(t, task) 863 defer cleanup() 864 865 waitForExist(t, client, handle) 866 867 container, err := client.InspectContainer(handle.ContainerID()) 868 if err != nil { 869 t.Fatalf("err: %v", err) 870 } 871 872 actual := container.HostConfig.NetworkMode 873 if actual != expected { 874 t.Fatalf("Got network mode %q; want %q", expected, actual) 875 } 876 } 877 878 func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) { 879 if !tu.IsTravis() { 880 t.Parallel() 881 } 882 if !testutil.DockerIsConnected(t) { 883 t.Skip("Docker not connected") 884 } 885 886 // Because go-dockerclient doesn't provide api for query network aliases, just check that 887 // a container can be created with a 'network_aliases' property 888 889 // Create network, network-scoped alias is supported only for containers in user defined networks 890 client := newTestDockerClient(t) 891 networkOpts := docker.CreateNetworkOptions{Name: "foobar", Driver: "bridge"} 892 network, err := client.CreateNetwork(networkOpts) 893 if err != nil { 894 t.Fatalf("err: %v", err) 895 } 896 defer client.RemoveNetwork(network.ID) 897 898 expected := []string{"foobar"} 899 task := &structs.Task{ 900 Name: "nc-demo", 901 Driver: "docker", 902 Config: map[string]interface{}{ 903 "image": "busybox", 904 "load": "busybox.tar", 905 "command": "/bin/nc", 906 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 907 "network_mode": network.Name, 908 "network_aliases": expected, 909 }, 910 Resources: &structs.Resources{ 911 MemoryMB: 256, 912 CPU: 512, 913 }, 914 LogConfig: &structs.LogConfig{ 915 MaxFiles: 10, 916 MaxFileSizeMB: 10, 917 }, 918 } 919 920 client, handle, cleanup := dockerSetupWithClient(t, task, client) 921 defer cleanup() 922 923 waitForExist(t, client, handle) 924 925 _, err = client.InspectContainer(handle.ContainerID()) 926 if err != nil { 927 t.Fatalf("err: %v", err) 928 } 929 } 930 931 func TestDockerDriver_Sysctl_Ulimit(t *testing.T) { 932 task, _, _ := dockerTask(t) 933 expectedUlimits := map[string]string{ 934 "nproc": "4242", 935 "nofile": "2048:4096", 936 } 937 task.Config["sysctl"] = []map[string]string{ 938 { 939 "net.core.somaxconn": "16384", 940 }, 941 } 942 task.Config["ulimit"] = []map[string]string{ 943 expectedUlimits, 944 } 945 946 client, handle, cleanup := dockerSetup(t, task) 947 defer cleanup() 948 949 waitForExist(t, client, handle) 950 951 container, err := client.InspectContainer(handle.ContainerID()) 952 assert.Nil(t, err, "unexpected error: %v", err) 953 954 want := "16384" 955 got := container.HostConfig.Sysctls["net.core.somaxconn"] 956 assert.Equal(t, want, got, "Wrong net.core.somaxconn config for docker job. Expect: %s, got: %s", want, got) 957 958 expectedUlimitLen := 2 959 actualUlimitLen := len(container.HostConfig.Ulimits) 960 assert.Equal(t, want, got, "Wrong number of ulimit configs for docker job. Expect: %d, got: %d", expectedUlimitLen, actualUlimitLen) 961 962 for _, got := range container.HostConfig.Ulimits { 963 if expectedStr, ok := expectedUlimits[got.Name]; !ok { 964 t.Errorf("%s config unexpected for docker job.", got.Name) 965 } else { 966 if !strings.Contains(expectedStr, ":") { 967 expectedStr = expectedStr + ":" + expectedStr 968 } 969 970 splitted := strings.SplitN(expectedStr, ":", 2) 971 soft, _ := strconv.Atoi(splitted[0]) 972 hard, _ := strconv.Atoi(splitted[1]) 973 assert.Equal(t, int64(soft), got.Soft, "Wrong soft %s ulimit for docker job. Expect: %d, got: %d", got.Name, soft, got.Soft) 974 assert.Equal(t, int64(hard), got.Hard, "Wrong hard %s ulimit for docker job. Expect: %d, got: %d", got.Name, hard, got.Hard) 975 976 } 977 } 978 } 979 980 func TestDockerDriver_Sysctl_Ulimit_Errors(t *testing.T) { 981 brokenConfigs := []interface{}{ 982 map[string]interface{}{ 983 "nofile": "", 984 }, 985 map[string]interface{}{ 986 "nofile": "abc:1234", 987 }, 988 map[string]interface{}{ 989 "nofile": "1234:abc", 990 }, 991 } 992 993 test_cases := []struct { 994 ulimitConfig interface{} 995 err error 996 }{ 997 {[]interface{}{brokenConfigs[0]}, fmt.Errorf("Malformed ulimit specification nofile: \"\", cannot be empty")}, 998 {[]interface{}{brokenConfigs[1]}, fmt.Errorf("Malformed soft ulimit nofile: abc:1234")}, 999 {[]interface{}{brokenConfigs[2]}, fmt.Errorf("Malformed hard ulimit nofile: 1234:abc")}, 1000 } 1001 1002 for _, tc := range test_cases { 1003 task, _, _ := dockerTask(t) 1004 task.Config["ulimit"] = tc.ulimitConfig 1005 1006 ctx := testDockerDriverContexts(t, task) 1007 driver := NewDockerDriver(ctx.DriverCtx) 1008 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 1009 defer ctx.AllocDir.Destroy() 1010 1011 _, err := driver.Prestart(ctx.ExecCtx, task) 1012 assert.NotNil(t, err, "Expected non nil error") 1013 assert.Equal(t, err.Error(), tc.err.Error(), "unexpected error in prestart, got %v, expected %v", err, tc.err) 1014 } 1015 } 1016 1017 func TestDockerDriver_Labels(t *testing.T) { 1018 if !tu.IsTravis() { 1019 t.Parallel() 1020 } 1021 if !testutil.DockerIsConnected(t) { 1022 t.Skip("Docker not connected") 1023 } 1024 1025 task, _, _ := dockerTask(t) 1026 task.Config["labels"] = []map[string]string{ 1027 { 1028 "label1": "value1", 1029 "label2": "value2", 1030 }, 1031 } 1032 1033 client, handle, cleanup := dockerSetup(t, task) 1034 defer cleanup() 1035 1036 waitForExist(t, client, handle) 1037 1038 container, err := client.InspectContainer(handle.ContainerID()) 1039 if err != nil { 1040 t.Fatalf("err: %v", err) 1041 } 1042 1043 if want, got := 2, len(container.Config.Labels); want != got { 1044 t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got) 1045 } 1046 1047 if want, got := "value1", container.Config.Labels["label1"]; want != got { 1048 t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got) 1049 } 1050 } 1051 1052 func TestDockerDriver_ForcePull_IsInvalidConfig(t *testing.T) { 1053 if !tu.IsTravis() { 1054 t.Parallel() 1055 } 1056 if !testutil.DockerIsConnected(t) { 1057 t.Skip("Docker not connected") 1058 } 1059 1060 task, _, _ := dockerTask(t) 1061 task.Config["force_pull"] = "nothing" 1062 1063 ctx := testDockerDriverContexts(t, task) 1064 defer ctx.AllocDir.Destroy() 1065 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 1066 driver := NewDockerDriver(ctx.DriverCtx) 1067 1068 if _, err := driver.Prestart(ctx.ExecCtx, task); err == nil { 1069 t.Fatalf("error expected in prestart") 1070 } 1071 } 1072 1073 func TestDockerDriver_ForcePull(t *testing.T) { 1074 if !tu.IsTravis() { 1075 t.Parallel() 1076 } 1077 if !testutil.DockerIsConnected(t) { 1078 t.Skip("Docker not connected") 1079 } 1080 1081 task, _, _ := dockerTask(t) 1082 task.Config["force_pull"] = "true" 1083 1084 client, handle, cleanup := dockerSetup(t, task) 1085 defer cleanup() 1086 1087 waitForExist(t, client, handle) 1088 1089 _, err := client.InspectContainer(handle.ContainerID()) 1090 if err != nil { 1091 t.Fatalf("err: %v", err) 1092 } 1093 } 1094 1095 func TestDockerDriver_SecurityOpt(t *testing.T) { 1096 if !tu.IsTravis() { 1097 t.Parallel() 1098 } 1099 if !testutil.DockerIsConnected(t) { 1100 t.Skip("Docker not connected") 1101 } 1102 1103 task, _, _ := dockerTask(t) 1104 task.Config["security_opt"] = []string{"seccomp=unconfined"} 1105 1106 client, handle, cleanup := dockerSetup(t, task) 1107 defer cleanup() 1108 1109 waitForExist(t, client, handle) 1110 1111 container, err := client.InspectContainer(handle.ContainerID()) 1112 if err != nil { 1113 t.Fatalf("err: %v", err) 1114 } 1115 1116 if !reflect.DeepEqual(task.Config["security_opt"], container.HostConfig.SecurityOpt) { 1117 t.Errorf("Security Opts don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["security_opt"], container.HostConfig.SecurityOpt) 1118 } 1119 } 1120 1121 func TestDockerDriver_Capabilities(t *testing.T) { 1122 if !tu.IsTravis() { 1123 t.Parallel() 1124 } 1125 if !testutil.DockerIsConnected(t) { 1126 t.Skip("Docker not connected") 1127 } 1128 if runtime.GOOS == "windows" { 1129 t.Skip("Capabilities not supported on windows") 1130 } 1131 1132 testCases := []struct { 1133 Name string 1134 CapAdd []string 1135 CapDrop []string 1136 Whitelist string 1137 StartError string 1138 }{ 1139 { 1140 Name: "default-whitelist-add-allowed", 1141 CapAdd: []string{"fowner", "mknod"}, 1142 CapDrop: []string{"all"}, 1143 }, 1144 { 1145 Name: "default-whitelist-add-forbidden", 1146 CapAdd: []string{"net_admin"}, 1147 StartError: "net_admin", 1148 }, 1149 { 1150 Name: "default-whitelist-drop-existing", 1151 CapDrop: []string{"fowner", "mknod"}, 1152 }, 1153 { 1154 Name: "restrictive-whitelist-drop-all", 1155 CapDrop: []string{"all"}, 1156 Whitelist: "fowner,mknod", 1157 }, 1158 { 1159 Name: "restrictive-whitelist-add-allowed", 1160 CapAdd: []string{"fowner", "mknod"}, 1161 CapDrop: []string{"all"}, 1162 Whitelist: "fowner,mknod", 1163 }, 1164 { 1165 Name: "restrictive-whitelist-add-forbidden", 1166 CapAdd: []string{"net_admin", "mknod"}, 1167 CapDrop: []string{"all"}, 1168 Whitelist: "fowner,mknod", 1169 StartError: "net_admin", 1170 }, 1171 { 1172 Name: "permissive-whitelist", 1173 CapAdd: []string{"net_admin", "mknod"}, 1174 Whitelist: "all", 1175 }, 1176 { 1177 Name: "permissive-whitelist-add-all", 1178 CapAdd: []string{"all"}, 1179 Whitelist: "all", 1180 }, 1181 } 1182 1183 for _, tc := range testCases { 1184 t.Run(tc.Name, func(t *testing.T) { 1185 client := newTestDockerClient(t) 1186 task, _, _ := dockerTask(t) 1187 if len(tc.CapAdd) > 0 { 1188 task.Config["cap_add"] = tc.CapAdd 1189 } 1190 if len(tc.CapDrop) > 0 { 1191 task.Config["cap_drop"] = tc.CapDrop 1192 } 1193 1194 tctx := testDockerDriverContexts(t, task) 1195 if tc.Whitelist != "" { 1196 tctx.DriverCtx.config.Options[dockerCapsWhitelistConfigOption] = tc.Whitelist 1197 } 1198 1199 driver := NewDockerDriver(tctx.DriverCtx) 1200 copyImage(t, tctx.ExecCtx.TaskDir, "busybox.tar") 1201 defer tctx.AllocDir.Destroy() 1202 1203 presp, err := driver.Prestart(tctx.ExecCtx, task) 1204 defer driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 1205 if err != nil { 1206 t.Fatalf("Error in prestart: %v", err) 1207 } 1208 1209 sresp, err := driver.Start(tctx.ExecCtx, task) 1210 if err == nil && tc.StartError != "" { 1211 t.Fatalf("Expected error in start: %v", tc.StartError) 1212 } else if err != nil { 1213 if tc.StartError == "" { 1214 t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack()) 1215 } else if !strings.Contains(err.Error(), tc.StartError) { 1216 t.Fatalf("Expect error containing \"%s\", got %v", tc.StartError, err) 1217 } 1218 return 1219 } 1220 1221 if sresp.Handle == nil { 1222 t.Fatalf("handle is nil\nStack\n%s", debug.Stack()) 1223 } 1224 defer sresp.Handle.Kill() 1225 handle := sresp.Handle.(*DockerHandle) 1226 1227 waitForExist(t, client, handle) 1228 1229 container, err := client.InspectContainer(handle.ContainerID()) 1230 if err != nil { 1231 t.Fatalf("Error inspecting container: %v", err) 1232 } 1233 1234 if !reflect.DeepEqual(tc.CapAdd, container.HostConfig.CapAdd) { 1235 t.Errorf("CapAdd doesn't match.\nExpected:\n%s\nGot:\n%s\n", tc.CapAdd, container.HostConfig.CapAdd) 1236 } 1237 1238 if !reflect.DeepEqual(tc.CapDrop, container.HostConfig.CapDrop) { 1239 t.Errorf("CapDrop doesn't match.\nExpected:\n%s\nGot:\n%s\n", tc.CapDrop, container.HostConfig.CapDrop) 1240 } 1241 }) 1242 } 1243 } 1244 1245 func TestDockerDriver_DNS(t *testing.T) { 1246 if !tu.IsTravis() { 1247 t.Parallel() 1248 } 1249 if !testutil.DockerIsConnected(t) { 1250 t.Skip("Docker not connected") 1251 } 1252 1253 task, _, _ := dockerTask(t) 1254 task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"} 1255 task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"} 1256 task.Config["dns_options"] = []string{"ndots:1"} 1257 1258 client, handle, cleanup := dockerSetup(t, task) 1259 defer cleanup() 1260 1261 waitForExist(t, client, handle) 1262 1263 container, err := client.InspectContainer(handle.ContainerID()) 1264 if err != nil { 1265 t.Fatalf("err: %v", err) 1266 } 1267 1268 if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) { 1269 t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS) 1270 } 1271 1272 if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) { 1273 t.Errorf("DNS Search Domains don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch) 1274 } 1275 1276 if !reflect.DeepEqual(task.Config["dns_options"], container.HostConfig.DNSOptions) { 1277 t.Errorf("DNS Options don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_options"], container.HostConfig.DNSOptions) 1278 } 1279 } 1280 1281 func TestDockerDriver_MACAddress(t *testing.T) { 1282 if !tu.IsTravis() { 1283 t.Parallel() 1284 } 1285 if !testutil.DockerIsConnected(t) { 1286 t.Skip("Docker not connected") 1287 } 1288 1289 task, _, _ := dockerTask(t) 1290 task.Config["mac_address"] = "00:16:3e:00:00:00" 1291 1292 client, handle, cleanup := dockerSetup(t, task) 1293 defer cleanup() 1294 1295 waitForExist(t, client, handle) 1296 1297 container, err := client.InspectContainer(handle.ContainerID()) 1298 if err != nil { 1299 t.Fatalf("err: %v", err) 1300 } 1301 1302 if container.NetworkSettings.MacAddress != task.Config["mac_address"] { 1303 t.Errorf("expected mac_address=%q but found %q", task.Config["mac_address"], container.NetworkSettings.MacAddress) 1304 } 1305 } 1306 1307 func TestDockerWorkDir(t *testing.T) { 1308 if !tu.IsTravis() { 1309 t.Parallel() 1310 } 1311 if !testutil.DockerIsConnected(t) { 1312 t.Skip("Docker not connected") 1313 } 1314 1315 task, _, _ := dockerTask(t) 1316 task.Config["work_dir"] = "/some/path" 1317 1318 client, handle, cleanup := dockerSetup(t, task) 1319 defer cleanup() 1320 1321 container, err := client.InspectContainer(handle.ContainerID()) 1322 if err != nil { 1323 t.Fatalf("err: %v", err) 1324 } 1325 1326 if want, got := "/some/path", container.Config.WorkingDir; want != got { 1327 t.Errorf("Wrong working directory for docker job. Expect: %s, got: %s", want, got) 1328 } 1329 } 1330 1331 func inSlice(needle string, haystack []string) bool { 1332 for _, h := range haystack { 1333 if h == needle { 1334 return true 1335 } 1336 } 1337 return false 1338 } 1339 1340 func TestDockerDriver_PortsNoMap(t *testing.T) { 1341 if !tu.IsTravis() { 1342 t.Parallel() 1343 } 1344 if !testutil.DockerIsConnected(t) { 1345 t.Skip("Docker not connected") 1346 } 1347 1348 task, res, dyn := dockerTask(t) 1349 1350 client, handle, cleanup := dockerSetup(t, task) 1351 defer cleanup() 1352 1353 waitForExist(t, client, handle) 1354 1355 container, err := client.InspectContainer(handle.ContainerID()) 1356 if err != nil { 1357 t.Fatalf("err: %v", err) 1358 } 1359 1360 // Verify that the correct ports are EXPOSED 1361 expectedExposedPorts := map[docker.Port]struct{}{ 1362 docker.Port(fmt.Sprintf("%d/tcp", res)): {}, 1363 docker.Port(fmt.Sprintf("%d/udp", res)): {}, 1364 docker.Port(fmt.Sprintf("%d/tcp", dyn)): {}, 1365 docker.Port(fmt.Sprintf("%d/udp", dyn)): {}, 1366 } 1367 1368 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 1369 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 1370 } 1371 1372 // Verify that the correct ports are FORWARDED 1373 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 1374 docker.Port(fmt.Sprintf("%d/tcp", res)): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 1375 docker.Port(fmt.Sprintf("%d/udp", res)): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 1376 docker.Port(fmt.Sprintf("%d/tcp", dyn)): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 1377 docker.Port(fmt.Sprintf("%d/udp", dyn)): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 1378 } 1379 1380 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 1381 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 1382 } 1383 1384 expectedEnvironment := map[string]string{ 1385 "NOMAD_ADDR_main": fmt.Sprintf("127.0.0.1:%d", res), 1386 "NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn), 1387 } 1388 1389 for key, val := range expectedEnvironment { 1390 search := fmt.Sprintf("%s=%s", key, val) 1391 if !inSlice(search, container.Config.Env) { 1392 t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env) 1393 } 1394 } 1395 } 1396 1397 func TestDockerDriver_PortsMapping(t *testing.T) { 1398 if !tu.IsTravis() { 1399 t.Parallel() 1400 } 1401 if !testutil.DockerIsConnected(t) { 1402 t.Skip("Docker not connected") 1403 } 1404 1405 task, res, dyn := dockerTask(t) 1406 task.Config["port_map"] = []map[string]string{ 1407 { 1408 "main": "8080", 1409 "REDIS": "6379", 1410 }, 1411 } 1412 1413 client, handle, cleanup := dockerSetup(t, task) 1414 defer cleanup() 1415 1416 waitForExist(t, client, handle) 1417 1418 container, err := client.InspectContainer(handle.ContainerID()) 1419 if err != nil { 1420 t.Fatalf("err: %v", err) 1421 } 1422 1423 // Verify that the correct ports are EXPOSED 1424 expectedExposedPorts := map[docker.Port]struct{}{ 1425 docker.Port("8080/tcp"): {}, 1426 docker.Port("8080/udp"): {}, 1427 docker.Port("6379/tcp"): {}, 1428 docker.Port("6379/udp"): {}, 1429 } 1430 1431 if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) { 1432 t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts) 1433 } 1434 1435 // Verify that the correct ports are FORWARDED 1436 expectedPortBindings := map[docker.Port][]docker.PortBinding{ 1437 docker.Port("8080/tcp"): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 1438 docker.Port("8080/udp"): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}}, 1439 docker.Port("6379/tcp"): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 1440 docker.Port("6379/udp"): {{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}}, 1441 } 1442 1443 if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) { 1444 t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings) 1445 } 1446 1447 expectedEnvironment := map[string]string{ 1448 "NOMAD_PORT_main": "8080", 1449 "NOMAD_PORT_REDIS": "6379", 1450 "NOMAD_HOST_PORT_main": strconv.Itoa(res), 1451 } 1452 1453 sort.Strings(container.Config.Env) 1454 for key, val := range expectedEnvironment { 1455 search := fmt.Sprintf("%s=%s", key, val) 1456 if !inSlice(search, container.Config.Env) { 1457 t.Errorf("Expected to find %s in container environment:\n%s\n\n", search, strings.Join(container.Config.Env, "\n")) 1458 } 1459 } 1460 } 1461 1462 func TestDockerDriver_User(t *testing.T) { 1463 if !tu.IsTravis() { 1464 t.Parallel() 1465 } 1466 if !testutil.DockerIsConnected(t) { 1467 t.Skip("Docker not connected") 1468 } 1469 1470 task := &structs.Task{ 1471 Name: "redis-demo", 1472 User: "alice", 1473 Driver: "docker", 1474 Config: map[string]interface{}{ 1475 "image": "busybox", 1476 "load": "busybox.tar", 1477 "command": "/bin/sleep", 1478 "args": []string{"10000"}, 1479 }, 1480 Resources: &structs.Resources{ 1481 MemoryMB: 256, 1482 CPU: 512, 1483 }, 1484 LogConfig: &structs.LogConfig{ 1485 MaxFiles: 10, 1486 MaxFileSizeMB: 10, 1487 }, 1488 } 1489 1490 ctx := testDockerDriverContexts(t, task) 1491 //ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"} 1492 driver := NewDockerDriver(ctx.DriverCtx) 1493 defer ctx.AllocDir.Destroy() 1494 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 1495 1496 _, err := driver.Prestart(ctx.ExecCtx, task) 1497 if err != nil { 1498 t.Fatalf("error in prestart: %v", err) 1499 } 1500 1501 // It should fail because the user "alice" does not exist on the given 1502 // image. 1503 resp, err := driver.Start(ctx.ExecCtx, task) 1504 if err == nil { 1505 resp.Handle.Kill() 1506 t.Fatalf("Should've failed") 1507 } 1508 1509 if !strings.Contains(err.Error(), "alice") { 1510 t.Fatalf("Expected failure string not found, found %q instead", err.Error()) 1511 } 1512 } 1513 1514 func TestDockerDriver_CleanupContainer(t *testing.T) { 1515 if !tu.IsTravis() { 1516 t.Parallel() 1517 } 1518 if !testutil.DockerIsConnected(t) { 1519 t.Skip("Docker not connected") 1520 } 1521 1522 task := &structs.Task{ 1523 Name: "redis-demo", 1524 Driver: "docker", 1525 Config: map[string]interface{}{ 1526 "image": "busybox", 1527 "load": "busybox.tar", 1528 "command": "/bin/echo", 1529 "args": []string{"hello"}, 1530 }, 1531 Resources: &structs.Resources{ 1532 MemoryMB: 256, 1533 CPU: 512, 1534 }, 1535 LogConfig: &structs.LogConfig{ 1536 MaxFiles: 10, 1537 MaxFileSizeMB: 10, 1538 }, 1539 } 1540 1541 _, handle, cleanup := dockerSetup(t, task) 1542 defer cleanup() 1543 1544 // Update should be a no-op 1545 err := handle.Update(task) 1546 if err != nil { 1547 t.Fatalf("err: %v", err) 1548 } 1549 1550 select { 1551 case res := <-handle.WaitCh(): 1552 if !res.Successful() { 1553 t.Fatalf("err: %v", res) 1554 } 1555 1556 time.Sleep(3 * time.Second) 1557 1558 // Ensure that the container isn't present 1559 _, err := client.InspectContainer(handle.containerID) 1560 if err == nil { 1561 t.Fatalf("expected to not get container") 1562 } 1563 1564 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 1565 t.Fatalf("timeout") 1566 } 1567 } 1568 1569 func TestDockerDriver_Stats(t *testing.T) { 1570 if !tu.IsTravis() { 1571 t.Parallel() 1572 } 1573 if !testutil.DockerIsConnected(t) { 1574 t.Skip("Docker not connected") 1575 } 1576 1577 task := &structs.Task{ 1578 Name: "sleep", 1579 Driver: "docker", 1580 Config: map[string]interface{}{ 1581 "image": "busybox", 1582 "load": "busybox.tar", 1583 "command": "/bin/sleep", 1584 "args": []string{"100"}, 1585 }, 1586 LogConfig: &structs.LogConfig{ 1587 MaxFiles: 10, 1588 MaxFileSizeMB: 10, 1589 }, 1590 Resources: basicResources, 1591 } 1592 1593 _, handle, cleanup := dockerSetup(t, task) 1594 defer cleanup() 1595 1596 waitForExist(t, client, handle) 1597 1598 go func() { 1599 time.Sleep(3 * time.Second) 1600 ru, err := handle.Stats() 1601 if err != nil { 1602 t.Fatalf("err: %v", err) 1603 } 1604 if ru.ResourceUsage == nil { 1605 handle.Kill() 1606 t.Fatalf("expected resource usage") 1607 } 1608 err = handle.Kill() 1609 if err != nil { 1610 t.Fatalf("err: %v", err) 1611 } 1612 }() 1613 1614 select { 1615 case res := <-handle.WaitCh(): 1616 if res.Successful() { 1617 t.Fatalf("should err: %v", res) 1618 } 1619 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1620 t.Fatalf("timeout") 1621 } 1622 } 1623 1624 func setupDockerVolumes(t *testing.T, cfg *config.Config, hostpath string) (*structs.Task, Driver, *ExecContext, string, func()) { 1625 if !testutil.DockerIsConnected(t) { 1626 t.Skip("Docker not connected") 1627 } 1628 1629 randfn := fmt.Sprintf("test-%d", rand.Int()) 1630 hostfile := filepath.Join(hostpath, randfn) 1631 containerPath := "/mnt/vol" 1632 containerFile := filepath.Join(containerPath, randfn) 1633 1634 task := &structs.Task{ 1635 Name: "ls", 1636 Env: map[string]string{"VOL_PATH": containerPath}, 1637 Driver: "docker", 1638 Config: map[string]interface{}{ 1639 "image": "busybox", 1640 "load": "busybox.tar", 1641 "command": "touch", 1642 "args": []string{containerFile}, 1643 "volumes": []string{fmt.Sprintf("%s:${VOL_PATH}", hostpath)}, 1644 }, 1645 LogConfig: &structs.LogConfig{ 1646 MaxFiles: 10, 1647 MaxFileSizeMB: 10, 1648 }, 1649 Resources: basicResources, 1650 } 1651 1652 // Build alloc and task directory structure 1653 allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(cfg.AllocDir, uuid.Generate())) 1654 if err := allocDir.Build(); err != nil { 1655 t.Fatalf("failed to build alloc dir: %v", err) 1656 } 1657 taskDir := allocDir.NewTaskDir(task.Name) 1658 if err := taskDir.Build(false, nil, cstructs.FSIsolationImage); err != nil { 1659 allocDir.Destroy() 1660 t.Fatalf("failed to build task dir: %v", err) 1661 } 1662 copyImage(t, taskDir, "busybox.tar") 1663 1664 // Setup driver 1665 alloc := mock.Alloc() 1666 logger := testLogger() 1667 emitter := func(m string, args ...interface{}) { 1668 logger.Printf("[EVENT] "+m, args...) 1669 } 1670 driverCtx := NewDriverContext(alloc.Job.Name, alloc.TaskGroup, task.Name, alloc.ID, cfg, cfg.Node, testLogger(), emitter) 1671 driver := NewDockerDriver(driverCtx) 1672 1673 // Setup execCtx 1674 envBuilder := env.NewBuilder(cfg.Node, alloc, task, cfg.Region) 1675 SetEnvvars(envBuilder, driver.FSIsolation(), taskDir, cfg) 1676 execCtx := NewExecContext(taskDir, envBuilder.Build()) 1677 1678 // Setup cleanup function 1679 cleanup := func() { 1680 allocDir.Destroy() 1681 if filepath.IsAbs(hostpath) { 1682 os.RemoveAll(hostpath) 1683 } 1684 } 1685 return task, driver, execCtx, hostfile, cleanup 1686 } 1687 1688 func TestDockerDriver_VolumesDisabled(t *testing.T) { 1689 if !tu.IsTravis() { 1690 t.Parallel() 1691 } 1692 if !testutil.DockerIsConnected(t) { 1693 t.Skip("Docker not connected") 1694 } 1695 1696 cfg := testConfig(t) 1697 cfg.Options = map[string]string{ 1698 dockerVolumesConfigOption: "false", 1699 "docker.cleanup.image": "false", 1700 } 1701 1702 { 1703 tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesdisabled") 1704 if err != nil { 1705 t.Fatalf("error creating temporary dir: %v", err) 1706 } 1707 1708 task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, tmpvol) 1709 defer cleanup() 1710 1711 _, err = driver.Prestart(execCtx, task) 1712 if err != nil { 1713 t.Fatalf("error in prestart: %v", err) 1714 } 1715 if _, err := driver.Start(execCtx, task); err == nil { 1716 t.Fatalf("Started driver successfully when volumes should have been disabled.") 1717 } 1718 } 1719 1720 // Relative paths should still be allowed 1721 { 1722 task, driver, execCtx, fn, cleanup := setupDockerVolumes(t, cfg, ".") 1723 defer cleanup() 1724 1725 _, err := driver.Prestart(execCtx, task) 1726 if err != nil { 1727 t.Fatalf("error in prestart: %v", err) 1728 } 1729 resp, err := driver.Start(execCtx, task) 1730 if err != nil { 1731 t.Fatalf("err: %v", err) 1732 } 1733 defer resp.Handle.Kill() 1734 1735 select { 1736 case res := <-resp.Handle.WaitCh(): 1737 if !res.Successful() { 1738 t.Fatalf("unexpected err: %v", res) 1739 } 1740 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1741 t.Fatalf("timeout") 1742 } 1743 1744 if _, err := ioutil.ReadFile(filepath.Join(execCtx.TaskDir.Dir, fn)); err != nil { 1745 t.Fatalf("unexpected error reading %s: %v", fn, err) 1746 } 1747 } 1748 1749 // Volume Drivers should be rejected (error) 1750 { 1751 task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, "fake_flocker_vol") 1752 defer cleanup() 1753 task.Config["volume_driver"] = "flocker" 1754 1755 if _, err := driver.Prestart(execCtx, task); err != nil { 1756 t.Fatalf("error in prestart: %v", err) 1757 } 1758 if _, err := driver.Start(execCtx, task); err == nil { 1759 t.Fatalf("Started driver successfully when volume drivers should have been disabled.") 1760 } 1761 } 1762 1763 } 1764 1765 func TestDockerDriver_VolumesEnabled(t *testing.T) { 1766 if !tu.IsTravis() { 1767 t.Parallel() 1768 } 1769 if !testutil.DockerIsConnected(t) { 1770 t.Skip("Docker not connected") 1771 } 1772 1773 cfg := testConfig(t) 1774 1775 tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesenabled") 1776 if err != nil { 1777 t.Fatalf("error creating temporary dir: %v", err) 1778 } 1779 1780 // Evaluate symlinks so it works on MacOS 1781 tmpvol, err = filepath.EvalSymlinks(tmpvol) 1782 if err != nil { 1783 t.Fatalf("error evaluating symlinks: %v", err) 1784 } 1785 1786 task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg, tmpvol) 1787 defer cleanup() 1788 1789 _, err = driver.Prestart(execCtx, task) 1790 if err != nil { 1791 t.Fatalf("error in prestart: %v", err) 1792 } 1793 resp, err := driver.Start(execCtx, task) 1794 if err != nil { 1795 t.Fatalf("Failed to start docker driver: %v", err) 1796 } 1797 defer resp.Handle.Kill() 1798 1799 select { 1800 case res := <-resp.Handle.WaitCh(): 1801 if !res.Successful() { 1802 t.Fatalf("unexpected err: %v", res) 1803 } 1804 case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): 1805 t.Fatalf("timeout") 1806 } 1807 1808 if _, err := ioutil.ReadFile(hostpath); err != nil { 1809 t.Fatalf("unexpected error reading %s: %v", hostpath, err) 1810 } 1811 } 1812 1813 func TestDockerDriver_Mounts(t *testing.T) { 1814 if !tu.IsTravis() { 1815 t.Parallel() 1816 } 1817 if !testutil.DockerIsConnected(t) { 1818 t.Skip("Docker not connected") 1819 } 1820 1821 goodMount := map[string]interface{}{ 1822 "target": "/nomad", 1823 "volume_options": []interface{}{ 1824 map[string]interface{}{ 1825 "labels": []interface{}{ 1826 map[string]string{"foo": "bar"}, 1827 }, 1828 "driver_config": []interface{}{ 1829 map[string]interface{}{ 1830 "name": "local", 1831 "options": []interface{}{ 1832 map[string]interface{}{ 1833 "foo": "bar", 1834 }, 1835 }, 1836 }, 1837 }, 1838 }, 1839 }, 1840 "readonly": true, 1841 "source": "test", 1842 } 1843 1844 cases := []struct { 1845 Name string 1846 Mounts []interface{} 1847 Error string 1848 }{ 1849 { 1850 Name: "good-one", 1851 Error: "", 1852 Mounts: []interface{}{goodMount}, 1853 }, 1854 { 1855 Name: "good-many", 1856 Error: "", 1857 Mounts: []interface{}{goodMount, goodMount, goodMount}, 1858 }, 1859 { 1860 Name: "multiple volume options", 1861 Error: "Only one volume_options stanza allowed", 1862 Mounts: []interface{}{ 1863 map[string]interface{}{ 1864 "target": "/nomad", 1865 "volume_options": []interface{}{ 1866 map[string]interface{}{ 1867 "driver_config": []interface{}{ 1868 map[string]interface{}{ 1869 "name": "local", 1870 }, 1871 }, 1872 }, 1873 map[string]interface{}{ 1874 "driver_config": []interface{}{ 1875 map[string]interface{}{ 1876 "name": "local", 1877 }, 1878 }, 1879 }, 1880 }, 1881 }, 1882 }, 1883 }, 1884 { 1885 Name: "multiple driver configs", 1886 Error: "volume driver config may only be specified once", 1887 Mounts: []interface{}{ 1888 map[string]interface{}{ 1889 "target": "/nomad", 1890 "volume_options": []interface{}{ 1891 map[string]interface{}{ 1892 "driver_config": []interface{}{ 1893 map[string]interface{}{ 1894 "name": "local", 1895 }, 1896 map[string]interface{}{ 1897 "name": "local", 1898 }, 1899 }, 1900 }, 1901 }, 1902 }, 1903 }, 1904 }, 1905 { 1906 Name: "multiple volume labels", 1907 Error: "labels may only be", 1908 Mounts: []interface{}{ 1909 map[string]interface{}{ 1910 "target": "/nomad", 1911 "volume_options": []interface{}{ 1912 map[string]interface{}{ 1913 "labels": []interface{}{ 1914 map[string]string{"foo": "bar"}, 1915 map[string]string{"baz": "bam"}, 1916 }, 1917 }, 1918 }, 1919 }, 1920 }, 1921 }, 1922 { 1923 Name: "multiple driver options", 1924 Error: "driver options may only", 1925 Mounts: []interface{}{ 1926 map[string]interface{}{ 1927 "target": "/nomad", 1928 "volume_options": []interface{}{ 1929 map[string]interface{}{ 1930 "driver_config": []interface{}{ 1931 map[string]interface{}{ 1932 "name": "local", 1933 "options": []interface{}{ 1934 map[string]interface{}{ 1935 "foo": "bar", 1936 }, 1937 map[string]interface{}{ 1938 "bam": "bar", 1939 }, 1940 }, 1941 }, 1942 }, 1943 }, 1944 }, 1945 }, 1946 }, 1947 }, 1948 } 1949 1950 task := &structs.Task{ 1951 Name: "redis-demo", 1952 Driver: "docker", 1953 Config: map[string]interface{}{ 1954 "image": "busybox", 1955 "load": "busybox.tar", 1956 "command": "/bin/sleep", 1957 "args": []string{"10000"}, 1958 }, 1959 Resources: &structs.Resources{ 1960 MemoryMB: 256, 1961 CPU: 512, 1962 }, 1963 LogConfig: &structs.LogConfig{ 1964 MaxFiles: 10, 1965 MaxFileSizeMB: 10, 1966 }, 1967 } 1968 1969 for _, c := range cases { 1970 t.Run(c.Name, func(t *testing.T) { 1971 // Build the task 1972 task.Config["mounts"] = c.Mounts 1973 1974 ctx := testDockerDriverContexts(t, task) 1975 driver := NewDockerDriver(ctx.DriverCtx) 1976 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 1977 defer ctx.AllocDir.Destroy() 1978 1979 _, err := driver.Prestart(ctx.ExecCtx, task) 1980 if err == nil && c.Error != "" { 1981 t.Fatalf("expected error: %v", c.Error) 1982 } else if err != nil { 1983 if c.Error == "" { 1984 t.Fatalf("unexpected error in prestart: %v", err) 1985 } else if !strings.Contains(err.Error(), c.Error) { 1986 t.Fatalf("expected error %q; got %v", c.Error, err) 1987 } 1988 } 1989 }) 1990 } 1991 } 1992 1993 // TestDockerDriver_Cleanup ensures Cleanup removes only downloaded images. 1994 func TestDockerDriver_Cleanup(t *testing.T) { 1995 if !tu.IsTravis() { 1996 t.Parallel() 1997 } 1998 if !testutil.DockerIsConnected(t) { 1999 t.Skip("Docker not connected") 2000 } 2001 2002 imageName := "hello-world:latest" 2003 task := &structs.Task{ 2004 Name: "cleanup_test", 2005 Driver: "docker", 2006 Config: map[string]interface{}{ 2007 "image": imageName, 2008 }, 2009 } 2010 tctx := testDockerDriverContexts(t, task) 2011 defer tctx.AllocDir.Destroy() 2012 2013 // Run Prestart 2014 driver := NewDockerDriver(tctx.DriverCtx).(*DockerDriver) 2015 resp, err := driver.Prestart(tctx.ExecCtx, task) 2016 if err != nil { 2017 t.Fatalf("error in prestart: %v", err) 2018 } 2019 res := resp.CreatedResources 2020 if len(res.Resources) == 0 || len(res.Resources[dockerImageResKey]) == 0 { 2021 t.Fatalf("no created resources: %#v", res) 2022 } 2023 2024 // Cleanup 2025 rescopy := res.Copy() 2026 if err := driver.Cleanup(tctx.ExecCtx, rescopy); err != nil { 2027 t.Fatalf("Cleanup failed: %v", err) 2028 } 2029 2030 // Make sure rescopy is updated 2031 if len(rescopy.Resources) > 0 { 2032 t.Errorf("Cleanup should have cleared resource map: %#v", rescopy.Resources) 2033 } 2034 2035 // Ensure image was removed 2036 tu.WaitForResult(func() (bool, error) { 2037 if _, err := client.InspectImage(driver.driverConfig.ImageName); err == nil { 2038 return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", imageName) 2039 } 2040 2041 return true, nil 2042 }, func(err error) { 2043 t.Fatalf("err: %v", err) 2044 }) 2045 2046 // The image doesn't exist which shouldn't be an error when calling 2047 // Cleanup, so call it again to make sure. 2048 if err := driver.Cleanup(tctx.ExecCtx, res.Copy()); err != nil { 2049 t.Fatalf("Cleanup failed: %v", err) 2050 } 2051 } 2052 2053 func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) { 2054 dst := filepath.Join(taskDir.LocalDir, image) 2055 copyFile(filepath.Join("./test-resources/docker", image), dst, t) 2056 } 2057 2058 func TestDockerDriver_AuthConfiguration(t *testing.T) { 2059 if !tu.IsTravis() { 2060 t.Parallel() 2061 } 2062 if !testutil.DockerIsConnected(t) { 2063 t.Skip("Docker not connected") 2064 } 2065 2066 path := "./test-resources/docker/auth.json" 2067 cases := []struct { 2068 Repo string 2069 AuthConfig *docker.AuthConfiguration 2070 }{ 2071 { 2072 Repo: "lolwhat.com/what:1337", 2073 AuthConfig: nil, 2074 }, 2075 { 2076 Repo: "redis:3.2", 2077 AuthConfig: &docker.AuthConfiguration{ 2078 Username: "test", 2079 Password: "1234", 2080 Email: "", 2081 ServerAddress: "https://index.docker.io/v1/", 2082 }, 2083 }, 2084 { 2085 Repo: "quay.io/redis:3.2", 2086 AuthConfig: &docker.AuthConfiguration{ 2087 Username: "test", 2088 Password: "5678", 2089 Email: "", 2090 ServerAddress: "quay.io", 2091 }, 2092 }, 2093 { 2094 Repo: "other.io/redis:3.2", 2095 AuthConfig: &docker.AuthConfiguration{ 2096 Username: "test", 2097 Password: "abcd", 2098 Email: "", 2099 ServerAddress: "https://other.io/v1/", 2100 }, 2101 }, 2102 } 2103 2104 for i, c := range cases { 2105 act, err := authFromDockerConfig(path)(c.Repo) 2106 if err != nil { 2107 t.Fatalf("Test %d failed: %v", i+1, err) 2108 } 2109 2110 if !reflect.DeepEqual(act, c.AuthConfig) { 2111 t.Fatalf("Test %d failed: Unexpected auth config: got %+v; want %+v", i+1, act, c.AuthConfig) 2112 } 2113 } 2114 } 2115 2116 func TestDockerDriver_OOMKilled(t *testing.T) { 2117 if !tu.IsTravis() { 2118 t.Parallel() 2119 } 2120 if !testutil.DockerIsConnected(t) { 2121 t.Skip("Docker not connected") 2122 } 2123 2124 task := &structs.Task{ 2125 Name: "oom-killed", 2126 Driver: "docker", 2127 Config: map[string]interface{}{ 2128 "image": "busybox", 2129 "load": "busybox.tar", 2130 "command": "sh", 2131 // Incrementally creates a bigger and bigger variable. 2132 "args": []string{"-c", "x=a; while true; do eval x='$x$x'; done"}, 2133 }, 2134 LogConfig: &structs.LogConfig{ 2135 MaxFiles: 10, 2136 MaxFileSizeMB: 10, 2137 }, 2138 Resources: &structs.Resources{ 2139 CPU: 250, 2140 MemoryMB: 10, 2141 DiskMB: 20, 2142 Networks: []*structs.NetworkResource{}, 2143 }, 2144 } 2145 2146 _, handle, cleanup := dockerSetup(t, task) 2147 defer cleanup() 2148 2149 select { 2150 case res := <-handle.WaitCh(): 2151 if res.Successful() { 2152 t.Fatalf("expected error, but container exited successful") 2153 } 2154 2155 if res.Err.Error() != "OOM Killed" { 2156 t.Fatalf("not killed by OOM killer: %s", res.Err) 2157 } 2158 2159 t.Logf("Successfully killed by OOM killer") 2160 2161 case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): 2162 t.Fatalf("timeout") 2163 } 2164 } 2165 2166 func TestDockerDriver_Devices_IsInvalidConfig(t *testing.T) { 2167 if !tu.IsTravis() { 2168 t.Parallel() 2169 } 2170 if !testutil.DockerIsConnected(t) { 2171 t.Skip("Docker not connected") 2172 } 2173 2174 brokenConfigs := []interface{}{ 2175 map[string]interface{}{ 2176 "host_path": "", 2177 }, 2178 map[string]interface{}{ 2179 "host_path": "/dev/sda1", 2180 "cgroup_permissions": "rxb", 2181 }, 2182 } 2183 2184 test_cases := []struct { 2185 deviceConfig interface{} 2186 err error 2187 }{ 2188 {[]interface{}{brokenConfigs[0]}, fmt.Errorf("host path must be set in configuration for devices")}, 2189 {[]interface{}{brokenConfigs[1]}, fmt.Errorf("invalid cgroup permission string: \"rxb\"")}, 2190 } 2191 2192 for _, tc := range test_cases { 2193 task, _, _ := dockerTask(t) 2194 task.Config["devices"] = tc.deviceConfig 2195 2196 ctx := testDockerDriverContexts(t, task) 2197 driver := NewDockerDriver(ctx.DriverCtx) 2198 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 2199 defer ctx.AllocDir.Destroy() 2200 2201 if _, err := driver.Prestart(ctx.ExecCtx, task); err == nil || err.Error() != tc.err.Error() { 2202 t.Fatalf("error expected in prestart, got %v, expected %v", err, tc.err) 2203 } 2204 } 2205 } 2206 2207 func TestDockerDriver_Device_Success(t *testing.T) { 2208 if !tu.IsTravis() { 2209 t.Parallel() 2210 } 2211 if !testutil.DockerIsConnected(t) { 2212 t.Skip("Docker not connected") 2213 } 2214 2215 if runtime.GOOS != "linux" { 2216 t.Skip("test device mounts only on linux") 2217 } 2218 2219 hostPath := "/dev/random" 2220 containerPath := "/dev/myrandom" 2221 perms := "rwm" 2222 2223 expectedDevice := docker.Device{ 2224 PathOnHost: hostPath, 2225 PathInContainer: containerPath, 2226 CgroupPermissions: perms, 2227 } 2228 config := map[string]interface{}{ 2229 "host_path": hostPath, 2230 "container_path": containerPath, 2231 } 2232 2233 task, _, _ := dockerTask(t) 2234 task.Config["devices"] = []interface{}{config} 2235 2236 client, handle, cleanup := dockerSetup(t, task) 2237 defer cleanup() 2238 2239 waitForExist(t, client, handle) 2240 2241 container, err := client.InspectContainer(handle.ContainerID()) 2242 if err != nil { 2243 t.Fatalf("err: %v", err) 2244 } 2245 2246 assert.NotEmpty(t, container.HostConfig.Devices, "Expected one device") 2247 assert.Equal(t, expectedDevice, container.HostConfig.Devices[0], "Incorrect device ") 2248 } 2249 2250 func TestDockerDriver_Entrypoint(t *testing.T) { 2251 if !tu.IsTravis() { 2252 t.Parallel() 2253 } 2254 if !testutil.DockerIsConnected(t) { 2255 t.Skip("Docker not connected") 2256 } 2257 2258 entrypoint := []string{"/bin/sh", "-c"} 2259 task, _, _ := dockerTask(t) 2260 task.Config["entrypoint"] = entrypoint 2261 2262 client, handle, cleanup := dockerSetup(t, task) 2263 defer cleanup() 2264 2265 waitForExist(t, client, handle) 2266 2267 container, err := client.InspectContainer(handle.ContainerID()) 2268 if err != nil { 2269 t.Fatalf("err: %v", err) 2270 } 2271 2272 require.Len(t, container.Config.Entrypoint, 2, "Expected one entrypoint") 2273 require.Equal(t, entrypoint, container.Config.Entrypoint, "Incorrect entrypoint ") 2274 } 2275 2276 func TestDockerDriver_Kill(t *testing.T) { 2277 assert := assert.New(t) 2278 if !tu.IsTravis() { 2279 t.Parallel() 2280 } 2281 if !testutil.DockerIsConnected(t) { 2282 t.Skip("Docker not connected") 2283 } 2284 2285 // Tasks started with a signal that is not supported should not error 2286 task := &structs.Task{ 2287 Name: "nc-demo", 2288 Driver: "docker", 2289 KillSignal: "SIGKILL", 2290 Config: map[string]interface{}{ 2291 "load": "busybox.tar", 2292 "image": "busybox", 2293 "command": "/bin/nc", 2294 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 2295 }, 2296 LogConfig: &structs.LogConfig{ 2297 MaxFiles: 10, 2298 MaxFileSizeMB: 10, 2299 }, 2300 Resources: basicResources, 2301 } 2302 2303 ctx := testDockerDriverContexts(t, task) 2304 defer ctx.AllocDir.Destroy() 2305 d := NewDockerDriver(ctx.DriverCtx) 2306 copyImage(t, ctx.ExecCtx.TaskDir, "busybox.tar") 2307 2308 _, err := d.Prestart(ctx.ExecCtx, task) 2309 if err != nil { 2310 t.Fatalf("error in prestart: %v", err) 2311 } 2312 2313 resp, err := d.Start(ctx.ExecCtx, task) 2314 assert.Nil(err) 2315 assert.NotNil(resp.Handle) 2316 2317 handle := resp.Handle.(*DockerHandle) 2318 waitForExist(t, client, handle) 2319 err = handle.Kill() 2320 assert.Nil(err) 2321 } 2322 2323 func TestDockerDriver_ReadonlyRootfs(t *testing.T) { 2324 if !tu.IsTravis() { 2325 t.Parallel() 2326 } 2327 if !testutil.DockerIsConnected(t) { 2328 t.Skip("Docker not connected") 2329 } 2330 2331 task, _, _ := dockerTask(t) 2332 task.Config["readonly_rootfs"] = true 2333 2334 client, handle, cleanup := dockerSetup(t, task) 2335 defer cleanup() 2336 2337 waitForExist(t, client, handle) 2338 2339 container, err := client.InspectContainer(handle.ContainerID()) 2340 assert.Nil(t, err, "Error inspecting container: %v", err) 2341 2342 assert.True(t, container.HostConfig.ReadonlyRootfs, "ReadonlyRootfs option not set") 2343 } 2344 2345 // fakeDockerClient can be used in places that accept an interface for the 2346 // docker client such as createContainer. 2347 type fakeDockerClient struct{} 2348 2349 func (fakeDockerClient) CreateContainer(docker.CreateContainerOptions) (*docker.Container, error) { 2350 return nil, fmt.Errorf("volume is attached on another node") 2351 } 2352 func (fakeDockerClient) InspectContainer(id string) (*docker.Container, error) { 2353 panic("not implemented") 2354 } 2355 func (fakeDockerClient) ListContainers(docker.ListContainersOptions) ([]docker.APIContainers, error) { 2356 panic("not implemented") 2357 } 2358 func (fakeDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error { 2359 panic("not implemented") 2360 } 2361 2362 // TestDockerDriver_VolumeError asserts volume related errors when creating a 2363 // container are recoverable. 2364 func TestDockerDriver_VolumeError(t *testing.T) { 2365 if !tu.IsTravis() { 2366 t.Parallel() 2367 } 2368 2369 // setup 2370 task, _, _ := dockerTask(t) 2371 tctx := testDockerDriverContexts(t, task) 2372 driver := NewDockerDriver(tctx.DriverCtx).(*DockerDriver) 2373 driver.driverConfig = &DockerDriverConfig{ImageName: "test"} 2374 2375 // assert volume error is recoverable 2376 _, err := driver.createContainer(fakeDockerClient{}, docker.CreateContainerOptions{}) 2377 require.True(t, structs.IsRecoverable(err)) 2378 } 2379 2380 func TestDockerDriver_AdvertiseIPv6Address(t *testing.T) { 2381 if !tu.IsTravis() { 2382 t.Parallel() 2383 } 2384 if !testutil.DockerIsConnected(t) { 2385 t.Skip("Docker not connected") 2386 } 2387 2388 expectedPrefix := "2001:db8:1::242:ac11" 2389 expectedAdvertise := true 2390 task := &structs.Task{ 2391 Name: "nc-demo", 2392 Driver: "docker", 2393 Config: map[string]interface{}{ 2394 "image": "busybox", 2395 "load": "busybox.tar", 2396 "command": "/bin/nc", 2397 "args": []string{"-l", "127.0.0.1", "-p", "0"}, 2398 "advertise_ipv6_address": expectedAdvertise, 2399 }, 2400 Resources: &structs.Resources{ 2401 MemoryMB: 256, 2402 CPU: 512, 2403 }, 2404 LogConfig: &structs.LogConfig{ 2405 MaxFiles: 10, 2406 MaxFileSizeMB: 10, 2407 }, 2408 } 2409 2410 client := newTestDockerClient(t) 2411 2412 // Make sure IPv6 is enabled 2413 net, err := client.NetworkInfo("bridge") 2414 if err != nil { 2415 t.Skip("error retrieving bridge network information, skipping") 2416 } 2417 if net == nil || !net.EnableIPv6 { 2418 t.Skip("IPv6 not enabled on bridge network, skipping") 2419 } 2420 2421 tctx := testDockerDriverContexts(t, task) 2422 driver := NewDockerDriver(tctx.DriverCtx) 2423 copyImage(t, tctx.ExecCtx.TaskDir, "busybox.tar") 2424 defer tctx.AllocDir.Destroy() 2425 2426 presp, err := driver.Prestart(tctx.ExecCtx, task) 2427 defer driver.Cleanup(tctx.ExecCtx, presp.CreatedResources) 2428 if err != nil { 2429 t.Fatalf("Error in prestart: %v", err) 2430 } 2431 2432 sresp, err := driver.Start(tctx.ExecCtx, task) 2433 if err != nil { 2434 t.Fatalf("Error in start: %v", err) 2435 } 2436 2437 if sresp.Handle == nil { 2438 t.Fatalf("handle is nil\nStack\n%s", debug.Stack()) 2439 } 2440 2441 assert.Equal(t, expectedAdvertise, sresp.Network.AutoAdvertise, "Wrong autoadvertise. Expect: %s, got: %s", expectedAdvertise, sresp.Network.AutoAdvertise) 2442 2443 if !strings.HasPrefix(sresp.Network.IP, expectedPrefix) { 2444 t.Fatalf("Got IP address %q want ip address with prefix %q", sresp.Network.IP, expectedPrefix) 2445 } 2446 2447 defer sresp.Handle.Kill() 2448 handle := sresp.Handle.(*DockerHandle) 2449 2450 waitForExist(t, client, handle) 2451 2452 container, err := client.InspectContainer(handle.ContainerID()) 2453 if err != nil { 2454 t.Fatalf("Error inspecting container: %v", err) 2455 } 2456 2457 if !strings.HasPrefix(container.NetworkSettings.GlobalIPv6Address, expectedPrefix) { 2458 t.Fatalf("Got GlobalIPv6address %s want GlobalIPv6address with prefix %s", expectedPrefix, container.NetworkSettings.GlobalIPv6Address) 2459 } 2460 }