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