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