github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/drivers/exec/driver_test.go (about)

     1  package exec
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"testing"
    17  	"time"
    18  
    19  	ctestutils "github.com/hashicorp/nomad/client/testutil"
    20  	"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
    21  	"github.com/hashicorp/nomad/helper/testlog"
    22  	"github.com/hashicorp/nomad/helper/testtask"
    23  	"github.com/hashicorp/nomad/helper/uuid"
    24  	"github.com/hashicorp/nomad/nomad/structs"
    25  	basePlug "github.com/hashicorp/nomad/plugins/base"
    26  	"github.com/hashicorp/nomad/plugins/drivers"
    27  	dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
    28  	"github.com/hashicorp/nomad/testutil"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  func TestMain(m *testing.M) {
    33  	if !testtask.Run() {
    34  		os.Exit(m.Run())
    35  	}
    36  }
    37  
    38  var testResources = &drivers.Resources{
    39  	NomadResources: &structs.AllocatedTaskResources{
    40  		Memory: structs.AllocatedMemoryResources{
    41  			MemoryMB: 128,
    42  		},
    43  		Cpu: structs.AllocatedCpuResources{
    44  			CpuShares: 100,
    45  		},
    46  	},
    47  	LinuxResources: &drivers.LinuxResources{
    48  		MemoryLimitBytes: 134217728,
    49  		CPUShares:        100,
    50  	},
    51  }
    52  
    53  func TestExecDriver_Fingerprint_NonLinux(t *testing.T) {
    54  	if !testutil.IsCI() {
    55  		t.Parallel()
    56  	}
    57  	require := require.New(t)
    58  	if runtime.GOOS == "linux" {
    59  		t.Skip("Test only available not on Linux")
    60  	}
    61  
    62  	d := NewExecDriver(testlog.HCLogger(t))
    63  	harness := dtestutil.NewDriverHarness(t, d)
    64  
    65  	fingerCh, err := harness.Fingerprint(context.Background())
    66  	require.NoError(err)
    67  	select {
    68  	case finger := <-fingerCh:
    69  		require.Equal(drivers.HealthStateUndetected, finger.Health)
    70  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
    71  		require.Fail("timeout receiving fingerprint")
    72  	}
    73  }
    74  
    75  func TestExecDriver_Fingerprint(t *testing.T) {
    76  	t.Parallel()
    77  	require := require.New(t)
    78  
    79  	ctestutils.ExecCompatible(t)
    80  
    81  	d := NewExecDriver(testlog.HCLogger(t))
    82  	harness := dtestutil.NewDriverHarness(t, d)
    83  
    84  	fingerCh, err := harness.Fingerprint(context.Background())
    85  	require.NoError(err)
    86  	select {
    87  	case finger := <-fingerCh:
    88  		require.Equal(drivers.HealthStateHealthy, finger.Health)
    89  		require.True(finger.Attributes["driver.exec"].GetBool())
    90  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
    91  		require.Fail("timeout receiving fingerprint")
    92  	}
    93  }
    94  
    95  func TestExecDriver_StartWait(t *testing.T) {
    96  	t.Parallel()
    97  	require := require.New(t)
    98  	ctestutils.ExecCompatible(t)
    99  
   100  	d := NewExecDriver(testlog.HCLogger(t))
   101  	harness := dtestutil.NewDriverHarness(t, d)
   102  	task := &drivers.TaskConfig{
   103  		ID:        uuid.Generate(),
   104  		Name:      "test",
   105  		Resources: testResources,
   106  	}
   107  
   108  	tc := &TaskConfig{
   109  		Command: "cat",
   110  		Args:    []string{"/proc/self/cgroup"},
   111  	}
   112  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   113  
   114  	cleanup := harness.MkAllocDir(task, false)
   115  	defer cleanup()
   116  
   117  	handle, _, err := harness.StartTask(task)
   118  	require.NoError(err)
   119  
   120  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   121  	require.NoError(err)
   122  	result := <-ch
   123  	require.Zero(result.ExitCode)
   124  	require.NoError(harness.DestroyTask(task.ID, true))
   125  }
   126  
   127  func TestExecDriver_StartWaitStopKill(t *testing.T) {
   128  	t.Parallel()
   129  	require := require.New(t)
   130  	ctestutils.ExecCompatible(t)
   131  
   132  	d := NewExecDriver(testlog.HCLogger(t))
   133  	harness := dtestutil.NewDriverHarness(t, d)
   134  	task := &drivers.TaskConfig{
   135  		ID:        uuid.Generate(),
   136  		Name:      "test",
   137  		Resources: testResources,
   138  	}
   139  
   140  	tc := &TaskConfig{
   141  		Command: "/bin/bash",
   142  		Args:    []string{"-c", "echo hi; sleep 600"},
   143  	}
   144  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   145  
   146  	cleanup := harness.MkAllocDir(task, false)
   147  	defer cleanup()
   148  
   149  	handle, _, err := harness.StartTask(task)
   150  	require.NoError(err)
   151  	defer harness.DestroyTask(task.ID, true)
   152  
   153  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   154  	require.NoError(err)
   155  
   156  	require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
   157  
   158  	go func() {
   159  		harness.StopTask(task.ID, 2*time.Second, "SIGINT")
   160  	}()
   161  
   162  	select {
   163  	case result := <-ch:
   164  		require.False(result.Successful())
   165  	case <-time.After(10 * time.Second):
   166  		require.Fail("timeout waiting for task to shutdown")
   167  	}
   168  
   169  	// Ensure that the task is marked as dead, but account
   170  	// for WaitTask() closing channel before internal state is updated
   171  	testutil.WaitForResult(func() (bool, error) {
   172  		status, err := harness.InspectTask(task.ID)
   173  		if err != nil {
   174  			return false, fmt.Errorf("inspecting task failed: %v", err)
   175  		}
   176  		if status.State != drivers.TaskStateExited {
   177  			return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
   178  		}
   179  
   180  		return true, nil
   181  	}, func(err error) {
   182  		require.NoError(err)
   183  	})
   184  
   185  	require.NoError(harness.DestroyTask(task.ID, true))
   186  }
   187  
   188  func TestExecDriver_StartWaitRecover(t *testing.T) {
   189  	t.Parallel()
   190  	require := require.New(t)
   191  	ctestutils.ExecCompatible(t)
   192  
   193  	d := NewExecDriver(testlog.HCLogger(t))
   194  	harness := dtestutil.NewDriverHarness(t, d)
   195  	task := &drivers.TaskConfig{
   196  		ID:        uuid.Generate(),
   197  		Name:      "test",
   198  		Resources: testResources,
   199  	}
   200  
   201  	tc := &TaskConfig{
   202  		Command: "/bin/sleep",
   203  		Args:    []string{"5"},
   204  	}
   205  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   206  
   207  	cleanup := harness.MkAllocDir(task, false)
   208  	defer cleanup()
   209  
   210  	handle, _, err := harness.StartTask(task)
   211  	require.NoError(err)
   212  
   213  	ctx, cancel := context.WithCancel(context.Background())
   214  
   215  	ch, err := harness.WaitTask(ctx, handle.Config.ID)
   216  	require.NoError(err)
   217  
   218  	var wg sync.WaitGroup
   219  	wg.Add(1)
   220  	go func() {
   221  		defer wg.Done()
   222  		result := <-ch
   223  		require.Error(result.Err)
   224  	}()
   225  
   226  	require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
   227  	cancel()
   228  
   229  	waitCh := make(chan struct{})
   230  	go func() {
   231  		defer close(waitCh)
   232  		wg.Wait()
   233  	}()
   234  
   235  	select {
   236  	case <-waitCh:
   237  		status, err := harness.InspectTask(task.ID)
   238  		require.NoError(err)
   239  		require.Equal(drivers.TaskStateRunning, status.State)
   240  	case <-time.After(1 * time.Second):
   241  		require.Fail("timeout waiting for task wait to cancel")
   242  	}
   243  
   244  	// Loose task
   245  	d.(*Driver).tasks.Delete(task.ID)
   246  	_, err = harness.InspectTask(task.ID)
   247  	require.Error(err)
   248  
   249  	require.NoError(harness.RecoverTask(handle))
   250  	status, err := harness.InspectTask(task.ID)
   251  	require.NoError(err)
   252  	require.Equal(drivers.TaskStateRunning, status.State)
   253  
   254  	require.NoError(harness.StopTask(task.ID, 0, ""))
   255  	require.NoError(harness.DestroyTask(task.ID, true))
   256  }
   257  
   258  // TestExecDriver_DestroyKillsAll asserts that when TaskDestroy is called all
   259  // task processes are cleaned up.
   260  func TestExecDriver_DestroyKillsAll(t *testing.T) {
   261  	t.Parallel()
   262  	require := require.New(t)
   263  	ctestutils.ExecCompatible(t)
   264  
   265  	d := NewExecDriver(testlog.HCLogger(t))
   266  	harness := dtestutil.NewDriverHarness(t, d)
   267  	defer harness.Kill()
   268  
   269  	task := &drivers.TaskConfig{
   270  		ID:   uuid.Generate(),
   271  		Name: "test",
   272  	}
   273  
   274  	cleanup := harness.MkAllocDir(task, true)
   275  	defer cleanup()
   276  
   277  	taskConfig := map[string]interface{}{}
   278  	taskConfig["command"] = "/bin/sh"
   279  	taskConfig["args"] = []string{"-c", fmt.Sprintf(`sleep 3600 & echo "SLEEP_PID=$!"`)}
   280  
   281  	require.NoError(task.EncodeConcreteDriverConfig(&taskConfig))
   282  
   283  	handle, _, err := harness.StartTask(task)
   284  	require.NoError(err)
   285  	defer harness.DestroyTask(task.ID, true)
   286  
   287  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   288  	require.NoError(err)
   289  
   290  	select {
   291  	case result := <-ch:
   292  		require.True(result.Successful(), "command failed: %#v", result)
   293  	case <-time.After(10 * time.Second):
   294  		require.Fail("timeout waiting for task to shutdown")
   295  	}
   296  
   297  	sleepPid := 0
   298  
   299  	// Ensure that the task is marked as dead, but account
   300  	// for WaitTask() closing channel before internal state is updated
   301  	testutil.WaitForResult(func() (bool, error) {
   302  		stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "test.stdout.0"))
   303  		if err != nil {
   304  			return false, fmt.Errorf("failed to output pid file: %v", err)
   305  		}
   306  
   307  		pidMatch := regexp.MustCompile(`SLEEP_PID=(\d+)`).FindStringSubmatch(string(stdout))
   308  		if len(pidMatch) != 2 {
   309  			return false, fmt.Errorf("failed to find pid in %s", string(stdout))
   310  		}
   311  
   312  		pid, err := strconv.Atoi(pidMatch[1])
   313  		if err != nil {
   314  			return false, fmt.Errorf("pid parts aren't int: %s", pidMatch[1])
   315  		}
   316  
   317  		sleepPid = pid
   318  		return true, nil
   319  	}, func(err error) {
   320  		require.NoError(err)
   321  	})
   322  
   323  	// isProcessRunning returns an error if process is not running
   324  	isProcessRunning := func(pid int) error {
   325  		process, err := os.FindProcess(pid)
   326  		if err != nil {
   327  			return fmt.Errorf("failed to find process: %s", err)
   328  		}
   329  
   330  		err = process.Signal(syscall.Signal(0))
   331  		if err != nil {
   332  			return fmt.Errorf("failed to signal process: %s", err)
   333  		}
   334  
   335  		return nil
   336  	}
   337  
   338  	require.NoError(isProcessRunning(sleepPid))
   339  
   340  	require.NoError(harness.DestroyTask(task.ID, true))
   341  
   342  	testutil.WaitForResult(func() (bool, error) {
   343  		err := isProcessRunning(sleepPid)
   344  		if err == nil {
   345  			return false, fmt.Errorf("child process is still running")
   346  		}
   347  
   348  		if !strings.Contains(err.Error(), "failed to signal process") {
   349  			return false, fmt.Errorf("unexpected error: %v", err)
   350  		}
   351  
   352  		return true, nil
   353  	}, func(err error) {
   354  		require.NoError(err)
   355  	})
   356  }
   357  
   358  func TestExecDriver_Stats(t *testing.T) {
   359  	t.Parallel()
   360  	require := require.New(t)
   361  	ctestutils.ExecCompatible(t)
   362  
   363  	d := NewExecDriver(testlog.HCLogger(t))
   364  	harness := dtestutil.NewDriverHarness(t, d)
   365  	task := &drivers.TaskConfig{
   366  		ID:        uuid.Generate(),
   367  		Name:      "test",
   368  		Resources: testResources,
   369  	}
   370  
   371  	tc := &TaskConfig{
   372  		Command: "/bin/sleep",
   373  		Args:    []string{"5"},
   374  	}
   375  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   376  
   377  	cleanup := harness.MkAllocDir(task, false)
   378  	defer cleanup()
   379  
   380  	handle, _, err := harness.StartTask(task)
   381  	require.NoError(err)
   382  	require.NotNil(handle)
   383  
   384  	require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
   385  	ctx, cancel := context.WithCancel(context.Background())
   386  	defer cancel()
   387  	statsCh, err := harness.TaskStats(ctx, task.ID, time.Second*10)
   388  	require.NoError(err)
   389  	select {
   390  	case stats := <-statsCh:
   391  		require.NotZero(stats.ResourceUsage.MemoryStats.RSS)
   392  		require.NotZero(stats.Timestamp)
   393  		require.WithinDuration(time.Now(), time.Unix(0, stats.Timestamp), time.Second)
   394  	case <-time.After(time.Second):
   395  		require.Fail("timeout receiving from channel")
   396  	}
   397  
   398  	require.NoError(harness.DestroyTask(task.ID, true))
   399  }
   400  
   401  func TestExecDriver_Start_Wait_AllocDir(t *testing.T) {
   402  	t.Parallel()
   403  	require := require.New(t)
   404  	ctestutils.ExecCompatible(t)
   405  
   406  	d := NewExecDriver(testlog.HCLogger(t))
   407  	harness := dtestutil.NewDriverHarness(t, d)
   408  	task := &drivers.TaskConfig{
   409  		ID:        uuid.Generate(),
   410  		Name:      "sleep",
   411  		Resources: testResources,
   412  	}
   413  	cleanup := harness.MkAllocDir(task, false)
   414  	defer cleanup()
   415  
   416  	exp := []byte{'w', 'i', 'n'}
   417  	file := "output.txt"
   418  	tc := &TaskConfig{
   419  		Command: "/bin/bash",
   420  		Args: []string{
   421  			"-c",
   422  			fmt.Sprintf(`sleep 1; echo -n %s > /alloc/%s`, string(exp), file),
   423  		},
   424  	}
   425  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   426  
   427  	handle, _, err := harness.StartTask(task)
   428  	require.NoError(err)
   429  	require.NotNil(handle)
   430  
   431  	// Task should terminate quickly
   432  	waitCh, err := harness.WaitTask(context.Background(), task.ID)
   433  	require.NoError(err)
   434  	select {
   435  	case res := <-waitCh:
   436  		require.True(res.Successful(), "task should have exited successfully: %v", res)
   437  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   438  		require.Fail("timeout waiting for task")
   439  	}
   440  
   441  	// Check that data was written to the shared alloc directory.
   442  	outputFile := filepath.Join(task.TaskDir().SharedAllocDir, file)
   443  	act, err := ioutil.ReadFile(outputFile)
   444  	require.NoError(err)
   445  	require.Exactly(exp, act)
   446  
   447  	require.NoError(harness.DestroyTask(task.ID, true))
   448  }
   449  
   450  func TestExecDriver_User(t *testing.T) {
   451  	t.Parallel()
   452  	require := require.New(t)
   453  	ctestutils.ExecCompatible(t)
   454  
   455  	d := NewExecDriver(testlog.HCLogger(t))
   456  	harness := dtestutil.NewDriverHarness(t, d)
   457  	task := &drivers.TaskConfig{
   458  		ID:        uuid.Generate(),
   459  		Name:      "sleep",
   460  		User:      "alice",
   461  		Resources: testResources,
   462  	}
   463  	cleanup := harness.MkAllocDir(task, false)
   464  	defer cleanup()
   465  
   466  	tc := &TaskConfig{
   467  		Command: "/bin/sleep",
   468  		Args:    []string{"100"},
   469  	}
   470  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   471  
   472  	handle, _, err := harness.StartTask(task)
   473  	require.Error(err)
   474  	require.Nil(handle)
   475  
   476  	msg := "user alice"
   477  	if !strings.Contains(err.Error(), msg) {
   478  		t.Fatalf("Expecting '%v' in '%v'", msg, err)
   479  	}
   480  }
   481  
   482  // TestExecDriver_HandlerExec ensures the exec driver's handle properly
   483  // executes commands inside the container.
   484  func TestExecDriver_HandlerExec(t *testing.T) {
   485  	t.Parallel()
   486  	require := require.New(t)
   487  	ctestutils.ExecCompatible(t)
   488  
   489  	d := NewExecDriver(testlog.HCLogger(t))
   490  	harness := dtestutil.NewDriverHarness(t, d)
   491  	task := &drivers.TaskConfig{
   492  		ID:        uuid.Generate(),
   493  		Name:      "sleep",
   494  		Resources: testResources,
   495  	}
   496  	cleanup := harness.MkAllocDir(task, false)
   497  	defer cleanup()
   498  
   499  	tc := &TaskConfig{
   500  		Command: "/bin/sleep",
   501  		Args:    []string{"9000"},
   502  	}
   503  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   504  
   505  	handle, _, err := harness.StartTask(task)
   506  	require.NoError(err)
   507  	require.NotNil(handle)
   508  
   509  	// Exec a command that should work and dump the environment
   510  	// TODO: enable section when exec env is fully loaded
   511  	/*res, err := harness.ExecTask(task.ID, []string{"/bin/sh", "-c", "env | grep ^NOMAD"}, time.Second)
   512  	require.NoError(err)
   513  	require.True(res.ExitResult.Successful())
   514  
   515  	// Assert exec'd commands are run in a task-like environment
   516  	scriptEnv := make(map[string]string)
   517  	for _, line := range strings.Split(string(res.Stdout), "\n") {
   518  		if line == "" {
   519  			continue
   520  		}
   521  		parts := strings.SplitN(string(line), "=", 2)
   522  		if len(parts) != 2 {
   523  			t.Fatalf("Invalid env var: %q", line)
   524  		}
   525  		scriptEnv[parts[0]] = parts[1]
   526  	}
   527  	if v, ok := scriptEnv["NOMAD_SECRETS_DIR"]; !ok || v != "/secrets" {
   528  		t.Errorf("Expected NOMAD_SECRETS_DIR=/secrets but found=%t value=%q", ok, v)
   529  	}*/
   530  
   531  	// Assert cgroup membership
   532  	res, err := harness.ExecTask(task.ID, []string{"/bin/cat", "/proc/self/cgroup"}, time.Second)
   533  	require.NoError(err)
   534  	require.True(res.ExitResult.Successful())
   535  	found := false
   536  	for _, line := range strings.Split(string(res.Stdout), "\n") {
   537  		// Every cgroup entry should be /nomad/$ALLOC_ID
   538  		if line == "" {
   539  			continue
   540  		}
   541  		// Skip rdma subsystem; rdma was added in most recent kernels and libcontainer/docker
   542  		// don't isolate it by default.
   543  		if strings.Contains(line, ":rdma:") {
   544  			continue
   545  		}
   546  		if !strings.Contains(line, ":/nomad/") {
   547  			t.Errorf("Not a member of the alloc's cgroup: expected=...:/nomad/... -- found=%q", line)
   548  			continue
   549  		}
   550  		found = true
   551  	}
   552  	require.True(found, "exec'd command isn't in the task's cgroup")
   553  
   554  	// Exec a command that should fail
   555  	res, err = harness.ExecTask(task.ID, []string{"/usr/bin/stat", "lkjhdsaflkjshowaisxmcvnlia"}, time.Second)
   556  	require.NoError(err)
   557  	require.False(res.ExitResult.Successful())
   558  	if expected := "No such file or directory"; !bytes.Contains(res.Stdout, []byte(expected)) {
   559  		t.Fatalf("expected output to contain %q but found: %q", expected, res.Stdout)
   560  	}
   561  
   562  	require.NoError(harness.DestroyTask(task.ID, true))
   563  }
   564  
   565  func TestExecDriver_DevicesAndMounts(t *testing.T) {
   566  	t.Parallel()
   567  	require := require.New(t)
   568  	ctestutils.ExecCompatible(t)
   569  
   570  	tmpDir, err := ioutil.TempDir("", "exec_binds_mounts")
   571  	require.NoError(err)
   572  	defer os.RemoveAll(tmpDir)
   573  
   574  	err = ioutil.WriteFile(filepath.Join(tmpDir, "testfile"), []byte("from-host"), 600)
   575  	require.NoError(err)
   576  
   577  	d := NewExecDriver(testlog.HCLogger(t))
   578  	harness := dtestutil.NewDriverHarness(t, d)
   579  	task := &drivers.TaskConfig{
   580  		ID:         uuid.Generate(),
   581  		Name:       "test",
   582  		User:       "root", // need permission to read mounts paths
   583  		Resources:  testResources,
   584  		StdoutPath: filepath.Join(tmpDir, "task-stdout"),
   585  		StderrPath: filepath.Join(tmpDir, "task-stderr"),
   586  		Devices: []*drivers.DeviceConfig{
   587  			{
   588  				TaskPath:    "/dev/inserted-random",
   589  				HostPath:    "/dev/random",
   590  				Permissions: "rw",
   591  			},
   592  		},
   593  		Mounts: []*drivers.MountConfig{
   594  			{
   595  				TaskPath: "/tmp/task-path-rw",
   596  				HostPath: tmpDir,
   597  				Readonly: false,
   598  			},
   599  			{
   600  				TaskPath: "/tmp/task-path-ro",
   601  				HostPath: tmpDir,
   602  				Readonly: true,
   603  			},
   604  		},
   605  	}
   606  
   607  	require.NoError(ioutil.WriteFile(task.StdoutPath, []byte{}, 660))
   608  	require.NoError(ioutil.WriteFile(task.StderrPath, []byte{}, 660))
   609  
   610  	tc := &TaskConfig{
   611  		Command: "/bin/bash",
   612  		Args: []string{"-c", `
   613  export LANG=en.UTF-8
   614  echo "mounted device /inserted-random: $(stat -c '%t:%T' /dev/inserted-random)"
   615  echo "reading from ro path: $(cat /tmp/task-path-ro/testfile)"
   616  echo "reading from rw path: $(cat /tmp/task-path-rw/testfile)"
   617  touch /tmp/task-path-rw/testfile && echo 'overwriting file in rw succeeded'
   618  touch /tmp/task-path-rw/testfile-from-rw && echo from-exec >  /tmp/task-path-rw/testfile-from-rw && echo 'writing new file in rw succeeded'
   619  touch /tmp/task-path-ro/testfile && echo 'overwriting file in ro succeeded'
   620  touch /tmp/task-path-ro/testfile-from-ro && echo from-exec >  /tmp/task-path-ro/testfile-from-ro && echo 'writing new file in ro succeeded'
   621  exit 0
   622  `},
   623  	}
   624  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   625  
   626  	cleanup := harness.MkAllocDir(task, false)
   627  	defer cleanup()
   628  
   629  	handle, _, err := harness.StartTask(task)
   630  	require.NoError(err)
   631  
   632  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   633  	require.NoError(err)
   634  	result := <-ch
   635  	require.NoError(harness.DestroyTask(task.ID, true))
   636  
   637  	stdout, err := ioutil.ReadFile(task.StdoutPath)
   638  	require.NoError(err)
   639  	require.Equal(`mounted device /inserted-random: 1:8
   640  reading from ro path: from-host
   641  reading from rw path: from-host
   642  overwriting file in rw succeeded
   643  writing new file in rw succeeded`, strings.TrimSpace(string(stdout)))
   644  
   645  	stderr, err := ioutil.ReadFile(task.StderrPath)
   646  	require.NoError(err)
   647  	require.Equal(`touch: cannot touch '/tmp/task-path-ro/testfile': Read-only file system
   648  touch: cannot touch '/tmp/task-path-ro/testfile-from-ro': Read-only file system`, strings.TrimSpace(string(stderr)))
   649  
   650  	// testing exit code last so we can inspect output first
   651  	require.Zero(result.ExitCode)
   652  
   653  	fromRWContent, err := ioutil.ReadFile(filepath.Join(tmpDir, "testfile-from-rw"))
   654  	require.NoError(err)
   655  	require.Equal("from-exec", strings.TrimSpace(string(fromRWContent)))
   656  }
   657  
   658  func TestConfig_ParseAllHCL(t *testing.T) {
   659  	cfgStr := `
   660  config {
   661    command = "/bin/bash"
   662    args = ["-c", "echo hello"]
   663  }`
   664  
   665  	expected := &TaskConfig{
   666  		Command: "/bin/bash",
   667  		Args:    []string{"-c", "echo hello"},
   668  	}
   669  
   670  	var tc *TaskConfig
   671  	hclutils.NewConfigParser(taskConfigSpec).ParseHCL(t, cfgStr, &tc)
   672  
   673  	require.EqualValues(t, expected, tc)
   674  }
   675  
   676  func TestExecDriver_NoPivotRoot(t *testing.T) {
   677  	t.Parallel()
   678  	require := require.New(t)
   679  	ctestutils.ExecCompatible(t)
   680  
   681  	d := NewExecDriver(testlog.HCLogger(t))
   682  	harness := dtestutil.NewDriverHarness(t, d)
   683  
   684  	config := &Config{NoPivotRoot: true}
   685  	var data []byte
   686  	require.NoError(basePlug.MsgPackEncode(&data, config))
   687  	bconfig := &basePlug.Config{PluginConfig: data}
   688  	require.NoError(harness.SetConfig(bconfig))
   689  
   690  	task := &drivers.TaskConfig{
   691  		ID:        uuid.Generate(),
   692  		Name:      "sleep",
   693  		Resources: testResources,
   694  	}
   695  	cleanup := harness.MkAllocDir(task, false)
   696  	defer cleanup()
   697  
   698  	tc := &TaskConfig{
   699  		Command: "/bin/sleep",
   700  		Args:    []string{"100"},
   701  	}
   702  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   703  
   704  	handle, _, err := harness.StartTask(task)
   705  	require.NoError(err)
   706  	require.NotNil(handle)
   707  }