github.com/anuvu/nomad@v0.8.7-atom1/client/driver/raw_exec_test.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "reflect" 11 "strconv" 12 "syscall" 13 "testing" 14 "time" 15 16 "github.com/hashicorp/nomad/client/config" 17 "github.com/hashicorp/nomad/client/driver/env" 18 cstructs "github.com/hashicorp/nomad/client/structs" 19 tu "github.com/hashicorp/nomad/client/testutil" 20 "github.com/hashicorp/nomad/helper/testtask" 21 "github.com/hashicorp/nomad/nomad/structs" 22 "github.com/hashicorp/nomad/testutil" 23 ) 24 25 func TestRawExecDriver_Fingerprint(t *testing.T) { 26 t.Parallel() 27 task := &structs.Task{ 28 Name: "foo", 29 Driver: "raw_exec", 30 Resources: structs.DefaultResources(), 31 } 32 ctx := testDriverContexts(t, task) 33 defer ctx.AllocDir.Destroy() 34 d := NewRawExecDriver(ctx.DriverCtx) 35 node := &structs.Node{ 36 Attributes: make(map[string]string), 37 } 38 39 // Disable raw exec. 40 cfg := &config.Config{Options: map[string]string{rawExecEnableOption: "false"}} 41 42 request := &cstructs.FingerprintRequest{Config: cfg, Node: node} 43 var response cstructs.FingerprintResponse 44 err := d.Fingerprint(request, &response) 45 if err != nil { 46 t.Fatalf("err: %v", err) 47 } 48 49 if response.Attributes["driver.raw_exec"] != "" { 50 t.Fatalf("driver incorrectly enabled") 51 } 52 53 // Enable raw exec. 54 request.Config.Options[rawExecEnableOption] = "true" 55 err = d.Fingerprint(request, &response) 56 if err != nil { 57 t.Fatalf("err: %v", err) 58 } 59 60 if !response.Detected { 61 t.Fatalf("expected response to be applicable") 62 } 63 64 if response.Attributes["driver.raw_exec"] != "1" { 65 t.Fatalf("driver not enabled") 66 } 67 } 68 69 func TestRawExecDriver_StartOpen_Wait(t *testing.T) { 70 t.Parallel() 71 task := &structs.Task{ 72 Name: "sleep", 73 Driver: "raw_exec", 74 Config: map[string]interface{}{ 75 "command": testtask.Path(), 76 "args": []string{"sleep", "1s"}, 77 }, 78 LogConfig: &structs.LogConfig{ 79 MaxFiles: 10, 80 MaxFileSizeMB: 10, 81 }, 82 Resources: basicResources, 83 } 84 testtask.SetTaskEnv(task) 85 ctx := testDriverContexts(t, task) 86 defer ctx.AllocDir.Destroy() 87 d := NewRawExecDriver(ctx.DriverCtx) 88 89 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 90 t.Fatalf("prestart err: %v", err) 91 } 92 resp, err := d.Start(ctx.ExecCtx, task) 93 if err != nil { 94 t.Fatalf("err: %v", err) 95 } 96 97 // Attempt to open 98 handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID()) 99 if err != nil { 100 t.Fatalf("err: %v", err) 101 } 102 if handle2 == nil { 103 t.Fatalf("missing handle") 104 } 105 106 // Task should terminate quickly 107 select { 108 case <-handle2.WaitCh(): 109 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 110 t.Fatalf("timeout") 111 } 112 resp.Handle.Kill() 113 handle2.Kill() 114 } 115 116 func TestRawExecDriver_Start_Wait(t *testing.T) { 117 t.Parallel() 118 task := &structs.Task{ 119 Name: "sleep", 120 Driver: "raw_exec", 121 Config: map[string]interface{}{ 122 "command": testtask.Path(), 123 "args": []string{"sleep", "1s"}, 124 }, 125 LogConfig: &structs.LogConfig{ 126 MaxFiles: 10, 127 MaxFileSizeMB: 10, 128 }, 129 Resources: basicResources, 130 } 131 testtask.SetTaskEnv(task) 132 ctx := testDriverContexts(t, task) 133 defer ctx.AllocDir.Destroy() 134 d := NewRawExecDriver(ctx.DriverCtx) 135 136 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 137 t.Fatalf("prestart err: %v", err) 138 } 139 resp, err := d.Start(ctx.ExecCtx, task) 140 if err != nil { 141 t.Fatalf("err: %v", err) 142 } 143 144 // Update should be a no-op 145 err = resp.Handle.Update(task) 146 if err != nil { 147 t.Fatalf("err: %v", err) 148 } 149 150 // Task should terminate quickly 151 select { 152 case res := <-resp.Handle.WaitCh(): 153 if !res.Successful() { 154 t.Fatalf("err: %v", res) 155 } 156 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 157 t.Fatalf("timeout") 158 } 159 } 160 161 func TestRawExecDriver_Start_Wait_AllocDir(t *testing.T) { 162 t.Parallel() 163 exp := []byte("win") 164 file := "output.txt" 165 outPath := fmt.Sprintf(`${%s}/%s`, env.AllocDir, file) 166 task := &structs.Task{ 167 Name: "sleep", 168 Driver: "raw_exec", 169 Config: map[string]interface{}{ 170 "command": testtask.Path(), 171 "args": []string{ 172 "sleep", "1s", 173 "write", string(exp), outPath, 174 }, 175 }, 176 LogConfig: &structs.LogConfig{ 177 MaxFiles: 10, 178 MaxFileSizeMB: 10, 179 }, 180 Resources: basicResources, 181 } 182 testtask.SetTaskEnv(task) 183 184 ctx := testDriverContexts(t, task) 185 defer ctx.AllocDir.Destroy() 186 d := NewRawExecDriver(ctx.DriverCtx) 187 188 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 189 t.Fatalf("prestart err: %v", err) 190 } 191 resp, err := d.Start(ctx.ExecCtx, task) 192 if err != nil { 193 t.Fatalf("err: %v", err) 194 } 195 196 // Task should terminate quickly 197 select { 198 case res := <-resp.Handle.WaitCh(): 199 if !res.Successful() { 200 t.Fatalf("err: %v", res) 201 } 202 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 203 t.Fatalf("timeout") 204 } 205 206 // Check that data was written to the shared alloc directory. 207 outputFile := filepath.Join(ctx.AllocDir.SharedDir, file) 208 act, err := ioutil.ReadFile(outputFile) 209 if err != nil { 210 t.Fatalf("Couldn't read expected output: %v", err) 211 } 212 213 if !reflect.DeepEqual(act, exp) { 214 t.Fatalf("Command outputted %v; want %v", act, exp) 215 } 216 } 217 218 func TestRawExecDriver_Start_Kill_Wait(t *testing.T) { 219 t.Parallel() 220 task := &structs.Task{ 221 Name: "sleep", 222 Driver: "raw_exec", 223 Config: map[string]interface{}{ 224 "command": testtask.Path(), 225 "args": []string{"sleep", "45s"}, 226 }, 227 LogConfig: &structs.LogConfig{ 228 MaxFiles: 10, 229 MaxFileSizeMB: 10, 230 }, 231 Resources: basicResources, 232 } 233 testtask.SetTaskEnv(task) 234 235 ctx := testDriverContexts(t, task) 236 defer ctx.AllocDir.Destroy() 237 d := NewRawExecDriver(ctx.DriverCtx) 238 239 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 240 t.Fatalf("prestart err: %v", err) 241 } 242 resp, err := d.Start(ctx.ExecCtx, task) 243 if err != nil { 244 t.Fatalf("err: %v", err) 245 } 246 247 go func() { 248 time.Sleep(1 * time.Second) 249 err := resp.Handle.Kill() 250 251 // Can't rely on the ordering between wait and kill on travis... 252 if !testutil.IsTravis() && err != nil { 253 t.Fatalf("err: %v", err) 254 } 255 }() 256 257 // Task should terminate quickly 258 select { 259 case res := <-resp.Handle.WaitCh(): 260 if res.Successful() { 261 t.Fatal("should err") 262 } 263 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 264 t.Fatalf("timeout") 265 } 266 } 267 268 // This test creates a process tree such that without cgroups tracking the 269 // processes cleanup of the children would not be possible. Thus the test 270 // asserts that the processes get killed properly when using cgroups. 271 func TestRawExecDriver_Start_Kill_Wait_Cgroup(t *testing.T) { 272 tu.ExecCompatible(t) 273 t.Parallel() 274 pidFile := "pid" 275 task := &structs.Task{ 276 Name: "sleep", 277 Driver: "raw_exec", 278 Config: map[string]interface{}{ 279 "command": testtask.Path(), 280 "args": []string{"fork/exec", pidFile, "pgrp", "0", "sleep", "20s"}, 281 }, 282 LogConfig: &structs.LogConfig{ 283 MaxFiles: 10, 284 MaxFileSizeMB: 10, 285 }, 286 Resources: basicResources, 287 User: "root", 288 } 289 testtask.SetTaskEnv(task) 290 291 ctx := testDriverContexts(t, task) 292 ctx.DriverCtx.node.Attributes["unique.cgroup.mountpoint"] = "foo" // Enable cgroups 293 defer ctx.AllocDir.Destroy() 294 d := NewRawExecDriver(ctx.DriverCtx) 295 296 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 297 t.Fatalf("prestart err: %v", err) 298 } 299 resp, err := d.Start(ctx.ExecCtx, task) 300 if err != nil { 301 t.Fatalf("err: %v", err) 302 } 303 304 // Find the process 305 var pidData []byte 306 testutil.WaitForResult(func() (bool, error) { 307 var err error 308 pidData, err = ioutil.ReadFile(filepath.Join(ctx.AllocDir.AllocDir, "sleep", pidFile)) 309 if err != nil { 310 return false, err 311 } 312 313 return true, nil 314 }, func(err error) { 315 t.Fatalf("err: %v", err) 316 }) 317 318 pid, err := strconv.Atoi(string(pidData)) 319 if err != nil { 320 t.Fatalf("failed to convert pid: %v", err) 321 } 322 323 // Check the pid is up 324 process, err := os.FindProcess(pid) 325 if err != nil { 326 t.Fatalf("failed to find process") 327 } 328 if err := process.Signal(syscall.Signal(0)); err != nil { 329 t.Fatalf("process doesn't exist: %v", err) 330 } 331 332 go func() { 333 time.Sleep(1 * time.Second) 334 err := resp.Handle.Kill() 335 336 // Can't rely on the ordering between wait and kill on travis... 337 if !testutil.IsTravis() && err != nil { 338 t.Fatalf("err: %v", err) 339 } 340 }() 341 342 // Task should terminate quickly 343 select { 344 case res := <-resp.Handle.WaitCh(): 345 if res.Successful() { 346 t.Fatal("should err") 347 } 348 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 349 t.Fatalf("timeout") 350 } 351 352 testutil.WaitForResult(func() (bool, error) { 353 if err := process.Signal(syscall.Signal(0)); err == nil { 354 return false, fmt.Errorf("process should not exist: %v", pid) 355 } 356 357 return true, nil 358 }, func(err error) { 359 t.Fatalf("err: %v", err) 360 }) 361 } 362 363 func TestRawExecDriver_HandlerExec(t *testing.T) { 364 t.Parallel() 365 task := &structs.Task{ 366 Name: "sleep", 367 Driver: "raw_exec", 368 Config: map[string]interface{}{ 369 "command": testtask.Path(), 370 "args": []string{"sleep", "9000s"}, 371 }, 372 LogConfig: &structs.LogConfig{ 373 MaxFiles: 10, 374 MaxFileSizeMB: 10, 375 }, 376 Resources: basicResources, 377 } 378 testtask.SetTaskEnv(task) 379 ctx := testDriverContexts(t, task) 380 defer ctx.AllocDir.Destroy() 381 d := NewRawExecDriver(ctx.DriverCtx) 382 383 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 384 t.Fatalf("prestart err: %v", err) 385 } 386 resp, err := d.Start(ctx.ExecCtx, task) 387 if err != nil { 388 t.Fatalf("err: %v", err) 389 } 390 391 // Exec a command that should work 392 out, code, err := resp.Handle.Exec(context.TODO(), "/usr/bin/stat", []string{"/tmp"}) 393 if err != nil { 394 t.Fatalf("error exec'ing stat: %v", err) 395 } 396 if code != 0 { 397 t.Fatalf("expected `stat /alloc` to succeed but exit code was: %d", code) 398 } 399 if expected := 100; len(out) < expected { 400 t.Fatalf("expected at least %d bytes of output but found %d:\n%s", expected, len(out), out) 401 } 402 403 // Exec a command that should fail 404 out, code, err = resp.Handle.Exec(context.TODO(), "/usr/bin/stat", []string{"lkjhdsaflkjshowaisxmcvnlia"}) 405 if err != nil { 406 t.Fatalf("error exec'ing stat: %v", err) 407 } 408 if code == 0 { 409 t.Fatalf("expected `stat` to fail but exit code was: %d", code) 410 } 411 if expected := "No such file or directory"; !bytes.Contains(out, []byte(expected)) { 412 t.Fatalf("expected output to contain %q but found: %q", expected, out) 413 } 414 415 select { 416 case res := <-resp.Handle.WaitCh(): 417 t.Fatalf("Shouldn't be exited: %v", res.String()) 418 default: 419 } 420 421 if err := resp.Handle.Kill(); err != nil { 422 t.Fatalf("error killing exec handle: %v", err) 423 } 424 }