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