github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/executor/executor_test.go (about)

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