Remove containerd files

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby
2016-11-07 13:10:09 -08:00
parent 992fdbfd76
commit e115b52ce2
74 changed files with 0 additions and 9757 deletions

View File

@@ -1,738 +0,0 @@
package runtime
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/containerd/specs"
ocs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
// Container defines the operations allowed on a container
type Container interface {
// ID returns the container ID
ID() string
// Path returns the path to the bundle
Path() string
// Start starts the init process of the container
Start(checkpointPath string, s Stdio) (Process, error)
// Exec starts another process in an existing container
Exec(string, specs.ProcessSpec, Stdio) (Process, error)
// Delete removes the container's state and any resources
Delete() error
// Processes returns all the containers processes that have been added
Processes() ([]Process, error)
// State returns the containers runtime state
State() State
// Resume resumes a paused container
Resume() error
// Pause pauses a running container
Pause() error
// RemoveProcess removes the specified process from the container
RemoveProcess(string) error
// Checkpoints returns all the checkpoints for a container
Checkpoints(checkpointDir string) ([]Checkpoint, error)
// Checkpoint creates a new checkpoint
Checkpoint(checkpoint Checkpoint, checkpointDir string) error
// DeleteCheckpoint deletes the checkpoint for the provided name
DeleteCheckpoint(name string, checkpointDir string) error
// Labels are user provided labels for the container
Labels() []string
// Pids returns all pids inside the container
Pids() ([]int, error)
// Stats returns realtime container stats and resource information
Stats() (*Stat, error)
// Name or path of the OCI compliant runtime used to execute the container
Runtime() string
// OOM signals the channel if the container received an OOM notification
OOM() (OOM, error)
// UpdateResource updates the containers resources to new values
UpdateResources(*Resource) error
// Status return the current status of the container.
Status() (State, error)
}
// OOM wraps a container OOM.
type OOM interface {
io.Closer
FD() int
ContainerID() string
Flush() error
Removed() bool
}
// Stdio holds the path to the 3 pipes used for the standard ios.
type Stdio struct {
Stdin string
Stdout string
Stderr string
}
// NewStdio wraps the given standard io path into an Stdio struct.
// If a given parameter is the empty string, it is replaced by "/dev/null"
func NewStdio(stdin, stdout, stderr string) Stdio {
for _, s := range []*string{
&stdin, &stdout, &stderr,
} {
if *s == "" {
*s = "/dev/null"
}
}
return Stdio{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
}
}
// ContainerOpts keeps the options passed at container creation
type ContainerOpts struct {
Root string
ID string
Bundle string
Runtime string
RuntimeArgs []string
Shim string
Labels []string
NoPivotRoot bool
Timeout time.Duration
}
// New returns a new container
func New(opts ContainerOpts) (Container, error) {
c := &container{
root: opts.Root,
id: opts.ID,
bundle: opts.Bundle,
labels: opts.Labels,
processes: make(map[string]*process),
runtime: opts.Runtime,
runtimeArgs: opts.RuntimeArgs,
shim: opts.Shim,
noPivotRoot: opts.NoPivotRoot,
timeout: opts.Timeout,
}
if err := os.Mkdir(filepath.Join(c.root, c.id), 0755); err != nil {
return nil, err
}
f, err := os.Create(filepath.Join(c.root, c.id, StateFile))
if err != nil {
return nil, err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(state{
Bundle: c.bundle,
Labels: c.labels,
Runtime: c.runtime,
RuntimeArgs: c.runtimeArgs,
Shim: c.shim,
NoPivotRoot: opts.NoPivotRoot,
}); err != nil {
return nil, err
}
return c, nil
}
// Load return a new container from the matchin state file on disk.
func Load(root, id, shimName string, timeout time.Duration) (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,
labels: s.Labels,
runtime: s.Runtime,
runtimeArgs: s.RuntimeArgs,
shim: s.Shim,
noPivotRoot: s.NoPivotRoot,
processes: make(map[string]*process),
timeout: timeout,
}
if c.shim == "" {
c.shim = shimName
}
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()
s, err := readProcessState(filepath.Join(root, id, pid))
if err != nil {
return nil, err
}
p, err := loadProcess(filepath.Join(root, id, pid), pid, c, s)
if err != nil {
logrus.WithField("id", id).WithField("pid", pid).Debug("containerd: error loading process %s", err)
continue
}
c.processes[pid] = p
}
return c, nil
}
func readProcessState(dir string) (*ProcessState, error) {
f, err := os.Open(filepath.Join(dir, "process.json"))
if err != nil {
return nil, err
}
defer f.Close()
var s ProcessState
if err := json.NewDecoder(f).Decode(&s); err != nil {
return nil, err
}
return &s, nil
}
type container struct {
// path to store runtime state information
root string
id string
bundle string
runtime string
runtimeArgs []string
shim string
processes map[string]*process
labels []string
oomFds []int
noPivotRoot bool
timeout time.Duration
}
func (c *container) ID() string {
return c.id
}
func (c *container) Path() string {
return c.bundle
}
func (c *container) Labels() []string {
return c.labels
}
func (c *container) readSpec() (*specs.Spec, error) {
var spec specs.Spec
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) Delete() error {
err := os.RemoveAll(filepath.Join(c.root, c.id))
args := c.runtimeArgs
args = append(args, "delete", c.id)
if b, derr := exec.Command(c.runtime, args...).CombinedOutput(); err != nil {
err = fmt.Errorf("%s: %q", derr, string(b))
} else if len(b) > 0 {
logrus.Debugf("%v %v: %q", c.runtime, args, string(b))
}
return err
}
func (c *container) Processes() ([]Process, error) {
out := []Process{}
for _, p := range c.processes {
out = append(out, p)
}
return out, nil
}
func (c *container) RemoveProcess(pid string) error {
delete(c.processes, pid)
return os.RemoveAll(filepath.Join(c.root, c.id, pid))
}
func (c *container) State() State {
proc := c.processes["init"]
if proc == nil {
return Stopped
}
return proc.State()
}
func (c *container) Runtime() string {
return c.runtime
}
func (c *container) Pause() error {
args := c.runtimeArgs
args = append(args, "pause", c.id)
b, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return fmt.Errorf("%s: %q", err.Error(), string(b))
}
return nil
}
func (c *container) Resume() error {
args := c.runtimeArgs
args = append(args, "resume", c.id)
b, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return fmt.Errorf("%s: %q", err.Error(), string(b))
}
return nil
}
func (c *container) Checkpoints(checkpointDir string) ([]Checkpoint, error) {
if checkpointDir == "" {
checkpointDir = filepath.Join(c.bundle, "checkpoints")
}
dirs, err := ioutil.ReadDir(checkpointDir)
if err != nil {
return nil, err
}
var out []Checkpoint
for _, d := range dirs {
if !d.IsDir() {
continue
}
path := filepath.Join(checkpointDir, d.Name(), "config.json")
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var cpt Checkpoint
if err := json.Unmarshal(data, &cpt); err != nil {
return nil, err
}
out = append(out, cpt)
}
return out, nil
}
func (c *container) Checkpoint(cpt Checkpoint, checkpointDir string) error {
if checkpointDir == "" {
checkpointDir = filepath.Join(c.bundle, "checkpoints")
}
if err := os.MkdirAll(checkpointDir, 0755); err != nil {
return err
}
path := filepath.Join(checkpointDir, cpt.Name)
if err := os.Mkdir(path, 0755); err != nil {
return err
}
f, err := os.Create(filepath.Join(path, "config.json"))
if err != nil {
return err
}
cpt.Created = time.Now()
err = json.NewEncoder(f).Encode(cpt)
f.Close()
if err != nil {
return err
}
args := []string{
"checkpoint",
"--image-path", path,
"--work-path", filepath.Join(path, "criu.work"),
}
add := func(flags ...string) {
args = append(args, flags...)
}
add(c.runtimeArgs...)
if !cpt.Exit {
add("--leave-running")
}
if cpt.Shell {
add("--shell-job")
}
if cpt.TCP {
add("--tcp-established")
}
if cpt.UnixSockets {
add("--ext-unix-sk")
}
for _, ns := range cpt.EmptyNS {
add("--empty-ns", ns)
}
add(c.id)
out, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return fmt.Errorf("%s: %q", err.Error(), string(out))
}
return err
}
func (c *container) DeleteCheckpoint(name string, checkpointDir string) error {
if checkpointDir == "" {
checkpointDir = filepath.Join(c.bundle, "checkpoints")
}
return os.RemoveAll(filepath.Join(checkpointDir, name))
}
func (c *container) Start(checkpointPath string, s Stdio) (Process, error) {
processRoot := filepath.Join(c.root, c.id, InitProcessID)
if err := os.Mkdir(processRoot, 0755); err != nil {
return nil, err
}
cmd := exec.Command(c.shim,
c.id, c.bundle, c.runtime,
)
cmd.Dir = processRoot
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
spec, err := c.readSpec()
if err != nil {
return nil, err
}
config := &processConfig{
checkpoint: checkpointPath,
root: processRoot,
id: InitProcessID,
c: c,
stdio: s,
spec: spec,
processSpec: specs.ProcessSpec(spec.Process),
}
p, err := newProcess(config)
if err != nil {
return nil, err
}
if err := c.createCmd(InitProcessID, cmd, p); err != nil {
return nil, err
}
return p, nil
}
func (c *container) Exec(pid string, pspec specs.ProcessSpec, s Stdio) (pp Process, err error) {
processRoot := filepath.Join(c.root, c.id, pid)
if err := os.Mkdir(processRoot, 0755); err != nil {
return nil, err
}
defer func() {
if err != nil {
c.RemoveProcess(pid)
}
}()
cmd := exec.Command(c.shim,
c.id, c.bundle, c.runtime,
)
cmd.Dir = processRoot
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
spec, err := c.readSpec()
if err != nil {
return nil, err
}
config := &processConfig{
exec: true,
id: pid,
root: processRoot,
c: c,
processSpec: pspec,
spec: spec,
stdio: s,
}
p, err := newProcess(config)
if err != nil {
return nil, err
}
if err := c.createCmd(pid, cmd, p); err != nil {
return nil, err
}
return p, nil
}
func (c *container) createCmd(pid string, cmd *exec.Cmd, p *process) error {
p.cmd = cmd
if err := cmd.Start(); err != nil {
close(p.cmdDoneCh)
if exErr, ok := err.(*exec.Error); ok {
if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist {
return fmt.Errorf("%s not installed on system", c.shim)
}
}
return err
}
// We need the pid file to have been written to run
defer func() {
go func() {
err := p.cmd.Wait()
if err == nil {
p.cmdSuccess = true
}
if same, err := p.isSameProcess(); same && p.pid > 0 {
// The process changed its PR_SET_PDEATHSIG, so force
// kill it
logrus.Infof("containerd: %s:%s (pid %v) has become an orphan, killing it", p.container.id, p.id, p.pid)
err = unix.Kill(p.pid, syscall.SIGKILL)
if err != nil && err != syscall.ESRCH {
logrus.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err)
} else {
for {
err = unix.Kill(p.pid, 0)
if err != nil {
break
}
time.Sleep(5 * time.Millisecond)
}
}
}
close(p.cmdDoneCh)
}()
}()
if err := c.waitForCreate(p, cmd); err != nil {
return err
}
c.processes[pid] = p
return nil
}
func hostIDFromMap(id uint32, mp []ocs.IDMapping) int {
for _, m := range mp {
if (id >= m.ContainerID) && (id <= (m.ContainerID + m.Size - 1)) {
return int(m.HostID + (id - m.ContainerID))
}
}
return 0
}
func (c *container) Pids() ([]int, error) {
args := c.runtimeArgs
args = append(args, "ps", "--format=json", c.id)
out, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("%s: %q", err.Error(), out)
}
var pids []int
if err := json.Unmarshal(out, &pids); err != nil {
return nil, err
}
return pids, nil
}
func (c *container) Stats() (*Stat, error) {
now := time.Now()
args := c.runtimeArgs
args = append(args, "events", "--stats", c.id)
out, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("%s: %q", err.Error(), out)
}
s := struct {
Data *Stat `json:"data"`
}{}
if err := json.Unmarshal(out, &s); err != nil {
return nil, err
}
s.Data.Timestamp = now
return s.Data, nil
}
// Status implements the runtime Container interface.
func (c *container) Status() (State, error) {
args := c.runtimeArgs
args = append(args, "state", c.id)
out, err := exec.Command(c.runtime, args...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("%s: %q", err.Error(), out)
}
// We only require the runtime json output to have a top level Status field.
var s struct {
Status State `json:"status"`
}
if err := json.Unmarshal(out, &s); err != nil {
return "", err
}
return s.Status, nil
}
func (c *container) writeEventFD(root string, cfd, efd int) error {
f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd))
return err
}
type waitArgs struct {
pid int
err error
}
func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error {
wc := make(chan error, 1)
go func() {
for {
if _, err := p.getPidFromFile(); err != nil {
if os.IsNotExist(err) || err == errInvalidPidInt {
alive, err := isAlive(cmd)
if err != nil {
wc <- err
return
}
if !alive {
// runc could have failed to run the container so lets get the error
// out of the logs or the shim could have encountered an error
messages, err := readLogMessages(filepath.Join(p.root, "shim-log.json"))
if err != nil {
wc <- err
return
}
for _, m := range messages {
if m.Level == "error" {
wc <- fmt.Errorf("shim error: %v", m.Msg)
return
}
}
// no errors reported back from shim, check for runc/runtime errors
messages, err = readLogMessages(filepath.Join(p.root, "log.json"))
if err != nil {
if os.IsNotExist(err) {
err = ErrContainerNotStarted
}
wc <- err
return
}
for _, m := range messages {
if m.Level == "error" {
wc <- fmt.Errorf("oci runtime error: %v", m.Msg)
return
}
}
wc <- ErrContainerNotStarted
return
}
time.Sleep(15 * time.Millisecond)
continue
}
wc <- err
return
}
// the pid file was read successfully
wc <- nil
return
}
}()
select {
case err := <-wc:
if err != nil {
return err
}
err = p.saveStartTime()
if err != nil {
logrus.Warnf("containerd: unable to save %s:%s starttime: %v", p.container.id, p.id, err)
}
return nil
case <-time.After(c.timeout):
cmd.Process.Kill()
cmd.Wait()
return ErrContainerStartTimeout
}
}
// isAlive checks if the shim that launched the container is still alive
func isAlive(cmd *exec.Cmd) (bool, error) {
if _, err := syscall.Wait4(cmd.Process.Pid, nil, syscall.WNOHANG, nil); err == nil {
return true, nil
}
if err := syscall.Kill(cmd.Process.Pid, 0); err != nil {
if err == syscall.ESRCH {
return false, nil
}
return false, err
}
return true, nil
}
type oom struct {
id string
root string
control *os.File
eventfd int
}
func (o *oom) ContainerID() string {
return o.id
}
func (o *oom) FD() int {
return o.eventfd
}
func (o *oom) Flush() error {
buf := make([]byte, 8)
_, err := syscall.Read(o.eventfd, buf)
return err
}
func (o *oom) Removed() bool {
_, err := os.Lstat(filepath.Join(o.root, "cgroup.event_control"))
return os.IsNotExist(err)
}
func (o *oom) Close() error {
err := syscall.Close(o.eventfd)
if cerr := o.control.Close(); err == nil {
err = cerr
}
return err
}
type message struct {
Level string `json:"level"`
Msg string `json:"msg"`
}
func readLogMessages(path string) ([]message, error) {
var out []message
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
dec := json.NewDecoder(f)
for {
var m message
if err := dec.Decode(&m); err != nil {
if err == io.EOF {
break
}
return nil, err
}
out = append(out, m)
}
return out, nil
}

