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