2017-02-24 23:50:59 +00:00
|
|
|
package linux
|
2017-02-13 18:23:28 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/docker/containerd"
|
|
|
|
"github.com/docker/containerd/api/services/shim"
|
|
|
|
"github.com/docker/containerd/api/types/container"
|
|
|
|
"github.com/docker/containerd/api/types/mount"
|
2017-02-15 22:24:15 +00:00
|
|
|
"github.com/docker/containerd/log"
|
2017-03-07 01:23:00 +00:00
|
|
|
"github.com/docker/containerd/plugin"
|
2017-02-13 18:23:28 +00:00
|
|
|
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
runtimeName = "linux"
|
|
|
|
configFilename = "config.json"
|
2017-03-07 00:26:19 +00:00
|
|
|
defaultRuntime = "runc"
|
2017-02-13 18:23:28 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2017-03-07 01:23:00 +00:00
|
|
|
plugin.Register(runtimeName, &plugin.Registration{
|
2017-03-07 00:26:19 +00:00
|
|
|
Type: plugin.RuntimePlugin,
|
|
|
|
Init: New,
|
|
|
|
Config: &Config{},
|
2017-02-16 22:45:13 +00:00
|
|
|
})
|
2017-02-13 18:23:28 +00:00
|
|
|
}
|
|
|
|
|
2017-03-07 00:26:19 +00:00
|
|
|
type Config struct {
|
|
|
|
// Runtime is a path or name of an OCI runtime used by the shim
|
|
|
|
Runtime string `toml:"runtime"`
|
2017-03-10 23:28:21 +00:00
|
|
|
// NoShim calls runc directly from within the pkg
|
|
|
|
NoShim bool `toml:"no_shim"`
|
2017-03-07 00:26:19 +00:00
|
|
|
}
|
|
|
|
|
2017-03-10 00:07:35 +00:00
|
|
|
func New(ic *plugin.InitContext) (interface{}, error) {
|
2017-03-23 22:40:09 +00:00
|
|
|
path := filepath.Join(ic.Root, runtimeName)
|
2017-02-22 00:35:42 +00:00
|
|
|
if err := os.MkdirAll(path, 0700); err != nil {
|
2017-02-13 18:23:28 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2017-03-07 00:26:19 +00:00
|
|
|
cfg := ic.Config.(*Config)
|
|
|
|
if cfg.Runtime == "" {
|
|
|
|
cfg.Runtime = defaultRuntime
|
|
|
|
}
|
2017-02-16 22:45:13 +00:00
|
|
|
c, cancel := context.WithCancel(ic.Context)
|
2017-02-13 18:23:28 +00:00
|
|
|
return &Runtime{
|
2017-02-22 00:35:42 +00:00
|
|
|
root: path,
|
2017-03-10 23:28:21 +00:00
|
|
|
remote: !cfg.NoShim,
|
2017-03-07 00:26:19 +00:00
|
|
|
runtime: cfg.Runtime,
|
2017-02-13 18:23:28 +00:00
|
|
|
events: make(chan *containerd.Event, 2048),
|
|
|
|
eventsContext: c,
|
|
|
|
eventsCancel: cancel,
|
2017-03-21 20:08:49 +00:00
|
|
|
monitor: ic.Monitor,
|
2017-02-13 18:23:28 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type Runtime struct {
|
2017-03-07 00:26:19 +00:00
|
|
|
root string
|
|
|
|
runtime string
|
2017-03-10 23:28:21 +00:00
|
|
|
remote bool
|
2017-02-13 18:23:28 +00:00
|
|
|
|
|
|
|
events chan *containerd.Event
|
|
|
|
eventsContext context.Context
|
|
|
|
eventsCancel func()
|
2017-03-21 20:08:49 +00:00
|
|
|
monitor plugin.ContainerMonitor
|
2017-02-13 18:23:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) Create(ctx context.Context, id string, opts containerd.CreateOpts) (containerd.Container, error) {
|
|
|
|
path, err := r.newBundle(id, opts.Spec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-03-10 23:28:21 +00:00
|
|
|
s, err := newShim(path, r.remote)
|
2017-02-13 18:23:28 +00:00
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(path)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := r.handleEvents(s); err != nil {
|
|
|
|
os.RemoveAll(path)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sopts := &shim.CreateRequest{
|
|
|
|
ID: id,
|
|
|
|
Bundle: path,
|
2017-03-07 00:26:19 +00:00
|
|
|
Runtime: r.runtime,
|
2017-02-13 18:23:28 +00:00
|
|
|
Stdin: opts.IO.Stdin,
|
|
|
|
Stdout: opts.IO.Stdout,
|
|
|
|
Stderr: opts.IO.Stderr,
|
|
|
|
Terminal: opts.IO.Terminal,
|
|
|
|
}
|
|
|
|
for _, m := range opts.Rootfs {
|
|
|
|
sopts.Rootfs = append(sopts.Rootfs, &mount.Mount{
|
|
|
|
Type: m.Type,
|
|
|
|
Source: m.Source,
|
|
|
|
Options: m.Options,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if _, err := s.Create(ctx, sopts); err != nil {
|
|
|
|
os.RemoveAll(path)
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-03-21 20:08:49 +00:00
|
|
|
c := &Container{
|
2017-02-13 18:23:28 +00:00
|
|
|
id: id,
|
|
|
|
shim: s,
|
2017-03-21 20:08:49 +00:00
|
|
|
}
|
|
|
|
// after the container is create add it to the monitor
|
|
|
|
if err := r.monitor.Monitor(c); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return c, nil
|
2017-02-13 18:23:28 +00:00
|
|
|
}
|
|
|
|
|
2017-03-01 14:58:05 +00:00
|
|
|
func (r *Runtime) Delete(ctx context.Context, c containerd.Container) (uint32, error) {
|
2017-02-13 18:23:28 +00:00
|
|
|
lc, ok := c.(*Container)
|
|
|
|
if !ok {
|
2017-03-01 14:58:05 +00:00
|
|
|
return 0, fmt.Errorf("container cannot be cast as *linux.Container")
|
2017-02-13 18:23:28 +00:00
|
|
|
}
|
2017-03-21 20:08:49 +00:00
|
|
|
// remove the container from the monitor
|
|
|
|
if err := r.monitor.Stop(lc); err != nil {
|
|
|
|
// TODO: log error here
|
|
|
|
return 0, err
|
|
|
|
}
|
2017-03-01 14:58:05 +00:00
|
|
|
rsp, err := lc.shim.Delete(ctx, &shim.DeleteRequest{})
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
2017-02-13 18:23:28 +00:00
|
|
|
}
|
|
|
|
lc.shim.Exit(ctx, &shim.ExitRequest{})
|
2017-03-01 14:58:05 +00:00
|
|
|
return rsp.ExitStatus, r.deleteBundle(lc.id)
|
2017-02-13 18:23:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) Containers() ([]containerd.Container, error) {
|
|
|
|
dir, err := ioutil.ReadDir(r.root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var o []containerd.Container
|
|
|
|
for _, fi := range dir {
|
|
|
|
if !fi.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
2017-02-16 22:45:13 +00:00
|
|
|
c, err := r.loadContainer(filepath.Join(r.root, fi.Name()))
|
2017-02-13 18:23:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
o = append(o, c)
|
|
|
|
}
|
|
|
|
return o, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) Events(ctx context.Context) <-chan *containerd.Event {
|
|
|
|
return r.events
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) handleEvents(s shim.ShimClient) error {
|
|
|
|
events, err := s.Events(r.eventsContext, &shim.EventsRequest{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
go r.forward(events)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) forward(events shim.Shim_EventsClient) {
|
|
|
|
for {
|
|
|
|
e, err := events.Recv()
|
|
|
|
if err != nil {
|
|
|
|
log.G(r.eventsContext).WithError(err).Error("get event from shim")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var et containerd.EventType
|
|
|
|
switch e.Type {
|
|
|
|
case container.Event_CREATE:
|
|
|
|
et = containerd.CreateEvent
|
|
|
|
case container.Event_EXEC_ADDED:
|
|
|
|
et = containerd.ExecAddEvent
|
|
|
|
case container.Event_EXIT:
|
|
|
|
et = containerd.ExitEvent
|
|
|
|
case container.Event_OOM:
|
|
|
|
et = containerd.OOMEvent
|
|
|
|
case container.Event_START:
|
|
|
|
et = containerd.StartEvent
|
|
|
|
}
|
|
|
|
r.events <- &containerd.Event{
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
Runtime: runtimeName,
|
|
|
|
Type: et,
|
|
|
|
Pid: e.Pid,
|
|
|
|
ID: e.ID,
|
|
|
|
ExitStatus: e.ExitStatus,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) newBundle(id string, spec []byte) (string, error) {
|
|
|
|
path := filepath.Join(r.root, id)
|
|
|
|
if err := os.Mkdir(path, 0700); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if err := os.Mkdir(filepath.Join(path, "rootfs"), 0700); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
f, err := os.Create(filepath.Join(path, configFilename))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2017-02-28 16:49:37 +00:00
|
|
|
defer f.Close()
|
2017-02-13 18:23:28 +00:00
|
|
|
_, err = io.Copy(f, bytes.NewReader(spec))
|
|
|
|
return path, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) deleteBundle(id string) error {
|
|
|
|
return os.RemoveAll(filepath.Join(r.root, id))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runtime) loadContainer(path string) (*Container, error) {
|
|
|
|
id := filepath.Base(path)
|
2017-03-10 23:28:21 +00:00
|
|
|
s, err := loadShim(path, r.remote)
|
2017-02-13 18:23:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Container{
|
|
|
|
id: id,
|
|
|
|
shim: s,
|
|
|
|
}, nil
|
|
|
|
}
|