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 }