github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/executor/executor_linux_test.go (about)

     1  package executor
     2  
     3  import (
     4  	"io/ioutil"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/hashicorp/nomad/client/allocdir"
    14  	"github.com/hashicorp/nomad/client/driver/env"
    15  	dstructs "github.com/hashicorp/nomad/client/driver/structs"
    16  	cstructs "github.com/hashicorp/nomad/client/structs"
    17  	"github.com/hashicorp/nomad/client/testutil"
    18  	"github.com/hashicorp/nomad/nomad/mock"
    19  )
    20  
    21  // testExecutorContextWithChroot returns an ExecutorContext and AllocDir with
    22  // chroot. Use testExecutorContext if you don't need a chroot.
    23  //
    24  // The caller is responsible for calling AllocDir.Destroy() to cleanup.
    25  func testExecutorContextWithChroot(t *testing.T) (*ExecutorContext, *allocdir.AllocDir) {
    26  	chrootEnv := map[string]string{
    27  		"/etc/ld.so.cache":  "/etc/ld.so.cache",
    28  		"/etc/ld.so.conf":   "/etc/ld.so.conf",
    29  		"/etc/ld.so.conf.d": "/etc/ld.so.conf.d",
    30  		"/lib":              "/lib",
    31  		"/lib64":            "/lib64",
    32  		"/usr/lib":          "/usr/lib",
    33  		"/bin/ls":           "/bin/ls",
    34  		"/bin/echo":         "/bin/echo",
    35  		"/bin/bash":         "/bin/bash",
    36  		"/bin/sleep":        "/bin/sleep",
    37  		"/foobar":           "/does/not/exist",
    38  	}
    39  
    40  	alloc := mock.Alloc()
    41  	task := alloc.Job.TaskGroups[0].Tasks[0]
    42  	taskEnv := env.NewBuilder(mock.Node(), alloc, task, "global").Build()
    43  
    44  	allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(os.TempDir(), alloc.ID))
    45  	if err := allocDir.Build(); err != nil {
    46  		log.Fatalf("AllocDir.Build() failed: %v", err)
    47  	}
    48  	if err := allocDir.NewTaskDir(task.Name).Build(false, chrootEnv, cstructs.FSIsolationChroot); err != nil {
    49  		allocDir.Destroy()
    50  		log.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
    51  	}
    52  	td := allocDir.TaskDirs[task.Name]
    53  	ctx := &ExecutorContext{
    54  		TaskEnv: taskEnv,
    55  		Task:    task,
    56  		TaskDir: td.Dir,
    57  		LogDir:  td.LogDir,
    58  	}
    59  	return ctx, allocDir
    60  }
    61  
    62  func TestExecutor_IsolationAndConstraints(t *testing.T) {
    63  	t.Parallel()
    64  	testutil.ExecCompatible(t)
    65  
    66  	execCmd := ExecCommand{Cmd: "/bin/ls", Args: []string{"-F", "/", "/etc/"}}
    67  	ctx, allocDir := testExecutorContextWithChroot(t)
    68  	defer allocDir.Destroy()
    69  
    70  	execCmd.FSIsolation = true
    71  	execCmd.ResourceLimits = true
    72  	execCmd.User = dstructs.DefaultUnpriviledgedUser
    73  
    74  	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
    75  
    76  	if err := executor.SetContext(ctx); err != nil {
    77  		t.Fatalf("Unexpected error: %v", err)
    78  	}
    79  
    80  	ps, err := executor.LaunchCmd(&execCmd)
    81  	if err != nil {
    82  		t.Fatalf("error in launching command: %v", err)
    83  	}
    84  	if ps.Pid == 0 {
    85  		t.Fatalf("expected process to start and have non zero pid")
    86  	}
    87  	_, err = executor.Wait()
    88  	if err != nil {
    89  		t.Fatalf("error in waiting for command: %v", err)
    90  	}
    91  
    92  	// Check if the resource contraints were applied
    93  	memLimits := filepath.Join(ps.IsolationConfig.CgroupPaths["memory"], "memory.limit_in_bytes")
    94  	data, err := ioutil.ReadFile(memLimits)
    95  	if err != nil {
    96  		t.Fatalf("err: %v", err)
    97  	}
    98  	expectedMemLim := strconv.Itoa(ctx.Task.Resources.MemoryMB * 1024 * 1024)
    99  	actualMemLim := strings.TrimSpace(string(data))
   100  	if actualMemLim != expectedMemLim {
   101  		t.Fatalf("actual mem limit: %v, expected: %v", string(data), expectedMemLim)
   102  	}
   103  
   104  	if err := executor.Exit(); err != nil {
   105  		t.Fatalf("error: %v", err)
   106  	}
   107  
   108  	// Check if Nomad has actually removed the cgroups
   109  	if _, err := os.Stat(memLimits); err == nil {
   110  		t.Fatalf("file %v hasn't been removed", memLimits)
   111  	}
   112  
   113  	expected := `/:
   114  alloc/
   115  bin/
   116  dev/
   117  etc/
   118  lib/
   119  lib64/
   120  local/
   121  proc/
   122  secrets/
   123  tmp/
   124  usr/
   125  
   126  /etc/:
   127  ld.so.cache
   128  ld.so.conf
   129  ld.so.conf.d/`
   130  	file := filepath.Join(ctx.LogDir, "web.stdout.0")
   131  	output, err := ioutil.ReadFile(file)
   132  	if err != nil {
   133  		t.Fatalf("Couldn't read file %v", file)
   134  	}
   135  
   136  	act := strings.TrimSpace(string(output))
   137  	if act != expected {
   138  		t.Fatalf("Command output incorrectly: want %v; got %v", expected, act)
   139  	}
   140  }
   141  
   142  func TestExecutor_ClientCleanup(t *testing.T) {
   143  	t.Parallel()
   144  	testutil.ExecCompatible(t)
   145  
   146  	ctx, allocDir := testExecutorContextWithChroot(t)
   147  	ctx.Task.LogConfig.MaxFiles = 1
   148  	ctx.Task.LogConfig.MaxFileSizeMB = 300
   149  	defer allocDir.Destroy()
   150  
   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  	// Need to run a command which will produce continuous output but not
   158  	// too quickly to ensure executor.Exit() stops the process.
   159  	execCmd := ExecCommand{Cmd: "/bin/bash", Args: []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}}
   160  	execCmd.FSIsolation = true
   161  	execCmd.ResourceLimits = true
   162  	execCmd.User = "nobody"
   163  
   164  	ps, err := executor.LaunchCmd(&execCmd)
   165  	if err != nil {
   166  		t.Fatalf("error in launching command: %v", err)
   167  	}
   168  	if ps.Pid == 0 {
   169  		t.Fatalf("expected process to start and have non zero pid")
   170  	}
   171  	time.Sleep(500 * time.Millisecond)
   172  	if err := executor.Exit(); err != nil {
   173  		t.Fatalf("err: %v", err)
   174  	}
   175  
   176  	file := filepath.Join(ctx.LogDir, "web.stdout.0")
   177  	finfo, err := os.Stat(file)
   178  	if err != nil {
   179  		t.Fatalf("error stating stdout file: %v", err)
   180  	}
   181  	if finfo.Size() == 0 {
   182  		t.Fatal("Nothing in stdout; expected at least one byte.")
   183  	}
   184  	time.Sleep(2 * time.Second)
   185  	finfo1, err := os.Stat(file)
   186  	if err != nil {
   187  		t.Fatalf("error stating stdout file: %v", err)
   188  	}
   189  	if finfo.Size() != finfo1.Size() {
   190  		t.Fatalf("Expected size: %v, actual: %v", finfo.Size(), finfo1.Size())
   191  	}
   192  }