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