package executor import ( "io/ioutil" "log" "os" "path/filepath" "testing" "time" "github.com/hashicorp/nomad/client/allocdir" "github.com/hashicorp/nomad/helper/testtask" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" ) func TestMain(m *testing.M) { if !testtask.Run() { os.Exit(m.Run()) } } var ( constraint = &structs.Resources{ CPU: 250, MemoryMB: 256, Networks: []*structs.NetworkResource{ &structs.NetworkResource{ MBits: 50, DynamicPorts: []structs.Port{{Label: "http"}}, }, }, } ) func mockAllocDir(t *testing.T) (string, *allocdir.AllocDir) { alloc := mock.Alloc() task := alloc.Job.TaskGroups[0].Tasks[0] allocDir := allocdir.NewAllocDir(filepath.Join(os.TempDir(), alloc.ID)) if err := allocDir.Build([]*structs.Task{task}); err != nil { log.Panicf("allocDir.Build() failed: %v", err) } return task.Name, allocDir } func testExecutor(t *testing.T, buildExecutor func() Executor, compatible func(*testing.T)) { if compatible != nil { compatible(t) } command := func(name string, args ...string) Executor { e := buildExecutor() SetCommand(e, name, args) testtask.SetEnv(e.Command()) return e } Executor_Start_Invalid(t, command) Executor_Start_Wait_Failure_Code(t, command) Executor_Start_Wait(t, command) Executor_Start_Kill(t, command) Executor_Open(t, command, buildExecutor) Executor_Open_Invalid(t, command, buildExecutor) } type buildExecCommand func(name string, args ...string) Executor func Executor_Start_Invalid(t *testing.T, command buildExecCommand) { invalid := "/bin/foobar" e := command(invalid, "1") if err := e.Limit(constraint); err != nil { log.Panicf("Limit() failed: %v", err) } task, alloc := mockAllocDir(t) defer alloc.Destroy() if err := e.ConfigureTaskDir(task, alloc); err != nil { log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) } if err := e.Start(); err == nil { log.Panicf("Start(%v) should have failed", invalid) } } func Executor_Start_Wait_Failure_Code(t *testing.T, command buildExecCommand) { e := command(testtask.Path(), "fail") if err := e.Limit(constraint); err != nil { log.Panicf("Limit() failed: %v", err) } task, alloc := mockAllocDir(t) defer alloc.Destroy() if err := e.ConfigureTaskDir(task, alloc); err != nil { log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) } if err := e.Start(); err != nil { log.Panicf("Start() failed: %v", err) } if err := e.Wait(); err == nil { log.Panicf("Wait() should have failed") } } func Executor_Start_Wait(t *testing.T, command buildExecCommand) { task, alloc := mockAllocDir(t) defer alloc.Destroy() taskDir, ok := alloc.TaskDirs[task] if !ok { log.Panicf("No task directory found for task %v", task) } expected := "hello world" file := filepath.Join(allocdir.TaskLocal, "output.txt") absFilePath := filepath.Join(taskDir, file) e := command(testtask.Path(), "sleep", "1s", "write", expected, file) if err := e.Limit(constraint); err != nil { log.Panicf("Limit() failed: %v", err) } if err := e.ConfigureTaskDir(task, alloc); err != nil { log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) } if err := e.Start(); err != nil { log.Panicf("Start() failed: %v", err) } if res := e.Wait(); !res.Successful() { log.Panicf("Wait() failed: %v", res) } output, err := ioutil.ReadFile(absFilePath) if err != nil { log.Panicf("Couldn't read file %v", absFilePath) } act := string(output) if act != expected { log.Panicf("Command output incorrectly: want %v; got %v", expected, act) } } func Executor_Start_Kill(t *testing.T, command buildExecCommand) { task, alloc := mockAllocDir(t) defer alloc.Destroy() taskDir, ok := alloc.TaskDirs[task] if !ok { log.Panicf("No task directory found for task %v", task) } filePath := filepath.Join(taskDir, "output") e := command(testtask.Path(), "sleep", "1s", "write", "failure", filePath) if err := e.Limit(constraint); err != nil { log.Panicf("Limit() failed: %v", err) } if err := e.ConfigureTaskDir(task, alloc); err != nil { log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) } if err := e.Start(); err != nil { log.Panicf("Start() failed: %v", err) } if err := e.Shutdown(); err != nil { log.Panicf("Shutdown() failed: %v", err) } time.Sleep(1500 * time.Millisecond) // Check that the file doesn't exist. if _, err := os.Stat(filePath); err == nil { log.Panicf("Stat(%v) should have failed: task not killed", filePath) } } func Executor_Open(t *testing.T, command buildExecCommand, newExecutor func() Executor) { task, alloc := mockAllocDir(t) defer alloc.Destroy() taskDir, ok := alloc.TaskDirs[task] if !ok { log.Panicf("No task directory found for task %v", task) } expected := "hello world" file := filepath.Join(allocdir.TaskLocal, "output.txt") absFilePath := filepath.Join(taskDir, file) e := command(testtask.Path(), "sleep", "1s", "write", expected, file) if err := e.Limit(constraint); err != nil { log.Panicf("Limit() failed: %v", err) } if err := e.ConfigureTaskDir(task, alloc); err != nil { log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) } if err := e.Start(); err != nil { log.Panicf("Start() failed: %v", err) } id, err := e.ID() if err != nil { log.Panicf("ID() failed: %v", err) } e2 := newExecutor() if err := e2.Open(id); err != nil { log.Panicf("Open(%v) failed: %v", id, err) } if res := e2.Wait(); !res.Successful() { log.Panicf("Wait() failed: %v", res) } output, err := ioutil.ReadFile(absFilePath) if err != nil { log.Panicf("Couldn't read file %v", absFilePath) } act := string(output) if act != expected { log.Panicf("Command output incorrectly: want %v; got %v", expected, act) } } func Executor_Open_Invalid(t *testing.T, command buildExecCommand, newExecutor func() Executor) { task, alloc := mockAllocDir(t) e := command(testtask.Path(), "echo", "foo") if err := e.Limit(constraint); err != nil { log.Panicf("Limit() failed: %v", err) } if err := e.ConfigureTaskDir(task, alloc); err != nil { log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) } if err := e.Start(); err != nil { log.Panicf("Start() failed: %v", err) } id, err := e.ID() if err != nil { log.Panicf("ID() failed: %v", err) } // Kill the task because some OSes (windows) will not let us destroy the // alloc (below) if the task is still running. if err := e.ForceStop(); err != nil { log.Panicf("e.ForceStop() failed: %v", err) } // Wait until process is actually gone, we don't care what the result was. e.Wait() // Destroy the allocdir which removes the exit code. if err := alloc.Destroy(); err != nil { log.Panicf("alloc.Destroy() failed: %v", err) } e2 := newExecutor() if err := e2.Open(id); err == nil { log.Panicf("Open(%v) should have failed", id) } }