github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/drivers/rawexec/driver_unix_test.go (about) 1 // +build !windows 2 3 package rawexec 4 5 import ( 6 "context" 7 "os" 8 "regexp" 9 "runtime" 10 "strconv" 11 "syscall" 12 "testing" 13 14 "fmt" 15 "io/ioutil" 16 "path/filepath" 17 "strings" 18 "time" 19 20 "github.com/hashicorp/nomad/helper/testtask" 21 "github.com/hashicorp/nomad/helper/uuid" 22 basePlug "github.com/hashicorp/nomad/plugins/base" 23 "github.com/hashicorp/nomad/plugins/drivers" 24 dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils" 25 "github.com/hashicorp/nomad/testutil" 26 "github.com/stretchr/testify/require" 27 "golang.org/x/sys/unix" 28 ) 29 30 func TestRawExecDriver_User(t *testing.T) { 31 t.Parallel() 32 if runtime.GOOS != "linux" { 33 t.Skip("Linux only test") 34 } 35 require := require.New(t) 36 37 d := newEnabledRawExecDriver(t) 38 harness := dtestutil.NewDriverHarness(t, d) 39 40 task := &drivers.TaskConfig{ 41 ID: uuid.Generate(), 42 Name: "sleep", 43 User: "alice", 44 } 45 46 cleanup := harness.MkAllocDir(task, false) 47 defer cleanup() 48 49 tc := &TaskConfig{ 50 Command: testtask.Path(), 51 Args: []string{"sleep", "45s"}, 52 } 53 require.NoError(task.EncodeConcreteDriverConfig(&tc)) 54 testtask.SetTaskConfigEnv(task) 55 56 _, _, err := harness.StartTask(task) 57 require.Error(err) 58 msg := "unknown user alice" 59 require.Contains(err.Error(), msg) 60 } 61 62 func TestRawExecDriver_Signal(t *testing.T) { 63 t.Parallel() 64 if runtime.GOOS != "linux" { 65 t.Skip("Linux only test") 66 } 67 require := require.New(t) 68 69 d := newEnabledRawExecDriver(t) 70 harness := dtestutil.NewDriverHarness(t, d) 71 72 task := &drivers.TaskConfig{ 73 ID: uuid.Generate(), 74 Name: "signal", 75 } 76 77 cleanup := harness.MkAllocDir(task, true) 78 defer cleanup() 79 80 tc := &TaskConfig{ 81 Command: "/bin/bash", 82 Args: []string{"test.sh"}, 83 } 84 require.NoError(task.EncodeConcreteDriverConfig(&tc)) 85 testtask.SetTaskConfigEnv(task) 86 87 testFile := filepath.Join(task.TaskDir().Dir, "test.sh") 88 testData := []byte(` 89 at_term() { 90 echo 'Terminated.' 91 exit 3 92 } 93 trap at_term USR1 94 while true; do 95 sleep 1 96 done 97 `) 98 require.NoError(ioutil.WriteFile(testFile, testData, 0777)) 99 100 _, _, err := harness.StartTask(task) 101 require.NoError(err) 102 103 go func() { 104 time.Sleep(100 * time.Millisecond) 105 require.NoError(harness.SignalTask(task.ID, "SIGUSR1")) 106 }() 107 108 // Task should terminate quickly 109 waitCh, err := harness.WaitTask(context.Background(), task.ID) 110 require.NoError(err) 111 select { 112 case res := <-waitCh: 113 require.False(res.Successful()) 114 require.Equal(3, res.ExitCode) 115 case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second): 116 require.Fail("WaitTask timeout") 117 } 118 119 // Check the log file to see it exited because of the signal 120 outputFile := filepath.Join(task.TaskDir().LogDir, "signal.stdout.0") 121 exp := "Terminated." 122 testutil.WaitForResult(func() (bool, error) { 123 act, err := ioutil.ReadFile(outputFile) 124 if err != nil { 125 return false, fmt.Errorf("Couldn't read expected output: %v", err) 126 } 127 128 if strings.TrimSpace(string(act)) != exp { 129 t.Logf("Read from %v", outputFile) 130 return false, fmt.Errorf("Command outputted %v; want %v", act, exp) 131 } 132 return true, nil 133 }, func(err error) { require.NoError(err) }) 134 } 135 136 func TestRawExecDriver_StartWaitStop(t *testing.T) { 137 t.Parallel() 138 require := require.New(t) 139 140 d := newEnabledRawExecDriver(t) 141 harness := dtestutil.NewDriverHarness(t, d) 142 defer harness.Kill() 143 144 // Disable cgroups so test works without root 145 config := &Config{NoCgroups: true, Enabled: true} 146 var data []byte 147 require.NoError(basePlug.MsgPackEncode(&data, config)) 148 bconfig := &basePlug.Config{PluginConfig: data} 149 require.NoError(harness.SetConfig(bconfig)) 150 151 task := &drivers.TaskConfig{ 152 ID: uuid.Generate(), 153 Name: "test", 154 } 155 156 taskConfig := map[string]interface{}{} 157 taskConfig["command"] = testtask.Path() 158 taskConfig["args"] = []string{"sleep", "100s"} 159 160 require.NoError(task.EncodeConcreteDriverConfig(&taskConfig)) 161 162 cleanup := harness.MkAllocDir(task, false) 163 defer cleanup() 164 165 handle, _, err := harness.StartTask(task) 166 require.NoError(err) 167 168 ch, err := harness.WaitTask(context.Background(), handle.Config.ID) 169 require.NoError(err) 170 171 require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second)) 172 173 go func() { 174 harness.StopTask(task.ID, 2*time.Second, "SIGINT") 175 }() 176 177 select { 178 case result := <-ch: 179 require.Equal(int(unix.SIGINT), result.Signal) 180 case <-time.After(10 * time.Second): 181 require.Fail("timeout waiting for task to shutdown") 182 } 183 184 // Ensure that the task is marked as dead, but account 185 // for WaitTask() closing channel before internal state is updated 186 testutil.WaitForResult(func() (bool, error) { 187 status, err := harness.InspectTask(task.ID) 188 if err != nil { 189 return false, fmt.Errorf("inspecting task failed: %v", err) 190 } 191 if status.State != drivers.TaskStateExited { 192 return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State) 193 } 194 195 return true, nil 196 }, func(err error) { 197 require.NoError(err) 198 }) 199 200 require.NoError(harness.DestroyTask(task.ID, true)) 201 } 202 203 // TestRawExecDriver_DestroyKillsAll asserts that when TaskDestroy is called all 204 // task processes are cleaned up. 205 func TestRawExecDriver_DestroyKillsAll(t *testing.T) { 206 t.Parallel() 207 208 // This only works reliably with cgroup PID tracking, happens in linux only 209 if runtime.GOOS != "linux" { 210 t.Skip("Linux only test") 211 } 212 213 require := require.New(t) 214 215 d := newEnabledRawExecDriver(t) 216 harness := dtestutil.NewDriverHarness(t, d) 217 defer harness.Kill() 218 219 task := &drivers.TaskConfig{ 220 ID: uuid.Generate(), 221 Name: "test", 222 } 223 224 cleanup := harness.MkAllocDir(task, true) 225 defer cleanup() 226 227 taskConfig := map[string]interface{}{} 228 taskConfig["command"] = "/bin/sh" 229 taskConfig["args"] = []string{"-c", fmt.Sprintf(`sleep 3600 & echo "SLEEP_PID=$!"`)} 230 231 require.NoError(task.EncodeConcreteDriverConfig(&taskConfig)) 232 233 handle, _, err := harness.StartTask(task) 234 require.NoError(err) 235 defer harness.DestroyTask(task.ID, true) 236 237 ch, err := harness.WaitTask(context.Background(), handle.Config.ID) 238 require.NoError(err) 239 240 select { 241 case result := <-ch: 242 require.True(result.Successful(), "command failed: %#v", result) 243 case <-time.After(10 * time.Second): 244 require.Fail("timeout waiting for task to shutdown") 245 } 246 247 sleepPid := 0 248 249 // Ensure that the task is marked as dead, but account 250 // for WaitTask() closing channel before internal state is updated 251 testutil.WaitForResult(func() (bool, error) { 252 stdout, err := ioutil.ReadFile(filepath.Join(task.TaskDir().LogDir, "test.stdout.0")) 253 if err != nil { 254 return false, fmt.Errorf("failed to output pid file: %v", err) 255 } 256 257 pidMatch := regexp.MustCompile(`SLEEP_PID=(\d+)`).FindStringSubmatch(string(stdout)) 258 if len(pidMatch) != 2 { 259 return false, fmt.Errorf("failed to find pid in %s", string(stdout)) 260 } 261 262 pid, err := strconv.Atoi(pidMatch[1]) 263 if err != nil { 264 return false, fmt.Errorf("pid parts aren't int: %s", pidMatch[1]) 265 } 266 267 sleepPid = pid 268 return true, nil 269 }, func(err error) { 270 require.NoError(err) 271 }) 272 273 // isProcessRunning returns an error if process is not running 274 isProcessRunning := func(pid int) error { 275 process, err := os.FindProcess(pid) 276 if err != nil { 277 return fmt.Errorf("failed to find process: %s", err) 278 } 279 280 err = process.Signal(syscall.Signal(0)) 281 if err != nil { 282 return fmt.Errorf("failed to signal process: %s", err) 283 } 284 285 return nil 286 } 287 288 require.NoError(isProcessRunning(sleepPid)) 289 290 require.NoError(harness.DestroyTask(task.ID, true)) 291 292 testutil.WaitForResult(func() (bool, error) { 293 err := isProcessRunning(sleepPid) 294 if err == nil { 295 return false, fmt.Errorf("child process is still running") 296 } 297 298 if !strings.Contains(err.Error(), "failed to signal process") { 299 return false, fmt.Errorf("unexpected error: %v", err) 300 } 301 302 return true, nil 303 }, func(err error) { 304 require.NoError(err) 305 }) 306 } 307 308 func TestRawExec_ExecTaskStreaming(t *testing.T) { 309 t.Parallel() 310 if runtime.GOOS == "darwin" { 311 t.Skip("skip running exec tasks on darwin as darwin has restrictions on starting tty shells") 312 } 313 require := require.New(t) 314 315 d := newEnabledRawExecDriver(t) 316 harness := dtestutil.NewDriverHarness(t, d) 317 defer harness.Kill() 318 319 task := &drivers.TaskConfig{ 320 ID: uuid.Generate(), 321 Name: "sleep", 322 } 323 324 cleanup := harness.MkAllocDir(task, false) 325 defer cleanup() 326 327 tc := &TaskConfig{ 328 Command: testtask.Path(), 329 Args: []string{"sleep", "9000s"}, 330 } 331 require.NoError(task.EncodeConcreteDriverConfig(&tc)) 332 testtask.SetTaskConfigEnv(task) 333 334 _, _, err := harness.StartTask(task) 335 require.NoError(err) 336 defer d.DestroyTask(task.ID, true) 337 338 dtestutil.ExecTaskStreamingConformanceTests(t, harness, task.ID) 339 340 } 341 342 func TestRawExec_ExecTaskStreaming_User(t *testing.T) { 343 t.Parallel() 344 if runtime.GOOS != "linux" { 345 t.Skip("skip, requires running on Linux for testing custom user setting") 346 } 347 348 d := newEnabledRawExecDriver(t) 349 harness := dtestutil.NewDriverHarness(t, d) 350 defer harness.Kill() 351 352 task := &drivers.TaskConfig{ 353 ID: uuid.Generate(), 354 Name: "sleep", 355 User: "nobody", 356 } 357 358 cleanup := harness.MkAllocDir(task, false) 359 defer cleanup() 360 361 err := os.Chmod(task.AllocDir, 0777) 362 require.NoError(t, err) 363 364 tc := &TaskConfig{ 365 Command: "/bin/sleep", 366 Args: []string{"9000"}, 367 } 368 require.NoError(t, task.EncodeConcreteDriverConfig(&tc)) 369 testtask.SetTaskConfigEnv(task) 370 371 _, _, err = harness.StartTask(task) 372 require.NoError(t, err) 373 defer d.DestroyTask(task.ID, true) 374 375 code, stdout, stderr := dtestutil.ExecTask(t, harness, task.ID, "whoami", false, "") 376 require.Zero(t, code) 377 require.Empty(t, stderr) 378 require.Contains(t, stdout, "nobody") 379 } 380 381 func TestRawExecDriver_NoCgroup(t *testing.T) { 382 t.Parallel() 383 if runtime.GOOS != "linux" { 384 t.Skip("Linux only test") 385 } 386 387 expectedBytes, err := ioutil.ReadFile("/proc/self/cgroup") 388 require.NoError(t, err) 389 expected := strings.TrimSpace(string(expectedBytes)) 390 391 d := newEnabledRawExecDriver(t) 392 d.config.NoCgroups = true 393 harness := dtestutil.NewDriverHarness(t, d) 394 395 task := &drivers.TaskConfig{ 396 ID: uuid.Generate(), 397 Name: "nocgroup", 398 } 399 400 cleanup := harness.MkAllocDir(task, true) 401 defer cleanup() 402 403 tc := &TaskConfig{ 404 Command: "/bin/cat", 405 Args: []string{"/proc/self/cgroup"}, 406 } 407 require.NoError(t, task.EncodeConcreteDriverConfig(&tc)) 408 testtask.SetTaskConfigEnv(task) 409 410 _, _, err = harness.StartTask(task) 411 require.NoError(t, err) 412 413 // Task should terminate quickly 414 waitCh, err := harness.WaitTask(context.Background(), task.ID) 415 require.NoError(t, err) 416 select { 417 case res := <-waitCh: 418 require.True(t, res.Successful()) 419 require.Zero(t, res.ExitCode) 420 case <-time.After(time.Duration(testutil.TestMultiplier()*6) * time.Second): 421 require.Fail(t, "WaitTask timeout") 422 } 423 424 // Check the log file to see it exited because of the signal 425 outputFile := filepath.Join(task.TaskDir().LogDir, "nocgroup.stdout.0") 426 testutil.WaitForResult(func() (bool, error) { 427 act, err := ioutil.ReadFile(outputFile) 428 if err != nil { 429 return false, fmt.Errorf("Couldn't read expected output: %v", err) 430 } 431 432 if strings.TrimSpace(string(act)) != expected { 433 t.Logf("Read from %v", outputFile) 434 return false, fmt.Errorf("Command outputted\n%v; want\n%v", string(act), expected) 435 } 436 return true, nil 437 }, func(err error) { require.NoError(t, err) }) 438 }