| // +build !windows |
| |
| package shim |
| |
| import ( |
| "context" |
| "sync" |
| "syscall" |
| |
| "github.com/containerd/console" |
| "github.com/containerd/containerd/errdefs" |
| shimapi "github.com/containerd/containerd/linux/shim/v1" |
| "github.com/containerd/fifo" |
| runc "github.com/containerd/go-runc" |
| "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 createdCheckpointState struct { |
| p *initProcess |
| opts *runc.RestoreOpts |
| } |
| |
| func (s *createdCheckpointState) 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 *createdCheckpointState) 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 *createdCheckpointState) 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 *createdCheckpointState) 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 *createdCheckpointState) 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 *createdCheckpointState) Resize(ws console.WinSize) error { |
| s.p.mu.Lock() |
| defer s.p.mu.Unlock() |
| |
| return s.p.resize(ws) |
| } |
| |
| func (s *createdCheckpointState) Start(ctx context.Context) error { |
| s.p.mu.Lock() |
| defer s.p.mu.Unlock() |
| p := s.p |
| if _, err := s.p.runtime.Restore(ctx, p.id, p.bundle, s.opts); err != nil { |
| return p.runtimeError(err, "OCI runtime restore failed") |
| } |
| sio := p.stdio |
| if sio.stdin != "" { |
| sc, err := fifo.OpenFifo(ctx, sio.stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) |
| if err != nil { |
| return errors.Wrapf(err, "failed to open stdin fifo %s", sio.stdin) |
| } |
| p.stdin = sc |
| p.closers = append(p.closers, sc) |
| } |
| var copyWaitGroup sync.WaitGroup |
| if !sio.isNull() { |
| if err := copyPipes(ctx, p.io, sio.stdin, sio.stdout, sio.stderr, &p.wg, ©WaitGroup); err != nil { |
| return errors.Wrap(err, "failed to start io pipe copy") |
| } |
| } |
| |
| copyWaitGroup.Wait() |
| pid, err := runc.ReadPidFile(s.opts.PidFile) |
| if err != nil { |
| return errors.Wrap(err, "failed to retrieve OCI runtime container pid") |
| } |
| p.pid = pid |
| |
| return s.transition("running") |
| } |
| |
| func (s *createdCheckpointState) 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 *createdCheckpointState) 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 *createdCheckpointState) 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 { |
| return errdefs.ToGRPCf(errdefs.ErrNotFound, "process %s not found", s.p.id) |
| } |
| |
| func (s *stoppedState) SetExited(status int) { |
| // no op |
| } |