github.com/janma/nomad@v0.11.3/drivers/rawexec/driver_unix_test.go (about)

     1  // +build !windows
     2  
     3  package rawexec
     4  
     5  import (
     6  	"context"
     7  	"os"
     8  	"regexp"
     9  	"runtime"
    10  	"strconv"
    11  	"syscall"
    12  	"testing"
    13  
    14  	"fmt"
    15  	"io/ioutil"
    16  	"path/filepath"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/hashicorp/nomad/helper/testtask"
    21  	"github.com/hashicorp/nomad/helper/uuid"
    22  	basePlug "github.com/hashicorp/nomad/plugins/base"
    23  	"github.com/hashicorp/nomad/plugins/drivers"
    24  	dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
    25  	"github.com/hashicorp/nomad/testutil"
    26  	"github.com/stretchr/testify/require"
    27  	"golang.org/x/sys/unix"
    28  )
    29  
    30  func TestRawExecDriver_User(t *testing.T) {
    31  	t.Parallel()
    32  	if runtime.GOOS != "linux" {
    33  		t.Skip("Linux only test")
    34  	}
    35  	require := require.New(t)
    36  
    37  	d := newEnabledRawExecDriver(t)
    38  	harness := dtestutil.NewDriverHarness(t, d)
    39  
    40  	task := &drivers.TaskConfig{
    41  		ID:   uuid.Generate(),
    42  		Name: "sleep",
    43  		User: "alice",
    44  	}
    45  
    46  	cleanup := harness.MkAllocDir(task, false)
    47  	defer cleanup()
    48  
    49  	tc := &TaskConfig{
    50  		Command: testtask.Path(),
    51  		Args:    []string{"sleep", "45s"},
    52  	}
    53  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
    54  	testtask.SetTaskConfigEnv(task)
    55  
    56  	_, _, err := harness.StartTask(task)
    57  	require.Error(err)
    58  	msg := "unknown user alice"
    59  	require.Contains(err.Error(), msg)
    60  }
    61  
    62  func TestRawExecDriver_Signal(t *testing.T) {
    63  	t.Parallel()
    64  	if runtime.GOOS != "linux" {
    65  		t.Skip("Linux only test")
    66  	}
    67  	require := require.New(t)
    68  
    69  	d := newEnabledRawExecDriver(t)
    70  	harness := dtestutil.NewDriverHarness(t, d)
    71  
    72  	task := &drivers.TaskConfig{
    73  		ID:   uuid.Generate(),
    74  		Name: "signal",
    75  	}
    76  
    77  	cleanup := harness.MkAllocDir(task, true)
    78  	defer cleanup()
    79  
    80  	tc := &TaskConfig{
    81  		Command: "/bin/bash",
    82  		Args:    []string{"test.sh"},
    83  	}
    84  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
    85  	testtask.SetTaskConfigEnv(task)
    86  
    87  	testFile := filepath.Join(task.TaskDir().Dir, "test.sh")
    88  	testData := []byte(`
    89  at_term() {
    90      echo 'Terminated.'
    91      exit 3
    92  }
    93  trap at_term USR1
    94  while true; do
    95      sleep 1
    96  done
    97  	`)
    98  	require.NoError(ioutil.WriteFile(testFile, testData, 0777))
    99  
   100  	_, _, err := harness.StartTask(task)
   101  	require.NoError(err)
   102  
   103  	go func() {
   104  		time.Sleep(100 * time.Millisecond)
   105  		require.NoError(harness.SignalTask(task.ID, "SIGUSR1"))
   106  	}()
   107  
   108  	// Task should terminate quickly
   109  	waitCh, err := harness.WaitTask(context.Background(), task.ID)
   110  	require.NoError(err)
   111  	select {
   112  	case res := <-waitCh:
   113  		require.False(res.Successful())
   114  		require.Equal(3, res.ExitCode)
   115  	case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second):
   116  		require.Fail("WaitTask timeout")
   117  	}
   118  
   119  	// Check the log file to see it exited because of the signal
   120  	outputFile := filepath.Join(task.TaskDir().LogDir, "signal.stdout.0")
   121  	exp := "Terminated."
   122  	testutil.WaitForResult(func() (bool, error) {
   123  		act, err := ioutil.ReadFile(outputFile)
   124  		if err != nil {
   125  			return false, fmt.Errorf("Couldn't read expected output: %v", err)
   126  		}
   127  
   128  		if strings.TrimSpace(string(act)) != exp {
   129  			t.Logf("Read from %v", outputFile)
   130  			return false, fmt.Errorf("Command outputted %v; want %v", act, exp)
   131  		}
   132  		return true, nil
   133  	}, func(err error) { require.NoError(err) })
   134  }
   135  
   136  func TestRawExecDriver_StartWaitStop(t *testing.T) {
   137  	t.Parallel()
   138  	require := require.New(t)
   139  
   140  	d := newEnabledRawExecDriver(t)
   141  	harness := dtestutil.NewDriverHarness(t, d)
   142  	defer harness.Kill()
   143  
   144  	// Disable cgroups so test works without root
   145  	config := &Config{NoCgroups: true, Enabled: true}
   146  	var data []byte
   147  	require.NoError(basePlug.MsgPackEncode(&data, config))
   148  	bconfig := &basePlug.Config{PluginConfig: data}
   149  	require.NoError(harness.SetConfig(bconfig))
   150  
   151  	task := &drivers.TaskConfig{
   152  		ID:   uuid.Generate(),
   153  		Name: "test",
   154  	}
   155  
   156  	taskConfig := map[string]interface{}{}
   157  	taskConfig["command"] = testtask.Path()
   158  	taskConfig["args"] = []string{"sleep", "100s"}
   159  
   160  	require.NoError(task.EncodeConcreteDriverConfig(&taskConfig))
   161  
   162  	cleanup := harness.MkAllocDir(task, false)
   163  	defer cleanup()
   164  
   165  	handle, _, err := harness.StartTask(task)
   166  	require.NoError(err)
   167  
   168  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   169  	require.NoError(err)
   170  
   171  	require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
   172  
   173  	go func() {
   174  		harness.StopTask(task.ID, 2*time.Second, "SIGINT")
   175  	}()
   176  
   177  	select {
   178  	case result := <-ch:
   179  		require.Equal(int(unix.SIGINT), result.Signal)
   180  	case <-time.After(10 * time.Second):
   181  		require.Fail("timeout waiting for task to shutdown")
   182  	}
   183  
   184  	// Ensure that the task is marked as dead, but account
   185  	// for WaitTask() closing channel before internal state is updated
   186  	testutil.WaitForResult(func() (bool, error) {
   187  		status, err := harness.InspectTask(task.ID)
   188  		if err != nil {
   189  			return false, fmt.Errorf("inspecting task failed: %v", err)
   190  		}
   191  		if status.State != drivers.TaskStateExited {
   192  			return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
   193  		}
   194  
   195  		return true, nil
   196  	}, func(err error) {
   197  		require.NoError(err)
   198  	})
   199  
   200  	require.NoError(harness.DestroyTask(task.ID, true))
   201  }
   202  
   203  // TestRawExecDriver_DestroyKillsAll asserts that when TaskDestroy is called all
   204  // task processes are cleaned up.
   205  func TestRawExecDriver_DestroyKillsAll(t *testing.T) {
   206  	t.Parallel()
   207  
   208  	// This only works reliably with cgroup PID tracking, happens in linux only
   209  	if runtime.GOOS != "linux" {
   210  		t.Skip("Linux only test")
   211  	}
   212  
   213  	require := require.New(t)
   214  
   215  	d := newEnabledRawExecDriver(t)
   216  	harness := dtestutil.NewDriverHarness(t, d)
   217  	defer harness.Kill()
   218  
   219  	task := &drivers.TaskConfig{
   220  		ID:   uuid.Generate(),
   221  		Name: "test",
   222  	}
   223  
   224  	cleanup := harness.MkAllocDir(task, true)
   225  	defer cleanup()
   226  
   227  	taskConfig := map[string]interface{}{}
   228  	taskConfig["command"] = "/bin/sh"
   229  	taskConfig["args"] = []string{"-c", fmt.Sprintf(`sleep 3600 & echo "SLEEP_PID=$!"`)}
   230  
   231  	require.NoError(task.EncodeConcreteDriverConfig(&taskConfig))
   232  
   233  	handle, _, err := harness.StartTask(task)
   234  	require.NoError(err)
   235  	defer harness.DestroyTask(task.ID, true)
   236  
   237  	ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
   238  	require.NoError(err)
   239  
   240  	select {
   241  	case result := <-ch:
   242  		require.True(result.Successful(), "command failed: %#v", result)
   243  	case <-time.After(10 * time.Second):
   244  		require.Fail("timeout waiting for task to shutdown")
   245  	}
   246  
   247  	sleepPid := 0
   248  
   249  	// Ensure that the task is marked as dead, but account
   250  	// for WaitTask() closing channel before internal state is updated
   251  	testutil.WaitForResult(func() (bool, error) {
   252  		stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "test.stdout.0"))
   253  		if err != nil {
   254  			return false, fmt.Errorf("failed to output pid file: %v", err)
   255  		}
   256  
   257  		pidMatch := regexp.MustCompile(`SLEEP_PID=(\d+)`).FindStringSubmatch(string(stdout))
   258  		if len(pidMatch) != 2 {
   259  			return false, fmt.Errorf("failed to find pid in %s", string(stdout))
   260  		}
   261  
   262  		pid, err := strconv.Atoi(pidMatch[1])
   263  		if err != nil {
   264  			return false, fmt.Errorf("pid parts aren't int: %s", pidMatch[1])
   265  		}
   266  
   267  		sleepPid = pid
   268  		return true, nil
   269  	}, func(err error) {
   270  		require.NoError(err)
   271  	})
   272  
   273  	// isProcessRunning returns an error if process is not running
   274  	isProcessRunning := func(pid int) error {
   275  		process, err := os.FindProcess(pid)
   276  		if err != nil {
   277  			return fmt.Errorf("failed to find process: %s", err)
   278  		}
   279  
   280  		err = process.Signal(syscall.Signal(0))
   281  		if err != nil {
   282  			return fmt.Errorf("failed to signal process: %s", err)
   283  		}
   284  
   285  		return nil
   286  	}
   287  
   288  	require.NoError(isProcessRunning(sleepPid))
   289  
   290  	require.NoError(harness.DestroyTask(task.ID, true))
   291  
   292  	testutil.WaitForResult(func() (bool, error) {
   293  		err := isProcessRunning(sleepPid)
   294  		if err == nil {
   295  			return false, fmt.Errorf("child process is still running")
   296  		}
   297  
   298  		if !strings.Contains(err.Error(), "failed to signal process") {
   299  			return false, fmt.Errorf("unexpected error: %v", err)
   300  		}
   301  
   302  		return true, nil
   303  	}, func(err error) {
   304  		require.NoError(err)
   305  	})
   306  }
   307  
   308  func TestRawExec_ExecTaskStreaming(t *testing.T) {
   309  	t.Parallel()
   310  	if runtime.GOOS == "darwin" {
   311  		t.Skip("skip running exec tasks on darwin as darwin has restrictions on starting tty shells")
   312  	}
   313  	require := require.New(t)
   314  
   315  	d := newEnabledRawExecDriver(t)
   316  	harness := dtestutil.NewDriverHarness(t, d)
   317  	defer harness.Kill()
   318  
   319  	task := &drivers.TaskConfig{
   320  		ID:   uuid.Generate(),
   321  		Name: "sleep",
   322  	}
   323  
   324  	cleanup := harness.MkAllocDir(task, false)
   325  	defer cleanup()
   326  
   327  	tc := &TaskConfig{
   328  		Command: testtask.Path(),
   329  		Args:    []string{"sleep", "9000s"},
   330  	}
   331  	require.NoError(task.EncodeConcreteDriverConfig(&tc))
   332  	testtask.SetTaskConfigEnv(task)
   333  
   334  	_, _, err := harness.StartTask(task)
   335  	require.NoError(err)
   336  	defer d.DestroyTask(task.ID, true)
   337  
   338  	dtestutil.ExecTaskStreamingConformanceTests(t, harness, task.ID)
   339  
   340  }