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