2015-11-05 17:58:57 +00:00
|
|
|
package executor
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
2015-11-20 15:56:30 +00:00
|
|
|
"os/exec"
|
2015-11-05 17:58:57 +00:00
|
|
|
"path/filepath"
|
2015-11-19 03:27:24 +00:00
|
|
|
"strings"
|
2015-11-05 17:58:57 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/client/allocdir"
|
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
2015-11-20 15:56:30 +00:00
|
|
|
// testBinary is the path to the running test binary
|
2015-11-25 00:21:39 +00:00
|
|
|
var testBinary = func() string {
|
|
|
|
abs, err := filepath.Abs(os.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
return err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
return abs
|
|
|
|
}()
|
2015-11-20 15:56:30 +00:00
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
func TestMain(m *testing.M) {
|
2015-11-20 15:56:30 +00:00
|
|
|
// The tests in this package recursively execute the test binary produced
|
|
|
|
// by go test. The TEST_MAIN environment variable controls the recursive
|
|
|
|
// execution.
|
2015-11-25 00:21:39 +00:00
|
|
|
switch tm := os.Getenv(testModeEnvVar); tm {
|
2015-11-20 15:56:30 +00:00
|
|
|
case "":
|
|
|
|
os.Exit(m.Run())
|
2015-11-19 03:27:24 +00:00
|
|
|
case "app":
|
|
|
|
appMain()
|
|
|
|
default:
|
2015-11-25 00:21:39 +00:00
|
|
|
fmt.Fprintf(os.Stderr,
|
|
|
|
"Unexpected value for test mode environment variable, %q\n", tm)
|
2015-11-20 15:56:30 +00:00
|
|
|
os.Exit(1)
|
2015-11-19 03:27:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-20 15:56:30 +00:00
|
|
|
// setTestAppEnv sets the environement of cmd for a recursive call into
|
|
|
|
// TestMain.
|
|
|
|
func setTestAppEnv(cmd *exec.Cmd) {
|
2015-11-25 00:21:39 +00:00
|
|
|
cmd.Env = append(os.Environ(), fmt.Sprintf("%v=app", testModeEnvVar))
|
2015-11-20 15:56:30 +00:00
|
|
|
}
|
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
func appMain() {
|
|
|
|
if len(os.Args) < 2 {
|
|
|
|
fmt.Fprintln(os.Stderr, "no command provided")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2015-11-20 15:56:30 +00:00
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
args := os.Args[1:]
|
2015-11-20 15:56:30 +00:00
|
|
|
|
|
|
|
// popArg removes the first argument from args and returns it.
|
|
|
|
popArg := func() string {
|
2015-11-19 03:27:24 +00:00
|
|
|
s := args[0]
|
|
|
|
args = args[1:]
|
|
|
|
return s
|
|
|
|
}
|
2015-11-20 15:56:30 +00:00
|
|
|
|
|
|
|
// execute a sequence of operations from args
|
2015-11-19 03:27:24 +00:00
|
|
|
for len(args) > 0 {
|
2015-11-20 15:56:30 +00:00
|
|
|
switch cmd := popArg(); cmd {
|
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
case "sleep":
|
2015-11-20 15:56:30 +00:00
|
|
|
// sleep <dur>: sleep for a duration indicated by the first
|
|
|
|
// argument
|
2015-11-19 03:27:24 +00:00
|
|
|
if len(args) < 1 {
|
|
|
|
fmt.Fprintln(os.Stderr, "expected arg for sleep")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2015-11-20 15:56:30 +00:00
|
|
|
dur, err := time.ParseDuration(popArg())
|
2015-11-19 03:27:24 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "could not parse sleep time: %v", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
time.Sleep(dur)
|
2015-11-20 15:56:30 +00:00
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
case "echo":
|
2015-11-20 15:56:30 +00:00
|
|
|
// echo <msg ...>: write the remaining arguments to stdout each
|
|
|
|
// separated by a single space and followed by a newline.
|
2015-11-19 03:27:24 +00:00
|
|
|
fmt.Println(strings.Join(args, " "))
|
|
|
|
args = args[:0]
|
2015-11-20 15:56:30 +00:00
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
case "write":
|
2015-11-20 15:56:30 +00:00
|
|
|
// write <msg> <file>: write a message to a file. The first
|
|
|
|
// argument is the msg. The second argument is the path to the
|
|
|
|
// target file.
|
2015-11-19 03:27:24 +00:00
|
|
|
if len(args) < 2 {
|
|
|
|
fmt.Fprintln(os.Stderr, "expected two args for write")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2015-11-20 15:56:30 +00:00
|
|
|
msg := popArg()
|
|
|
|
file := popArg()
|
2015-11-19 03:27:24 +00:00
|
|
|
ioutil.WriteFile(file, []byte(msg), 0666)
|
2015-11-20 15:56:30 +00:00
|
|
|
|
2015-11-19 03:27:24 +00:00
|
|
|
default:
|
|
|
|
fmt.Fprintln(os.Stderr, "unknown command:", cmd)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-05 17:58:57 +00:00
|
|
|
var (
|
|
|
|
constraint = &structs.Resources{
|
|
|
|
CPU: 250,
|
|
|
|
MemoryMB: 256,
|
|
|
|
Networks: []*structs.NetworkResource{
|
|
|
|
&structs.NetworkResource{
|
|
|
|
MBits: 50,
|
2015-11-16 04:53:04 +00:00
|
|
|
DynamicPorts: []structs.Port{{Label: "http"}},
|
2015-11-05 17:58:57 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-11-05 19:31:50 +00:00
|
|
|
func testExecutor(t *testing.T, buildExecutor func() Executor, compatible func(*testing.T)) {
|
2015-11-05 17:58:57 +00:00
|
|
|
if compatible != nil {
|
|
|
|
compatible(t)
|
|
|
|
}
|
|
|
|
|
|
|
|
command := func(name string, args ...string) Executor {
|
2015-11-19 03:27:24 +00:00
|
|
|
e := buildExecutor()
|
|
|
|
SetCommand(e, name, args)
|
2015-11-20 15:56:30 +00:00
|
|
|
setTestAppEnv(e.Command())
|
2015-11-19 03:27:24 +00:00
|
|
|
return e
|
2015-11-05 17:58:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Executor_Start_Invalid(t, command)
|
|
|
|
Executor_Start_Wait_Failure_Code(t, command)
|
|
|
|
Executor_Start_Wait(t, command)
|
|
|
|
Executor_Start_Kill(t, command)
|
2015-11-05 19:54:51 +00:00
|
|
|
Executor_Open(t, command, buildExecutor)
|
2015-11-23 18:25:26 +00:00
|
|
|
Executor_Open_Invalid(t, command, buildExecutor)
|
2015-11-05 17:58:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2015-11-20 15:56:30 +00:00
|
|
|
e := command(testBinary, "fail")
|
2015-11-05 17:58:57 +00:00
|
|
|
|
|
|
|
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)
|
2015-11-20 15:56:30 +00:00
|
|
|
e := command(testBinary, "sleep", "1s", "write", expected, file)
|
2015-11-05 17:58:57 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2015-11-14 06:07:13 +00:00
|
|
|
if res := e.Wait(); !res.Successful() {
|
|
|
|
log.Panicf("Wait() failed: %v", res)
|
2015-11-05 17:58:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
2015-11-20 15:56:30 +00:00
|
|
|
e := command(testBinary, "sleep", "1s", "write", "failure", filePath)
|
2015-11-05 17:58:57 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-05 19:54:51 +00:00
|
|
|
func Executor_Open(t *testing.T, command buildExecCommand, newExecutor func() Executor) {
|
2015-11-05 17:58:57 +00:00
|
|
|
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)
|
2015-11-20 15:56:30 +00:00
|
|
|
e := command(testBinary, "sleep", "1s", "write", expected, file)
|
2015-11-05 17:58:57 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2015-11-05 19:54:51 +00:00
|
|
|
e2 := newExecutor()
|
2015-11-05 17:58:57 +00:00
|
|
|
if err := e2.Open(id); err != nil {
|
|
|
|
log.Panicf("Open(%v) failed: %v", id, err)
|
|
|
|
}
|
|
|
|
|
2015-11-14 06:07:13 +00:00
|
|
|
if res := e2.Wait(); !res.Successful() {
|
|
|
|
log.Panicf("Wait() failed: %v", res)
|
2015-11-05 17:58:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2015-11-06 19:23:27 +00:00
|
|
|
|
|
|
|
func Executor_Open_Invalid(t *testing.T, command buildExecCommand, newExecutor func() Executor) {
|
|
|
|
task, alloc := mockAllocDir(t)
|
2015-11-23 18:26:09 +00:00
|
|
|
e := command(testBinary, "echo", "foo")
|
2015-11-06 19:23:27 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2015-11-23 18:26:09 +00:00
|
|
|
// 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()
|
|
|
|
|
2015-11-06 19:23:27 +00:00
|
|
|
// Destroy the allocdir which removes the exit code.
|
2015-11-23 18:26:09 +00:00
|
|
|
if err := alloc.Destroy(); err != nil {
|
|
|
|
log.Panicf("alloc.Destroy() failed: %v", err)
|
|
|
|
}
|
2015-11-06 19:23:27 +00:00
|
|
|
|
|
|
|
e2 := newExecutor()
|
|
|
|
if err := e2.Open(id); err == nil {
|
|
|
|
log.Panicf("Open(%v) should have failed", id)
|
|
|
|
}
|
|
|
|
}
|