View File

@@ -1,174 +0,0 @@
package runtime
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"github.com/docker/containerd/specs"
ocs "github.com/opencontainers/runtime-spec/specs-go"
)
func findCgroupMountpointAndRoot(pid int, subsystem string) (string, string, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return "", "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
txt := scanner.Text()
fields := strings.Split(txt, " ")
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[4], fields[3], nil
}
}
}
if err := scanner.Err(); err != nil {
return "", "", err
}
return "", "", fmt.Errorf("cgroup path for %s not found", subsystem)
}
func parseCgroupFile(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
s := bufio.NewScanner(f)
cgroups := make(map[string]string)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
parts := strings.Split(text, ":")
for _, subs := range strings.Split(parts[1], ",") {
cgroups[subs] = parts[2]
}
}
return cgroups, nil
}
func (c *container) OOM() (OOM, error) {
p := c.processes[InitProcessID]
if p == nil {
return nil, fmt.Errorf("no init process found")
}
mountpoint, hostRoot, err := findCgroupMountpointAndRoot(os.Getpid(), "memory")
if err != nil {
return nil, err
}
cgroups, err := parseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", p.pid))
if err != nil {
return nil, err
}
root, ok := cgroups["memory"]
if !ok {
return nil, fmt.Errorf("no memory cgroup for container %s", c.ID())
}
// Take care of the case were we're running inside a container
// ourself
root = strings.TrimPrefix(root, hostRoot)
return c.getMemeoryEventFD(filepath.Join(mountpoint, root))
}
func u64Ptr(i uint64) *uint64 { return &i }
func (c *container) UpdateResources(r *Resource) error {
sr := ocs.Resources{
Memory: &ocs.Memory{
Limit: u64Ptr(uint64(r.Memory)),
Reservation: u64Ptr(uint64(r.MemoryReservation)),
Swap: u64Ptr(uint64(r.MemorySwap)),
Kernel: u64Ptr(uint64(r.KernelMemory)),
KernelTCP: u64Ptr(uint64(r.KernelTCPMemory)),
},
CPU: &ocs.CPU{
Shares: u64Ptr(uint64(r.CPUShares)),
Quota: u64Ptr(uint64(r.CPUQuota)),
Period: u64Ptr(uint64(r.CPUPeriod)),
Cpus: &r.CpusetCpus,
Mems: &r.CpusetMems,
},
BlockIO: &ocs.BlockIO{
Weight: &r.BlkioWeight,
},
}
srStr := bytes.NewBuffer(nil)
if err := json.NewEncoder(srStr).Encode(&sr); err != nil {
return err
}
args := c.runtimeArgs
args = append(args, "update", "-r", "-", c.id)
cmd := exec.Command(c.runtime, args...)
cmd.Stdin = srStr
b, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf(string(b))
}
return nil
}
func getRootIDs(s *specs.Spec) (int, int, error) {
if s == nil {
return 0, 0, nil
}
var hasUserns bool
for _, ns := range s.Linux.Namespaces {
if ns.Type == ocs.UserNamespace {
hasUserns = true
break
}
}
if !hasUserns {
return 0, 0, nil
}
uid := hostIDFromMap(0, s.Linux.UIDMappings)
gid := hostIDFromMap(0, s.Linux.GIDMappings)
return uid, gid, nil
}
func (c *container) getMemeoryEventFD(root string) (*oom, error) {
f, err := os.Open(filepath.Join(root, "memory.oom_control"))
if err != nil {
return nil, err
}
fd, _, serr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
if serr != 0 {
f.Close()
return nil, serr
}
if err := c.writeEventFD(root, int(f.Fd()), int(fd)); err != nil {
syscall.Close(int(fd))
f.Close()
return nil, err
}
return &oom{
root: root,
id: c.id,
eventfd: int(fd),
control: f,
}, nil
}

