github.com/anuvu/nomad@v0.8.7-atom1/client/driver/exec_test.go (about) 1 package driver 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "path/filepath" 9 "reflect" 10 "runtime" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/hashicorp/nomad/client/config" 16 "github.com/hashicorp/nomad/client/driver/env" 17 cstructs "github.com/hashicorp/nomad/client/structs" 18 "github.com/hashicorp/nomad/nomad/structs" 19 "github.com/hashicorp/nomad/testutil" 20 21 ctestutils "github.com/hashicorp/nomad/client/testutil" 22 ) 23 24 // Test that we do not enable exec on non-linux machines 25 func TestExecDriver_Fingerprint_NonLinux(t *testing.T) { 26 if !testutil.IsTravis() { 27 t.Parallel() 28 } 29 if runtime.GOOS == "linux" { 30 t.Skip("Test only available not on Linux") 31 } 32 33 d := NewExecDriver(&DriverContext{}) 34 node := &structs.Node{} 35 36 request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node} 37 var response cstructs.FingerprintResponse 38 err := d.Fingerprint(request, &response) 39 if err != nil { 40 t.Fatalf("err: %v", err) 41 } 42 43 if response.Detected { 44 t.Fatalf("Should not be detected on non-linux platforms") 45 } 46 } 47 48 func TestExecDriver_Fingerprint(t *testing.T) { 49 if !testutil.IsTravis() { 50 t.Parallel() 51 } 52 ctestutils.ExecCompatible(t) 53 task := &structs.Task{ 54 Name: "foo", 55 Driver: "exec", 56 Resources: structs.DefaultResources(), 57 } 58 ctx := testDriverContexts(t, task) 59 defer ctx.AllocDir.Destroy() 60 d := NewExecDriver(ctx.DriverCtx) 61 node := &structs.Node{ 62 Attributes: map[string]string{ 63 "unique.cgroup.mountpoint": "/sys/fs/cgroup", 64 }, 65 } 66 67 request := &cstructs.FingerprintRequest{Config: &config.Config{}, Node: node} 68 var response cstructs.FingerprintResponse 69 err := d.Fingerprint(request, &response) 70 if err != nil { 71 t.Fatalf("err: %v", err) 72 } 73 74 if !response.Detected { 75 t.Fatalf("expected response to be applicable") 76 } 77 78 if response.Attributes == nil || response.Attributes["driver.exec"] == "" { 79 t.Fatalf("missing driver") 80 } 81 } 82 83 func TestExecDriver_StartOpen_Wait(t *testing.T) { 84 if !testutil.IsTravis() { 85 t.Parallel() 86 } 87 ctestutils.ExecCompatible(t) 88 task := &structs.Task{ 89 Name: "sleep", 90 Driver: "exec", 91 Config: map[string]interface{}{ 92 "command": "/bin/sleep", 93 "args": []string{"5"}, 94 }, 95 LogConfig: &structs.LogConfig{ 96 MaxFiles: 10, 97 MaxFileSizeMB: 10, 98 }, 99 Resources: basicResources, 100 } 101 102 ctx := testDriverContexts(t, task) 103 defer ctx.AllocDir.Destroy() 104 d := NewExecDriver(ctx.DriverCtx) 105 106 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 107 t.Fatalf("prestart err: %v", err) 108 } 109 resp, err := d.Start(ctx.ExecCtx, task) 110 if err != nil { 111 t.Fatalf("err: %v", err) 112 } 113 114 // Attempt to open 115 handle2, err := d.Open(ctx.ExecCtx, resp.Handle.ID()) 116 if err != nil { 117 t.Fatalf("err: %v", err) 118 } 119 if handle2 == nil { 120 t.Fatalf("missing handle") 121 } 122 123 resp.Handle.Kill() 124 handle2.Kill() 125 } 126 127 func TestExecDriver_Start_Wait(t *testing.T) { 128 if !testutil.IsTravis() { 129 t.Parallel() 130 } 131 ctestutils.ExecCompatible(t) 132 task := &structs.Task{ 133 Name: "sleep", 134 Driver: "exec", 135 Config: map[string]interface{}{ 136 "command": "/bin/sleep", 137 "args": []string{"2"}, 138 }, 139 LogConfig: &structs.LogConfig{ 140 MaxFiles: 10, 141 MaxFileSizeMB: 10, 142 }, 143 Resources: basicResources, 144 } 145 146 ctx := testDriverContexts(t, task) 147 defer ctx.AllocDir.Destroy() 148 d := NewExecDriver(ctx.DriverCtx) 149 150 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 151 t.Fatalf("prestart err: %v", err) 152 } 153 resp, err := d.Start(ctx.ExecCtx, task) 154 if err != nil { 155 t.Fatalf("err: %v", err) 156 } 157 158 // Update should be a no-op 159 err = resp.Handle.Update(task) 160 if err != nil { 161 t.Fatalf("err: %v", err) 162 } 163 164 // Task should terminate quickly 165 select { 166 case res := <-resp.Handle.WaitCh(): 167 if !res.Successful() { 168 t.Fatalf("err: %v", res) 169 } 170 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 171 t.Fatalf("timeout") 172 } 173 } 174 175 func TestExecDriver_Start_Wait_AllocDir(t *testing.T) { 176 if !testutil.IsTravis() { 177 t.Parallel() 178 } 179 ctestutils.ExecCompatible(t) 180 181 exp := []byte{'w', 'i', 'n'} 182 file := "output.txt" 183 task := &structs.Task{ 184 Name: "sleep", 185 Driver: "exec", 186 Config: map[string]interface{}{ 187 "command": "/bin/bash", 188 "args": []string{ 189 "-c", 190 fmt.Sprintf(`sleep 1; echo -n %s > ${%s}/%s`, string(exp), env.AllocDir, file), 191 }, 192 }, 193 LogConfig: &structs.LogConfig{ 194 MaxFiles: 10, 195 MaxFileSizeMB: 10, 196 }, 197 Resources: basicResources, 198 } 199 200 ctx := testDriverContexts(t, task) 201 defer ctx.AllocDir.Destroy() 202 d := NewExecDriver(ctx.DriverCtx) 203 204 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 205 t.Fatalf("prestart err: %v", err) 206 } 207 resp, err := d.Start(ctx.ExecCtx, task) 208 if err != nil { 209 t.Fatalf("err: %v", err) 210 } 211 212 // Task should terminate quickly 213 select { 214 case res := <-resp.Handle.WaitCh(): 215 if !res.Successful() { 216 t.Fatalf("err: %v", res) 217 } 218 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 219 t.Fatalf("timeout") 220 } 221 222 // Check that data was written to the shared alloc directory. 223 outputFile := filepath.Join(ctx.AllocDir.SharedDir, file) 224 act, err := ioutil.ReadFile(outputFile) 225 if err != nil { 226 t.Fatalf("Couldn't read expected output: %v", err) 227 } 228 229 if !reflect.DeepEqual(act, exp) { 230 t.Fatalf("Command outputted %v; want %v", act, exp) 231 } 232 } 233 234 func TestExecDriver_Start_Kill_Wait(t *testing.T) { 235 if !testutil.IsTravis() { 236 t.Parallel() 237 } 238 ctestutils.ExecCompatible(t) 239 task := &structs.Task{ 240 Name: "sleep", 241 Driver: "exec", 242 Config: map[string]interface{}{ 243 "command": "/bin/sleep", 244 "args": []string{"100"}, 245 }, 246 LogConfig: &structs.LogConfig{ 247 MaxFiles: 10, 248 MaxFileSizeMB: 10, 249 }, 250 Resources: basicResources, 251 KillTimeout: 10 * time.Second, 252 } 253 254 ctx := testDriverContexts(t, task) 255 defer ctx.AllocDir.Destroy() 256 d := NewExecDriver(ctx.DriverCtx) 257 258 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 259 t.Fatalf("prestart err: %v", err) 260 } 261 resp, err := d.Start(ctx.ExecCtx, task) 262 if err != nil { 263 t.Fatalf("err: %v", err) 264 } 265 266 go func() { 267 time.Sleep(100 * time.Millisecond) 268 err := resp.Handle.Kill() 269 if err != nil { 270 t.Fatalf("err: %v", err) 271 } 272 }() 273 274 // Task should terminate quickly 275 select { 276 case res := <-resp.Handle.WaitCh(): 277 if res.Successful() { 278 t.Fatal("should err") 279 } 280 case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second): 281 t.Fatalf("timeout") 282 } 283 } 284 285 func TestExecDriverUser(t *testing.T) { 286 if !testutil.IsTravis() { 287 t.Parallel() 288 } 289 ctestutils.ExecCompatible(t) 290 task := &structs.Task{ 291 Name: "sleep", 292 Driver: "exec", 293 User: "alice", 294 Config: map[string]interface{}{ 295 "command": "/bin/sleep", 296 "args": []string{"100"}, 297 }, 298 LogConfig: &structs.LogConfig{ 299 MaxFiles: 10, 300 MaxFileSizeMB: 10, 301 }, 302 Resources: basicResources, 303 KillTimeout: 10 * time.Second, 304 } 305 306 ctx := testDriverContexts(t, task) 307 defer ctx.AllocDir.Destroy() 308 d := NewExecDriver(ctx.DriverCtx) 309 310 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 311 t.Fatalf("prestart err: %v", err) 312 } 313 resp, err := d.Start(ctx.ExecCtx, task) 314 if err == nil { 315 resp.Handle.Kill() 316 t.Fatalf("Should've failed") 317 } 318 msg := "user alice" 319 if !strings.Contains(err.Error(), msg) { 320 t.Fatalf("Expecting '%v' in '%v'", msg, err) 321 } 322 } 323 324 // TestExecDriver_HandlerExec ensures the exec driver's handle properly 325 // executes commands inside the container. 326 func TestExecDriver_HandlerExec(t *testing.T) { 327 if !testutil.IsTravis() { 328 t.Parallel() 329 } 330 ctestutils.ExecCompatible(t) 331 task := &structs.Task{ 332 Name: "sleep", 333 Driver: "exec", 334 Config: map[string]interface{}{ 335 "command": "/bin/sleep", 336 "args": []string{"9000"}, 337 }, 338 LogConfig: &structs.LogConfig{ 339 MaxFiles: 10, 340 MaxFileSizeMB: 10, 341 }, 342 Resources: basicResources, 343 } 344 345 ctx := testDriverContexts(t, task) 346 defer ctx.AllocDir.Destroy() 347 d := NewExecDriver(ctx.DriverCtx) 348 349 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 350 t.Fatalf("prestart err: %v", err) 351 } 352 resp, err := d.Start(ctx.ExecCtx, task) 353 if err != nil { 354 t.Fatalf("err: %v", err) 355 } 356 handle := resp.Handle 357 358 // Exec a command that should work and dump the environment 359 out, code, err := handle.Exec(context.Background(), "/bin/sh", []string{"-c", "env | grep ^NOMAD"}) 360 if err != nil { 361 t.Fatalf("error exec'ing stat: %v", err) 362 } 363 if code != 0 { 364 t.Fatalf("expected `stat /alloc` to succeed but exit code was: %d", code) 365 } 366 367 // Assert exec'd commands are run in a task-like environment 368 scriptEnv := make(map[string]string) 369 for _, line := range strings.Split(string(out), "\n") { 370 if line == "" { 371 continue 372 } 373 parts := strings.SplitN(string(line), "=", 2) 374 if len(parts) != 2 { 375 t.Fatalf("Invalid env var: %q", line) 376 } 377 scriptEnv[parts[0]] = parts[1] 378 } 379 if v, ok := scriptEnv["NOMAD_SECRETS_DIR"]; !ok || v != "/secrets" { 380 t.Errorf("Expected NOMAD_SECRETS_DIR=/secrets but found=%t value=%q", ok, v) 381 } 382 if v, ok := scriptEnv["NOMAD_ALLOC_ID"]; !ok || v != ctx.DriverCtx.allocID { 383 t.Errorf("Expected NOMAD_SECRETS_DIR=%q but found=%t value=%q", ctx.DriverCtx.allocID, ok, v) 384 } 385 386 // Assert cgroup membership 387 out, code, err = handle.Exec(context.Background(), "/bin/cat", []string{"/proc/self/cgroup"}) 388 if err != nil { 389 t.Fatalf("error exec'ing cat /proc/self/cgroup: %v", err) 390 } 391 if code != 0 { 392 t.Fatalf("expected `cat /proc/self/cgroup` to succeed but exit code was: %d", code) 393 } 394 found := false 395 for _, line := range strings.Split(string(out), "\n") { 396 // Every cgroup entry should be /nomad/$ALLOC_ID 397 if line == "" { 398 continue 399 } 400 if !strings.Contains(line, ":/nomad/") { 401 t.Errorf("Not a member of the alloc's cgroup: expected=...:/nomad/... -- found=%q", line) 402 continue 403 } 404 found = true 405 } 406 if !found { 407 t.Errorf("exec'd command isn't in the task's cgroup") 408 } 409 410 // Exec a command that should fail 411 out, code, err = handle.Exec(context.Background(), "/usr/bin/stat", []string{"lkjhdsaflkjshowaisxmcvnlia"}) 412 if err != nil { 413 t.Fatalf("error exec'ing stat: %v", err) 414 } 415 if code == 0 { 416 t.Fatalf("expected `stat` to fail but exit code was: %d", code) 417 } 418 if expected := "No such file or directory"; !bytes.Contains(out, []byte(expected)) { 419 t.Fatalf("expected output to contain %q but found: %q", expected, out) 420 } 421 422 if err := handle.Kill(); err != nil { 423 t.Fatalf("error killing exec handle: %v", err) 424 } 425 }