github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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/debug"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  	"testing"
    15  	"time"
    16  
    17  	docker "github.com/fsouza/go-dockerclient"
    18  	"github.com/hashicorp/nomad/client/allocdir"
    19  	"github.com/hashicorp/nomad/client/config"
    20  	"github.com/hashicorp/nomad/client/driver/env"
    21  	"github.com/hashicorp/nomad/client/testutil"
    22  	"github.com/hashicorp/nomad/nomad/mock"
    23  	"github.com/hashicorp/nomad/nomad/structs"
    24  	tu "github.com/hashicorp/nomad/testutil"
    25  )
    26  
    27  func dockerIsRemote(t *testing.T) bool {
    28  	client, err := docker.NewClientFromEnv()
    29  	if err != nil {
    30  		return false
    31  	}
    32  
    33  	// Technically this could be a local tcp socket but for testing purposes
    34  	// we'll just assume that tcp is only used for remote connections.
    35  	if client.Endpoint()[0:3] == "tcp" {
    36  		return true
    37  	}
    38  	return false
    39  }
    40  
    41  // Ports used by tests
    42  var (
    43  	docker_reserved = 32768 + int(rand.Int31n(25000))
    44  	docker_dynamic  = 32768 + int(rand.Int31n(25000))
    45  )
    46  
    47  // Returns a task with a reserved and dynamic port. The ports are returned
    48  // respectively.
    49  func dockerTask() (*structs.Task, int, int) {
    50  	docker_reserved += 1
    51  	docker_dynamic += 1
    52  	return &structs.Task{
    53  		Name: "redis-demo",
    54  		Config: map[string]interface{}{
    55  			"image":   "busybox",
    56  			"load":    []string{"busybox.tar"},
    57  			"command": "/bin/nc",
    58  			"args":    []string{"-l", "127.0.0.1", "-p", "0"},
    59  		},
    60  		LogConfig: &structs.LogConfig{
    61  			MaxFiles:      10,
    62  			MaxFileSizeMB: 10,
    63  		},
    64  		Resources: &structs.Resources{
    65  			MemoryMB: 256,
    66  			CPU:      512,
    67  			Networks: []*structs.NetworkResource{
    68  				&structs.NetworkResource{
    69  					IP:            "127.0.0.1",
    70  					ReservedPorts: []structs.Port{{"main", docker_reserved}},
    71  					DynamicPorts:  []structs.Port{{"REDIS", docker_dynamic}},
    72  				},
    73  			},
    74  		},
    75  	}, docker_reserved, docker_dynamic
    76  }
    77  
    78  // dockerSetup does all of the basic setup you need to get a running docker
    79  // process up and running for testing. Use like:
    80  //
    81  //	task := taskTemplate()
    82  //	// do custom task configuration
    83  //	client, handle, cleanup := dockerSetup(t, task)
    84  //	defer cleanup()
    85  //	// do test stuff
    86  //
    87  // If there is a problem during setup this function will abort or skip the test
    88  // and indicate the reason.
    89  func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) {
    90  	if !testutil.DockerIsConnected(t) {
    91  		t.SkipNow()
    92  	}
    93  
    94  	client, err := docker.NewClientFromEnv()
    95  	if err != nil {
    96  		t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack())
    97  	}
    98  
    99  	driverCtx, execCtx := testDriverContexts(task)
   100  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   101  	driver := NewDockerDriver(driverCtx)
   102  	copyImage(execCtx, task, "busybox.tar", t)
   103  
   104  	handle, err := driver.Start(execCtx, task)
   105  	if err != nil {
   106  		execCtx.AllocDir.Destroy()
   107  		t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack())
   108  	}
   109  	if handle == nil {
   110  		execCtx.AllocDir.Destroy()
   111  		t.Fatalf("handle is nil\nStack\n%s", debug.Stack())
   112  	}
   113  
   114  	cleanup := func() {
   115  		handle.Kill()
   116  		execCtx.AllocDir.Destroy()
   117  	}
   118  
   119  	return client, handle, cleanup
   120  }
   121  
   122  // This test should always pass, even if docker daemon is not available
   123  func TestDockerDriver_Fingerprint(t *testing.T) {
   124  	driverCtx, execCtx := testDriverContexts(&structs.Task{Name: "foo", Resources: basicResources})
   125  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   126  	defer execCtx.AllocDir.Destroy()
   127  	d := NewDockerDriver(driverCtx)
   128  	node := &structs.Node{
   129  		Attributes: make(map[string]string),
   130  	}
   131  	apply, err := d.Fingerprint(&config.Config{}, node)
   132  	if err != nil {
   133  		t.Fatalf("err: %v", err)
   134  	}
   135  	if apply != testutil.DockerIsConnected(t) {
   136  		t.Fatalf("Fingerprinter should detect when docker is available")
   137  	}
   138  	if node.Attributes["driver.docker"] != "1" {
   139  		t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.")
   140  	}
   141  	t.Logf("Found docker version %s", node.Attributes["driver.docker.version"])
   142  }
   143  
   144  func TestDockerDriver_StartOpen_Wait(t *testing.T) {
   145  	if !testutil.DockerIsConnected(t) {
   146  		t.SkipNow()
   147  	}
   148  
   149  	task := &structs.Task{
   150  		Name: "nc-demo",
   151  		Config: map[string]interface{}{
   152  			"load":    []string{"busybox.tar"},
   153  			"image":   "busybox",
   154  			"command": "/bin/nc",
   155  			"args":    []string{"-l", "127.0.0.1", "-p", "0"},
   156  		},
   157  		LogConfig: &structs.LogConfig{
   158  			MaxFiles:      10,
   159  			MaxFileSizeMB: 10,
   160  		},
   161  		Resources: basicResources,
   162  	}
   163  
   164  	driverCtx, execCtx := testDriverContexts(task)
   165  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   166  	defer execCtx.AllocDir.Destroy()
   167  	d := NewDockerDriver(driverCtx)
   168  	copyImage(execCtx, task, "busybox.tar", t)
   169  
   170  	handle, err := d.Start(execCtx, task)
   171  	if err != nil {
   172  		t.Fatalf("err: %v", err)
   173  	}
   174  	if handle == nil {
   175  		t.Fatalf("missing handle")
   176  	}
   177  	defer handle.Kill()
   178  
   179  	// Attempt to open
   180  	handle2, err := d.Open(execCtx, handle.ID())
   181  	if err != nil {
   182  		t.Fatalf("err: %v", err)
   183  	}
   184  	if handle2 == nil {
   185  		t.Fatalf("missing handle")
   186  	}
   187  }
   188  
   189  func TestDockerDriver_Start_Wait(t *testing.T) {
   190  	task := &structs.Task{
   191  		Name: "nc-demo",
   192  		Config: map[string]interface{}{
   193  			"load":    []string{"busybox.tar"},
   194  			"image":   "busybox",
   195  			"command": "/bin/echo",
   196  			"args":    []string{"hello"},
   197  		},
   198  		Resources: &structs.Resources{
   199  			MemoryMB: 256,
   200  			CPU:      512,
   201  		},
   202  		LogConfig: &structs.LogConfig{
   203  			MaxFiles:      10,
   204  			MaxFileSizeMB: 10,
   205  		},
   206  	}
   207  
   208  	_, handle, cleanup := dockerSetup(t, task)
   209  	defer cleanup()
   210  
   211  	// Update should be a no-op
   212  	err := handle.Update(task)
   213  	if err != nil {
   214  		t.Fatalf("err: %v", err)
   215  	}
   216  
   217  	select {
   218  	case res := <-handle.WaitCh():
   219  		if !res.Successful() {
   220  			t.Fatalf("err: %v", res)
   221  		}
   222  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   223  		t.Fatalf("timeout")
   224  	}
   225  }
   226  
   227  func TestDockerDriver_Start_LoadImage(t *testing.T) {
   228  	if !testutil.DockerIsConnected(t) {
   229  		t.SkipNow()
   230  	}
   231  	task := &structs.Task{
   232  		Name: "busybox-demo",
   233  		Config: map[string]interface{}{
   234  			"image":   "busybox",
   235  			"load":    []string{"busybox.tar"},
   236  			"command": "/bin/echo",
   237  			"args": []string{
   238  				"hello",
   239  			},
   240  		},
   241  		LogConfig: &structs.LogConfig{
   242  			MaxFiles:      10,
   243  			MaxFileSizeMB: 10,
   244  		},
   245  		Resources: &structs.Resources{
   246  			MemoryMB: 256,
   247  			CPU:      512,
   248  		},
   249  	}
   250  
   251  	driverCtx, execCtx := testDriverContexts(task)
   252  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   253  	defer execCtx.AllocDir.Destroy()
   254  	d := NewDockerDriver(driverCtx)
   255  
   256  	// Copy the image into the task's directory
   257  	copyImage(execCtx, task, "busybox.tar", t)
   258  
   259  	handle, err := d.Start(execCtx, task)
   260  	if err != nil {
   261  		t.Fatalf("err: %v", err)
   262  	}
   263  	if handle == nil {
   264  		t.Fatalf("missing handle")
   265  	}
   266  	defer handle.Kill()
   267  
   268  	select {
   269  	case res := <-handle.WaitCh():
   270  		if !res.Successful() {
   271  			t.Fatalf("err: %v", res)
   272  		}
   273  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   274  		t.Fatalf("timeout")
   275  	}
   276  
   277  	// Check that data was written to the shared alloc directory.
   278  	outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "busybox-demo.stdout.0")
   279  	act, err := ioutil.ReadFile(outputFile)
   280  	if err != nil {
   281  		t.Fatalf("Couldn't read expected output: %v", err)
   282  	}
   283  
   284  	exp := "hello"
   285  	if strings.TrimSpace(string(act)) != exp {
   286  		t.Fatalf("Command outputted %v; want %v", act, exp)
   287  	}
   288  
   289  }
   290  
   291  func TestDockerDriver_Start_BadPull_Recoverable(t *testing.T) {
   292  	if !testutil.DockerIsConnected(t) {
   293  		t.SkipNow()
   294  	}
   295  	task := &structs.Task{
   296  		Name: "busybox-demo",
   297  		Config: map[string]interface{}{
   298  			"image":   "127.0.1.1:32121/foo", // bad path
   299  			"command": "/bin/echo",
   300  			"args": []string{
   301  				"hello",
   302  			},
   303  		},
   304  		LogConfig: &structs.LogConfig{
   305  			MaxFiles:      10,
   306  			MaxFileSizeMB: 10,
   307  		},
   308  		Resources: &structs.Resources{
   309  			MemoryMB: 256,
   310  			CPU:      512,
   311  		},
   312  	}
   313  
   314  	driverCtx, execCtx := testDriverContexts(task)
   315  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   316  	defer execCtx.AllocDir.Destroy()
   317  	d := NewDockerDriver(driverCtx)
   318  
   319  	_, err := d.Start(execCtx, task)
   320  	if err == nil {
   321  		t.Fatalf("want err: %v", err)
   322  	}
   323  
   324  	if rerr, ok := err.(*structs.RecoverableError); !ok {
   325  		t.Fatalf("want recoverable error: %+v", err)
   326  	} else if !rerr.Recoverable {
   327  		t.Fatalf("error not recoverable: %+v", err)
   328  	}
   329  }
   330  
   331  func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
   332  	// This test requires that the alloc dir be mounted into docker as a volume.
   333  	// Because this cannot happen when docker is run remotely, e.g. when running
   334  	// docker in a VM, we skip this when we detect Docker is being run remotely.
   335  	if !testutil.DockerIsConnected(t) || dockerIsRemote(t) {
   336  		t.SkipNow()
   337  	}
   338  
   339  	exp := []byte{'w', 'i', 'n'}
   340  	file := "output.txt"
   341  	task := &structs.Task{
   342  		Name: "nc-demo",
   343  		Config: map[string]interface{}{
   344  			"image":   "busybox",
   345  			"load":    []string{"busybox.tar"},
   346  			"command": "/bin/sh",
   347  			"args": []string{
   348  				"-c",
   349  				fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`,
   350  					string(exp), env.AllocDir, file),
   351  			},
   352  		},
   353  		LogConfig: &structs.LogConfig{
   354  			MaxFiles:      10,
   355  			MaxFileSizeMB: 10,
   356  		},
   357  		Resources: &structs.Resources{
   358  			MemoryMB: 256,
   359  			CPU:      512,
   360  		},
   361  	}
   362  
   363  	driverCtx, execCtx := testDriverContexts(task)
   364  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   365  	defer execCtx.AllocDir.Destroy()
   366  	d := NewDockerDriver(driverCtx)
   367  	copyImage(execCtx, task, "busybox.tar", t)
   368  
   369  	handle, err := d.Start(execCtx, task)
   370  	if err != nil {
   371  		t.Fatalf("err: %v", err)
   372  	}
   373  	if handle == nil {
   374  		t.Fatalf("missing handle")
   375  	}
   376  	defer handle.Kill()
   377  
   378  	select {
   379  	case res := <-handle.WaitCh():
   380  		if !res.Successful() {
   381  			t.Fatalf("err: %v", res)
   382  		}
   383  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   384  		t.Fatalf("timeout")
   385  	}
   386  
   387  	// Check that data was written to the shared alloc directory.
   388  	outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file)
   389  	act, err := ioutil.ReadFile(outputFile)
   390  	if err != nil {
   391  		t.Fatalf("Couldn't read expected output: %v", err)
   392  	}
   393  
   394  	if !reflect.DeepEqual(act, exp) {
   395  		t.Fatalf("Command outputted %v; want %v", act, exp)
   396  	}
   397  }
   398  
   399  func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
   400  	task := &structs.Task{
   401  		Name: "nc-demo",
   402  		Config: map[string]interface{}{
   403  			"image":   "busybox",
   404  			"load":    []string{"busybox.tar"},
   405  			"command": "/bin/sleep",
   406  			"args":    []string{"10"},
   407  		},
   408  		LogConfig: &structs.LogConfig{
   409  			MaxFiles:      10,
   410  			MaxFileSizeMB: 10,
   411  		},
   412  		Resources: basicResources,
   413  	}
   414  
   415  	_, handle, cleanup := dockerSetup(t, task)
   416  	defer cleanup()
   417  
   418  	go func() {
   419  		time.Sleep(100 * time.Millisecond)
   420  		err := handle.Kill()
   421  		if err != nil {
   422  			t.Fatalf("err: %v", err)
   423  		}
   424  	}()
   425  
   426  	select {
   427  	case res := <-handle.WaitCh():
   428  		if res.Successful() {
   429  			t.Fatalf("should err: %v", res)
   430  		}
   431  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
   432  		t.Fatalf("timeout")
   433  	}
   434  }
   435  
   436  func TestDockerDriver_StartN(t *testing.T) {
   437  	if !testutil.DockerIsConnected(t) {
   438  		t.SkipNow()
   439  	}
   440  
   441  	task1, _, _ := dockerTask()
   442  	task2, _, _ := dockerTask()
   443  	task3, _, _ := dockerTask()
   444  	taskList := []*structs.Task{task1, task2, task3}
   445  
   446  	handles := make([]DriverHandle, len(taskList))
   447  
   448  	t.Logf("Starting %d tasks", len(taskList))
   449  
   450  	// Let's spin up a bunch of things
   451  	var err error
   452  	for idx, task := range taskList {
   453  		driverCtx, execCtx := testDriverContexts(task)
   454  		driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   455  		defer execCtx.AllocDir.Destroy()
   456  		d := NewDockerDriver(driverCtx)
   457  		copyImage(execCtx, task, "busybox.tar", t)
   458  
   459  		handles[idx], err = d.Start(execCtx, task)
   460  		if err != nil {
   461  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   462  		}
   463  	}
   464  
   465  	t.Log("All tasks are started. Terminating...")
   466  
   467  	for idx, handle := range handles {
   468  		if handle == nil {
   469  			t.Errorf("Bad handle for task #%d", idx+1)
   470  			continue
   471  		}
   472  
   473  		err := handle.Kill()
   474  		if err != nil {
   475  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   476  		}
   477  	}
   478  
   479  	t.Log("Test complete!")
   480  }
   481  
   482  func TestDockerDriver_StartNVersions(t *testing.T) {
   483  	if !testutil.DockerIsConnected(t) {
   484  		t.SkipNow()
   485  	}
   486  
   487  	task1, _, _ := dockerTask()
   488  	task1.Config["image"] = "busybox"
   489  	task1.Config["load"] = []string{"busybox.tar"}
   490  
   491  	task2, _, _ := dockerTask()
   492  	task2.Config["image"] = "busybox:musl"
   493  	task2.Config["load"] = []string{"busybox_musl.tar"}
   494  
   495  	task3, _, _ := dockerTask()
   496  	task3.Config["image"] = "busybox:glibc"
   497  	task3.Config["load"] = []string{"busybox_glibc.tar"}
   498  
   499  	taskList := []*structs.Task{task1, task2, task3}
   500  
   501  	handles := make([]DriverHandle, len(taskList))
   502  
   503  	t.Logf("Starting %d tasks", len(taskList))
   504  
   505  	// Let's spin up a bunch of things
   506  	var err error
   507  	for idx, task := range taskList {
   508  		driverCtx, execCtx := testDriverContexts(task)
   509  		driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   510  		defer execCtx.AllocDir.Destroy()
   511  		d := NewDockerDriver(driverCtx)
   512  		copyImage(execCtx, task, "busybox.tar", t)
   513  		copyImage(execCtx, task, "busybox_musl.tar", t)
   514  		copyImage(execCtx, task, "busybox_glibc.tar", t)
   515  
   516  		handles[idx], err = d.Start(execCtx, task)
   517  		if err != nil {
   518  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   519  		}
   520  	}
   521  
   522  	t.Log("All tasks are started. Terminating...")
   523  
   524  	for idx, handle := range handles {
   525  		if handle == nil {
   526  			t.Errorf("Bad handle for task #%d", idx+1)
   527  			continue
   528  		}
   529  
   530  		err := handle.Kill()
   531  		if err != nil {
   532  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   533  		}
   534  	}
   535  
   536  	t.Log("Test complete!")
   537  }
   538  
   539  func waitForExist(t *testing.T, client *docker.Client, handle *DockerHandle) {
   540  	tu.WaitForResult(func() (bool, error) {
   541  		container, err := client.InspectContainer(handle.ContainerID())
   542  		if err != nil {
   543  			if _, ok := err.(*docker.NoSuchContainer); !ok {
   544  				return false, err
   545  			}
   546  		}
   547  
   548  		return container != nil, nil
   549  	}, func(err error) {
   550  		t.Fatalf("err: %v", err)
   551  	})
   552  }
   553  
   554  func TestDockerDriver_NetworkMode_Host(t *testing.T) {
   555  	expected := "host"
   556  
   557  	task := &structs.Task{
   558  		Name: "nc-demo",
   559  		Config: map[string]interface{}{
   560  			"image":        "busybox",
   561  			"load":         []string{"busybox.tar"},
   562  			"command":      "/bin/nc",
   563  			"args":         []string{"-l", "127.0.0.1", "-p", "0"},
   564  			"network_mode": expected,
   565  		},
   566  		Resources: &structs.Resources{
   567  			MemoryMB: 256,
   568  			CPU:      512,
   569  		},
   570  		LogConfig: &structs.LogConfig{
   571  			MaxFiles:      10,
   572  			MaxFileSizeMB: 10,
   573  		},
   574  	}
   575  
   576  	client, handle, cleanup := dockerSetup(t, task)
   577  	defer cleanup()
   578  
   579  	waitForExist(t, client, handle.(*DockerHandle))
   580  
   581  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   582  	if err != nil {
   583  		t.Fatalf("err: %v", err)
   584  	}
   585  
   586  	actual := container.HostConfig.NetworkMode
   587  	if actual != expected {
   588  		t.Fatalf("Got network mode %q; want %q", expected, actual)
   589  	}
   590  }
   591  
   592  func TestDockerDriver_Labels(t *testing.T) {
   593  	task, _, _ := dockerTask()
   594  	task.Config["labels"] = []map[string]string{
   595  		map[string]string{
   596  			"label1": "value1",
   597  			"label2": "value2",
   598  		},
   599  	}
   600  
   601  	client, handle, cleanup := dockerSetup(t, task)
   602  	defer cleanup()
   603  
   604  	waitForExist(t, client, handle.(*DockerHandle))
   605  
   606  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   607  	if err != nil {
   608  		t.Fatalf("err: %v", err)
   609  	}
   610  
   611  	if want, got := 2, len(container.Config.Labels); want != got {
   612  		t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got)
   613  	}
   614  
   615  	if want, got := "value1", container.Config.Labels["label1"]; want != got {
   616  		t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got)
   617  	}
   618  }
   619  
   620  func TestDockerDriver_DNS(t *testing.T) {
   621  	task, _, _ := dockerTask()
   622  	task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"}
   623  	task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"}
   624  
   625  	client, handle, cleanup := dockerSetup(t, task)
   626  	defer cleanup()
   627  
   628  	waitForExist(t, client, handle.(*DockerHandle))
   629  
   630  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   631  	if err != nil {
   632  		t.Fatalf("err: %v", err)
   633  	}
   634  
   635  	if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) {
   636  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS)
   637  	}
   638  
   639  	if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) {
   640  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch)
   641  	}
   642  }
   643  
   644  func TestDockerWorkDir(t *testing.T) {
   645  	task, _, _ := dockerTask()
   646  	task.Config["work_dir"] = "/some/path"
   647  
   648  	client, handle, cleanup := dockerSetup(t, task)
   649  	defer cleanup()
   650  
   651  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   652  	if err != nil {
   653  		t.Fatalf("err: %v", err)
   654  	}
   655  
   656  	if want, got := "/some/path", container.Config.WorkingDir; want != got {
   657  		t.Errorf("Wrong working directory for docker job. Expect: %d, got: %d", want, got)
   658  	}
   659  }
   660  
   661  func inSlice(needle string, haystack []string) bool {
   662  	for _, h := range haystack {
   663  		if h == needle {
   664  			return true
   665  		}
   666  	}
   667  	return false
   668  }
   669  
   670  func TestDockerDriver_PortsNoMap(t *testing.T) {
   671  	task, res, dyn := dockerTask()
   672  
   673  	client, handle, cleanup := dockerSetup(t, task)
   674  	defer cleanup()
   675  
   676  	waitForExist(t, client, handle.(*DockerHandle))
   677  
   678  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   679  	if err != nil {
   680  		t.Fatalf("err: %v", err)
   681  	}
   682  
   683  	// Verify that the correct ports are EXPOSED
   684  	expectedExposedPorts := map[docker.Port]struct{}{
   685  		docker.Port(fmt.Sprintf("%d/tcp", res)): struct{}{},
   686  		docker.Port(fmt.Sprintf("%d/udp", res)): struct{}{},
   687  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): struct{}{},
   688  		docker.Port(fmt.Sprintf("%d/udp", dyn)): struct{}{},
   689  	}
   690  
   691  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   692  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   693  	}
   694  
   695  	// Verify that the correct ports are FORWARDED
   696  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   697  		docker.Port(fmt.Sprintf("%d/tcp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   698  		docker.Port(fmt.Sprintf("%d/udp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   699  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   700  		docker.Port(fmt.Sprintf("%d/udp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   701  	}
   702  
   703  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   704  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   705  	}
   706  
   707  	expectedEnvironment := map[string]string{
   708  		"NOMAD_ADDR_main":  fmt.Sprintf("127.0.0.1:%d", res),
   709  		"NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn),
   710  	}
   711  
   712  	for key, val := range expectedEnvironment {
   713  		search := fmt.Sprintf("%s=%s", key, val)
   714  		if !inSlice(search, container.Config.Env) {
   715  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   716  		}
   717  	}
   718  }
   719  
   720  func TestDockerDriver_PortsMapping(t *testing.T) {
   721  	task, res, dyn := dockerTask()
   722  	task.Config["port_map"] = []map[string]string{
   723  		map[string]string{
   724  			"main":  "8080",
   725  			"REDIS": "6379",
   726  		},
   727  	}
   728  
   729  	client, handle, cleanup := dockerSetup(t, task)
   730  	defer cleanup()
   731  
   732  	waitForExist(t, client, handle.(*DockerHandle))
   733  
   734  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   735  	if err != nil {
   736  		t.Fatalf("err: %v", err)
   737  	}
   738  
   739  	// Verify that the correct ports are EXPOSED
   740  	expectedExposedPorts := map[docker.Port]struct{}{
   741  		docker.Port("8080/tcp"): struct{}{},
   742  		docker.Port("8080/udp"): struct{}{},
   743  		docker.Port("6379/tcp"): struct{}{},
   744  		docker.Port("6379/udp"): struct{}{},
   745  	}
   746  
   747  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   748  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   749  	}
   750  
   751  	// Verify that the correct ports are FORWARDED
   752  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   753  		docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   754  		docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   755  		docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   756  		docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   757  	}
   758  
   759  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   760  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   761  	}
   762  
   763  	expectedEnvironment := map[string]string{
   764  		"NOMAD_ADDR_main":      "127.0.0.1:8080",
   765  		"NOMAD_ADDR_REDIS":     "127.0.0.1:6379",
   766  		"NOMAD_HOST_PORT_main": strconv.Itoa(docker_reserved),
   767  	}
   768  
   769  	for key, val := range expectedEnvironment {
   770  		search := fmt.Sprintf("%s=%s", key, val)
   771  		if !inSlice(search, container.Config.Env) {
   772  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   773  		}
   774  	}
   775  }
   776  
   777  func TestDockerDriver_User(t *testing.T) {
   778  	task := &structs.Task{
   779  		Name: "redis-demo",
   780  		User: "alice",
   781  		Config: map[string]interface{}{
   782  			"image":   "busybox",
   783  			"load":    []string{"busybox.tar"},
   784  			"command": "/bin/sleep",
   785  			"args":    []string{"10000"},
   786  		},
   787  		Resources: &structs.Resources{
   788  			MemoryMB: 256,
   789  			CPU:      512,
   790  		},
   791  		LogConfig: &structs.LogConfig{
   792  			MaxFiles:      10,
   793  			MaxFileSizeMB: 10,
   794  		},
   795  	}
   796  
   797  	if !testutil.DockerIsConnected(t) {
   798  		t.SkipNow()
   799  	}
   800  
   801  	driverCtx, execCtx := testDriverContexts(task)
   802  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   803  	driver := NewDockerDriver(driverCtx)
   804  	defer execCtx.AllocDir.Destroy()
   805  	copyImage(execCtx, task, "busybox.tar", t)
   806  
   807  	// It should fail because the user "alice" does not exist on the given
   808  	// image.
   809  	handle, err := driver.Start(execCtx, task)
   810  	if err == nil {
   811  		handle.Kill()
   812  		t.Fatalf("Should've failed")
   813  	}
   814  
   815  	if !strings.Contains(err.Error(), "alice") {
   816  		t.Fatalf("Expected failure string not found, found %q instead", err.Error())
   817  	}
   818  }
   819  
   820  func TestDockerDriver_CleanupContainer(t *testing.T) {
   821  	task := &structs.Task{
   822  		Name: "redis-demo",
   823  		Config: map[string]interface{}{
   824  			"image":   "busybox",
   825  			"load":    []string{"busybox.tar"},
   826  			"command": "/bin/echo",
   827  			"args":    []string{"hello"},
   828  		},
   829  		Resources: &structs.Resources{
   830  			MemoryMB: 256,
   831  			CPU:      512,
   832  		},
   833  		LogConfig: &structs.LogConfig{
   834  			MaxFiles:      10,
   835  			MaxFileSizeMB: 10,
   836  		},
   837  	}
   838  
   839  	_, handle, cleanup := dockerSetup(t, task)
   840  	defer cleanup()
   841  
   842  	// Update should be a no-op
   843  	err := handle.Update(task)
   844  	if err != nil {
   845  		t.Fatalf("err: %v", err)
   846  	}
   847  
   848  	select {
   849  	case res := <-handle.WaitCh():
   850  		if !res.Successful() {
   851  			t.Fatalf("err: %v", res)
   852  		}
   853  
   854  		time.Sleep(3 * time.Second)
   855  
   856  		// Ensure that the container isn't present
   857  		_, err := client.InspectContainer(handle.(*DockerHandle).containerID)
   858  		if err == nil {
   859  			t.Fatalf("expected to not get container")
   860  		}
   861  
   862  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   863  		t.Fatalf("timeout")
   864  	}
   865  }
   866  
   867  func TestDockerDriver_Stats(t *testing.T) {
   868  	task := &structs.Task{
   869  		Name: "sleep",
   870  		Config: map[string]interface{}{
   871  			"image":   "busybox",
   872  			"load":    []string{"busybox.tar"},
   873  			"command": "/bin/sleep",
   874  			"args":    []string{"100"},
   875  		},
   876  		LogConfig: &structs.LogConfig{
   877  			MaxFiles:      10,
   878  			MaxFileSizeMB: 10,
   879  		},
   880  		Resources: basicResources,
   881  	}
   882  
   883  	_, handle, cleanup := dockerSetup(t, task)
   884  	defer cleanup()
   885  
   886  	waitForExist(t, client, handle.(*DockerHandle))
   887  
   888  	go func() {
   889  		time.Sleep(3 * time.Second)
   890  		ru, err := handle.Stats()
   891  		if err != nil {
   892  			t.Fatalf("err: %v", err)
   893  		}
   894  		if ru.ResourceUsage == nil {
   895  			handle.Kill()
   896  			t.Fatalf("expected resource usage")
   897  		}
   898  		err = handle.Kill()
   899  		if err != nil {
   900  			t.Fatalf("err: %v", err)
   901  		}
   902  	}()
   903  
   904  	select {
   905  	case res := <-handle.WaitCh():
   906  		if res.Successful() {
   907  			t.Fatalf("should err: %v", res)
   908  		}
   909  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
   910  		t.Fatalf("timeout")
   911  	}
   912  }
   913  
   914  func TestDockerDriver_Signal(t *testing.T) {
   915  	task := &structs.Task{
   916  		Name: "redis-demo",
   917  		Config: map[string]interface{}{
   918  			"image":   "busybox",
   919  			"load":    []string{"busybox.tar"},
   920  			"command": "/bin/sh",
   921  			"args":    []string{"local/test.sh"},
   922  		},
   923  		Resources: &structs.Resources{
   924  			MemoryMB: 256,
   925  			CPU:      512,
   926  		},
   927  		LogConfig: &structs.LogConfig{
   928  			MaxFiles:      10,
   929  			MaxFileSizeMB: 10,
   930  		},
   931  	}
   932  
   933  	driverCtx, execCtx := testDriverContexts(task)
   934  	driverCtx.config.Options = map[string]string{"docker.cleanup.image": "false"}
   935  	defer execCtx.AllocDir.Destroy()
   936  	d := NewDockerDriver(driverCtx)
   937  
   938  	// Copy the image into the task's directory
   939  	copyImage(execCtx, task, "busybox.tar", t)
   940  
   941  	testFile := filepath.Join(execCtx.AllocDir.TaskDirs["redis-demo"], allocdir.TaskLocal, "test.sh")
   942  	testData := []byte(`
   943  at_term() {
   944      echo 'Terminated.'
   945      exit 3
   946  }
   947  trap at_term USR1
   948  while true; do
   949      sleep 1
   950  done
   951  	`)
   952  	if err := ioutil.WriteFile(testFile, testData, 0777); err != nil {
   953  		fmt.Errorf("Failed to write data")
   954  	}
   955  
   956  	handle, err := d.Start(execCtx, task)
   957  	if err != nil {
   958  		t.Fatalf("err: %v", err)
   959  	}
   960  	if handle == nil {
   961  		t.Fatalf("missing handle")
   962  	}
   963  	defer handle.Kill()
   964  
   965  	waitForExist(t, handle.(*DockerHandle).client, handle.(*DockerHandle))
   966  
   967  	time.Sleep(1 * time.Second)
   968  	if err := handle.Signal(syscall.SIGUSR1); err != nil {
   969  		t.Fatalf("Signal returned an error: %v", err)
   970  	}
   971  
   972  	select {
   973  	case res := <-handle.WaitCh():
   974  		if res.Successful() {
   975  			t.Fatalf("should err: %v", res)
   976  		}
   977  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   978  		t.Fatalf("timeout")
   979  	}
   980  
   981  	// Check the log file to see it exited because of the signal
   982  	outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "redis-demo.stdout.0")
   983  	act, err := ioutil.ReadFile(outputFile)
   984  	if err != nil {
   985  		t.Fatalf("Couldn't read expected output: %v", err)
   986  	}
   987  
   988  	exp := "Terminated."
   989  	if strings.TrimSpace(string(act)) != exp {
   990  		t.Fatalf("Command outputted %v; want %v", act, exp)
   991  	}
   992  }
   993  
   994  func setupDockerVolumes(t *testing.T, cfg *config.Config, hostpath string) (*structs.Task, Driver, *ExecContext, string, func()) {
   995  	if !testutil.DockerIsConnected(t) {
   996  		t.SkipNow()
   997  	}
   998  
   999  	randfn := fmt.Sprintf("test-%d", rand.Int())
  1000  	hostfile := filepath.Join(hostpath, randfn)
  1001  	containerPath := "/mnt/vol"
  1002  	containerFile := filepath.Join(containerPath, randfn)
  1003  
  1004  	task := &structs.Task{
  1005  		Name: "ls",
  1006  		Env:  map[string]string{"VOL_PATH": containerPath},
  1007  		Config: map[string]interface{}{
  1008  			"image":   "busybox",
  1009  			"load":    []string{"busybox.tar"},
  1010  			"command": "touch",
  1011  			"args":    []string{containerFile},
  1012  			"volumes": []string{fmt.Sprintf("%s:${VOL_PATH}", hostpath)},
  1013  		},
  1014  		LogConfig: &structs.LogConfig{
  1015  			MaxFiles:      10,
  1016  			MaxFileSizeMB: 10,
  1017  		},
  1018  		Resources: basicResources,
  1019  	}
  1020  
  1021  	allocDir := allocdir.NewAllocDir(filepath.Join(cfg.AllocDir, structs.GenerateUUID()))
  1022  	allocDir.Build([]*structs.Task{task})
  1023  	alloc := mock.Alloc()
  1024  	execCtx := NewExecContext(allocDir, alloc.ID)
  1025  	cleanup := func() {
  1026  		execCtx.AllocDir.Destroy()
  1027  		if filepath.IsAbs(hostpath) {
  1028  			os.RemoveAll(hostpath)
  1029  		}
  1030  	}
  1031  
  1032  	taskEnv, err := GetTaskEnv(allocDir, cfg.Node, task, alloc, "")
  1033  	if err != nil {
  1034  		cleanup()
  1035  		t.Fatalf("Failed to get task env: %v", err)
  1036  	}
  1037  
  1038  	driverCtx := NewDriverContext(task.Name, cfg, cfg.Node, testLogger(), taskEnv)
  1039  	driver := NewDockerDriver(driverCtx)
  1040  	copyImage(execCtx, task, "busybox.tar", t)
  1041  
  1042  	return task, driver, execCtx, hostfile, cleanup
  1043  }
  1044  
  1045  func TestDockerDriver_VolumesDisabled(t *testing.T) {
  1046  	cfg := testConfig()
  1047  	cfg.Options = map[string]string{
  1048  		dockerVolumesConfigOption: "false",
  1049  		"docker.cleanup.image":    "false",
  1050  	}
  1051  
  1052  	{
  1053  		tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesdisabled")
  1054  		if err != nil {
  1055  			t.Fatalf("error creating temporary dir: %v", err)
  1056  		}
  1057  
  1058  		task, driver, execCtx, _, cleanup := setupDockerVolumes(t, cfg, tmpvol)
  1059  		defer cleanup()
  1060  
  1061  		if _, err := driver.Start(execCtx, task); err == nil {
  1062  			t.Fatalf("Started driver successfully when volumes should have been disabled.")
  1063  		}
  1064  	}
  1065  
  1066  	// Relative paths should still be allowed
  1067  	{
  1068  		task, driver, execCtx, fn, cleanup := setupDockerVolumes(t, cfg, ".")
  1069  		defer cleanup()
  1070  
  1071  		handle, err := driver.Start(execCtx, task)
  1072  		if err != nil {
  1073  			t.Fatalf("err: %v", err)
  1074  		}
  1075  		defer handle.Kill()
  1076  
  1077  		select {
  1078  		case res := <-handle.WaitCh():
  1079  			if !res.Successful() {
  1080  				t.Fatalf("unexpected err: %v", res)
  1081  			}
  1082  		case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
  1083  			t.Fatalf("timeout")
  1084  		}
  1085  
  1086  		if _, err := ioutil.ReadFile(filepath.Join(execCtx.AllocDir.SharedDir, fn)); err != nil {
  1087  			t.Fatalf("unexpected error reading %s: %v", fn, err)
  1088  		}
  1089  	}
  1090  
  1091  }
  1092  
  1093  func TestDockerDriver_VolumesEnabled(t *testing.T) {
  1094  	cfg := testConfig()
  1095  
  1096  	tmpvol, err := ioutil.TempDir("", "nomadtest_docker_volumesenabled")
  1097  	if err != nil {
  1098  		t.Fatalf("error creating temporary dir: %v", err)
  1099  	}
  1100  
  1101  	task, driver, execCtx, hostpath, cleanup := setupDockerVolumes(t, cfg, tmpvol)
  1102  	defer cleanup()
  1103  
  1104  	handle, err := driver.Start(execCtx, task)
  1105  	if err != nil {
  1106  		t.Fatalf("Failed to start docker driver: %v", err)
  1107  	}
  1108  	defer handle.Kill()
  1109  
  1110  	select {
  1111  	case res := <-handle.WaitCh():
  1112  		if !res.Successful() {
  1113  			t.Fatalf("unexpected err: %v", res)
  1114  		}
  1115  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
  1116  		t.Fatalf("timeout")
  1117  	}
  1118  
  1119  	if _, err := ioutil.ReadFile(hostpath); err != nil {
  1120  		t.Fatalf("unexpected error reading %s: %v", hostpath, err)
  1121  	}
  1122  }
  1123  
  1124  func copyImage(execCtx *ExecContext, task *structs.Task, image string, t *testing.T) {
  1125  	taskDir, _ := execCtx.AllocDir.TaskDirs[task.Name]
  1126  	dst := filepath.Join(taskDir, allocdir.TaskLocal, image)
  1127  	copyFile(filepath.Join("./test-resources/docker", image), dst, t)
  1128  }