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