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