github.com/ilhicas/nomad@v0.11.8/drivers/docker/driver_test.go (about)

     1  package docker
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math/rand"
     8  	"path/filepath"
     9  	"reflect"
    10  	"runtime"
    11  	"runtime/debug"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	docker "github.com/fsouza/go-dockerclient"
    17  	hclog "github.com/hashicorp/go-hclog"
    18  	"github.com/hashicorp/nomad/client/taskenv"
    19  	"github.com/hashicorp/nomad/client/testutil"
    20  	"github.com/hashicorp/nomad/devices/gpu/nvidia"
    21  	"github.com/hashicorp/nomad/helper/freeport"
    22  	"github.com/hashicorp/nomad/helper/pluginutils/hclspecutils"
    23  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    24  	"github.com/hashicorp/nomad/helper/pluginutils/loader"
    25  	"github.com/hashicorp/nomad/helper/testlog"
    26  	"github.com/hashicorp/nomad/helper/uuid"
    27  	"github.com/hashicorp/nomad/nomad/structs"
    28  	"github.com/hashicorp/nomad/plugins/base"
    29  	"github.com/hashicorp/nomad/plugins/drivers"
    30  	dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
    31  	tu "github.com/hashicorp/nomad/testutil"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  var (
    37  	basicResources = &drivers.Resources{
    38  		NomadResources: &structs.AllocatedTaskResources{
    39  			Memory: structs.AllocatedMemoryResources{
    40  				MemoryMB: 256,
    41  			},
    42  			Cpu: structs.AllocatedCpuResources{
    43  				CpuShares: 250,
    44  			},
    45  		},
    46  		LinuxResources: &drivers.LinuxResources{
    47  			CPUShares:        512,
    48  			MemoryLimitBytes: 256 * 1024 * 1024,
    49  		},
    50  	}
    51  )
    52  
    53  func dockerIsRemote(t *testing.T) bool {
    54  	client, err := docker.NewClientFromEnv()
    55  	if err != nil {
    56  		return false
    57  	}
    58  
    59  	// Technically this could be a local tcp socket but for testing purposes
    60  	// we'll just assume that tcp is only used for remote connections.
    61  	if client.Endpoint()[0:3] == "tcp" {
    62  		return true
    63  	}
    64  	return false
    65  }
    66  
    67  var (
    68  	// busyboxLongRunningCmd is a busybox command that runs indefinitely, and
    69  	// ideally responds to SIGINT/SIGTERM.  Sadly, busybox:1.29.3 /bin/sleep doesn't.
    70  	busyboxLongRunningCmd = []string{"nc", "-l", "-p", "3000", "127.0.0.1"}
    71  )
    72  
    73  // Returns a task with a reserved and dynamic port. The ports are returned
    74  // respectively, and should be reclaimed with freeport.Return at the end of a test.
    75  func dockerTask(t *testing.T) (*drivers.TaskConfig, *TaskConfig, []int) {
    76  	ports := freeport.MustTake(2)
    77  	dockerReserved := ports[0]
    78  	dockerDynamic := ports[1]
    79  
    80  	cfg := newTaskConfig("", busyboxLongRunningCmd)
    81  	task := &drivers.TaskConfig{
    82  		ID:      uuid.Generate(),
    83  		Name:    "redis-demo",
    84  		AllocID: uuid.Generate(),
    85  		Env: map[string]string{
    86  			"test": t.Name(),
    87  		},
    88  		DeviceEnv: make(map[string]string),
    89  		Resources: &drivers.Resources{
    90  			NomadResources: &structs.AllocatedTaskResources{
    91  				Memory: structs.AllocatedMemoryResources{
    92  					MemoryMB: 256,
    93  				},
    94  				Cpu: structs.AllocatedCpuResources{
    95  					CpuShares: 512,
    96  				},
    97  				Networks: []*structs.NetworkResource{
    98  					{
    99  						IP:            "127.0.0.1",
   100  						ReservedPorts: []structs.Port{{Label: "main", Value: dockerReserved}},
   101  						DynamicPorts:  []structs.Port{{Label: "REDIS", Value: dockerDynamic}},
   102  					},
   103  				},
   104  			},
   105  			LinuxResources: &drivers.LinuxResources{
   106  				CPUShares:        512,
   107  				MemoryLimitBytes: 256 * 1024 * 1024,
   108  				PercentTicks:     float64(512) / float64(4096),
   109  			},
   110  		},
   111  	}
   112  
   113  	require.NoError(t, task.EncodeConcreteDriverConfig(&cfg))
   114  
   115  	return task, &cfg, ports
   116  }
   117  
   118  // dockerSetup does all of the basic setup you need to get a running docker
   119  // process up and running for testing. Use like:
   120  //
   121  //	task := taskTemplate()
   122  //	// do custom task configuration
   123  //	client, handle, cleanup := dockerSetup(t, task, nil)
   124  //	defer cleanup()
   125  //	// do test stuff
   126  //
   127  // If there is a problem during setup this function will abort or skip the test
   128  // and indicate the reason.
   129  func dockerSetup(t *testing.T, task *drivers.TaskConfig, driverCfg map[string]interface{}) (*docker.Client, *dtestutil.DriverHarness, *taskHandle, func()) {
   130  	client := newTestDockerClient(t)
   131  	driver := dockerDriverHarness(t, driverCfg)
   132  	cleanup := driver.MkAllocDir(task, true)
   133  
   134  	copyImage(t, task.TaskDir(), "busybox.tar")
   135  	_, _, err := driver.StartTask(task)
   136  	require.NoError(t, err)
   137  
   138  	dockerDriver, ok := driver.Impl().(*Driver)
   139  	require.True(t, ok)
   140  	handle, ok := dockerDriver.tasks.Get(task.ID)
   141  	require.True(t, ok)
   142  
   143  	return client, driver, handle, func() {
   144  		driver.DestroyTask(task.ID, true)
   145  		cleanup()
   146  	}
   147  }
   148  
   149  // cleanSlate removes the specified docker image, including potentially stopping/removing any
   150  // containers based on that image. This is used to decouple tests that would be coupled
   151  // by using the same container image.
   152  func cleanSlate(client *docker.Client, imageID string) {
   153  	if img, _ := client.InspectImage(imageID); img == nil {
   154  		return
   155  	}
   156  	containers, _ := client.ListContainers(docker.ListContainersOptions{
   157  		All: true,
   158  		Filters: map[string][]string{
   159  			"ancestor": {imageID},
   160  		},
   161  	})
   162  	for _, c := range containers {
   163  		client.RemoveContainer(docker.RemoveContainerOptions{
   164  			Force: true,
   165  			ID:    c.ID,
   166  		})
   167  	}
   168  	client.RemoveImageExtended(imageID, docker.RemoveImageOptions{
   169  		Force: true,
   170  	})
   171  	return
   172  }
   173  
   174  // dockerDriverHarness wires up everything needed to launch a task with a docker driver.
   175  // A driver plugin interface and cleanup function is returned
   176  func dockerDriverHarness(t *testing.T, cfg map[string]interface{}) *dtestutil.DriverHarness {
   177  	logger := testlog.HCLogger(t)
   178  	ctx, cancel := context.WithCancel(context.Background())
   179  	t.Cleanup(func() { cancel() })
   180  	harness := dtestutil.NewDriverHarness(t, NewDockerDriver(ctx, logger))
   181  	if cfg == nil {
   182  		cfg = map[string]interface{}{
   183  			"gc": map[string]interface{}{
   184  				"image":       false,
   185  				"image_delay": "1s",
   186  			},
   187  		}
   188  	}
   189  	plugLoader, err := loader.NewPluginLoader(&loader.PluginLoaderConfig{
   190  		Logger:            logger,
   191  		PluginDir:         "./plugins",
   192  		SupportedVersions: loader.AgentSupportedApiVersions,
   193  		InternalPlugins: map[loader.PluginID]*loader.InternalPluginConfig{
   194  			PluginID: {
   195  				Config: cfg,
   196  				Factory: func(context.Context, hclog.Logger) interface{} {
   197  					return harness
   198  				},
   199  			},
   200  		},
   201  	})
   202  
   203  	require.NoError(t, err)
   204  	instance, err := plugLoader.Dispense(pluginName, base.PluginTypeDriver, nil, logger)
   205  	require.NoError(t, err)
   206  	driver, ok := instance.Plugin().(*dtestutil.DriverHarness)
   207  	if !ok {
   208  		t.Fatal("plugin instance is not a driver... wat?")
   209  	}
   210  
   211  	return driver
   212  }
   213  
   214  func newTestDockerClient(t *testing.T) *docker.Client {
   215  	t.Helper()
   216  	testutil.DockerCompatible(t)
   217  
   218  	client, err := docker.NewClientFromEnv()
   219  	if err != nil {
   220  		t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack())
   221  	}
   222  	return client
   223  }
   224  
   225  /*
   226  // This test should always pass, even if docker daemon is not available
   227  func TestDockerDriver_Fingerprint(t *testing.T) {
   228  	if !tu.IsCI() {
   229  		t.Parallel()
   230  	}
   231  
   232  	ctx := testDockerDriverContexts(t, &structs.Task{Name: "foo", Driver: "docker", Resources: basicResources})
   233  	//ctx.DriverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   234  	defer ctx.Destroy()
   235  	d := NewDockerDriver(ctx.DriverCtx)
   236  	node := &structs.Node{
   237  		Attributes: make(map[string]string),
   238  	}
   239  
   240  	request := &fingerprint.FingerprintRequest{Config: &config.Config{}, Node: node}
   241  	var response fingerprint.FingerprintResponse
   242  	err := d.Fingerprint(request, &response)
   243  	if err != nil {
   244  		t.Fatalf("err: %v", err)
   245  	}
   246  
   247  	attributes := response.Attributes
   248  	if testutil.DockerIsConnected(t) && attributes["driver.docker"] == "" {
   249  		t.Fatalf("Fingerprinter should detect when docker is available")
   250  	}
   251  
   252  	if attributes["driver.docker"] != "1" {
   253  		t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.")
   254  	} else {
   255  
   256  		// if docker is available, make sure that the response is tagged as
   257  		// applicable
   258  		if !response.Detected {
   259  			t.Fatalf("expected response to be applicable")
   260  		}
   261  	}
   262  
   263  	t.Logf("Found docker version %s", attributes["driver.docker.version"])
   264  }
   265  
   266  // TestDockerDriver_Fingerprint_Bridge asserts that if Docker is running we set
   267  // the bridge network's IP as a node attribute. See #2785
   268  func TestDockerDriver_Fingerprint_Bridge(t *testing.T) {
   269  	if !tu.IsCI() {
   270  		t.Parallel()
   271  	}
   272  	testutil.DockerCompatible(t)
   273  	if runtime.GOOS != "linux" {
   274  		t.Skip("expect only on linux")
   275  	}
   276  
   277  	// This seems fragile, so we might need to reconsider this test if it
   278  	// proves flaky
   279  	expectedAddr, err := sockaddr.GetInterfaceIP("docker0")
   280  	if err != nil {
   281  		t.Fatalf("unable to get ip for docker0: %v", err)
   282  	}
   283  	if expectedAddr == "" {
   284  		t.Fatalf("unable to get ip for docker bridge")
   285  	}
   286  
   287  	conf := testConfig(t)
   288  	conf.Node = mock.Node()
   289  	dd := NewDockerDriver(NewDriverContext("", "", "", "", conf, conf.Node, testlog.Logger(t), nil))
   290  
   291  	request := &fingerprint.FingerprintRequest{Config: conf, Node: conf.Node}
   292  	var response fingerprint.FingerprintResponse
   293  
   294  	err = dd.Fingerprint(request, &response)
   295  	if err != nil {
   296  		t.Fatalf("error fingerprinting docker: %v", err)
   297  	}
   298  
   299  	if !response.Detected {
   300  		t.Fatalf("expected response to be applicable")
   301  	}
   302  
   303  	attributes := response.Attributes
   304  	if attributes == nil {
   305  		t.Fatalf("expected attributes to be set")
   306  	}
   307  
   308  	if attributes["driver.docker"] == "" {
   309  		t.Fatalf("expected Docker to be enabled but false was returned")
   310  	}
   311  
   312  	if found := attributes["driver.docker.bridge_ip"]; found != expectedAddr {
   313  		t.Fatalf("expected bridge ip %q but found: %q", expectedAddr, found)
   314  	}
   315  	t.Logf("docker bridge ip: %q", attributes["driver.docker.bridge_ip"])
   316  }
   317  
   318  func TestDockerDriver_Check_DockerHealthStatus(t *testing.T) {
   319  	if !tu.IsCI() {
   320  		t.Parallel()
   321  	}
   322  	testutil.DockerCompatible(t)
   323  	if runtime.GOOS != "linux" {
   324  		t.Skip("expect only on linux")
   325  	}
   326  
   327  	require := require.New(t)
   328  
   329  	expectedAddr, err := sockaddr.GetInterfaceIP("docker0")
   330  	if err != nil {
   331  		t.Fatalf("unable to get ip for docker0: %v", err)
   332  	}
   333  	if expectedAddr == "" {
   334  		t.Fatalf("unable to get ip for docker bridge")
   335  	}
   336  
   337  	conf := testConfig(t)
   338  	conf.Node = mock.Node()
   339  	dd := NewDockerDriver(NewDriverContext("", "", "", "", conf, conf.Node, testlog.Logger(t), nil))
   340  
   341  	request := &cstructs.HealthCheckRequest{}
   342  	var response cstructs.HealthCheckResponse
   343  
   344  	dc, ok := dd.(fingerprint.HealthCheck)
   345  	require.True(ok)
   346  	err = dc.HealthCheck(request, &response)
   347  	require.Nil(err)
   348  
   349  	driverInfo := response.Drivers["docker"]
   350  	require.NotNil(driverInfo)
   351  	require.True(driverInfo.Healthy)
   352  }*/
   353  
   354  func TestDockerDriver_Start_Wait(t *testing.T) {
   355  	if !tu.IsCI() {
   356  		t.Parallel()
   357  	}
   358  	testutil.DockerCompatible(t)
   359  
   360  	taskCfg := newTaskConfig("", busyboxLongRunningCmd)
   361  	task := &drivers.TaskConfig{
   362  		ID:        uuid.Generate(),
   363  		Name:      "nc-demo",
   364  		AllocID:   uuid.Generate(),
   365  		Resources: basicResources,
   366  	}
   367  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   368  
   369  	d := dockerDriverHarness(t, nil)
   370  	cleanup := d.MkAllocDir(task, true)
   371  	defer cleanup()
   372  	copyImage(t, task.TaskDir(), "busybox.tar")
   373  
   374  	_, _, err := d.StartTask(task)
   375  	require.NoError(t, err)
   376  
   377  	defer d.DestroyTask(task.ID, true)
   378  
   379  	// Attempt to wait
   380  	waitCh, err := d.WaitTask(context.Background(), task.ID)
   381  	require.NoError(t, err)
   382  
   383  	select {
   384  	case <-waitCh:
   385  		t.Fatalf("wait channel should not have received an exit result")
   386  	case <-time.After(time.Duration(tu.TestMultiplier()*1) * time.Second):
   387  	}
   388  }
   389  
   390  func TestDockerDriver_Start_WaitFinish(t *testing.T) {
   391  	if !tu.IsCI() {
   392  		t.Parallel()
   393  	}
   394  	testutil.DockerCompatible(t)
   395  
   396  	taskCfg := newTaskConfig("", []string{"echo", "hello"})
   397  	task := &drivers.TaskConfig{
   398  		ID:        uuid.Generate(),
   399  		Name:      "nc-demo",
   400  		AllocID:   uuid.Generate(),
   401  		Resources: basicResources,
   402  	}
   403  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   404  
   405  	d := dockerDriverHarness(t, nil)
   406  	cleanup := d.MkAllocDir(task, true)
   407  	defer cleanup()
   408  	copyImage(t, task.TaskDir(), "busybox.tar")
   409  
   410  	_, _, err := d.StartTask(task)
   411  	require.NoError(t, err)
   412  
   413  	defer d.DestroyTask(task.ID, true)
   414  
   415  	// Attempt to wait
   416  	waitCh, err := d.WaitTask(context.Background(), task.ID)
   417  	require.NoError(t, err)
   418  
   419  	select {
   420  	case res := <-waitCh:
   421  		if !res.Successful() {
   422  			require.Fail(t, "ExitResult should be successful: %v", res)
   423  		}
   424  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   425  		require.Fail(t, "timeout")
   426  	}
   427  }
   428  
   429  // TestDockerDriver_Start_StoppedContainer asserts that Nomad will detect a
   430  // stopped task container, remove it, and start a new container.
   431  //
   432  // See https://github.com/hashicorp/nomad/issues/3419
   433  func TestDockerDriver_Start_StoppedContainer(t *testing.T) {
   434  	if !tu.IsCI() {
   435  		t.Parallel()
   436  	}
   437  	testutil.DockerCompatible(t)
   438  
   439  	taskCfg := newTaskConfig("", []string{"sleep", "9001"})
   440  	task := &drivers.TaskConfig{
   441  		ID:        uuid.Generate(),
   442  		Name:      "nc-demo",
   443  		AllocID:   uuid.Generate(),
   444  		Resources: basicResources,
   445  	}
   446  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   447  
   448  	d := dockerDriverHarness(t, nil)
   449  	cleanup := d.MkAllocDir(task, true)
   450  	defer cleanup()
   451  	copyImage(t, task.TaskDir(), "busybox.tar")
   452  
   453  	client := newTestDockerClient(t)
   454  
   455  	var imageID string
   456  	var err error
   457  
   458  	if runtime.GOOS != "windows" {
   459  		imageID, err = d.Impl().(*Driver).loadImage(task, &taskCfg, client)
   460  	} else {
   461  		image, lErr := client.InspectImage("hashicorpnomad/busybox-windows:server2016-0.1")
   462  		err = lErr
   463  		if image != nil {
   464  			imageID = image.ID
   465  		}
   466  	}
   467  	require.NoError(t, err)
   468  	require.NotEmpty(t, imageID)
   469  
   470  	// Create a container of the same name but don't start it. This mimics
   471  	// the case of dockerd getting restarted and stopping containers while
   472  	// Nomad is watching them.
   473  	opts := docker.CreateContainerOptions{
   474  		Name: strings.Replace(task.ID, "/", "_", -1),
   475  		Config: &docker.Config{
   476  			Image: taskCfg.Image,
   477  			Cmd:   []string{"sleep", "9000"},
   478  			Env:   []string{fmt.Sprintf("test=%s", t.Name())},
   479  		},
   480  	}
   481  
   482  	if _, err := client.CreateContainer(opts); err != nil {
   483  		t.Fatalf("error creating initial container: %v", err)
   484  	}
   485  
   486  	_, _, err = d.StartTask(task)
   487  	defer d.DestroyTask(task.ID, true)
   488  	require.NoError(t, err)
   489  
   490  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
   491  	require.NoError(t, d.DestroyTask(task.ID, true))
   492  }
   493  
   494  func TestDockerDriver_Start_LoadImage(t *testing.T) {
   495  	if !tu.IsCI() {
   496  		t.Parallel()
   497  	}
   498  	testutil.DockerCompatible(t)
   499  
   500  	taskCfg := newTaskConfig("", []string{"sh", "-c", "echo hello > $NOMAD_TASK_DIR/output"})
   501  	task := &drivers.TaskConfig{
   502  		ID:        uuid.Generate(),
   503  		Name:      "busybox-demo",
   504  		AllocID:   uuid.Generate(),
   505  		Resources: basicResources,
   506  	}
   507  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   508  
   509  	d := dockerDriverHarness(t, nil)
   510  	cleanup := d.MkAllocDir(task, true)
   511  	defer cleanup()
   512  	copyImage(t, task.TaskDir(), "busybox.tar")
   513  
   514  	_, _, err := d.StartTask(task)
   515  	require.NoError(t, err)
   516  
   517  	defer d.DestroyTask(task.ID, true)
   518  
   519  	waitCh, err := d.WaitTask(context.Background(), task.ID)
   520  	require.NoError(t, err)
   521  	select {
   522  	case res := <-waitCh:
   523  		if !res.Successful() {
   524  			require.Fail(t, "ExitResult should be successful: %v", res)
   525  		}
   526  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   527  		require.Fail(t, "timeout")
   528  	}
   529  
   530  	// Check that data was written to the shared alloc directory.
   531  	outputFile := filepath.Join(task.TaskDir().LocalDir, "output")
   532  	act, err := ioutil.ReadFile(outputFile)
   533  	if err != nil {
   534  		t.Fatalf("Couldn't read expected output: %v", err)
   535  	}
   536  
   537  	exp := "hello"
   538  	if strings.TrimSpace(string(act)) != exp {
   539  		t.Fatalf("Command outputted %v; want %v", act, exp)
   540  	}
   541  
   542  }
   543  
   544  // Tests that starting a task without an image fails
   545  func TestDockerDriver_Start_NoImage(t *testing.T) {
   546  	if !tu.IsCI() {
   547  		t.Parallel()
   548  	}
   549  	testutil.DockerCompatible(t)
   550  
   551  	taskCfg := TaskConfig{
   552  		Command: "echo",
   553  		Args:    []string{"foo"},
   554  	}
   555  	task := &drivers.TaskConfig{
   556  		ID:        uuid.Generate(),
   557  		Name:      "echo",
   558  		AllocID:   uuid.Generate(),
   559  		Resources: basicResources,
   560  	}
   561  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   562  
   563  	d := dockerDriverHarness(t, nil)
   564  	cleanup := d.MkAllocDir(task, false)
   565  	defer cleanup()
   566  
   567  	_, _, err := d.StartTask(task)
   568  	require.Error(t, err)
   569  	require.Contains(t, err.Error(), "image name required")
   570  
   571  	d.DestroyTask(task.ID, true)
   572  }
   573  
   574  func TestDockerDriver_Start_BadPull_Recoverable(t *testing.T) {
   575  	if !tu.IsCI() {
   576  		t.Parallel()
   577  	}
   578  	testutil.DockerCompatible(t)
   579  
   580  	taskCfg := TaskConfig{
   581  		Image:   "127.0.0.1:32121/foo", // bad path
   582  		Command: "echo",
   583  		Args: []string{
   584  			"hello",
   585  		},
   586  	}
   587  	task := &drivers.TaskConfig{
   588  		ID:        uuid.Generate(),
   589  		Name:      "busybox-demo",
   590  		AllocID:   uuid.Generate(),
   591  		Resources: basicResources,
   592  	}
   593  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   594  
   595  	d := dockerDriverHarness(t, nil)
   596  	cleanup := d.MkAllocDir(task, true)
   597  	defer cleanup()
   598  
   599  	_, _, err := d.StartTask(task)
   600  	require.Error(t, err)
   601  
   602  	defer d.DestroyTask(task.ID, true)
   603  
   604  	if rerr, ok := err.(*structs.RecoverableError); !ok {
   605  		t.Fatalf("want recoverable error: %+v", err)
   606  	} else if !rerr.IsRecoverable() {
   607  		t.Fatalf("error not recoverable: %+v", err)
   608  	}
   609  }
   610  
   611  func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
   612  	if !tu.IsCI() {
   613  		t.Parallel()
   614  	}
   615  	// This test requires that the alloc dir be mounted into docker as a volume.
   616  	// Because this cannot happen when docker is run remotely, e.g. when running
   617  	// docker in a VM, we skip this when we detect Docker is being run remotely.
   618  	if !testutil.DockerIsConnected(t) || dockerIsRemote(t) {
   619  		t.Skip("Docker not connected")
   620  	}
   621  
   622  	exp := []byte{'w', 'i', 'n'}
   623  	file := "output.txt"
   624  
   625  	taskCfg := newTaskConfig("", []string{
   626  		"sh",
   627  		"-c",
   628  		fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`,
   629  			string(exp), taskenv.AllocDir, file),
   630  	})
   631  	task := &drivers.TaskConfig{
   632  		ID:        uuid.Generate(),
   633  		Name:      "busybox-demo",
   634  		AllocID:   uuid.Generate(),
   635  		Resources: basicResources,
   636  	}
   637  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   638  
   639  	d := dockerDriverHarness(t, nil)
   640  	cleanup := d.MkAllocDir(task, true)
   641  	defer cleanup()
   642  	copyImage(t, task.TaskDir(), "busybox.tar")
   643  
   644  	_, _, err := d.StartTask(task)
   645  	require.NoError(t, err)
   646  
   647  	defer d.DestroyTask(task.ID, true)
   648  
   649  	// Attempt to wait
   650  	waitCh, err := d.WaitTask(context.Background(), task.ID)
   651  	require.NoError(t, err)
   652  
   653  	select {
   654  	case res := <-waitCh:
   655  		if !res.Successful() {
   656  			require.Fail(t, fmt.Sprintf("ExitResult should be successful: %v", res))
   657  		}
   658  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   659  		require.Fail(t, "timeout")
   660  	}
   661  
   662  	// Check that data was written to the shared alloc directory.
   663  	outputFile := filepath.Join(task.TaskDir().SharedAllocDir, file)
   664  	act, err := ioutil.ReadFile(outputFile)
   665  	if err != nil {
   666  		t.Fatalf("Couldn't read expected output: %v", err)
   667  	}
   668  
   669  	if !reflect.DeepEqual(act, exp) {
   670  		t.Fatalf("Command outputted %v; want %v", act, exp)
   671  	}
   672  }
   673  
   674  func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
   675  	if !tu.IsCI() {
   676  		t.Parallel()
   677  	}
   678  	testutil.DockerCompatible(t)
   679  
   680  	taskCfg := newTaskConfig("", busyboxLongRunningCmd)
   681  	task := &drivers.TaskConfig{
   682  		ID:        uuid.Generate(),
   683  		Name:      "busybox-demo",
   684  		AllocID:   uuid.Generate(),
   685  		Resources: basicResources,
   686  	}
   687  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   688  
   689  	d := dockerDriverHarness(t, nil)
   690  	cleanup := d.MkAllocDir(task, true)
   691  	defer cleanup()
   692  	copyImage(t, task.TaskDir(), "busybox.tar")
   693  
   694  	_, _, err := d.StartTask(task)
   695  	require.NoError(t, err)
   696  
   697  	defer d.DestroyTask(task.ID, true)
   698  
   699  	go func(t *testing.T) {
   700  		time.Sleep(100 * time.Millisecond)
   701  		signal := "SIGINT"
   702  		if runtime.GOOS == "windows" {
   703  			signal = "SIGKILL"
   704  		}
   705  		require.NoError(t, d.StopTask(task.ID, time.Second, signal))
   706  	}(t)
   707  
   708  	// Attempt to wait
   709  	waitCh, err := d.WaitTask(context.Background(), task.ID)
   710  	require.NoError(t, err)
   711  
   712  	select {
   713  	case res := <-waitCh:
   714  		if res.Successful() {
   715  			require.Fail(t, "ExitResult should err: %v", res)
   716  		}
   717  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   718  		require.Fail(t, "timeout")
   719  	}
   720  }
   721  
   722  func TestDockerDriver_Start_KillTimeout(t *testing.T) {
   723  	if !tu.IsCI() {
   724  		t.Parallel()
   725  	}
   726  	testutil.DockerCompatible(t)
   727  
   728  	if runtime.GOOS == "windows" {
   729  		t.Skip("Windows Docker does not support SIGUSR1")
   730  	}
   731  
   732  	timeout := 2 * time.Second
   733  	taskCfg := newTaskConfig("", []string{"sleep", "10"})
   734  	task := &drivers.TaskConfig{
   735  		ID:        uuid.Generate(),
   736  		Name:      "busybox-demo",
   737  		AllocID:   uuid.Generate(),
   738  		Resources: basicResources,
   739  	}
   740  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   741  
   742  	d := dockerDriverHarness(t, nil)
   743  	cleanup := d.MkAllocDir(task, true)
   744  	defer cleanup()
   745  	copyImage(t, task.TaskDir(), "busybox.tar")
   746  
   747  	_, _, err := d.StartTask(task)
   748  	require.NoError(t, err)
   749  
   750  	defer d.DestroyTask(task.ID, true)
   751  
   752  	var killSent time.Time
   753  	go func() {
   754  		time.Sleep(100 * time.Millisecond)
   755  		killSent = time.Now()
   756  		require.NoError(t, d.StopTask(task.ID, timeout, "SIGUSR1"))
   757  	}()
   758  
   759  	// Attempt to wait
   760  	waitCh, err := d.WaitTask(context.Background(), task.ID)
   761  	require.NoError(t, err)
   762  
   763  	var killed time.Time
   764  	select {
   765  	case <-waitCh:
   766  		killed = time.Now()
   767  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   768  		require.Fail(t, "timeout")
   769  	}
   770  
   771  	require.True(t, killed.Sub(killSent) > timeout)
   772  }
   773  
   774  func TestDockerDriver_StartN(t *testing.T) {
   775  	if runtime.GOOS == "windows" {
   776  		t.Skip("Windows Docker does not support SIGINT")
   777  	}
   778  	if !tu.IsCI() {
   779  		t.Parallel()
   780  	}
   781  	testutil.DockerCompatible(t)
   782  	require := require.New(t)
   783  
   784  	task1, _, ports1 := dockerTask(t)
   785  	defer freeport.Return(ports1)
   786  
   787  	task2, _, ports2 := dockerTask(t)
   788  	defer freeport.Return(ports2)
   789  
   790  	task3, _, ports3 := dockerTask(t)
   791  	defer freeport.Return(ports3)
   792  
   793  	taskList := []*drivers.TaskConfig{task1, task2, task3}
   794  
   795  	t.Logf("Starting %d tasks", len(taskList))
   796  
   797  	d := dockerDriverHarness(t, nil)
   798  	// Let's spin up a bunch of things
   799  	for _, task := range taskList {
   800  		cleanup := d.MkAllocDir(task, true)
   801  		defer cleanup()
   802  		copyImage(t, task.TaskDir(), "busybox.tar")
   803  		_, _, err := d.StartTask(task)
   804  		require.NoError(err)
   805  
   806  	}
   807  
   808  	defer d.DestroyTask(task3.ID, true)
   809  	defer d.DestroyTask(task2.ID, true)
   810  	defer d.DestroyTask(task1.ID, true)
   811  
   812  	t.Log("All tasks are started. Terminating...")
   813  	for _, task := range taskList {
   814  		require.NoError(d.StopTask(task.ID, time.Second, "SIGINT"))
   815  
   816  		// Attempt to wait
   817  		waitCh, err := d.WaitTask(context.Background(), task.ID)
   818  		require.NoError(err)
   819  
   820  		select {
   821  		case <-waitCh:
   822  		case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   823  			require.Fail("timeout waiting on task")
   824  		}
   825  	}
   826  
   827  	t.Log("Test complete!")
   828  }
   829  
   830  func TestDockerDriver_StartNVersions(t *testing.T) {
   831  	if runtime.GOOS == "windows" {
   832  		t.Skip("Skipped on windows, we don't have image variants available")
   833  	}
   834  	if !tu.IsCI() {
   835  		t.Parallel()
   836  	}
   837  	testutil.DockerCompatible(t)
   838  	require := require.New(t)
   839  
   840  	task1, cfg1, ports1 := dockerTask(t)
   841  	defer freeport.Return(ports1)
   842  	tcfg1 := newTaskConfig("", []string{"echo", "hello"})
   843  	cfg1.Image = tcfg1.Image
   844  	cfg1.LoadImage = tcfg1.LoadImage
   845  	require.NoError(task1.EncodeConcreteDriverConfig(cfg1))
   846  
   847  	task2, cfg2, ports2 := dockerTask(t)
   848  	defer freeport.Return(ports2)
   849  	tcfg2 := newTaskConfig("musl", []string{"echo", "hello"})
   850  	cfg2.Image = tcfg2.Image
   851  	cfg2.LoadImage = tcfg2.LoadImage
   852  	require.NoError(task2.EncodeConcreteDriverConfig(cfg2))
   853  
   854  	task3, cfg3, ports3 := dockerTask(t)
   855  	defer freeport.Return(ports3)
   856  	tcfg3 := newTaskConfig("glibc", []string{"echo", "hello"})
   857  	cfg3.Image = tcfg3.Image
   858  	cfg3.LoadImage = tcfg3.LoadImage
   859  	require.NoError(task3.EncodeConcreteDriverConfig(cfg3))
   860  
   861  	taskList := []*drivers.TaskConfig{task1, task2, task3}
   862  
   863  	t.Logf("Starting %d tasks", len(taskList))
   864  	d := dockerDriverHarness(t, nil)
   865  
   866  	// Let's spin up a bunch of things
   867  	for _, task := range taskList {
   868  		cleanup := d.MkAllocDir(task, true)
   869  		defer cleanup()
   870  		copyImage(t, task.TaskDir(), "busybox.tar")
   871  		copyImage(t, task.TaskDir(), "busybox_musl.tar")
   872  		copyImage(t, task.TaskDir(), "busybox_glibc.tar")
   873  		_, _, err := d.StartTask(task)
   874  		require.NoError(err)
   875  
   876  		require.NoError(d.WaitUntilStarted(task.ID, 5*time.Second))
   877  	}
   878  
   879  	defer d.DestroyTask(task3.ID, true)
   880  	defer d.DestroyTask(task2.ID, true)
   881  	defer d.DestroyTask(task1.ID, true)
   882  
   883  	t.Log("All tasks are started. Terminating...")
   884  	for _, task := range taskList {
   885  		require.NoError(d.StopTask(task.ID, time.Second, "SIGINT"))
   886  
   887  		// Attempt to wait
   888  		waitCh, err := d.WaitTask(context.Background(), task.ID)
   889  		require.NoError(err)
   890  
   891  		select {
   892  		case <-waitCh:
   893  		case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   894  			require.Fail("timeout waiting on task")
   895  		}
   896  	}
   897  
   898  	t.Log("Test complete!")
   899  }
   900  
   901  func TestDockerDriver_Labels(t *testing.T) {
   902  	if !tu.IsCI() {
   903  		t.Parallel()
   904  	}
   905  	testutil.DockerCompatible(t)
   906  
   907  	task, cfg, ports := dockerTask(t)
   908  	defer freeport.Return(ports)
   909  
   910  	cfg.Labels = map[string]string{
   911  		"label1": "value1",
   912  		"label2": "value2",
   913  	}
   914  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   915  
   916  	client, d, handle, cleanup := dockerSetup(t, task, nil)
   917  	defer cleanup()
   918  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
   919  
   920  	container, err := client.InspectContainer(handle.containerID)
   921  	if err != nil {
   922  		t.Fatalf("err: %v", err)
   923  	}
   924  
   925  	// expect to see 1 additional standard labels
   926  	require.Equal(t, len(cfg.Labels)+1, len(container.Config.Labels))
   927  	for k, v := range cfg.Labels {
   928  		require.Equal(t, v, container.Config.Labels[k])
   929  	}
   930  }
   931  
   932  func TestDockerDriver_ForcePull(t *testing.T) {
   933  	if !tu.IsCI() {
   934  		t.Parallel()
   935  	}
   936  	testutil.DockerCompatible(t)
   937  
   938  	task, cfg, ports := dockerTask(t)
   939  	defer freeport.Return(ports)
   940  
   941  	cfg.ForcePull = true
   942  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   943  
   944  	client, d, handle, cleanup := dockerSetup(t, task, nil)
   945  	defer cleanup()
   946  
   947  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
   948  
   949  	_, err := client.InspectContainer(handle.containerID)
   950  	if err != nil {
   951  		t.Fatalf("err: %v", err)
   952  	}
   953  }
   954  
   955  func TestDockerDriver_ForcePull_RepoDigest(t *testing.T) {
   956  	if runtime.GOOS == "windows" {
   957  		t.Skip("TODO: Skipped digest test on Windows")
   958  	}
   959  
   960  	if !tu.IsCI() {
   961  		t.Parallel()
   962  	}
   963  	testutil.DockerCompatible(t)
   964  
   965  	task, cfg, ports := dockerTask(t)
   966  	defer freeport.Return(ports)
   967  	cfg.LoadImage = ""
   968  	cfg.Image = "library/busybox@sha256:58ac43b2cc92c687a32c8be6278e50a063579655fe3090125dcb2af0ff9e1a64"
   969  	localDigest := "sha256:8ac48589692a53a9b8c2d1ceaa6b402665aa7fe667ba51ccc03002300856d8c7"
   970  	cfg.ForcePull = true
   971  	cfg.Command = busyboxLongRunningCmd[0]
   972  	cfg.Args = busyboxLongRunningCmd[1:]
   973  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   974  
   975  	client, d, handle, cleanup := dockerSetup(t, task, nil)
   976  	defer cleanup()
   977  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
   978  
   979  	container, err := client.InspectContainer(handle.containerID)
   980  	require.NoError(t, err)
   981  	require.Equal(t, localDigest, container.Image)
   982  }
   983  
   984  func TestDockerDriver_SecurityOptUnconfined(t *testing.T) {
   985  	if runtime.GOOS == "windows" {
   986  		t.Skip("Windows does not support seccomp")
   987  	}
   988  	if !tu.IsCI() {
   989  		t.Parallel()
   990  	}
   991  	testutil.DockerCompatible(t)
   992  
   993  	task, cfg, ports := dockerTask(t)
   994  	defer freeport.Return(ports)
   995  	cfg.SecurityOpt = []string{"seccomp=unconfined"}
   996  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   997  
   998  	client, d, handle, cleanup := dockerSetup(t, task, nil)
   999  	defer cleanup()
  1000  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1001  
  1002  	container, err := client.InspectContainer(handle.containerID)
  1003  	if err != nil {
  1004  		t.Fatalf("err: %v", err)
  1005  	}
  1006  
  1007  	require.Exactly(t, cfg.SecurityOpt, container.HostConfig.SecurityOpt)
  1008  }
  1009  
  1010  func TestDockerDriver_SecurityOptFromFile(t *testing.T) {
  1011  
  1012  	if runtime.GOOS == "windows" {
  1013  		t.Skip("Windows does not support seccomp")
  1014  	}
  1015  	if !tu.IsCI() {
  1016  		t.Parallel()
  1017  	}
  1018  	testutil.DockerCompatible(t)
  1019  
  1020  	task, cfg, ports := dockerTask(t)
  1021  	defer freeport.Return(ports)
  1022  	cfg.SecurityOpt = []string{"seccomp=./test-resources/docker/seccomp.json"}
  1023  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1024  
  1025  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1026  	defer cleanup()
  1027  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1028  
  1029  	container, err := client.InspectContainer(handle.containerID)
  1030  	require.NoError(t, err)
  1031  
  1032  	require.Contains(t, container.HostConfig.SecurityOpt[0], "reboot")
  1033  }
  1034  
  1035  func TestDockerDriver_Runtime(t *testing.T) {
  1036  	if !tu.IsCI() {
  1037  		t.Parallel()
  1038  	}
  1039  	testutil.DockerCompatible(t)
  1040  
  1041  	task, cfg, ports := dockerTask(t)
  1042  	defer freeport.Return(ports)
  1043  	cfg.Runtime = "runc"
  1044  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1045  
  1046  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1047  	defer cleanup()
  1048  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1049  
  1050  	container, err := client.InspectContainer(handle.containerID)
  1051  	if err != nil {
  1052  		t.Fatalf("err: %v", err)
  1053  	}
  1054  
  1055  	require.Exactly(t, cfg.Runtime, container.HostConfig.Runtime)
  1056  }
  1057  
  1058  func TestDockerDriver_CreateContainerConfig(t *testing.T) {
  1059  	t.Parallel()
  1060  
  1061  	task, cfg, ports := dockerTask(t)
  1062  	defer freeport.Return(ports)
  1063  	opt := map[string]string{"size": "120G"}
  1064  
  1065  	cfg.StorageOpt = opt
  1066  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1067  
  1068  	dh := dockerDriverHarness(t, nil)
  1069  	driver := dh.Impl().(*Driver)
  1070  
  1071  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1072  	require.NoError(t, err)
  1073  
  1074  	require.Equal(t, "org/repo:0.1", c.Config.Image)
  1075  	require.EqualValues(t, opt, c.HostConfig.StorageOpt)
  1076  
  1077  	// Container name should be /<task_name>-<alloc_id> for backward compat
  1078  	containerName := fmt.Sprintf("%s-%s", strings.Replace(task.Name, "/", "_", -1), task.AllocID)
  1079  	require.Equal(t, containerName, c.Name)
  1080  }
  1081  
  1082  func TestDockerDriver_CreateContainerConfig_RuntimeConflict(t *testing.T) {
  1083  	t.Parallel()
  1084  
  1085  	task, cfg, ports := dockerTask(t)
  1086  	defer freeport.Return(ports)
  1087  	task.DeviceEnv[nvidia.NvidiaVisibleDevices] = "GPU_UUID_1"
  1088  
  1089  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1090  
  1091  	dh := dockerDriverHarness(t, nil)
  1092  	driver := dh.Impl().(*Driver)
  1093  	driver.gpuRuntime = true
  1094  
  1095  	// Should error if a runtime was explicitly set that doesn't match gpu runtime
  1096  	cfg.Runtime = "nvidia"
  1097  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1098  	require.NoError(t, err)
  1099  	require.Equal(t, "nvidia", c.HostConfig.Runtime)
  1100  
  1101  	cfg.Runtime = "custom"
  1102  	_, err = driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1103  	require.Error(t, err)
  1104  	require.Contains(t, err.Error(), "conflicting runtime requests")
  1105  }
  1106  
  1107  func TestDockerDriver_CreateContainerConfig_ChecksAllowRuntimes(t *testing.T) {
  1108  	t.Parallel()
  1109  
  1110  	dh := dockerDriverHarness(t, nil)
  1111  	driver := dh.Impl().(*Driver)
  1112  	driver.gpuRuntime = true
  1113  	driver.config.allowRuntimes = map[string]struct{}{
  1114  		"runc":   struct{}{},
  1115  		"custom": struct{}{},
  1116  	}
  1117  
  1118  	allowRuntime := []string{
  1119  		"", // default always works
  1120  		"runc",
  1121  		"custom",
  1122  	}
  1123  
  1124  	task, cfg, ports := dockerTask(t)
  1125  	defer freeport.Return(ports)
  1126  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1127  
  1128  	for _, runtime := range allowRuntime {
  1129  		t.Run(runtime, func(t *testing.T) {
  1130  			cfg.Runtime = runtime
  1131  			c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1132  			require.NoError(t, err)
  1133  			require.Equal(t, runtime, c.HostConfig.Runtime)
  1134  		})
  1135  	}
  1136  
  1137  	t.Run("not allowed: denied", func(t *testing.T) {
  1138  		cfg.Runtime = "denied"
  1139  		_, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1140  		require.Error(t, err)
  1141  		require.Contains(t, err.Error(), `runtime "denied" is not allowed`)
  1142  	})
  1143  
  1144  }
  1145  
  1146  func TestDockerDriver_CreateContainerConfig_User(t *testing.T) {
  1147  	t.Parallel()
  1148  
  1149  	task, cfg, ports := dockerTask(t)
  1150  	defer freeport.Return(ports)
  1151  	task.User = "random-user-1"
  1152  
  1153  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1154  
  1155  	dh := dockerDriverHarness(t, nil)
  1156  	driver := dh.Impl().(*Driver)
  1157  
  1158  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1159  	require.NoError(t, err)
  1160  
  1161  	require.Equal(t, task.User, c.Config.User)
  1162  }
  1163  
  1164  func TestDockerDriver_CreateContainerConfig_Labels(t *testing.T) {
  1165  	t.Parallel()
  1166  
  1167  	task, cfg, ports := dockerTask(t)
  1168  	defer freeport.Return(ports)
  1169  	task.AllocID = uuid.Generate()
  1170  	task.JobName = "redis-demo-job"
  1171  
  1172  	cfg.Labels = map[string]string{
  1173  		"user_label": "user_value",
  1174  
  1175  		// com.hashicorp.nomad. labels are reserved and
  1176  		// cannot be overridden
  1177  		"com.hashicorp.nomad.alloc_id": "bad_value",
  1178  	}
  1179  
  1180  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1181  
  1182  	dh := dockerDriverHarness(t, nil)
  1183  	driver := dh.Impl().(*Driver)
  1184  
  1185  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1186  	require.NoError(t, err)
  1187  
  1188  	expectedLabels := map[string]string{
  1189  		// user provided labels
  1190  		"user_label": "user_value",
  1191  		// default labels
  1192  		"com.hashicorp.nomad.alloc_id": task.AllocID,
  1193  	}
  1194  
  1195  	require.Equal(t, expectedLabels, c.Config.Labels)
  1196  }
  1197  
  1198  func TestDockerDriver_CreateContainerConfig_Logging(t *testing.T) {
  1199  	t.Parallel()
  1200  
  1201  	cases := []struct {
  1202  		name           string
  1203  		loggingConfig  DockerLogging
  1204  		expectedConfig DockerLogging
  1205  	}{
  1206  		{
  1207  			"simple type",
  1208  			DockerLogging{Type: "fluentd"},
  1209  			DockerLogging{
  1210  				Type:   "fluentd",
  1211  				Config: map[string]string{},
  1212  			},
  1213  		},
  1214  		{
  1215  			"simple driver",
  1216  			DockerLogging{Driver: "fluentd"},
  1217  			DockerLogging{
  1218  				Type:   "fluentd",
  1219  				Config: map[string]string{},
  1220  			},
  1221  		},
  1222  		{
  1223  			"type takes precedence",
  1224  			DockerLogging{
  1225  				Type:   "json-file",
  1226  				Driver: "fluentd",
  1227  			},
  1228  			DockerLogging{
  1229  				Type:   "json-file",
  1230  				Config: map[string]string{},
  1231  			},
  1232  		},
  1233  		{
  1234  			"user config takes precedence, even if no type provided",
  1235  			DockerLogging{
  1236  				Type:   "",
  1237  				Config: map[string]string{"max-file": "3", "max-size": "10m"},
  1238  			},
  1239  			DockerLogging{
  1240  				Type:   "",
  1241  				Config: map[string]string{"max-file": "3", "max-size": "10m"},
  1242  			},
  1243  		},
  1244  		{
  1245  			"defaults to json-file w/ log rotation",
  1246  			DockerLogging{
  1247  				Type: "",
  1248  			},
  1249  			DockerLogging{
  1250  				Type:   "json-file",
  1251  				Config: map[string]string{"max-file": "2", "max-size": "2m"},
  1252  			},
  1253  		},
  1254  	}
  1255  
  1256  	for _, c := range cases {
  1257  		t.Run(c.name, func(t *testing.T) {
  1258  			task, cfg, ports := dockerTask(t)
  1259  			defer freeport.Return(ports)
  1260  
  1261  			cfg.Logging = c.loggingConfig
  1262  			require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1263  
  1264  			dh := dockerDriverHarness(t, nil)
  1265  			driver := dh.Impl().(*Driver)
  1266  
  1267  			cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1268  			require.NoError(t, err)
  1269  
  1270  			require.Equal(t, c.expectedConfig.Type, cc.HostConfig.LogConfig.Type)
  1271  			require.Equal(t, c.expectedConfig.Config["max-file"], cc.HostConfig.LogConfig.Config["max-file"])
  1272  			require.Equal(t, c.expectedConfig.Config["max-size"], cc.HostConfig.LogConfig.Config["max-size"])
  1273  		})
  1274  	}
  1275  }
  1276  
  1277  func TestDockerDriver_CreateContainerConfigWithRuntimes(t *testing.T) {
  1278  	if !tu.IsCI() {
  1279  		t.Parallel()
  1280  	}
  1281  	testCases := []struct {
  1282  		description           string
  1283  		gpuRuntimeSet         bool
  1284  		expectToReturnError   bool
  1285  		expectedRuntime       string
  1286  		nvidiaDevicesProvided bool
  1287  	}{
  1288  		{
  1289  			description:           "gpu devices are provided, docker driver was able to detect nvidia-runtime 1",
  1290  			gpuRuntimeSet:         true,
  1291  			expectToReturnError:   false,
  1292  			expectedRuntime:       "nvidia",
  1293  			nvidiaDevicesProvided: true,
  1294  		},
  1295  		{
  1296  			description:           "gpu devices are provided, docker driver was able to detect nvidia-runtime 2",
  1297  			gpuRuntimeSet:         true,
  1298  			expectToReturnError:   false,
  1299  			expectedRuntime:       "nvidia-runtime-modified-name",
  1300  			nvidiaDevicesProvided: true,
  1301  		},
  1302  		{
  1303  			description:           "no gpu devices provided - no runtime should be set",
  1304  			gpuRuntimeSet:         true,
  1305  			expectToReturnError:   false,
  1306  			expectedRuntime:       "nvidia",
  1307  			nvidiaDevicesProvided: false,
  1308  		},
  1309  		{
  1310  			description:           "no gpuRuntime supported by docker driver",
  1311  			gpuRuntimeSet:         false,
  1312  			expectToReturnError:   true,
  1313  			expectedRuntime:       "nvidia",
  1314  			nvidiaDevicesProvided: true,
  1315  		},
  1316  	}
  1317  	for _, testCase := range testCases {
  1318  		t.Run(testCase.description, func(t *testing.T) {
  1319  			task, cfg, ports := dockerTask(t)
  1320  			defer freeport.Return(ports)
  1321  
  1322  			dh := dockerDriverHarness(t, map[string]interface{}{
  1323  				"allow_runtimes": []string{"runc", "nvidia", "nvidia-runtime-modified-name"},
  1324  			})
  1325  			driver := dh.Impl().(*Driver)
  1326  
  1327  			driver.gpuRuntime = testCase.gpuRuntimeSet
  1328  			driver.config.GPURuntimeName = testCase.expectedRuntime
  1329  			if testCase.nvidiaDevicesProvided {
  1330  				task.DeviceEnv[nvidia.NvidiaVisibleDevices] = "GPU_UUID_1"
  1331  			}
  1332  
  1333  			c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1334  			if testCase.expectToReturnError {
  1335  				require.NotNil(t, err)
  1336  			} else {
  1337  				require.NoError(t, err)
  1338  				if testCase.nvidiaDevicesProvided {
  1339  					require.Equal(t, testCase.expectedRuntime, c.HostConfig.Runtime)
  1340  				} else {
  1341  					// no nvidia devices provided -> no point to use nvidia runtime
  1342  					require.Equal(t, "", c.HostConfig.Runtime)
  1343  				}
  1344  			}
  1345  		})
  1346  	}
  1347  }
  1348  
  1349  func TestDockerDriver_Capabilities(t *testing.T) {
  1350  	if !tu.IsCI() {
  1351  		t.Parallel()
  1352  	}
  1353  	testutil.DockerCompatible(t)
  1354  	if runtime.GOOS == "windows" {
  1355  		t.Skip("Capabilities not supported on windows")
  1356  	}
  1357  
  1358  	testCases := []struct {
  1359  		Name       string
  1360  		CapAdd     []string
  1361  		CapDrop    []string
  1362  		Whitelist  string
  1363  		StartError string
  1364  	}{
  1365  		{
  1366  			Name:    "default-whitelist-add-allowed",
  1367  			CapAdd:  []string{"fowner", "mknod"},
  1368  			CapDrop: []string{"all"},
  1369  		},
  1370  		{
  1371  			Name:       "default-whitelist-add-forbidden",
  1372  			CapAdd:     []string{"net_admin"},
  1373  			StartError: "net_admin",
  1374  		},
  1375  		{
  1376  			Name:    "default-whitelist-drop-existing",
  1377  			CapDrop: []string{"fowner", "mknod"},
  1378  		},
  1379  		{
  1380  			Name:      "restrictive-whitelist-drop-all",
  1381  			CapDrop:   []string{"all"},
  1382  			Whitelist: "fowner,mknod",
  1383  		},
  1384  		{
  1385  			Name:      "restrictive-whitelist-add-allowed",
  1386  			CapAdd:    []string{"fowner", "mknod"},
  1387  			CapDrop:   []string{"all"},
  1388  			Whitelist: "fowner,mknod",
  1389  		},
  1390  		{
  1391  			Name:       "restrictive-whitelist-add-forbidden",
  1392  			CapAdd:     []string{"net_admin", "mknod"},
  1393  			CapDrop:    []string{"all"},
  1394  			Whitelist:  "fowner,mknod",
  1395  			StartError: "net_admin",
  1396  		},
  1397  		{
  1398  			Name:      "permissive-whitelist",
  1399  			CapAdd:    []string{"net_admin", "mknod"},
  1400  			Whitelist: "all",
  1401  		},
  1402  		{
  1403  			Name:      "permissive-whitelist-add-all",
  1404  			CapAdd:    []string{"all"},
  1405  			Whitelist: "all",
  1406  		},
  1407  	}
  1408  
  1409  	for _, tc := range testCases {
  1410  		t.Run(tc.Name, func(t *testing.T) {
  1411  			client := newTestDockerClient(t)
  1412  			task, cfg, ports := dockerTask(t)
  1413  			defer freeport.Return(ports)
  1414  
  1415  			if len(tc.CapAdd) > 0 {
  1416  				cfg.CapAdd = tc.CapAdd
  1417  			}
  1418  			if len(tc.CapDrop) > 0 {
  1419  				cfg.CapDrop = tc.CapDrop
  1420  			}
  1421  			require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1422  
  1423  			d := dockerDriverHarness(t, nil)
  1424  			dockerDriver, ok := d.Impl().(*Driver)
  1425  			require.True(t, ok)
  1426  			if tc.Whitelist != "" {
  1427  				dockerDriver.config.AllowCaps = strings.Split(tc.Whitelist, ",")
  1428  			}
  1429  
  1430  			cleanup := d.MkAllocDir(task, true)
  1431  			defer cleanup()
  1432  			copyImage(t, task.TaskDir(), "busybox.tar")
  1433  
  1434  			_, _, err := d.StartTask(task)
  1435  			defer d.DestroyTask(task.ID, true)
  1436  			if err == nil && tc.StartError != "" {
  1437  				t.Fatalf("Expected error in start: %v", tc.StartError)
  1438  			} else if err != nil {
  1439  				if tc.StartError == "" {
  1440  					require.NoError(t, err)
  1441  				} else {
  1442  					require.Contains(t, err.Error(), tc.StartError)
  1443  				}
  1444  				return
  1445  			}
  1446  
  1447  			handle, ok := dockerDriver.tasks.Get(task.ID)
  1448  			require.True(t, ok)
  1449  
  1450  			require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1451  
  1452  			container, err := client.InspectContainer(handle.containerID)
  1453  			require.NoError(t, err)
  1454  
  1455  			require.Exactly(t, tc.CapAdd, container.HostConfig.CapAdd)
  1456  			require.Exactly(t, tc.CapDrop, container.HostConfig.CapDrop)
  1457  		})
  1458  	}
  1459  }
  1460  
  1461  func TestDockerDriver_DNS(t *testing.T) {
  1462  	if !tu.IsCI() {
  1463  		t.Parallel()
  1464  	}
  1465  	testutil.DockerCompatible(t)
  1466  
  1467  	task, cfg, ports := dockerTask(t)
  1468  	defer freeport.Return(ports)
  1469  	cfg.DNSServers = []string{"8.8.8.8", "8.8.4.4"}
  1470  	cfg.DNSSearchDomains = []string{"example.com", "example.org", "example.net"}
  1471  	cfg.DNSOptions = []string{"ndots:1"}
  1472  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1473  
  1474  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1475  	defer cleanup()
  1476  
  1477  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1478  
  1479  	container, err := client.InspectContainer(handle.containerID)
  1480  	require.NoError(t, err)
  1481  
  1482  	require.Exactly(t, cfg.DNSServers, container.HostConfig.DNS)
  1483  	require.Exactly(t, cfg.DNSSearchDomains, container.HostConfig.DNSSearch)
  1484  	require.Exactly(t, cfg.DNSOptions, container.HostConfig.DNSOptions)
  1485  }
  1486  
  1487  func TestDockerDriver_MemoryHardLimit(t *testing.T) {
  1488  	if !tu.IsCI() {
  1489  		t.Parallel()
  1490  	}
  1491  	testutil.DockerCompatible(t)
  1492  	if runtime.GOOS == "windows" {
  1493  		t.Skip("Windows does not support MemoryReservation")
  1494  	}
  1495  
  1496  	task, cfg, ports := dockerTask(t)
  1497  	defer freeport.Return(ports)
  1498  
  1499  	cfg.MemoryHardLimit = 300
  1500  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1501  
  1502  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1503  	defer cleanup()
  1504  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1505  
  1506  	container, err := client.InspectContainer(handle.containerID)
  1507  	require.NoError(t, err)
  1508  
  1509  	require.Equal(t, task.Resources.LinuxResources.MemoryLimitBytes, container.HostConfig.MemoryReservation)
  1510  	require.Equal(t, cfg.MemoryHardLimit*1024*1024, container.HostConfig.Memory)
  1511  }
  1512  
  1513  func TestDockerDriver_MACAddress(t *testing.T) {
  1514  	if !tu.IsCI() {
  1515  		t.Parallel()
  1516  	}
  1517  	testutil.DockerCompatible(t)
  1518  	if runtime.GOOS == "windows" {
  1519  		t.Skip("Windows docker does not support setting MacAddress")
  1520  	}
  1521  
  1522  	task, cfg, ports := dockerTask(t)
  1523  	defer freeport.Return(ports)
  1524  	cfg.MacAddress = "00:16:3e:00:00:00"
  1525  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1526  
  1527  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1528  	defer cleanup()
  1529  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1530  
  1531  	container, err := client.InspectContainer(handle.containerID)
  1532  	require.NoError(t, err)
  1533  
  1534  	require.Equal(t, cfg.MacAddress, container.NetworkSettings.MacAddress)
  1535  }
  1536  
  1537  func TestDockerWorkDir(t *testing.T) {
  1538  	if !tu.IsCI() {
  1539  		t.Parallel()
  1540  	}
  1541  	testutil.DockerCompatible(t)
  1542  
  1543  	task, cfg, ports := dockerTask(t)
  1544  	defer freeport.Return(ports)
  1545  	cfg.WorkDir = "/some/path"
  1546  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1547  
  1548  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1549  	defer cleanup()
  1550  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1551  
  1552  	container, err := client.InspectContainer(handle.containerID)
  1553  	require.NoError(t, err)
  1554  	require.Equal(t, cfg.WorkDir, filepath.ToSlash(container.Config.WorkingDir))
  1555  }
  1556  
  1557  func inSlice(needle string, haystack []string) bool {
  1558  	for _, h := range haystack {
  1559  		if h == needle {
  1560  			return true
  1561  		}
  1562  	}
  1563  	return false
  1564  }
  1565  
  1566  func TestDockerDriver_PortsNoMap(t *testing.T) {
  1567  	if !tu.IsCI() {
  1568  		t.Parallel()
  1569  	}
  1570  	testutil.DockerCompatible(t)
  1571  
  1572  	task, _, ports := dockerTask(t)
  1573  	defer freeport.Return(ports)
  1574  	res := ports[0]
  1575  	dyn := ports[1]
  1576  
  1577  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1578  	defer cleanup()
  1579  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1580  
  1581  	container, err := client.InspectContainer(handle.containerID)
  1582  	require.NoError(t, err)
  1583  
  1584  	// Verify that the correct ports are EXPOSED
  1585  	expectedExposedPorts := map[docker.Port]struct{}{
  1586  		docker.Port(fmt.Sprintf("%d/tcp", res)): {},
  1587  		docker.Port(fmt.Sprintf("%d/udp", res)): {},
  1588  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): {},
  1589  		docker.Port(fmt.Sprintf("%d/udp", dyn)): {},
  1590  	}
  1591  
  1592  	require.Exactly(t, expectedExposedPorts, container.Config.ExposedPorts)
  1593  
  1594  	hostIP := "127.0.0.1"
  1595  	if runtime.GOOS == "windows" {
  1596  		hostIP = ""
  1597  	}
  1598  
  1599  	// Verify that the correct ports are FORWARDED
  1600  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
  1601  		docker.Port(fmt.Sprintf("%d/tcp", res)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}},
  1602  		docker.Port(fmt.Sprintf("%d/udp", res)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}},
  1603  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}},
  1604  		docker.Port(fmt.Sprintf("%d/udp", dyn)): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}},
  1605  	}
  1606  
  1607  	require.Exactly(t, expectedPortBindings, container.HostConfig.PortBindings)
  1608  }
  1609  
  1610  func TestDockerDriver_PortsMapping(t *testing.T) {
  1611  	if !tu.IsCI() {
  1612  		t.Parallel()
  1613  	}
  1614  	testutil.DockerCompatible(t)
  1615  
  1616  	task, cfg, ports := dockerTask(t)
  1617  	defer freeport.Return(ports)
  1618  	res := ports[0]
  1619  	dyn := ports[1]
  1620  	cfg.PortMap = map[string]int{
  1621  		"main":  8080,
  1622  		"REDIS": 6379,
  1623  	}
  1624  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1625  
  1626  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1627  	defer cleanup()
  1628  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1629  
  1630  	container, err := client.InspectContainer(handle.containerID)
  1631  	require.NoError(t, err)
  1632  
  1633  	// Verify that the port environment variables are set
  1634  	require.Contains(t, container.Config.Env, "NOMAD_PORT_main=8080")
  1635  	require.Contains(t, container.Config.Env, "NOMAD_PORT_REDIS=6379")
  1636  
  1637  	// Verify that the correct ports are EXPOSED
  1638  	expectedExposedPorts := map[docker.Port]struct{}{
  1639  		docker.Port("8080/tcp"): {},
  1640  		docker.Port("8080/udp"): {},
  1641  		docker.Port("6379/tcp"): {},
  1642  		docker.Port("6379/udp"): {},
  1643  	}
  1644  
  1645  	require.Exactly(t, expectedExposedPorts, container.Config.ExposedPorts)
  1646  
  1647  	hostIP := "127.0.0.1"
  1648  	if runtime.GOOS == "windows" {
  1649  		hostIP = ""
  1650  	}
  1651  
  1652  	// Verify that the correct ports are FORWARDED
  1653  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
  1654  		docker.Port("8080/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}},
  1655  		docker.Port("8080/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}},
  1656  		docker.Port("6379/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}},
  1657  		docker.Port("6379/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}},
  1658  	}
  1659  	require.Exactly(t, expectedPortBindings, container.HostConfig.PortBindings)
  1660  }
  1661  
  1662  func TestDockerDriver_CreateContainerConfig_PortsMapping(t *testing.T) {
  1663  	t.Parallel()
  1664  
  1665  	task, cfg, ports := dockerTask(t)
  1666  	defer freeport.Return(ports)
  1667  	res := ports[0]
  1668  	dyn := ports[1]
  1669  	cfg.PortMap = map[string]int{
  1670  		"main":  8080,
  1671  		"REDIS": 6379,
  1672  	}
  1673  	dh := dockerDriverHarness(t, nil)
  1674  	driver := dh.Impl().(*Driver)
  1675  
  1676  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  1677  	require.NoError(t, err)
  1678  
  1679  	require.Equal(t, "org/repo:0.1", c.Config.Image)
  1680  	require.Contains(t, c.Config.Env, "NOMAD_PORT_main=8080")
  1681  	require.Contains(t, c.Config.Env, "NOMAD_PORT_REDIS=6379")
  1682  
  1683  	// Verify that the correct ports are FORWARDED
  1684  	hostIP := "127.0.0.1"
  1685  	if runtime.GOOS == "windows" {
  1686  		hostIP = ""
  1687  	}
  1688  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
  1689  		docker.Port("8080/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}},
  1690  		docker.Port("8080/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", res)}},
  1691  		docker.Port("6379/tcp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}},
  1692  		docker.Port("6379/udp"): {{HostIP: hostIP, HostPort: fmt.Sprintf("%d", dyn)}},
  1693  	}
  1694  	require.Exactly(t, expectedPortBindings, c.HostConfig.PortBindings)
  1695  
  1696  }
  1697  
  1698  func TestDockerDriver_CleanupContainer(t *testing.T) {
  1699  	if !tu.IsCI() {
  1700  		t.Parallel()
  1701  	}
  1702  	testutil.DockerCompatible(t)
  1703  
  1704  	task, cfg, ports := dockerTask(t)
  1705  	defer freeport.Return(ports)
  1706  	cfg.Command = "echo"
  1707  	cfg.Args = []string{"hello"}
  1708  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1709  
  1710  	client, d, handle, cleanup := dockerSetup(t, task, nil)
  1711  	defer cleanup()
  1712  
  1713  	waitCh, err := d.WaitTask(context.Background(), task.ID)
  1714  	require.NoError(t, err)
  1715  
  1716  	select {
  1717  	case res := <-waitCh:
  1718  		if !res.Successful() {
  1719  			t.Fatalf("err: %v", res)
  1720  		}
  1721  
  1722  		err = d.DestroyTask(task.ID, false)
  1723  		require.NoError(t, err)
  1724  
  1725  		time.Sleep(3 * time.Second)
  1726  
  1727  		// Ensure that the container isn't present
  1728  		_, err := client.InspectContainer(handle.containerID)
  1729  		if err == nil {
  1730  			t.Fatalf("expected to not get container")
  1731  		}
  1732  
  1733  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
  1734  		t.Fatalf("timeout")
  1735  	}
  1736  }
  1737  
  1738  func TestDockerDriver_EnableImageGC(t *testing.T) {
  1739  	testutil.DockerCompatible(t)
  1740  
  1741  	task, cfg, ports := dockerTask(t)
  1742  	defer freeport.Return(ports)
  1743  	cfg.Command = "echo"
  1744  	cfg.Args = []string{"hello"}
  1745  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1746  
  1747  	client := newTestDockerClient(t)
  1748  	driver := dockerDriverHarness(t, map[string]interface{}{
  1749  		"gc": map[string]interface{}{
  1750  			"container":   true,
  1751  			"image":       true,
  1752  			"image_delay": "2s",
  1753  		},
  1754  	})
  1755  	cleanup := driver.MkAllocDir(task, true)
  1756  	defer cleanup()
  1757  
  1758  	cleanSlate(client, cfg.Image)
  1759  
  1760  	copyImage(t, task.TaskDir(), "busybox.tar")
  1761  	_, _, err := driver.StartTask(task)
  1762  	require.NoError(t, err)
  1763  
  1764  	dockerDriver, ok := driver.Impl().(*Driver)
  1765  	require.True(t, ok)
  1766  	_, ok = dockerDriver.tasks.Get(task.ID)
  1767  	require.True(t, ok)
  1768  
  1769  	waitCh, err := dockerDriver.WaitTask(context.Background(), task.ID)
  1770  	require.NoError(t, err)
  1771  	select {
  1772  	case res := <-waitCh:
  1773  		if !res.Successful() {
  1774  			t.Fatalf("err: %v", res)
  1775  		}
  1776  
  1777  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
  1778  		t.Fatalf("timeout")
  1779  	}
  1780  
  1781  	// we haven't called DestroyTask, image should be present
  1782  	_, err = client.InspectImage(cfg.Image)
  1783  	require.NoError(t, err)
  1784  
  1785  	err = dockerDriver.DestroyTask(task.ID, false)
  1786  	require.NoError(t, err)
  1787  
  1788  	// image_delay is 3s, so image should still be around for a bit
  1789  	_, err = client.InspectImage(cfg.Image)
  1790  	require.NoError(t, err)
  1791  
  1792  	// Ensure image was removed
  1793  	tu.WaitForResult(func() (bool, error) {
  1794  		if _, err := client.InspectImage(cfg.Image); err == nil {
  1795  			return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image)
  1796  		}
  1797  
  1798  		return true, nil
  1799  	}, func(err error) {
  1800  		require.NoError(t, err)
  1801  	})
  1802  }
  1803  
  1804  func TestDockerDriver_DisableImageGC(t *testing.T) {
  1805  	testutil.DockerCompatible(t)
  1806  
  1807  	task, cfg, ports := dockerTask(t)
  1808  	defer freeport.Return(ports)
  1809  	cfg.Command = "echo"
  1810  	cfg.Args = []string{"hello"}
  1811  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1812  
  1813  	client := newTestDockerClient(t)
  1814  	driver := dockerDriverHarness(t, map[string]interface{}{
  1815  		"gc": map[string]interface{}{
  1816  			"container":   true,
  1817  			"image":       false,
  1818  			"image_delay": "1s",
  1819  		},
  1820  	})
  1821  	cleanup := driver.MkAllocDir(task, true)
  1822  	defer cleanup()
  1823  
  1824  	cleanSlate(client, cfg.Image)
  1825  
  1826  	copyImage(t, task.TaskDir(), "busybox.tar")
  1827  	_, _, err := driver.StartTask(task)
  1828  	require.NoError(t, err)
  1829  
  1830  	dockerDriver, ok := driver.Impl().(*Driver)
  1831  	require.True(t, ok)
  1832  	handle, ok := dockerDriver.tasks.Get(task.ID)
  1833  	require.True(t, ok)
  1834  
  1835  	waitCh, err := dockerDriver.WaitTask(context.Background(), task.ID)
  1836  	require.NoError(t, err)
  1837  	select {
  1838  	case res := <-waitCh:
  1839  		if !res.Successful() {
  1840  			t.Fatalf("err: %v", res)
  1841  		}
  1842  
  1843  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
  1844  		t.Fatalf("timeout")
  1845  	}
  1846  
  1847  	// we haven't called DestroyTask, image should be present
  1848  	_, err = client.InspectImage(handle.containerImage)
  1849  	require.NoError(t, err)
  1850  
  1851  	err = dockerDriver.DestroyTask(task.ID, false)
  1852  	require.NoError(t, err)
  1853  
  1854  	// image_delay is 1s, wait a little longer
  1855  	time.Sleep(3 * time.Second)
  1856  
  1857  	// image should not have been removed or scheduled to be removed
  1858  	_, err = client.InspectImage(cfg.Image)
  1859  	require.NoError(t, err)
  1860  	dockerDriver.coordinator.imageLock.Lock()
  1861  	_, ok = dockerDriver.coordinator.deleteFuture[handle.containerImage]
  1862  	require.False(t, ok, "image should not be registered for deletion")
  1863  	dockerDriver.coordinator.imageLock.Unlock()
  1864  }
  1865  
  1866  func TestDockerDriver_MissingContainer_Cleanup(t *testing.T) {
  1867  	testutil.DockerCompatible(t)
  1868  
  1869  	task, cfg, ports := dockerTask(t)
  1870  	defer freeport.Return(ports)
  1871  	cfg.Command = "echo"
  1872  	cfg.Args = []string{"hello"}
  1873  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1874  
  1875  	client := newTestDockerClient(t)
  1876  	driver := dockerDriverHarness(t, map[string]interface{}{
  1877  		"gc": map[string]interface{}{
  1878  			"container":   true,
  1879  			"image":       true,
  1880  			"image_delay": "0s",
  1881  		},
  1882  	})
  1883  	cleanup := driver.MkAllocDir(task, true)
  1884  	defer cleanup()
  1885  
  1886  	cleanSlate(client, cfg.Image)
  1887  
  1888  	copyImage(t, task.TaskDir(), "busybox.tar")
  1889  	_, _, err := driver.StartTask(task)
  1890  	require.NoError(t, err)
  1891  
  1892  	dockerDriver, ok := driver.Impl().(*Driver)
  1893  	require.True(t, ok)
  1894  	h, ok := dockerDriver.tasks.Get(task.ID)
  1895  	require.True(t, ok)
  1896  
  1897  	waitCh, err := dockerDriver.WaitTask(context.Background(), task.ID)
  1898  	require.NoError(t, err)
  1899  	select {
  1900  	case res := <-waitCh:
  1901  		if !res.Successful() {
  1902  			t.Fatalf("err: %v", res)
  1903  		}
  1904  
  1905  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
  1906  		t.Fatalf("timeout")
  1907  	}
  1908  
  1909  	// remove the container out-of-band
  1910  	require.NoError(t, client.RemoveContainer(docker.RemoveContainerOptions{
  1911  		ID: h.containerID,
  1912  	}))
  1913  
  1914  	require.NoError(t, dockerDriver.DestroyTask(task.ID, false))
  1915  
  1916  	// Ensure image was removed
  1917  	tu.WaitForResult(func() (bool, error) {
  1918  		if _, err := client.InspectImage(cfg.Image); err == nil {
  1919  			return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image)
  1920  		}
  1921  
  1922  		return true, nil
  1923  	}, func(err error) {
  1924  		require.NoError(t, err)
  1925  	})
  1926  
  1927  	// Ensure that task handle was removed
  1928  	_, ok = dockerDriver.tasks.Get(task.ID)
  1929  	require.False(t, ok)
  1930  }
  1931  
  1932  func TestDockerDriver_Stats(t *testing.T) {
  1933  	if !tu.IsCI() {
  1934  		t.Parallel()
  1935  	}
  1936  	testutil.DockerCompatible(t)
  1937  
  1938  	task, cfg, ports := dockerTask(t)
  1939  	defer freeport.Return(ports)
  1940  	cfg.Command = "sleep"
  1941  	cfg.Args = []string{"1000"}
  1942  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  1943  
  1944  	_, d, handle, cleanup := dockerSetup(t, task, nil)
  1945  	defer cleanup()
  1946  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
  1947  
  1948  	go func() {
  1949  		defer d.DestroyTask(task.ID, true)
  1950  		ctx, cancel := context.WithCancel(context.Background())
  1951  		defer cancel()
  1952  		ch, err := handle.Stats(ctx, 1*time.Second)
  1953  		assert.NoError(t, err)
  1954  		select {
  1955  		case ru := <-ch:
  1956  			assert.NotNil(t, ru.ResourceUsage)
  1957  		case <-time.After(3 * time.Second):
  1958  			assert.Fail(t, "stats timeout")
  1959  		}
  1960  	}()
  1961  
  1962  	waitCh, err := d.WaitTask(context.Background(), task.ID)
  1963  	require.NoError(t, err)
  1964  	select {
  1965  	case res := <-waitCh:
  1966  		if res.Successful() {
  1967  			t.Fatalf("should err: %v", res)
  1968  		}
  1969  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
  1970  		t.Fatalf("timeout")
  1971  	}
  1972  }
  1973  
  1974  func setupDockerVolumes(t *testing.T, cfg map[string]interface{}, hostpath string) (*drivers.TaskConfig, *dtestutil.DriverHarness, *TaskConfig, string, func()) {
  1975  	testutil.DockerCompatible(t)
  1976  
  1977  	randfn := fmt.Sprintf("test-%d", rand.Int())
  1978  	hostfile := filepath.Join(hostpath, randfn)
  1979  	var containerPath string
  1980  	if runtime.GOOS == "windows" {
  1981  		containerPath = "C:\\data"
  1982  	} else {
  1983  		containerPath = "/mnt/vol"
  1984  	}
  1985  	containerFile := filepath.Join(containerPath, randfn)
  1986  
  1987  	taskCfg := newTaskConfig("", []string{"touch", containerFile})
  1988  	taskCfg.Volumes = []string{fmt.Sprintf("%s:%s", hostpath, containerPath)}
  1989  
  1990  	task := &drivers.TaskConfig{
  1991  		ID:        uuid.Generate(),
  1992  		Name:      "ls",
  1993  		AllocID:   uuid.Generate(),
  1994  		Env:       map[string]string{"VOL_PATH": containerPath},
  1995  		Resources: basicResources,
  1996  	}
  1997  	require.NoError(t, task.EncodeConcreteDriverConfig(taskCfg))
  1998  
  1999  	d := dockerDriverHarness(t, cfg)
  2000  	cleanup := d.MkAllocDir(task, true)
  2001  
  2002  	copyImage(t, task.TaskDir(), "busybox.tar")
  2003  
  2004  	return task, d, &taskCfg, hostfile, cleanup
  2005  }
  2006  
  2007  func TestDockerDriver_VolumesDisabled(t *testing.T) {
  2008  	if !tu.IsCI() {
  2009  		t.Parallel()
  2010  	}
  2011  	testutil.DockerCompatible(t)
  2012  
  2013  	cfg := map[string]interface{}{
  2014  		"volumes": map[string]interface{}{
  2015  			"enabled": false,
  2016  		},
  2017  		"gc": map[string]interface{}{
  2018  			"image": false,
  2019  		},
  2020  	}
  2021  
  2022  	{
  2023  		tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesdisabled")
  2024  		if err != nil {
  2025  			t.Fatalf("error creating temporary dir: %v", err)
  2026  		}
  2027  
  2028  		task, driver, _, _, cleanup := setupDockerVolumes(t, cfg, tmpvol)
  2029  		defer cleanup()
  2030  
  2031  		_, _, err = driver.StartTask(task)
  2032  		defer driver.DestroyTask(task.ID, true)
  2033  		if err == nil {
  2034  			require.Fail(t, "Started driver successfully when volumes should have been disabled.")
  2035  		}
  2036  	}
  2037  
  2038  	// Relative paths should still be allowed
  2039  	{
  2040  		task, driver, _, fn, cleanup := setupDockerVolumes(t, cfg, ".")
  2041  		defer cleanup()
  2042  
  2043  		_, _, err := driver.StartTask(task)
  2044  		require.NoError(t, err)
  2045  		defer driver.DestroyTask(task.ID, true)
  2046  
  2047  		waitCh, err := driver.WaitTask(context.Background(), task.ID)
  2048  		require.NoError(t, err)
  2049  		select {
  2050  		case res := <-waitCh:
  2051  			if !res.Successful() {
  2052  				t.Fatalf("unexpected err: %v", res)
  2053  			}
  2054  		case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
  2055  			t.Fatalf("timeout")
  2056  		}
  2057  
  2058  		if _, err := ioutil.ReadFile(filepath.Join(task.TaskDir().Dir, fn)); err != nil {
  2059  			t.Fatalf("unexpected error reading %s: %v", fn, err)
  2060  		}
  2061  	}
  2062  
  2063  	// Volume Drivers should be rejected (error)
  2064  	{
  2065  		task, driver, taskCfg, _, cleanup := setupDockerVolumes(t, cfg, "fake_flocker_vol")
  2066  		defer cleanup()
  2067  
  2068  		taskCfg.VolumeDriver = "flocker"
  2069  		require.NoError(t, task.EncodeConcreteDriverConfig(taskCfg))
  2070  
  2071  		_, _, err := driver.StartTask(task)
  2072  		defer driver.DestroyTask(task.ID, true)
  2073  		if err == nil {
  2074  			require.Fail(t, "Started driver successfully when volume drivers should have been disabled.")
  2075  		}
  2076  	}
  2077  }
  2078  
  2079  func TestDockerDriver_VolumesEnabled(t *testing.T) {
  2080  	if !tu.IsCI() {
  2081  		t.Parallel()
  2082  	}
  2083  	testutil.DockerCompatible(t)
  2084  
  2085  	cfg := map[string]interface{}{
  2086  		"volumes": map[string]interface{}{
  2087  			"enabled": true,
  2088  		},
  2089  		"gc": map[string]interface{}{
  2090  			"image": false,
  2091  		},
  2092  	}
  2093  
  2094  	tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesenabled")
  2095  	require.NoError(t, err)
  2096  
  2097  	// Evaluate symlinks so it works on MacOS
  2098  	tmpvol, err = filepath.EvalSymlinks(tmpvol)
  2099  	require.NoError(t, err)
  2100  
  2101  	task, driver, _, hostpath, cleanup := setupDockerVolumes(t, cfg, tmpvol)
  2102  	defer cleanup()
  2103  
  2104  	_, _, err = driver.StartTask(task)
  2105  	require.NoError(t, err)
  2106  	defer driver.DestroyTask(task.ID, true)
  2107  
  2108  	waitCh, err := driver.WaitTask(context.Background(), task.ID)
  2109  	require.NoError(t, err)
  2110  	select {
  2111  	case res := <-waitCh:
  2112  		if !res.Successful() {
  2113  			t.Fatalf("unexpected err: %v", res)
  2114  		}
  2115  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
  2116  		t.Fatalf("timeout")
  2117  	}
  2118  
  2119  	if _, err := ioutil.ReadFile(hostpath); err != nil {
  2120  		t.Fatalf("unexpected error reading %s: %v", hostpath, err)
  2121  	}
  2122  }
  2123  
  2124  func TestDockerDriver_Mounts(t *testing.T) {
  2125  	if !tu.IsCI() {
  2126  		t.Parallel()
  2127  	}
  2128  	testutil.DockerCompatible(t)
  2129  
  2130  	goodMount := DockerMount{
  2131  		Target: "/nomad",
  2132  		VolumeOptions: DockerVolumeOptions{
  2133  			Labels: map[string]string{"foo": "bar"},
  2134  			DriverConfig: DockerVolumeDriverConfig{
  2135  				Name: "local",
  2136  			},
  2137  		},
  2138  		ReadOnly: true,
  2139  		Source:   "test",
  2140  	}
  2141  
  2142  	if runtime.GOOS == "windows" {
  2143  		goodMount.Target = "C:\\nomad"
  2144  	}
  2145  
  2146  	cases := []struct {
  2147  		Name   string
  2148  		Mounts []DockerMount
  2149  		Error  string
  2150  	}{
  2151  		{
  2152  			Name:   "good-one",
  2153  			Error:  "",
  2154  			Mounts: []DockerMount{goodMount},
  2155  		},
  2156  		{
  2157  			Name:   "duplicate",
  2158  			Error:  "Duplicate mount point",
  2159  			Mounts: []DockerMount{goodMount, goodMount, goodMount},
  2160  		},
  2161  	}
  2162  
  2163  	for _, c := range cases {
  2164  		t.Run(c.Name, func(t *testing.T) {
  2165  			d := dockerDriverHarness(t, nil)
  2166  			driver := d.Impl().(*Driver)
  2167  			driver.config.Volumes.Enabled = true
  2168  
  2169  			// Build the task
  2170  			task, cfg, ports := dockerTask(t)
  2171  			defer freeport.Return(ports)
  2172  			cfg.Command = "sleep"
  2173  			cfg.Args = []string{"10000"}
  2174  			cfg.Mounts = c.Mounts
  2175  			require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2176  			cleanup := d.MkAllocDir(task, true)
  2177  			defer cleanup()
  2178  
  2179  			copyImage(t, task.TaskDir(), "busybox.tar")
  2180  
  2181  			_, _, err := d.StartTask(task)
  2182  			defer d.DestroyTask(task.ID, true)
  2183  			if err == nil && c.Error != "" {
  2184  				t.Fatalf("expected error: %v", c.Error)
  2185  			} else if err != nil {
  2186  				if c.Error == "" {
  2187  					t.Fatalf("unexpected error in prestart: %v", err)
  2188  				} else if !strings.Contains(err.Error(), c.Error) {
  2189  					t.Fatalf("expected error %q; got %v", c.Error, err)
  2190  				}
  2191  			}
  2192  		})
  2193  	}
  2194  }
  2195  
  2196  func TestDockerDriver_AuthConfiguration(t *testing.T) {
  2197  	if !tu.IsCI() {
  2198  		t.Parallel()
  2199  	}
  2200  	testutil.DockerCompatible(t)
  2201  
  2202  	path := "./test-resources/docker/auth.json"
  2203  	cases := []struct {
  2204  		Repo       string
  2205  		AuthConfig *docker.AuthConfiguration
  2206  	}{
  2207  		{
  2208  			Repo:       "lolwhat.com/what:1337",
  2209  			AuthConfig: nil,
  2210  		},
  2211  		{
  2212  			Repo: "redis:3.2",
  2213  			AuthConfig: &docker.AuthConfiguration{
  2214  				Username:      "test",
  2215  				Password:      "1234",
  2216  				Email:         "",
  2217  				ServerAddress: "https://index.docker.io/v1/",
  2218  			},
  2219  		},
  2220  		{
  2221  			Repo: "quay.io/redis:3.2",
  2222  			AuthConfig: &docker.AuthConfiguration{
  2223  				Username:      "test",
  2224  				Password:      "5678",
  2225  				Email:         "",
  2226  				ServerAddress: "quay.io",
  2227  			},
  2228  		},
  2229  		{
  2230  			Repo: "other.io/redis:3.2",
  2231  			AuthConfig: &docker.AuthConfiguration{
  2232  				Username:      "test",
  2233  				Password:      "abcd",
  2234  				Email:         "",
  2235  				ServerAddress: "https://other.io/v1/",
  2236  			},
  2237  		},
  2238  	}
  2239  
  2240  	for _, c := range cases {
  2241  		act, err := authFromDockerConfig(path)(c.Repo)
  2242  		require.NoError(t, err)
  2243  		require.Exactly(t, c.AuthConfig, act)
  2244  	}
  2245  }
  2246  
  2247  func TestDockerDriver_AuthFromTaskConfig(t *testing.T) {
  2248  	if !tu.IsCI() {
  2249  		t.Parallel()
  2250  	}
  2251  
  2252  	cases := []struct {
  2253  		Auth       DockerAuth
  2254  		AuthConfig *docker.AuthConfiguration
  2255  		Desc       string
  2256  	}{
  2257  		{
  2258  			Auth:       DockerAuth{},
  2259  			AuthConfig: nil,
  2260  			Desc:       "Empty Config",
  2261  		},
  2262  		{
  2263  			Auth: DockerAuth{
  2264  				Username:   "foo",
  2265  				Password:   "bar",
  2266  				Email:      "foo@bar.com",
  2267  				ServerAddr: "www.foobar.com",
  2268  			},
  2269  			AuthConfig: &docker.AuthConfiguration{
  2270  				Username:      "foo",
  2271  				Password:      "bar",
  2272  				Email:         "foo@bar.com",
  2273  				ServerAddress: "www.foobar.com",
  2274  			},
  2275  			Desc: "All fields set",
  2276  		},
  2277  		{
  2278  			Auth: DockerAuth{
  2279  				Username:   "foo",
  2280  				Password:   "bar",
  2281  				ServerAddr: "www.foobar.com",
  2282  			},
  2283  			AuthConfig: &docker.AuthConfiguration{
  2284  				Username:      "foo",
  2285  				Password:      "bar",
  2286  				ServerAddress: "www.foobar.com",
  2287  			},
  2288  			Desc: "Email not set",
  2289  		},
  2290  	}
  2291  
  2292  	for _, c := range cases {
  2293  		t.Run(c.Desc, func(t *testing.T) {
  2294  			act, err := authFromTaskConfig(&TaskConfig{Auth: c.Auth})("test")
  2295  			require.NoError(t, err)
  2296  			require.Exactly(t, c.AuthConfig, act)
  2297  		})
  2298  	}
  2299  }
  2300  
  2301  func TestDockerDriver_OOMKilled(t *testing.T) {
  2302  	if !tu.IsCI() {
  2303  		t.Parallel()
  2304  	}
  2305  	testutil.DockerCompatible(t)
  2306  
  2307  	if runtime.GOOS == "windows" {
  2308  		t.Skip("Windows does not support OOM Killer")
  2309  	}
  2310  
  2311  	taskCfg := newTaskConfig("", []string{"sh", "-c", `sleep 2 && x=a && while true; do x="$x$x"; done`})
  2312  	task := &drivers.TaskConfig{
  2313  		ID:        uuid.Generate(),
  2314  		Name:      "oom-killed",
  2315  		AllocID:   uuid.Generate(),
  2316  		Resources: basicResources,
  2317  	}
  2318  	task.Resources.LinuxResources.MemoryLimitBytes = 10 * 1024 * 1024
  2319  	task.Resources.NomadResources.Memory.MemoryMB = 10
  2320  
  2321  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
  2322  
  2323  	d := dockerDriverHarness(t, nil)
  2324  	cleanup := d.MkAllocDir(task, true)
  2325  	defer cleanup()
  2326  	copyImage(t, task.TaskDir(), "busybox.tar")
  2327  
  2328  	_, _, err := d.StartTask(task)
  2329  	require.NoError(t, err)
  2330  
  2331  	defer d.DestroyTask(task.ID, true)
  2332  
  2333  	waitCh, err := d.WaitTask(context.Background(), task.ID)
  2334  	require.NoError(t, err)
  2335  	select {
  2336  	case res := <-waitCh:
  2337  		if res.Successful() {
  2338  			t.Fatalf("expected error, but container exited successful")
  2339  		}
  2340  
  2341  		if !res.OOMKilled {
  2342  			t.Fatalf("not killed by OOM killer: %s", res.Err)
  2343  		}
  2344  
  2345  		t.Logf("Successfully killed by OOM killer")
  2346  
  2347  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
  2348  		t.Fatalf("timeout")
  2349  	}
  2350  }
  2351  
  2352  func TestDockerDriver_Devices_IsInvalidConfig(t *testing.T) {
  2353  	if !tu.IsCI() {
  2354  		t.Parallel()
  2355  	}
  2356  	testutil.DockerCompatible(t)
  2357  
  2358  	brokenConfigs := []DockerDevice{
  2359  		{
  2360  			HostPath: "",
  2361  		},
  2362  		{
  2363  			HostPath:          "/dev/sda1",
  2364  			CgroupPermissions: "rxb",
  2365  		},
  2366  	}
  2367  
  2368  	testCases := []struct {
  2369  		deviceConfig []DockerDevice
  2370  		err          error
  2371  	}{
  2372  		{brokenConfigs[:1], fmt.Errorf("host path must be set in configuration for devices")},
  2373  		{brokenConfigs[1:], fmt.Errorf("invalid cgroup permission string: \"rxb\"")},
  2374  	}
  2375  
  2376  	for _, tc := range testCases {
  2377  		task, cfg, ports := dockerTask(t)
  2378  		cfg.Devices = tc.deviceConfig
  2379  		require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2380  		d := dockerDriverHarness(t, nil)
  2381  		cleanup := d.MkAllocDir(task, true)
  2382  		copyImage(t, task.TaskDir(), "busybox.tar")
  2383  		defer cleanup()
  2384  
  2385  		_, _, err := d.StartTask(task)
  2386  		require.Error(t, err)
  2387  		require.Contains(t, err.Error(), tc.err.Error())
  2388  		freeport.Return(ports)
  2389  	}
  2390  }
  2391  
  2392  func TestDockerDriver_Device_Success(t *testing.T) {
  2393  	if !tu.IsCI() {
  2394  		t.Parallel()
  2395  	}
  2396  	testutil.DockerCompatible(t)
  2397  
  2398  	if runtime.GOOS != "linux" {
  2399  		t.Skip("test device mounts only on linux")
  2400  	}
  2401  
  2402  	hostPath := "/dev/random"
  2403  	containerPath := "/dev/myrandom"
  2404  	perms := "rwm"
  2405  
  2406  	expectedDevice := docker.Device{
  2407  		PathOnHost:        hostPath,
  2408  		PathInContainer:   containerPath,
  2409  		CgroupPermissions: perms,
  2410  	}
  2411  	config := DockerDevice{
  2412  		HostPath:      hostPath,
  2413  		ContainerPath: containerPath,
  2414  	}
  2415  
  2416  	task, cfg, ports := dockerTask(t)
  2417  	defer freeport.Return(ports)
  2418  	cfg.Devices = []DockerDevice{config}
  2419  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2420  
  2421  	client, driver, handle, cleanup := dockerSetup(t, task, nil)
  2422  	defer cleanup()
  2423  	require.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second))
  2424  
  2425  	container, err := client.InspectContainer(handle.containerID)
  2426  	require.NoError(t, err)
  2427  
  2428  	require.NotEmpty(t, container.HostConfig.Devices, "Expected one device")
  2429  	require.Equal(t, expectedDevice, container.HostConfig.Devices[0], "Incorrect device ")
  2430  }
  2431  
  2432  func TestDockerDriver_Entrypoint(t *testing.T) {
  2433  	if !tu.IsCI() {
  2434  		t.Parallel()
  2435  	}
  2436  	testutil.DockerCompatible(t)
  2437  
  2438  	entrypoint := []string{"sh", "-c"}
  2439  	task, cfg, ports := dockerTask(t)
  2440  	defer freeport.Return(ports)
  2441  	cfg.Entrypoint = entrypoint
  2442  	cfg.Command = strings.Join(busyboxLongRunningCmd, " ")
  2443  	cfg.Args = []string{}
  2444  
  2445  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2446  
  2447  	client, driver, handle, cleanup := dockerSetup(t, task, nil)
  2448  	defer cleanup()
  2449  
  2450  	require.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second))
  2451  
  2452  	container, err := client.InspectContainer(handle.containerID)
  2453  	require.NoError(t, err)
  2454  
  2455  	require.Len(t, container.Config.Entrypoint, 2, "Expected one entrypoint")
  2456  	require.Equal(t, entrypoint, container.Config.Entrypoint, "Incorrect entrypoint ")
  2457  }
  2458  
  2459  func TestDockerDriver_ReadonlyRootfs(t *testing.T) {
  2460  	if !tu.IsCI() {
  2461  		t.Parallel()
  2462  	}
  2463  	testutil.DockerCompatible(t)
  2464  
  2465  	if runtime.GOOS == "windows" {
  2466  		t.Skip("Windows Docker does not support root filesystem in read-only mode")
  2467  	}
  2468  
  2469  	task, cfg, ports := dockerTask(t)
  2470  	defer freeport.Return(ports)
  2471  	cfg.ReadonlyRootfs = true
  2472  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2473  
  2474  	client, driver, handle, cleanup := dockerSetup(t, task, nil)
  2475  	defer cleanup()
  2476  	require.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second))
  2477  
  2478  	container, err := client.InspectContainer(handle.containerID)
  2479  	require.NoError(t, err)
  2480  
  2481  	require.True(t, container.HostConfig.ReadonlyRootfs, "ReadonlyRootfs option not set")
  2482  }
  2483  
  2484  // fakeDockerClient can be used in places that accept an interface for the
  2485  // docker client such as createContainer.
  2486  type fakeDockerClient struct{}
  2487  
  2488  func (fakeDockerClient) CreateContainer(docker.CreateContainerOptions) (*docker.Container, error) {
  2489  	return nil, fmt.Errorf("volume is attached on another node")
  2490  }
  2491  func (fakeDockerClient) InspectContainer(id string) (*docker.Container, error) {
  2492  	panic("not implemented")
  2493  }
  2494  func (fakeDockerClient) ListContainers(docker.ListContainersOptions) ([]docker.APIContainers, error) {
  2495  	panic("not implemented")
  2496  }
  2497  func (fakeDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error {
  2498  	panic("not implemented")
  2499  }
  2500  
  2501  // TestDockerDriver_VolumeError asserts volume related errors when creating a
  2502  // container are recoverable.
  2503  func TestDockerDriver_VolumeError(t *testing.T) {
  2504  	if !tu.IsCI() {
  2505  		t.Parallel()
  2506  	}
  2507  
  2508  	// setup
  2509  	_, cfg, ports := dockerTask(t)
  2510  	defer freeport.Return(ports)
  2511  	driver := dockerDriverHarness(t, nil)
  2512  
  2513  	// assert volume error is recoverable
  2514  	_, err := driver.Impl().(*Driver).createContainer(fakeDockerClient{}, docker.CreateContainerOptions{Config: &docker.Config{}}, cfg.Image)
  2515  	require.True(t, structs.IsRecoverable(err))
  2516  }
  2517  
  2518  func TestDockerDriver_AdvertiseIPv6Address(t *testing.T) {
  2519  	if !tu.IsCI() {
  2520  		t.Parallel()
  2521  	}
  2522  	testutil.DockerCompatible(t)
  2523  
  2524  	expectedPrefix := "2001:db8:1::242:ac11"
  2525  	expectedAdvertise := true
  2526  	task, cfg, ports := dockerTask(t)
  2527  	defer freeport.Return(ports)
  2528  	cfg.AdvertiseIPv6Addr = expectedAdvertise
  2529  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2530  
  2531  	client := newTestDockerClient(t)
  2532  
  2533  	// Make sure IPv6 is enabled
  2534  	net, err := client.NetworkInfo("bridge")
  2535  	if err != nil {
  2536  		t.Skip("error retrieving bridge network information, skipping")
  2537  	}
  2538  	if net == nil || !net.EnableIPv6 {
  2539  		t.Skip("IPv6 not enabled on bridge network, skipping")
  2540  	}
  2541  
  2542  	driver := dockerDriverHarness(t, nil)
  2543  	cleanup := driver.MkAllocDir(task, true)
  2544  	copyImage(t, task.TaskDir(), "busybox.tar")
  2545  	defer cleanup()
  2546  
  2547  	_, network, err := driver.StartTask(task)
  2548  	defer driver.DestroyTask(task.ID, true)
  2549  	require.NoError(t, err)
  2550  
  2551  	require.Equal(t, expectedAdvertise, network.AutoAdvertise, "Wrong autoadvertise. Expect: %s, got: %s", expectedAdvertise, network.AutoAdvertise)
  2552  
  2553  	if !strings.HasPrefix(network.IP, expectedPrefix) {
  2554  		t.Fatalf("Got IP address %q want ip address with prefix %q", network.IP, expectedPrefix)
  2555  	}
  2556  
  2557  	handle, ok := driver.Impl().(*Driver).tasks.Get(task.ID)
  2558  	require.True(t, ok)
  2559  
  2560  	require.NoError(t, driver.WaitUntilStarted(task.ID, time.Second))
  2561  
  2562  	container, err := client.InspectContainer(handle.containerID)
  2563  	require.NoError(t, err)
  2564  
  2565  	if !strings.HasPrefix(container.NetworkSettings.GlobalIPv6Address, expectedPrefix) {
  2566  		t.Fatalf("Got GlobalIPv6address %s want GlobalIPv6address with prefix %s", expectedPrefix, container.NetworkSettings.GlobalIPv6Address)
  2567  	}
  2568  }
  2569  
  2570  func TestParseDockerImage(t *testing.T) {
  2571  	tests := []struct {
  2572  		Image string
  2573  		Repo  string
  2574  		Tag   string
  2575  	}{
  2576  		{"library/hello-world:1.0", "library/hello-world", "1.0"},
  2577  		{"library/hello-world", "library/hello-world", "latest"},
  2578  		{"library/hello-world:latest", "library/hello-world", "latest"},
  2579  		{"library/hello-world@sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", "library/hello-world@sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", ""},
  2580  	}
  2581  	for _, test := range tests {
  2582  		t.Run(test.Image, func(t *testing.T) {
  2583  			repo, tag := parseDockerImage(test.Image)
  2584  			require.Equal(t, test.Repo, repo)
  2585  			require.Equal(t, test.Tag, tag)
  2586  		})
  2587  	}
  2588  }
  2589  
  2590  func TestDockerImageRef(t *testing.T) {
  2591  	tests := []struct {
  2592  		Image string
  2593  		Repo  string
  2594  		Tag   string
  2595  	}{
  2596  		{"library/hello-world:1.0", "library/hello-world", "1.0"},
  2597  		{"library/hello-world:latest", "library/hello-world", "latest"},
  2598  		{"library/hello-world@sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", "library/hello-world@sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77", ""},
  2599  	}
  2600  	for _, test := range tests {
  2601  		t.Run(test.Image, func(t *testing.T) {
  2602  			image := dockerImageRef(test.Repo, test.Tag)
  2603  			require.Equal(t, test.Image, image)
  2604  		})
  2605  	}
  2606  }
  2607  
  2608  func waitForExist(t *testing.T, client *docker.Client, containerID string) {
  2609  	tu.WaitForResult(func() (bool, error) {
  2610  		container, err := client.InspectContainer(containerID)
  2611  		if err != nil {
  2612  			if _, ok := err.(*docker.NoSuchContainer); !ok {
  2613  				return false, err
  2614  			}
  2615  		}
  2616  
  2617  		return container != nil, nil
  2618  	}, func(err error) {
  2619  		require.NoError(t, err)
  2620  	})
  2621  }
  2622  
  2623  // TestDockerDriver_CreationIdempotent asserts that createContainer and
  2624  // and startContainers functions are idempotent, as we have some retry
  2625  // logic there without ensureing we delete/destroy containers
  2626  func TestDockerDriver_CreationIdempotent(t *testing.T) {
  2627  	if !tu.IsCI() {
  2628  		t.Parallel()
  2629  	}
  2630  	testutil.DockerCompatible(t)
  2631  
  2632  	task, cfg, ports := dockerTask(t)
  2633  	defer freeport.Return(ports)
  2634  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
  2635  
  2636  	client := newTestDockerClient(t)
  2637  	driver := dockerDriverHarness(t, nil)
  2638  	cleanup := driver.MkAllocDir(task, true)
  2639  	defer cleanup()
  2640  
  2641  	copyImage(t, task.TaskDir(), "busybox.tar")
  2642  
  2643  	d, ok := driver.Impl().(*Driver)
  2644  	require.True(t, ok)
  2645  
  2646  	_, err := d.createImage(task, cfg, client)
  2647  	require.NoError(t, err)
  2648  
  2649  	containerCfg, err := d.createContainerConfig(task, cfg, cfg.Image)
  2650  	require.NoError(t, err)
  2651  
  2652  	c, err := d.createContainer(client, containerCfg, cfg.Image)
  2653  	require.NoError(t, err)
  2654  	defer client.RemoveContainer(docker.RemoveContainerOptions{
  2655  		ID:    c.ID,
  2656  		Force: true,
  2657  	})
  2658  
  2659  	// calling createContainer again creates a new one and remove old one
  2660  	c2, err := d.createContainer(client, containerCfg, cfg.Image)
  2661  	require.NoError(t, err)
  2662  	defer client.RemoveContainer(docker.RemoveContainerOptions{
  2663  		ID:    c2.ID,
  2664  		Force: true,
  2665  	})
  2666  
  2667  	require.NotEqual(t, c.ID, c2.ID)
  2668  	// old container was destroyed
  2669  	{
  2670  		_, err := client.InspectContainer(c.ID)
  2671  		require.Error(t, err)
  2672  		require.Contains(t, err.Error(), NoSuchContainerError)
  2673  	}
  2674  
  2675  	// now start container twice
  2676  	require.NoError(t, d.startContainer(c2))
  2677  	require.NoError(t, d.startContainer(c2))
  2678  
  2679  	tu.WaitForResult(func() (bool, error) {
  2680  		c, err := client.InspectContainer(c2.ID)
  2681  		if err != nil {
  2682  			return false, fmt.Errorf("failed to get container status: %v", err)
  2683  		}
  2684  
  2685  		if !c.State.Running {
  2686  			return false, fmt.Errorf("container is not running but %v", c.State)
  2687  		}
  2688  
  2689  		return true, nil
  2690  	}, func(err error) {
  2691  		require.NoError(t, err)
  2692  	})
  2693  }
  2694  
  2695  // TestDockerDriver_CreateContainerConfig_CPUHardLimit asserts that a default
  2696  // CPU quota and period are set when cpu_hard_limit = true.
  2697  func TestDockerDriver_CreateContainerConfig_CPUHardLimit(t *testing.T) {
  2698  	t.Parallel()
  2699  
  2700  	task, _, ports := dockerTask(t)
  2701  	defer freeport.Return(ports)
  2702  
  2703  	dh := dockerDriverHarness(t, nil)
  2704  	driver := dh.Impl().(*Driver)
  2705  	schema, _ := driver.TaskConfigSchema()
  2706  	spec, _ := hclspecutils.Convert(schema)
  2707  
  2708  	val, _, _ := hclutils.ParseHclInterface(map[string]interface{}{
  2709  		"image":          "foo/bar",
  2710  		"cpu_hard_limit": true,
  2711  	}, spec, nil)
  2712  
  2713  	require.NoError(t, task.EncodeDriverConfig(val))
  2714  	cfg := &TaskConfig{}
  2715  	require.NoError(t, task.DecodeDriverConfig(cfg))
  2716  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
  2717  	require.NoError(t, err)
  2718  
  2719  	require.NotZero(t, c.HostConfig.CPUQuota)
  2720  	require.NotZero(t, c.HostConfig.CPUPeriod)
  2721  }
  2722  
  2723  func TestDockerDriver_memoryLimits(t *testing.T) {
  2724  	t.Parallel()
  2725  
  2726  	t.Run("driver hard limit not set", func(t *testing.T) {
  2727  		memory, memoryReservation := new(Driver).memoryLimits(0, 256*1024*1024)
  2728  		require.Equal(t, int64(256*1024*1024), memory)
  2729  		require.Equal(t, int64(0), memoryReservation)
  2730  	})
  2731  
  2732  	t.Run("driver hard limit is set", func(t *testing.T) {
  2733  		memory, memoryReservation := new(Driver).memoryLimits(512, 256*1024*1024)
  2734  		require.Equal(t, int64(512*1024*1024), memory)
  2735  		require.Equal(t, int64(256*1024*1024), memoryReservation)
  2736  	})
  2737  }