github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/plugin/executor/containerd/containerd.go (about)

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