From 90f5e232a5f6a07c3d52a37b97ddd6df17e33170 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Fri, 21 Apr 2017 16:20:37 -0700 Subject: [PATCH] Switch java/exec to use Exec in Executor --- client/alloc_runner.go | 1 - client/driver/exec.go | 7 ++++- client/driver/executor/executor.go | 46 ++++++++++++++++++++++++++++-- client/driver/executor_plugin.go | 36 +++++++++++++++++++++++ client/driver/java.go | 7 ++++- client/task_runner.go | 8 ++++-- 6 files changed, 98 insertions(+), 7 deletions(-) diff --git a/client/alloc_runner.go b/client/alloc_runner.go index 7da40c678..35a58e1cd 100644 --- a/client/alloc_runner.go +++ b/client/alloc_runner.go @@ -195,7 +195,6 @@ func (r *AllocRunner) RestoreState() error { // Restart task runner if RestoreState gave a reason if restartReason != "" { - r.logger.Printf("[INFO] client: restarting alloc %s task %q due to upgrade: %s", r.alloc.ID, name, restartReason) tr.Restart("upgrade", restartReason) } } diff --git a/client/driver/exec.go b/client/driver/exec.go index bc6ee3aee..fdc3bbc01 100644 --- a/client/driver/exec.go +++ b/client/driver/exec.go @@ -259,7 +259,12 @@ func (h *execHandle) Update(task *structs.Task) error { } func (h *execHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { - return execChroot(ctx, h.taskDir.Dir, cmd, args) + deadline, ok := ctx.Deadline() + if !ok { + // No deadline set on context; default to 1 minute + deadline = time.Now().Add(time.Minute) + } + return h.executor.Exec(deadline, cmd, args) } func (h *execHandle) Signal(s os.Signal) error { diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 90797fbef..295dc8928 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -1,6 +1,7 @@ package executor import ( + "context" "fmt" "io/ioutil" "log" @@ -15,6 +16,7 @@ import ( "syscall" "time" + "github.com/armon/circbuf" "github.com/hashicorp/go-multierror" "github.com/mitchellh/go-ps" "github.com/shirou/gopsutil/process" @@ -57,6 +59,7 @@ type Executor interface { Version() (*ExecutorVersion, error) Stats() (*cstructs.TaskResourceUsage, error) Signal(s os.Signal) error + Exec(deadline time.Time, cmd string, args []string) ([]byte, int, error) } // ExecutorContext holds context to configure the command user @@ -203,8 +206,8 @@ func (e *UniversalExecutor) SetContext(ctx *ExecutorContext) error { return nil } -// LaunchCmd launches a process and returns it's state. It also configures an -// applies isolation on certain platforms. +// LaunchCmd launches the main process and returns its state. It also +// configures an applies isolation on certain platforms. func (e *UniversalExecutor) LaunchCmd(command *ExecCommand) (*ProcessState, error) { e.logger.Printf("[DEBUG] executor: launching command %v %v", command.Cmd, strings.Join(command.Args, " ")) @@ -283,6 +286,45 @@ func (e *UniversalExecutor) LaunchCmd(command *ExecCommand) (*ProcessState, erro return &ProcessState{Pid: e.cmd.Process.Pid, ExitCode: -1, IsolationConfig: ic, Time: time.Now()}, nil } +// Exec a command inside a container for exec and java drivers. +func (e *UniversalExecutor) Exec(deadline time.Time, name string, args []string) ([]byte, int, error) { + ctx, cancel := context.WithDeadline(context.Background(), deadline) + defer cancel() + + name = e.ctx.TaskEnv.ReplaceEnv(name) + cmd := exec.CommandContext(ctx, name, e.ctx.TaskEnv.ParseAndReplace(args)...) + + // Copy runtime environment from the main command + cmd.SysProcAttr = e.cmd.SysProcAttr + cmd.Dir = e.cmd.Dir + cmd.Env = e.ctx.TaskEnv.EnvList() + + // Capture output + buf, _ := circbuf.NewBuffer(int64(dstructs.CheckBufSize)) + cmd.Stdout = buf + cmd.Stderr = buf + + if err := cmd.Run(); err != nil { + exitErr, ok := err.(*exec.ExitError) + if !ok { + // Non-exit error, return it and let the caller treat + // it as a critical failure + return nil, 0, err + } + + // Some kind of error happened; default to critical + exitCode := 2 + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + exitCode = status.ExitStatus() + } + + // Don't return the exitError as the caller only needs the + // output and code. + return buf.Bytes(), exitCode, nil + } + return buf.Bytes(), 0, nil +} + // configureLoggers sets up the standard out/error file rotators func (e *UniversalExecutor) configureLoggers() error { e.rotatorLock.Lock() diff --git a/client/driver/executor_plugin.go b/client/driver/executor_plugin.go index 7c4074fef..01e63343d 100644 --- a/client/driver/executor_plugin.go +++ b/client/driver/executor_plugin.go @@ -6,6 +6,7 @@ import ( "net/rpc" "os" "syscall" + "time" "github.com/hashicorp/go-plugin" "github.com/hashicorp/nomad/client/driver/executor" @@ -33,6 +34,17 @@ type LaunchCmdArgs struct { Cmd *executor.ExecCommand } +type ExecCmdArgs struct { + Deadline time.Time + Name string + Args []string +} + +type ExecCmdReturn struct { + Output []byte + Code int +} + func (e *ExecutorRPC) LaunchCmd(cmd *executor.ExecCommand) (*executor.ProcessState, error) { var ps *executor.ProcessState err := e.client.Call("Plugin.LaunchCmd", LaunchCmdArgs{Cmd: cmd}, &ps) @@ -91,6 +103,20 @@ func (e *ExecutorRPC) Signal(s os.Signal) error { return e.client.Call("Plugin.Signal", &s, new(interface{})) } +func (e *ExecutorRPC) Exec(deadline time.Time, name string, args []string) ([]byte, int, error) { + req := ExecCmdArgs{ + Deadline: deadline, + Name: name, + Args: args, + } + var resp *ExecCmdReturn + err := e.client.Call("Plugin.Exec", req, &resp) + if resp == nil { + return nil, 0, err + } + return resp.Output, resp.Code, err +} + type ExecutorRPCServer struct { Impl executor.Executor logger *log.Logger @@ -165,6 +191,16 @@ func (e *ExecutorRPCServer) Signal(args os.Signal, resp *interface{}) error { return e.Impl.Signal(args) } +func (e *ExecutorRPCServer) Exec(args ExecCmdArgs, result *ExecCmdReturn) error { + out, code, err := e.Impl.Exec(args.Deadline, args.Name, args.Args) + ret := &ExecCmdReturn{ + Output: out, + Code: code, + } + *result = *ret + return err +} + type ExecutorPlugin struct { logger *log.Logger Impl *ExecutorRPCServer diff --git a/client/driver/java.go b/client/driver/java.go index c215e6882..5801d2e83 100644 --- a/client/driver/java.go +++ b/client/driver/java.go @@ -390,7 +390,12 @@ func (h *javaHandle) Update(task *structs.Task) error { } func (h *javaHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) { - return execChroot(ctx, h.taskDir, cmd, args) + deadline, ok := ctx.Deadline() + if !ok { + // No deadline set on context; default to 1 minute + deadline = time.Now().Add(time.Minute) + } + return h.executor.Exec(deadline, cmd, args) } func (h *javaHandle) Signal(s os.Signal) error { diff --git a/client/task_runner.go b/client/task_runner.go index a9f1d7442..b88bc6a7d 100644 --- a/client/task_runner.go +++ b/client/task_runner.go @@ -297,7 +297,7 @@ func (r *TaskRunner) RestoreState() (string, error) { return "", nil } - if pre06ScriptCheck(snap.Version, r.task.Services) { + if pre06ScriptCheck(snap.Version, r.task.Driver, r.task.Services) { restartReason = "upgrading pre-0.6 script checks" } @@ -323,7 +323,11 @@ func (r *TaskRunner) RestoreState() (string, error) { var ver06 = version.Must(version.NewVersion("0.6.0dev")) // pre06ScriptCheck returns true if version is prior to 0.6.0dev. -func pre06ScriptCheck(ver string, services []*structs.Service) bool { +func pre06ScriptCheck(ver, driver string, services []*structs.Service) bool { + if driver != "exec" && driver != "java" { + // Only exec and java are affected + return false + } v, err := version.NewVersion(ver) if err != nil { // Treat it as old