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