Merge pull request #1427 from crosbymichael/states
Add procesStates for shim processes
This commit is contained in:
commit
ab1968d590
50
linux/shim/deleted_state.go
Normal file
50
linux/shim/deleted_state.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
166
linux/shim/exec_state.go
Normal file
166
linux/shim/exec_state.go
Normal 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
|
||||
}
|
@ -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 == ""
|
||||
}
|
||||
|
356
linux/shim/init_state.go
Normal file
356
linux/shim/init_state.go
Normal 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
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
86
linux/shim/utils.go
Normal file
86
linux/shim/utils.go
Normal 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 == ""
|
||||
}
|
Loading…
Reference in New Issue
Block a user