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