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