github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/client/driver/executor/executor_test.go (about)

     1  package executor
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"syscall"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/hashicorp/nomad/client/allocdir"
    13  	"github.com/hashicorp/nomad/client/driver/env"
    14  	cstructs "github.com/hashicorp/nomad/client/structs"
    15  	"github.com/hashicorp/nomad/helper/testlog"
    16  	"github.com/hashicorp/nomad/nomad/mock"
    17  	tu "github.com/hashicorp/nomad/testutil"
    18  	"github.com/mitchellh/go-ps"
    19  )
    20  
    21  // testExecutorContext returns an ExecutorContext and AllocDir.
    22  //
    23  // The caller is responsible for calling AllocDir.Destroy() to cleanup.
    24  func testExecutorContext(t *testing.T) (*ExecutorContext, *allocdir.AllocDir) {
    25  	alloc := mock.Alloc()
    26  	task := alloc.Job.TaskGroups[0].Tasks[0]
    27  	taskEnv := env.NewBuilder(mock.Node(), alloc, task, "global").Build()
    28  
    29  	allocDir := allocdir.NewAllocDir(testlog.Logger(t), filepath.Join(os.TempDir(), alloc.ID))
    30  	if err := allocDir.Build(); err != nil {
    31  		t.Fatalf("AllocDir.Build() failed: %v", err)
    32  	}
    33  	if err := allocDir.NewTaskDir(task.Name).Build(false, nil, cstructs.FSIsolationNone); err != nil {
    34  		allocDir.Destroy()
    35  		t.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
    36  	}
    37  	td := allocDir.TaskDirs[task.Name]
    38  	ctx := &ExecutorContext{
    39  		TaskEnv: taskEnv,
    40  		Task:    task,
    41  		TaskDir: td.Dir,
    42  		LogDir:  td.LogDir,
    43  	}
    44  	return ctx, allocDir
    45  }
    46  
    47  func TestExecutor_Start_Invalid(t *testing.T) {
    48  	t.Parallel()
    49  	invalid := "/bin/foobar"
    50  	execCmd := ExecCommand{Cmd: invalid, Args: []string{"1"}}
    51  	ctx, allocDir := testExecutorContext(t)
    52  	defer allocDir.Destroy()
    53  	executor := NewExecutor(testlog.Logger(t))
    54  
    55  	if err := executor.SetContext(ctx); err != nil {
    56  		t.Fatalf("Unexpected error")
    57  	}
    58  
    59  	if _, err := executor.LaunchCmd(&execCmd); err == nil {
    60  		t.Fatalf("Expected error")
    61  	}
    62  }
    63  
    64  func TestExecutor_Start_Wait_Failure_Code(t *testing.T) {
    65  	t.Parallel()
    66  	execCmd := ExecCommand{Cmd: "/bin/date", Args: []string{"fail"}}
    67  	ctx, allocDir := testExecutorContext(t)
    68  	defer allocDir.Destroy()
    69  	executor := NewExecutor(testlog.Logger(t))
    70  
    71  	if err := executor.SetContext(ctx); err != nil {
    72  		t.Fatalf("Unexpected error")
    73  	}
    74  
    75  	ps, err := executor.LaunchCmd(&execCmd)
    76  	if err != nil {
    77  		t.Fatalf("Unexpected error")
    78  	}
    79  
    80  	if ps.Pid == 0 {
    81  		t.Fatalf("expected process to start and have non zero pid")
    82  	}
    83  	ps, _ = executor.Wait()
    84  	if ps.ExitCode < 1 {
    85  		t.Fatalf("expected exit code to be non zero, actual: %v", ps.ExitCode)
    86  	}
    87  	if err := executor.Exit(); err != nil {
    88  		t.Fatalf("error: %v", err)
    89  	}
    90  }
    91  
    92  func TestExecutor_Start_Wait(t *testing.T) {
    93  	t.Parallel()
    94  	execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}}
    95  	ctx, allocDir := testExecutorContext(t)
    96  	defer allocDir.Destroy()
    97  	executor := NewExecutor(testlog.Logger(t))
    98  
    99  	if err := executor.SetContext(ctx); err != nil {
   100  		t.Fatalf("Unexpected error")
   101  	}
   102  
   103  	ps, err := executor.LaunchCmd(&execCmd)
   104  	if err != nil {
   105  		t.Fatalf("error in launching command: %v", err)
   106  	}
   107  	if ps.Pid == 0 {
   108  		t.Fatalf("expected process to start and have non zero pid")
   109  	}
   110  	ps, err = executor.Wait()
   111  	if err != nil {
   112  		t.Fatalf("error in waiting for command: %v", err)
   113  	}
   114  	if err := executor.Exit(); err != nil {
   115  		t.Fatalf("error: %v", err)
   116  	}
   117  
   118  	expected := "hello world"
   119  	file := filepath.Join(ctx.LogDir, "web.stdout.0")
   120  	output, err := ioutil.ReadFile(file)
   121  	if err != nil {
   122  		t.Fatalf("Couldn't read file %v", file)
   123  	}
   124  
   125  	act := strings.TrimSpace(string(output))
   126  	if act != expected {
   127  		t.Fatalf("Command output incorrectly: want %v; got %v", expected, act)
   128  	}
   129  }
   130  
   131  func TestExecutor_WaitExitSignal(t *testing.T) {
   132  	t.Parallel()
   133  	execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10000"}}
   134  	ctx, allocDir := testExecutorContext(t)
   135  	defer allocDir.Destroy()
   136  	executor := NewExecutor(testlog.Logger(t))
   137  
   138  	if err := executor.SetContext(ctx); err != nil {
   139  		t.Fatalf("Unexpected error")
   140  	}
   141  
   142  	ps, err := executor.LaunchCmd(&execCmd)
   143  	if err != nil {
   144  		t.Fatalf("err: %v", err)
   145  	}
   146  
   147  	go func() {
   148  		time.Sleep(2 * time.Second)
   149  		ru, err := executor.Stats()
   150  		if err != nil {
   151  			t.Fatalf("err: %v", err)
   152  		}
   153  		if len(ru.Pids) == 0 {
   154  			t.Fatalf("expected pids")
   155  		}
   156  		proc, err := os.FindProcess(ps.Pid)
   157  		if err != nil {
   158  			t.Fatalf("err: %v", err)
   159  		}
   160  		if err := proc.Signal(syscall.SIGKILL); err != nil {
   161  			t.Fatalf("err: %v", err)
   162  		}
   163  	}()
   164  
   165  	ps, err = executor.Wait()
   166  	if err != nil {
   167  		t.Fatalf("err: %v", err)
   168  	}
   169  	if ps.Signal != int(syscall.SIGKILL) {
   170  		t.Fatalf("expected signal: %v, actual: %v", int(syscall.SIGKILL), ps.Signal)
   171  	}
   172  }
   173  
   174  func TestExecutor_Start_Kill(t *testing.T) {
   175  	t.Parallel()
   176  	execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10 && hello world"}}
   177  	ctx, allocDir := testExecutorContext(t)
   178  	defer allocDir.Destroy()
   179  	executor := NewExecutor(testlog.Logger(t))
   180  
   181  	if err := executor.SetContext(ctx); err != nil {
   182  		t.Fatalf("Unexpected error")
   183  	}
   184  
   185  	ps, err := executor.LaunchCmd(&execCmd)
   186  	if err != nil {
   187  		t.Fatalf("error in launching command: %v", err)
   188  	}
   189  	if ps.Pid == 0 {
   190  		t.Fatalf("expected process to start and have non zero pid")
   191  	}
   192  	ps, err = executor.Wait()
   193  	if err != nil {
   194  		t.Fatalf("error in waiting for command: %v", err)
   195  	}
   196  	if err := executor.Exit(); err != nil {
   197  		t.Fatalf("error: %v", err)
   198  	}
   199  
   200  	file := filepath.Join(ctx.LogDir, "web.stdout.0")
   201  	time.Sleep(time.Duration(tu.TestMultiplier()*2) * time.Second)
   202  
   203  	output, err := ioutil.ReadFile(file)
   204  	if err != nil {
   205  		t.Fatalf("Couldn't read file %v", file)
   206  	}
   207  
   208  	expected := ""
   209  	act := strings.TrimSpace(string(output))
   210  	if act != expected {
   211  		t.Fatalf("Command output incorrectly: want %v; got %v", expected, act)
   212  	}
   213  }
   214  
   215  func TestExecutor_MakeExecutable(t *testing.T) {
   216  	t.Parallel()
   217  	// Create a temp file
   218  	f, err := ioutil.TempFile("", "")
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	defer f.Close()
   223  	defer os.Remove(f.Name())
   224  
   225  	// Set its permissions to be non-executable
   226  	f.Chmod(os.FileMode(0610))
   227  
   228  	// Make a fake executor
   229  	executor := NewExecutor(testlog.Logger(t))
   230  
   231  	err = executor.(*UniversalExecutor).makeExecutable(f.Name())
   232  	if err != nil {
   233  		t.Fatalf("makeExecutable() failed: %v", err)
   234  	}
   235  
   236  	// Check the permissions
   237  	stat, err := f.Stat()
   238  	if err != nil {
   239  		t.Fatalf("Stat() failed: %v", err)
   240  	}
   241  
   242  	act := stat.Mode().Perm()
   243  	exp := os.FileMode(0755)
   244  	if act != exp {
   245  		t.Fatalf("expected permissions %v; got %v", exp, act)
   246  	}
   247  }
   248  
   249  func TestScanPids(t *testing.T) {
   250  	t.Parallel()
   251  	p1 := NewFakeProcess(2, 5)
   252  	p2 := NewFakeProcess(10, 2)
   253  	p3 := NewFakeProcess(15, 6)
   254  	p4 := NewFakeProcess(3, 10)
   255  	p5 := NewFakeProcess(20, 18)
   256  
   257  	// Make a fake executor
   258  	executor := NewExecutor(testlog.Logger(t)).(*UniversalExecutor)
   259  
   260  	nomadPids, err := executor.scanPids(5, []ps.Process{p1, p2, p3, p4, p5})
   261  	if err != nil {
   262  		t.Fatalf("error: %v", err)
   263  	}
   264  	if len(nomadPids) != 4 {
   265  		t.Fatalf("expected: 4, actual: %v", len(nomadPids))
   266  	}
   267  }
   268  
   269  type FakeProcess struct {
   270  	pid  int
   271  	ppid int
   272  }
   273  
   274  func (f FakeProcess) Pid() int {
   275  	return f.pid
   276  }
   277  
   278  func (f FakeProcess) PPid() int {
   279  	return f.ppid
   280  }
   281  
   282  func (f FakeProcess) Executable() string {
   283  	return "fake"
   284  }
   285  
   286  func NewFakeProcess(pid int, ppid int) ps.Process {
   287  	return FakeProcess{pid: pid, ppid: ppid}
   288  }