github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/client/driver/exec_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"syscall"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hashicorp/nomad/client/config"
    16  	"github.com/hashicorp/nomad/client/driver/env"
    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  	ctestutils.ExecCompatible(t)
    25  	task := &structs.Task{
    26  		Name:      "foo",
    27  		Resources: structs.DefaultResources(),
    28  	}
    29  	driverCtx, execCtx := testDriverContexts(task)
    30  	defer execCtx.AllocDir.Destroy()
    31  	d := NewExecDriver(driverCtx)
    32  	node := &structs.Node{
    33  		Attributes: map[string]string{
    34  			"unique.cgroup.mountpoint": "/sys/fs/cgroup",
    35  		},
    36  	}
    37  	apply, err := d.Fingerprint(&config.Config{}, node)
    38  	if err != nil {
    39  		t.Fatalf("err: %v", err)
    40  	}
    41  	if !apply {
    42  		t.Fatalf("should apply")
    43  	}
    44  	if node.Attributes["driver.exec"] == "" {
    45  		t.Fatalf("missing driver")
    46  	}
    47  }
    48  
    49  func TestExecDriver_StartOpen_Wait(t *testing.T) {
    50  	ctestutils.ExecCompatible(t)
    51  	task := &structs.Task{
    52  		Name: "sleep",
    53  		Config: map[string]interface{}{
    54  			"command": "/bin/sleep",
    55  			"args":    []string{"5"},
    56  		},
    57  		LogConfig: &structs.LogConfig{
    58  			MaxFiles:      10,
    59  			MaxFileSizeMB: 10,
    60  		},
    61  		Resources: basicResources,
    62  	}
    63  
    64  	driverCtx, execCtx := testDriverContexts(task)
    65  	defer execCtx.AllocDir.Destroy()
    66  	d := NewExecDriver(driverCtx)
    67  
    68  	handle, err := d.Start(execCtx, task)
    69  	if err != nil {
    70  		t.Fatalf("err: %v", err)
    71  	}
    72  	if handle == nil {
    73  		t.Fatalf("missing handle")
    74  	}
    75  
    76  	// Attempt to open
    77  	handle2, err := d.Open(execCtx, handle.ID())
    78  	if err != nil {
    79  		t.Fatalf("err: %v", err)
    80  	}
    81  	if handle2 == nil {
    82  		t.Fatalf("missing handle")
    83  	}
    84  
    85  	handle.Kill()
    86  	handle2.Kill()
    87  }
    88  
    89  func TestExecDriver_KillUserPid_OnPluginReconnectFailure(t *testing.T) {
    90  	ctestutils.ExecCompatible(t)
    91  	task := &structs.Task{
    92  		Name: "sleep",
    93  		Config: map[string]interface{}{
    94  			"command": "/bin/sleep",
    95  			"args":    []string{"1000000"},
    96  		},
    97  		LogConfig: &structs.LogConfig{
    98  			MaxFiles:      10,
    99  			MaxFileSizeMB: 10,
   100  		},
   101  		Resources: basicResources,
   102  	}
   103  
   104  	driverCtx, execCtx := testDriverContexts(task)
   105  	defer execCtx.AllocDir.Destroy()
   106  	d := NewExecDriver(driverCtx)
   107  
   108  	handle, err := d.Start(execCtx, task)
   109  	if err != nil {
   110  		t.Fatalf("err: %v", err)
   111  	}
   112  	if handle == nil {
   113  		t.Fatalf("missing handle")
   114  	}
   115  	defer handle.Kill()
   116  
   117  	id := &execId{}
   118  	if err := json.Unmarshal([]byte(handle.ID()), id); err != nil {
   119  		t.Fatalf("Failed to parse handle '%s': %v", handle.ID(), err)
   120  	}
   121  	pluginPid := id.PluginConfig.Pid
   122  	proc, err := os.FindProcess(pluginPid)
   123  	if err != nil {
   124  		t.Fatalf("can't find plugin pid: %v", pluginPid)
   125  	}
   126  	if err := proc.Kill(); err != nil {
   127  		t.Fatalf("can't kill plugin pid: %v", err)
   128  	}
   129  
   130  	// Attempt to open
   131  	handle2, err := d.Open(execCtx, handle.ID())
   132  	if err == nil {
   133  		t.Fatalf("expected error")
   134  	}
   135  	if handle2 != nil {
   136  		handle2.Kill()
   137  		t.Fatalf("expected handle2 to be nil")
   138  	}
   139  	// Test if the userpid is still present
   140  	userProc, err := os.FindProcess(id.UserPid)
   141  
   142  	err = userProc.Signal(syscall.Signal(0))
   143  
   144  	if err == nil {
   145  		t.Fatalf("expected user process to die")
   146  	}
   147  }
   148  
   149  func TestExecDriver_Start_Wait(t *testing.T) {
   150  	ctestutils.ExecCompatible(t)
   151  	task := &structs.Task{
   152  		Name: "sleep",
   153  		Config: map[string]interface{}{
   154  			"command": "/bin/sleep",
   155  			"args":    []string{"2"},
   156  		},
   157  		LogConfig: &structs.LogConfig{
   158  			MaxFiles:      10,
   159  			MaxFileSizeMB: 10,
   160  		},
   161  		Resources: basicResources,
   162  	}
   163  
   164  	driverCtx, execCtx := testDriverContexts(task)
   165  	defer execCtx.AllocDir.Destroy()
   166  	d := NewExecDriver(driverCtx)
   167  
   168  	handle, err := d.Start(execCtx, task)
   169  	if err != nil {
   170  		t.Fatalf("err: %v", err)
   171  	}
   172  	if handle == nil {
   173  		t.Fatalf("missing handle")
   174  	}
   175  
   176  	// Update should be a no-op
   177  	err = handle.Update(task)
   178  	if err != nil {
   179  		t.Fatalf("err: %v", err)
   180  	}
   181  
   182  	// Task should terminate quickly
   183  	select {
   184  	case res := <-handle.WaitCh():
   185  		if !res.Successful() {
   186  			t.Fatalf("err: %v", res)
   187  		}
   188  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   189  		t.Fatalf("timeout")
   190  	}
   191  }
   192  
   193  func TestExecDriver_Start_Wait_AllocDir(t *testing.T) {
   194  	ctestutils.ExecCompatible(t)
   195  
   196  	exp := []byte{'w', 'i', 'n'}
   197  	file := "output.txt"
   198  	task := &structs.Task{
   199  		Name: "sleep",
   200  		Config: map[string]interface{}{
   201  			"command": "/bin/bash",
   202  			"args": []string{
   203  				"-c",
   204  				fmt.Sprintf(`sleep 1; echo -n %s > ${%s}/%s`, string(exp), env.AllocDir, file),
   205  			},
   206  		},
   207  		LogConfig: &structs.LogConfig{
   208  			MaxFiles:      10,
   209  			MaxFileSizeMB: 10,
   210  		},
   211  		Resources: basicResources,
   212  	}
   213  
   214  	driverCtx, execCtx := testDriverContexts(task)
   215  	defer execCtx.AllocDir.Destroy()
   216  	d := NewExecDriver(driverCtx)
   217  
   218  	handle, err := d.Start(execCtx, task)
   219  	if err != nil {
   220  		t.Fatalf("err: %v", err)
   221  	}
   222  	if handle == nil {
   223  		t.Fatalf("missing handle")
   224  	}
   225  
   226  	// Task should terminate quickly
   227  	select {
   228  	case res := <-handle.WaitCh():
   229  		if !res.Successful() {
   230  			t.Fatalf("err: %v", res)
   231  		}
   232  	case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second):
   233  		t.Fatalf("timeout")
   234  	}
   235  
   236  	// Check that data was written to the shared alloc directory.
   237  	outputFile := filepath.Join(execCtx.AllocDir.SharedDir, file)
   238  	act, err := ioutil.ReadFile(outputFile)
   239  	if err != nil {
   240  		t.Fatalf("Couldn't read expected output: %v", err)
   241  	}
   242  
   243  	if !reflect.DeepEqual(act, exp) {
   244  		t.Fatalf("Command outputted %v; want %v", act, exp)
   245  	}
   246  }
   247  
   248  func TestExecDriver_Start_Kill_Wait(t *testing.T) {
   249  	ctestutils.ExecCompatible(t)
   250  	task := &structs.Task{
   251  		Name: "sleep",
   252  		Config: map[string]interface{}{
   253  			"command": "/bin/sleep",
   254  			"args":    []string{"100"},
   255  		},
   256  		LogConfig: &structs.LogConfig{
   257  			MaxFiles:      10,
   258  			MaxFileSizeMB: 10,
   259  		},
   260  		Resources:   basicResources,
   261  		KillTimeout: 10 * time.Second,
   262  	}
   263  
   264  	driverCtx, execCtx := testDriverContexts(task)
   265  	defer execCtx.AllocDir.Destroy()
   266  	d := NewExecDriver(driverCtx)
   267  
   268  	handle, err := d.Start(execCtx, task)
   269  	if err != nil {
   270  		t.Fatalf("err: %v", err)
   271  	}
   272  	if handle == nil {
   273  		t.Fatalf("missing handle")
   274  	}
   275  
   276  	go func() {
   277  		time.Sleep(100 * time.Millisecond)
   278  		err := handle.Kill()
   279  		if err != nil {
   280  			t.Fatalf("err: %v", err)
   281  		}
   282  	}()
   283  
   284  	// Task should terminate quickly
   285  	select {
   286  	case res := <-handle.WaitCh():
   287  		if res.Successful() {
   288  			t.Fatal("should err")
   289  		}
   290  	case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second):
   291  		t.Fatalf("timeout")
   292  	}
   293  }
   294  
   295  func TestExecDriver_Signal(t *testing.T) {
   296  	ctestutils.ExecCompatible(t)
   297  	task := &structs.Task{
   298  		Name: "signal",
   299  		Config: map[string]interface{}{
   300  			"command": "/bin/bash",
   301  			"args":    []string{"test.sh"},
   302  		},
   303  		LogConfig: &structs.LogConfig{
   304  			MaxFiles:      10,
   305  			MaxFileSizeMB: 10,
   306  		},
   307  		Resources:   basicResources,
   308  		KillTimeout: 10 * time.Second,
   309  	}
   310  
   311  	driverCtx, execCtx := testDriverContexts(task)
   312  	defer execCtx.AllocDir.Destroy()
   313  	d := NewExecDriver(driverCtx)
   314  
   315  	testFile := filepath.Join(execCtx.AllocDir.TaskDirs["signal"], "test.sh")
   316  	testData := []byte(`
   317  at_term() {
   318      echo 'Terminated.'
   319      exit 3
   320  }
   321  trap at_term USR1
   322  while true; do
   323      sleep 1
   324  done
   325  	`)
   326  	if err := ioutil.WriteFile(testFile, testData, 0777); err != nil {
   327  		fmt.Errorf("Failed to write data")
   328  	}
   329  
   330  	handle, err := d.Start(execCtx, task)
   331  	if err != nil {
   332  		t.Fatalf("err: %v", err)
   333  	}
   334  	if handle == nil {
   335  		t.Fatalf("missing handle")
   336  	}
   337  
   338  	go func() {
   339  		time.Sleep(100 * time.Millisecond)
   340  		err := handle.Signal(syscall.SIGUSR1)
   341  		if err != nil {
   342  			t.Fatalf("err: %v", err)
   343  		}
   344  	}()
   345  
   346  	// Task should terminate quickly
   347  	select {
   348  	case res := <-handle.WaitCh():
   349  		if res.Successful() {
   350  			t.Fatal("should err")
   351  		}
   352  	case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second):
   353  		t.Fatalf("timeout")
   354  	}
   355  
   356  	// Check the log file to see it exited because of the signal
   357  	outputFile := filepath.Join(execCtx.AllocDir.LogDir(), "signal.stdout.0")
   358  	act, err := ioutil.ReadFile(outputFile)
   359  	if err != nil {
   360  		t.Fatalf("Couldn't read expected output: %v", err)
   361  	}
   362  
   363  	exp := "Terminated."
   364  	if strings.TrimSpace(string(act)) != exp {
   365  		t.Logf("Read from %v", outputFile)
   366  		t.Fatalf("Command outputted %v; want %v", act, exp)
   367  	}
   368  }
   369  
   370  func TestExecDriverUser(t *testing.T) {
   371  	ctestutils.ExecCompatible(t)
   372  	task := &structs.Task{
   373  		Name: "sleep",
   374  		User: "alice",
   375  		Config: map[string]interface{}{
   376  			"command": "/bin/sleep",
   377  			"args":    []string{"100"},
   378  		},
   379  		LogConfig: &structs.LogConfig{
   380  			MaxFiles:      10,
   381  			MaxFileSizeMB: 10,
   382  		},
   383  		Resources:   basicResources,
   384  		KillTimeout: 10 * time.Second,
   385  	}
   386  
   387  	driverCtx, execCtx := testDriverContexts(task)
   388  	defer execCtx.AllocDir.Destroy()
   389  	d := NewExecDriver(driverCtx)
   390  
   391  	handle, err := d.Start(execCtx, task)
   392  	if err == nil {
   393  		handle.Kill()
   394  		t.Fatalf("Should've failed")
   395  	}
   396  	msg := "user alice"
   397  	if !strings.Contains(err.Error(), msg) {
   398  		t.Fatalf("Expecting '%v' in '%v'", msg, err)
   399  	}
   400  }