View File

@@ -1,19 +0,0 @@
package runtime
import (
"errors"
"github.com/docker/containerd/specs"
)
func (c *container) OOM() (OOM, error) {
return nil, errors.New("runtime OOM() not implemented on Solaris")
}
func (c *container) UpdateResources(r *Resource) error {
return errors.New("runtime UpdateResources() not implemented on Solaris")
}
func getRootIDs(s *specs.Spec) (int, int, error) {
return 0, 0, errors.New("runtime getRootIDs() not implemented on Solaris")
}

View File

@@ -1,467 +0,0 @@
package runtime
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/containerd/specs"
"golang.org/x/sys/unix"
)
// Process holds the operation allowed on a container's process
type Process interface {
io.Closer
// 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
// Start unblocks the associated container init process.
// This should only be called on the process with ID "init"
Start() error
CloseStdin() error
Resize(int, int) error
// FD returns the fd the provides an event when the process exits
FD() int
// ExitStatus returns the exit status of the process or an error if it
// has not exited
ExitStatus() (uint32, error)
// Spec returns the process spec that created the process
Spec() specs.ProcessSpec
// Signal sends the provided signal to the process
Signal(os.Signal) error
// Container returns the container that the process belongs to
Container() Container
// Stdio of the container
Stdio() Stdio
// SystemPid is the pid on the system
SystemPid() int
// State returns if the process is running or not
State() State
// Wait reaps the shim process if avaliable
Wait()
}
type processConfig struct {
id string
root string
processSpec specs.ProcessSpec
spec *specs.Spec
c *container
stdio Stdio
exec bool
checkpoint string
}
func newProcess(config *processConfig) (*process, error) {
p := &process{
root: config.root,
id: config.id,
container: config.c,
spec: config.processSpec,
stdio: config.stdio,
cmdDoneCh: make(chan struct{}),
state: Running,
}
uid, gid, err := getRootIDs(config.spec)
if err != nil {
return nil, err
}
f, err := os.Create(filepath.Join(config.root, "process.json"))
if err != nil {
return nil, err
}
defer f.Close()
ps := ProcessState{
ProcessSpec: config.processSpec,
Exec: config.exec,
PlatformProcessState: PlatformProcessState{
Checkpoint: config.checkpoint,
RootUID: uid,
RootGID: gid,
},
Stdin: config.stdio.Stdin,
Stdout: config.stdio.Stdout,
Stderr: config.stdio.Stderr,
RuntimeArgs: config.c.runtimeArgs,
NoPivotRoot: config.c.noPivotRoot,
}
if err := json.NewEncoder(f).Encode(ps); err != nil {
return nil, err
}
exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
if err != nil {
return nil, err
}
control, err := getControlPipe(filepath.Join(config.root, ControlFile))
if err != nil {
return nil, err
}
p.exitPipe = exit
p.controlPipe = control
return p, nil
}
func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) {
p := &process{
root: root,
id: id,
container: c,
spec: s.ProcessSpec,
stdio: Stdio{
Stdin: s.Stdin,
Stdout: s.Stdout,
Stderr: s.Stderr,
},
state: Stopped,
}
startTime, err := ioutil.ReadFile(filepath.Join(p.root, StartTimeFile))
if err != nil && !os.IsNotExist(err) {
return nil, err
}
p.startTime = string(startTime)
if _, err := p.getPidFromFile(); err != nil {
return nil, err
}
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
control, err := getControlPipe(filepath.Join(root, ControlFile))
if err != nil {
return nil, err
}
p.controlPipe = control
p.state = Running
return p, nil
}
return nil, err
}
return p, nil
}
func readProcStatField(pid int, field int) (string, error) {
data, err := ioutil.ReadFile(filepath.Join(string(filepath.Separator), "proc", strconv.Itoa(pid), "stat"))
if err != nil {
return "", err
}
if field > 2 {
// First, split out the name since he could contains spaces.
parts := strings.Split(string(data), ") ")
// Now split out the rest, we end up with 2 fields less
parts = strings.Split(parts[1], " ")
return parts[field-2-1], nil // field count start at 1 in manual
}
parts := strings.Split(string(data), " (")
if field == 1 {
return parts[0], nil
}
parts = strings.Split(parts[1], ") ")
return parts[0], nil
}
type process struct {
root string
id string
pid int
exitPipe *os.File
controlPipe *os.File
container *container
spec specs.ProcessSpec
stdio Stdio
cmd *exec.Cmd
cmdSuccess bool
cmdDoneCh chan struct{}
state State
stateLock sync.Mutex
startTime string
}
func (p *process) ID() string {
return p.id
}
func (p *process) Container() Container {
return p.container
}
func (p *process) SystemPid() int {
return p.pid
}
// FD returns the fd of the exit pipe
func (p *process) FD() int {
return int(p.exitPipe.Fd())
}
func (p *process) CloseStdin() error {
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
return err
}
func (p *process) Resize(w, h int) error {
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
return err
}
func (p *process) updateExitStatusFile(status uint32) (uint32, error) {
p.stateLock.Lock()
p.state = Stopped
p.stateLock.Unlock()
err := ioutil.WriteFile(filepath.Join(p.root, ExitStatusFile), []byte(fmt.Sprintf("%u", status)), 0644)
return status, err
}
func (p *process) handleSigkilledShim(rst uint32, rerr error) (uint32, error) {
if p.cmd == nil || p.cmd.Process == nil {
e := unix.Kill(p.pid, 0)
if e == syscall.ESRCH {
logrus.Warnf("containerd: %s:%s (pid %d) does not exist", p.container.id, p.id, p.pid)
// The process died while containerd was down (probably of
// SIGKILL, but no way to be sure)
return p.updateExitStatusFile(UnknownStatus)
}
// If it's not the same process, just mark it stopped and set
// the status to the UnknownStatus value (i.e. 255)
if same, err := p.isSameProcess(); !same {
logrus.Warnf("containerd: %s:%s (pid %d) is not the same process anymore (%v)", p.container.id, p.id, p.pid, err)
// Create the file so we get the exit event generated once monitor kicks in
// without having to go through all this process again
return p.updateExitStatusFile(UnknownStatus)
}
ppid, err := readProcStatField(p.pid, 4)
if err != nil {
return rst, fmt.Errorf("could not check process ppid: %v (%v)", err, rerr)
}
if ppid == "1" {
logrus.Warnf("containerd: %s:%s shim died, killing associated process", p.container.id, p.id)
unix.Kill(p.pid, syscall.SIGKILL)
if err != nil && err != syscall.ESRCH {
return UnknownStatus, fmt.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err)
}
// wait for the process to die
for {
e := unix.Kill(p.pid, 0)
if e == syscall.ESRCH {
break
}
time.Sleep(5 * time.Millisecond)
}
// Create the file so we get the exit event generated once monitor kicks in
// without having to go through all this process again
return p.updateExitStatusFile(128 + uint32(syscall.SIGKILL))
}
return rst, rerr
}
// Possible that the shim was SIGKILLED
e := unix.Kill(p.cmd.Process.Pid, 0)
if e != syscall.ESRCH {
return rst, rerr
}
// Ensure we got the shim ProcessState
<-p.cmdDoneCh
shimStatus := p.cmd.ProcessState.Sys().(syscall.WaitStatus)
if shimStatus.Signaled() && shimStatus.Signal() == syscall.SIGKILL {
logrus.Debugf("containerd: ExitStatus(container: %s, process: %s): shim was SIGKILL'ed reaping its child with pid %d", p.container.id, p.id, p.pid)
rerr = nil
rst = 128 + uint32(shimStatus.Signal())
p.stateLock.Lock()
p.state = Stopped
p.stateLock.Unlock()
}
return rst, rerr
}
func (p *process) ExitStatus() (rst uint32, rerr error) {
data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
defer func() {
if rerr != nil {
rst, rerr = p.handleSigkilledShim(rst, rerr)
}
}()
if err != nil {
if os.IsNotExist(err) {
return UnknownStatus, ErrProcessNotExited
}
return UnknownStatus, err
}
if len(data) == 0 {
return UnknownStatus, ErrProcessNotExited
}
p.stateLock.Lock()
p.state = Stopped
p.stateLock.Unlock()
i, err := strconv.ParseUint(string(data), 10, 32)
return uint32(i), err
}
func (p *process) Spec() specs.ProcessSpec {
return p.spec
}
func (p *process) Stdio() Stdio {
return p.stdio
}
// Close closes any open files and/or resouces on the process
func (p *process) Close() error {
err := p.exitPipe.Close()
if cerr := p.controlPipe.Close(); err == nil {
err = cerr
}
return err
}
func (p *process) State() State {
p.stateLock.Lock()
defer p.stateLock.Unlock()
return p.state
}
func (p *process) getPidFromFile() (int, error) {
data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
if err != nil {
return -1, err
}
i, err := strconv.Atoi(string(data))
if err != nil {
return -1, errInvalidPidInt
}
p.pid = i
return i, nil
}
func (p *process) readStartTime() (string, error) {
return readProcStatField(p.pid, 22)
}
func (p *process) saveStartTime() error {
startTime, err := p.readStartTime()
if err != nil {
return err
}
p.startTime = startTime
return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644)
}
func (p *process) isSameProcess() (bool, error) {
// for backward compat assume it's the same if startTime wasn't set
if p.startTime == "" {
return true, nil
}
if p.pid == 0 {
_, err := p.getPidFromFile()
if err != nil {
return false, err
}
}
startTime, err := p.readStartTime()
if err != nil {
return false, err
}
return startTime == p.startTime, nil
}
// Wait will reap the shim process
func (p *process) Wait() {
if p.cmdDoneCh != nil {
<-p.cmdDoneCh
}
}
func getExitPipe(path string) (*os.File, error) {
if err := unix.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)
}
func getControlPipe(path string) (*os.File, error) {
if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
return nil, err
}
return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
}
// Signal sends the provided signal to the process
func (p *process) Signal(s os.Signal) error {
return syscall.Kill(p.pid, s.(syscall.Signal))
}
// Start unblocks the associated container init process.
// This should only be called on the process with ID "init"
func (p *process) Start() error {
if p.ID() == InitProcessID {
var (
errC = make(chan error, 1)
args = append(p.container.runtimeArgs, "start", p.container.id)
cmd = exec.Command(p.container.runtime, args...)
)
go func() {
out, err := cmd.CombinedOutput()
if err != nil {
errC <- fmt.Errorf("%s: %q", err.Error(), out)
}
errC <- nil
}()
select {
case err := <-errC:
if err != nil {
return err
}
case <-p.cmdDoneCh:
if !p.cmdSuccess {
if cmd.Process != nil {
cmd.Process.Kill()
}
cmd.Wait()
return ErrShimExited
}
err := <-errC
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,130 +0,0 @@
package runtime
import (
"errors"
"time"
"github.com/docker/containerd/specs"
)
var (
// ErrContainerExited is returned when access to an exited
// container is attempted
ErrContainerExited = errors.New("containerd: container has exited")
// ErrProcessNotExited is returned when trying to retrieve the exit
// status of an alive process
ErrProcessNotExited = errors.New("containerd: process has not exited")
// ErrContainerNotStarted is returned when a container fails to
// start without error from the shim or the OCI runtime
ErrContainerNotStarted = errors.New("containerd: container not started")
// ErrContainerStartTimeout is returned if a container takes too
// long to start
ErrContainerStartTimeout = errors.New("containerd: container did not start before the specified timeout")
// ErrShimExited is returned if the shim or the contianer's init process
// exits before completing
ErrShimExited = errors.New("containerd: shim exited before container process was started")
errNoPidFile = errors.New("containerd: no process pid file found")
errInvalidPidInt = errors.New("containerd: process pid is invalid")
errNotImplemented = errors.New("containerd: not implemented")
)
const (
// ExitFile holds the name of the pipe used to monitor process
// exit
ExitFile = "exit"
// ExitStatusFile holds the name of the file where the container
// exit code is to be written
ExitStatusFile = "exitStatus"
// StateFile holds the name of the file where the container state
// is written
StateFile = "state.json"
// ControlFile holds the name of the pipe used to control the shim
ControlFile = "control"
// InitProcessID holds the special ID used for the very first
// container's process
InitProcessID = "init"
// StartTimeFile holds the name of the file in which the process
// start time is saved
StartTimeFile = "starttime"
// UnknownStatus is the value returned when a process exit
// status cannot be determined
UnknownStatus = 255
)
// Checkpoint holds information regarding a container checkpoint
type Checkpoint struct {
// Timestamp is the time that checkpoint happened
Created time.Time `json:"created"`
// Name is the name of the checkpoint
Name string `json:"name"`
// TCP checkpoints open tcp connections
TCP bool `json:"tcp"`
// UnixSockets persists unix sockets in the checkpoint
UnixSockets bool `json:"unixSockets"`
// Shell persists tty sessions in the checkpoint
Shell bool `json:"shell"`
// Exit exits the container after the checkpoint is finished
Exit bool `json:"exit"`
// EmptyNS tells CRIU to omit a specified namespace
EmptyNS []string `json:"emptyNS,omitempty"`
}
// PlatformProcessState container platform-specific fields in the ProcessState structure
type PlatformProcessState struct {
Checkpoint string `json:"checkpoint"`
RootUID int `json:"rootUID"`
RootGID int `json:"rootGID"`
}
// State represents a container state
type State string
// Resource regroups the various container limits that can be updated
type Resource struct {
CPUShares int64
BlkioWeight uint16
CPUPeriod int64
CPUQuota int64
CpusetCpus string
CpusetMems string
KernelMemory int64
KernelTCPMemory int64
Memory int64
MemoryReservation int64
MemorySwap int64
}
// Possible container states
const (
Paused = State("paused")
Stopped = State("stopped")
Running = State("running")
)
type state struct {
Bundle string `json:"bundle"`
Labels []string `json:"labels"`
Stdin string `json:"stdin"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
Runtime string `json:"runtime"`
RuntimeArgs []string `json:"runtimeArgs"`
Shim string `json:"shim"`
NoPivotRoot bool `json:"noPivotRoot"`
}
// ProcessState holds the process OCI specs along with various fields
// required by containerd
type ProcessState struct {
specs.ProcessSpec
Exec bool `json:"exec"`
Stdin string `json:"containerdStdin"`
Stdout string `json:"containerdStdout"`
Stderr string `json:"containerdStderr"`
RuntimeArgs []string `json:"runtimeArgs"`
NoPivotRoot bool `json:"noPivotRoot"`
PlatformProcessState
}

View File

@@ -1,180 +0,0 @@
package runtime
import (
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
"time"
utils "github.com/docker/containerd/testutils"
)
var (
devNull = "/dev/null"
stdin io.WriteCloser
runtimeTool = flag.String("runtime", "runc", "Runtime to use for this test")
)
// Create containerd state and oci bundles directory
func setup() error {
if err := os.MkdirAll(utils.StateDir, 0755); err != nil {
return err
}
if err := os.MkdirAll(utils.BundlesRoot, 0755); err != nil {
return err
}
return nil
}
// Creates the bundleDir with rootfs, io fifo dir and a default spec.
// On success, returns the bundlePath
func setupBundle(bundleName string) (string, error) {
bundlePath := filepath.Join(utils.BundlesRoot, bundleName)
if err := os.MkdirAll(bundlePath, 0755); err != nil {
fmt.Println("Unable to create bundlePath due to ", err)
return "", err
}
io := filepath.Join(bundlePath, "io")
if err := os.MkdirAll(io, 0755); err != nil {
fmt.Println("Unable to create io dir due to ", err)
return "", err
}
if err := utils.GenerateReferenceSpecs(bundlePath); err != nil {
fmt.Println("Unable to generate OCI reference spec: ", err)
return "", err
}
if err := utils.CreateBusyboxBundle(bundleName); err != nil {
fmt.Println("CreateBusyboxBundle error: ", err)
return "", err
}
return bundlePath, nil
}
func setupStdio(cwd string, bundlePath string, bundleName string) (Stdio, error) {
s := NewStdio(devNull, devNull, devNull)
pid := "init"
for stdName, stdPath := range map[string]*string{
"stdin": &s.Stdin,
"stdout": &s.Stdout,
"stderr": &s.Stderr,
} {
*stdPath = filepath.Join(cwd, bundlePath, "io", bundleName+"-"+pid+"-"+stdName)
if err := syscall.Mkfifo(*stdPath, 0755); err != nil && !os.IsExist(err) {
fmt.Println("Mkfifo error: ", err)
return s, err
}
}
err := attachStdio(s)
if err != nil {
fmt.Println("attachStdio error: ", err)
return s, err
}
return s, nil
}
func attachStdio(s Stdio) error {
stdinf, err := os.OpenFile(s.Stdin, syscall.O_RDWR, 0)
if err != nil {
return err
}
stdin = stdinf
stdoutf, err := os.OpenFile(s.Stdout, syscall.O_RDWR, 0)
if err != nil {
return err
}
go io.Copy(os.Stdout, stdoutf)
stderrf, err := os.OpenFile(s.Stderr, syscall.O_RDWR, 0)
if err != nil {
return err
}
go io.Copy(os.Stderr, stderrf)
return nil
}
func teardownBundle(bundleName string) {
containerRoot := filepath.Join(utils.StateDir, bundleName)
os.RemoveAll(containerRoot)
bundlePath := filepath.Join(utils.BundlesRoot, bundleName)
os.RemoveAll(bundlePath)
return
}
// Remove containerd state and oci bundles directory
func teardown() {
os.RemoveAll(utils.StateDir)
os.RemoveAll(utils.BundlesRoot)
}
func BenchmarkBusyboxSh(b *testing.B) {
bundleName := "busybox-sh"
wd := utils.GetTestOutDir()
if err := os.Chdir(wd); err != nil {
b.Fatalf("Could not change working directory: %v", err)
}
if err := setup(); err != nil {
b.Fatalf("Error setting up test: %v", err)
}
defer teardown()
for n := 0; n < b.N; n++ {
bundlePath, err := setupBundle(bundleName)
if err != nil {
return
}
s, err := setupStdio(wd, bundlePath, bundleName)
if err != nil {
return
}
c, err := New(ContainerOpts{
Root: utils.StateDir,
ID: bundleName,
Bundle: filepath.Join(wd, bundlePath),
Runtime: *runtimeTool,
Shim: "containerd-shim",
Timeout: 15 * time.Second,
})
if err != nil {
b.Fatalf("Error creating a New container: ", err)
}
benchmarkStartContainer(b, c, s, bundleName)
teardownBundle(bundleName)
}
}
func benchmarkStartContainer(b *testing.B, c Container, s Stdio, bundleName string) {
p, err := c.Start("", s)
if err != nil {
b.Fatalf("Error starting container %v", err)
}
kill := exec.Command(c.Runtime(), "kill", bundleName, "KILL")
kill.Run()
p.Wait()
c.Delete()
// wait for kill to finish. selected wait time is arbitrary
time.Sleep(500 * time.Millisecond)
}

View File

@@ -1,87 +0,0 @@
package runtime
import "time"
// Stat holds a container statistics
type Stat struct {
// Timestamp is the time that the statistics where collected
Timestamp time.Time
CPU CPU `json:"cpu"`
Memory Memory `json:"memory"`
Pids Pids `json:"pids"`
Blkio Blkio `json:"blkio"`
Hugetlb map[string]Hugetlb `json:"hugetlb"`
}
// Hugetlb holds information regarding a container huge tlb usage
type Hugetlb struct {
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
// BlkioEntry represents a single record for a Blkio stat
type BlkioEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,omitempty"`
Value uint64 `json:"value,omitempty"`
}
// Blkio regroups all the Blkio related stats
type Blkio struct {
IoServiceBytesRecursive []BlkioEntry `json:"ioServiceBytesRecursive,omitempty"`
IoServicedRecursive []BlkioEntry `json:"ioServicedRecursive,omitempty"`
IoQueuedRecursive []BlkioEntry `json:"ioQueueRecursive,omitempty"`
IoServiceTimeRecursive []BlkioEntry `json:"ioServiceTimeRecursive,omitempty"`
IoWaitTimeRecursive []BlkioEntry `json:"ioWaitTimeRecursive,omitempty"`
IoMergedRecursive []BlkioEntry `json:"ioMergedRecursive,omitempty"`
IoTimeRecursive []BlkioEntry `json:"ioTimeRecursive,omitempty"`
SectorsRecursive []BlkioEntry `json:"sectorsRecursive,omitempty"`
}
// Pids holds the stat of the pid usage of the machine
type Pids struct {
Current uint64 `json:"current,omitempty"`
Limit uint64 `json:"limit,omitempty"`
}
// Throttling holds a cpu throttling information
type Throttling struct {
Periods uint64 `json:"periods,omitempty"`
ThrottledPeriods uint64 `json:"throttledPeriods,omitempty"`
ThrottledTime uint64 `json:"throttledTime,omitempty"`
}
// CPUUsage holds information regarding cpu usage
type CPUUsage struct {
// Units: nanoseconds.
Total uint64 `json:"total,omitempty"`
Percpu []uint64 `json:"percpu,omitempty"`
Kernel uint64 `json:"kernel"`
User uint64 `json:"user"`
}
// CPU regroups both a CPU usage and throttling information
type CPU struct {
Usage CPUUsage `json:"usage,omitempty"`
Throttling Throttling `json:"throttling,omitempty"`
}
// MemoryEntry regroups statistic about a given type of memory
type MemoryEntry struct {
Limit uint64 `json:"limit"`
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
// Memory holds information regarding the different type of memories available
type Memory struct {
Cache uint64 `json:"cache,omitempty"`
Usage MemoryEntry `json:"usage,omitempty"`
Swap MemoryEntry `json:"swap,omitempty"`
Kernel MemoryEntry `json:"kernel,omitempty"`
KernelTCP MemoryEntry `json:"kernelTCP,omitempty"`
Raw map[string]uint64 `json:"raw,omitempty"`
}