github.com/anuvu/nomad@v0.8.7-atom1/client/driver/executor/executor_test.go (about)

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