github.com/quite/nomad@v0.8.6/client/driver/docker_test.go (about)

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