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