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