mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 02:15:43 +03:00
executor: use grpc instead of netrpc as plugin protocol
* Added protobuf spec for executor * Seperated executor structs into their own package
This commit is contained in:
@@ -3,8 +3,6 @@ package executor
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -18,8 +16,8 @@ import (
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/lib/fifo"
|
||||
"github.com/hashicorp/nomad/client/stats"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor/structs"
|
||||
shelpers "github.com/hashicorp/nomad/helper/stats"
|
||||
|
||||
"github.com/hashicorp/consul-template/signals"
|
||||
@@ -41,164 +39,14 @@ var (
|
||||
ExecutorBasicMeasuredCpuStats = []string{"System Mode", "User Mode", "Percent"}
|
||||
)
|
||||
|
||||
// Executor is the interface which allows a driver to launch and supervise
|
||||
// a process
|
||||
type Executor interface {
|
||||
// Launch a user process configured by the given ExecCommand
|
||||
Launch(launchCmd *ExecCommand) (*ProcessState, error)
|
||||
|
||||
// Wait blocks until the process exits or an error occures
|
||||
Wait() (*ProcessState, error)
|
||||
|
||||
// Shutdown will shutdown the executor by stopping the user process,
|
||||
// cleaning up and resources created by the executor. The shutdown sequence
|
||||
// will first send the given signal to the process. This defaults to "SIGINT"
|
||||
// if not specified. The executor will then wait for the process to exit
|
||||
// before cleaning up other resources. If the executor waits longer than the
|
||||
// given grace period, the process is forcefully killed.
|
||||
//
|
||||
// To force kill the user process, gracePeriod can be set to 0.
|
||||
Shutdown(signal string, gracePeriod time.Duration) error
|
||||
|
||||
// UpdateResources updates any resource isolation enforcement with new
|
||||
// constraints if supported.
|
||||
UpdateResources(*Resources) error
|
||||
|
||||
// Version returns the executor API version
|
||||
Version() (*ExecutorVersion, error)
|
||||
|
||||
// Stats fetchs process usage stats for the executor and each pid if available
|
||||
Stats() (*cstructs.TaskResourceUsage, error)
|
||||
|
||||
// Signal sends the given signal to the user process
|
||||
Signal(os.Signal) error
|
||||
|
||||
// Exec executes the given command and args inside the executor context
|
||||
// and returns the output and exit code.
|
||||
Exec(deadline time.Time, cmd string, args []string) ([]byte, int, error)
|
||||
}
|
||||
|
||||
// Resources describes the resource isolation required
|
||||
type Resources struct {
|
||||
CPU int
|
||||
MemoryMB int
|
||||
DiskMB int
|
||||
IOPS int
|
||||
}
|
||||
|
||||
// ExecCommand holds the user command, args, and other isolation related
|
||||
// settings.
|
||||
type ExecCommand struct {
|
||||
// Cmd is the command that the user wants to run.
|
||||
Cmd string
|
||||
|
||||
// Args is the args of the command that the user wants to run.
|
||||
Args []string
|
||||
|
||||
// Resources defined by the task
|
||||
Resources *Resources
|
||||
|
||||
// StdoutPath is the path the procoess stdout should be written to
|
||||
StdoutPath string
|
||||
stdout io.WriteCloser
|
||||
|
||||
// StderrPath is the path the procoess stderr should be written to
|
||||
StderrPath string
|
||||
stderr io.WriteCloser
|
||||
|
||||
// Env is the list of KEY=val pairs of environment variables to be set
|
||||
Env []string
|
||||
|
||||
// User is the user which the executor uses to run the command.
|
||||
User string
|
||||
|
||||
// TaskDir is the directory path on the host where for the task
|
||||
TaskDir string
|
||||
|
||||
// ResourceLimits determines whether resource limits are enforced by the
|
||||
// executor.
|
||||
ResourceLimits bool
|
||||
|
||||
// Cgroup marks whether we put the process in a cgroup. Setting this field
|
||||
// doesn't enforce resource limits. To enforce limits, set ResourceLimits.
|
||||
// Using the cgroup does allow more precise cleanup of processes.
|
||||
BasicProcessCgroup bool
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
// Stdout returns a writer for the configured file descriptor
|
||||
func (c *ExecCommand) Stdout() (io.WriteCloser, error) {
|
||||
if c.stdout == nil {
|
||||
if c.StdoutPath != "" {
|
||||
f, err := fifo.Open(c.StdoutPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stdout: %v", err)
|
||||
}
|
||||
c.stdout = f
|
||||
} else {
|
||||
c.stdout = nopCloser{ioutil.Discard}
|
||||
}
|
||||
}
|
||||
return c.stdout, nil
|
||||
}
|
||||
|
||||
// Stderr returns a writer for the configured file descriptor
|
||||
func (c *ExecCommand) Stderr() (io.WriteCloser, error) {
|
||||
if c.stderr == nil {
|
||||
if c.StderrPath != "" {
|
||||
f, err := fifo.Open(c.StderrPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stderr: %v", err)
|
||||
}
|
||||
c.stderr = f
|
||||
} else {
|
||||
c.stderr = nopCloser{ioutil.Discard}
|
||||
}
|
||||
}
|
||||
return c.stderr, nil
|
||||
}
|
||||
|
||||
func (c *ExecCommand) Close() {
|
||||
stdout, err := c.Stdout()
|
||||
if err == nil {
|
||||
stdout.Close()
|
||||
}
|
||||
stderr, err := c.Stderr()
|
||||
if err == nil {
|
||||
stderr.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessState holds information about the state of a user process.
|
||||
type ProcessState struct {
|
||||
Pid int
|
||||
ExitCode int
|
||||
Signal int
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// ExecutorVersion is the version of the executor
|
||||
type ExecutorVersion struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (v *ExecutorVersion) GoString() string {
|
||||
return v.Version
|
||||
}
|
||||
|
||||
// UniversalExecutor is an implementation of the Executor which launches and
|
||||
// supervises processes. In addition to process supervision it provides resource
|
||||
// and file system isolation
|
||||
type UniversalExecutor struct {
|
||||
childCmd exec.Cmd
|
||||
commandCfg *ExecCommand
|
||||
commandCfg *structs.ExecCommand
|
||||
|
||||
exitState *ProcessState
|
||||
exitState *structs.ProcessState
|
||||
processExited chan interface{}
|
||||
|
||||
// resConCtx is used to track and cleanup additional resources created by
|
||||
@@ -214,7 +62,7 @@ type UniversalExecutor struct {
|
||||
}
|
||||
|
||||
// NewExecutor returns an Executor
|
||||
func NewExecutor(logger hclog.Logger) Executor {
|
||||
func NewExecutor(logger hclog.Logger) structs.Executor {
|
||||
logger = logger.Named("executor")
|
||||
if err := shelpers.Init(); err != nil {
|
||||
logger.Error("unable to initialize stats", "error", err)
|
||||
@@ -230,13 +78,13 @@ func NewExecutor(logger hclog.Logger) Executor {
|
||||
}
|
||||
|
||||
// Version returns the api version of the executor
|
||||
func (e *UniversalExecutor) Version() (*ExecutorVersion, error) {
|
||||
return &ExecutorVersion{Version: ExecutorVersionLatest}, nil
|
||||
func (e *UniversalExecutor) Version() (*structs.ExecutorVersion, error) {
|
||||
return &structs.ExecutorVersion{Version: ExecutorVersionLatest}, nil
|
||||
}
|
||||
|
||||
// Launch launches the main process and returns its state. It also
|
||||
// configures an applies isolation on certain platforms.
|
||||
func (e *UniversalExecutor) Launch(command *ExecCommand) (*ProcessState, error) {
|
||||
func (e *UniversalExecutor) Launch(command *structs.ExecCommand) (*structs.ProcessState, error) {
|
||||
e.logger.Info("launching command", "command", command.Cmd, "args", strings.Join(command.Args, " "))
|
||||
|
||||
e.commandCfg = command
|
||||
@@ -298,7 +146,7 @@ func (e *UniversalExecutor) Launch(command *ExecCommand) (*ProcessState, error)
|
||||
|
||||
go e.pidCollector.collectPids(e.processExited, getAllPids)
|
||||
go e.wait()
|
||||
return &ProcessState{Pid: e.childCmd.Process.Pid, ExitCode: -1, Time: time.Now()}, nil
|
||||
return &structs.ProcessState{Pid: e.childCmd.Process.Pid, ExitCode: -1, Time: time.Now()}, nil
|
||||
}
|
||||
|
||||
// Exec a command inside a container for exec and java drivers.
|
||||
@@ -346,12 +194,16 @@ func ExecScript(ctx context.Context, dir string, env []string, attrs *syscall.Sy
|
||||
}
|
||||
|
||||
// Wait waits until a process has exited and returns it's exitcode and errors
|
||||
func (e *UniversalExecutor) Wait() (*ProcessState, error) {
|
||||
<-e.processExited
|
||||
return e.exitState, nil
|
||||
func (e *UniversalExecutor) Wait(ctx context.Context) (*structs.ProcessState, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-e.processExited:
|
||||
return e.exitState, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *UniversalExecutor) UpdateResources(resources *Resources) error {
|
||||
func (e *UniversalExecutor) UpdateResources(resources *structs.Resources) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -360,7 +212,7 @@ func (e *UniversalExecutor) wait() {
|
||||
pid := e.childCmd.Process.Pid
|
||||
err := e.childCmd.Wait()
|
||||
if err == nil {
|
||||
e.exitState = &ProcessState{Pid: pid, ExitCode: 0, Time: time.Now()}
|
||||
e.exitState = &structs.ProcessState{Pid: pid, ExitCode: 0, Time: time.Now()}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -388,7 +240,7 @@ func (e *UniversalExecutor) wait() {
|
||||
e.logger.Warn("unexpected Cmd.Wait() error type", "error", err)
|
||||
}
|
||||
|
||||
e.exitState = &ProcessState{Pid: pid, ExitCode: exitCode, Signal: signal, Time: time.Now()}
|
||||
e.exitState = &structs.ProcessState{Pid: pid, ExitCode: exitCode, Signal: signal, Time: time.Now()}
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
package executor
|
||||
|
||||
import hclog "github.com/hashicorp/go-hclog"
|
||||
import (
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor/structs"
|
||||
)
|
||||
|
||||
func NewExecutorWithIsolation(logger hclog.Logger) Executor {
|
||||
func NewExecutorWithIsolation(logger hclog.Logger) structs.Executor {
|
||||
logger = logger.Named("executor")
|
||||
logger.Error("isolation executor is not supported on this platform, using default")
|
||||
return NewExecutor(logger)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/nomad/client/stats"
|
||||
cstructs "github.com/hashicorp/nomad/client/structs"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor/structs"
|
||||
"github.com/hashicorp/nomad/helper/discover"
|
||||
shelpers "github.com/hashicorp/nomad/helper/stats"
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
@@ -61,7 +62,7 @@ func init() {
|
||||
// LibcontainerExecutor implements an Executor with the runc/libcontainer api
|
||||
type LibcontainerExecutor struct {
|
||||
id string
|
||||
command *ExecCommand
|
||||
command *structs.ExecCommand
|
||||
|
||||
logger hclog.Logger
|
||||
|
||||
@@ -73,10 +74,10 @@ type LibcontainerExecutor struct {
|
||||
container libcontainer.Container
|
||||
userProc *libcontainer.Process
|
||||
userProcExited chan interface{}
|
||||
exitState *ProcessState
|
||||
exitState *structs.ProcessState
|
||||
}
|
||||
|
||||
func NewExecutorWithIsolation(logger hclog.Logger) Executor {
|
||||
func NewExecutorWithIsolation(logger hclog.Logger) structs.Executor {
|
||||
logger = logger.Named("isolated_executor")
|
||||
if err := shelpers.Init(); err != nil {
|
||||
logger.Error("unable to initialize stats", "error", err)
|
||||
@@ -92,7 +93,7 @@ func NewExecutorWithIsolation(logger hclog.Logger) Executor {
|
||||
}
|
||||
|
||||
// Launch creates a new container in libcontainer and starts a new process with it
|
||||
func (l *LibcontainerExecutor) Launch(command *ExecCommand) (*ProcessState, error) {
|
||||
func (l *LibcontainerExecutor) Launch(command *structs.ExecCommand) (*structs.ProcessState, error) {
|
||||
l.logger.Info("launching command", "command", command.Cmd, "args", strings.Join(command.Args, " "))
|
||||
// Find the nomad executable to launch the executor process with
|
||||
bin, err := discover.NomadExecutable()
|
||||
@@ -101,7 +102,7 @@ func (l *LibcontainerExecutor) Launch(command *ExecCommand) (*ProcessState, erro
|
||||
}
|
||||
|
||||
if command.Resources == nil {
|
||||
command.Resources = &Resources{}
|
||||
command.Resources = &structs.Resources{}
|
||||
}
|
||||
|
||||
l.command = command
|
||||
@@ -206,7 +207,7 @@ func (l *LibcontainerExecutor) Launch(command *ExecCommand) (*ProcessState, erro
|
||||
go l.pidCollector.collectPids(l.userProcExited, l.getAllPids)
|
||||
go l.wait()
|
||||
|
||||
return &ProcessState{
|
||||
return &structs.ProcessState{
|
||||
Pid: pid,
|
||||
ExitCode: -1,
|
||||
Time: time.Now(),
|
||||
@@ -231,9 +232,13 @@ func (l *LibcontainerExecutor) getAllPids() (map[int]*nomadPid, error) {
|
||||
}
|
||||
|
||||
// Wait waits until a process has exited and returns it's exitcode and errors
|
||||
func (l *LibcontainerExecutor) Wait() (*ProcessState, error) {
|
||||
<-l.userProcExited
|
||||
return l.exitState, nil
|
||||
func (l *LibcontainerExecutor) Wait(ctx context.Context) (*structs.ProcessState, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-l.userProcExited:
|
||||
return l.exitState, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LibcontainerExecutor) wait() {
|
||||
@@ -247,7 +252,7 @@ func (l *LibcontainerExecutor) wait() {
|
||||
ps = exitErr.ProcessState
|
||||
} else {
|
||||
l.logger.Error("failed to call wait on user process", "error", err)
|
||||
l.exitState = &ProcessState{Pid: 0, ExitCode: 0, Time: time.Now()}
|
||||
l.exitState = &structs.ProcessState{Pid: 0, ExitCode: 0, Time: time.Now()}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -265,7 +270,7 @@ func (l *LibcontainerExecutor) wait() {
|
||||
}
|
||||
}
|
||||
|
||||
l.exitState = &ProcessState{
|
||||
l.exitState = &structs.ProcessState{
|
||||
Pid: ps.Pid(),
|
||||
ExitCode: exitCode,
|
||||
Signal: signal,
|
||||
@@ -327,13 +332,13 @@ func (l *LibcontainerExecutor) Shutdown(signal string, grace time.Duration) erro
|
||||
}
|
||||
|
||||
// UpdateResources updates the resource isolation with new values to be enforced
|
||||
func (l *LibcontainerExecutor) UpdateResources(resources *Resources) error {
|
||||
func (l *LibcontainerExecutor) UpdateResources(resources *structs.Resources) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version returns the api version of the executor
|
||||
func (l *LibcontainerExecutor) Version() (*ExecutorVersion, error) {
|
||||
return &ExecutorVersion{Version: ExecutorVersionLatest}, nil
|
||||
func (l *LibcontainerExecutor) Version() (*structs.ExecutorVersion, error) {
|
||||
return &structs.ExecutorVersion{Version: ExecutorVersionLatest}, nil
|
||||
}
|
||||
|
||||
// Stats returns the resource statistics for processes managed by the executor
|
||||
@@ -453,7 +458,7 @@ func (l *LibcontainerExecutor) handleExecWait(ch chan *waitResult, process *libc
|
||||
ch <- &waitResult{ps, err}
|
||||
}
|
||||
|
||||
func configureCapabilities(cfg *lconfigs.Config, command *ExecCommand) {
|
||||
func configureCapabilities(cfg *lconfigs.Config, command *structs.ExecCommand) {
|
||||
// TODO: allow better control of these
|
||||
cfg.Capabilities = &lconfigs.Capabilities{
|
||||
Bounding: allCaps,
|
||||
@@ -465,7 +470,7 @@ func configureCapabilities(cfg *lconfigs.Config, command *ExecCommand) {
|
||||
|
||||
}
|
||||
|
||||
func configureIsolation(cfg *lconfigs.Config, command *ExecCommand) {
|
||||
func configureIsolation(cfg *lconfigs.Config, command *structs.ExecCommand) {
|
||||
defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
|
||||
|
||||
// set the new root directory for the container
|
||||
@@ -531,7 +536,7 @@ func configureIsolation(cfg *lconfigs.Config, command *ExecCommand) {
|
||||
}
|
||||
}
|
||||
|
||||
func configureCgroups(cfg *lconfigs.Config, command *ExecCommand) error {
|
||||
func configureCgroups(cfg *lconfigs.Config, command *structs.ExecCommand) error {
|
||||
|
||||
// If resources are not limited then manually create cgroups needed
|
||||
if !command.ResourceLimits {
|
||||
@@ -597,7 +602,7 @@ func configureBasicCgroups(cfg *lconfigs.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newLibcontainerConfig(command *ExecCommand) *lconfigs.Config {
|
||||
func newLibcontainerConfig(command *structs.ExecCommand) *lconfigs.Config {
|
||||
cfg := &lconfigs.Config{
|
||||
Cgroups: &lconfigs.Cgroup{
|
||||
Resources: &lconfigs.Resources{
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
cstructs "github.com/hashicorp/nomad/client/structs"
|
||||
"github.com/hashicorp/nomad/client/taskenv"
|
||||
"github.com/hashicorp/nomad/client/testutil"
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor/structs"
|
||||
"github.com/hashicorp/nomad/helper/testlog"
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
tu "github.com/hashicorp/nomad/testutil"
|
||||
@@ -25,7 +26,7 @@ func init() {
|
||||
executorFactories["LibcontainerExecutor"] = libcontainerFactory
|
||||
}
|
||||
|
||||
func libcontainerFactory(l hclog.Logger) Executor {
|
||||
func libcontainerFactory(l hclog.Logger) structs.Executor {
|
||||
return NewExecutorWithIsolation(l)
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ func libcontainerFactory(l hclog.Logger) Executor {
|
||||
// chroot. Use testExecutorContext if you don't need a chroot.
|
||||
//
|
||||
// The caller is responsible for calling AllocDir.Destroy() to cleanup.
|
||||
func testExecutorCommandWithChroot(t *testing.T) (*ExecCommand, *allocdir.AllocDir) {
|
||||
func testExecutorCommandWithChroot(t *testing.T) (*structs.ExecCommand, *allocdir.AllocDir) {
|
||||
chrootEnv := map[string]string{
|
||||
"/etc/ld.so.cache": "/etc/ld.so.cache",
|
||||
"/etc/ld.so.conf": "/etc/ld.so.conf",
|
||||
@@ -61,10 +62,10 @@ func testExecutorCommandWithChroot(t *testing.T) (*ExecCommand, *allocdir.AllocD
|
||||
t.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
|
||||
}
|
||||
td := allocDir.TaskDirs[task.Name]
|
||||
cmd := &ExecCommand{
|
||||
cmd := &structs.ExecCommand{
|
||||
Env: taskEnv.List(),
|
||||
TaskDir: td.Dir,
|
||||
Resources: &Resources{
|
||||
Resources: &structs.Resources{
|
||||
CPU: task.Resources.CPU,
|
||||
MemoryMB: task.Resources.MemoryMB,
|
||||
IOPS: task.Resources.IOPS,
|
||||
@@ -143,7 +144,8 @@ ld.so.cache
|
||||
ld.so.conf
|
||||
ld.so.conf.d/`
|
||||
tu.WaitForResult(func() (bool, error) {
|
||||
output := execCmd.stdout.(*bufferCloser).String()
|
||||
outWriter, _ := execCmd.GetWriters()
|
||||
output := outWriter.(*bufferCloser).String()
|
||||
act := strings.TrimSpace(string(output))
|
||||
if act != expected {
|
||||
return false, fmt.Errorf("Command output incorrectly: want %v; got %v", expected, act)
|
||||
@@ -176,9 +178,10 @@ func TestExecutor_ClientCleanup(t *testing.T) {
|
||||
require.NoError(executor.Shutdown("SIGINT", 100*time.Millisecond))
|
||||
executor.Wait()
|
||||
|
||||
output := execCmd.stdout.(*bufferCloser).String()
|
||||
outWriter, _ := execCmd.GetWriters()
|
||||
output := outWriter.(*bufferCloser).String()
|
||||
require.NotZero(len(output))
|
||||
time.Sleep(2 * time.Second)
|
||||
output1 := execCmd.stdout.(*bufferCloser).String()
|
||||
output1 := outWriter.(*bufferCloser).String()
|
||||
require.Equal(len(output), len(output1))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/drivers/shared/executor/structs"
|
||||
tu "github.com/hashicorp/nomad/testutil"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
@@ -22,8 +23,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var executorFactories = map[string]func(hclog.Logger) Executor{}
|
||||
var universalFactory = func(l hclog.Logger) Executor {
|
||||
var executorFactories = map[string]func(hclog.Logger) structs.Executor{}
|
||||
var universalFactory = func(l hclog.Logger) structs.Executor {
|
||||
return NewExecutor(l)
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ func init() {
|
||||
// testExecutorContext returns an ExecutorContext and AllocDir.
|
||||
//
|
||||
// The caller is responsible for calling AllocDir.Destroy() to cleanup.
|
||||
func testExecutorCommand(t *testing.T) (*ExecCommand, *allocdir.AllocDir) {
|
||||
func testExecutorCommand(t *testing.T) (*structs.ExecCommand, *allocdir.AllocDir) {
|
||||
alloc := mock.Alloc()
|
||||
task := alloc.Job.TaskGroups[0].Tasks[0]
|
||||
taskEnv := taskenv.NewBuilder(mock.Node(), alloc, task, "global").Build()
|
||||
@@ -48,10 +49,10 @@ func testExecutorCommand(t *testing.T) (*ExecCommand, *allocdir.AllocDir) {
|
||||
t.Fatalf("allocDir.NewTaskDir(%q) failed: %v", task.Name, err)
|
||||
}
|
||||
td := allocDir.TaskDirs[task.Name]
|
||||
cmd := &ExecCommand{
|
||||
cmd := &structs.ExecCommand{
|
||||
Env: taskEnv.List(),
|
||||
TaskDir: td.Dir,
|
||||
Resources: &Resources{
|
||||
Resources: &structs.Resources{
|
||||
CPU: task.Resources.CPU,
|
||||
MemoryMB: task.Resources.MemoryMB,
|
||||
IOPS: task.Resources.IOPS,
|
||||
@@ -68,9 +69,8 @@ type bufferCloser struct {
|
||||
|
||||
func (_ *bufferCloser) Close() error { return nil }
|
||||
|
||||
func configureTLogging(cmd *ExecCommand) (stdout bufferCloser, stderr bufferCloser) {
|
||||
cmd.stdout = &stdout
|
||||
cmd.stderr = &stderr
|
||||
func configureTLogging(cmd *structs.ExecCommand) (stdout bufferCloser, stderr bufferCloser) {
|
||||
cmd.SetWriters(&stdout, &stderr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -140,7 +140,8 @@ func TestExecutor_Start_Wait(pt *testing.T) {
|
||||
|
||||
expected := "hello world"
|
||||
tu.WaitForResult(func() (bool, error) {
|
||||
output := execCmd.stdout.(*bufferCloser).String()
|
||||
outWriter, _ := execCmd.GetWriters()
|
||||
output := outWriter.(*bufferCloser).String()
|
||||
act := strings.TrimSpace(string(output))
|
||||
if expected != act {
|
||||
return false, fmt.Errorf("expected: '%s' actual: '%s'", expected, act)
|
||||
@@ -207,7 +208,8 @@ func TestExecutor_Start_Kill(pt *testing.T) {
|
||||
require.NoError(executor.Shutdown("SIGINT", 100*time.Millisecond))
|
||||
|
||||
time.Sleep(time.Duration(tu.TestMultiplier()*2) * time.Second)
|
||||
output := execCmd.stdout.(*bufferCloser).String()
|
||||
outWriter, _ := execCmd.GetWriters()
|
||||
output := outWriter.(*bufferCloser).String()
|
||||
expected := ""
|
||||
act := strings.TrimSpace(string(output))
|
||||
if act != expected {
|
||||
|
||||
177
drivers/shared/executor/structs/structs.go
Normal file
177
drivers/shared/executor/structs/structs.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/client/lib/fifo"
|
||||
cstructs "github.com/hashicorp/nomad/client/structs"
|
||||
)
|
||||
|
||||
// Executor is the interface which allows a driver to launch and supervise
|
||||
// a process
|
||||
type Executor interface {
|
||||
// Launch a user process configured by the given ExecCommand
|
||||
Launch(launchCmd *ExecCommand) (*ProcessState, error)
|
||||
|
||||
// Wait blocks until the process exits or an error occures
|
||||
Wait(ctx context.Context) (*ProcessState, error)
|
||||
|
||||
// Shutdown will shutdown the executor by stopping the user process,
|
||||
// cleaning up and resources created by the executor. The shutdown sequence
|
||||
// will first send the given signal to the process. This defaults to "SIGINT"
|
||||
// if not specified. The executor will then wait for the process to exit
|
||||
// before cleaning up other resources. If the executor waits longer than the
|
||||
// given grace period, the process is forcefully killed.
|
||||
//
|
||||
// To force kill the user process, gracePeriod can be set to 0.
|
||||
Shutdown(signal string, gracePeriod time.Duration) error
|
||||
|
||||
// UpdateResources updates any resource isolation enforcement with new
|
||||
// constraints if supported.
|
||||
UpdateResources(*Resources) error
|
||||
|
||||
// Version returns the executor API version
|
||||
Version() (*ExecutorVersion, error)
|
||||
|
||||
// Stats fetchs process usage stats for the executor and each pid if available
|
||||
Stats() (*cstructs.TaskResourceUsage, error)
|
||||
|
||||
// Signal sends the given signal to the user process
|
||||
Signal(os.Signal) error
|
||||
|
||||
// Exec executes the given command and args inside the executor context
|
||||
// and returns the output and exit code.
|
||||
Exec(deadline time.Time, cmd string, args []string) ([]byte, int, error)
|
||||
}
|
||||
|
||||
// Resources describes the resource isolation required
|
||||
type Resources struct {
|
||||
CPU int
|
||||
MemoryMB int
|
||||
DiskMB int
|
||||
IOPS int
|
||||
}
|
||||
|
||||
// ExecCommand holds the user command, args, and other isolation related
|
||||
// settings.
|
||||
type ExecCommand struct {
|
||||
// Cmd is the command that the user wants to run.
|
||||
Cmd string
|
||||
|
||||
// Args is the args of the command that the user wants to run.
|
||||
Args []string
|
||||
|
||||
// Resources defined by the task
|
||||
Resources *Resources
|
||||
|
||||
// StdoutPath is the path the process stdout should be written to
|
||||
StdoutPath string
|
||||
stdout io.WriteCloser
|
||||
|
||||
// StderrPath is the path the process stderr should be written to
|
||||
StderrPath string
|
||||
stderr io.WriteCloser
|
||||
|
||||
// Env is the list of KEY=val pairs of environment variables to be set
|
||||
Env []string
|
||||
|
||||
// User is the user which the executor uses to run the command.
|
||||
User string
|
||||
|
||||
// TaskDir is the directory path on the host where for the task
|
||||
TaskDir string
|
||||
|
||||
// ResourceLimits determines whether resource limits are enforced by the
|
||||
// executor.
|
||||
ResourceLimits bool
|
||||
|
||||
// Cgroup marks whether we put the process in a cgroup. Setting this field
|
||||
// doesn't enforce resource limits. To enforce limits, set ResourceLimits.
|
||||
// Using the cgroup does allow more precise cleanup of processes.
|
||||
BasicProcessCgroup bool
|
||||
}
|
||||
|
||||
// SetWriters sets the writer for the process stdout and stderr. This should
|
||||
// not be used if writing to a file path such as a fifo file. SetStdoutWriter
|
||||
// is mainly used for unit testing purposes.
|
||||
func (c *ExecCommand) SetWriters(out io.WriteCloser, err io.WriteCloser) {
|
||||
c.stdout = out
|
||||
c.stderr = err
|
||||
}
|
||||
|
||||
// GetWriters returns the unexported io.WriteCloser for the stdout and stderr
|
||||
// handles. This is mainly used for unit testing purposes.
|
||||
func (c *ExecCommand) GetWriters() (stdout io.WriteCloser, stderr io.WriteCloser) {
|
||||
return c.stdout, c.stderr
|
||||
}
|
||||
|
||||
type nopCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
// Stdout returns a writer for the configured file descriptor
|
||||
func (c *ExecCommand) Stdout() (io.WriteCloser, error) {
|
||||
if c.stdout == nil {
|
||||
if c.StdoutPath != "" {
|
||||
f, err := fifo.Open(c.StdoutPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stdout: %v", err)
|
||||
}
|
||||
c.stdout = f
|
||||
} else {
|
||||
c.stdout = nopCloser{ioutil.Discard}
|
||||
}
|
||||
}
|
||||
return c.stdout, nil
|
||||
}
|
||||
|
||||
// Stderr returns a writer for the configured file descriptor
|
||||
func (c *ExecCommand) Stderr() (io.WriteCloser, error) {
|
||||
if c.stderr == nil {
|
||||
if c.StderrPath != "" {
|
||||
f, err := fifo.Open(c.StderrPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create stderr: %v", err)
|
||||
}
|
||||
c.stderr = f
|
||||
} else {
|
||||
c.stderr = nopCloser{ioutil.Discard}
|
||||
}
|
||||
}
|
||||
return c.stderr, nil
|
||||
}
|
||||
|
||||
func (c *ExecCommand) Close() {
|
||||
stdout, err := c.Stdout()
|
||||
if err == nil {
|
||||
stdout.Close()
|
||||
}
|
||||
stderr, err := c.Stderr()
|
||||
if err == nil {
|
||||
stderr.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessState holds information about the state of a user process.
|
||||
type ProcessState struct {
|
||||
Pid int
|
||||
ExitCode int
|
||||
Signal int
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// ExecutorVersion is the version of the executor
|
||||
type ExecutorVersion struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func (v *ExecutorVersion) GoString() string {
|
||||
return v.Version
|
||||
}
|
||||
Reference in New Issue
Block a user