github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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/hashicorp/nomad/client/allocdir"
    15  	"github.com/hashicorp/nomad/client/driver/env"
    16  	"github.com/hashicorp/nomad/client/testutil"
    17  	"github.com/hashicorp/nomad/nomad/mock"
    18  	"github.com/hashicorp/nomad/nomad/structs"
    19  	tu "github.com/hashicorp/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 mockAllocDir(t *testing.T) (*structs.Task, *allocdir.AllocDir) {
    37  	alloc := mock.Alloc()
    38  	task := alloc.Job.TaskGroups[0].Tasks[0]
    39  
    40  	allocDir := allocdir.NewAllocDir(filepath.Join(os.TempDir(), alloc.ID))
    41  	if err := allocDir.Build([]*structs.Task{task}); err != nil {
    42  		log.Panicf("allocDir.Build() failed: %v", err)
    43  	}
    44  
    45  	return task, allocDir
    46  }
    47  
    48  func testExecutorContext(t *testing.T) *ExecutorContext {
    49  	taskEnv := env.NewTaskEnvironment(mock.Node())
    50  	task, allocDir := mockAllocDir(t)
    51  	ctx := &ExecutorContext{
    52  		TaskEnv:  taskEnv,
    53  		Task:     task,
    54  		AllocDir: allocDir,
    55  	}
    56  	return ctx
    57  }
    58  
    59  func TestExecutor_Start_Invalid(t *testing.T) {
    60  	invalid := "/bin/foobar"
    61  	execCmd := ExecCommand{Cmd: invalid, Args: []string{"1"}}
    62  	ctx := testExecutorContext(t)
    63  	defer ctx.AllocDir.Destroy()
    64  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
    65  
    66  	if err := executor.SetContext(ctx); err != nil {
    67  		t.Fatalf("Unexpected error")
    68  	}
    69  
    70  	if _, err := executor.LaunchCmd(&execCmd); err == nil {
    71  		t.Fatalf("Expected error")
    72  	}
    73  }
    74  
    75  func TestExecutor_Start_Wait_Failure_Code(t *testing.T) {
    76  	execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"fail"}}
    77  	ctx := testExecutorContext(t)
    78  	defer ctx.AllocDir.Destroy()
    79  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
    80  
    81  	if err := executor.SetContext(ctx); err != nil {
    82  		t.Fatalf("Unexpected error")
    83  	}
    84  
    85  	ps, err := executor.LaunchCmd(&execCmd)
    86  	if err != nil {
    87  		t.Fatalf("Unexpected error")
    88  	}
    89  
    90  	if ps.Pid == 0 {
    91  		t.Fatalf("expected process to start and have non zero pid")
    92  	}
    93  	ps, _ = executor.Wait()
    94  	if ps.ExitCode < 1 {
    95  		t.Fatalf("expected exit code to be non zero, actual: %v", ps.ExitCode)
    96  	}
    97  	if err := executor.Exit(); err != nil {
    98  		t.Fatalf("error: %v", err)
    99  	}
   100  }
   101  
   102  func TestExecutor_Start_Wait(t *testing.T) {
   103  	execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}}
   104  	ctx := testExecutorContext(t)
   105  	defer ctx.AllocDir.Destroy()
   106  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
   107  
   108  	if err := executor.SetContext(ctx); err != nil {
   109  		t.Fatalf("Unexpected error")
   110  	}
   111  
   112  	ps, err := executor.LaunchCmd(&execCmd)
   113  	if err != nil {
   114  		t.Fatalf("error in launching command: %v", err)
   115  	}
   116  	if ps.Pid == 0 {
   117  		t.Fatalf("expected process to start and have non zero pid")
   118  	}
   119  	ps, err = executor.Wait()
   120  	if err != nil {
   121  		t.Fatalf("error in waiting for command: %v", err)
   122  	}
   123  	if err := executor.Exit(); err != nil {
   124  		t.Fatalf("error: %v", err)
   125  	}
   126  
   127  	expected := "hello world"
   128  	file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0")
   129  	output, err := ioutil.ReadFile(file)
   130  	if err != nil {
   131  		t.Fatalf("Couldn't read file %v", file)
   132  	}
   133  
   134  	act := strings.TrimSpace(string(output))
   135  	if act != expected {
   136  		t.Fatalf("Command output incorrectly: want %v; got %v", expected, act)
   137  	}
   138  }
   139  
   140  func TestExecutor_WaitExitSignal(t *testing.T) {
   141  	execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10000"}}
   142  	ctx := testExecutorContext(t)
   143  	defer ctx.AllocDir.Destroy()
   144  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
   145  
   146  	if err := executor.SetContext(ctx); err != nil {
   147  		t.Fatalf("Unexpected error")
   148  	}
   149  
   150  	ps, err := executor.LaunchCmd(&execCmd)
   151  	if err != nil {
   152  		t.Fatalf("err: %v", err)
   153  	}
   154  
   155  	go func() {
   156  		time.Sleep(3 * time.Second)
   157  		ru, err := executor.Stats()
   158  		if err != nil {
   159  			t.Fatalf("err: %v", err)
   160  		}
   161  		if len(ru.Pids) != 2 {
   162  			t.Fatalf("expected number of pids: 2, actual: %v", len(ru.Pids))
   163  		}
   164  		proc, err := os.FindProcess(ps.Pid)
   165  		if err != nil {
   166  			t.Fatalf("err: %v", err)
   167  		}
   168  		if err := proc.Signal(syscall.SIGKILL); err != nil {
   169  			t.Fatalf("err: %v", err)
   170  		}
   171  	}()
   172  
   173  	ps, err = executor.Wait()
   174  	if err != nil {
   175  		t.Fatalf("err: %v", err)
   176  	}
   177  	if ps.Signal != int(syscall.SIGKILL) {
   178  		t.Fatalf("expected signal: %v, actual: %v", int(syscall.SIGKILL), ps.Signal)
   179  	}
   180  }
   181  
   182  func TestExecutor_ClientCleanup(t *testing.T) {
   183  	testutil.ExecCompatible(t)
   184  
   185  	execCmd := ExecCommand{Cmd: "/bin/bash", Args: []string{"-c", "/usr/bin/yes"}}
   186  	ctx := testExecutorContext(t)
   187  	ctx.Task.LogConfig.MaxFiles = 1
   188  	ctx.Task.LogConfig.MaxFileSizeMB = 300
   189  	defer ctx.AllocDir.Destroy()
   190  
   191  	execCmd.FSIsolation = true
   192  	execCmd.ResourceLimits = true
   193  	execCmd.User = "nobody"
   194  
   195  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
   196  
   197  	if err := executor.SetContext(ctx); err != nil {
   198  		t.Fatalf("Unexpected error")
   199  	}
   200  
   201  	ps, err := executor.LaunchCmd(&execCmd)
   202  	if err != nil {
   203  		t.Fatalf("error in launching command: %v", err)
   204  	}
   205  	if ps.Pid == 0 {
   206  		t.Fatalf("expected process to start and have non zero pid")
   207  	}
   208  	time.Sleep(200 * time.Millisecond)
   209  	if err := executor.Exit(); err != nil {
   210  		t.Fatalf("err: %v", err)
   211  	}
   212  
   213  	file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0")
   214  	finfo, err := os.Stat(file)
   215  	if err != nil {
   216  		t.Fatalf("error stating stdout file: %v", err)
   217  	}
   218  	time.Sleep(1 * time.Second)
   219  	finfo1, err := os.Stat(file)
   220  	if err != nil {
   221  		t.Fatalf("error stating stdout file: %v", err)
   222  	}
   223  	if finfo.Size() != finfo1.Size() {
   224  		t.Fatalf("Expected size: %v, actual: %v", finfo.Size(), finfo1.Size())
   225  	}
   226  }
   227  
   228  func TestExecutor_Start_Kill(t *testing.T) {
   229  	execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10 && hello world"}}
   230  	ctx := testExecutorContext(t)
   231  	defer ctx.AllocDir.Destroy()
   232  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
   233  
   234  	if err := executor.SetContext(ctx); err != nil {
   235  		t.Fatalf("Unexpected error")
   236  	}
   237  
   238  	ps, err := executor.LaunchCmd(&execCmd)
   239  	if err != nil {
   240  		t.Fatalf("error in launching command: %v", err)
   241  	}
   242  	if ps.Pid == 0 {
   243  		t.Fatalf("expected process to start and have non zero pid")
   244  	}
   245  	ps, err = executor.Wait()
   246  	if err != nil {
   247  		t.Fatalf("error in waiting for command: %v", err)
   248  	}
   249  	if err := executor.Exit(); err != nil {
   250  		t.Fatalf("error: %v", err)
   251  	}
   252  
   253  	file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0")
   254  	time.Sleep(time.Duration(tu.TestMultiplier()*2) * time.Second)
   255  
   256  	output, err := ioutil.ReadFile(file)
   257  	if err != nil {
   258  		t.Fatalf("Couldn't read file %v", file)
   259  	}
   260  
   261  	expected := ""
   262  	act := strings.TrimSpace(string(output))
   263  	if act != expected {
   264  		t.Fatalf("Command output incorrectly: want %v; got %v", expected, act)
   265  	}
   266  }
   267  
   268  func TestExecutor_MakeExecutable(t *testing.T) {
   269  	// Create a temp file
   270  	f, err := ioutil.TempFile("", "")
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  	defer f.Close()
   275  	defer os.Remove(f.Name())
   276  
   277  	// Set its permissions to be non-executable
   278  	f.Chmod(os.FileMode(0610))
   279  
   280  	// Make a fake exececutor
   281  	ctx := testExecutorContext(t)
   282  	defer ctx.AllocDir.Destroy()
   283  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
   284  
   285  	err = executor.(*UniversalExecutor).makeExecutable(f.Name())
   286  	if err != nil {
   287  		t.Fatalf("makeExecutable() failed: %v", err)
   288  	}
   289  
   290  	// Check the permissions
   291  	stat, err := f.Stat()
   292  	if err != nil {
   293  		t.Fatalf("Stat() failed: %v", err)
   294  	}
   295  
   296  	act := stat.Mode().Perm()
   297  	exp := os.FileMode(0755)
   298  	if act != exp {
   299  		t.Fatalf("expected permissions %v; got %v", err)
   300  	}
   301  }
   302  
   303  func TestExecutorInterpolateServices(t *testing.T) {
   304  	task := mock.Job().TaskGroups[0].Tasks[0]
   305  	// Make a fake exececutor
   306  	ctx := testExecutorContext(t)
   307  	defer ctx.AllocDir.Destroy()
   308  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
   309  
   310  	executor.(*UniversalExecutor).ctx = ctx
   311  	executor.(*UniversalExecutor).interpolateServices(task)
   312  	expectedTags := []string{"pci:true", "datacenter:dc1"}
   313  	if !reflect.DeepEqual(task.Services[0].Tags, expectedTags) {
   314  		t.Fatalf("expected: %v, actual: %v", expectedTags, task.Services[0].Tags)
   315  	}
   316  
   317  	expectedCheckCmd := "/usr/local/check-table-mysql"
   318  	expectedCheckArgs := []string{"5.6"}
   319  	if !reflect.DeepEqual(task.Services[0].Checks[0].Command, expectedCheckCmd) {
   320  		t.Fatalf("expected: %v, actual: %v", expectedCheckCmd, task.Services[0].Checks[0].Command)
   321  	}
   322  
   323  	if !reflect.DeepEqual(task.Services[0].Checks[0].Args, expectedCheckArgs) {
   324  		t.Fatalf("expected: %v, actual: %v", expectedCheckArgs, task.Services[0].Checks[0].Args)
   325  	}
   326  }
   327  
   328  func TestScanPids(t *testing.T) {
   329  	p1 := NewFakeProcess(2, 5)
   330  	p2 := NewFakeProcess(10, 2)
   331  	p3 := NewFakeProcess(15, 6)
   332  	p4 := NewFakeProcess(3, 10)
   333  	p5 := NewFakeProcess(20, 18)
   334  
   335  	// Make a fake exececutor
   336  	ctx := testExecutorContext(t)
   337  	defer ctx.AllocDir.Destroy()
   338  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)).(*UniversalExecutor)
   339  
   340  	nomadPids, err := executor.scanPids(5, []ps.Process{p1, p2, p3, p4, p5})
   341  	if err != nil {
   342  		t.Fatalf("error: %v", err)
   343  	}
   344  	if len(nomadPids) != 4 {
   345  		t.Fatalf("expected: 4, actual: %v", len(nomadPids))
   346  	}
   347  }
   348  
   349  type FakeProcess struct {
   350  	pid  int
   351  	ppid int
   352  }
   353  
   354  func (f FakeProcess) Pid() int {
   355  	return f.pid
   356  }
   357  
   358  func (f FakeProcess) PPid() int {
   359  	return f.ppid
   360  }
   361  
   362  func (f FakeProcess) Executable() string {
   363  	return "fake"
   364  }
   365  
   366  func NewFakeProcess(pid int, ppid int) ps.Process {
   367  	return FakeProcess{pid: pid, ppid: ppid}
   368  }