github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/driver/executor/executor_test.go (about) 1 package executor 2 3 import ( 4 "io/ioutil" 5 "log" 6 "os" 7 "path/filepath" 8 "strings" 9 "syscall" 10 "testing" 11 "time" 12 13 "github.com/hashicorp/nomad/client/allocdir" 14 "github.com/hashicorp/nomad/client/driver/env" 15 cstructs "github.com/hashicorp/nomad/client/structs" 16 "github.com/hashicorp/nomad/nomad/mock" 17 "github.com/hashicorp/nomad/nomad/structs" 18 tu "github.com/hashicorp/nomad/testutil" 19 "github.com/mitchellh/go-ps" 20 ) 21 22 var ( 23 constraint = &structs.Resources{ 24 CPU: 250, 25 MemoryMB: 256, 26 Networks: []*structs.NetworkResource{ 27 &structs.NetworkResource{ 28 MBits: 50, 29 DynamicPorts: []structs.Port{{Label: "http"}}, 30 }, 31 }, 32 } 33 ) 34 35 func testLogger() *log.Logger { 36 return log.New(os.Stderr, "", log.LstdFlags) 37 } 38 39 // testExecutorContext returns an ExecutorContext and AllocDir. 40 // 41 // The caller is responsible for calling AllocDir.Destroy() to cleanup. 42 func testExecutorContext(t *testing.T) (*ExecutorContext, *allocdir.AllocDir) { 43 alloc := mock.Alloc() 44 task := alloc.Job.TaskGroups[0].Tasks[0] 45 taskEnv := env.NewBuilder(mock.Node(), alloc, task, "global").Build() 46 47 allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(os.TempDir(), alloc.ID)) 48 if err := allocDir.Build(); err != nil { 49 log.Fatalf("AllocDir.Build() failed: %v", err) 50 } 51 if err := allocDir.NewTaskDir(task.Name).Build(false, nil, cstructs.FSIsolationNone); err != nil { 52 allocDir.Destroy() 53 log.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err) 54 } 55 td := allocDir.TaskDirs[task.Name] 56 ctx := &ExecutorContext{ 57 TaskEnv: taskEnv, 58 Task: task, 59 TaskDir: td.Dir, 60 LogDir: td.LogDir, 61 } 62 return ctx, allocDir 63 } 64 65 func TestExecutor_Start_Invalid(t *testing.T) { 66 t.Parallel() 67 invalid := "/bin/foobar" 68 execCmd := ExecCommand{Cmd: invalid, Args: []string{"1"}} 69 ctx, allocDir := testExecutorContext(t) 70 defer allocDir.Destroy() 71 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 72 73 if err := executor.SetContext(ctx); err != nil { 74 t.Fatalf("Unexpected error") 75 } 76 77 if _, err := executor.LaunchCmd(&execCmd); err == nil { 78 t.Fatalf("Expected error") 79 } 80 } 81 82 func TestExecutor_Start_Wait_Failure_Code(t *testing.T) { 83 t.Parallel() 84 execCmd := ExecCommand{Cmd: "/bin/date", Args: []string{"fail"}} 85 ctx, allocDir := testExecutorContext(t) 86 defer allocDir.Destroy() 87 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 88 89 if err := executor.SetContext(ctx); err != nil { 90 t.Fatalf("Unexpected error") 91 } 92 93 ps, err := executor.LaunchCmd(&execCmd) 94 if err != nil { 95 t.Fatalf("Unexpected error") 96 } 97 98 if ps.Pid == 0 { 99 t.Fatalf("expected process to start and have non zero pid") 100 } 101 ps, _ = executor.Wait() 102 if ps.ExitCode < 1 { 103 t.Fatalf("expected exit code to be non zero, actual: %v", ps.ExitCode) 104 } 105 if err := executor.Exit(); err != nil { 106 t.Fatalf("error: %v", err) 107 } 108 } 109 110 func TestExecutor_Start_Wait(t *testing.T) { 111 t.Parallel() 112 execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}} 113 ctx, allocDir := testExecutorContext(t) 114 defer allocDir.Destroy() 115 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 116 117 if err := executor.SetContext(ctx); err != nil { 118 t.Fatalf("Unexpected error") 119 } 120 121 ps, err := executor.LaunchCmd(&execCmd) 122 if err != nil { 123 t.Fatalf("error in launching command: %v", err) 124 } 125 if ps.Pid == 0 { 126 t.Fatalf("expected process to start and have non zero pid") 127 } 128 ps, err = executor.Wait() 129 if err != nil { 130 t.Fatalf("error in waiting for command: %v", err) 131 } 132 if err := executor.Exit(); err != nil { 133 t.Fatalf("error: %v", err) 134 } 135 136 expected := "hello world" 137 file := filepath.Join(ctx.LogDir, "web.stdout.0") 138 output, err := ioutil.ReadFile(file) 139 if err != nil { 140 t.Fatalf("Couldn't read file %v", file) 141 } 142 143 act := strings.TrimSpace(string(output)) 144 if act != expected { 145 t.Fatalf("Command output incorrectly: want %v; got %v", expected, act) 146 } 147 } 148 149 func TestExecutor_WaitExitSignal(t *testing.T) { 150 t.Parallel() 151 execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10000"}} 152 ctx, allocDir := testExecutorContext(t) 153 defer allocDir.Destroy() 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 ps, err := executor.LaunchCmd(&execCmd) 161 if err != nil { 162 t.Fatalf("err: %v", err) 163 } 164 165 go func() { 166 time.Sleep(2 * time.Second) 167 ru, err := executor.Stats() 168 if err != nil { 169 t.Fatalf("err: %v", err) 170 } 171 if len(ru.Pids) == 0 { 172 t.Fatalf("expected pids") 173 } 174 proc, err := os.FindProcess(ps.Pid) 175 if err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 if err := proc.Signal(syscall.SIGKILL); err != nil { 179 t.Fatalf("err: %v", err) 180 } 181 }() 182 183 ps, err = executor.Wait() 184 if err != nil { 185 t.Fatalf("err: %v", err) 186 } 187 if ps.Signal != int(syscall.SIGKILL) { 188 t.Fatalf("expected signal: %v, actual: %v", int(syscall.SIGKILL), ps.Signal) 189 } 190 } 191 192 func TestExecutor_Start_Kill(t *testing.T) { 193 t.Parallel() 194 execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10 && hello world"}} 195 ctx, allocDir := testExecutorContext(t) 196 defer allocDir.Destroy() 197 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 198 199 if err := executor.SetContext(ctx); err != nil { 200 t.Fatalf("Unexpected error") 201 } 202 203 ps, err := executor.LaunchCmd(&execCmd) 204 if err != nil { 205 t.Fatalf("error in launching command: %v", err) 206 } 207 if ps.Pid == 0 { 208 t.Fatalf("expected process to start and have non zero pid") 209 } 210 ps, err = executor.Wait() 211 if err != nil { 212 t.Fatalf("error in waiting for command: %v", err) 213 } 214 if err := executor.Exit(); err != nil { 215 t.Fatalf("error: %v", err) 216 } 217 218 file := filepath.Join(ctx.LogDir, "web.stdout.0") 219 time.Sleep(time.Duration(tu.TestMultiplier()*2) * time.Second) 220 221 output, err := ioutil.ReadFile(file) 222 if err != nil { 223 t.Fatalf("Couldn't read file %v", file) 224 } 225 226 expected := "" 227 act := strings.TrimSpace(string(output)) 228 if act != expected { 229 t.Fatalf("Command output incorrectly: want %v; got %v", expected, act) 230 } 231 } 232 233 func TestExecutor_MakeExecutable(t *testing.T) { 234 t.Parallel() 235 // Create a temp file 236 f, err := ioutil.TempFile("", "") 237 if err != nil { 238 t.Fatal(err) 239 } 240 defer f.Close() 241 defer os.Remove(f.Name()) 242 243 // Set its permissions to be non-executable 244 f.Chmod(os.FileMode(0610)) 245 246 // Make a fake exececutor 247 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 248 249 err = executor.(*UniversalExecutor).makeExecutable(f.Name()) 250 if err != nil { 251 t.Fatalf("makeExecutable() failed: %v", err) 252 } 253 254 // Check the permissions 255 stat, err := f.Stat() 256 if err != nil { 257 t.Fatalf("Stat() failed: %v", err) 258 } 259 260 act := stat.Mode().Perm() 261 exp := os.FileMode(0755) 262 if act != exp { 263 t.Fatalf("expected permissions %v; got %v", exp, act) 264 } 265 } 266 267 func TestScanPids(t *testing.T) { 268 t.Parallel() 269 p1 := NewFakeProcess(2, 5) 270 p2 := NewFakeProcess(10, 2) 271 p3 := NewFakeProcess(15, 6) 272 p4 := NewFakeProcess(3, 10) 273 p5 := NewFakeProcess(20, 18) 274 275 // Make a fake exececutor 276 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)).(*UniversalExecutor) 277 278 nomadPids, err := executor.scanPids(5, []ps.Process{p1, p2, p3, p4, p5}) 279 if err != nil { 280 t.Fatalf("error: %v", err) 281 } 282 if len(nomadPids) != 4 { 283 t.Fatalf("expected: 4, actual: %v", len(nomadPids)) 284 } 285 } 286 287 type FakeProcess struct { 288 pid int 289 ppid int 290 } 291 292 func (f FakeProcess) Pid() int { 293 return f.pid 294 } 295 296 func (f FakeProcess) PPid() int { 297 return f.ppid 298 } 299 300 func (f FakeProcess) Executable() string { 301 return "fake" 302 } 303 304 func NewFakeProcess(pid int, ppid int) ps.Process { 305 return FakeProcess{pid: pid, ppid: ppid} 306 }