github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/exec_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hashicorp/nomad/client/config"
    15  	"github.com/hashicorp/nomad/client/driver/env"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/testutil"
    18  
    19  	ctestutils "github.com/hashicorp/nomad/client/testutil"
    20  )
    21  
    22  func TestExecDriver_Fingerprint(t *testing.T) {
    23  	if !testutil.IsTravis() {
    24  		t.Parallel()
    25  	}
    26  	ctestutils.ExecCompatible(t)
    27  	task := &structs.Task{
    28  		Name:      "foo",
    29  		Driver:    "exec",
    30  		Resources: structs.DefaultResources(),
    31  	}
    32  	ctx := testDriverContexts(t, task)
    33  	defer ctx.AllocDir.Destroy()
    34  	d := NewExecDriver(ctx.DriverCtx)
    35  	node := &structs.Node{
    36  		Attributes: map[string]string{
    37  			"unique.cgroup.mountpoint": "/sys/fs/cgroup",
    38  		},
    39  	}
    40  	apply, err := d.Fingerprint(&config.Config{}, node)
    41  	if err != nil {
    42  		t.Fatalf("err: %v", err)
    43  	}
    44  	if !apply {
    45  		t.Fatalf("should apply")
    46  	}
    47  	if node.Attributes["driver.exec"] == "" {
    48  		t.Fatalf("missing driver")
    49  	}
    50  }
    51  
    52  func TestExecDriver_StartOpen_Wait(t *testing.T) {
    53  	if !testutil.IsTravis() {
    54  		t.Parallel()
    55  	}
    56  	ctestutils.ExecCompatible(t)
    57  	task := &structs.Task{
    58  		Name:   "sleep",
    59  		Driver: "exec",
    60  		Config: map[string]interface{}{
    61  			"command": "/bin/sleep",
    62  			"args":    []string{"5"},
    63  		},
    64  		LogConfig: &structs.LogConfig{
    65  			MaxFiles:      10,
    66  			MaxFileSizeMB: 10,
    67  		},
    68  		Resources: basicResources,
    69  	}
    70  
    71  	ctx := testDriverContexts(t, task)
    72  	defer ctx.AllocDir.Destroy()
    73  	d := NewExecDriver(ctx.DriverCtx)
    74  
    75  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
    76  		t.Fatalf("prestart err: %v", err)
    77  	}
    78  	resp, err := d.Start(ctx.ExecCtx, task)
    79  	if err != nil {
    80  		t.Fatalf("err: %v", err)
    81  	}
    82  
    83  	// Attempt to open
    84  	handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID())
    85  	if err != nil {
    86  		t.Fatalf("err: %v", err)
    87  	}
    88  	if handle2 == nil {
    89  		t.Fatalf("missing handle")
    90  	}
    91  
    92  	resp.Handle.Kill()
    93  	handle2.Kill()
    94  }
    95  
    96  func TestExecDriver_Start_Wait(t *testing.T) {
    97  	if !testutil.IsTravis() {
    98  		t.Parallel()
    99  	}
   100  	ctestutils.ExecCompatible(t)
   101  	task := &structs.Task{
   102  		Name:   "sleep",
   103  		Driver: "exec",
   104  		Config: map[string]interface{}{
   105  			"command": "/bin/sleep",
   106  			"args":    []string{"2"},
   107  		},
   108  		LogConfig: &structs.LogConfig{
   109  			MaxFiles:      10,
   110  			MaxFileSizeMB: 10,
   111  		},
   112  		Resources: basicResources,
   113  	}
   114  
   115  	ctx := testDriverContexts(t, task)
   116  	defer ctx.AllocDir.Destroy()
   117  	d := NewExecDriver(ctx.DriverCtx)
   118  
   119  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   120  		t.Fatalf("prestart err: %v", err)
   121  	}
   122  	resp, err := d.Start(ctx.ExecCtx, task)
   123  	if err != nil {
   124  		t.Fatalf("err: %v", err)
   125  	}
   126  
   127  	// Update should be a no-op
   128  	err = resp.Handle.Update(task)
   129  	if err != nil {
   130  		t.Fatalf("err: %v", err)
   131  	}
   132  
   133  	// Task should terminate quickly
   134  	select {
   135  	case res := <-resp.Handle.WaitCh():
   136  		if !res.Successful() {
   137  			t.Fatalf("err: %v", res)
   138  		}
   139  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   140  		t.Fatalf("timeout")
   141  	}
   142  }
   143  
   144  func TestExecDriver_Start_Wait_AllocDir(t *testing.T) {
   145  	if !testutil.IsTravis() {
   146  		t.Parallel()
   147  	}
   148  	ctestutils.ExecCompatible(t)
   149  
   150  	exp := []byte{'w', 'i', 'n'}
   151  	file := "output.txt"
   152  	task := &structs.Task{
   153  		Name:   "sleep",
   154  		Driver: "exec",
   155  		Config: map[string]interface{}{
   156  			"command": "/bin/bash",
   157  			"args": []string{
   158  				"-c",
   159  				fmt.Sprintf(`sleep 1; echo -n %s > ${%s}/%s`, string(exp), env.AllocDir, file),
   160  			},
   161  		},
   162  		LogConfig: &structs.LogConfig{
   163  			MaxFiles:      10,
   164  			MaxFileSizeMB: 10,
   165  		},
   166  		Resources: basicResources,
   167  	}
   168  
   169  	ctx := testDriverContexts(t, task)
   170  	defer ctx.AllocDir.Destroy()
   171  	d := NewExecDriver(ctx.DriverCtx)
   172  
   173  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   174  		t.Fatalf("prestart err: %v", err)
   175  	}
   176  	resp, err := d.Start(ctx.ExecCtx, task)
   177  	if err != nil {
   178  		t.Fatalf("err: %v", err)
   179  	}
   180  
   181  	// Task should terminate quickly
   182  	select {
   183  	case res := <-resp.Handle.WaitCh():
   184  		if !res.Successful() {
   185  			t.Fatalf("err: %v", res)
   186  		}
   187  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   188  		t.Fatalf("timeout")
   189  	}
   190  
   191  	// Check that data was written to the shared alloc directory.
   192  	outputFile := filepath.Join(ctx.AllocDir.SharedDir, file)
   193  	act, err := ioutil.ReadFile(outputFile)
   194  	if err != nil {
   195  		t.Fatalf("Couldn't read expected output: %v", err)
   196  	}
   197  
   198  	if !reflect.DeepEqual(act, exp) {
   199  		t.Fatalf("Command outputted %v; want %v", act, exp)
   200  	}
   201  }
   202  
   203  func TestExecDriver_Start_Kill_Wait(t *testing.T) {
   204  	if !testutil.IsTravis() {
   205  		t.Parallel()
   206  	}
   207  	ctestutils.ExecCompatible(t)
   208  	task := &structs.Task{
   209  		Name:   "sleep",
   210  		Driver: "exec",
   211  		Config: map[string]interface{}{
   212  			"command": "/bin/sleep",
   213  			"args":    []string{"100"},
   214  		},
   215  		LogConfig: &structs.LogConfig{
   216  			MaxFiles:      10,
   217  			MaxFileSizeMB: 10,
   218  		},
   219  		Resources:   basicResources,
   220  		KillTimeout: 10 * time.Second,
   221  	}
   222  
   223  	ctx := testDriverContexts(t, task)
   224  	defer ctx.AllocDir.Destroy()
   225  	d := NewExecDriver(ctx.DriverCtx)
   226  
   227  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   228  		t.Fatalf("prestart err: %v", err)
   229  	}
   230  	resp, err := d.Start(ctx.ExecCtx, task)
   231  	if err != nil {
   232  		t.Fatalf("err: %v", err)
   233  	}
   234  
   235  	go func() {
   236  		time.Sleep(100 * time.Millisecond)
   237  		err := resp.Handle.Kill()
   238  		if err != nil {
   239  			t.Fatalf("err: %v", err)
   240  		}
   241  	}()
   242  
   243  	// Task should terminate quickly
   244  	select {
   245  	case res := <-resp.Handle.WaitCh():
   246  		if res.Successful() {
   247  			t.Fatal("should err")
   248  		}
   249  	case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second):
   250  		t.Fatalf("timeout")
   251  	}
   252  }
   253  
   254  func TestExecDriverUser(t *testing.T) {
   255  	if !testutil.IsTravis() {
   256  		t.Parallel()
   257  	}
   258  	ctestutils.ExecCompatible(t)
   259  	task := &structs.Task{
   260  		Name:   "sleep",
   261  		Driver: "exec",
   262  		User:   "alice",
   263  		Config: map[string]interface{}{
   264  			"command": "/bin/sleep",
   265  			"args":    []string{"100"},
   266  		},
   267  		LogConfig: &structs.LogConfig{
   268  			MaxFiles:      10,
   269  			MaxFileSizeMB: 10,
   270  		},
   271  		Resources:   basicResources,
   272  		KillTimeout: 10 * time.Second,
   273  	}
   274  
   275  	ctx := testDriverContexts(t, task)
   276  	defer ctx.AllocDir.Destroy()
   277  	d := NewExecDriver(ctx.DriverCtx)
   278  
   279  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   280  		t.Fatalf("prestart err: %v", err)
   281  	}
   282  	resp, err := d.Start(ctx.ExecCtx, task)
   283  	if err == nil {
   284  		resp.Handle.Kill()
   285  		t.Fatalf("Should've failed")
   286  	}
   287  	msg := "user alice"
   288  	if !strings.Contains(err.Error(), msg) {
   289  		t.Fatalf("Expecting '%v' in '%v'", msg, err)
   290  	}
   291  }
   292  
   293  // TestExecDriver_HandlerExec ensures the exec driver's handle properly
   294  // executes commands inside the container.
   295  func TestExecDriver_HandlerExec(t *testing.T) {
   296  	if !testutil.IsTravis() {
   297  		t.Parallel()
   298  	}
   299  	ctestutils.ExecCompatible(t)
   300  	task := &structs.Task{
   301  		Name:   "sleep",
   302  		Driver: "exec",
   303  		Config: map[string]interface{}{
   304  			"command": "/bin/sleep",
   305  			"args":    []string{"9000"},
   306  		},
   307  		LogConfig: &structs.LogConfig{
   308  			MaxFiles:      10,
   309  			MaxFileSizeMB: 10,
   310  		},
   311  		Resources: basicResources,
   312  	}
   313  
   314  	ctx := testDriverContexts(t, task)
   315  	defer ctx.AllocDir.Destroy()
   316  	d := NewExecDriver(ctx.DriverCtx)
   317  
   318  	if _, err := d.Prestart(ctx.ExecCtx, task); err != nil {
   319  		t.Fatalf("prestart err: %v", err)
   320  	}
   321  	resp, err := d.Start(ctx.ExecCtx, task)
   322  	if err != nil {
   323  		t.Fatalf("err: %v", err)
   324  	}
   325  	handle := resp.Handle
   326  
   327  	// Exec a command that should work and dump the environment
   328  	out, code, err := handle.Exec(context.Background(), "/bin/sh", []string{"-c", "env | grep NOMAD"})
   329  	if err != nil {
   330  		t.Fatalf("error exec'ing stat: %v", err)
   331  	}
   332  	if code != 0 {
   333  		t.Fatalf("expected `stat /alloc` to succeed but exit code was: %d", code)
   334  	}
   335  
   336  	// Assert exec'd commands are run in a task-like environment
   337  	scriptEnv := make(map[string]string)
   338  	for _, line := range strings.Split(string(out), "\n") {
   339  		if line == "" {
   340  			continue
   341  		}
   342  		parts := strings.SplitN(string(line), "=", 2)
   343  		if len(parts) != 2 {
   344  			t.Fatalf("Invalid env var: %q", line)
   345  		}
   346  		scriptEnv[parts[0]] = parts[1]
   347  	}
   348  	if v, ok := scriptEnv["NOMAD_SECRETS_DIR"]; !ok || v != "/secrets" {
   349  		t.Errorf("Expected NOMAD_SECRETS_DIR=/secrets but found=%t value=%q", ok, v)
   350  	}
   351  	if v, ok := scriptEnv["NOMAD_ALLOC_ID"]; !ok || v != ctx.DriverCtx.allocID {
   352  		t.Errorf("Expected NOMAD_SECRETS_DIR=%q but found=%t value=%q", ctx.DriverCtx.allocID, ok, v)
   353  	}
   354  
   355  	// Assert cgroup membership
   356  	out, code, err = handle.Exec(context.Background(), "/bin/cat", []string{"/proc/self/cgroup"})
   357  	if err != nil {
   358  		t.Fatalf("error exec'ing cat /proc/self/cgroup: %v", err)
   359  	}
   360  	if code != 0 {
   361  		t.Fatalf("expected `cat /proc/self/cgroup` to succeed but exit code was: %d", code)
   362  	}
   363  	found := false
   364  	for _, line := range strings.Split(string(out), "\n") {
   365  		// Every cgroup entry should be /nomad/$ALLOC_ID
   366  		if line == "" {
   367  			continue
   368  		}
   369  		if !strings.Contains(line, ":/nomad/") {
   370  			t.Errorf("Not a member of the alloc's cgroup: expected=...:/nomad/... -- found=%q", line)
   371  			continue
   372  		}
   373  		found = true
   374  	}
   375  	if !found {
   376  		t.Errorf("exec'd command isn't in the task's cgroup")
   377  	}
   378  
   379  	// Exec a command that should fail
   380  	out, code, err = handle.Exec(context.Background(), "/usr/bin/stat", []string{"lkjhdsaflkjshowaisxmcvnlia"})
   381  	if err != nil {
   382  		t.Fatalf("error exec'ing stat: %v", err)
   383  	}
   384  	if code == 0 {
   385  		t.Fatalf("expected `stat` to fail but exit code was: %d", code)
   386  	}
   387  	if expected := "No such file or directory"; !bytes.Contains(out, []byte(expected)) {
   388  		t.Fatalf("expected output to contain %q but found: %q", expected, out)
   389  	}
   390  
   391  	if err := handle.Kill(); err != nil {
   392  		t.Fatalf("error killing exec handle: %v", err)
   393  	}
   394  }