github.com/quite/nomad@v0.8.6/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.DefaultUnprivilegedUser
    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  	state, err := executor.Wait()
    88  	if err != nil {
    89  		t.Fatalf("error in waiting for command: %v", err)
    90  	}
    91  	if state.ExitCode != 0 {
    92  		t.Errorf("exited with non-zero code: %v", state.ExitCode)
    93  	}
    94  
    95  	// Check if the resource constraints were applied
    96  	memLimits := filepath.Join(ps.IsolationConfig.CgroupPaths["memory"], "memory.limit_in_bytes")
    97  	data, err := ioutil.ReadFile(memLimits)
    98  	if err != nil {
    99  		t.Fatalf("err: %v", err)
   100  	}
   101  	expectedMemLim := strconv.Itoa(ctx.Task.Resources.MemoryMB * 1024 * 1024)
   102  	actualMemLim := strings.TrimSpace(string(data))
   103  	if actualMemLim != expectedMemLim {
   104  		t.Fatalf("actual mem limit: %v, expected: %v", string(data), expectedMemLim)
   105  	}
   106  
   107  	if err := executor.Exit(); err != nil {
   108  		t.Fatalf("error: %v", err)
   109  	}
   110  
   111  	// Check if Nomad has actually removed the cgroups
   112  	if _, err := os.Stat(memLimits); err == nil {
   113  		t.Fatalf("file %v hasn't been removed", memLimits)
   114  	}
   115  
   116  	expected := `/:
   117  alloc/
   118  bin/
   119  dev/
   120  etc/
   121  lib/
   122  lib64/
   123  local/
   124  proc/
   125  secrets/
   126  tmp/
   127  usr/
   128  
   129  /etc/:
   130  ld.so.cache
   131  ld.so.conf
   132  ld.so.conf.d/`
   133  	file := filepath.Join(ctx.LogDir, "web.stdout.0")
   134  	output, err := ioutil.ReadFile(file)
   135  	if err != nil {
   136  		t.Fatalf("Couldn't read file %v", file)
   137  	}
   138  
   139  	act := strings.TrimSpace(string(output))
   140  	if act != expected {
   141  		t.Errorf("Command output incorrectly: want %v; got %v", expected, act)
   142  	}
   143  }
   144  
   145  func TestExecutor_ClientCleanup(t *testing.T) {
   146  	t.Parallel()
   147  	testutil.ExecCompatible(t)
   148  
   149  	ctx, allocDir := testExecutorContextWithChroot(t)
   150  	ctx.Task.LogConfig.MaxFiles = 1
   151  	ctx.Task.LogConfig.MaxFileSizeMB = 300
   152  	defer allocDir.Destroy()
   153  
   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  	// Need to run a command which will produce continuous output but not
   161  	// too quickly to ensure executor.Exit() stops the process.
   162  	execCmd := ExecCommand{Cmd: "/bin/bash", Args: []string{"-c", "while true; do /bin/echo X; /bin/sleep 1; done"}}
   163  	execCmd.FSIsolation = true
   164  	execCmd.ResourceLimits = true
   165  	execCmd.User = "nobody"
   166  
   167  	ps, err := executor.LaunchCmd(&execCmd)
   168  	if err != nil {
   169  		t.Fatalf("error in launching command: %v", err)
   170  	}
   171  	if ps.Pid == 0 {
   172  		t.Fatalf("expected process to start and have non zero pid")
   173  	}
   174  	time.Sleep(500 * time.Millisecond)
   175  	if err := executor.Exit(); err != nil {
   176  		t.Fatalf("err: %v", err)
   177  	}
   178  
   179  	file := filepath.Join(ctx.LogDir, "web.stdout.0")
   180  	finfo, err := os.Stat(file)
   181  	if err != nil {
   182  		t.Fatalf("error stating stdout file: %v", err)
   183  	}
   184  	if finfo.Size() == 0 {
   185  		t.Fatal("Nothing in stdout; expected at least one byte.")
   186  	}
   187  	time.Sleep(2 * time.Second)
   188  	finfo1, err := os.Stat(file)
   189  	if err != nil {
   190  		t.Fatalf("error stating stdout file: %v", err)
   191  	}
   192  	if finfo.Size() != finfo1.Size() {
   193  		t.Fatalf("Expected size: %v, actual: %v", finfo.Size(), finfo1.Size())
   194  	}
   195  }