From 13ea9bc9fff4da16852838da31aec30a635a8339 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Tue, 3 Nov 2015 12:47:48 -0800 Subject: [PATCH] Make a basic executor that can be shared and fix some fingerprinting/tests --- client/driver/java.go | 4 +- client/driver/java_test.go | 6 +- client/executor/exec_basic.go | 107 +++++++++++++++++++++++++++ client/executor/exec_universal.go | 24 ++---- client/testutil/driver_compatible.go | 6 ++ 5 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 client/executor/exec_basic.go diff --git a/client/driver/java.go b/client/driver/java.go index ac2c3c6f3..8aad1dd65 100644 --- a/client/driver/java.go +++ b/client/driver/java.go @@ -38,8 +38,8 @@ func NewJavaDriver(ctx *DriverContext) Driver { func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { // Only enable if we are root when running on non-windows systems. - if runtime.GOOS != "windows" && syscall.Geteuid() != 0 { - d.logger.Printf("[DEBUG] driver.java: must run as root user, disabling") + if runtime.GOOS == "linux" && syscall.Geteuid() != 0 { + d.logger.Printf("[DEBUG] driver.java: must run as root user on linux, disabling") return false, nil } diff --git a/client/driver/java_test.go b/client/driver/java_test.go index eecfc0faf..b4f2f2e15 100644 --- a/client/driver/java_test.go +++ b/client/driver/java_test.go @@ -19,7 +19,7 @@ func javaLocated() bool { // The fingerprinter test should always pass, even if Java is not installed. func TestJavaDriver_Fingerprint(t *testing.T) { - ctestutils.ExecCompatible(t) + ctestutils.JavaCompatible(t) d := NewJavaDriver(testDriverContext("")) node := &structs.Node{ Attributes: make(map[string]string), @@ -93,7 +93,7 @@ func TestJavaDriver_Start_Wait(t *testing.T) { t.Skip("Java not found; skipping") } - ctestutils.ExecCompatible(t) + ctestutils.JavaCompatible(t) task := &structs.Task{ Name: "demo-app", Config: map[string]string{ @@ -141,7 +141,7 @@ func TestJavaDriver_Start_Kill_Wait(t *testing.T) { t.Skip("Java not found; skipping") } - ctestutils.ExecCompatible(t) + ctestutils.JavaCompatible(t) task := &structs.Task{ Name: "demo-app", Config: map[string]string{ diff --git a/client/executor/exec_basic.go b/client/executor/exec_basic.go new file mode 100644 index 000000000..81f17d414 --- /dev/null +++ b/client/executor/exec_basic.go @@ -0,0 +1,107 @@ +package executor + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/hashicorp/nomad/client/allocdir" + "github.com/hashicorp/nomad/client/driver/args" + "github.com/hashicorp/nomad/client/driver/environment" + "github.com/hashicorp/nomad/nomad/structs" +) + +// BasicExecutor should work everywhere, and as a result does not include +// any resource restrictions or runas capabilities. +type BasicExecutor struct { + cmd +} + +// TODO: Update to use the Spawner. +// TODO: Have raw_exec use this as well. +func NewBasicExecutor() Executor { + return &BasicExecutor{} +} + +func (e *BasicExecutor) Limit(resources *structs.Resources) error { + if resources == nil { + return errNoResources + } + return nil +} + +func (e *BasicExecutor) ConfigureTaskDir(taskName string, alloc *allocdir.AllocDir) error { + taskDir, ok := alloc.TaskDirs[taskName] + if !ok { + return fmt.Errorf("Error finding task dir for (%s)", taskName) + } + e.Dir = taskDir + return nil +} + +func (e *BasicExecutor) Start() error { + // Parse the commands arguments and replace instances of Nomad environment + // variables. + envVars, err := environment.ParseFromList(e.cmd.Env) + if err != nil { + return err + } + + parsedPath, err := args.ParseAndReplace(e.cmd.Path, envVars.Map()) + if err != nil { + return err + } else if len(parsedPath) != 1 { + return fmt.Errorf("couldn't properly parse command path: %v", e.cmd.Path) + } + + e.cmd.Path = parsedPath[0] + combined := strings.Join(e.cmd.Args, " ") + parsed, err := args.ParseAndReplace(combined, envVars.Map()) + if err != nil { + return err + } + e.Cmd.Args = parsed + + // We don't want to call ourself. We want to call Start on our embedded Cmd + return e.cmd.Start() +} + +func (e *BasicExecutor) Open(pid string) error { + pidNum, err := strconv.Atoi(pid) + if err != nil { + return fmt.Errorf("Failed to parse pid %v: %v", pid, err) + } + + process, err := os.FindProcess(pidNum) + if err != nil { + return fmt.Errorf("Failed to reopen pid %d: %v", pidNum, err) + } + e.Process = process + return nil +} + +func (e *BasicExecutor) Wait() error { + // We don't want to call ourself. We want to call Start on our embedded Cmd + return e.cmd.Wait() +} + +func (e *BasicExecutor) ID() (string, error) { + if e.cmd.Process != nil { + return strconv.Itoa(e.cmd.Process.Pid), nil + } else { + return "", fmt.Errorf("Process has finished or was never started") + } +} + +func (e *BasicExecutor) Shutdown() error { + return e.ForceStop() +} + +func (e *BasicExecutor) ForceStop() error { + return e.Process.Kill() +} + +func (e *BasicExecutor) Command() *cmd { + return &e.cmd +} diff --git a/client/executor/exec_universal.go b/client/executor/exec_universal.go index 4979ae3b7..318faea4b 100644 --- a/client/executor/exec_universal.go +++ b/client/executor/exec_universal.go @@ -2,21 +2,11 @@ package executor -import ( - "github.com/hashicorp/nomad/client/allocdir" - "github.com/hashicorp/nomad/nomad/structs" -) +func NewExecutor() Executor { + return &UniversalExecutor{BasicExecutor{}} +} -// UniversalExecutor exists to make the exec driver compile on all operating systems. -type UniversalExecutor struct{} - -func NewExecutor() Executor { return &UniversalExecutor{} } -func (e *UniversalExecutor) Limit(resources *structs.Resources) error { return nil } -func (e *UniversalExecutor) ConfigureTaskDir(string, *allocdir.AllocDir) error { return nil } -func (e *UniversalExecutor) Start() error { return nil } -func (e *UniversalExecutor) Open(pid string) error { return nil } -func (e *UniversalExecutor) Wait() error { return nil } -func (e *UniversalExecutor) ID() (string, error) { return "", nil } -func (e *UniversalExecutor) Shutdown() error { return nil } -func (e *UniversalExecutor) ForceStop() error { return nil } -func (e *UniversalExecutor) Command() *cmd { return nil } +// UniversalExecutor wraps the BasicExecutor +type UniversalExecutor struct { + BasicExecutor +} diff --git a/client/testutil/driver_compatible.go b/client/testutil/driver_compatible.go index 94ae6225c..768051e63 100644 --- a/client/testutil/driver_compatible.go +++ b/client/testutil/driver_compatible.go @@ -13,6 +13,12 @@ func ExecCompatible(t *testing.T) { } } +func JavaCompatible(t *testing.T) { + if runtime.GOOS == "linux" && syscall.Geteuid() != 0 { + t.Skip("Test only available when running as root on linux") + } +} + func QemuCompatible(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Must be on non-windows environments to run test")