open-nomad/client/driver/executor/exec.go

130 lines
4.1 KiB
Go

// Package executor is used to invoke child processes across various operating
// systems in a way that provides the following features:
//
// - Least privilege
// - Resource constraints
// - Process isolation
//
// An operating system may be something like "windows" or "linux with systemd".
// Executors allow drivers like `exec` and `java` to share an implementation
// for isolation capabilities on a particular operating system.
//
// For example:
//
// - `exec` and `java` on Linux use a cgroups executor
// - `exec` and `java` on FreeBSD use a jails executor
//
// However, drivers that provide their own isolation should not use executors.
// For example, using an executor to start QEMU means that the QEMU call is
// run inside a chroot+cgroup, even though the VM already provides isolation for
// the task running inside it. This is an extraneous level of indirection.
package executor
import (
"fmt"
"os/exec"
"path/filepath"
"strings"
"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/nomad/structs"
cstructs "github.com/hashicorp/nomad/client/driver/structs"
)
var errNoResources = fmt.Errorf("No resources are associated with this task")
// If testModeEnvVar is set in a process's environment variables, the
// LinuxExecutor will detect it and inject the current binary into the chroot.
// This enables using the test binary in tests to provide portability.
var testModeEnvVar = "NOMAD_EXECUTOR_TEST_ONLY_13871827980214"
// 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. Note that an error may be returned ONLY IF the
// executor implements resource limiting. Otherwise Limit is ignored.
Limit(*structs.Resources) error
// ConfigureTaskDir must be called before Start and ensures that the tasks
// directory is properly configured.
ConfigureTaskDir(taskName string, alloc *allocdir.AllocDir) error
// Start the process. This may wrap the actual process in another command,
// depending on the capabilities in this environment. Errors that arise from
// Limits or Runas may bubble through Start()
Start() error
// Open should be called to restore a previous execution. This might be needed if
// nomad is restarted.
Open(string) error
// Wait waits till the user's command is completed.
Wait() *cstructs.WaitResult
// Returns a handle that is executor specific for use in reopening.
ID() (string, 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, Shutdown() should call ForceStop().
Shutdown() error
// ForceStop will terminate the process without waiting for cleanup. Every
// implementations must provide this.
ForceStop() error
// Command provides access the underlying Cmd struct in case the Executor
// interface doesn't expose the functionality you need.
Command() *exec.Cmd
}
// Command is a mirror of exec.Command that returns a platform-specific Executor
func Command(name string, args ...string) Executor {
executor := NewExecutor()
SetCommand(executor, name, args)
return executor
}
func SetCommand(e Executor, name string, args []string) {
cmd := e.Command()
cmd.Path = name
cmd.Args = append([]string{name}, args...)
if filepath.Base(name) == name {
if lp, err := exec.LookPath(name); err != nil {
// cmd.lookPathErr = err
} else {
cmd.Path = lp
}
}
}
// OpenId is similar to executor.Command but will attempt to reopen with the
// passed ID.
func OpenId(id string) (Executor, error) {
executor := NewExecutor()
err := executor.Open(id)
if err != nil {
return nil, err
}
return executor, nil
}
// isTest returns whether the cmd is a test binary.
func isTest(cmd *exec.Cmd) bool {
if cmd == nil {
return false
}
for _, env := range cmd.Env {
if strings.HasPrefix(env, testModeEnvVar) {
return true
}
}
return false
}