github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/driver/docker_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"math/rand"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime/debug"
    12  	"testing"
    13  	"time"
    14  
    15  	docker "github.com/fsouza/go-dockerclient"
    16  	"github.com/hashicorp/go-plugin"
    17  	"github.com/hashicorp/nomad/client/config"
    18  	"github.com/hashicorp/nomad/client/driver/env"
    19  	cstructs "github.com/hashicorp/nomad/client/driver/structs"
    20  	"github.com/hashicorp/nomad/helper/discover"
    21  	"github.com/hashicorp/nomad/nomad/structs"
    22  	"github.com/hashicorp/nomad/testutil"
    23  )
    24  
    25  // dockerIsConnected checks to see if a docker daemon is available (local or remote)
    26  func dockerIsConnected(t *testing.T) bool {
    27  	client, err := docker.NewClientFromEnv()
    28  	if err != nil {
    29  		return false
    30  	}
    31  
    32  	// Creating a client doesn't actually connect, so make sure we do something
    33  	// like call Version() on it.
    34  	env, err := client.Version()
    35  	if err != nil {
    36  		t.Logf("Failed to connect to docker daemon: %s", err)
    37  		return false
    38  	}
    39  
    40  	t.Logf("Successfully connected to docker daemon running version %s", env.Get("Version"))
    41  	return true
    42  }
    43  
    44  func dockerIsRemote(t *testing.T) bool {
    45  	client, err := docker.NewClientFromEnv()
    46  	if err != nil {
    47  		return false
    48  	}
    49  
    50  	// Technically this could be a local tcp socket but for testing purposes
    51  	// we'll just assume that tcp is only used for remote connections.
    52  	if client.Endpoint()[0:3] == "tcp" {
    53  		return true
    54  	}
    55  	return false
    56  }
    57  
    58  // Ports used by tests
    59  var (
    60  	docker_reserved = 32768 + int(rand.Int31n(25000))
    61  	docker_dynamic  = 32768 + int(rand.Int31n(25000))
    62  )
    63  
    64  // Returns a task with a reserved and dynamic port. The ports are returned
    65  // respectively.
    66  func dockerTask() (*structs.Task, int, int) {
    67  	docker_reserved += 1
    68  	docker_dynamic += 1
    69  	return &structs.Task{
    70  		Name: "redis-demo",
    71  		Config: map[string]interface{}{
    72  			"image": "redis",
    73  		},
    74  		LogConfig: &structs.LogConfig{
    75  			MaxFiles:      10,
    76  			MaxFileSizeMB: 10,
    77  		},
    78  		Resources: &structs.Resources{
    79  			MemoryMB: 256,
    80  			CPU:      512,
    81  			Networks: []*structs.NetworkResource{
    82  				&structs.NetworkResource{
    83  					IP:            "127.0.0.1",
    84  					ReservedPorts: []structs.Port{{"main", docker_reserved}},
    85  					DynamicPorts:  []structs.Port{{"REDIS", docker_dynamic}},
    86  				},
    87  			},
    88  		},
    89  	}, docker_reserved, docker_dynamic
    90  }
    91  
    92  // dockerSetup does all of the basic setup you need to get a running docker
    93  // process up and running for testing. Use like:
    94  //
    95  //	task := taskTemplate()
    96  //	// do custom task configuration
    97  //	client, handle, cleanup := dockerSetup(t, task)
    98  //	defer cleanup()
    99  //	// do test stuff
   100  //
   101  // If there is a problem during setup this function will abort or skip the test
   102  // and indicate the reason.
   103  func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) {
   104  	if !dockerIsConnected(t) {
   105  		t.SkipNow()
   106  	}
   107  
   108  	client, err := docker.NewClientFromEnv()
   109  	if err != nil {
   110  		t.Fatalf("Failed to initialize client: %s\nStack\n%s", err, debug.Stack())
   111  	}
   112  
   113  	driverCtx, execCtx := testDriverContexts(task)
   114  	driver := NewDockerDriver(driverCtx)
   115  
   116  	handle, err := driver.Start(execCtx, task)
   117  	if err != nil {
   118  		execCtx.AllocDir.Destroy()
   119  		t.Fatalf("Failed to start driver: %s\nStack\n%s", err, debug.Stack())
   120  	}
   121  	if handle == nil {
   122  		execCtx.AllocDir.Destroy()
   123  		t.Fatalf("handle is nil\nStack\n%s", debug.Stack())
   124  	}
   125  
   126  	cleanup := func() {
   127  		handle.Kill()
   128  		execCtx.AllocDir.Destroy()
   129  	}
   130  
   131  	return client, handle, cleanup
   132  }
   133  
   134  func TestDockerDriver_Handle(t *testing.T) {
   135  	t.Parallel()
   136  
   137  	bin, err := discover.NomadExecutable()
   138  	if err != nil {
   139  		t.Fatalf("got an err: %v", err)
   140  	}
   141  
   142  	f, _ := ioutil.TempFile(os.TempDir(), "")
   143  	defer f.Close()
   144  	defer os.Remove(f.Name())
   145  	pluginConfig := &plugin.ClientConfig{
   146  		Cmd: exec.Command(bin, "syslog", f.Name()),
   147  	}
   148  	logCollector, pluginClient, err := createLogCollector(pluginConfig, os.Stdout, &config.Config{})
   149  	if err != nil {
   150  		t.Fatalf("got an err: %v", err)
   151  	}
   152  	defer pluginClient.Kill()
   153  
   154  	h := &DockerHandle{
   155  		version:      "version",
   156  		imageID:      "imageid",
   157  		logCollector: logCollector,
   158  		pluginClient: pluginClient,
   159  		containerID:  "containerid",
   160  		killTimeout:  5 * time.Nanosecond,
   161  		doneCh:       make(chan struct{}),
   162  		waitCh:       make(chan *cstructs.WaitResult, 1),
   163  	}
   164  
   165  	actual := h.ID()
   166  	expected := fmt.Sprintf("DOCKER:{\"Version\":\"version\",\"ImageID\":\"imageid\",\"ContainerID\":\"containerid\",\"KillTimeout\":5,\"PluginConfig\":{\"Pid\":%d,\"AddrNet\":\"unix\",\"AddrName\":\"%s\"}}",
   167  		pluginClient.ReattachConfig().Pid, pluginClient.ReattachConfig().Addr.String())
   168  	if actual != expected {
   169  		t.Errorf("Expected `%s`, found `%s`", expected, actual)
   170  	}
   171  }
   172  
   173  // This test should always pass, even if docker daemon is not available
   174  func TestDockerDriver_Fingerprint(t *testing.T) {
   175  	t.Parallel()
   176  	driverCtx, _ := testDriverContexts(&structs.Task{Name: "foo"})
   177  	d := NewDockerDriver(driverCtx)
   178  	node := &structs.Node{
   179  		Attributes: make(map[string]string),
   180  	}
   181  	apply, err := d.Fingerprint(&config.Config{}, node)
   182  	if err != nil {
   183  		t.Fatalf("err: %v", err)
   184  	}
   185  	if apply != dockerIsConnected(t) {
   186  		t.Fatalf("Fingerprinter should detect when docker is available")
   187  	}
   188  	if node.Attributes["driver.docker"] != "1" {
   189  		t.Log("Docker daemon not available. The remainder of the docker tests will be skipped.")
   190  	}
   191  	t.Logf("Found docker version %s", node.Attributes["driver.docker.version"])
   192  }
   193  
   194  func TestDockerDriver_StartOpen_Wait(t *testing.T) {
   195  	t.Parallel()
   196  	if !dockerIsConnected(t) {
   197  		t.SkipNow()
   198  	}
   199  
   200  	task := &structs.Task{
   201  		Name: "redis-demo",
   202  		Config: map[string]interface{}{
   203  			"image": "redis",
   204  		},
   205  		LogConfig: &structs.LogConfig{
   206  			MaxFiles:      10,
   207  			MaxFileSizeMB: 10,
   208  		},
   209  		Resources: basicResources,
   210  	}
   211  
   212  	driverCtx, execCtx := testDriverContexts(task)
   213  	defer execCtx.AllocDir.Destroy()
   214  	d := NewDockerDriver(driverCtx)
   215  
   216  	handle, err := d.Start(execCtx, task)
   217  	if err != nil {
   218  		t.Fatalf("err: %v", err)
   219  	}
   220  	if handle == nil {
   221  		t.Fatalf("missing handle")
   222  	}
   223  	defer handle.Kill()
   224  
   225  	// Attempt to open
   226  	handle2, err := d.Open(execCtx, handle.ID())
   227  	if err != nil {
   228  		t.Fatalf("err: %v", err)
   229  	}
   230  	if handle2 == nil {
   231  		t.Fatalf("missing handle")
   232  	}
   233  }
   234  
   235  func TestDockerDriver_Start_Wait(t *testing.T) {
   236  	t.Parallel()
   237  	task := &structs.Task{
   238  		Name: "redis-demo",
   239  		Config: map[string]interface{}{
   240  			"image":   "redis",
   241  			"command": "redis-server",
   242  			"args":    []string{"-v"},
   243  		},
   244  		Resources: &structs.Resources{
   245  			MemoryMB: 256,
   246  			CPU:      512,
   247  		},
   248  		LogConfig: &structs.LogConfig{
   249  			MaxFiles:      10,
   250  			MaxFileSizeMB: 10,
   251  		},
   252  	}
   253  
   254  	_, handle, cleanup := dockerSetup(t, task)
   255  	defer cleanup()
   256  
   257  	// Update should be a no-op
   258  	err := handle.Update(task)
   259  	if err != nil {
   260  		t.Fatalf("err: %v", err)
   261  	}
   262  
   263  	select {
   264  	case res := <-handle.WaitCh():
   265  		if !res.Successful() {
   266  			t.Fatalf("err: %v", res)
   267  		}
   268  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   269  		t.Fatalf("timeout")
   270  	}
   271  }
   272  
   273  func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
   274  	t.Parallel()
   275  	// This test requires that the alloc dir be mounted into docker as a volume.
   276  	// Because this cannot happen when docker is run remotely, e.g. when running
   277  	// docker in a VM, we skip this when we detect Docker is being run remotely.
   278  	if !dockerIsConnected(t) || dockerIsRemote(t) {
   279  		t.SkipNow()
   280  	}
   281  
   282  	exp := []byte{'w', 'i', 'n'}
   283  	file := "output.txt"
   284  	task := &structs.Task{
   285  		Name: "redis-demo",
   286  		Config: map[string]interface{}{
   287  			"image":   "redis",
   288  			"command": "/bin/bash",
   289  			"args": []string{
   290  				"-c",
   291  				fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`,
   292  					string(exp), env.AllocDir, file),
   293  			},
   294  		},
   295  		LogConfig: &structs.LogConfig{
   296  			MaxFiles:      10,
   297  			MaxFileSizeMB: 10,
   298  		},
   299  		Resources: &structs.Resources{
   300  			MemoryMB: 256,
   301  			CPU:      512,
   302  		},
   303  	}
   304  
   305  	driverCtx, execCtx := testDriverContexts(task)
   306  	defer execCtx.AllocDir.Destroy()
   307  	d := NewDockerDriver(driverCtx)
   308  
   309  	handle, err := d.Start(execCtx, task)
   310  	if err != nil {
   311  		t.Fatalf("err: %v", err)
   312  	}
   313  	if handle == nil {
   314  		t.Fatalf("missing handle")
   315  	}
   316  	defer handle.Kill()
   317  
   318  	select {
   319  	case res := <-handle.WaitCh():
   320  		if !res.Successful() {
   321  			t.Fatalf("err: %v", res)
   322  		}
   323  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   324  		t.Fatalf("timeout")
   325  	}
   326  
   327  	// Check that data was written to the shared alloc directory.
   328  	outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file)
   329  	act, err := ioutil.ReadFile(outputFile)
   330  	if err != nil {
   331  		t.Fatalf("Couldn't read expected output: %v", err)
   332  	}
   333  
   334  	if !reflect.DeepEqual(act, exp) {
   335  		t.Fatalf("Command outputted %v; want %v", act, exp)
   336  	}
   337  }
   338  
   339  func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
   340  	t.Parallel()
   341  	task := &structs.Task{
   342  		Name: "redis-demo",
   343  		Config: map[string]interface{}{
   344  			"image":   "redis",
   345  			"command": "/bin/sleep",
   346  			"args":    []string{"10"},
   347  		},
   348  		LogConfig: &structs.LogConfig{
   349  			MaxFiles:      10,
   350  			MaxFileSizeMB: 10,
   351  		},
   352  		Resources: basicResources,
   353  	}
   354  
   355  	_, handle, cleanup := dockerSetup(t, task)
   356  	defer cleanup()
   357  
   358  	go func() {
   359  		time.Sleep(100 * time.Millisecond)
   360  		err := handle.Kill()
   361  		if err != nil {
   362  			t.Fatalf("err: %v", err)
   363  		}
   364  	}()
   365  
   366  	select {
   367  	case res := <-handle.WaitCh():
   368  		if res.Successful() {
   369  			t.Fatalf("should err: %v", res)
   370  		}
   371  	case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second):
   372  		t.Fatalf("timeout")
   373  	}
   374  }
   375  
   376  func TestDocker_StartN(t *testing.T) {
   377  	t.Parallel()
   378  	if !dockerIsConnected(t) {
   379  		t.SkipNow()
   380  	}
   381  
   382  	task1, _, _ := dockerTask()
   383  	task2, _, _ := dockerTask()
   384  	task3, _, _ := dockerTask()
   385  	taskList := []*structs.Task{task1, task2, task3}
   386  
   387  	handles := make([]DriverHandle, len(taskList))
   388  
   389  	t.Logf("==> Starting %d tasks", len(taskList))
   390  
   391  	// Let's spin up a bunch of things
   392  	var err error
   393  	for idx, task := range taskList {
   394  		driverCtx, execCtx := testDriverContexts(task)
   395  		defer execCtx.AllocDir.Destroy()
   396  		d := NewDockerDriver(driverCtx)
   397  
   398  		handles[idx], err = d.Start(execCtx, task)
   399  		if err != nil {
   400  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   401  		}
   402  	}
   403  
   404  	t.Log("==> All tasks are started. Terminating...")
   405  
   406  	for idx, handle := range handles {
   407  		if handle == nil {
   408  			t.Errorf("Bad handle for task #%d", idx+1)
   409  			continue
   410  		}
   411  
   412  		err := handle.Kill()
   413  		if err != nil {
   414  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   415  		}
   416  	}
   417  
   418  	t.Log("==> Test complete!")
   419  }
   420  
   421  func TestDocker_StartNVersions(t *testing.T) {
   422  	t.Parallel()
   423  	if !dockerIsConnected(t) {
   424  		t.SkipNow()
   425  	}
   426  
   427  	task1, _, _ := dockerTask()
   428  	task1.Config["image"] = "redis"
   429  
   430  	task2, _, _ := dockerTask()
   431  	task2.Config["image"] = "redis:latest"
   432  
   433  	task3, _, _ := dockerTask()
   434  	task3.Config["image"] = "redis:3.0"
   435  
   436  	taskList := []*structs.Task{task1, task2, task3}
   437  
   438  	handles := make([]DriverHandle, len(taskList))
   439  
   440  	t.Logf("==> Starting %d tasks", len(taskList))
   441  
   442  	// Let's spin up a bunch of things
   443  	var err error
   444  	for idx, task := range taskList {
   445  		driverCtx, execCtx := testDriverContexts(task)
   446  		defer execCtx.AllocDir.Destroy()
   447  		d := NewDockerDriver(driverCtx)
   448  
   449  		handles[idx], err = d.Start(execCtx, task)
   450  		if err != nil {
   451  			t.Errorf("Failed starting task #%d: %s", idx+1, err)
   452  		}
   453  	}
   454  
   455  	t.Log("==> All tasks are started. Terminating...")
   456  
   457  	for idx, handle := range handles {
   458  		if handle == nil {
   459  			t.Errorf("Bad handle for task #%d", idx+1)
   460  			continue
   461  		}
   462  
   463  		err := handle.Kill()
   464  		if err != nil {
   465  			t.Errorf("Failed stopping task #%d: %s", idx+1, err)
   466  		}
   467  	}
   468  
   469  	t.Log("==> Test complete!")
   470  }
   471  
   472  func TestDockerHostNet(t *testing.T) {
   473  	t.Parallel()
   474  	expected := "host"
   475  
   476  	task := &structs.Task{
   477  		Name: "redis-demo",
   478  		Config: map[string]interface{}{
   479  			"image":        "redis",
   480  			"network_mode": expected,
   481  		},
   482  		Resources: &structs.Resources{
   483  			MemoryMB: 256,
   484  			CPU:      512,
   485  		},
   486  		LogConfig: &structs.LogConfig{
   487  			MaxFiles:      10,
   488  			MaxFileSizeMB: 10,
   489  		},
   490  	}
   491  
   492  	client, handle, cleanup := dockerSetup(t, task)
   493  	defer cleanup()
   494  
   495  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   496  	if err != nil {
   497  		t.Fatalf("err: %v", err)
   498  	}
   499  
   500  	actual := container.HostConfig.NetworkMode
   501  	if actual != expected {
   502  		t.Errorf("DNS Network mode doesn't match.\nExpected:\n%s\nGot:\n%s\n", expected, actual)
   503  	}
   504  }
   505  
   506  func TestDockerLabels(t *testing.T) {
   507  	t.Parallel()
   508  	task, _, _ := dockerTask()
   509  	task.Config["labels"] = []map[string]string{
   510  		map[string]string{
   511  			"label1": "value1",
   512  			"label2": "value2",
   513  		},
   514  	}
   515  
   516  	client, handle, cleanup := dockerSetup(t, task)
   517  	defer cleanup()
   518  
   519  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   520  	if err != nil {
   521  		t.Fatalf("err: %v", err)
   522  	}
   523  
   524  	if want, got := 2, len(container.Config.Labels); want != got {
   525  		t.Errorf("Wrong labels count for docker job. Expect: %d, got: %d", want, got)
   526  	}
   527  
   528  	if want, got := "value1", container.Config.Labels["label1"]; want != got {
   529  		t.Errorf("Wrong label value docker job. Expect: %s, got: %s", want, got)
   530  	}
   531  }
   532  
   533  func TestDockerDNS(t *testing.T) {
   534  	t.Parallel()
   535  	task, _, _ := dockerTask()
   536  	task.Config["dns_servers"] = []string{"8.8.8.8", "8.8.4.4"}
   537  	task.Config["dns_search_domains"] = []string{"example.com", "example.org", "example.net"}
   538  
   539  	client, handle, cleanup := dockerSetup(t, task)
   540  	defer cleanup()
   541  
   542  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   543  	if err != nil {
   544  		t.Fatalf("err: %v", err)
   545  	}
   546  
   547  	if !reflect.DeepEqual(task.Config["dns_servers"], container.HostConfig.DNS) {
   548  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_servers"], container.HostConfig.DNS)
   549  	}
   550  
   551  	if !reflect.DeepEqual(task.Config["dns_search_domains"], container.HostConfig.DNSSearch) {
   552  		t.Errorf("DNS Servers don't match.\nExpected:\n%s\nGot:\n%s\n", task.Config["dns_search_domains"], container.HostConfig.DNSSearch)
   553  	}
   554  }
   555  
   556  func inSlice(needle string, haystack []string) bool {
   557  	for _, h := range haystack {
   558  		if h == needle {
   559  			return true
   560  		}
   561  	}
   562  	return false
   563  }
   564  
   565  func TestDockerPortsNoMap(t *testing.T) {
   566  	t.Parallel()
   567  	task, res, dyn := dockerTask()
   568  
   569  	client, handle, cleanup := dockerSetup(t, task)
   570  	defer cleanup()
   571  
   572  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   573  	if err != nil {
   574  		t.Fatalf("err: %v", err)
   575  	}
   576  
   577  	// Verify that the correct ports are EXPOSED
   578  	expectedExposedPorts := map[docker.Port]struct{}{
   579  		docker.Port(fmt.Sprintf("%d/tcp", res)): struct{}{},
   580  		docker.Port(fmt.Sprintf("%d/udp", res)): struct{}{},
   581  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): struct{}{},
   582  		docker.Port(fmt.Sprintf("%d/udp", dyn)): struct{}{},
   583  		// This one comes from the redis container
   584  		docker.Port("6379/tcp"): struct{}{},
   585  	}
   586  
   587  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   588  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   589  	}
   590  
   591  	// Verify that the correct ports are FORWARDED
   592  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   593  		docker.Port(fmt.Sprintf("%d/tcp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   594  		docker.Port(fmt.Sprintf("%d/udp", res)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   595  		docker.Port(fmt.Sprintf("%d/tcp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   596  		docker.Port(fmt.Sprintf("%d/udp", dyn)): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   597  	}
   598  
   599  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   600  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   601  	}
   602  
   603  	expectedEnvironment := map[string]string{
   604  		"NOMAD_ADDR_main":  fmt.Sprintf("127.0.0.1:%d", res),
   605  		"NOMAD_ADDR_REDIS": fmt.Sprintf("127.0.0.1:%d", dyn),
   606  	}
   607  
   608  	for key, val := range expectedEnvironment {
   609  		search := fmt.Sprintf("%s=%s", key, val)
   610  		if !inSlice(search, container.Config.Env) {
   611  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   612  		}
   613  	}
   614  }
   615  
   616  func TestDockerPortsMapping(t *testing.T) {
   617  	t.Parallel()
   618  	task, res, dyn := dockerTask()
   619  	task.Config["port_map"] = []map[string]string{
   620  		map[string]string{
   621  			"main":  "8080",
   622  			"REDIS": "6379",
   623  		},
   624  	}
   625  
   626  	client, handle, cleanup := dockerSetup(t, task)
   627  	defer cleanup()
   628  
   629  	container, err := client.InspectContainer(handle.(*DockerHandle).ContainerID())
   630  	if err != nil {
   631  		t.Fatalf("err: %v", err)
   632  	}
   633  
   634  	// Verify that the correct ports are EXPOSED
   635  	expectedExposedPorts := map[docker.Port]struct{}{
   636  		docker.Port("8080/tcp"): struct{}{},
   637  		docker.Port("8080/udp"): struct{}{},
   638  		docker.Port("6379/tcp"): struct{}{},
   639  		docker.Port("6379/udp"): struct{}{},
   640  	}
   641  
   642  	if !reflect.DeepEqual(container.Config.ExposedPorts, expectedExposedPorts) {
   643  		t.Errorf("Exposed ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedExposedPorts, container.Config.ExposedPorts)
   644  	}
   645  
   646  	// Verify that the correct ports are FORWARDED
   647  	expectedPortBindings := map[docker.Port][]docker.PortBinding{
   648  		docker.Port("8080/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   649  		docker.Port("8080/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", res)}},
   650  		docker.Port("6379/tcp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   651  		docker.Port("6379/udp"): []docker.PortBinding{docker.PortBinding{HostIP: "127.0.0.1", HostPort: fmt.Sprintf("%d", dyn)}},
   652  	}
   653  
   654  	if !reflect.DeepEqual(container.HostConfig.PortBindings, expectedPortBindings) {
   655  		t.Errorf("Forwarded ports don't match.\nExpected:\n%s\nGot:\n%s\n", expectedPortBindings, container.HostConfig.PortBindings)
   656  	}
   657  
   658  	expectedEnvironment := map[string]string{
   659  		"NOMAD_ADDR_main":      "127.0.0.1:8080",
   660  		"NOMAD_ADDR_REDIS":     "127.0.0.1:6379",
   661  		"NOMAD_HOST_PORT_main": "8080",
   662  	}
   663  
   664  	for key, val := range expectedEnvironment {
   665  		search := fmt.Sprintf("%s=%s", key, val)
   666  		if !inSlice(search, container.Config.Env) {
   667  			t.Errorf("Expected to find %s in container environment: %+v", search, container.Config.Env)
   668  		}
   669  	}
   670  }