Merge pull request #1427 from crosbymichael/states

Add procesStates for shim processes
This commit is contained in:
Derek McGowan 2017-08-25 12:03:27 -07:00 committed by GitHub
commit ab1968d590
7 changed files with 716 additions and 108 deletions

View File

@ -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
}

View File

@ -26,6 +26,8 @@ import (
type execProcess struct { type execProcess struct {
sync.WaitGroup sync.WaitGroup
processState
mu sync.Mutex mu sync.Mutex
id string id string
console console.Console console console.Console
@ -65,6 +67,7 @@ func newExecProcess(context context.Context, path string, r *shimapi.ExecProcess
terminal: r.Terminal, terminal: r.Terminal,
}, },
} }
e.processState = &execCreatedState{p: e}
return e, nil return e, nil
} }
@ -79,14 +82,18 @@ func (e *execProcess) Pid() int {
} }
func (e *execProcess) ExitStatus() int { func (e *execProcess) ExitStatus() int {
e.mu.Lock()
defer e.mu.Unlock()
return e.status return e.status
} }
func (e *execProcess) ExitedAt() time.Time { func (e *execProcess) ExitedAt() time.Time {
e.mu.Lock()
defer e.mu.Unlock()
return e.exited return e.exited
} }
func (e *execProcess) SetExited(status int) { func (e *execProcess) setExited(status int) {
e.status = status e.status = status
e.exited = time.Now() e.exited = time.Now()
e.parent.platform.shutdownConsole(context.Background(), e.console) 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 { func (e *execProcess) resize(ws console.WinSize) error {
return nil
}
func (e *execProcess) Resize(ws console.WinSize) error {
if e.console == nil { if e.console == nil {
return nil return nil
} }
return e.console.Resize(ws) return e.console.Resize(ws)
} }
func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error { func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error {
e.mu.Lock()
pid := e.pid pid := e.pid
e.mu.Unlock()
if pid != 0 { if pid != 0 {
if err := unix.Kill(pid, syscall.Signal(sig)); err != nil { if err := unix.Kill(pid, syscall.Signal(sig)); err != nil {
return errors.Wrapf(checkKillError(err), "exec kill error") return errors.Wrapf(checkKillError(err), "exec kill error")
@ -130,7 +131,7 @@ func (e *execProcess) Stdio() stdio {
return e.stdio return e.stdio
} }
func (e *execProcess) Start(ctx context.Context) (err error) { func (e *execProcess) start(ctx context.Context) (err error) {
var ( var (
socket *runc.Socket socket *runc.Socket
pidfile = filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)) 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 { if err != nil {
return errors.Wrap(err, "failed to retrieve OCI runtime exec pid") return errors.Wrap(err, "failed to retrieve OCI runtime exec pid")
} }
e.mu.Lock()
e.pid = pid e.pid = pid
e.mu.Unlock()
return nil return nil
} }

166
linux/shim/exec_state.go Normal file
View File

@ -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
}

View File

@ -14,10 +14,7 @@ import (
"syscall" "syscall"
"time" "time"
"golang.org/x/sys/unix"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/linux/runcopts" "github.com/containerd/containerd/linux/runcopts"
shimapi "github.com/containerd/containerd/linux/shim/v1" shimapi "github.com/containerd/containerd/linux/shim/v1"
@ -32,6 +29,7 @@ import (
type initProcess struct { type initProcess struct {
sync.WaitGroup sync.WaitGroup
initState
// mu is used to ensure that `Start()` and `Exited()` calls return in // mu is used to ensure that `Start()` and `Exited()` calls return in
// the right order when invoked in separate go routines. // 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, rootfs: rootfs,
workDir: workDir, workDir: workDir,
} }
p.initState = &createdState{p: p}
var ( var (
err error err error
socket *runc.Socket socket *runc.Socket
@ -207,14 +206,17 @@ func (p *initProcess) Pid() int {
} }
func (p *initProcess) ExitStatus() int { func (p *initProcess) ExitStatus() int {
p.mu.Lock()
defer p.mu.Unlock()
return p.status return p.status
} }
func (p *initProcess) ExitedAt() time.Time { func (p *initProcess) ExitedAt() time.Time {
p.mu.Lock()
defer p.mu.Unlock()
return p.exited return p.exited
} }
// Status return the state of the container (created, running, paused, stopped)
func (p *initProcess) Status(ctx context.Context) (string, error) { func (p *initProcess) Status(ctx context.Context) (string, error) {
c, err := p.runtime.State(ctx, p.id) c, err := p.runtime.State(ctx, p.id)
if err != nil { if err != nil {
@ -223,22 +225,18 @@ func (p *initProcess) Status(ctx context.Context) (string, error) {
return c.Status, nil return c.Status, nil
} }
func (p *initProcess) Start(context context.Context) error { func (p *initProcess) start(context context.Context) error {
p.mu.Lock()
defer p.mu.Unlock()
err := p.runtime.Start(context, p.id) err := p.runtime.Start(context, p.id)
return p.runtimeError(err, "OCI runtime start failed") return p.runtimeError(err, "OCI runtime start failed")
} }
func (p *initProcess) SetExited(status int) { func (p *initProcess) setExited(status int) {
p.mu.Lock()
p.status = status p.status = status
p.exited = time.Now() p.exited = time.Now()
p.platform.shutdownConsole(context.Background(), p.console) 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.killAll(context)
p.Wait() p.Wait()
err := p.runtime.Delete(context, p.id, nil) err := p.runtime.Delete(context, p.id, nil)
@ -269,24 +267,24 @@ func (p *initProcess) Delete(context context.Context) error {
return err return err
} }
func (p *initProcess) Resize(ws console.WinSize) error { func (p *initProcess) resize(ws console.WinSize) error {
if p.console == nil { if p.console == nil {
return nil return nil
} }
return p.console.Resize(ws) 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) err := p.runtime.Pause(context, p.id)
return p.runtimeError(err, "OCI runtime pause failed") 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) err := p.runtime.Resume(context, p.id)
return p.runtimeError(err, "OCI runtime resume failed") 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{ err := p.runtime.Kill(context, p.id, int(signal), &runc.KillOpts{
All: all, All: all,
}) })
@ -304,7 +302,7 @@ func (p *initProcess) Stdin() io.Closer {
return p.stdin 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 var options runcopts.CheckpointOptions
if r.Options != nil { if r.Options != nil {
v, err := typeurl.UnmarshalAny(r.Options) v, err := typeurl.UnmarshalAny(r.Options)
@ -337,7 +335,7 @@ func (p *initProcess) Checkpoint(context context.Context, r *shimapi.CheckpointT
return nil 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 var resources specs.LinuxResources
if err := json.Unmarshal(r.Resources.Value, &resources); err != nil { if err := json.Unmarshal(r.Resources.Value, &resources); err != nil {
return err return err
@ -349,39 +347,6 @@ func (p *initProcess) Stdio() stdio {
return p.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 { func (p *initProcess) runtimeError(rErr error, msg string) error {
if rErr == nil { if rErr == nil {
return nil return nil
@ -397,39 +362,3 @@ func (p *initProcess) runtimeError(rErr error, msg string) error {
return errors.Errorf("%s: %s", msg, rMsg) 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 == ""
}

356
linux/shim/init_state.go Normal file
View File

@ -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
}

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/pkg/errors"
) )
type stdio struct { type stdio struct {
@ -22,28 +23,49 @@ func (s stdio) isNull() bool {
} }
type process interface { type process interface {
processState
// ID returns the id for the process // ID returns the id for the process
ID() string ID() string
// Pid returns the pid for the process // Pid returns the pid for the process
Pid() int 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 returns the exit status
ExitStatus() int ExitStatus() int
// ExitedAt is the time the process exited // ExitedAt is the time the process exited
ExitedAt() time.Time ExitedAt() time.Time
// Delete deletes the process and its resourcess
Delete(context.Context) error
// Stdin returns the process STDIN // Stdin returns the process STDIN
Stdin() io.Closer Stdin() io.Closer
// Kill kills the process
Kill(context.Context, uint32, bool) error
// Stdio returns io information for the container // Stdio returns io information for the container
Stdio() stdio Stdio() stdio
// Start execution of the process
Start(context.Context) error
// Status returns the process status // Status returns the process status
Status(ctx context.Context) (string, error) 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))
}

86
linux/shim/utils.go Normal file
View File

@ -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 == ""
}