mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
6a2f385aea
The existing logic to handle container ID conflicts when attempting to create a plugin container is not nearly as robust as the implementation in daemon for user containers. Extract and refine the logic from daemon and use it in the plugin executor. Signed-off-by: Cory Snider <csnider@mirantis.com>
208 lines
5.5 KiB
Go
208 lines
5.5 KiB
Go
package containerd // import "github.com/docker/docker/plugin/executor/containerd"
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/containerd/containerd/cio"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/libcontainerd"
|
|
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// ExitHandler represents an object that is called when the exit event is received from containerd
|
|
type ExitHandler interface {
|
|
HandleExitEvent(id string) error
|
|
}
|
|
|
|
// New creates a new containerd plugin executor
|
|
func New(ctx context.Context, rootDir string, cli *containerd.Client, ns string, exitHandler ExitHandler, runtime types.Runtime) (*Executor, error) {
|
|
e := &Executor{
|
|
rootDir: rootDir,
|
|
exitHandler: exitHandler,
|
|
runtime: runtime,
|
|
plugins: make(map[string]*c8dPlugin),
|
|
}
|
|
|
|
client, err := libcontainerd.NewClient(ctx, cli, rootDir, ns, e)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error creating containerd exec client")
|
|
}
|
|
e.client = client
|
|
return e, nil
|
|
}
|
|
|
|
// Executor is the containerd client implementation of a plugin executor
|
|
type Executor struct {
|
|
rootDir string
|
|
client libcontainerdtypes.Client
|
|
exitHandler ExitHandler
|
|
runtime types.Runtime
|
|
|
|
mu sync.Mutex // Guards plugins map
|
|
plugins map[string]*c8dPlugin
|
|
}
|
|
|
|
type c8dPlugin struct {
|
|
log *logrus.Entry
|
|
ctr libcontainerdtypes.Container
|
|
tsk libcontainerdtypes.Task
|
|
}
|
|
|
|
// deleteTaskAndContainer deletes plugin task and then plugin container from containerd
|
|
func (p c8dPlugin) deleteTaskAndContainer(ctx context.Context) {
|
|
if p.tsk != nil {
|
|
if _, err := p.tsk.Delete(ctx); err != nil && !errdefs.IsNotFound(err) {
|
|
p.log.WithError(err).Error("failed to delete plugin task from containerd")
|
|
}
|
|
}
|
|
if p.ctr != nil {
|
|
if err := p.ctr.Delete(ctx); err != nil && !errdefs.IsNotFound(err) {
|
|
p.log.WithError(err).Error("failed to delete plugin container from containerd")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create creates a new container
|
|
func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
|
|
ctx := context.Background()
|
|
log := logrus.WithField("plugin", id)
|
|
ctr, err := libcontainerd.ReplaceContainer(ctx, e.client, id, &spec, e.runtime.Shim.Binary, e.runtime.Shim.Opts)
|
|
if err != nil {
|
|
return errors.Wrap(err, "error creating containerd container for plugin")
|
|
}
|
|
|
|
p := c8dPlugin{log: log, ctr: ctr}
|
|
p.tsk, err = ctr.Start(ctx, "", false, attachStreamsFunc(stdout, stderr))
|
|
if err != nil {
|
|
p.deleteTaskAndContainer(ctx)
|
|
return err
|
|
}
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
e.plugins[id] = &p
|
|
return nil
|
|
}
|
|
|
|
// Restore restores a container
|
|
func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) {
|
|
ctx := context.Background()
|
|
p := c8dPlugin{log: logrus.WithField("plugin", id)}
|
|
ctr, err := e.client.LoadContainer(ctx, id)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
p.tsk, err = ctr.AttachTask(ctx, attachStreamsFunc(stdout, stderr))
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
p.deleteTaskAndContainer(ctx)
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
s, err := p.tsk.Status(ctx)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
// Task vanished after attaching?
|
|
p.tsk = nil
|
|
p.deleteTaskAndContainer(ctx)
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
if s.Status == containerd.Stopped {
|
|
p.deleteTaskAndContainer(ctx)
|
|
return false, nil
|
|
}
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
e.plugins[id] = &p
|
|
return true, nil
|
|
}
|
|
|
|
// IsRunning returns if the container with the given id is running
|
|
func (e *Executor) IsRunning(id string) (bool, error) {
|
|
e.mu.Lock()
|
|
p := e.plugins[id]
|
|
e.mu.Unlock()
|
|
if p == nil {
|
|
return false, errdefs.NotFound(fmt.Errorf("unknown plugin %q", id))
|
|
}
|
|
status, err := p.tsk.Status(context.Background())
|
|
return status.Status == containerd.Running, err
|
|
}
|
|
|
|
// Signal sends the specified signal to the container
|
|
func (e *Executor) Signal(id string, signal syscall.Signal) error {
|
|
e.mu.Lock()
|
|
p := e.plugins[id]
|
|
e.mu.Unlock()
|
|
if p == nil {
|
|
return errdefs.NotFound(fmt.Errorf("unknown plugin %q", id))
|
|
}
|
|
return p.tsk.Kill(context.Background(), signal)
|
|
}
|
|
|
|
// ProcessEvent handles events from containerd
|
|
// All events are ignored except the exit event, which is sent of to the stored handler
|
|
func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
|
|
switch et {
|
|
case libcontainerdtypes.EventExit:
|
|
e.mu.Lock()
|
|
p := e.plugins[id]
|
|
e.mu.Unlock()
|
|
if p == nil {
|
|
logrus.WithField("id", id).Warn("Received exit event for an unknown plugin")
|
|
} else {
|
|
p.deleteTaskAndContainer(context.Background())
|
|
}
|
|
return e.exitHandler.HandleExitEvent(ei.ContainerID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type rio struct {
|
|
cio.IO
|
|
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func (c *rio) Wait() {
|
|
c.wg.Wait()
|
|
c.IO.Wait()
|
|
}
|
|
|
|
func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
|
|
return func(iop *cio.DirectIO) (cio.IO, error) {
|
|
if iop.Stdin != nil {
|
|
iop.Stdin.Close()
|
|
// closing stdin shouldn't be needed here, it should never be open
|
|
panic("plugin stdin shouldn't have been created!")
|
|
}
|
|
|
|
rio := &rio{IO: iop}
|
|
rio.wg.Add(2)
|
|
go func() {
|
|
io.Copy(stdout, iop.Stdout)
|
|
stdout.Close()
|
|
rio.wg.Done()
|
|
}()
|
|
go func() {
|
|
io.Copy(stderr, iop.Stderr)
|
|
stderr.Close()
|
|
rio.wg.Done()
|
|
}()
|
|
return rio, nil
|
|
}
|
|
}
|