Add shim for reattach of processes
Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Remove runtime files from containerd Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Update supervisor for orphaned containers Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Remove ctr/container.go back to rpc calls Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add attach to loaded container Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add monitor based on epoll for process exits Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Convert pids in containerd to string This is so that we no longer care about linux or system level pids and processes in containerd have user defined process id(pid) kinda like the exec process ids that docker has today. Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add reaper back to containerd Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Implement list containers with new process model Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Implement restore of processes Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add NONBLOCK to exit fifo open Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Implement tty reattach Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Fix race in exit pipe creation Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add delete to shim Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Update shim to use pid-file and not stdout Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
8d1f71c3d7
commit
fe38efda50
33 changed files with 1210 additions and 1836 deletions
|
@ -10,9 +10,27 @@ import (
|
|||
|
||||
type Process interface {
|
||||
io.Closer
|
||||
Pid() (int, error)
|
||||
|
||||
// ID of the process.
|
||||
// This is either "init" when it is the container's init process or
|
||||
// it is a user provided id for the process similar to the container id
|
||||
ID() string
|
||||
// Stdin returns the path the the processes stdin fifo
|
||||
Stdin() string
|
||||
// Stdout returns the path the the processes stdout fifo
|
||||
Stdout() string
|
||||
// Stderr returns the path the the processes stderr fifo
|
||||
Stderr() string
|
||||
// ExitFD returns the fd the provides an event when the process exits
|
||||
ExitFD() int
|
||||
// ExitStatus returns the exit status of the process or an error if it
|
||||
// has not exited
|
||||
ExitStatus() (int, error)
|
||||
Spec() specs.Process
|
||||
// Signal sends the provided signal to the process
|
||||
Signal(os.Signal) error
|
||||
// Container returns the container that the process belongs to
|
||||
Container() Container
|
||||
}
|
||||
|
||||
type State string
|
||||
|
@ -77,20 +95,16 @@ type Checkpoint struct {
|
|||
type Container interface {
|
||||
// ID returns the container ID
|
||||
ID() string
|
||||
// Start starts the init process of the container
|
||||
Start() error
|
||||
// Path returns the path to the bundle
|
||||
Path() string
|
||||
// Pid returns the container's init process id
|
||||
Pid() (int, error)
|
||||
// SetExited sets the exit status of the container after its init dies
|
||||
SetExited(status int)
|
||||
// Delete deletes the container
|
||||
// Start starts the init process of the container
|
||||
Start() (Process, error)
|
||||
// Delete removes the container's state and any resources
|
||||
Delete() error
|
||||
// Pid returns the container's init process id
|
||||
// Pid() (int, error)
|
||||
// Processes returns all the containers processes that have been added
|
||||
Processes() ([]Process, error)
|
||||
// RemoveProcess removes a specific process for the container because it exited
|
||||
RemoveProcess(pid int) error
|
||||
// State returns the containers runtime state
|
||||
State() State
|
||||
// Resume resumes a paused container
|
||||
|
@ -98,15 +112,15 @@ type Container interface {
|
|||
// Pause pauses a running container
|
||||
Pause() error
|
||||
// Checkpoints returns all the checkpoints for a container
|
||||
Checkpoints() ([]Checkpoint, error)
|
||||
// Checkpoints() ([]Checkpoint, error)
|
||||
// Checkpoint creates a new checkpoint
|
||||
Checkpoint(Checkpoint) error
|
||||
// Checkpoint(Checkpoint) error
|
||||
// DeleteCheckpoint deletes the checkpoint for the provided name
|
||||
DeleteCheckpoint(name string) error
|
||||
// DeleteCheckpoint(name string) error
|
||||
// Restore restores the container to that of the checkpoint provided by name
|
||||
Restore(name string) error
|
||||
// Restore(name string) error
|
||||
// Stats returns realtime container stats and resource information
|
||||
Stats() (*Stat, error)
|
||||
// Stats() (*Stat, error)
|
||||
// OOM signals the channel if the container received an OOM notification
|
||||
OOM() (<-chan struct{}, error)
|
||||
// OOM() (<-chan struct{}, error)
|
||||
}
|
||||
|
|
172
runtime/lib.go
Normal file
172
runtime/lib.go
Normal file
|
@ -0,0 +1,172 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/opencontainers/specs"
|
||||
)
|
||||
|
||||
const (
|
||||
ExitFile = "exit"
|
||||
ExitStatusFile = "exitStatus"
|
||||
StateFile = "state.json"
|
||||
InitProcessID = "init"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
Bundle string `json:"bundle"`
|
||||
}
|
||||
|
||||
// New returns a new container
|
||||
func New(root, id, bundle string) (Container, error) {
|
||||
c := &container{
|
||||
root: root,
|
||||
id: id,
|
||||
bundle: bundle,
|
||||
processes: make(map[string]*process),
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(root, id), 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Create(filepath.Join(root, id, StateFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewEncoder(f).Encode(state{
|
||||
Bundle: bundle,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func Load(root, id string) (Container, error) {
|
||||
var s state
|
||||
f, err := os.Open(filepath.Join(root, id, StateFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewDecoder(f).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &container{
|
||||
root: root,
|
||||
id: id,
|
||||
bundle: s.Bundle,
|
||||
processes: make(map[string]*process),
|
||||
}
|
||||
dirs, err := ioutil.ReadDir(filepath.Join(root, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, d := range dirs {
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
pid := d.Name()
|
||||
// TODO: get the process spec from a state file in the process dir
|
||||
p, err := loadProcess(filepath.Join(root, id, pid), pid, c, specs.Process{})
|
||||
if err != nil {
|
||||
if err == ErrProcessExited {
|
||||
logrus.WithField("id", id).WithField("pid", pid).Debug("containerd: process exited while away")
|
||||
// TODO: fire events to do the removal
|
||||
if err := os.RemoveAll(filepath.Join(root, id, pid)); err != nil {
|
||||
logrus.WithField("error", err).Warn("containerd: remove process state")
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
c.processes[pid] = p
|
||||
}
|
||||
if len(c.processes) == 0 {
|
||||
return nil, ErrContainerExited
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type container struct {
|
||||
// path to store runtime state information
|
||||
root string
|
||||
id string
|
||||
bundle string
|
||||
processes map[string]*process
|
||||
}
|
||||
|
||||
func (c *container) ID() string {
|
||||
return c.id
|
||||
}
|
||||
|
||||
func (c *container) Path() string {
|
||||
return c.bundle
|
||||
}
|
||||
|
||||
func (c *container) Start() (Process, error) {
|
||||
processRoot := filepath.Join(c.root, c.id, InitProcessID)
|
||||
if err := os.MkdirAll(processRoot, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.Command("containerd-shim", processRoot, c.id)
|
||||
cmd.Dir = c.bundle
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
spec, err := c.readSpec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := newProcess(processRoot, InitProcessID, c, spec.Process)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.processes[InitProcessID] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (c *container) readSpec() (*specs.LinuxSpec, error) {
|
||||
var spec specs.LinuxSpec
|
||||
f, err := os.Open(filepath.Join(c.bundle, "config.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
func (c *container) Pause() error {
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
func (c *container) Resume() error {
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
func (c *container) State() State {
|
||||
return Running
|
||||
}
|
||||
|
||||
func (c *container) Delete() error {
|
||||
return os.RemoveAll(filepath.Join(c.root, c.id))
|
||||
}
|
||||
|
||||
func (c *container) Processes() ([]Process, error) {
|
||||
out := []Process{}
|
||||
for _, p := range c.processes {
|
||||
out = append(out, p)
|
||||
}
|
||||
return out, nil
|
||||
}
|
141
runtime/process.go
Normal file
141
runtime/process.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/opencontainers/specs"
|
||||
)
|
||||
|
||||
func newProcess(root, id string, c *container, s specs.Process) (*process, error) {
|
||||
p := &process{
|
||||
root: root,
|
||||
id: id,
|
||||
container: c,
|
||||
spec: s,
|
||||
}
|
||||
// create fifo's for the process
|
||||
for name, fd := range map[string]*string{
|
||||
"stdin": &p.stdin,
|
||||
"stdout": &p.stdout,
|
||||
"stderr": &p.stderr,
|
||||
} {
|
||||
path := filepath.Join(root, name)
|
||||
if err := syscall.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
*fd = path
|
||||
}
|
||||
exit, err := getExitPipe(filepath.Join(root, ExitFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.exitPipe = exit
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func loadProcess(root, id string, c *container, s specs.Process) (*process, error) {
|
||||
p := &process{
|
||||
root: root,
|
||||
id: id,
|
||||
container: c,
|
||||
spec: s,
|
||||
stdin: filepath.Join(root, "stdin"),
|
||||
stdout: filepath.Join(root, "stdout"),
|
||||
stderr: filepath.Join(root, "stderr"),
|
||||
}
|
||||
if _, err := p.ExitStatus(); err != nil {
|
||||
if err == ErrProcessNotExited {
|
||||
exit, err := getExitPipe(filepath.Join(root, ExitFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.exitPipe = exit
|
||||
return p, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, ErrProcessExited
|
||||
}
|
||||
|
||||
func getExitPipe(path string) (*os.File, error) {
|
||||
if err := syscall.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
// add NONBLOCK in case the other side has already closed or else
|
||||
// this function would never return
|
||||
return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
}
|
||||
|
||||
type process struct {
|
||||
root string
|
||||
id string
|
||||
// stdio fifos
|
||||
stdin string
|
||||
stdout string
|
||||
stderr string
|
||||
|
||||
exitPipe *os.File
|
||||
container *container
|
||||
spec specs.Process
|
||||
}
|
||||
|
||||
func (p *process) ID() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
func (p *process) Container() Container {
|
||||
return p.container
|
||||
}
|
||||
|
||||
// ExitFD returns the fd of the exit pipe
|
||||
func (p *process) ExitFD() int {
|
||||
return int(p.exitPipe.Fd())
|
||||
}
|
||||
|
||||
func (p *process) ExitStatus() (int, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return -1, ErrProcessNotExited
|
||||
}
|
||||
return -1, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return -1, ErrProcessNotExited
|
||||
}
|
||||
i, err := strconv.Atoi(string(data))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// Signal sends the provided signal to the process
|
||||
func (p *process) Signal(s os.Signal) error {
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
func (p *process) Spec() specs.Process {
|
||||
return p.spec
|
||||
}
|
||||
|
||||
func (p *process) Stdin() string {
|
||||
return p.stdin
|
||||
}
|
||||
|
||||
func (p *process) Stdout() string {
|
||||
return p.stdout
|
||||
}
|
||||
|
||||
func (p *process) Stderr() string {
|
||||
return p.stderr
|
||||
}
|
||||
|
||||
// Close closes any open files and/or resouces on the process
|
||||
func (p *process) Close() error {
|
||||
return p.exitPipe.Close()
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/opencontainers/specs"
|
||||
)
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNotChildProcess = errors.New("containerd: not a child process for container")
|
||||
|
@ -13,14 +9,8 @@ var (
|
|||
ErrCheckpointExists = errors.New("containerd: checkpoint already exists")
|
||||
ErrContainerExited = errors.New("containerd: container has exited")
|
||||
ErrTerminalsNotSupported = errors.New("containerd: terminals are not supported for runtime")
|
||||
)
|
||||
ErrProcessNotExited = errors.New("containerd: process has not exited")
|
||||
ErrProcessExited = errors.New("containerd: process has exited")
|
||||
|
||||
// Runtime handles containers, containers handle their own actions
|
||||
type Runtime interface {
|
||||
// Type of the runtime
|
||||
Type() string
|
||||
// Create creates a new container initialized but without it starting it
|
||||
Create(id, bundlePath, consolePath string) (Container, *IO, error)
|
||||
// StartProcess adds a new process to the container
|
||||
StartProcess(c Container, p specs.Process, consolePath string) (Process, *IO, error)
|
||||
}
|
||||
errNotImplemented = errors.New("containerd: not implemented")
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue