From 967497097a889300807af3e56e37b5fc75e942de Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 24 Aug 2017 16:27:35 -0400 Subject: [PATCH] Add procesStates for shim processes Use the state pattern to handle process transitions from one state to another and what actions can be performed on a process in a specific state. Signed-off-by: Michael Crosby --- linux/shim/deleted_state.go | 50 +++++ linux/shim/exec.go | 23 ++- linux/shim/exec_state.go | 166 +++++++++++++++++ linux/shim/init.go | 101 ++-------- linux/shim/init_state.go | 356 ++++++++++++++++++++++++++++++++++++ linux/shim/process.go | 42 ++++- linux/shim/utils.go | 86 +++++++++ 7 files changed, 716 insertions(+), 108 deletions(-) create mode 100644 linux/shim/deleted_state.go create mode 100644 linux/shim/exec_state.go create mode 100644 linux/shim/init_state.go create mode 100644 linux/shim/utils.go diff --git a/linux/shim/deleted_state.go b/linux/shim/deleted_state.go new file mode 100644 index 000000000..6d2273501 --- /dev/null +++ b/linux/shim/deleted_state.go @@ -0,0 +1,50 @@ +// +build !windows + +package shim + +import ( + "context" + + "github.com/containerd/console" + shimapi "github.com/containerd/containerd/linux/shim/v1" + "github.com/pkg/errors" +) + +type deletedState struct { +} + +func (s *deletedState) Pause(ctx context.Context) error { + return errors.Errorf("cannot pause a deleted process") +} + +func (s *deletedState) Resume(ctx context.Context) error { + return errors.Errorf("cannot resume a deleted process") +} + +func (s *deletedState) Update(context context.Context, r *shimapi.UpdateTaskRequest) error { + return errors.Errorf("cannot update a deleted process") +} + +func (s *deletedState) Checkpoint(ctx context.Context, r *shimapi.CheckpointTaskRequest) error { + return errors.Errorf("cannot checkpoint a deleted process") +} + +func (s *deletedState) Resize(ws console.WinSize) error { + return errors.Errorf("cannot resize a deleted process") +} + +func (s *deletedState) Start(ctx context.Context) error { + return errors.Errorf("cannot start a deleted process") +} + +func (s *deletedState) Delete(ctx context.Context) error { + return errors.Errorf("cannot delete a deleted process") +} + +func (s *deletedState) Kill(ctx context.Context, sig uint32, all bool) error { + return errors.Errorf("cannot kill a deleted process") +} + +func (s *deletedState) SetExited(status int) { + // no op +} diff --git a/linux/shim/exec.go b/linux/shim/exec.go index 75d4a3221..483e6bc73 100644 --- a/linux/shim/exec.go +++ b/linux/shim/exec.go @@ -26,6 +26,8 @@ import ( type execProcess struct { sync.WaitGroup + processState + mu sync.Mutex id string console console.Console @@ -65,6 +67,7 @@ func newExecProcess(context context.Context, path string, r *shimapi.ExecProcess terminal: r.Terminal, }, } + e.processState = &execCreatedState{p: e} return e, nil } @@ -79,14 +82,18 @@ func (e *execProcess) Pid() int { } func (e *execProcess) ExitStatus() int { + e.mu.Lock() + defer e.mu.Unlock() return e.status } func (e *execProcess) ExitedAt() time.Time { + e.mu.Lock() + defer e.mu.Unlock() return e.exited } -func (e *execProcess) SetExited(status int) { +func (e *execProcess) setExited(status int) { e.status = status e.exited = time.Now() e.parent.platform.shutdownConsole(context.Background(), e.console) @@ -99,21 +106,15 @@ func (e *execProcess) SetExited(status int) { } } -func (e *execProcess) Delete(ctx context.Context) error { - return nil -} - -func (e *execProcess) Resize(ws console.WinSize) error { +func (e *execProcess) resize(ws console.WinSize) error { if e.console == nil { return nil } return e.console.Resize(ws) } -func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error { - e.mu.Lock() +func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error { pid := e.pid - e.mu.Unlock() if pid != 0 { if err := unix.Kill(pid, syscall.Signal(sig)); err != nil { return errors.Wrapf(checkKillError(err), "exec kill error") @@ -130,7 +131,7 @@ func (e *execProcess) Stdio() stdio { return e.stdio } -func (e *execProcess) Start(ctx context.Context) (err error) { +func (e *execProcess) start(ctx context.Context) (err error) { var ( socket *runc.Socket pidfile = filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)) @@ -187,9 +188,7 @@ func (e *execProcess) Start(ctx context.Context) (err error) { if err != nil { return errors.Wrap(err, "failed to retrieve OCI runtime exec pid") } - e.mu.Lock() e.pid = pid - e.mu.Unlock() return nil } diff --git a/linux/shim/exec_state.go b/linux/shim/exec_state.go new file mode 100644 index 000000000..65d3ab074 --- /dev/null +++ b/linux/shim/exec_state.go @@ -0,0 +1,166 @@ +// +build !windows + +package shim + +import ( + "context" + + "github.com/containerd/console" + "github.com/pkg/errors" +) + +type execCreatedState struct { + p *execProcess +} + +func (s *execCreatedState) transition(name string) error { + switch name { + case "running": + s.p.processState = &execRunningState{p: s.p} + case "stopped": + s.p.processState = &execStoppedState{p: s.p} + case "deleted": + s.p.processState = &deletedState{} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *execCreatedState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.resize(ws) +} + +func (s *execCreatedState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + if err := s.p.start(ctx); err != nil { + return err + } + return s.transition("running") +} + +func (s *execCreatedState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + return s.transition("deleted") +} + +func (s *execCreatedState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *execCreatedState) SetExited(status int) { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + s.p.setExited(status) + + if err := s.transition("stopped"); err != nil { + panic(err) + } +} + +type execRunningState struct { + p *execProcess +} + +func (s *execRunningState) transition(name string) error { + switch name { + case "stopped": + s.p.processState = &execStoppedState{p: s.p} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *execRunningState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.resize(ws) +} + +func (s *execRunningState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot start a running process") +} + +func (s *execRunningState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot delete a running process") +} + +func (s *execRunningState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *execRunningState) SetExited(status int) { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + s.p.setExited(status) + + if err := s.transition("stopped"); err != nil { + panic(err) + } +} + +type execStoppedState struct { + p *execProcess +} + +func (s *execStoppedState) transition(name string) error { + switch name { + case "deleted": + s.p.processState = &deletedState{} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *execStoppedState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot resize a stopped container") +} + +func (s *execStoppedState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot start a stopped process") +} + +func (s *execStoppedState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + return s.transition("deleted") +} + +func (s *execStoppedState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *execStoppedState) SetExited(status int) { + // no op +} diff --git a/linux/shim/init.go b/linux/shim/init.go index b52cfe02f..94f33718b 100644 --- a/linux/shim/init.go +++ b/linux/shim/init.go @@ -14,10 +14,7 @@ import ( "syscall" "time" - "golang.org/x/sys/unix" - "github.com/containerd/console" - "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/linux/runcopts" shimapi "github.com/containerd/containerd/linux/shim/v1" @@ -32,6 +29,7 @@ import ( type initProcess struct { sync.WaitGroup + initState // mu is used to ensure that `Start()` and `Exited()` calls return in // the right order when invoked in separate go routines. @@ -114,6 +112,7 @@ func newInitProcess(context context.Context, plat platform, path, namespace, wor rootfs: rootfs, workDir: workDir, } + p.initState = &createdState{p: p} var ( err error socket *runc.Socket @@ -207,14 +206,17 @@ func (p *initProcess) Pid() int { } func (p *initProcess) ExitStatus() int { + p.mu.Lock() + defer p.mu.Unlock() return p.status } func (p *initProcess) ExitedAt() time.Time { + p.mu.Lock() + defer p.mu.Unlock() return p.exited } -// Status return the state of the container (created, running, paused, stopped) func (p *initProcess) Status(ctx context.Context) (string, error) { c, err := p.runtime.State(ctx, p.id) if err != nil { @@ -223,22 +225,18 @@ func (p *initProcess) Status(ctx context.Context) (string, error) { return c.Status, nil } -func (p *initProcess) Start(context context.Context) error { - p.mu.Lock() - defer p.mu.Unlock() +func (p *initProcess) start(context context.Context) error { err := p.runtime.Start(context, p.id) return p.runtimeError(err, "OCI runtime start failed") } -func (p *initProcess) SetExited(status int) { - p.mu.Lock() +func (p *initProcess) setExited(status int) { p.status = status p.exited = time.Now() p.platform.shutdownConsole(context.Background(), p.console) - p.mu.Unlock() } -func (p *initProcess) Delete(context context.Context) error { +func (p *initProcess) delete(context context.Context) error { p.killAll(context) p.Wait() err := p.runtime.Delete(context, p.id, nil) @@ -269,24 +267,24 @@ func (p *initProcess) Delete(context context.Context) error { return err } -func (p *initProcess) Resize(ws console.WinSize) error { +func (p *initProcess) resize(ws console.WinSize) error { if p.console == nil { return nil } return p.console.Resize(ws) } -func (p *initProcess) Pause(context context.Context) error { +func (p *initProcess) pause(context context.Context) error { err := p.runtime.Pause(context, p.id) return p.runtimeError(err, "OCI runtime pause failed") } -func (p *initProcess) Resume(context context.Context) error { +func (p *initProcess) resume(context context.Context) error { err := p.runtime.Resume(context, p.id) return p.runtimeError(err, "OCI runtime resume failed") } -func (p *initProcess) Kill(context context.Context, signal uint32, all bool) error { +func (p *initProcess) kill(context context.Context, signal uint32, all bool) error { err := p.runtime.Kill(context, p.id, int(signal), &runc.KillOpts{ All: all, }) @@ -304,7 +302,7 @@ func (p *initProcess) Stdin() io.Closer { return p.stdin } -func (p *initProcess) Checkpoint(context context.Context, r *shimapi.CheckpointTaskRequest) error { +func (p *initProcess) checkpoint(context context.Context, r *shimapi.CheckpointTaskRequest) error { var options runcopts.CheckpointOptions if r.Options != nil { v, err := typeurl.UnmarshalAny(r.Options) @@ -337,7 +335,7 @@ func (p *initProcess) Checkpoint(context context.Context, r *shimapi.CheckpointT return nil } -func (p *initProcess) Update(context context.Context, r *shimapi.UpdateTaskRequest) error { +func (p *initProcess) update(context context.Context, r *shimapi.UpdateTaskRequest) error { var resources specs.LinuxResources if err := json.Unmarshal(r.Resources.Value, &resources); err != nil { return err @@ -349,39 +347,6 @@ func (p *initProcess) Stdio() stdio { return p.stdio } -// TODO(mlaventure): move to runc package? -func getLastRuntimeError(r *runc.Runc) (string, error) { - if r.Log == "" { - return "", nil - } - - f, err := os.OpenFile(r.Log, os.O_RDONLY, 0400) - if err != nil { - return "", err - } - - var ( - errMsg string - log struct { - Level string - Msg string - Time time.Time - } - ) - - dec := json.NewDecoder(f) - for err = nil; err == nil; { - if err = dec.Decode(&log); err != nil && err != io.EOF { - return "", err - } - if log.Level == "error" { - errMsg = strings.TrimSpace(log.Msg) - } - } - - return errMsg, nil -} - func (p *initProcess) runtimeError(rErr error, msg string) error { if rErr == nil { return nil @@ -397,39 +362,3 @@ func (p *initProcess) runtimeError(rErr error, msg string) error { return errors.Errorf("%s: %s", msg, rMsg) } } - -// criuError returns only the first line of the error message from criu -// it tries to add an invalid dump log location when returning the message -func criuError(err error) string { - parts := strings.Split(err.Error(), "\n") - return parts[0] -} - -func copyFile(to, from string) error { - ff, err := os.Open(from) - if err != nil { - return err - } - defer ff.Close() - tt, err := os.Create(to) - if err != nil { - return err - } - defer tt.Close() - _, err = io.Copy(tt, ff) - return err -} - -func checkKillError(err error) error { - if err == nil { - return nil - } - if strings.Contains(err.Error(), "os: process already finished") || err == unix.ESRCH { - return errors.Wrapf(errdefs.ErrNotFound, "process already finished") - } - return errors.Wrapf(err, "unknown error after kill") -} - -func hasNoIO(r *shimapi.CreateTaskRequest) bool { - return r.Stdin == "" && r.Stdout == "" && r.Stderr == "" -} diff --git a/linux/shim/init_state.go b/linux/shim/init_state.go new file mode 100644 index 000000000..aa0456a16 --- /dev/null +++ b/linux/shim/init_state.go @@ -0,0 +1,356 @@ +// +build !windows + +package shim + +import ( + "context" + + "github.com/containerd/console" + shimapi "github.com/containerd/containerd/linux/shim/v1" + "github.com/pkg/errors" +) + +type initState interface { + processState + + Pause(context.Context) error + Resume(context.Context) error + Update(context.Context, *shimapi.UpdateTaskRequest) error + Checkpoint(context.Context, *shimapi.CheckpointTaskRequest) error +} + +type createdState struct { + p *initProcess +} + +func (s *createdState) transition(name string) error { + switch name { + case "running": + s.p.initState = &runningState{p: s.p} + case "stopped": + s.p.initState = &stoppedState{p: s.p} + case "deleted": + s.p.initState = &deletedState{} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *createdState) Pause(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot pause task in created state") +} + +func (s *createdState) Resume(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot resume task in created state") +} + +func (s *createdState) Update(context context.Context, r *shimapi.UpdateTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.update(context, r) +} + +func (s *createdState) Checkpoint(context context.Context, r *shimapi.CheckpointTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot checkpoint a task in created state") +} + +func (s *createdState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.resize(ws) +} + +func (s *createdState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + if err := s.p.start(ctx); err != nil { + return err + } + return s.transition("running") +} + +func (s *createdState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + if err := s.p.delete(ctx); err != nil { + return err + } + return s.transition("deleted") +} + +func (s *createdState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *createdState) SetExited(status int) { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + s.p.setExited(status) + + if err := s.transition("stopped"); err != nil { + panic(err) + } +} + +type runningState struct { + p *initProcess +} + +func (s *runningState) transition(name string) error { + switch name { + case "stopped": + s.p.initState = &stoppedState{p: s.p} + case "paused": + s.p.initState = &pausedState{p: s.p} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *runningState) Pause(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + if err := s.p.pause(ctx); err != nil { + return err + } + return s.transition("paused") +} + +func (s *runningState) Resume(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot resume a running process") +} + +func (s *runningState) Update(context context.Context, r *shimapi.UpdateTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.update(context, r) +} + +func (s *runningState) Checkpoint(ctx context.Context, r *shimapi.CheckpointTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.checkpoint(ctx, r) +} + +func (s *runningState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.resize(ws) +} + +func (s *runningState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot start a running process") +} + +func (s *runningState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot delete a running process") +} + +func (s *runningState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *runningState) SetExited(status int) { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + s.p.setExited(status) + + if err := s.transition("stopped"); err != nil { + panic(err) + } +} + +type pausedState struct { + p *initProcess +} + +func (s *pausedState) transition(name string) error { + switch name { + case "running": + s.p.initState = &runningState{p: s.p} + case "stopped": + s.p.initState = &stoppedState{p: s.p} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *pausedState) Pause(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot pause a paused container") +} + +func (s *pausedState) Resume(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + if err := s.p.resume(ctx); err != nil { + return err + } + return s.transition("running") +} + +func (s *pausedState) Update(context context.Context, r *shimapi.UpdateTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.update(context, r) +} + +func (s *pausedState) Checkpoint(ctx context.Context, r *shimapi.CheckpointTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.checkpoint(ctx, r) +} + +func (s *pausedState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.resize(ws) +} + +func (s *pausedState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot start a paused process") +} + +func (s *pausedState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot delete a paused process") +} + +func (s *pausedState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *pausedState) SetExited(status int) { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + s.p.setExited(status) + + if err := s.transition("stopped"); err != nil { + panic(err) + } +} + +type stoppedState struct { + p *initProcess +} + +func (s *stoppedState) transition(name string) error { + switch name { + case "deleted": + s.p.initState = &deletedState{} + default: + return errors.Errorf("invalid state transition %q to %q", stateName(s), name) + } + return nil +} + +func (s *stoppedState) Pause(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot pause a stopped container") +} + +func (s *stoppedState) Resume(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot resume a stopped container") +} + +func (s *stoppedState) Update(context context.Context, r *shimapi.UpdateTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot update a stopped container") +} + +func (s *stoppedState) Checkpoint(ctx context.Context, r *shimapi.CheckpointTaskRequest) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot checkpoint a stopped container") +} + +func (s *stoppedState) Resize(ws console.WinSize) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot resize a stopped container") +} + +func (s *stoppedState) Start(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return errors.Errorf("cannot start a stopped process") +} + +func (s *stoppedState) Delete(ctx context.Context) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + if err := s.p.delete(ctx); err != nil { + return err + } + return s.transition("deleted") +} + +func (s *stoppedState) Kill(ctx context.Context, sig uint32, all bool) error { + s.p.mu.Lock() + defer s.p.mu.Unlock() + + return s.p.kill(ctx, sig, all) +} + +func (s *stoppedState) SetExited(status int) { + // no op +} diff --git a/linux/shim/process.go b/linux/shim/process.go index 46f1b31c0..8a0fa6ce2 100644 --- a/linux/shim/process.go +++ b/linux/shim/process.go @@ -8,6 +8,7 @@ import ( "time" "github.com/containerd/console" + "github.com/pkg/errors" ) type stdio struct { @@ -22,28 +23,49 @@ func (s stdio) isNull() bool { } type process interface { + processState + // ID returns the id for the process ID() string // Pid returns the pid for the process Pid() int - // Resize resizes the process console - Resize(ws console.WinSize) error - // SetExited sets the exit status for the process - SetExited(status int) // ExitStatus returns the exit status ExitStatus() int // ExitedAt is the time the process exited ExitedAt() time.Time - // Delete deletes the process and its resourcess - Delete(context.Context) error // Stdin returns the process STDIN Stdin() io.Closer - // Kill kills the process - Kill(context.Context, uint32, bool) error // Stdio returns io information for the container Stdio() stdio - // Start execution of the process - Start(context.Context) error // Status returns the process status Status(ctx context.Context) (string, error) } + +type processState interface { + // Resize resizes the process console + Resize(ws console.WinSize) error + // Start execution of the process + Start(context.Context) error + // Delete deletes the process and its resourcess + Delete(context.Context) error + // Kill kills the process + Kill(context.Context, uint32, bool) error + // SetExited sets the exit status for the process + SetExited(status int) +} + +func stateName(v interface{}) string { + switch v.(type) { + case *runningState, *execRunningState: + return "running" + case *createdState, *execCreatedState: + return "created" + case *pausedState: + return "paused" + case *deletedState: + return "deleted" + case *stoppedState: + return "stopped" + } + panic(errors.Errorf("invalid state %v", v)) +} diff --git a/linux/shim/utils.go b/linux/shim/utils.go new file mode 100644 index 000000000..317f8daee --- /dev/null +++ b/linux/shim/utils.go @@ -0,0 +1,86 @@ +// +build !windows + +package shim + +import ( + "encoding/json" + "io" + "os" + "strings" + "time" + + "github.com/containerd/containerd/errdefs" + shimapi "github.com/containerd/containerd/linux/shim/v1" + runc "github.com/containerd/go-runc" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +// TODO(mlaventure): move to runc package? +func getLastRuntimeError(r *runc.Runc) (string, error) { + if r.Log == "" { + return "", nil + } + + f, err := os.OpenFile(r.Log, os.O_RDONLY, 0400) + if err != nil { + return "", err + } + + var ( + errMsg string + log struct { + Level string + Msg string + Time time.Time + } + ) + + dec := json.NewDecoder(f) + for err = nil; err == nil; { + if err = dec.Decode(&log); err != nil && err != io.EOF { + return "", err + } + if log.Level == "error" { + errMsg = strings.TrimSpace(log.Msg) + } + } + + return errMsg, nil +} + +// criuError returns only the first line of the error message from criu +// it tries to add an invalid dump log location when returning the message +func criuError(err error) string { + parts := strings.Split(err.Error(), "\n") + return parts[0] +} + +func copyFile(to, from string) error { + ff, err := os.Open(from) + if err != nil { + return err + } + defer ff.Close() + tt, err := os.Create(to) + if err != nil { + return err + } + defer tt.Close() + _, err = io.Copy(tt, ff) + return err +} + +func checkKillError(err error) error { + if err == nil { + return nil + } + if strings.Contains(err.Error(), "os: process already finished") || err == unix.ESRCH { + return errors.Wrapf(errdefs.ErrNotFound, "process already finished") + } + return errors.Wrapf(err, "unknown error after kill") +} + +func hasNoIO(r *shimapi.CreateTaskRequest) bool { + return r.Stdin == "" && r.Stdout == "" && r.Stderr == "" +}