github.com/bigcommerce/nomad@v0.9.3-bc/drivers/docker/driver_unix_test.go (about)

     1  // +build darwin dragonfly freebsd linux netbsd openbsd solaris
     2  
     3  package docker
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	docker "github.com/fsouza/go-dockerclient"
    17  	"github.com/hashicorp/nomad/client/allocdir"
    18  	"github.com/hashicorp/nomad/client/testutil"
    19  	"github.com/hashicorp/nomad/helper/uuid"
    20  	"github.com/hashicorp/nomad/plugins/drivers"
    21  	dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
    22  	tu "github.com/hashicorp/nomad/testutil"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestDockerDriver_User(t *testing.T) {
    28  	if !tu.IsCI() {
    29  		t.Parallel()
    30  	}
    31  	testutil.DockerCompatible(t)
    32  	task, cfg, _ := dockerTask(t)
    33  	task.User = "alice"
    34  	cfg.Command = "/bin/sleep"
    35  	cfg.Args = []string{"10000"}
    36  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
    37  
    38  	d := dockerDriverHarness(t, nil)
    39  	cleanup := d.MkAllocDir(task, true)
    40  	defer cleanup()
    41  	copyImage(t, task.TaskDir(), "busybox.tar")
    42  
    43  	_, _, err := d.StartTask(task)
    44  	if err == nil {
    45  		d.DestroyTask(task.ID, true)
    46  		t.Fatalf("Should've failed")
    47  	}
    48  
    49  	if !strings.Contains(err.Error(), "alice") {
    50  		t.Fatalf("Expected failure string not found, found %q instead", err.Error())
    51  	}
    52  }
    53  
    54  func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) {
    55  	if !tu.IsCI() {
    56  		t.Parallel()
    57  	}
    58  	testutil.DockerCompatible(t)
    59  	require := require.New(t)
    60  
    61  	// Because go-dockerclient doesn't provide api for query network aliases, just check that
    62  	// a container can be created with a 'network_aliases' property
    63  
    64  	// Create network, network-scoped alias is supported only for containers in user defined networks
    65  	client := newTestDockerClient(t)
    66  	networkOpts := docker.CreateNetworkOptions{Name: "foobar", Driver: "bridge"}
    67  	network, err := client.CreateNetwork(networkOpts)
    68  	require.NoError(err)
    69  	defer client.RemoveNetwork(network.ID)
    70  
    71  	expected := []string{"foobar"}
    72  	taskCfg := newTaskConfig("", busyboxLongRunningCmd)
    73  	taskCfg.NetworkMode = network.Name
    74  	taskCfg.NetworkAliases = expected
    75  	task := &drivers.TaskConfig{
    76  		ID:        uuid.Generate(),
    77  		Name:      "busybox",
    78  		Resources: basicResources,
    79  	}
    80  	require.NoError(task.EncodeConcreteDriverConfig(&taskCfg))
    81  
    82  	d := dockerDriverHarness(t, nil)
    83  	cleanup := d.MkAllocDir(task, true)
    84  	defer cleanup()
    85  	copyImage(t, task.TaskDir(), "busybox.tar")
    86  
    87  	_, _, err = d.StartTask(task)
    88  	require.NoError(err)
    89  	require.NoError(d.WaitUntilStarted(task.ID, 5*time.Second))
    90  
    91  	defer d.DestroyTask(task.ID, true)
    92  
    93  	dockerDriver, ok := d.Impl().(*Driver)
    94  	require.True(ok)
    95  
    96  	handle, ok := dockerDriver.tasks.Get(task.ID)
    97  	require.True(ok)
    98  
    99  	_, err = client.InspectContainer(handle.containerID)
   100  	require.NoError(err)
   101  }
   102  
   103  func TestDockerDriver_NetworkMode_Host(t *testing.T) {
   104  	if !tu.IsCI() {
   105  		t.Parallel()
   106  	}
   107  	testutil.DockerCompatible(t)
   108  	expected := "host"
   109  
   110  	taskCfg := newTaskConfig("", busyboxLongRunningCmd)
   111  	taskCfg.NetworkMode = expected
   112  
   113  	task := &drivers.TaskConfig{
   114  		ID:        uuid.Generate(),
   115  		Name:      "busybox-demo",
   116  		Resources: basicResources,
   117  	}
   118  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   119  
   120  	d := dockerDriverHarness(t, nil)
   121  	cleanup := d.MkAllocDir(task, true)
   122  	defer cleanup()
   123  	copyImage(t, task.TaskDir(), "busybox.tar")
   124  
   125  	_, _, err := d.StartTask(task)
   126  	require.NoError(t, err)
   127  
   128  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
   129  
   130  	defer d.DestroyTask(task.ID, true)
   131  
   132  	dockerDriver, ok := d.Impl().(*Driver)
   133  	require.True(t, ok)
   134  
   135  	handle, ok := dockerDriver.tasks.Get(task.ID)
   136  	require.True(t, ok)
   137  
   138  	container, err := client.InspectContainer(handle.containerID)
   139  	if err != nil {
   140  		t.Fatalf("err: %v", err)
   141  	}
   142  
   143  	actual := container.HostConfig.NetworkMode
   144  	require.Equal(t, expected, actual)
   145  }
   146  
   147  func TestDockerDriver_CPUCFSPeriod(t *testing.T) {
   148  	if !tu.IsCI() {
   149  		t.Parallel()
   150  	}
   151  	testutil.DockerCompatible(t)
   152  
   153  	task, cfg, _ := dockerTask(t)
   154  	cfg.CPUHardLimit = true
   155  	cfg.CPUCFSPeriod = 1000000
   156  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   157  
   158  	client, _, handle, cleanup := dockerSetup(t, task)
   159  	defer cleanup()
   160  
   161  	waitForExist(t, client, handle.containerID)
   162  
   163  	container, err := client.InspectContainer(handle.containerID)
   164  	require.NoError(t, err)
   165  
   166  	require.Equal(t, cfg.CPUCFSPeriod, container.HostConfig.CPUPeriod)
   167  }
   168  
   169  func TestDockerDriver_Sysctl_Ulimit(t *testing.T) {
   170  	testutil.DockerCompatible(t)
   171  	task, cfg, _ := dockerTask(t)
   172  	expectedUlimits := map[string]string{
   173  		"nproc":  "4242",
   174  		"nofile": "2048:4096",
   175  	}
   176  	cfg.Sysctl = map[string]string{
   177  		"net.core.somaxconn": "16384",
   178  	}
   179  	cfg.Ulimit = expectedUlimits
   180  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   181  
   182  	client, d, handle, cleanup := dockerSetup(t, task)
   183  	defer cleanup()
   184  	require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
   185  
   186  	container, err := client.InspectContainer(handle.containerID)
   187  	assert.Nil(t, err, "unexpected error: %v", err)
   188  
   189  	want := "16384"
   190  	got := container.HostConfig.Sysctls["net.core.somaxconn"]
   191  	assert.Equal(t, want, got, "Wrong net.core.somaxconn config for docker job. Expect: %s, got: %s", want, got)
   192  
   193  	expectedUlimitLen := 2
   194  	actualUlimitLen := len(container.HostConfig.Ulimits)
   195  	assert.Equal(t, want, got, "Wrong number of ulimit configs for docker job. Expect: %d, got: %d", expectedUlimitLen, actualUlimitLen)
   196  
   197  	for _, got := range container.HostConfig.Ulimits {
   198  		if expectedStr, ok := expectedUlimits[got.Name]; !ok {
   199  			t.Errorf("%s config unexpected for docker job.", got.Name)
   200  		} else {
   201  			if !strings.Contains(expectedStr, ":") {
   202  				expectedStr = expectedStr + ":" + expectedStr
   203  			}
   204  
   205  			splitted := strings.SplitN(expectedStr, ":", 2)
   206  			soft, _ := strconv.Atoi(splitted[0])
   207  			hard, _ := strconv.Atoi(splitted[1])
   208  			assert.Equal(t, int64(soft), got.Soft, "Wrong soft %s ulimit for docker job. Expect: %d, got: %d", got.Name, soft, got.Soft)
   209  			assert.Equal(t, int64(hard), got.Hard, "Wrong hard %s ulimit for docker job. Expect: %d, got: %d", got.Name, hard, got.Hard)
   210  
   211  		}
   212  	}
   213  }
   214  
   215  func TestDockerDriver_Sysctl_Ulimit_Errors(t *testing.T) {
   216  	testutil.DockerCompatible(t)
   217  	brokenConfigs := []map[string]string{
   218  		{
   219  			"nofile": "",
   220  		},
   221  		{
   222  			"nofile": "abc:1234",
   223  		},
   224  		{
   225  			"nofile": "1234:abc",
   226  		},
   227  	}
   228  
   229  	testCases := []struct {
   230  		ulimitConfig map[string]string
   231  		err          error
   232  	}{
   233  		{brokenConfigs[0], fmt.Errorf("Malformed ulimit specification nofile: \"\", cannot be empty")},
   234  		{brokenConfigs[1], fmt.Errorf("Malformed soft ulimit nofile: abc:1234")},
   235  		{brokenConfigs[2], fmt.Errorf("Malformed hard ulimit nofile: 1234:abc")},
   236  	}
   237  
   238  	for _, tc := range testCases {
   239  		task, cfg, _ := dockerTask(t)
   240  		cfg.Ulimit = tc.ulimitConfig
   241  		require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   242  
   243  		d := dockerDriverHarness(t, nil)
   244  		cleanup := d.MkAllocDir(task, true)
   245  		defer cleanup()
   246  		copyImage(t, task.TaskDir(), "busybox.tar")
   247  
   248  		_, _, err := d.StartTask(task)
   249  		require.NotNil(t, err, "Expected non nil error")
   250  		require.Contains(t, err.Error(), tc.err.Error())
   251  	}
   252  }
   253  
   254  // This test does not run on Windows due to stricter path validation in the
   255  // negative case for non existent mount paths. We should write a similar test
   256  // for windows.
   257  func TestDockerDriver_BindMountsHonorVolumesEnabledFlag(t *testing.T) {
   258  	t.Parallel()
   259  
   260  	testutil.DockerCompatible(t)
   261  
   262  	allocDir := "/tmp/nomad/alloc-dir"
   263  
   264  	cases := []struct {
   265  		name            string
   266  		requiresVolumes bool
   267  
   268  		volumeDriver string
   269  		volumes      []string
   270  
   271  		expectedVolumes []string
   272  	}{
   273  		{
   274  			name:            "basic plugin",
   275  			requiresVolumes: true,
   276  			volumeDriver:    "nfs",
   277  			volumes:         []string{"test-path:/tmp/taskpath"},
   278  			expectedVolumes: []string{"test-path:/tmp/taskpath"},
   279  		},
   280  		{
   281  			name:            "absolute default driver",
   282  			requiresVolumes: true,
   283  			volumeDriver:    "",
   284  			volumes:         []string{"/abs/test-path:/tmp/taskpath"},
   285  			expectedVolumes: []string{"/abs/test-path:/tmp/taskpath"},
   286  		},
   287  		{
   288  			name:            "absolute local driver",
   289  			requiresVolumes: true,
   290  			volumeDriver:    "local",
   291  			volumes:         []string{"/abs/test-path:/tmp/taskpath"},
   292  			expectedVolumes: []string{"/abs/test-path:/tmp/taskpath"},
   293  		},
   294  		{
   295  			name:            "relative default driver",
   296  			requiresVolumes: false,
   297  			volumeDriver:    "",
   298  			volumes:         []string{"test-path:/tmp/taskpath"},
   299  			expectedVolumes: []string{"/tmp/nomad/alloc-dir/demo/test-path:/tmp/taskpath"},
   300  		},
   301  		{
   302  			name:            "named volume local driver",
   303  			requiresVolumes: true,
   304  			volumeDriver:    "local",
   305  			volumes:         []string{"test-path:/tmp/taskpath"},
   306  			expectedVolumes: []string{"test-path:/tmp/taskpath"},
   307  		},
   308  		{
   309  			name:            "relative outside task-dir default driver",
   310  			requiresVolumes: false,
   311  			volumeDriver:    "",
   312  			volumes:         []string{"../test-path:/tmp/taskpath"},
   313  			expectedVolumes: []string{"/tmp/nomad/alloc-dir/test-path:/tmp/taskpath"},
   314  		},
   315  		{
   316  			name:            "relative outside alloc-dir default driver",
   317  			requiresVolumes: true,
   318  			volumeDriver:    "",
   319  			volumes:         []string{"../../test-path:/tmp/taskpath"},
   320  			expectedVolumes: []string{"/tmp/nomad/test-path:/tmp/taskpath"},
   321  		},
   322  		{
   323  			name:            "clean path local driver",
   324  			requiresVolumes: true,
   325  			volumeDriver:    "local",
   326  			volumes:         []string{"/tmp/nomad/../test-path:/tmp/taskpath"},
   327  			expectedVolumes: []string{"/tmp/test-path:/tmp/taskpath"},
   328  		},
   329  	}
   330  
   331  	t.Run("with volumes enabled", func(t *testing.T) {
   332  		dh := dockerDriverHarness(t, nil)
   333  		driver := dh.Impl().(*Driver)
   334  		driver.config.Volumes.Enabled = true
   335  
   336  		for _, c := range cases {
   337  			t.Run(c.name, func(t *testing.T) {
   338  				task, cfg, _ := dockerTask(t)
   339  				cfg.VolumeDriver = c.volumeDriver
   340  				cfg.Volumes = c.volumes
   341  
   342  				task.AllocDir = allocDir
   343  				task.Name = "demo"
   344  
   345  				require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   346  
   347  				cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
   348  				require.NoError(t, err)
   349  
   350  				for _, v := range c.expectedVolumes {
   351  					require.Contains(t, cc.HostConfig.Binds, v)
   352  				}
   353  			})
   354  		}
   355  	})
   356  
   357  	t.Run("with volumes disabled", func(t *testing.T) {
   358  		dh := dockerDriverHarness(t, nil)
   359  		driver := dh.Impl().(*Driver)
   360  		driver.config.Volumes.Enabled = false
   361  
   362  		for _, c := range cases {
   363  			t.Run(c.name, func(t *testing.T) {
   364  				task, cfg, _ := dockerTask(t)
   365  				cfg.VolumeDriver = c.volumeDriver
   366  				cfg.Volumes = c.volumes
   367  
   368  				task.AllocDir = allocDir
   369  				task.Name = "demo"
   370  
   371  				require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   372  
   373  				cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
   374  				if c.requiresVolumes {
   375  					require.Error(t, err, "volumes are not enabled")
   376  				} else {
   377  					require.NoError(t, err)
   378  
   379  					for _, v := range c.expectedVolumes {
   380  						require.Contains(t, cc.HostConfig.Binds, v)
   381  					}
   382  				}
   383  			})
   384  		}
   385  	})
   386  }
   387  
   388  // This test does not run on windows due to differences in the definition of
   389  // an absolute path, changing path expansion behaviour. A similar test should
   390  // be written for windows.
   391  func TestDockerDriver_MountsSerialization(t *testing.T) {
   392  	t.Parallel()
   393  	testutil.DockerCompatible(t)
   394  
   395  	allocDir := "/tmp/nomad/alloc-dir"
   396  
   397  	cases := []struct {
   398  		name            string
   399  		requiresVolumes bool
   400  		passedMounts    []DockerMount
   401  		expectedMounts  []docker.HostMount
   402  	}{
   403  		{
   404  			name: "basic volume",
   405  			passedMounts: []DockerMount{
   406  				{
   407  					Target:   "/nomad",
   408  					ReadOnly: true,
   409  					Source:   "test",
   410  				},
   411  			},
   412  			expectedMounts: []docker.HostMount{
   413  				{
   414  					Type:          "volume",
   415  					Target:        "/nomad",
   416  					Source:        "test",
   417  					ReadOnly:      true,
   418  					VolumeOptions: &docker.VolumeOptions{},
   419  				},
   420  			},
   421  		},
   422  		{
   423  			name: "basic bind",
   424  			passedMounts: []DockerMount{
   425  				{
   426  					Type:   "bind",
   427  					Target: "/nomad",
   428  					Source: "test",
   429  				},
   430  			},
   431  			expectedMounts: []docker.HostMount{
   432  				{
   433  					Type:        "bind",
   434  					Target:      "/nomad",
   435  					Source:      "/tmp/nomad/alloc-dir/demo/test",
   436  					BindOptions: &docker.BindOptions{},
   437  				},
   438  			},
   439  		},
   440  		{
   441  			name:            "basic absolute bind",
   442  			requiresVolumes: true,
   443  			passedMounts: []DockerMount{
   444  				{
   445  					Type:   "bind",
   446  					Target: "/nomad",
   447  					Source: "/tmp/test",
   448  				},
   449  			},
   450  			expectedMounts: []docker.HostMount{
   451  				{
   452  					Type:        "bind",
   453  					Target:      "/nomad",
   454  					Source:      "/tmp/test",
   455  					BindOptions: &docker.BindOptions{},
   456  				},
   457  			},
   458  		},
   459  		{
   460  			name:            "bind relative outside",
   461  			requiresVolumes: true,
   462  			passedMounts: []DockerMount{
   463  				{
   464  					Type:   "bind",
   465  					Target: "/nomad",
   466  					Source: "../../test",
   467  				},
   468  			},
   469  			expectedMounts: []docker.HostMount{
   470  				{
   471  					Type:        "bind",
   472  					Target:      "/nomad",
   473  					Source:      "/tmp/nomad/test",
   474  					BindOptions: &docker.BindOptions{},
   475  				},
   476  			},
   477  		},
   478  		{
   479  			name:            "basic tmpfs",
   480  			requiresVolumes: false,
   481  			passedMounts: []DockerMount{
   482  				{
   483  					Type:   "tmpfs",
   484  					Target: "/nomad",
   485  					TmpfsOptions: DockerTmpfsOptions{
   486  						SizeBytes: 321,
   487  						Mode:      0666,
   488  					},
   489  				},
   490  			},
   491  			expectedMounts: []docker.HostMount{
   492  				{
   493  					Type:   "tmpfs",
   494  					Target: "/nomad",
   495  					TempfsOptions: &docker.TempfsOptions{
   496  						SizeBytes: 321,
   497  						Mode:      0666,
   498  					},
   499  				},
   500  			},
   501  		},
   502  	}
   503  
   504  	t.Run("with volumes enabled", func(t *testing.T) {
   505  		dh := dockerDriverHarness(t, nil)
   506  		driver := dh.Impl().(*Driver)
   507  		driver.config.Volumes.Enabled = true
   508  
   509  		for _, c := range cases {
   510  			t.Run(c.name, func(t *testing.T) {
   511  				task, cfg, _ := dockerTask(t)
   512  				cfg.Mounts = c.passedMounts
   513  
   514  				task.AllocDir = allocDir
   515  				task.Name = "demo"
   516  
   517  				require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   518  
   519  				cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
   520  				require.NoError(t, err)
   521  				require.EqualValues(t, c.expectedMounts, cc.HostConfig.Mounts)
   522  			})
   523  		}
   524  	})
   525  
   526  	t.Run("with volumes disabled", func(t *testing.T) {
   527  		dh := dockerDriverHarness(t, nil)
   528  		driver := dh.Impl().(*Driver)
   529  		driver.config.Volumes.Enabled = false
   530  
   531  		for _, c := range cases {
   532  			t.Run(c.name, func(t *testing.T) {
   533  				task, cfg, _ := dockerTask(t)
   534  				cfg.Mounts = c.passedMounts
   535  
   536  				task.AllocDir = allocDir
   537  				task.Name = "demo"
   538  
   539  				require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   540  
   541  				cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
   542  				if c.requiresVolumes {
   543  					require.Error(t, err, "volumes are not enabled")
   544  				} else {
   545  					require.NoError(t, err)
   546  					require.EqualValues(t, c.expectedMounts, cc.HostConfig.Mounts)
   547  				}
   548  			})
   549  		}
   550  	})
   551  }
   552  
   553  // TestDockerDriver_CreateContainerConfig_MountsCombined asserts that
   554  // devices and mounts set by device managers/plugins are honored
   555  // and present in docker.CreateContainerOptions, and that it is appended
   556  // to any devices/mounts a user sets in the task config.
   557  func TestDockerDriver_CreateContainerConfig_MountsCombined(t *testing.T) {
   558  	t.Parallel()
   559  	testutil.DockerCompatible(t)
   560  
   561  	task, cfg, _ := dockerTask(t)
   562  
   563  	task.Devices = []*drivers.DeviceConfig{
   564  		{
   565  			HostPath:    "/dev/fuse",
   566  			TaskPath:    "/container/dev/task-fuse",
   567  			Permissions: "rw",
   568  		},
   569  	}
   570  	task.Mounts = []*drivers.MountConfig{
   571  		{
   572  			HostPath: "/tmp/task-mount",
   573  			TaskPath: "/container/tmp/task-mount",
   574  			Readonly: true,
   575  		},
   576  	}
   577  
   578  	cfg.Devices = []DockerDevice{
   579  		{
   580  			HostPath:          "/dev/stdout",
   581  			ContainerPath:     "/container/dev/cfg-stdout",
   582  			CgroupPermissions: "rwm",
   583  		},
   584  	}
   585  	cfg.Mounts = []DockerMount{
   586  		{
   587  			Type:     "bind",
   588  			Source:   "/tmp/cfg-mount",
   589  			Target:   "/container/tmp/cfg-mount",
   590  			ReadOnly: false,
   591  		},
   592  	}
   593  
   594  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   595  
   596  	dh := dockerDriverHarness(t, nil)
   597  	driver := dh.Impl().(*Driver)
   598  
   599  	c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
   600  	require.NoError(t, err)
   601  
   602  	expectedMounts := []docker.HostMount{
   603  		{
   604  			Type:        "bind",
   605  			Source:      "/tmp/cfg-mount",
   606  			Target:      "/container/tmp/cfg-mount",
   607  			ReadOnly:    false,
   608  			BindOptions: &docker.BindOptions{},
   609  		},
   610  		{
   611  			Type:     "bind",
   612  			Source:   "/tmp/task-mount",
   613  			Target:   "/container/tmp/task-mount",
   614  			ReadOnly: true,
   615  		},
   616  	}
   617  	foundMounts := c.HostConfig.Mounts
   618  	sort.Slice(foundMounts, func(i, j int) bool {
   619  		return foundMounts[i].Target < foundMounts[j].Target
   620  	})
   621  	require.EqualValues(t, expectedMounts, foundMounts)
   622  
   623  	expectedDevices := []docker.Device{
   624  		{
   625  			PathOnHost:        "/dev/stdout",
   626  			PathInContainer:   "/container/dev/cfg-stdout",
   627  			CgroupPermissions: "rwm",
   628  		},
   629  		{
   630  			PathOnHost:        "/dev/fuse",
   631  			PathInContainer:   "/container/dev/task-fuse",
   632  			CgroupPermissions: "rw",
   633  		},
   634  	}
   635  
   636  	foundDevices := c.HostConfig.Devices
   637  	sort.Slice(foundDevices, func(i, j int) bool {
   638  		return foundDevices[i].PathInContainer < foundDevices[j].PathInContainer
   639  	})
   640  	require.EqualValues(t, expectedDevices, foundDevices)
   641  }
   642  
   643  // TestDockerDriver_Cleanup ensures Cleanup removes only downloaded images.
   644  // Doesn't run on windows because it requires an image variant
   645  func TestDockerDriver_Cleanup(t *testing.T) {
   646  	testutil.DockerCompatible(t)
   647  
   648  	// using a small image and an specific point release to avoid accidental conflicts with other tasks
   649  	cfg := newTaskConfig("", []string{"sleep", "100"})
   650  	cfg.Image = "busybox:1.29.2"
   651  	cfg.LoadImage = ""
   652  	task := &drivers.TaskConfig{
   653  		ID:        uuid.Generate(),
   654  		Name:      "cleanup_test",
   655  		Resources: basicResources,
   656  	}
   657  
   658  	require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
   659  
   660  	client, driver, handle, cleanup := dockerSetup(t, task)
   661  	defer cleanup()
   662  
   663  	require.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second))
   664  	// Cleanup
   665  	require.NoError(t, driver.DestroyTask(task.ID, true))
   666  
   667  	// Ensure image was removed
   668  	tu.WaitForResult(func() (bool, error) {
   669  		if _, err := client.InspectImage(cfg.Image); err == nil {
   670  			return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image)
   671  		}
   672  
   673  		return true, nil
   674  	}, func(err error) {
   675  		require.NoError(t, err)
   676  	})
   677  
   678  	// The image doesn't exist which shouldn't be an error when calling
   679  	// Cleanup, so call it again to make sure.
   680  	require.NoError(t, driver.Impl().(*Driver).cleanupImage(handle))
   681  }
   682  
   683  // Tests that images prefixed with "https://" are supported
   684  func TestDockerDriver_Start_Image_HTTPS(t *testing.T) {
   685  	if !tu.IsCI() {
   686  		t.Parallel()
   687  	}
   688  	testutil.DockerCompatible(t)
   689  
   690  	taskCfg := TaskConfig{
   691  		Image: "https://gcr.io/google_containers/pause:0.8.0",
   692  	}
   693  	task := &drivers.TaskConfig{
   694  		ID:        uuid.Generate(),
   695  		Name:      "pause",
   696  		AllocID:   uuid.Generate(),
   697  		Resources: basicResources,
   698  	}
   699  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   700  
   701  	d := dockerDriverHarness(t, nil)
   702  	cleanup := d.MkAllocDir(task, true)
   703  	defer cleanup()
   704  
   705  	_, _, err := d.StartTask(task)
   706  	require.NoError(t, err)
   707  
   708  	d.DestroyTask(task.ID, true)
   709  }
   710  
   711  func newTaskConfig(variant string, command []string) TaskConfig {
   712  	// busyboxImageID is the ID stored in busybox.tar
   713  	busyboxImageID := "busybox:1.29.3"
   714  
   715  	image := busyboxImageID
   716  	loadImage := "busybox.tar"
   717  	if variant != "" {
   718  		image = fmt.Sprintf("%s-%s", busyboxImageID, variant)
   719  		loadImage = fmt.Sprintf("busybox_%s.tar", variant)
   720  	}
   721  
   722  	return TaskConfig{
   723  		Image:     image,
   724  		LoadImage: loadImage,
   725  		Command:   command[0],
   726  		Args:      command[1:],
   727  	}
   728  }
   729  
   730  func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) {
   731  	dst := filepath.Join(taskDir.LocalDir, image)
   732  	copyFile(filepath.Join("./test-resources/docker", image), dst, t)
   733  }
   734  
   735  // copyFile moves an existing file to the destination
   736  func copyFile(src, dst string, t *testing.T) {
   737  	in, err := os.Open(src)
   738  	if err != nil {
   739  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   740  	}
   741  	defer in.Close()
   742  	out, err := os.Create(dst)
   743  	if err != nil {
   744  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   745  	}
   746  	defer func() {
   747  		if err := out.Close(); err != nil {
   748  			t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   749  		}
   750  	}()
   751  	if _, err = io.Copy(out, in); err != nil {
   752  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   753  	}
   754  	if err := out.Sync(); err != nil {
   755  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
   756  	}
   757  }
   758  
   759  func TestDocker_ExecTaskStreaming(t *testing.T) {
   760  	if !tu.IsCI() {
   761  		t.Parallel()
   762  	}
   763  	testutil.DockerCompatible(t)
   764  
   765  	taskCfg := newTaskConfig("", []string{"/bin/sleep", "1000"})
   766  	task := &drivers.TaskConfig{
   767  		ID:        uuid.Generate(),
   768  		Name:      "nc-demo",
   769  		AllocID:   uuid.Generate(),
   770  		Resources: basicResources,
   771  	}
   772  	require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
   773  
   774  	d := dockerDriverHarness(t, nil)
   775  	cleanup := d.MkAllocDir(task, true)
   776  	defer cleanup()
   777  	copyImage(t, task.TaskDir(), "busybox.tar")
   778  
   779  	_, _, err := d.StartTask(task)
   780  	require.NoError(t, err)
   781  
   782  	defer d.DestroyTask(task.ID, true)
   783  
   784  	dtestutil.ExecTaskStreamingConformanceTests(t, d, task.ID)
   785  
   786  }