github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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/hashicorp/nomad/client/allocdir" 15 "github.com/hashicorp/nomad/client/driver/env" 16 "github.com/hashicorp/nomad/client/testutil" 17 "github.com/hashicorp/nomad/nomad/mock" 18 "github.com/hashicorp/nomad/nomad/structs" 19 tu "github.com/hashicorp/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 mockAllocDir(t *testing.T) (*structs.Task, *allocdir.AllocDir) { 37 alloc := mock.Alloc() 38 task := alloc.Job.TaskGroups[0].Tasks[0] 39 40 allocDir := allocdir.NewAllocDir(filepath.Join(os.TempDir(), alloc.ID)) 41 if err := allocDir.Build([]*structs.Task{task}); err != nil { 42 log.Panicf("allocDir.Build() failed: %v", err) 43 } 44 45 return task, allocDir 46 } 47 48 func testExecutorContext(t *testing.T) *ExecutorContext { 49 taskEnv := env.NewTaskEnvironment(mock.Node()) 50 task, allocDir := mockAllocDir(t) 51 ctx := &ExecutorContext{ 52 TaskEnv: taskEnv, 53 Task: task, 54 AllocDir: allocDir, 55 } 56 return ctx 57 } 58 59 func TestExecutor_Start_Invalid(t *testing.T) { 60 invalid := "/bin/foobar" 61 execCmd := ExecCommand{Cmd: invalid, Args: []string{"1"}} 62 ctx := testExecutorContext(t) 63 defer ctx.AllocDir.Destroy() 64 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 65 66 if err := executor.SetContext(ctx); err != nil { 67 t.Fatalf("Unexpected error") 68 } 69 70 if _, err := executor.LaunchCmd(&execCmd); err == nil { 71 t.Fatalf("Expected error") 72 } 73 } 74 75 func TestExecutor_Start_Wait_Failure_Code(t *testing.T) { 76 execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"fail"}} 77 ctx := testExecutorContext(t) 78 defer ctx.AllocDir.Destroy() 79 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 80 81 if err := executor.SetContext(ctx); err != nil { 82 t.Fatalf("Unexpected error") 83 } 84 85 ps, err := executor.LaunchCmd(&execCmd) 86 if err != nil { 87 t.Fatalf("Unexpected error") 88 } 89 90 if ps.Pid == 0 { 91 t.Fatalf("expected process to start and have non zero pid") 92 } 93 ps, _ = executor.Wait() 94 if ps.ExitCode < 1 { 95 t.Fatalf("expected exit code to be non zero, actual: %v", ps.ExitCode) 96 } 97 if err := executor.Exit(); err != nil { 98 t.Fatalf("error: %v", err) 99 } 100 } 101 102 func TestExecutor_Start_Wait(t *testing.T) { 103 execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}} 104 ctx := testExecutorContext(t) 105 defer ctx.AllocDir.Destroy() 106 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 107 108 if err := executor.SetContext(ctx); err != nil { 109 t.Fatalf("Unexpected error") 110 } 111 112 ps, err := executor.LaunchCmd(&execCmd) 113 if err != nil { 114 t.Fatalf("error in launching command: %v", err) 115 } 116 if ps.Pid == 0 { 117 t.Fatalf("expected process to start and have non zero pid") 118 } 119 ps, err = executor.Wait() 120 if err != nil { 121 t.Fatalf("error in waiting for command: %v", err) 122 } 123 if err := executor.Exit(); err != nil { 124 t.Fatalf("error: %v", err) 125 } 126 127 expected := "hello world" 128 file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0") 129 output, err := ioutil.ReadFile(file) 130 if err != nil { 131 t.Fatalf("Couldn't read file %v", file) 132 } 133 134 act := strings.TrimSpace(string(output)) 135 if act != expected { 136 t.Fatalf("Command output incorrectly: want %v; got %v", expected, act) 137 } 138 } 139 140 func TestExecutor_WaitExitSignal(t *testing.T) { 141 execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10000"}} 142 ctx := testExecutorContext(t) 143 defer ctx.AllocDir.Destroy() 144 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 145 146 if err := executor.SetContext(ctx); err != nil { 147 t.Fatalf("Unexpected error") 148 } 149 150 ps, err := executor.LaunchCmd(&execCmd) 151 if err != nil { 152 t.Fatalf("err: %v", err) 153 } 154 155 go func() { 156 time.Sleep(3 * time.Second) 157 ru, err := executor.Stats() 158 if err != nil { 159 t.Fatalf("err: %v", err) 160 } 161 if len(ru.Pids) != 2 { 162 t.Fatalf("expected number of pids: 2, actual: %v", len(ru.Pids)) 163 } 164 proc, err := os.FindProcess(ps.Pid) 165 if err != nil { 166 t.Fatalf("err: %v", err) 167 } 168 if err := proc.Signal(syscall.SIGKILL); err != nil { 169 t.Fatalf("err: %v", err) 170 } 171 }() 172 173 ps, err = executor.Wait() 174 if err != nil { 175 t.Fatalf("err: %v", err) 176 } 177 if ps.Signal != int(syscall.SIGKILL) { 178 t.Fatalf("expected signal: %v, actual: %v", int(syscall.SIGKILL), ps.Signal) 179 } 180 } 181 182 func TestExecutor_ClientCleanup(t *testing.T) { 183 testutil.ExecCompatible(t) 184 185 execCmd := ExecCommand{Cmd: "/bin/bash", Args: []string{"-c", "/usr/bin/yes"}} 186 ctx := testExecutorContext(t) 187 ctx.Task.LogConfig.MaxFiles = 1 188 ctx.Task.LogConfig.MaxFileSizeMB = 300 189 defer ctx.AllocDir.Destroy() 190 191 execCmd.FSIsolation = true 192 execCmd.ResourceLimits = true 193 execCmd.User = "nobody" 194 195 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 196 197 if err := executor.SetContext(ctx); err != nil { 198 t.Fatalf("Unexpected error") 199 } 200 201 ps, err := executor.LaunchCmd(&execCmd) 202 if err != nil { 203 t.Fatalf("error in launching command: %v", err) 204 } 205 if ps.Pid == 0 { 206 t.Fatalf("expected process to start and have non zero pid") 207 } 208 time.Sleep(200 * time.Millisecond) 209 if err := executor.Exit(); err != nil { 210 t.Fatalf("err: %v", err) 211 } 212 213 file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0") 214 finfo, err := os.Stat(file) 215 if err != nil { 216 t.Fatalf("error stating stdout file: %v", err) 217 } 218 time.Sleep(1 * time.Second) 219 finfo1, err := os.Stat(file) 220 if err != nil { 221 t.Fatalf("error stating stdout file: %v", err) 222 } 223 if finfo.Size() != finfo1.Size() { 224 t.Fatalf("Expected size: %v, actual: %v", finfo.Size(), finfo1.Size()) 225 } 226 } 227 228 func TestExecutor_Start_Kill(t *testing.T) { 229 execCmd := ExecCommand{Cmd: "/bin/sleep", Args: []string{"10 && hello world"}} 230 ctx := testExecutorContext(t) 231 defer ctx.AllocDir.Destroy() 232 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 233 234 if err := executor.SetContext(ctx); err != nil { 235 t.Fatalf("Unexpected error") 236 } 237 238 ps, err := executor.LaunchCmd(&execCmd) 239 if err != nil { 240 t.Fatalf("error in launching command: %v", err) 241 } 242 if ps.Pid == 0 { 243 t.Fatalf("expected process to start and have non zero pid") 244 } 245 ps, err = executor.Wait() 246 if err != nil { 247 t.Fatalf("error in waiting for command: %v", err) 248 } 249 if err := executor.Exit(); err != nil { 250 t.Fatalf("error: %v", err) 251 } 252 253 file := filepath.Join(ctx.AllocDir.LogDir(), "web.stdout.0") 254 time.Sleep(time.Duration(tu.TestMultiplier()*2) * time.Second) 255 256 output, err := ioutil.ReadFile(file) 257 if err != nil { 258 t.Fatalf("Couldn't read file %v", file) 259 } 260 261 expected := "" 262 act := strings.TrimSpace(string(output)) 263 if act != expected { 264 t.Fatalf("Command output incorrectly: want %v; got %v", expected, act) 265 } 266 } 267 268 func TestExecutor_MakeExecutable(t *testing.T) { 269 // Create a temp file 270 f, err := ioutil.TempFile("", "") 271 if err != nil { 272 t.Fatal(err) 273 } 274 defer f.Close() 275 defer os.Remove(f.Name()) 276 277 // Set its permissions to be non-executable 278 f.Chmod(os.FileMode(0610)) 279 280 // Make a fake exececutor 281 ctx := testExecutorContext(t) 282 defer ctx.AllocDir.Destroy() 283 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 284 285 err = executor.(*UniversalExecutor).makeExecutable(f.Name()) 286 if err != nil { 287 t.Fatalf("makeExecutable() failed: %v", err) 288 } 289 290 // Check the permissions 291 stat, err := f.Stat() 292 if err != nil { 293 t.Fatalf("Stat() failed: %v", err) 294 } 295 296 act := stat.Mode().Perm() 297 exp := os.FileMode(0755) 298 if act != exp { 299 t.Fatalf("expected permissions %v; got %v", err) 300 } 301 } 302 303 func TestExecutorInterpolateServices(t *testing.T) { 304 task := mock.Job().TaskGroups[0].Tasks[0] 305 // Make a fake exececutor 306 ctx := testExecutorContext(t) 307 defer ctx.AllocDir.Destroy() 308 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) 309 310 executor.(*UniversalExecutor).ctx = ctx 311 executor.(*UniversalExecutor).interpolateServices(task) 312 expectedTags := []string{"pci:true", "datacenter:dc1"} 313 if !reflect.DeepEqual(task.Services[0].Tags, expectedTags) { 314 t.Fatalf("expected: %v, actual: %v", expectedTags, task.Services[0].Tags) 315 } 316 317 expectedCheckCmd := "/usr/local/check-table-mysql" 318 expectedCheckArgs := []string{"5.6"} 319 if !reflect.DeepEqual(task.Services[0].Checks[0].Command, expectedCheckCmd) { 320 t.Fatalf("expected: %v, actual: %v", expectedCheckCmd, task.Services[0].Checks[0].Command) 321 } 322 323 if !reflect.DeepEqual(task.Services[0].Checks[0].Args, expectedCheckArgs) { 324 t.Fatalf("expected: %v, actual: %v", expectedCheckArgs, task.Services[0].Checks[0].Args) 325 } 326 } 327 328 func TestScanPids(t *testing.T) { 329 p1 := NewFakeProcess(2, 5) 330 p2 := NewFakeProcess(10, 2) 331 p3 := NewFakeProcess(15, 6) 332 p4 := NewFakeProcess(3, 10) 333 p5 := NewFakeProcess(20, 18) 334 335 // Make a fake exececutor 336 ctx := testExecutorContext(t) 337 defer ctx.AllocDir.Destroy() 338 executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)).(*UniversalExecutor) 339 340 nomadPids, err := executor.scanPids(5, []ps.Process{p1, p2, p3, p4, p5}) 341 if err != nil { 342 t.Fatalf("error: %v", err) 343 } 344 if len(nomadPids) != 4 { 345 t.Fatalf("expected: 4, actual: %v", len(nomadPids)) 346 } 347 } 348 349 type FakeProcess struct { 350 pid int 351 ppid int 352 } 353 354 func (f FakeProcess) Pid() int { 355 return f.pid 356 } 357 358 func (f FakeProcess) PPid() int { 359 return f.ppid 360 } 361 362 func (f FakeProcess) Executable() string { 363 return "fake" 364 } 365 366 func NewFakeProcess(pid int, ppid int) ps.Process { 367 return FakeProcess{pid: pid, ppid: ppid} 368 }