github.com/bigcommerce/nomad@v0.9.3-bc/drivers/docker/driver_unix_test.go (about) 1 // +build darwin dragonfly freebsd linux netbsd openbsd solaris 2 3 package docker 4 5 import ( 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "sort" 11 "strconv" 12 "strings" 13 "testing" 14 "time" 15 16 docker "github.com/fsouza/go-dockerclient" 17 "github.com/hashicorp/nomad/client/allocdir" 18 "github.com/hashicorp/nomad/client/testutil" 19 "github.com/hashicorp/nomad/helper/uuid" 20 "github.com/hashicorp/nomad/plugins/drivers" 21 dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" 22 tu "github.com/hashicorp/nomad/testutil" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 ) 26 27 func TestDockerDriver_User(t *testing.T) { 28 if !tu.IsCI() { 29 t.Parallel() 30 } 31 testutil.DockerCompatible(t) 32 task, cfg, _ := dockerTask(t) 33 task.User = "alice" 34 cfg.Command = "/bin/sleep" 35 cfg.Args = []string{"10000"} 36 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 37 38 d := dockerDriverHarness(t, nil) 39 cleanup := d.MkAllocDir(task, true) 40 defer cleanup() 41 copyImage(t, task.TaskDir(), "busybox.tar") 42 43 _, _, err := d.StartTask(task) 44 if err == nil { 45 d.DestroyTask(task.ID, true) 46 t.Fatalf("Should've failed") 47 } 48 49 if !strings.Contains(err.Error(), "alice") { 50 t.Fatalf("Expected failure string not found, found %q instead", err.Error()) 51 } 52 } 53 54 func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) { 55 if !tu.IsCI() { 56 t.Parallel() 57 } 58 testutil.DockerCompatible(t) 59 require := require.New(t) 60 61 // Because go-dockerclient doesn't provide api for query network aliases, just check that 62 // a container can be created with a 'network_aliases' property 63 64 // Create network, network-scoped alias is supported only for containers in user defined networks 65 client := newTestDockerClient(t) 66 networkOpts := docker.CreateNetworkOptions{Name: "foobar", Driver: "bridge"} 67 network, err := client.CreateNetwork(networkOpts) 68 require.NoError(err) 69 defer client.RemoveNetwork(network.ID) 70 71 expected := []string{"foobar"} 72 taskCfg := newTaskConfig("", busyboxLongRunningCmd) 73 taskCfg.NetworkMode = network.Name 74 taskCfg.NetworkAliases = expected 75 task := &drivers.TaskConfig{ 76 ID: uuid.Generate(), 77 Name: "busybox", 78 Resources: basicResources, 79 } 80 require.NoError(task.EncodeConcreteDriverConfig(&taskCfg)) 81 82 d := dockerDriverHarness(t, nil) 83 cleanup := d.MkAllocDir(task, true) 84 defer cleanup() 85 copyImage(t, task.TaskDir(), "busybox.tar") 86 87 _, _, err = d.StartTask(task) 88 require.NoError(err) 89 require.NoError(d.WaitUntilStarted(task.ID, 5*time.Second)) 90 91 defer d.DestroyTask(task.ID, true) 92 93 dockerDriver, ok := d.Impl().(*Driver) 94 require.True(ok) 95 96 handle, ok := dockerDriver.tasks.Get(task.ID) 97 require.True(ok) 98 99 _, err = client.InspectContainer(handle.containerID) 100 require.NoError(err) 101 } 102 103 func TestDockerDriver_NetworkMode_Host(t *testing.T) { 104 if !tu.IsCI() { 105 t.Parallel() 106 } 107 testutil.DockerCompatible(t) 108 expected := "host" 109 110 taskCfg := newTaskConfig("", busyboxLongRunningCmd) 111 taskCfg.NetworkMode = expected 112 113 task := &drivers.TaskConfig{ 114 ID: uuid.Generate(), 115 Name: "busybox-demo", 116 Resources: basicResources, 117 } 118 require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 119 120 d := dockerDriverHarness(t, nil) 121 cleanup := d.MkAllocDir(task, true) 122 defer cleanup() 123 copyImage(t, task.TaskDir(), "busybox.tar") 124 125 _, _, err := d.StartTask(task) 126 require.NoError(t, err) 127 128 require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) 129 130 defer d.DestroyTask(task.ID, true) 131 132 dockerDriver, ok := d.Impl().(*Driver) 133 require.True(t, ok) 134 135 handle, ok := dockerDriver.tasks.Get(task.ID) 136 require.True(t, ok) 137 138 container, err := client.InspectContainer(handle.containerID) 139 if err != nil { 140 t.Fatalf("err: %v", err) 141 } 142 143 actual := container.HostConfig.NetworkMode 144 require.Equal(t, expected, actual) 145 } 146 147 func TestDockerDriver_CPUCFSPeriod(t *testing.T) { 148 if !tu.IsCI() { 149 t.Parallel() 150 } 151 testutil.DockerCompatible(t) 152 153 task, cfg, _ := dockerTask(t) 154 cfg.CPUHardLimit = true 155 cfg.CPUCFSPeriod = 1000000 156 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 157 158 client, _, handle, cleanup := dockerSetup(t, task) 159 defer cleanup() 160 161 waitForExist(t, client, handle.containerID) 162 163 container, err := client.InspectContainer(handle.containerID) 164 require.NoError(t, err) 165 166 require.Equal(t, cfg.CPUCFSPeriod, container.HostConfig.CPUPeriod) 167 } 168 169 func TestDockerDriver_Sysctl_Ulimit(t *testing.T) { 170 testutil.DockerCompatible(t) 171 task, cfg, _ := dockerTask(t) 172 expectedUlimits := map[string]string{ 173 "nproc": "4242", 174 "nofile": "2048:4096", 175 } 176 cfg.Sysctl = map[string]string{ 177 "net.core.somaxconn": "16384", 178 } 179 cfg.Ulimit = expectedUlimits 180 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 181 182 client, d, handle, cleanup := dockerSetup(t, task) 183 defer cleanup() 184 require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) 185 186 container, err := client.InspectContainer(handle.containerID) 187 assert.Nil(t, err, "unexpected error: %v", err) 188 189 want := "16384" 190 got := container.HostConfig.Sysctls["net.core.somaxconn"] 191 assert.Equal(t, want, got, "Wrong net.core.somaxconn config for docker job. Expect: %s, got: %s", want, got) 192 193 expectedUlimitLen := 2 194 actualUlimitLen := len(container.HostConfig.Ulimits) 195 assert.Equal(t, want, got, "Wrong number of ulimit configs for docker job. Expect: %d, got: %d", expectedUlimitLen, actualUlimitLen) 196 197 for _, got := range container.HostConfig.Ulimits { 198 if expectedStr, ok := expectedUlimits[got.Name]; !ok { 199 t.Errorf("%s config unexpected for docker job.", got.Name) 200 } else { 201 if !strings.Contains(expectedStr, ":") { 202 expectedStr = expectedStr + ":" + expectedStr 203 } 204 205 splitted := strings.SplitN(expectedStr, ":", 2) 206 soft, _ := strconv.Atoi(splitted[0]) 207 hard, _ := strconv.Atoi(splitted[1]) 208 assert.Equal(t, int64(soft), got.Soft, "Wrong soft %s ulimit for docker job. Expect: %d, got: %d", got.Name, soft, got.Soft) 209 assert.Equal(t, int64(hard), got.Hard, "Wrong hard %s ulimit for docker job. Expect: %d, got: %d", got.Name, hard, got.Hard) 210 211 } 212 } 213 } 214 215 func TestDockerDriver_Sysctl_Ulimit_Errors(t *testing.T) { 216 testutil.DockerCompatible(t) 217 brokenConfigs := []map[string]string{ 218 { 219 "nofile": "", 220 }, 221 { 222 "nofile": "abc:1234", 223 }, 224 { 225 "nofile": "1234:abc", 226 }, 227 } 228 229 testCases := []struct { 230 ulimitConfig map[string]string 231 err error 232 }{ 233 {brokenConfigs[0], fmt.Errorf("Malformed ulimit specification nofile: \"\", cannot be empty")}, 234 {brokenConfigs[1], fmt.Errorf("Malformed soft ulimit nofile: abc:1234")}, 235 {brokenConfigs[2], fmt.Errorf("Malformed hard ulimit nofile: 1234:abc")}, 236 } 237 238 for _, tc := range testCases { 239 task, cfg, _ := dockerTask(t) 240 cfg.Ulimit = tc.ulimitConfig 241 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 242 243 d := dockerDriverHarness(t, nil) 244 cleanup := d.MkAllocDir(task, true) 245 defer cleanup() 246 copyImage(t, task.TaskDir(), "busybox.tar") 247 248 _, _, err := d.StartTask(task) 249 require.NotNil(t, err, "Expected non nil error") 250 require.Contains(t, err.Error(), tc.err.Error()) 251 } 252 } 253 254 // This test does not run on Windows due to stricter path validation in the 255 // negative case for non existent mount paths. We should write a similar test 256 // for windows. 257 func TestDockerDriver_BindMountsHonorVolumesEnabledFlag(t *testing.T) { 258 t.Parallel() 259 260 testutil.DockerCompatible(t) 261 262 allocDir := "/tmp/nomad/alloc-dir" 263 264 cases := []struct { 265 name string 266 requiresVolumes bool 267 268 volumeDriver string 269 volumes []string 270 271 expectedVolumes []string 272 }{ 273 { 274 name: "basic plugin", 275 requiresVolumes: true, 276 volumeDriver: "nfs", 277 volumes: []string{"test-path:/tmp/taskpath"}, 278 expectedVolumes: []string{"test-path:/tmp/taskpath"}, 279 }, 280 { 281 name: "absolute default driver", 282 requiresVolumes: true, 283 volumeDriver: "", 284 volumes: []string{"/abs/test-path:/tmp/taskpath"}, 285 expectedVolumes: []string{"/abs/test-path:/tmp/taskpath"}, 286 }, 287 { 288 name: "absolute local driver", 289 requiresVolumes: true, 290 volumeDriver: "local", 291 volumes: []string{"/abs/test-path:/tmp/taskpath"}, 292 expectedVolumes: []string{"/abs/test-path:/tmp/taskpath"}, 293 }, 294 { 295 name: "relative default driver", 296 requiresVolumes: false, 297 volumeDriver: "", 298 volumes: []string{"test-path:/tmp/taskpath"}, 299 expectedVolumes: []string{"/tmp/nomad/alloc-dir/demo/test-path:/tmp/taskpath"}, 300 }, 301 { 302 name: "named volume local driver", 303 requiresVolumes: true, 304 volumeDriver: "local", 305 volumes: []string{"test-path:/tmp/taskpath"}, 306 expectedVolumes: []string{"test-path:/tmp/taskpath"}, 307 }, 308 { 309 name: "relative outside task-dir default driver", 310 requiresVolumes: false, 311 volumeDriver: "", 312 volumes: []string{"../test-path:/tmp/taskpath"}, 313 expectedVolumes: []string{"/tmp/nomad/alloc-dir/test-path:/tmp/taskpath"}, 314 }, 315 { 316 name: "relative outside alloc-dir default driver", 317 requiresVolumes: true, 318 volumeDriver: "", 319 volumes: []string{"../../test-path:/tmp/taskpath"}, 320 expectedVolumes: []string{"/tmp/nomad/test-path:/tmp/taskpath"}, 321 }, 322 { 323 name: "clean path local driver", 324 requiresVolumes: true, 325 volumeDriver: "local", 326 volumes: []string{"/tmp/nomad/../test-path:/tmp/taskpath"}, 327 expectedVolumes: []string{"/tmp/test-path:/tmp/taskpath"}, 328 }, 329 } 330 331 t.Run("with volumes enabled", func(t *testing.T) { 332 dh := dockerDriverHarness(t, nil) 333 driver := dh.Impl().(*Driver) 334 driver.config.Volumes.Enabled = true 335 336 for _, c := range cases { 337 t.Run(c.name, func(t *testing.T) { 338 task, cfg, _ := dockerTask(t) 339 cfg.VolumeDriver = c.volumeDriver 340 cfg.Volumes = c.volumes 341 342 task.AllocDir = allocDir 343 task.Name = "demo" 344 345 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 346 347 cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1") 348 require.NoError(t, err) 349 350 for _, v := range c.expectedVolumes { 351 require.Contains(t, cc.HostConfig.Binds, v) 352 } 353 }) 354 } 355 }) 356 357 t.Run("with volumes disabled", func(t *testing.T) { 358 dh := dockerDriverHarness(t, nil) 359 driver := dh.Impl().(*Driver) 360 driver.config.Volumes.Enabled = false 361 362 for _, c := range cases { 363 t.Run(c.name, func(t *testing.T) { 364 task, cfg, _ := dockerTask(t) 365 cfg.VolumeDriver = c.volumeDriver 366 cfg.Volumes = c.volumes 367 368 task.AllocDir = allocDir 369 task.Name = "demo" 370 371 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 372 373 cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1") 374 if c.requiresVolumes { 375 require.Error(t, err, "volumes are not enabled") 376 } else { 377 require.NoError(t, err) 378 379 for _, v := range c.expectedVolumes { 380 require.Contains(t, cc.HostConfig.Binds, v) 381 } 382 } 383 }) 384 } 385 }) 386 } 387 388 // This test does not run on windows due to differences in the definition of 389 // an absolute path, changing path expansion behaviour. A similar test should 390 // be written for windows. 391 func TestDockerDriver_MountsSerialization(t *testing.T) { 392 t.Parallel() 393 testutil.DockerCompatible(t) 394 395 allocDir := "/tmp/nomad/alloc-dir" 396 397 cases := []struct { 398 name string 399 requiresVolumes bool 400 passedMounts []DockerMount 401 expectedMounts []docker.HostMount 402 }{ 403 { 404 name: "basic volume", 405 passedMounts: []DockerMount{ 406 { 407 Target: "/nomad", 408 ReadOnly: true, 409 Source: "test", 410 }, 411 }, 412 expectedMounts: []docker.HostMount{ 413 { 414 Type: "volume", 415 Target: "/nomad", 416 Source: "test", 417 ReadOnly: true, 418 VolumeOptions: &docker.VolumeOptions{}, 419 }, 420 }, 421 }, 422 { 423 name: "basic bind", 424 passedMounts: []DockerMount{ 425 { 426 Type: "bind", 427 Target: "/nomad", 428 Source: "test", 429 }, 430 }, 431 expectedMounts: []docker.HostMount{ 432 { 433 Type: "bind", 434 Target: "/nomad", 435 Source: "/tmp/nomad/alloc-dir/demo/test", 436 BindOptions: &docker.BindOptions{}, 437 }, 438 }, 439 }, 440 { 441 name: "basic absolute bind", 442 requiresVolumes: true, 443 passedMounts: []DockerMount{ 444 { 445 Type: "bind", 446 Target: "/nomad", 447 Source: "/tmp/test", 448 }, 449 }, 450 expectedMounts: []docker.HostMount{ 451 { 452 Type: "bind", 453 Target: "/nomad", 454 Source: "/tmp/test", 455 BindOptions: &docker.BindOptions{}, 456 }, 457 }, 458 }, 459 { 460 name: "bind relative outside", 461 requiresVolumes: true, 462 passedMounts: []DockerMount{ 463 { 464 Type: "bind", 465 Target: "/nomad", 466 Source: "../../test", 467 }, 468 }, 469 expectedMounts: []docker.HostMount{ 470 { 471 Type: "bind", 472 Target: "/nomad", 473 Source: "/tmp/nomad/test", 474 BindOptions: &docker.BindOptions{}, 475 }, 476 }, 477 }, 478 { 479 name: "basic tmpfs", 480 requiresVolumes: false, 481 passedMounts: []DockerMount{ 482 { 483 Type: "tmpfs", 484 Target: "/nomad", 485 TmpfsOptions: DockerTmpfsOptions{ 486 SizeBytes: 321, 487 Mode: 0666, 488 }, 489 }, 490 }, 491 expectedMounts: []docker.HostMount{ 492 { 493 Type: "tmpfs", 494 Target: "/nomad", 495 TempfsOptions: &docker.TempfsOptions{ 496 SizeBytes: 321, 497 Mode: 0666, 498 }, 499 }, 500 }, 501 }, 502 } 503 504 t.Run("with volumes enabled", func(t *testing.T) { 505 dh := dockerDriverHarness(t, nil) 506 driver := dh.Impl().(*Driver) 507 driver.config.Volumes.Enabled = true 508 509 for _, c := range cases { 510 t.Run(c.name, func(t *testing.T) { 511 task, cfg, _ := dockerTask(t) 512 cfg.Mounts = c.passedMounts 513 514 task.AllocDir = allocDir 515 task.Name = "demo" 516 517 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 518 519 cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1") 520 require.NoError(t, err) 521 require.EqualValues(t, c.expectedMounts, cc.HostConfig.Mounts) 522 }) 523 } 524 }) 525 526 t.Run("with volumes disabled", func(t *testing.T) { 527 dh := dockerDriverHarness(t, nil) 528 driver := dh.Impl().(*Driver) 529 driver.config.Volumes.Enabled = false 530 531 for _, c := range cases { 532 t.Run(c.name, func(t *testing.T) { 533 task, cfg, _ := dockerTask(t) 534 cfg.Mounts = c.passedMounts 535 536 task.AllocDir = allocDir 537 task.Name = "demo" 538 539 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 540 541 cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1") 542 if c.requiresVolumes { 543 require.Error(t, err, "volumes are not enabled") 544 } else { 545 require.NoError(t, err) 546 require.EqualValues(t, c.expectedMounts, cc.HostConfig.Mounts) 547 } 548 }) 549 } 550 }) 551 } 552 553 // TestDockerDriver_CreateContainerConfig_MountsCombined asserts that 554 // devices and mounts set by device managers/plugins are honored 555 // and present in docker.CreateContainerOptions, and that it is appended 556 // to any devices/mounts a user sets in the task config. 557 func TestDockerDriver_CreateContainerConfig_MountsCombined(t *testing.T) { 558 t.Parallel() 559 testutil.DockerCompatible(t) 560 561 task, cfg, _ := dockerTask(t) 562 563 task.Devices = []*drivers.DeviceConfig{ 564 { 565 HostPath: "/dev/fuse", 566 TaskPath: "/container/dev/task-fuse", 567 Permissions: "rw", 568 }, 569 } 570 task.Mounts = []*drivers.MountConfig{ 571 { 572 HostPath: "/tmp/task-mount", 573 TaskPath: "/container/tmp/task-mount", 574 Readonly: true, 575 }, 576 } 577 578 cfg.Devices = []DockerDevice{ 579 { 580 HostPath: "/dev/stdout", 581 ContainerPath: "/container/dev/cfg-stdout", 582 CgroupPermissions: "rwm", 583 }, 584 } 585 cfg.Mounts = []DockerMount{ 586 { 587 Type: "bind", 588 Source: "/tmp/cfg-mount", 589 Target: "/container/tmp/cfg-mount", 590 ReadOnly: false, 591 }, 592 } 593 594 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 595 596 dh := dockerDriverHarness(t, nil) 597 driver := dh.Impl().(*Driver) 598 599 c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1") 600 require.NoError(t, err) 601 602 expectedMounts := []docker.HostMount{ 603 { 604 Type: "bind", 605 Source: "/tmp/cfg-mount", 606 Target: "/container/tmp/cfg-mount", 607 ReadOnly: false, 608 BindOptions: &docker.BindOptions{}, 609 }, 610 { 611 Type: "bind", 612 Source: "/tmp/task-mount", 613 Target: "/container/tmp/task-mount", 614 ReadOnly: true, 615 }, 616 } 617 foundMounts := c.HostConfig.Mounts 618 sort.Slice(foundMounts, func(i, j int) bool { 619 return foundMounts[i].Target < foundMounts[j].Target 620 }) 621 require.EqualValues(t, expectedMounts, foundMounts) 622 623 expectedDevices := []docker.Device{ 624 { 625 PathOnHost: "/dev/stdout", 626 PathInContainer: "/container/dev/cfg-stdout", 627 CgroupPermissions: "rwm", 628 }, 629 { 630 PathOnHost: "/dev/fuse", 631 PathInContainer: "/container/dev/task-fuse", 632 CgroupPermissions: "rw", 633 }, 634 } 635 636 foundDevices := c.HostConfig.Devices 637 sort.Slice(foundDevices, func(i, j int) bool { 638 return foundDevices[i].PathInContainer < foundDevices[j].PathInContainer 639 }) 640 require.EqualValues(t, expectedDevices, foundDevices) 641 } 642 643 // TestDockerDriver_Cleanup ensures Cleanup removes only downloaded images. 644 // Doesn't run on windows because it requires an image variant 645 func TestDockerDriver_Cleanup(t *testing.T) { 646 testutil.DockerCompatible(t) 647 648 // using a small image and an specific point release to avoid accidental conflicts with other tasks 649 cfg := newTaskConfig("", []string{"sleep", "100"}) 650 cfg.Image = "busybox:1.29.2" 651 cfg.LoadImage = "" 652 task := &drivers.TaskConfig{ 653 ID: uuid.Generate(), 654 Name: "cleanup_test", 655 Resources: basicResources, 656 } 657 658 require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) 659 660 client, driver, handle, cleanup := dockerSetup(t, task) 661 defer cleanup() 662 663 require.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second)) 664 // Cleanup 665 require.NoError(t, driver.DestroyTask(task.ID, true)) 666 667 // Ensure image was removed 668 tu.WaitForResult(func() (bool, error) { 669 if _, err := client.InspectImage(cfg.Image); err == nil { 670 return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image) 671 } 672 673 return true, nil 674 }, func(err error) { 675 require.NoError(t, err) 676 }) 677 678 // The image doesn't exist which shouldn't be an error when calling 679 // Cleanup, so call it again to make sure. 680 require.NoError(t, driver.Impl().(*Driver).cleanupImage(handle)) 681 } 682 683 // Tests that images prefixed with "https://" are supported 684 func TestDockerDriver_Start_Image_HTTPS(t *testing.T) { 685 if !tu.IsCI() { 686 t.Parallel() 687 } 688 testutil.DockerCompatible(t) 689 690 taskCfg := TaskConfig{ 691 Image: "https://gcr.io/google_containers/pause:0.8.0", 692 } 693 task := &drivers.TaskConfig{ 694 ID: uuid.Generate(), 695 Name: "pause", 696 AllocID: uuid.Generate(), 697 Resources: basicResources, 698 } 699 require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 700 701 d := dockerDriverHarness(t, nil) 702 cleanup := d.MkAllocDir(task, true) 703 defer cleanup() 704 705 _, _, err := d.StartTask(task) 706 require.NoError(t, err) 707 708 d.DestroyTask(task.ID, true) 709 } 710 711 func newTaskConfig(variant string, command []string) TaskConfig { 712 // busyboxImageID is the ID stored in busybox.tar 713 busyboxImageID := "busybox:1.29.3" 714 715 image := busyboxImageID 716 loadImage := "busybox.tar" 717 if variant != "" { 718 image = fmt.Sprintf("%s-%s", busyboxImageID, variant) 719 loadImage = fmt.Sprintf("busybox_%s.tar", variant) 720 } 721 722 return TaskConfig{ 723 Image: image, 724 LoadImage: loadImage, 725 Command: command[0], 726 Args: command[1:], 727 } 728 } 729 730 func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) { 731 dst := filepath.Join(taskDir.LocalDir, image) 732 copyFile(filepath.Join("./test-resources/docker", image), dst, t) 733 } 734 735 // copyFile moves an existing file to the destination 736 func copyFile(src, dst string, t *testing.T) { 737 in, err := os.Open(src) 738 if err != nil { 739 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 740 } 741 defer in.Close() 742 out, err := os.Create(dst) 743 if err != nil { 744 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 745 } 746 defer func() { 747 if err := out.Close(); err != nil { 748 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 749 } 750 }() 751 if _, err = io.Copy(out, in); err != nil { 752 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 753 } 754 if err := out.Sync(); err != nil { 755 t.Fatalf("copying %v -> %v failed: %v", src, dst, err) 756 } 757 } 758 759 func TestDocker_ExecTaskStreaming(t *testing.T) { 760 if !tu.IsCI() { 761 t.Parallel() 762 } 763 testutil.DockerCompatible(t) 764 765 taskCfg := newTaskConfig("", []string{"/bin/sleep", "1000"}) 766 task := &drivers.TaskConfig{ 767 ID: uuid.Generate(), 768 Name: "nc-demo", 769 AllocID: uuid.Generate(), 770 Resources: basicResources, 771 } 772 require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg)) 773 774 d := dockerDriverHarness(t, nil) 775 cleanup := d.MkAllocDir(task, true) 776 defer cleanup() 777 copyImage(t, task.TaskDir(), "busybox.tar") 778 779 _, _, err := d.StartTask(task) 780 require.NoError(t, err) 781 782 defer d.DestroyTask(task.ID, true) 783 784 dtestutil.ExecTaskStreamingConformanceTests(t, d, task.ID) 785 786 }