mirror of
https://github.com/kemko/nomad.git
synced 2026-01-05 09:55:44 +03:00
Merge pull request #2585 from hashicorp/b-2554-container-exec
Execute exec/java script checks in containers
This commit is contained in:
@@ -186,13 +186,19 @@ func (r *AllocRunner) RestoreState() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := tr.RestoreState(); err != nil {
|
||||
r.logger.Printf("[ERR] client: failed to restore state for alloc %s task '%s': %v", r.alloc.ID, name, err)
|
||||
if restartReason, err := tr.RestoreState(); err != nil {
|
||||
r.logger.Printf("[ERR] client: failed to restore state for alloc %s task %q: %v", r.alloc.ID, name, err)
|
||||
mErr.Errors = append(mErr.Errors, err)
|
||||
} else if !r.alloc.TerminalStatus() {
|
||||
// Only start if the alloc isn't in a terminal status.
|
||||
go tr.Run()
|
||||
|
||||
// Restart task runner if RestoreState gave a reason
|
||||
if restartReason != "" {
|
||||
tr.Restart("upgrade", restartReason)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return mErr.ErrorOrNil()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -477,6 +478,84 @@ func TestAllocRunner_SaveRestoreState_TerminalAlloc(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestAllocRunner_SaveRestoreState_Upgrade asserts that pre-0.6 exec tasks are
|
||||
// restarted on upgrade.
|
||||
func TestAllocRunner_SaveRestoreState_Upgrade(t *testing.T) {
|
||||
alloc := mock.Alloc()
|
||||
task := alloc.Job.TaskGroups[0].Tasks[0]
|
||||
task.Driver = "mock_driver"
|
||||
task.Config = map[string]interface{}{
|
||||
"exit_code": "0",
|
||||
"run_for": "10s",
|
||||
}
|
||||
|
||||
upd, ar := testAllocRunnerFromAlloc(alloc, false)
|
||||
// Hack in old version to cause an upgrade on RestoreState
|
||||
origConfig := ar.config.Copy()
|
||||
ar.config.Version = "0.5.6"
|
||||
go ar.Run()
|
||||
|
||||
// Snapshot state
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
return len(ar.tasks) == 1, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("task never started: %v", err)
|
||||
})
|
||||
|
||||
err := ar.SaveState()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Create a new alloc runner
|
||||
l2 := prefixedTestLogger("----- ar2: ")
|
||||
ar2 := NewAllocRunner(l2, origConfig, upd.Update,
|
||||
&structs.Allocation{ID: ar.alloc.ID}, ar.vaultClient,
|
||||
ar.consulClient)
|
||||
err = ar2.RestoreState()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
go ar2.Run()
|
||||
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
if len(ar2.tasks) != 1 {
|
||||
return false, fmt.Errorf("Incorrect number of tasks")
|
||||
}
|
||||
|
||||
if upd.Count < 3 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, ev := range ar2.alloc.TaskStates["web"].Events {
|
||||
if strings.HasSuffix(ev.RestartReason, pre06ScriptCheckReason) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("no restart with proper reason found")
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %v\nAllocs: %#v\nWeb State: %#v", err, upd.Allocs, ar2.alloc.TaskStates["web"])
|
||||
})
|
||||
|
||||
// Destroy and wait
|
||||
ar2.Destroy()
|
||||
start := time.Now()
|
||||
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
alloc := ar2.Alloc()
|
||||
if alloc.ClientStatus != structs.AllocClientStatusComplete {
|
||||
return false, fmt.Errorf("Bad client status; got %v; want %v", alloc.ClientStatus, structs.AllocClientStatusComplete)
|
||||
}
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %v %#v %#v", err, upd.Allocs[0], ar.alloc.TaskStates)
|
||||
})
|
||||
|
||||
if time.Since(start) > time.Duration(testutil.TestMultiplier()*5)*time.Second {
|
||||
t.Fatalf("took too long to terminate")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure pre-#2132 state files containing the Context struct are properly
|
||||
// migrated to the new format.
|
||||
//
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -283,7 +283,8 @@ func TestExecDriverUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestExecDriver_HandlerExec ensures the exec driver's handle properly executes commands inside the chroot.
|
||||
// TestExecDriver_HandlerExec ensures the exec driver's handle properly
|
||||
// executes commands inside the container.
|
||||
func TestExecDriver_HandlerExec(t *testing.T) {
|
||||
ctestutils.ExecCompatible(t)
|
||||
task := &structs.Task{
|
||||
@@ -315,20 +316,60 @@ func TestExecDriver_HandlerExec(t *testing.T) {
|
||||
t.Fatalf("missing handle")
|
||||
}
|
||||
|
||||
// Exec a command that should work
|
||||
out, code, err := handle.Exec(context.TODO(), "/usr/bin/stat", []string{"/alloc"})
|
||||
// Exec a command that should work and dump the environment
|
||||
out, code, err := handle.Exec(context.Background(), "/bin/sh", []string{"-c", "env | grep NOMAD"})
|
||||
if err != nil {
|
||||
t.Fatalf("error exec'ing stat: %v", err)
|
||||
}
|
||||
if code != 0 {
|
||||
t.Fatalf("expected `stat /alloc` to succeed but exit code was: %d", code)
|
||||
}
|
||||
if expected := 100; len(out) < expected {
|
||||
t.Fatalf("expected at least %d bytes of output but found %d:\n%s", expected, len(out), out)
|
||||
|
||||
// Assert exec'd commands are run in a task-like environment
|
||||
scriptEnv := make(map[string]string)
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(string(line), "=", 2)
|
||||
if len(parts) != 2 {
|
||||
t.Fatalf("Invalid env var: %q", line)
|
||||
}
|
||||
scriptEnv[parts[0]] = parts[1]
|
||||
}
|
||||
if v, ok := scriptEnv["NOMAD_SECRETS_DIR"]; !ok || v != "/secrets" {
|
||||
t.Errorf("Expected NOMAD_SECRETS_DIR=/secrets but found=%t value=%q", ok, v)
|
||||
}
|
||||
if v, ok := scriptEnv["NOMAD_ALLOC_ID"]; !ok || v != ctx.DriverCtx.allocID {
|
||||
t.Errorf("Expected NOMAD_SECRETS_DIR=%q but found=%t value=%q", ok, v)
|
||||
}
|
||||
|
||||
// Assert cgroup membership
|
||||
out, code, err = handle.Exec(context.Background(), "/bin/cat", []string{"/proc/self/cgroup"})
|
||||
if err != nil {
|
||||
t.Fatalf("error exec'ing cat /proc/self/cgroup: %v", err)
|
||||
}
|
||||
if code != 0 {
|
||||
t.Fatalf("expected `cat /proc/self/cgroup` to succeed but exit code was: %d", code)
|
||||
}
|
||||
found := false
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
// Every cgroup entry should be /nomad/$ALLOC_ID
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, ":/nomad/") {
|
||||
t.Errorf("Not a member of the alloc's cgroup: expected=...:/nomad/... -- found=%q", line)
|
||||
continue
|
||||
}
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("exec'd command isn't in the task's cgroup")
|
||||
}
|
||||
|
||||
// Exec a command that should fail
|
||||
out, code, err = handle.Exec(context.TODO(), "/usr/bin/stat", []string{"lkjhdsaflkjshowaisxmcvnlia"})
|
||||
out, code, err = handle.Exec(context.Background(), "/usr/bin/stat", []string{"lkjhdsaflkjshowaisxmcvnlia"})
|
||||
if err != nil {
|
||||
t.Fatalf("error exec'ing stat: %v", err)
|
||||
}
|
||||
|
||||
@@ -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,51 @@ 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()
|
||||
return ExecScript(ctx, e.cmd.Dir, e.ctx.TaskEnv, e.cmd.SysProcAttr, name, args)
|
||||
}
|
||||
|
||||
// ExecScript executes cmd with args and returns the output, exit code, and
|
||||
// error. Output is truncated to client/driver/structs.CheckBufSize
|
||||
func ExecScript(ctx context.Context, dir string, env *env.TaskEnvironment, attrs *syscall.SysProcAttr,
|
||||
name string, args []string) ([]byte, int, error) {
|
||||
name = env.ReplaceEnv(name)
|
||||
cmd := exec.CommandContext(ctx, name, env.ParseAndReplace(args)...)
|
||||
|
||||
// Copy runtime environment from the main command
|
||||
cmd.SysProcAttr = attrs
|
||||
cmd.Dir = dir
|
||||
cmd.Env = env.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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/env"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
dstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
@@ -48,6 +50,8 @@ type rawExecHandle struct {
|
||||
logger *log.Logger
|
||||
waitCh chan *dstructs.WaitResult
|
||||
doneCh chan struct{}
|
||||
taskEnv *env.TaskEnvironment
|
||||
taskDir *allocdir.TaskDir
|
||||
}
|
||||
|
||||
// NewRawExecDriver is used to create a new raw exec driver
|
||||
@@ -165,6 +169,8 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl
|
||||
logger: d.logger,
|
||||
doneCh: make(chan struct{}),
|
||||
waitCh: make(chan *dstructs.WaitResult, 1),
|
||||
taskEnv: d.taskEnv,
|
||||
taskDir: ctx.TaskDir,
|
||||
}
|
||||
go h.run()
|
||||
return h, nil
|
||||
@@ -212,6 +218,8 @@ func (d *RawExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, e
|
||||
version: id.Version,
|
||||
doneCh: make(chan struct{}),
|
||||
waitCh: make(chan *dstructs.WaitResult, 1),
|
||||
taskEnv: d.taskEnv,
|
||||
taskDir: ctx.TaskDir,
|
||||
}
|
||||
go h.run()
|
||||
return h, nil
|
||||
@@ -247,7 +255,7 @@ func (h *rawExecHandle) Update(task *structs.Task) error {
|
||||
}
|
||||
|
||||
func (h *rawExecHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte, int, error) {
|
||||
return execChroot(ctx, "", cmd, args)
|
||||
return executor.ExecScript(ctx, h.taskDir.Dir, h.taskEnv, nil, cmd, args)
|
||||
}
|
||||
|
||||
func (h *rawExecHandle) Signal(s os.Signal) error {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/env"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
dstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
cstructs "github.com/hashicorp/nomad/client/structs"
|
||||
@@ -87,6 +88,8 @@ type RktDriverConfig struct {
|
||||
// rktHandle is returned from Start/Open as a handle to the PID
|
||||
type rktHandle struct {
|
||||
uuid string
|
||||
env *env.TaskEnvironment
|
||||
taskDir *allocdir.TaskDir
|
||||
pluginClient *plugin.Client
|
||||
executorPid int
|
||||
executor executor.Executor
|
||||
@@ -474,6 +477,8 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e
|
||||
maxKill := d.DriverContext.config.MaxKillTimeout
|
||||
h := &rktHandle{
|
||||
uuid: uuid,
|
||||
env: d.taskEnv,
|
||||
taskDir: ctx.TaskDir,
|
||||
pluginClient: pluginClient,
|
||||
executor: execIntf,
|
||||
executorPid: ps.Pid,
|
||||
@@ -514,6 +519,8 @@ func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error
|
||||
// Return a driver handle
|
||||
h := &rktHandle{
|
||||
uuid: id.UUID,
|
||||
env: d.taskEnv,
|
||||
taskDir: ctx.TaskDir,
|
||||
pluginClient: pluginClient,
|
||||
executorPid: id.ExecutorPid,
|
||||
executor: exec,
|
||||
@@ -566,7 +573,7 @@ func (h *rktHandle) Exec(ctx context.Context, cmd string, args []string) ([]byte
|
||||
enterArgs[1] = h.uuid
|
||||
enterArgs[2] = cmd
|
||||
copy(enterArgs[3:], args)
|
||||
return execChroot(ctx, "", rktCmd, enterArgs)
|
||||
return executor.ExecScript(ctx, h.taskDir.Dir, h.env, nil, rktCmd, enterArgs)
|
||||
}
|
||||
|
||||
func (h *rktHandle) Signal(s os.Signal) error {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
func TestRktVersionRegex(t *testing.T) {
|
||||
if os.Getenv("NOMAD_TEST_RKT") == "" {
|
||||
t.Skip("skipping rkt tests")
|
||||
t.Skip("NOMAD_TEST_RKT unset, skipping")
|
||||
}
|
||||
|
||||
input_rkt := "rkt version 0.8.1"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -9,10 +8,8 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/armon/circbuf"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
@@ -181,36 +178,3 @@ func getExecutorUser(task *structs.Task) string {
|
||||
}
|
||||
return task.User
|
||||
}
|
||||
|
||||
// execChroot executes cmd with args inside chroot if set and returns the
|
||||
// output, exit code, and error. If chroot is an empty string the command is
|
||||
// executed on the host.
|
||||
func execChroot(ctx context.Context, chroot, name string, args []string) ([]byte, int, error) {
|
||||
buf, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize))
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Dir = "/"
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = buf
|
||||
if chroot != "" {
|
||||
setChroot(cmd, chroot)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/golang/snappy"
|
||||
"github.com/hashicorp/consul-template/signals"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
version "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver"
|
||||
@@ -234,17 +235,20 @@ func (r *TaskRunner) stateFilePath() string {
|
||||
return path
|
||||
}
|
||||
|
||||
// RestoreState is used to restore our state
|
||||
func (r *TaskRunner) RestoreState() error {
|
||||
// RestoreState is used to restore our state. If a non-empty string is returned
|
||||
// the task is restarted with the string as the reason. This is useful for
|
||||
// backwards incompatible upgrades that need to restart tasks with a new
|
||||
// executor.
|
||||
func (r *TaskRunner) RestoreState() (string, error) {
|
||||
// Load the snapshot
|
||||
var snap taskRunnerState
|
||||
if err := restoreState(r.stateFilePath(), &snap); err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Restore fields
|
||||
if snap.Task == nil {
|
||||
return fmt.Errorf("task runner snapshot includes nil Task")
|
||||
return "", fmt.Errorf("task runner snapshot includes nil Task")
|
||||
} else {
|
||||
r.task = snap.Task
|
||||
}
|
||||
@@ -255,7 +259,7 @@ func (r *TaskRunner) RestoreState() error {
|
||||
r.setCreatedResources(snap.CreatedResources)
|
||||
|
||||
if err := r.setTaskEnv(); err != nil {
|
||||
return fmt.Errorf("client: failed to create task environment for task %q in allocation %q: %v",
|
||||
return "", fmt.Errorf("client: failed to create task environment for task %q in allocation %q: %v",
|
||||
r.task.Name, r.alloc.ID, err)
|
||||
}
|
||||
|
||||
@@ -265,7 +269,7 @@ func (r *TaskRunner) RestoreState() error {
|
||||
data, err := ioutil.ReadFile(tokenPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to read token for task %q in alloc %q: %v", r.task.Name, r.alloc.ID, err)
|
||||
return "", fmt.Errorf("failed to read token for task %q in alloc %q: %v", r.task.Name, r.alloc.ID, err)
|
||||
}
|
||||
|
||||
// Token file doesn't exist
|
||||
@@ -276,10 +280,11 @@ func (r *TaskRunner) RestoreState() error {
|
||||
}
|
||||
|
||||
// Restore the driver
|
||||
restartReason := ""
|
||||
if snap.HandleID != "" {
|
||||
d, err := r.createDriver()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
ctx := driver.NewExecContext(r.taskDir)
|
||||
@@ -289,7 +294,11 @@ func (r *TaskRunner) RestoreState() error {
|
||||
if err != nil {
|
||||
r.logger.Printf("[ERR] client: failed to open handle to task %q for alloc %q: %v",
|
||||
r.task.Name, r.alloc.ID, err)
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if pre06ScriptCheck(snap.Version, r.task.Driver, r.task.Services) {
|
||||
restartReason = pre06ScriptCheckReason
|
||||
}
|
||||
|
||||
if err := r.registerServices(d, handle); err != nil {
|
||||
@@ -308,7 +317,40 @@ func (r *TaskRunner) RestoreState() error {
|
||||
r.running = true
|
||||
r.runningLock.Unlock()
|
||||
}
|
||||
return nil
|
||||
return restartReason, nil
|
||||
}
|
||||
|
||||
// ver06 is used for checking for pre-0.6 script checks
|
||||
var ver06 = version.Must(version.NewVersion("0.6.0dev"))
|
||||
|
||||
// pre06ScriptCheckReason is the restart reason given when a pre-0.6 script
|
||||
// check is found on an exec/java task.
|
||||
const pre06ScriptCheckReason = "upgrading pre-0.6 script checks"
|
||||
|
||||
// pre06ScriptCheck returns true if version is prior to 0.6.0dev, has a script
|
||||
// check, and uses exec or java drivers.
|
||||
func pre06ScriptCheck(ver, driver string, services []*structs.Service) bool {
|
||||
if driver != "exec" && driver != "java" && driver != "mock_driver" {
|
||||
// Only exec and java are affected
|
||||
return false
|
||||
}
|
||||
v, err := version.NewVersion(ver)
|
||||
if err != nil {
|
||||
// Treat it as old
|
||||
return true
|
||||
}
|
||||
if !v.LessThan(ver06) {
|
||||
// >= 0.6.0dev
|
||||
return false
|
||||
}
|
||||
for _, service := range services {
|
||||
for _, check := range service.Checks {
|
||||
if check.Type == "script" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveState is used to snapshot our state
|
||||
|
||||
@@ -370,7 +370,7 @@ func TestTaskRunner_SaveRestoreState(t *testing.T) {
|
||||
tr2 := NewTaskRunner(ctx.tr.logger, ctx.tr.config, ctx.upd.Update,
|
||||
ctx.tr.taskDir, ctx.tr.alloc, task2, ctx.tr.vaultClient, ctx.tr.consul)
|
||||
tr2.restartTracker = noRestartsTracker()
|
||||
if err := tr2.RestoreState(); err != nil {
|
||||
if _, err := tr2.RestoreState(); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
go tr2.Run()
|
||||
@@ -1521,3 +1521,49 @@ func TestTaskRunner_CleanupFail(t *testing.T) {
|
||||
t.Fatalf("expected %#v but found: %#v", expected, ctx.tr.createdResources.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskRunner_Pre06ScriptCheck(t *testing.T) {
|
||||
run := func(ver, driver, checkType string, exp bool) (string, func(t *testing.T)) {
|
||||
name := fmt.Sprintf("%s %s %s returns %t", ver, driver, checkType, exp)
|
||||
return name, func(t *testing.T) {
|
||||
services := []*structs.Service{
|
||||
{
|
||||
Checks: []*structs.ServiceCheck{
|
||||
{
|
||||
Type: checkType,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if act := pre06ScriptCheck(ver, driver, services); act != exp {
|
||||
t.Errorf("expected %t received %t", exp, act)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Run(run("0.5.6", "exec", "script", true))
|
||||
t.Run(run("0.5.6", "java", "script", true))
|
||||
t.Run(run("0.5.6", "mock_driver", "script", true))
|
||||
t.Run(run("0.5.9", "exec", "script", true))
|
||||
t.Run(run("0.5.9", "java", "script", true))
|
||||
t.Run(run("0.5.9", "mock_driver", "script", true))
|
||||
|
||||
t.Run(run("0.6.0dev", "exec", "script", false))
|
||||
t.Run(run("0.6.0dev", "java", "script", false))
|
||||
t.Run(run("0.6.0dev", "mock_driver", "script", false))
|
||||
t.Run(run("0.6.0", "exec", "script", false))
|
||||
t.Run(run("0.6.0", "java", "script", false))
|
||||
t.Run(run("0.6.0", "mock_driver", "script", false))
|
||||
t.Run(run("1.0.0", "exec", "script", false))
|
||||
t.Run(run("1.0.0", "java", "script", false))
|
||||
t.Run(run("1.0.0", "mock_driver", "script", false))
|
||||
|
||||
t.Run(run("0.5.6", "rkt", "script", false))
|
||||
t.Run(run("0.5.6", "docker", "script", false))
|
||||
t.Run(run("0.5.6", "qemu", "script", false))
|
||||
t.Run(run("0.5.6", "raw_exec", "script", false))
|
||||
t.Run(run("0.5.6", "invalid", "script", false))
|
||||
|
||||
t.Run(run("0.5.6", "exec", "tcp", false))
|
||||
t.Run(run("0.5.6", "java", "tcp", false))
|
||||
t.Run(run("0.5.6", "mock_driver", "tcp", false))
|
||||
}
|
||||
|
||||
11
vendor/github.com/hashicorp/go-version/.travis.yml
generated
vendored
11
vendor/github.com/hashicorp/go-version/.travis.yml
generated
vendored
@@ -1,11 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
|
||||
script:
|
||||
- go test
|
||||
2
vendor/github.com/hashicorp/go-version/README.md
generated
vendored
2
vendor/github.com/hashicorp/go-version/README.md
generated
vendored
@@ -1,5 +1,5 @@
|
||||
# Versioning Library for Go
|
||||
[](https://travis-ci.org/hashicorp/go-version)
|
||||
[](https://travis-ci.org/hashicorp/go-version)
|
||||
|
||||
go-version is a library for parsing versions and version constraints,
|
||||
and verifying versions against a set of constraints. go-version
|
||||
|
||||
24
vendor/github.com/hashicorp/go-version/constraint.go
generated
vendored
24
vendor/github.com/hashicorp/go-version/constraint.go
generated
vendored
@@ -37,7 +37,7 @@ func init() {
|
||||
}
|
||||
|
||||
ops := make([]string, 0, len(constraintOperators))
|
||||
for k, _ := range constraintOperators {
|
||||
for k := range constraintOperators {
|
||||
ops = append(ops, regexp.QuoteMeta(k))
|
||||
}
|
||||
|
||||
@@ -142,15 +142,37 @@ func constraintLessThanEqual(v, c *Version) bool {
|
||||
}
|
||||
|
||||
func constraintPessimistic(v, c *Version) bool {
|
||||
// If the version being checked is naturally less than the constraint, then there
|
||||
// is no way for the version to be valid against the constraint
|
||||
if v.LessThan(c) {
|
||||
return false
|
||||
}
|
||||
// We'll use this more than once, so grab the length now so it's a little cleaner
|
||||
// to write the later checks
|
||||
cs := len(c.segments)
|
||||
|
||||
// If the version being checked has less specificity than the constraint, then there
|
||||
// is no way for the version to be valid against the constraint
|
||||
if cs > len(v.segments) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check the segments in the constraint against those in the version. If the version
|
||||
// being checked, at any point, does not have the same values in each index of the
|
||||
// constraints segments, then it cannot be valid against the constraint.
|
||||
for i := 0; i < c.si-1; i++ {
|
||||
if v.segments[i] != c.segments[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check the last part of the segment in the constraint. If the version segment at
|
||||
// this index is less than the constraints segment at this index, then it cannot
|
||||
// be valid against the constraint
|
||||
if c.segments[cs-1] > v.segments[cs-1] {
|
||||
return false
|
||||
}
|
||||
|
||||
// If nothing has rejected the version by now, it's valid
|
||||
return true
|
||||
}
|
||||
|
||||
115
vendor/github.com/hashicorp/go-version/version.go
generated
vendored
115
vendor/github.com/hashicorp/go-version/version.go
generated
vendored
@@ -14,8 +14,8 @@ var versionRegexp *regexp.Regexp
|
||||
|
||||
// The raw regular expression string used for testing the validity
|
||||
// of a version.
|
||||
const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+){0,2})` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
|
||||
`(-?([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`?`
|
||||
|
||||
@@ -23,7 +23,7 @@ const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+){0,2})` +
|
||||
type Version struct {
|
||||
metadata string
|
||||
pre string
|
||||
segments []int
|
||||
segments []int64
|
||||
si int
|
||||
}
|
||||
|
||||
@@ -38,20 +38,23 @@ func NewVersion(v string) (*Version, error) {
|
||||
if matches == nil {
|
||||
return nil, fmt.Errorf("Malformed version: %s", v)
|
||||
}
|
||||
|
||||
segmentsStr := strings.Split(matches[1], ".")
|
||||
segments := make([]int, len(segmentsStr), 3)
|
||||
segments := make([]int64, len(segmentsStr))
|
||||
si := 0
|
||||
for i, str := range segmentsStr {
|
||||
val, err := strconv.ParseInt(str, 10, 32)
|
||||
val, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
segments[i] = int(val)
|
||||
si += 1
|
||||
segments[i] = int64(val)
|
||||
si++
|
||||
}
|
||||
|
||||
// Even though we could support more than three segments, if we
|
||||
// got less than three, pad it with 0s. This is to cover the basic
|
||||
// default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum
|
||||
for i := len(segments); i < 3; i++ {
|
||||
segments = append(segments, 0)
|
||||
}
|
||||
@@ -86,8 +89,8 @@ func (v *Version) Compare(other *Version) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
segmentsSelf := v.Segments()
|
||||
segmentsOther := other.Segments()
|
||||
segmentsSelf := v.Segments64()
|
||||
segmentsOther := other.Segments64()
|
||||
|
||||
// If the segments are the same, we must compare on prerelease info
|
||||
if reflect.DeepEqual(segmentsSelf, segmentsOther) {
|
||||
@@ -106,21 +109,56 @@ func (v *Version) Compare(other *Version) int {
|
||||
return comparePrereleases(preSelf, preOther)
|
||||
}
|
||||
|
||||
// Get the highest specificity (hS), or if they're equal, just use segmentSelf length
|
||||
lenSelf := len(segmentsSelf)
|
||||
lenOther := len(segmentsOther)
|
||||
hS := lenSelf
|
||||
if lenSelf < lenOther {
|
||||
hS = lenOther
|
||||
}
|
||||
// Compare the segments
|
||||
for i := 0; i < len(segmentsSelf); i++ {
|
||||
// Because a constraint could have more/less specificity than the version it's
|
||||
// checking, we need to account for a lopsided or jagged comparison
|
||||
for i := 0; i < hS; i++ {
|
||||
if i > lenSelf-1 {
|
||||
// This means Self had the lower specificity
|
||||
// Check to see if the remaining segments in Other are all zeros
|
||||
if !allZero(segmentsOther[i:]) {
|
||||
// if not, it means that Other has to be greater than Self
|
||||
return -1
|
||||
}
|
||||
break
|
||||
} else if i > lenOther-1 {
|
||||
// this means Other had the lower specificity
|
||||
// Check to see if the remaining segments in Self are all zeros -
|
||||
if !allZero(segmentsSelf[i:]) {
|
||||
//if not, it means that Self has to be greater than Other
|
||||
return 1
|
||||
}
|
||||
break
|
||||
}
|
||||
lhs := segmentsSelf[i]
|
||||
rhs := segmentsOther[i]
|
||||
|
||||
if lhs == rhs {
|
||||
continue
|
||||
} else if lhs < rhs {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
// Otherwis, rhs was > lhs, they're not equal
|
||||
return 1
|
||||
}
|
||||
|
||||
panic("should not be reached")
|
||||
// if we got this far, they're equal
|
||||
return 0
|
||||
}
|
||||
|
||||
func allZero(segs []int64) bool {
|
||||
for _, s := range segs {
|
||||
if s != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func comparePart(preSelf string, preOther string) int {
|
||||
@@ -128,24 +166,38 @@ func comparePart(preSelf string, preOther string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
selfNumeric := true
|
||||
_, err := strconv.ParseInt(preSelf, 10, 64)
|
||||
if err != nil {
|
||||
selfNumeric = false
|
||||
}
|
||||
|
||||
otherNumeric := true
|
||||
_, err = strconv.ParseInt(preOther, 10, 64)
|
||||
if err != nil {
|
||||
otherNumeric = false
|
||||
}
|
||||
|
||||
// if a part is empty, we use the other to decide
|
||||
if preSelf == "" {
|
||||
_, notIsNumeric := strconv.ParseInt(preOther, 10, 64)
|
||||
if notIsNumeric == nil {
|
||||
if otherNumeric {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if preOther == "" {
|
||||
_, notIsNumeric := strconv.ParseInt(preSelf, 10, 64)
|
||||
if notIsNumeric == nil {
|
||||
if selfNumeric {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
if preSelf > preOther {
|
||||
if selfNumeric && !otherNumeric {
|
||||
return -1
|
||||
} else if !selfNumeric && otherNumeric {
|
||||
return 1
|
||||
} else if preSelf > preOther {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -226,12 +278,25 @@ func (v *Version) Prerelease() string {
|
||||
return v.pre
|
||||
}
|
||||
|
||||
// Segments returns the numeric segments of the version as a slice.
|
||||
// Segments returns the numeric segments of the version as a slice of ints.
|
||||
//
|
||||
// This excludes any metadata or pre-release information. For example,
|
||||
// for a version "1.2.3-beta", segments will return a slice of
|
||||
// 1, 2, 3.
|
||||
func (v *Version) Segments() []int {
|
||||
segmentSlice := make([]int, len(v.segments))
|
||||
for i, v := range v.segments {
|
||||
segmentSlice[i] = int(v)
|
||||
}
|
||||
return segmentSlice
|
||||
}
|
||||
|
||||
// Segments64 returns the numeric segments of the version as a slice of int64s.
|
||||
//
|
||||
// This excludes any metadata or pre-release information. For example,
|
||||
// for a version "1.2.3-beta", segments will return a slice of
|
||||
// 1, 2, 3.
|
||||
func (v *Version) Segments64() []int64 {
|
||||
return v.segments
|
||||
}
|
||||
|
||||
@@ -239,7 +304,13 @@ func (v *Version) Segments() []int {
|
||||
// and metadata information.
|
||||
func (v *Version) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", v.segments[0], v.segments[1], v.segments[2])
|
||||
fmtParts := make([]string, len(v.segments))
|
||||
for i, s := range v.segments {
|
||||
// We can ignore err here since we've pre-parsed the values in segments
|
||||
str := strconv.FormatInt(s, 10)
|
||||
fmtParts[i] = str
|
||||
}
|
||||
fmt.Fprintf(&buf, strings.Join(fmtParts, "."))
|
||||
if v.pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", v.pre)
|
||||
}
|
||||
|
||||
4
vendor/vendor.json
vendored
4
vendor/vendor.json
vendored
@@ -743,8 +743,10 @@
|
||||
"revision": "42a2b573b664dbf281bd48c3cc12c086b17a39ba"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "tUGxc7rfX0cmhOOUDhMuAZ9rWsA=",
|
||||
"path": "github.com/hashicorp/go-version",
|
||||
"revision": "2e7f5ea8e27bb3fdf9baa0881d16757ac4637332"
|
||||
"revision": "03c5bf6be031b6dd45afec16b1cf94fc8938bc77",
|
||||
"revisionTime": "2017-02-02T08:07:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "d9PxF1XQGLMJZRct2R8qVM/eYlE=",
|
||||
|
||||
Reference in New Issue
Block a user