github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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 }