diff --git a/client/exec/exec.go b/client/exec/exec.go index 00476c1cb..d72027685 100644 --- a/client/exec/exec.go +++ b/client/exec/exec.go @@ -1,47 +1,147 @@ -// Package exec is used to invoke child processes across various operating -// systems and child processes and provide the following features: +// Package exec is used to invoke child processes across various platforms to +// provide the following features: // // - Least privilege // - Resource constraints // - Process isolation // -// The semantics and implementation for these differ between operating systems, -// operating system versions, and types of child processes. For example, running -// Docker on Linux has different semantics than running Java on Windows. Also, -// versions of an OS may provide different capabilities for resource isolation, -// such as ulimits, cgroups, containers, jails, etc. Please refer to the -// relevant implementation for specific details. +// A "platform" may be defined as coarsely as "Windows" or as specifically as +// "linux 3.20 with systemd". This allows Nomad to use best-effort, best- +// available capabilities of each platform to provide resource constraints, +// process isolation, and security features, or otherwise take advantage of +// features that are unique to that platform. +// +// The semantics of any particular instance are left up to the implementation. +// However, these should be completely transparent to the calling context. In +// other words, the Java driver should be able to call exec for any platform and +// just work. package exec -import "github.com/hashicorp/nomad/nomad/structs" +import ( + "fmt" + "os" + "os/exec" + "path/filepath" -type Command struct { - // This may be a username or Uid. The implementation will decide how to use it. - UserID string -} + "github.com/hashicorp/nomad/nomad/structs" +) +// Executor is an interface that any platform- or capability-specific exec +// wrapper must implement. You should not need to implement a Java executor. +// Rather, you would implement a cgroups executor that the Java driver will use. type Executor interface { // Limit must be called before Start and restricts the amount of resources // the process can use Limit(structs.Resources) + // RunAs sets the user we should use to run this command. This may be set as + // a username, uid, or other identifier. The implementation will decide what + // to do with it, if anything. + RunAs(string) + // Start the process. This may wrap the actual process in another command, - // depending on the capabilities in this environment. + // depending on the capabilities in this environment. Errors that arise from + // Limits or Runas will bubble through Start() Start() error + // Open should be called to restore a previous pid. This might be needed if + // nomad is restarted. This sets os.Process internally. + Open(int) error + // Shutdown should use a graceful stop mechanism so the application can // perform checkpointing or cleanup, if such a mechanism is available. - // If such a mechanism is not available, Showdown() should call ForceStop(). + // If such a mechanism is not available, Shutdown() should call ForceStop(). Shutdown() error // ForceStop will terminate the process without waiting for cleanup. Every // implementations must provide this. ForceStop() error + + // Access the underlying Cmd struct. This should never be nil. Also, this is + // not intended to be access outside the exec package, so YMMV. + Command() *cmd } -// DefaultExecutor uses capability testing to give you the best available +// Cmd is an extension of exec.Cmd that incorporates functionality for +// re-attaching to processes, dropping priviledges, etc., based on platform- +// specific implementations. +type cmd struct { + exec.Cmd + + // Resources is used to limit CPU and RAM used by the process, by way of + // cgroups or a similar mechanism. + Resources structs.Resources + + // RunAs may be a username or Uid. The implementation will decide how to use it. + RunAs string +} + +// Command is a mirror of exec.Command that returns a platform-specific Executor +func Command(name string, arg ...string) Executor { + executor := AutoselectExecutor() + cmd := executor.Command() + cmd.Path = name + cmd.Args = append([]string{name}, arg...) + + if filepath.Base(name) == name { + if lp, err := exec.LookPath(name); err != nil { + // cmd.lookPathErr = err + } else { + cmd.Path = lp + } + } + return executor +} + +func OpenPid(int) Executor { + executor := AutoselectExecutor() + return executor +} + +// AutoselectExecutor uses capability testing to give you the best available // executor based on your platform and execution environment. If you need a // specific executor, call it directly. -func DefaultExecutor() Executor { - // TODO Implement this +func AutoselectExecutor() Executor { + // TODO platform switching + return &UniversalExecutor{} +} + +// UniversalExecutor should work everywhere, and as a result does not include +// any resource restrictions or runas capabilities. +type UniversalExecutor struct { + cmd +} + +func (e *UniversalExecutor) Limit(resources structs.Resources) { + // No-op +} + +func (e *UniversalExecutor) RunAs(userid string) { + // No-op +} + +func (e *UniversalExecutor) Start() error { + // We don't want to call ourself. We want to call Start on our embedded Cmd + return e.cmd.Start() +} + +func (e *UniversalExecutor) Open(pid int) error { + process, err := os.FindProcess(pid) + if err != nil { + return fmt.Errorf("Failed to reopen pid %d: %s", pid, err) + } + e.Process = process + return nil +} + +func (e *UniversalExecutor) Shutdown() error { + return e.ForceStop() +} + +func (e *UniversalExecutor) ForceStop() error { + return e.Process.Kill() +} + +func (e *UniversalExecutor) Command() *cmd { + return &e.cmd } diff --git a/client/exec/exec_linux.go b/client/exec/exec_linux.go index ce7bafc23..14591dad5 100644 --- a/client/exec/exec_linux.go +++ b/client/exec/exec_linux.go @@ -1,3 +1,35 @@ package exec -// TODO Implement this! +import "github.com/hashicorp/nomad/nomad/structs" + +type LinuxExecutor struct { + cmd +} + +func (e *LinuxExecutor) Limit(resources structs.Resources) { + +} + +func (e *LinuxExecutor) RunAs(userid string) { + +} + +func (e *LinuxExecutor) Start() error { + return nil +} + +func (e *LinuxExecutor) Open(pid int) error { + return nil +} + +func (e *LinuxExecutor) Shutdown() error { + return nil +} + +func (e *LinuxExecutor) ForceStop() error { + return nil +} + +func (e *LinuxExecutor) Command() *cmd { + return &e.cmd +}