2015-12-01 11:56:08 -08:00
|
|
|
package runtime
|
2015-11-05 15:29:53 -08:00
|
|
|
|
2015-11-10 14:57:10 -08:00
|
|
|
import (
|
2016-02-01 11:02:41 -08:00
|
|
|
"encoding/json"
|
2016-04-26 13:29:35 -07:00
|
|
|
"fmt"
|
2016-02-29 14:15:16 -08:00
|
|
|
"io"
|
2016-02-01 11:02:41 -08:00
|
|
|
"io/ioutil"
|
2015-11-10 14:57:10 -08:00
|
|
|
"os"
|
2016-03-14 13:57:28 -07:00
|
|
|
"os/exec"
|
2016-02-01 11:02:41 -08:00
|
|
|
"path/filepath"
|
2016-04-26 13:29:35 -07:00
|
|
|
"syscall"
|
2016-03-28 13:30:37 -07:00
|
|
|
"time"
|
2015-11-10 14:57:10 -08:00
|
|
|
|
2016-02-01 11:02:41 -08:00
|
|
|
"github.com/Sirupsen/logrus"
|
2016-02-29 10:48:39 -08:00
|
|
|
"github.com/docker/containerd/specs"
|
2016-04-26 13:29:35 -07:00
|
|
|
ocs "github.com/opencontainers/runtime-spec/specs-go"
|
2015-11-10 14:57:10 -08:00
|
|
|
)
|
2015-11-10 13:44:35 -08:00
|
|
|
|
2016-06-03 15:00:49 -07:00
|
|
|
// Container defines the operations allowed on a container
|
2015-11-05 15:29:53 -08:00
|
|
|
type Container interface {
|
2015-11-12 13:40:23 -08:00
|
|
|
// ID returns the container ID
|
2015-11-05 16:40:57 -08:00
|
|
|
ID() string
|
2015-11-12 13:40:23 -08:00
|
|
|
// Path returns the path to the bundle
|
2015-11-10 11:38:26 -08:00
|
|
|
Path() string
|
2016-01-06 13:32:46 -08:00
|
|
|
// Start starts the init process of the container
|
2016-05-25 20:42:37 -04:00
|
|
|
Start(checkpointPath string, s Stdio) (Process, error)
|
2016-02-01 11:02:41 -08:00
|
|
|
// Exec starts another process in an existing container
|
2016-02-29 10:48:39 -08:00
|
|
|
Exec(string, specs.ProcessSpec, Stdio) (Process, error)
|
2016-01-06 13:32:46 -08:00
|
|
|
// Delete removes the container's state and any resources
|
2015-11-05 15:29:53 -08:00
|
|
|
Delete() error
|
2015-11-12 13:40:23 -08:00
|
|
|
// Processes returns all the containers processes that have been added
|
2015-11-10 13:44:35 -08:00
|
|
|
Processes() ([]Process, error)
|
2015-11-12 13:40:23 -08:00
|
|
|
// State returns the containers runtime state
|
|
|
|
State() State
|
|
|
|
// Resume resumes a paused container
|
|
|
|
Resume() error
|
|
|
|
// Pause pauses a running container
|
|
|
|
Pause() error
|
2016-02-01 15:07:02 -08:00
|
|
|
// RemoveProcess removes the specified process from the container
|
2016-02-01 11:02:41 -08:00
|
|
|
RemoveProcess(string) error
|
2015-12-07 14:47:03 -08:00
|
|
|
// Checkpoints returns all the checkpoints for a container
|
2016-05-25 20:42:37 -04:00
|
|
|
Checkpoints(checkpointDir string) ([]Checkpoint, error)
|
2015-12-07 14:47:03 -08:00
|
|
|
// Checkpoint creates a new checkpoint
|
2016-05-25 20:42:37 -04:00
|
|
|
Checkpoint(checkpoint Checkpoint, checkpointDir string) error
|
2015-12-07 14:47:03 -08:00
|
|
|
// DeleteCheckpoint deletes the checkpoint for the provided name
|
2016-05-25 20:42:37 -04:00
|
|
|
DeleteCheckpoint(name string, checkpointDir string) error
|
2016-02-11 13:44:25 -08:00
|
|
|
// Labels are user provided labels for the container
|
|
|
|
Labels() []string
|
2016-02-11 14:07:34 -08:00
|
|
|
// Pids returns all pids inside the container
|
|
|
|
Pids() ([]int, error)
|
2015-12-08 10:04:31 -08:00
|
|
|
// Stats returns realtime container stats and resource information
|
2016-02-11 14:36:32 -08:00
|
|
|
Stats() (*Stat, error)
|
2016-02-25 12:59:34 -08:00
|
|
|
// Name or path of the OCI compliant runtime used to execute the container
|
|
|
|
Runtime() string
|
2016-02-11 14:36:32 -08:00
|
|
|
// OOM signals the channel if the container received an OOM notification
|
2016-02-29 14:15:16 -08:00
|
|
|
OOM() (OOM, error)
|
2016-03-07 15:23:52 -08:00
|
|
|
// UpdateResource updates the containers resources to new values
|
|
|
|
UpdateResources(*Resource) error
|
2016-04-22 15:49:55 -04:00
|
|
|
|
|
|
|
// Status return the current status of the container.
|
|
|
|
Status() (State, error)
|
2016-02-29 14:15:16 -08:00
|
|
|
}
|
|
|
|
|
2016-06-03 15:00:49 -07:00
|
|
|
// OOM wraps a container OOM.
|
2016-02-29 14:15:16 -08:00
|
|
|
type OOM interface {
|
|
|
|
io.Closer
|
|
|
|
FD() int
|
|
|
|
ContainerID() string
|
|
|
|
Flush()
|
|
|
|
Removed() bool
|
2015-11-05 15:29:53 -08:00
|
|
|
}
|
2016-02-01 11:02:41 -08:00
|
|
|
|
2016-06-03 15:00:49 -07:00
|
|
|
// Stdio holds the path to the 3 pipes used for the standard ios.
|
2016-02-03 13:56:15 -08:00
|
|
|
type Stdio struct {
|
|
|
|
Stdin string
|
|
|
|
Stdout string
|
|
|
|
Stderr string
|
|
|
|
}
|
|
|
|
|
2016-06-03 15:00:49 -07:00
|
|
|
// 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"
|
2016-02-11 12:20:29 -08:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-03 15:00:49 -07:00
|
|
|
// ContainerOpts keeps the options passed at container creation
|
2016-03-30 14:25:42 -07:00
|
|
|
type ContainerOpts struct {
|
|
|
|
Root string
|
|
|
|
ID string
|
|
|
|
Bundle string
|
|
|
|
Runtime string
|
|
|
|
RuntimeArgs []string
|
2016-04-06 14:42:47 +08:00
|
|
|
Shim string
|
2016-03-30 14:25:42 -07:00
|
|
|
Labels []string
|
|
|
|
NoPivotRoot bool
|
2016-03-28 13:30:37 -07:00
|
|
|
Timeout time.Duration
|
2016-03-30 14:25:42 -07:00
|
|
|
}
|
|
|
|
|
2016-02-01 11:02:41 -08:00
|
|
|
// New returns a new container
|
2016-03-30 14:25:42 -07:00
|
|
|
func New(opts ContainerOpts) (Container, error) {
|
2016-02-01 11:02:41 -08:00
|
|
|
c := &container{
|
2016-03-30 14:25:42 -07:00
|
|
|
root: opts.Root,
|
|
|
|
id: opts.ID,
|
|
|
|
bundle: opts.Bundle,
|
|
|
|
labels: opts.Labels,
|
2016-03-24 13:30:27 -07:00
|
|
|
processes: make(map[string]*process),
|
2016-03-30 14:25:42 -07:00
|
|
|
runtime: opts.Runtime,
|
|
|
|
runtimeArgs: opts.RuntimeArgs,
|
2016-04-06 14:42:47 +08:00
|
|
|
shim: opts.Shim,
|
2016-03-30 14:25:42 -07:00
|
|
|
noPivotRoot: opts.NoPivotRoot,
|
2016-03-28 13:30:37 -07:00
|
|
|
timeout: opts.Timeout,
|
2016-02-01 11:02:41 -08:00
|
|
|
}
|
2016-03-30 14:25:42 -07:00
|
|
|
if err := os.Mkdir(filepath.Join(c.root, c.id), 0755); err != nil {
|
2016-02-01 11:02:41 -08:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-03-30 14:25:42 -07:00
|
|
|
f, err := os.Create(filepath.Join(c.root, c.id, StateFile))
|
2016-02-01 11:02:41 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
if err := json.NewEncoder(f).Encode(state{
|
2016-03-30 14:25:42 -07:00
|
|
|
Bundle: c.bundle,
|
|
|
|
Labels: c.labels,
|
|
|
|
Runtime: c.runtime,
|
|
|
|
RuntimeArgs: c.runtimeArgs,
|
2016-05-02 14:01:48 -07:00
|
|
|
Shim: c.shim,
|
2016-03-30 14:25:42 -07:00
|
|
|
NoPivotRoot: opts.NoPivotRoot,
|
2016-02-01 11:02:41 -08:00
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2016-06-03 15:00:49 -07:00
|
|
|
// Load return a new container from the matchin state file on disk.
|
2016-05-02 14:03:01 -07:00
|
|
|
func Load(root, id string, timeout time.Duration) (Container, error) {
|
2016-02-01 11:02:41 -08:00
|
|
|
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{
|
2016-03-24 13:30:27 -07:00
|
|
|
root: root,
|
|
|
|
id: id,
|
|
|
|
bundle: s.Bundle,
|
|
|
|
labels: s.Labels,
|
|
|
|
runtime: s.Runtime,
|
|
|
|
runtimeArgs: s.RuntimeArgs,
|
2016-04-06 14:42:47 +08:00
|
|
|
shim: s.Shim,
|
2016-03-30 14:25:42 -07:00
|
|
|
noPivotRoot: s.NoPivotRoot,
|
2016-03-24 13:30:27 -07:00
|
|
|
processes: make(map[string]*process),
|
2016-05-02 14:03:01 -07:00
|
|
|
timeout: timeout,
|
2016-02-01 11:02:41 -08:00
|
|
|
}
|
|
|
|
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()
|
2016-02-03 13:56:15 -08:00
|
|
|
s, err := readProcessState(filepath.Join(root, id, pid))
|
2016-02-01 11:02:41 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-02-03 13:56:15 -08:00
|
|
|
p, err := loadProcess(filepath.Join(root, id, pid), pid, c, s)
|
2016-02-01 11:02:41 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-02-03 13:56:15 -08:00
|
|
|
func readProcessState(dir string) (*ProcessState, error) {
|
2016-02-01 11:02:41 -08:00
|
|
|
f, err := os.Open(filepath.Join(dir, "process.json"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2016-02-03 13:56:15 -08:00
|
|
|
var s ProcessState
|
2016-02-01 11:02:41 -08:00
|
|
|
if err := json.NewDecoder(f).Decode(&s); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type container struct {
|
|
|
|
// path to store runtime state information
|
2016-03-24 13:30:27 -07:00
|
|
|
root string
|
|
|
|
id string
|
|
|
|
bundle string
|
|
|
|
runtime string
|
|
|
|
runtimeArgs []string
|
2016-04-06 14:42:47 +08:00
|
|
|
shim string
|
2016-03-24 13:30:27 -07:00
|
|
|
processes map[string]*process
|
|
|
|
labels []string
|
|
|
|
oomFds []int
|
2016-03-30 14:25:42 -07:00
|
|
|
noPivotRoot bool
|
2016-03-28 13:30:37 -07:00
|
|
|
timeout time.Duration
|
2016-02-01 11:02:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *container) ID() string {
|
|
|
|
return c.id
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *container) Path() string {
|
|
|
|
return c.bundle
|
|
|
|
}
|
|
|
|
|
2016-02-11 13:44:25 -08:00
|
|
|
func (c *container) Labels() []string {
|
|
|
|
return c.labels
|
|
|
|
}
|
|
|
|
|
2016-03-15 11:31:32 -07:00
|
|
|
func (c *container) readSpec() (*specs.Spec, error) {
|
|
|
|
var spec specs.Spec
|
2016-02-01 11:02:41 -08:00
|
|
|
f, err := os.Open(filepath.Join(c.bundle, "config.json"))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2016-03-10 23:08:54 -08:00
|
|
|
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
2016-02-01 11:02:41 -08:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &spec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *container) Delete() error {
|
2016-03-14 13:57:28 -07:00
|
|
|
err := os.RemoveAll(filepath.Join(c.root, c.id))
|
2016-03-29 10:26:37 -07:00
|
|
|
|
|
|
|
args := c.runtimeArgs
|
|
|
|
args = append(args, "delete", c.id)
|
2016-03-28 13:30:37 -07:00
|
|
|
if derr := exec.Command(c.runtime, args...).Run(); err == nil {
|
|
|
|
err = derr
|
|
|
|
}
|
2016-03-14 13:57:28 -07:00
|
|
|
return err
|
2016-02-01 11:02:41 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2016-02-03 13:56:15 -08:00
|
|
|
return os.RemoveAll(filepath.Join(c.root, c.id, pid))
|
2016-02-01 11:02:41 -08:00
|
|
|
}
|
2016-03-07 15:23:52 -08:00
|
|
|
|
2016-04-26 13:29:35 -07:00
|
|
|
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 {
|
2016-05-10 20:26:41 -07:00
|
|
|
return fmt.Errorf("%s: %q", err.Error(), string(b))
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
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 {
|
2016-05-10 20:26:41 -07:00
|
|
|
return fmt.Errorf("%s: %q", err.Error(), string(b))
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-05-25 20:42:37 -04:00
|
|
|
func (c *container) Checkpoints(checkpointDir string) ([]Checkpoint, error) {
|
|
|
|
if checkpointDir == "" {
|
|
|
|
checkpointDir = filepath.Join(c.bundle, "checkpoints")
|
|
|
|
}
|
|
|
|
|
|
|
|
dirs, err := ioutil.ReadDir(checkpointDir)
|
2016-04-26 13:29:35 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var out []Checkpoint
|
|
|
|
for _, d := range dirs {
|
|
|
|
if !d.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
2016-05-25 20:42:37 -04:00
|
|
|
path := filepath.Join(checkpointDir, d.Name(), "config.json")
|
2016-04-26 13:29:35 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-05-25 20:42:37 -04:00
|
|
|
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 {
|
2016-04-26 13:29:35 -07:00
|
|
|
return err
|
|
|
|
}
|
2016-05-25 20:42:37 -04:00
|
|
|
|
|
|
|
path := filepath.Join(checkpointDir, cpt.Name)
|
2016-04-26 13:29:35 -07:00
|
|
|
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,
|
2016-06-07 17:05:22 -04:00
|
|
|
"--work-path", filepath.Join(path, "criu.work"),
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
add := func(flags ...string) {
|
|
|
|
args = append(args, flags...)
|
|
|
|
}
|
|
|
|
add(c.runtimeArgs...)
|
|
|
|
if !cpt.Exit {
|
|
|
|
add("--leave-running")
|
|
|
|
}
|
|
|
|
if cpt.Shell {
|
|
|
|
add("--shell-job")
|
|
|
|
}
|
2016-06-03 15:00:49 -07:00
|
|
|
if cpt.TCP {
|
2016-04-26 13:29:35 -07:00
|
|
|
add("--tcp-established")
|
|
|
|
}
|
|
|
|
if cpt.UnixSockets {
|
|
|
|
add("--ext-unix-sk")
|
|
|
|
}
|
2016-06-07 16:25:54 -04:00
|
|
|
for _, ns := range cpt.EmptyNS {
|
|
|
|
add("--empty-ns", ns)
|
|
|
|
}
|
2016-04-26 13:29:35 -07:00
|
|
|
add(c.id)
|
|
|
|
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
|
|
|
if err != nil {
|
2016-05-10 20:26:41 -07:00
|
|
|
return fmt.Errorf("%s: %q", err.Error(), string(out))
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-05-25 20:42:37 -04:00
|
|
|
func (c *container) DeleteCheckpoint(name string, checkpointDir string) error {
|
|
|
|
if checkpointDir == "" {
|
|
|
|
checkpointDir = filepath.Join(c.bundle, "checkpoints")
|
|
|
|
}
|
|
|
|
return os.RemoveAll(filepath.Join(checkpointDir, name))
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
|
2016-05-25 20:42:37 -04:00
|
|
|
func (c *container) Start(checkpointPath string, s Stdio) (Process, error) {
|
2016-04-26 13:29:35 -07:00
|
|
|
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{
|
2016-05-25 20:42:37 -04:00
|
|
|
checkpoint: checkpointPath,
|
2016-04-26 13:29:35 -07:00
|
|
|
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
|
|
|
|
}
|
2016-06-09 13:33:26 -07:00
|
|
|
if err := c.createCmd(InitProcessID, cmd, p); err != nil {
|
2016-04-26 13:29:35 -07:00
|
|
|
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
|
|
|
|
}
|
2016-06-09 13:33:26 -07:00
|
|
|
if err := c.createCmd(pid, cmd, p); err != nil {
|
2016-04-26 13:29:35 -07:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
2016-06-09 13:33:26 -07:00
|
|
|
func (c *container) createCmd(pid string, cmd *exec.Cmd, p *process) error {
|
2016-04-27 12:00:29 -07:00
|
|
|
p.cmd = cmd
|
2016-04-26 13:29:35 -07:00
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
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
|
|
|
|
}
|
2016-06-09 13:33:26 -07:00
|
|
|
if err := c.waitForCreate(p, cmd); err != nil {
|
2016-04-26 13:29:35 -07:00
|
|
|
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) {
|
2016-04-26 15:01:24 -07:00
|
|
|
args := c.runtimeArgs
|
|
|
|
args = append(args, "ps", "--format=json", c.id)
|
|
|
|
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
2016-04-26 13:29:35 -07:00
|
|
|
if err != nil {
|
2016-05-10 20:26:41 -07:00
|
|
|
return nil, fmt.Errorf("%s: %q", err.Error(), out)
|
2016-04-26 14:40:05 -07:00
|
|
|
}
|
|
|
|
var pids []int
|
|
|
|
if err := json.Unmarshal(out, &pids); err != nil {
|
2016-04-26 13:29:35 -07:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-04-26 14:40:05 -07:00
|
|
|
return pids, nil
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *container) Stats() (*Stat, error) {
|
|
|
|
now := time.Now()
|
2016-04-26 15:01:24 -07:00
|
|
|
args := c.runtimeArgs
|
|
|
|
args = append(args, "events", "--stats", c.id)
|
|
|
|
out, err := exec.Command(c.runtime, args...).CombinedOutput()
|
2016-04-26 13:29:35 -07:00
|
|
|
if err != nil {
|
2016-05-10 20:26:41 -07:00
|
|
|
return nil, fmt.Errorf("%s: %q", err.Error(), out)
|
2016-04-26 14:57:30 -07:00
|
|
|
}
|
|
|
|
s := struct {
|
|
|
|
Data *Stat `json:"data"`
|
|
|
|
}{}
|
|
|
|
if err := json.Unmarshal(out, &s); err != nil {
|
2016-04-26 13:29:35 -07:00
|
|
|
return nil, err
|
|
|
|
}
|
2016-04-26 14:57:30 -07:00
|
|
|
s.Data.Timestamp = now
|
|
|
|
return s.Data, nil
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2016-05-10 20:26:41 -07:00
|
|
|
return "", fmt.Errorf("%s: %q", err.Error(), out)
|
2016-04-26 13:29:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2016-06-09 13:33:26 -07:00
|
|
|
func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error {
|
2016-04-26 13:29:35 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
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) {
|
2016-04-27 12:00:29 -07:00
|
|
|
if _, err := syscall.Wait4(cmd.Process.Pid, nil, syscall.WNOHANG, nil); err == nil {
|
|
|
|
return true, nil
|
|
|
|
}
|
2016-04-26 13:29:35 -07:00
|
|
|
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() {
|
|
|
|
buf := make([]byte, 8)
|
|
|
|
syscall.Read(o.eventfd, buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|