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 {
|
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
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"
|
"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
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"
|
"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
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