github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/plugin/executor/containerd/containerd.go (about)

     1  package containerd // import "github.com/docker/docker/plugin/executor/containerd"
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"path/filepath"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/containerd/containerd"
    11  	"github.com/containerd/containerd/cio"
    12  	"github.com/containerd/containerd/runtime/linux/runctypes"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/docker/docker/libcontainerd"
    15  	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
    16  	"github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  // PluginNamespace is the name used for the plugins namespace
    22  const PluginNamespace = "plugins.moby"
    23  
    24  // ExitHandler represents an object that is called when the exit event is received from containerd
    25  type ExitHandler interface {
    26  	HandleExitEvent(id string) error
    27  }
    28  
    29  // Client is used by the exector to perform operations.
    30  // TODO(@cpuguy83): This should really just be based off the containerd client interface.
    31  // However right now this whole package is tied to github.com/docker/docker/libcontainerd
    32  type Client interface {
    33  	Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
    34  	Restore(ctx context.Context, containerID string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error)
    35  	Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error)
    36  	Delete(ctx context.Context, containerID string) error
    37  	DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error)
    38  	Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error)
    39  	SignalProcess(ctx context.Context, containerID, processID string, signal int) error
    40  }
    41  
    42  // New creates a new containerd plugin executor
    43  func New(ctx context.Context, rootDir string, cli *containerd.Client, exitHandler ExitHandler) (*Executor, error) {
    44  	e := &Executor{
    45  		rootDir:     rootDir,
    46  		exitHandler: exitHandler,
    47  	}
    48  
    49  	client, err := libcontainerd.NewClient(ctx, cli, rootDir, PluginNamespace, e)
    50  	if err != nil {
    51  		return nil, errors.Wrap(err, "error creating containerd exec client")
    52  	}
    53  	e.client = client
    54  	return e, nil
    55  }
    56  
    57  // Executor is the containerd client implementation of a plugin executor
    58  type Executor struct {
    59  	rootDir     string
    60  	client      Client
    61  	exitHandler ExitHandler
    62  }
    63  
    64  // deleteTaskAndContainer deletes plugin task and then plugin container from containerd
    65  func deleteTaskAndContainer(ctx context.Context, cli Client, id string) {
    66  	_, _, err := cli.DeleteTask(ctx, id)
    67  	if err != nil && !errdefs.IsNotFound(err) {
    68  		logrus.WithError(err).WithField("id", id).Error("failed to delete plugin task from containerd")
    69  	}
    70  
    71  	err = cli.Delete(ctx, id)
    72  	if err != nil && !errdefs.IsNotFound(err) {
    73  		logrus.WithError(err).WithField("id", id).Error("failed to delete plugin container from containerd")
    74  	}
    75  }
    76  
    77  // Create creates a new container
    78  func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
    79  	opts := runctypes.RuncOptions{
    80  		RuntimeRoot: filepath.Join(e.rootDir, "runtime-root"),
    81  	}
    82  	ctx := context.Background()
    83  	err := e.client.Create(ctx, id, &spec, &opts)
    84  	if err != nil {
    85  		status, err2 := e.client.Status(ctx, id)
    86  		if err2 != nil {
    87  			if !errdefs.IsNotFound(err2) {
    88  				logrus.WithError(err2).WithField("id", id).Warn("Received an error while attempting to read plugin status")
    89  			}
    90  		} else {
    91  			if status != libcontainerdtypes.StatusRunning && status != libcontainerdtypes.StatusUnknown {
    92  				if err2 := e.client.Delete(ctx, id); err2 != nil && !errdefs.IsNotFound(err2) {
    93  					logrus.WithError(err2).WithField("plugin", id).Error("Error cleaning up containerd container")
    94  				}
    95  				err = e.client.Create(ctx, id, &spec, &opts)
    96  			}
    97  		}
    98  
    99  		if err != nil {
   100  			return errors.Wrap(err, "error creating containerd container")
   101  		}
   102  	}
   103  
   104  	_, err = e.client.Start(ctx, id, "", false, attachStreamsFunc(stdout, stderr))
   105  	if err != nil {
   106  		deleteTaskAndContainer(ctx, e.client, id)
   107  	}
   108  	return err
   109  }
   110  
   111  // Restore restores a container
   112  func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) {
   113  	alive, _, err := e.client.Restore(context.Background(), id, attachStreamsFunc(stdout, stderr))
   114  	if err != nil && !errdefs.IsNotFound(err) {
   115  		return false, err
   116  	}
   117  	if !alive {
   118  		deleteTaskAndContainer(context.Background(), e.client, id)
   119  	}
   120  	return alive, nil
   121  }
   122  
   123  // IsRunning returns if the container with the given id is running
   124  func (e *Executor) IsRunning(id string) (bool, error) {
   125  	status, err := e.client.Status(context.Background(), id)
   126  	return status == libcontainerdtypes.StatusRunning, err
   127  }
   128  
   129  // Signal sends the specified signal to the container
   130  func (e *Executor) Signal(id string, signal int) error {
   131  	return e.client.SignalProcess(context.Background(), id, libcontainerdtypes.InitProcessName, signal)
   132  }
   133  
   134  // ProcessEvent handles events from containerd
   135  // All events are ignored except the exit event, which is sent of to the stored handler
   136  func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
   137  	switch et {
   138  	case libcontainerdtypes.EventExit:
   139  		deleteTaskAndContainer(context.Background(), e.client, id)
   140  		return e.exitHandler.HandleExitEvent(ei.ContainerID)
   141  	}
   142  	return nil
   143  }
   144  
   145  type rio struct {
   146  	cio.IO
   147  
   148  	wg sync.WaitGroup
   149  }
   150  
   151  func (c *rio) Wait() {
   152  	c.wg.Wait()
   153  	c.IO.Wait()
   154  }
   155  
   156  func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
   157  	return func(iop *cio.DirectIO) (cio.IO, error) {
   158  		if iop.Stdin != nil {
   159  			iop.Stdin.Close()
   160  			// closing stdin shouldn't be needed here, it should never be open
   161  			panic("plugin stdin shouldn't have been created!")
   162  		}
   163  
   164  		rio := &rio{IO: iop}
   165  		rio.wg.Add(2)
   166  		go func() {
   167  			io.Copy(stdout, iop.Stdout)
   168  			stdout.Close()
   169  			rio.wg.Done()
   170  		}()
   171  		go func() {
   172  			io.Copy(stderr, iop.Stderr)
   173  			stderr.Close()
   174  			rio.wg.Done()
   175  		}()
   176  		return rio, nil
   177  	}
   178  }