github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/client/driver/docker_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"math/rand"
     7  	"path/filepath"
     8  	"reflect"
     9  	"runtime/debug"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	docker "github.com/fsouza/go-dockerclient"
    16  	"github.com/hashicorp/nomad/client/allocdir"
    17  	"github.com/hashicorp/nomad/client/config"
    18  	"github.com/hashicorp/nomad/client/driver/env"
    19  	"github.com/hashicorp/nomad/client/testutil"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  	tu "github.com/hashicorp/nomad/testutil"
    22  )
    23  
    24  func dockerIsRemote(t *testing.T) bool {
    25  	client, err := docker.NewClientFromEnv()
    26  	if err != nil {
    27  		return false
    28  	}
    29  
    30  	// Technically this could be a local tcp socket but for testing purposes
    31  	// we'll just assume that tcp is only used for remote connections.
    32  	if client.Endpoint()[0:3] == "tcp" {
    33  		return true
    34  	}
    35  	return false
    36  }
    37  
    38  // Ports used by tests
    39  var (
    40  	docker_reserved = 32768 + int(rand.Int31n(25000))
    41  	docker_dynamic  = 32768 + int(rand.Int31n(25000))
    42  )
    43  
    44  // Returns a task with a reserved and dynamic port. The ports are returned
    45  // respectively.
    46  func dockerTask() (*structs.Task, int, int) {
    47  	docker_reserved += 1
    48  	docker_dynamic += 1
    49  	return &structs.Task{
    50  		Name: "redis-demo",
    51  		Config: map[string]interface{}{
    52  			"image":   "busybox",
    53  			"load":    []string{"busybox.tar"},
    54  			"command": "/bin/nc",
    55  			"args":    []string{"-l", "127.0.0.1", "-p", "0"},
    56  		},
    57  		LogConfig: &structs.LogConfig{
    58  			MaxFiles:      10,
    59  			MaxFileSizeMB: 10,
    60  		},
    61  		Resources: &structs.Resources{
    62  			MemoryMB: 256,
    63  			CPU:      512,
    64  			Networks: []*structs.NetworkResource{
    65  				&structs.NetworkResource{
    66  					IP:            "127.0.0.1",
    67  					ReservedPorts: []structs.Port{{"main", docker_reserved}},
    68  					DynamicPorts:  []structs.Port{{"REDIS", docker_dynamic}},
    69  				},
    70  			},
    71  		},
    72  	}, docker_reserved, docker_dynamic
    73  }
    74  
    75  // dockerSetup does all of the basic setup you need to get a running docker
    76  // process up and running for testing. Use like:
    77  //
    78  //	task := taskTemplate()
    79  //	// do custom task configuration
    80  //	client, handle, cleanup := dockerSetup(t, task)
    81  //	defer cleanup()
    82  //	// do test stuff
    83  //
    84  // If there is a problem during setup this function will abort or skip the test
    85  // and indicate the reason.
    86  func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) {
    87  	if !testutil.DockerIsConnected(t) {
    88  		t.SkipNow()
    89  	}
    90  
    91  	client, err := docker.NewClientFromEnv()
    92  	if err != nil {
    93  		t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack())
    94  	}
    95  
    96  	driverCtx, execCtx := testDriverContexts(task)
    97  	driver := NewDockerDriver(driverCtx)
    98  	copyImage(execCtx, task, "busybox.tar", t)
    99  
   100  	handle, err := driver.Start(execCtx, task)
   101  	if err != nil {
   102  		execCtx.AllocDir.Destroy()
   103  		t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack())
   104  	}
   105  	if handle == nil {
   106  		execCtx.AllocDir.Destroy()
   107  		t.Fatalf("handle is nil\nStack\n%s", debug.Stack())
   108  	}
   109  
   110  	cleanup := func() {
   111  		handle.Kill()
   112  		execCtx.AllocDir.Destroy()
   113  	}
   114  
   115  	return client, handle, cleanup
   116  }
   117  
   118  // This test should always pass, even if docker daemon is not available
   119  func TestDockerDriver_Fingerprint(t *testing.T) {
   120  	driverCtx, execCtx := testDriverContexts(&structs.Task{Name: "foo", Resources: basicResources})
   121  	defer execCtx.AllocDir.Destroy()
   122  	d := NewDockerDriver(driverCtx)
   123  	node := &structs.Node{
   124  		Attributes: make(map[string]string),
   125  	}
   126  	apply, err := d.Fingerprint(&config.Config{}, node)
   127  	if err != nil {
   128  		t.Fatalf("err: %v", err)
   129  	}
   130  	if apply != testutil.DockerIsConnected(t) {
   131  		t.Fatalf("Fingerprinter should detect when docker is available")
   132  	}
   133  	if node.Attributes["driver.docker"] != "1" {
   134  		t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.")
   135  	}
   136  	t.Logf("Found docker version %s", node.Attributes["driver.docker.version"])
   137  }
   138  
   139  func TestDockerDriver_StartOpen_Wait(t *testing.T) {
   140  	if !testutil.DockerIsConnected(t) {
   141  		t.SkipNow()
   142  	}
   143  
   144  	task := &structs.Task{
   145  		Name: "nc-demo",
   146  		Config: map[string]interface{}{
   147  			"load":    []string{"busybox.tar"},
   148  			"image":   "busybox",
   149  			"command": "/bin/nc",
   150  			"args":    []string{"-l", "127.0.0.1", "-p", "0"},
   151  		},
   152  		LogConfig: &structs.LogConfig{
   153  			MaxFiles:      10,
   154  			MaxFileSizeMB: 10,
   155  		},
   156  		Resources: basicResources,
   157  	}
   158  
   159  	driverCtx, execCtx := testDriverContexts(task)
   160  	defer execCtx.AllocDir.Destroy()
   161  	d := NewDockerDriver(driverCtx)
   162  	copyImage(execCtx, task, "busybox.tar", t)
   163  
   164  	handle, err := d.Start(execCtx, task)
   165  	if err != nil {
   166  		t.Fatalf("err: %v", err)
   167  	}
   168  	if handle == nil {
   169  		t.Fatalf("missing handle")
   170  	}
   171  	defer handle.Kill()
   172  
   173  	// Attempt to open
   174  	handle2, err := d.Open(execCtx, handle.ID())
   175  	if err != nil {
   176  		t.Fatalf("err: %v", err)
   177  	}
   178  	if handle2 == nil {
   179  		t.Fatalf("missing handle")
   180  	}
   181  }
   182  
   183  func TestDockerDriver_Start_Wait(t *testing.T) {
   184  	task := &structs.Task{
   185  		Name: "nc-demo",
   186  		Config: map[string]interface{}{
   187  			"load":    []string{"busybox.tar"},
   188  			"image":   "busybox",
   189  			"command": "/bin/echo",
   190  			"args":    []string{"hello"},
   191  		},
   192  		Resources: &structs.Resources{
   193  			MemoryMB: 256,
   194  			CPU:      512,
   195  		},
   196  		LogConfig: &structs.LogConfig{
   197  			MaxFiles:      10,
   198  			MaxFileSizeMB: 10,
   199  		},
   200  	}
   201  
   202  	_, handle, cleanup := dockerSetup(t, task)
   203  	defer cleanup()
   204  
   205  	// Update should be a no-op
   206  	err := handle.Update(task)
   207  	if err != nil {
   208  		t.Fatalf("err: %v", err)
   209  	}
   210  
   211  	select {
   212  	case res := <-handle.WaitCh():
   213  		if !res.Successful() {
   214  			t.Fatalf("err: %v", res)
   215  		}
   216  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   217  		t.Fatalf("timeout")
   218  	}
   219  }
   220  
   221  func TestDockerDriver_Start_LoadImage(t *testing.T) {
   222  	if !testutil.DockerIsConnected(t) {
   223  		t.SkipNow()
   224  	}
   225  	task := &structs.Task{
   226  		Name: "busybox-demo",
   227  		Config: map[string]interface{}{
   228  			"image":   "busybox",
   229  			"load":    []string{"busybox.tar"},
   230  			"command": "/bin/echo",
   231  			"args": []string{
   232  				"hello",
   233  			},
   234  		},
   235  		LogConfig: &structs.LogConfig{
   236  			MaxFiles:      10,
   237  			MaxFileSizeMB: 10,
   238  		},
   239  		Resources: &structs.Resources{
   240  			MemoryMB: 256,
   241  			CPU:      512,
   242  		},
   243  	}
   244  
   245  	driverCtx, execCtx := testDriverContexts(task)
   246  	defer execCtx.AllocDir.Destroy()
   247  	d := NewDockerDriver(driverCtx)
   248  
   249  	// Copy the image into the task's directory
   250  	copyImage(execCtx, task, "busybox.tar", t)
   251  
   252  	handle, err := d.Start(execCtx, task)
   253  	if err != nil {
   254  		t.Fatalf("err: %v", err)
   255  	}
   256  	if handle == nil {
   257  		t.Fatalf("missing handle")
   258  	}
   259  	defer handle.Kill()
   260  
   261  	select {
   262  	case res := <-handle.WaitCh():
   263  		if !res.Successful() {
   264  			t.Fatalf("err: %v", res)
   265  		}
   266  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   267  		t.Fatalf("timeout")
   268  	}
   269  
   270  	// Check that data was written to the shared alloc directory.
   271  	outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "busybox-demo.stdout.0")
   272  	act, err := ioutil.ReadFile(outputFile)
   273  	if err != nil {
   274  		t.Fatalf("Couldn't read expected output: %v", err)
   275  	}
   276  
   277  	exp := "hello"
   278  	if strings.TrimSpace(string(act)) != exp {
   279  		t.Fatalf("Command outputted %v; want %v", act, exp)
   280  	}
   281  
   282  }
   283  
   284  func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
   285  	// This test requires that the alloc dir be mounted into docker as a volume.
   286  	// Because this cannot happen when docker is run remotely, e.g. when running
   287  	// docker in a VM, we skip this when we detect Docker is being run remotely.
   288  	if !testutil.DockerIsConnected(t) || dockerIsRemote(t) {
   289  		t.SkipNow()
   290  	}
   291  
   292  	exp := []byte{'w', 'i', 'n'}
   293  	file := "output.txt"
   294  	task := &structs.Task{
   295  		Name: "nc-demo",
   296  		Config: map[string]interface{}{
   297  			"image":   "busybox",
   298  			"load":    []string{"busybox.tar"},
   299  			"command": "/bin/sh",
   300  			"args": []string{
   301  				"-c",
   302  				fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`,
   303  					string(exp), env.AllocDir, file),
   304  			},
   305  		},
   306  		LogConfig: &structs.LogConfig{
   307  			MaxFiles:      10,
   308  			MaxFileSizeMB: 10,
   309  		},
   310  		Resources: &structs.Resources{
   311  			MemoryMB: 256,
   312  			CPU:      512,
   313  		},
   314  	}
   315  
   316  	driverCtx, execCtx := testDriverContexts(task)
   317  	defer execCtx.AllocDir.Destroy()
   318  	d := NewDockerDriver(driverCtx)
   319  	copyImage(execCtx, task, "busybox.tar", t)
   320  
   321  	handle, err := d.Start(execCtx, task)
   322  	if err != nil {
   323  		t.Fatalf("err: %v", err)
   324  	}
   325  	if handle == nil {
   326  		t.Fatalf("missing handle")
   327  	}
   328  	defer handle.Kill()
   329  
   330  	select {
   331  	case res := <-handle.WaitCh():
   332  		if !res.Successful() {
   333  			t.Fatalf("err: %v", res)
   334  		}
   335  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   336  		t.Fatalf("timeout")
   337  	}
   338  
   339  	// Check that data was written to the shared alloc directory.
   340  	outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file)
   341  	act, err := ioutil.ReadFile(outputFile)
   342  	if err != nil {
   343  		t.Fatalf("Couldn't read expected output: %v", err)
   344  	}
   345  
   346  	if !reflect.DeepEqual(act, exp) {
   347  		t.Fatalf("Command outputted %v; want %v", act, exp)
   348  	}
   349  }
   350  
   351  func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
   352  	task := &structs.Task{
   353  		Name: "nc-demo",
   354  		Config: map[string]interface{}{
   355  			"image":   "busybox",
   356  			"load":    []string{"busybox.tar"},
   357  			"command": "/bin/sleep",
   358  			"args":    []string{"10"},
   359  		},
   360  		LogConfig: &structs.LogConfig{
   361  			MaxFiles:      10,
   362  			MaxFileSizeMB: 10,
   363  		},
   364  		Resources: basicResources,
   365  	}
   366  
   367  	_, handle, cleanup := dockerSetup(t, task)
   368  	defer cleanup()
   369  
   370  	go func() {
   371  		time.Sleep(100 * time.Millisecond)
   372  		err := handle.Kill()
   373  		if err != nil {
   374  			t.Fatalf("err: %v", err)
   375  		}
   376  	}()
   377  
   378  	select {
   379  	case res := <-handle.WaitCh():
   380  		if res.Successful() {
   381  			t.Fatalf("should err: %v", res)
   382  		}
   383  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
   384  		t.Fatalf("timeout")
   385  	}
   386  }
   387  
   388  func TestDockerDriver_StartN(t *testing.T) {
   389  	if !testutil.DockerIsConnected(t) {
   390  		t.SkipNow()
   391  	}
   392  
   393  	task1, _, _ := dockerTask()
   394  	task2, _, _ := dockerTask()
   395  	task3, _, _ := dockerTask()
   396  	taskList := []*structs.Task{task1, task2, task3}
   397  
   398  	handles := make([]DriverHandle, len(taskList))
   399  
   400  	t.Logf("Starting %d tasks", len(taskList))
   401  
   402  	// Let's spin up a bunch of things
   403  	var err error
   404  	for idx, task := range taskList {
   405  		driverCtx, execCtx := testDriverContexts(task)
   406  		defer execCtx.AllocDir.Destroy()
   407  		d := NewDockerDriver(driverCtx)
   408  		copyImage(execCtx, task, "busybox.tar", t)
   409  
   410  		handles[idx], err = d.Start(execCtx, task)
   411  		if err != nil {
   412  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   413  		}
   414  	}
   415  
   416  	t.Log("All tasks are started. Terminating...")
   417  
   418  	for idx, handle := range handles {
   419  		if handle == nil {
   420  			t.Errorf("Bad handle for task #%d", idx+1)
   421  			continue
   422  		}
   423  
   424  		err := handle.Kill()
   425  		if err != nil {
   426  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   427  		}
   428  	}
   429  
   430  	t.Log("Test complete!")
   431  }
   432  
   433  func TestDockerDriver_StartNVersions(t *testing.T) {
   434  	if !testutil.DockerIsConnected(t) {
   435  		t.SkipNow()
   436  	}
   437  
   438  	task1, _, _ := dockerTask()
   439  	task1.Config["image"] = "busybox"
   440  	task1.Config["load"] = []string{"busybox.tar"}
   441  
   442  	task2, _, _ := dockerTask()
   443  	task2.Config["image"] = "busybox:musl"
   444  	task2.Config["load"] = []string{"busybox_musl.tar"}
   445  
   446  	task3, _, _ := dockerTask()
   447  	task3.Config["image"] = "busybox:glibc"
   448  	task3.Config["load"] = []string{"busybox_glibc.tar"}
   449  
   450  	taskList := []*structs.Task{task1, task2, task3}
   451  
   452  	handles := make([]DriverHandle, len(taskList))
   453  
   454  	t.Logf("Starting %d tasks", len(taskList))
   455  
   456  	// Let's spin up a bunch of things
   457  	var err error
   458  	for idx, task := range taskList {
   459  		driverCtx, execCtx := testDriverContexts(task)
   460  		defer execCtx.AllocDir.Destroy()
   461  		d := NewDockerDriver(driverCtx)
   462  		copyImage(execCtx, task, "busybox.tar", t)
   463  		copyImage(execCtx, task, "busybox_musl.tar", t)
   464  		copyImage(execCtx, task, "busybox_glibc.tar", t)
   465  
   466  		handles[idx], err = d.Start(execCtx, task)
   467  		if err != nil {
   468  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   469  		}
   470  	}
   471  
   472  	t.Log("All tasks are started. Terminating...")
   473  
   474  	for idx, handle := range handles {
   475  		if handle == nil {
   476  			t.Errorf("Bad handle for task #%d", idx+1)
   477  			continue
   478  		}
   479  
   480  		err := handle.Kill()
   481  		if err != nil {
   482  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   483  		}
   484  	}
   485  
   486  	t.Log("Test complete!")
   487  }
   488  
   489  func waitForExist(t *testing.T, client *docker.Client, handle *DockerHandle) {
   490  	tu.WaitForResult(func() (bool, error) {
   491  		container, err := client.InspectContainer(handle.ContainerID())
   492  		if err != nil {
   493  			if _, ok := err.(*docker.NoSuchContainer); !ok {
   494  				return false, err
   495  			}
   496  		}
   497  
   498  		return container != nil, nil
   499  	}, func(err error) {
   500  		t.Fatalf("err: %v", err)
   501  	})
   502  }
   503  
   504  func TestDockerDriver_NetworkMode_Host(t *testing.T) {
   505  	expected := "host"
   506  
   507  	task := &structs.Task{
   508  		Name: "nc-demo",
   509  		Config: map[string]interface{}{
   510  			"image":        "busybox",
   511  			"load":         []string{"busybox.tar"},
   512  			"command":      "/bin/nc",
   513  			"args":         []string{"-l", "127.0.0.1", "-p", "0"},
   514  			"network_mode": expected,
   515  		},
   516  		Resources: &structs.Resources{
   517  			MemoryMB: 256,
   518  			CPU:      512,
   519  		},
   520  		LogConfig: &structs.LogConfig{
   521  			MaxFiles:      10,
   522  			MaxFileSizeMB: 10,
   523  		},
   524  	}
   525  
   526  	client, handle, cleanup := dockerSetup(t, task)
   527  	defer cleanup()
   528  
   529  	waitForExist(t, client, handle.(*DockerHandle))
   530  
   531  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   532  	if err != nil {
   533  		t.Fatalf("err: %v", err)
   534  	}
   535  
   536  	actual := container.HostConfig.NetworkMode
   537  	if actual != expected {
   538  		t.Fatalf("Got network mode %q; want %q", expected, actual)
   539  	}
   540  }
   541  
   542  func TestDockerDriver_Labels(t *testing.T) {
   543  	task, _, _ := dockerTask()
   544  	task.Config["labels"] = []map[string]string{
   545  		map[string]string{
   546  			"label1": "value1",
   547  			"label2": "value2",
   548  		},
   549  	}
   550  
   551  	client, handle, cleanup := dockerSetup(t, task)
   552  	defer cleanup()
   553  
   554  	waitForExist(t, client, handle.(*DockerHandle))
   555  
   556  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   557  	if err != nil {
   558  		t.Fatalf("err: %v", err)
   559  	}
   560  
   561  	if want, got := 2, len(container.Config.Labels); want != got {
   562  		t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got)
   563  	}
   564  
   565  	if want, got := "value1", container.Config.Labels["label1"]; want != got {
   566  		t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got)
   567  	}
   568  }
   569  
   570  func TestDockerDriver_DNS(t *testing.T) {
   571  	task, _, _ := dockerTask()
   572  	task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"}
   573  	task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"}
   574  
   575  	client, handle, cleanup := dockerSetup(t, task)
   576  	defer cleanup()
   577  
   578  	waitForExist(t, client, handle.(*DockerHandle))
   579  
   580  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   581  	if err != nil {
   582  		t.Fatalf("err: %v", err)
   583  	}
   584  
   585  	if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) {
   586  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS)
   587  	}
   588  
   589  	if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) {
   590  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch)
   591  	}
   592  }
   593  
   594  func TestDockerWorkDir(t *testing.T) {
   595  	task, _, _ := dockerTask()
   596  	task.Config["work_dir"] = "/some/path"
   597  
   598  	client, handle, cleanup := dockerSetup(t, task)
   599  	defer cleanup()
   600  
   601  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   602  	if err != nil {
   603  		t.Fatalf("err: %v", err)
   604  	}
   605  
   606  	if want, got := "/some/path", container.Config.WorkingDir; want != got {
   607  		t.Errorf("Wrong working directory for docker job. Expect: %d, got: %d", want, got)
   608  	}
   609  }
   610  
   611  func inSlice(needle string, haystack []string) bool {
   612  	for _, h := range haystack {
   613  		if h == needle {
   614  			return true
   615  		}
   616  	}
   617  	return false
   618  }
   619  
   620  func TestDockerDriver_PortsNoMap(t *testing.T) {
   621  	task, res, dyn := dockerTask()
   622  
   623  	client, handle, cleanup := dockerSetup(t, task)
   624  	defer cleanup()
   625  
   626  	waitForExist(t, client, handle.(*DockerHandle))
   627  
   628  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   629  	if err != nil {
   630  		t.Fatalf("err: %v", err)
   631  	}
   632  
   633  	// Verify that the correct ports are EXPOSED
   634  	expectedExposedPorts := map[docker.Port]struct{}{
   635  		docker.Port(fmt.Sprintf("%d/tcp", res)): struct{}{},
   636  		docker.Port(fmt.Sprintf("%d/udp", res)): struct{}{},
   637  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): struct{}{},
   638  		docker.Port(fmt.Sprintf("%d/udp", dyn)): struct{}{},
   639  	}
   640  
   641  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   642  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   643  	}
   644  
   645  	// Verify that the correct ports are FORWARDED
   646  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   647  		docker.Port(fmt.Sprintf("%d/tcp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   648  		docker.Port(fmt.Sprintf("%d/udp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   649  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   650  		docker.Port(fmt.Sprintf("%d/udp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   651  	}
   652  
   653  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   654  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   655  	}
   656  
   657  	expectedEnvironment := map[string]string{
   658  		"NOMAD_ADDR_main":  fmt.Sprintf("127.0.0.1:%d", res),
   659  		"NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn),
   660  	}
   661  
   662  	for key, val := range expectedEnvironment {
   663  		search := fmt.Sprintf("%s=%s", key, val)
   664  		if !inSlice(search, container.Config.Env) {
   665  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   666  		}
   667  	}
   668  }
   669  
   670  func TestDockerDriver_PortsMapping(t *testing.T) {
   671  	task, res, dyn := dockerTask()
   672  	task.Config["port_map"] = []map[string]string{
   673  		map[string]string{
   674  			"main":  "8080",
   675  			"REDIS": "6379",
   676  		},
   677  	}
   678  
   679  	client, handle, cleanup := dockerSetup(t, task)
   680  	defer cleanup()
   681  
   682  	waitForExist(t, client, handle.(*DockerHandle))
   683  
   684  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   685  	if err != nil {
   686  		t.Fatalf("err: %v", err)
   687  	}
   688  
   689  	// Verify that the correct ports are EXPOSED
   690  	expectedExposedPorts := map[docker.Port]struct{}{
   691  		docker.Port("8080/tcp"): struct{}{},
   692  		docker.Port("8080/udp"): struct{}{},
   693  		docker.Port("6379/tcp"): struct{}{},
   694  		docker.Port("6379/udp"): struct{}{},
   695  	}
   696  
   697  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   698  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   699  	}
   700  
   701  	// Verify that the correct ports are FORWARDED
   702  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   703  		docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   704  		docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   705  		docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   706  		docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   707  	}
   708  
   709  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   710  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   711  	}
   712  
   713  	expectedEnvironment := map[string]string{
   714  		"NOMAD_ADDR_main":      "127.0.0.1:8080",
   715  		"NOMAD_ADDR_REDIS":     "127.0.0.1:6379",
   716  		"NOMAD_HOST_PORT_main": strconv.Itoa(docker_reserved),
   717  	}
   718  
   719  	for key, val := range expectedEnvironment {
   720  		search := fmt.Sprintf("%s=%s", key, val)
   721  		if !inSlice(search, container.Config.Env) {
   722  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   723  		}
   724  	}
   725  }
   726  
   727  func TestDockerDriver_User(t *testing.T) {
   728  	task := &structs.Task{
   729  		Name: "redis-demo",
   730  		User: "alice",
   731  		Config: map[string]interface{}{
   732  			"image":   "busybox",
   733  			"load":    []string{"busybox.tar"},
   734  			"command": "/bin/sleep",
   735  			"args":    []string{"10000"},
   736  		},
   737  		Resources: &structs.Resources{
   738  			MemoryMB: 256,
   739  			CPU:      512,
   740  		},
   741  		LogConfig: &structs.LogConfig{
   742  			MaxFiles:      10,
   743  			MaxFileSizeMB: 10,
   744  		},
   745  	}
   746  
   747  	if !testutil.DockerIsConnected(t) {
   748  		t.SkipNow()
   749  	}
   750  
   751  	driverCtx, execCtx := testDriverContexts(task)
   752  	driver := NewDockerDriver(driverCtx)
   753  	defer execCtx.AllocDir.Destroy()
   754  	copyImage(execCtx, task, "busybox.tar", t)
   755  
   756  	// It should fail because the user "alice" does not exist on the given
   757  	// image.
   758  	handle, err := driver.Start(execCtx, task)
   759  	if err == nil {
   760  		handle.Kill()
   761  		t.Fatalf("Should've failed")
   762  	}
   763  
   764  	if !strings.Contains(err.Error(), "alice") {
   765  		t.Fatalf("Expected failure string not found, found %q instead", err.Error())
   766  	}
   767  }
   768  
   769  func TestDockerDriver_CleanupContainer(t *testing.T) {
   770  	task := &structs.Task{
   771  		Name: "redis-demo",
   772  		Config: map[string]interface{}{
   773  			"image":   "busybox",
   774  			"load":    []string{"busybox.tar"},
   775  			"command": "/bin/echo",
   776  			"args":    []string{"hello"},
   777  		},
   778  		Resources: &structs.Resources{
   779  			MemoryMB: 256,
   780  			CPU:      512,
   781  		},
   782  		LogConfig: &structs.LogConfig{
   783  			MaxFiles:      10,
   784  			MaxFileSizeMB: 10,
   785  		},
   786  	}
   787  
   788  	_, handle, cleanup := dockerSetup(t, task)
   789  	defer cleanup()
   790  
   791  	// Update should be a no-op
   792  	err := handle.Update(task)
   793  	if err != nil {
   794  		t.Fatalf("err: %v", err)
   795  	}
   796  
   797  	select {
   798  	case res := <-handle.WaitCh():
   799  		if !res.Successful() {
   800  			t.Fatalf("err: %v", res)
   801  		}
   802  
   803  		time.Sleep(3 * time.Second)
   804  
   805  		// Ensure that the container isn't present
   806  		_, err := client.InspectContainer(handle.(*DockerHandle).containerID)
   807  		if err == nil {
   808  			t.Fatalf("expected to not get container")
   809  		}
   810  
   811  	case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
   812  		t.Fatalf("timeout")
   813  	}
   814  }
   815  
   816  func TestDockerDriver_Stats(t *testing.T) {
   817  	task := &structs.Task{
   818  		Name: "sleep",
   819  		Config: map[string]interface{}{
   820  			"image":   "busybox",
   821  			"load":    []string{"busybox.tar"},
   822  			"command": "/bin/sleep",
   823  			"args":    []string{"100"},
   824  		},
   825  		LogConfig: &structs.LogConfig{
   826  			MaxFiles:      10,
   827  			MaxFileSizeMB: 10,
   828  		},
   829  		Resources: basicResources,
   830  	}
   831  
   832  	_, handle, cleanup := dockerSetup(t, task)
   833  	defer cleanup()
   834  
   835  	waitForExist(t, client, handle.(*DockerHandle))
   836  
   837  	go func() {
   838  		time.Sleep(3 * time.Second)
   839  		ru, err := handle.Stats()
   840  		if err != nil {
   841  			t.Fatalf("err: %v", err)
   842  		}
   843  		if ru.ResourceUsage == nil {
   844  			handle.Kill()
   845  			t.Fatalf("expected resource usage")
   846  		}
   847  		err = handle.Kill()
   848  		if err != nil {
   849  			t.Fatalf("err: %v", err)
   850  		}
   851  	}()
   852  
   853  	select {
   854  	case res := <-handle.WaitCh():
   855  		if res.Successful() {
   856  			t.Fatalf("should err: %v", res)
   857  		}
   858  	case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second):
   859  		t.Fatalf("timeout")
   860  	}
   861  
   862  }
   863  
   864  func copyImage(execCtx *ExecContext, task *structs.Task, image string, t *testing.T) {
   865  	taskDir, _ := execCtx.AllocDir.TaskDirs[task.Name]
   866  	dst := filepath.Join(taskDir, allocdir.TaskLocal, image)
   867  	copyFile(filepath.Join("./test-resources/docker", image), dst, t)
   868  }