1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/plugin/executor/containerd/containerd.go
Cory Snider 6a2f385aea Share logic to create-or-replace a container
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>
2022-08-24 14:59:08 -04:00

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
}
}