github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/exec_test.go (about) 1 package driver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "reflect" 10 "strings" 11 "syscall" 12 "testing" 13 "time" 14 15 "github.com/ncodes/nomad/client/config" 16 "github.com/ncodes/nomad/client/driver/env" 17 "github.com/ncodes/nomad/nomad/structs" 18 "github.com/ncodes/nomad/testutil" 19 20 ctestutils "github.com/ncodes/nomad/client/testutil" 21 ) 22 23 func TestExecDriver_Fingerprint(t *testing.T) { 24 ctestutils.ExecCompatible(t) 25 task := &structs.Task{ 26 Name: "foo", 27 Driver: "exec", 28 Resources: structs.DefaultResources(), 29 } 30 ctx := testDriverContexts(t, task) 31 defer ctx.AllocDir.Destroy() 32 d := NewExecDriver(ctx.DriverCtx) 33 node := &structs.Node{ 34 Attributes: map[string]string{ 35 "unique.cgroup.mountpoint": "/sys/fs/cgroup", 36 }, 37 } 38 apply, err := d.Fingerprint(&config.Config{}, node) 39 if err != nil { 40 t.Fatalf("err: %v", err) 41 } 42 if !apply { 43 t.Fatalf("should apply") 44 } 45 if node.Attributes["driver.exec"] == "" { 46 t.Fatalf("missing driver") 47 } 48 } 49 50 func TestExecDriver_StartOpen_Wait(t *testing.T) { 51 ctestutils.ExecCompatible(t) 52 task := &structs.Task{ 53 Name: "sleep", 54 Driver: "exec", 55 Config: map[string]interface{}{ 56 "command": "/bin/sleep", 57 "args": []string{"5"}, 58 }, 59 LogConfig: &structs.LogConfig{ 60 MaxFiles: 10, 61 MaxFileSizeMB: 10, 62 }, 63 Resources: basicResources, 64 } 65 66 ctx := testDriverContexts(t, task) 67 defer ctx.AllocDir.Destroy() 68 d := NewExecDriver(ctx.DriverCtx) 69 70 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 71 t.Fatalf("prestart err: %v", err) 72 } 73 handle, err := d.Start(ctx.ExecCtx, task) 74 if err != nil { 75 t.Fatalf("err: %v", err) 76 } 77 if handle == nil { 78 t.Fatalf("missing handle") 79 } 80 81 // Attempt to open 82 handle2, err := d.Open(ctx.ExecCtx, handle.ID()) 83 if err != nil { 84 t.Fatalf("err: %v", err) 85 } 86 if handle2 == nil { 87 t.Fatalf("missing handle") 88 } 89 90 handle.Kill() 91 handle2.Kill() 92 } 93 94 func TestExecDriver_KillUserPid_OnPluginReconnectFailure(t *testing.T) { 95 ctestutils.ExecCompatible(t) 96 task := &structs.Task{ 97 Name: "sleep", 98 Driver: "exec", 99 Config: map[string]interface{}{ 100 "command": "/bin/sleep", 101 "args": []string{"1000000"}, 102 }, 103 LogConfig: &structs.LogConfig{ 104 MaxFiles: 10, 105 MaxFileSizeMB: 10, 106 }, 107 Resources: basicResources, 108 } 109 110 ctx := testDriverContexts(t, task) 111 defer ctx.AllocDir.Destroy() 112 d := NewExecDriver(ctx.DriverCtx) 113 114 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 115 t.Fatalf("prestart err: %v", err) 116 } 117 handle, err := d.Start(ctx.ExecCtx, task) 118 if err != nil { 119 t.Fatalf("err: %v", err) 120 } 121 if handle == nil { 122 t.Fatalf("missing handle") 123 } 124 defer handle.Kill() 125 126 id := &execId{} 127 if err := json.Unmarshal([]byte(handle.ID()), id); err != nil { 128 t.Fatalf("Failed to parse handle '%s': %v", handle.ID(), err) 129 } 130 pluginPid := id.PluginConfig.Pid 131 proc, err := os.FindProcess(pluginPid) 132 if err != nil { 133 t.Fatalf("can't find plugin pid: %v", pluginPid) 134 } 135 if err := proc.Kill(); err != nil { 136 t.Fatalf("can't kill plugin pid: %v", err) 137 } 138 139 // Attempt to open 140 handle2, err := d.Open(ctx.ExecCtx, handle.ID()) 141 if err == nil { 142 t.Fatalf("expected error") 143 } 144 if handle2 != nil { 145 handle2.Kill() 146 t.Fatalf("expected handle2 to be nil") 147 } 148 149 // Test if the userpid is still present 150 userProc, _ := os.FindProcess(id.UserPid) 151 152 for retry := 3; retry > 0; retry-- { 153 if err = userProc.Signal(syscall.Signal(0)); err != nil { 154 // Process is gone as expected; exit 155 return 156 } 157 158 // Killing processes is async; wait and check again 159 time.Sleep(time.Second) 160 } 161 if err = userProc.Signal(syscall.Signal(0)); err == nil { 162 t.Fatalf("expected user process to die") 163 } 164 } 165 166 func TestExecDriver_Start_Wait(t *testing.T) { 167 ctestutils.ExecCompatible(t) 168 task := &structs.Task{ 169 Name: "sleep", 170 Driver: "exec", 171 Config: map[string]interface{}{ 172 "command": "/bin/sleep", 173 "args": []string{"2"}, 174 }, 175 LogConfig: &structs.LogConfig{ 176 MaxFiles: 10, 177 MaxFileSizeMB: 10, 178 }, 179 Resources: basicResources, 180 } 181 182 ctx := testDriverContexts(t, task) 183 defer ctx.AllocDir.Destroy() 184 d := NewExecDriver(ctx.DriverCtx) 185 186 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 187 t.Fatalf("prestart err: %v", err) 188 } 189 handle, err := d.Start(ctx.ExecCtx, task) 190 if err != nil { 191 t.Fatalf("err: %v", err) 192 } 193 if handle == nil { 194 t.Fatalf("missing handle") 195 } 196 197 // Update should be a no-op 198 err = handle.Update(task) 199 if err != nil { 200 t.Fatalf("err: %v", err) 201 } 202 203 // Task should terminate quickly 204 select { 205 case res := <-handle.WaitCh(): 206 if !res.Successful() { 207 t.Fatalf("err: %v", res) 208 } 209 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 210 t.Fatalf("timeout") 211 } 212 } 213 214 func TestExecDriver_Start_Wait_AllocDir(t *testing.T) { 215 ctestutils.ExecCompatible(t) 216 217 exp := []byte{'w', 'i', 'n'} 218 file := "output.txt" 219 task := &structs.Task{ 220 Name: "sleep", 221 Driver: "exec", 222 Config: map[string]interface{}{ 223 "command": "/bin/bash", 224 "args": []string{ 225 "-c", 226 fmt.Sprintf(`sleep 1; echo -n %s > ${%s}/%s`, string(exp), env.AllocDir, file), 227 }, 228 }, 229 LogConfig: &structs.LogConfig{ 230 MaxFiles: 10, 231 MaxFileSizeMB: 10, 232 }, 233 Resources: basicResources, 234 } 235 236 ctx := testDriverContexts(t, task) 237 defer ctx.AllocDir.Destroy() 238 d := NewExecDriver(ctx.DriverCtx) 239 240 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 241 t.Fatalf("prestart err: %v", err) 242 } 243 handle, err := d.Start(ctx.ExecCtx, task) 244 if err != nil { 245 t.Fatalf("err: %v", err) 246 } 247 if handle == nil { 248 t.Fatalf("missing handle") 249 } 250 251 // Task should terminate quickly 252 select { 253 case res := <-handle.WaitCh(): 254 if !res.Successful() { 255 t.Fatalf("err: %v", res) 256 } 257 case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): 258 t.Fatalf("timeout") 259 } 260 261 // Check that data was written to the shared alloc directory. 262 outputFile := filepath.Join(ctx.AllocDir.SharedDir, file) 263 act, err := ioutil.ReadFile(outputFile) 264 if err != nil { 265 t.Fatalf("Couldn't read expected output: %v", err) 266 } 267 268 if !reflect.DeepEqual(act, exp) { 269 t.Fatalf("Command outputted %v; want %v", act, exp) 270 } 271 } 272 273 func TestExecDriver_Start_Kill_Wait(t *testing.T) { 274 ctestutils.ExecCompatible(t) 275 task := &structs.Task{ 276 Name: "sleep", 277 Driver: "exec", 278 Config: map[string]interface{}{ 279 "command": "/bin/sleep", 280 "args": []string{"100"}, 281 }, 282 LogConfig: &structs.LogConfig{ 283 MaxFiles: 10, 284 MaxFileSizeMB: 10, 285 }, 286 Resources: basicResources, 287 KillTimeout: 10 * time.Second, 288 } 289 290 ctx := testDriverContexts(t, task) 291 defer ctx.AllocDir.Destroy() 292 d := NewExecDriver(ctx.DriverCtx) 293 294 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 295 t.Fatalf("prestart err: %v", err) 296 } 297 handle, err := d.Start(ctx.ExecCtx, task) 298 if err != nil { 299 t.Fatalf("err: %v", err) 300 } 301 if handle == nil { 302 t.Fatalf("missing handle") 303 } 304 305 go func() { 306 time.Sleep(100 * time.Millisecond) 307 err := handle.Kill() 308 if err != nil { 309 t.Fatalf("err: %v", err) 310 } 311 }() 312 313 // Task should terminate quickly 314 select { 315 case res := <-handle.WaitCh(): 316 if res.Successful() { 317 t.Fatal("should err") 318 } 319 case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second): 320 t.Fatalf("timeout") 321 } 322 } 323 324 func TestExecDriver_Signal(t *testing.T) { 325 ctestutils.ExecCompatible(t) 326 task := &structs.Task{ 327 Name: "signal", 328 Driver: "exec", 329 Config: map[string]interface{}{ 330 "command": "/bin/bash", 331 "args": []string{"test.sh"}, 332 }, 333 LogConfig: &structs.LogConfig{ 334 MaxFiles: 10, 335 MaxFileSizeMB: 10, 336 }, 337 Resources: basicResources, 338 KillTimeout: 10 * time.Second, 339 } 340 341 ctx := testDriverContexts(t, task) 342 defer ctx.AllocDir.Destroy() 343 d := NewExecDriver(ctx.DriverCtx) 344 345 testFile := filepath.Join(ctx.ExecCtx.TaskDir.Dir, "test.sh") 346 testData := []byte(` 347 at_term() { 348 echo 'Terminated.' 349 exit 3 350 } 351 trap at_term USR1 352 while true; do 353 sleep 1 354 done 355 `) 356 if err := ioutil.WriteFile(testFile, testData, 0777); err != nil { 357 t.Fatalf("Failed to write data: %v", err) 358 } 359 360 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 361 t.Fatalf("prestart err: %v", err) 362 } 363 handle, err := d.Start(ctx.ExecCtx, task) 364 if err != nil { 365 t.Fatalf("err: %v", err) 366 } 367 if handle == nil { 368 t.Fatalf("missing handle") 369 } 370 371 go func() { 372 time.Sleep(100 * time.Millisecond) 373 err := handle.Signal(syscall.SIGUSR1) 374 if err != nil { 375 t.Fatalf("err: %v", err) 376 } 377 }() 378 379 // Task should terminate quickly 380 select { 381 case res := <-handle.WaitCh(): 382 if res.Successful() { 383 t.Fatal("should err") 384 } 385 case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second): 386 t.Fatalf("timeout") 387 } 388 389 // Check the log file to see it exited because of the signal 390 outputFile := filepath.Join(ctx.ExecCtx.TaskDir.LogDir, "signal.stdout.0") 391 act, err := ioutil.ReadFile(outputFile) 392 if err != nil { 393 t.Fatalf("Couldn't read expected output: %v", err) 394 } 395 396 exp := "Terminated." 397 if strings.TrimSpace(string(act)) != exp { 398 t.Logf("Read from %v", outputFile) 399 t.Fatalf("Command outputted %v; want %v", act, exp) 400 } 401 } 402 403 func TestExecDriverUser(t *testing.T) { 404 ctestutils.ExecCompatible(t) 405 task := &structs.Task{ 406 Name: "sleep", 407 Driver: "exec", 408 User: "alice", 409 Config: map[string]interface{}{ 410 "command": "/bin/sleep", 411 "args": []string{"100"}, 412 }, 413 LogConfig: &structs.LogConfig{ 414 MaxFiles: 10, 415 MaxFileSizeMB: 10, 416 }, 417 Resources: basicResources, 418 KillTimeout: 10 * time.Second, 419 } 420 421 ctx := testDriverContexts(t, task) 422 defer ctx.AllocDir.Destroy() 423 d := NewExecDriver(ctx.DriverCtx) 424 425 if _, err := d.Prestart(ctx.ExecCtx, task); err != nil { 426 t.Fatalf("prestart err: %v", err) 427 } 428 handle, err := d.Start(ctx.ExecCtx, task) 429 if err == nil { 430 handle.Kill() 431 t.Fatalf("Should've failed") 432 } 433 msg := "user alice" 434 if !strings.Contains(err.Error(), msg) { 435 t.Fatalf("Expecting '%v' in '%v'", msg, err) 436 } 437 }