github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/plugin/executor/containerd/containerd.go (about)

     1  package containerd // import "github.com/docker/docker/plugin/executor/containerd"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"syscall"
     9  
    10  	"github.com/containerd/containerd"
    11  	"github.com/containerd/containerd/cio"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/errdefs"
    14  	"github.com/docker/docker/libcontainerd"
    15  	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
    16  	specs "github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  )
    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(ctx context.Context, rootDir string, cli *containerd.Client, ns string, exitHandler ExitHandler, runtime types.Runtime) (*Executor, error) {
    28  	e := &Executor{
    29  		rootDir:     rootDir,
    30  		exitHandler: exitHandler,
    31  		runtime:     runtime,
    32  		plugins:     make(map[string]*c8dPlugin),
    33  	}
    34  
    35  	client, err := libcontainerd.NewClient(ctx, cli, rootDir, ns, e)
    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  	runtime     types.Runtime
    49  
    50  	mu      sync.Mutex // Guards plugins map
    51  	plugins map[string]*c8dPlugin
    52  }
    53  
    54  type c8dPlugin struct {
    55  	log *logrus.Entry
    56  	ctr libcontainerdtypes.Container
    57  	tsk libcontainerdtypes.Task
    58  }
    59  
    60  // deleteTaskAndContainer deletes plugin task and then plugin container from containerd
    61  func (p c8dPlugin) deleteTaskAndContainer(ctx context.Context) {
    62  	if p.tsk != nil {
    63  		if err := p.tsk.ForceDelete(ctx); err != nil && !errdefs.IsNotFound(err) {
    64  			p.log.WithError(err).Error("failed to delete plugin task from containerd")
    65  		}
    66  	}
    67  	if p.ctr != nil {
    68  		if err := p.ctr.Delete(ctx); err != nil && !errdefs.IsNotFound(err) {
    69  			p.log.WithError(err).Error("failed to delete plugin container from containerd")
    70  		}
    71  	}
    72  }
    73  
    74  // Create creates a new container
    75  func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
    76  	ctx := context.Background()
    77  	log := logrus.WithField("plugin", id)
    78  	ctr, err := libcontainerd.ReplaceContainer(ctx, e.client, id, &spec, e.runtime.Shim.Binary, e.runtime.Shim.Opts)
    79  	if err != nil {
    80  		return errors.Wrap(err, "error creating containerd container for plugin")
    81  	}
    82  
    83  	p := c8dPlugin{log: log, ctr: ctr}
    84  	p.tsk, err = ctr.Start(ctx, "", false, attachStreamsFunc(stdout, stderr))
    85  	if err != nil {
    86  		p.deleteTaskAndContainer(ctx)
    87  		return err
    88  	}
    89  	e.mu.Lock()
    90  	defer e.mu.Unlock()
    91  	e.plugins[id] = &p
    92  	return nil
    93  }
    94  
    95  // Restore restores a container
    96  func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) {
    97  	ctx := context.Background()
    98  	p := c8dPlugin{log: logrus.WithField("plugin", id)}
    99  	ctr, err := e.client.LoadContainer(ctx, id)
   100  	if err != nil {
   101  		if errdefs.IsNotFound(err) {
   102  			return false, nil
   103  		}
   104  		return false, err
   105  	}
   106  	p.tsk, err = ctr.AttachTask(ctx, attachStreamsFunc(stdout, stderr))
   107  	if err != nil {
   108  		if errdefs.IsNotFound(err) {
   109  			p.deleteTaskAndContainer(ctx)
   110  			return false, nil
   111  		}
   112  		return false, err
   113  	}
   114  	s, err := p.tsk.Status(ctx)
   115  	if err != nil {
   116  		if errdefs.IsNotFound(err) {
   117  			// Task vanished after attaching?
   118  			p.tsk = nil
   119  			p.deleteTaskAndContainer(ctx)
   120  			return false, nil
   121  		}
   122  		return false, err
   123  	}
   124  	if s.Status == containerd.Stopped {
   125  		p.deleteTaskAndContainer(ctx)
   126  		return false, nil
   127  	}
   128  	e.mu.Lock()
   129  	defer e.mu.Unlock()
   130  	e.plugins[id] = &p
   131  	return true, nil
   132  }
   133  
   134  // IsRunning returns if the container with the given id is running
   135  func (e *Executor) IsRunning(id string) (bool, error) {
   136  	e.mu.Lock()
   137  	p := e.plugins[id]
   138  	e.mu.Unlock()
   139  	if p == nil {
   140  		return false, errdefs.NotFound(fmt.Errorf("unknown plugin %q", id))
   141  	}
   142  	status, err := p.tsk.Status(context.Background())
   143  	return status.Status == containerd.Running, err
   144  }
   145  
   146  // Signal sends the specified signal to the container
   147  func (e *Executor) Signal(id string, signal syscall.Signal) error {
   148  	e.mu.Lock()
   149  	p := e.plugins[id]
   150  	e.mu.Unlock()
   151  	if p == nil {
   152  		return errdefs.NotFound(fmt.Errorf("unknown plugin %q", id))
   153  	}
   154  	return p.tsk.Kill(context.Background(), signal)
   155  }
   156  
   157  // ProcessEvent handles events from containerd
   158  // All events are ignored except the exit event, which is sent of to the stored handler
   159  func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
   160  	switch et {
   161  	case libcontainerdtypes.EventExit:
   162  		e.mu.Lock()
   163  		p := e.plugins[id]
   164  		e.mu.Unlock()
   165  		if p == nil {
   166  			logrus.WithField("id", id).Warn("Received exit event for an unknown plugin")
   167  		} else {
   168  			p.deleteTaskAndContainer(context.Background())
   169  		}
   170  		return e.exitHandler.HandleExitEvent(ei.ContainerID)
   171  	}
   172  	return nil
   173  }
   174  
   175  type rio struct {
   176  	cio.IO
   177  
   178  	wg sync.WaitGroup
   179  }
   180  
   181  func (c *rio) Wait() {
   182  	c.wg.Wait()
   183  	c.IO.Wait()
   184  }
   185  
   186  func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
   187  	return func(iop *cio.DirectIO) (cio.IO, error) {
   188  		if iop.Stdin != nil {
   189  			iop.Stdin.Close()
   190  			// closing stdin shouldn't be needed here, it should never be open
   191  			panic("plugin stdin shouldn't have been created!")
   192  		}
   193  
   194  		rio := &rio{IO: iop}
   195  		rio.wg.Add(2)
   196  		go func() {
   197  			io.Copy(stdout, iop.Stdout)
   198  			stdout.Close()
   199  			rio.wg.Done()
   200  		}()
   201  		go func() {
   202  			io.Copy(stderr, iop.Stderr)
   203  			stderr.Close()
   204  			rio.wg.Done()
   205  		}()
   206  		return rio, nil
   207  	}
   208  }