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