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