github.com/moby/docker@v26.1.3+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/containerd/log"
    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  )
    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 *log.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  	ctr, err := libcontainerd.ReplaceContainer(ctx, e.client, id, &spec, e.shim, e.shimOpts)
    79  	if err != nil {
    80  		return errors.Wrap(err, "error creating containerd container for plugin")
    81  	}
    82  
    83  	p := c8dPlugin{log: log.G(ctx).WithField("plugin", id), ctr: ctr}
    84  	p.tsk, err = ctr.NewTask(ctx, "", false, attachStreamsFunc(stdout, stderr))
    85  	if err != nil {
    86  		p.deleteTaskAndContainer(ctx)
    87  		return err
    88  	}
    89  	if err := p.tsk.Start(ctx); err != nil {
    90  		p.deleteTaskAndContainer(ctx)
    91  		return err
    92  	}
    93  	e.mu.Lock()
    94  	defer e.mu.Unlock()
    95  	e.plugins[id] = &p
    96  	return nil
    97  }
    98  
    99  // Restore restores a container
   100  func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) {
   101  	ctx := context.Background()
   102  	p := c8dPlugin{log: log.G(ctx).WithField("plugin", id)}
   103  	ctr, err := e.client.LoadContainer(ctx, id)
   104  	if err != nil {
   105  		if errdefs.IsNotFound(err) {
   106  			return false, nil
   107  		}
   108  		return false, err
   109  	}
   110  	p.tsk, err = ctr.AttachTask(ctx, attachStreamsFunc(stdout, stderr))
   111  	if err != nil {
   112  		if errdefs.IsNotFound(err) {
   113  			p.deleteTaskAndContainer(ctx)
   114  			return false, nil
   115  		}
   116  		return false, err
   117  	}
   118  	s, err := p.tsk.Status(ctx)
   119  	if err != nil {
   120  		if errdefs.IsNotFound(err) {
   121  			// Task vanished after attaching?
   122  			p.tsk = nil
   123  			p.deleteTaskAndContainer(ctx)
   124  			return false, nil
   125  		}
   126  		return false, err
   127  	}
   128  	if s.Status == containerd.Stopped {
   129  		p.deleteTaskAndContainer(ctx)
   130  		return false, nil
   131  	}
   132  	e.mu.Lock()
   133  	defer e.mu.Unlock()
   134  	e.plugins[id] = &p
   135  	return true, nil
   136  }
   137  
   138  // IsRunning returns if the container with the given id is running
   139  func (e *Executor) IsRunning(id string) (bool, error) {
   140  	e.mu.Lock()
   141  	p := e.plugins[id]
   142  	e.mu.Unlock()
   143  	if p == nil {
   144  		return false, errdefs.NotFound(fmt.Errorf("unknown plugin %q", id))
   145  	}
   146  	status, err := p.tsk.Status(context.Background())
   147  	return status.Status == containerd.Running, err
   148  }
   149  
   150  // Signal sends the specified signal to the container
   151  func (e *Executor) Signal(id string, signal syscall.Signal) error {
   152  	e.mu.Lock()
   153  	p := e.plugins[id]
   154  	e.mu.Unlock()
   155  	if p == nil {
   156  		return errdefs.NotFound(fmt.Errorf("unknown plugin %q", id))
   157  	}
   158  	return p.tsk.Kill(context.Background(), signal)
   159  }
   160  
   161  // ProcessEvent handles events from containerd
   162  // All events are ignored except the exit event, which is sent of to the stored handler
   163  func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
   164  	switch et {
   165  	case libcontainerdtypes.EventExit:
   166  		e.mu.Lock()
   167  		p := e.plugins[id]
   168  		e.mu.Unlock()
   169  		if p == nil {
   170  			log.G(context.TODO()).WithField("id", id).Warn("Received exit event for an unknown plugin")
   171  		} else {
   172  			p.deleteTaskAndContainer(context.Background())
   173  		}
   174  		return e.exitHandler.HandleExitEvent(ei.ContainerID)
   175  	}
   176  	return nil
   177  }
   178  
   179  type rio struct {
   180  	cio.IO
   181  
   182  	wg sync.WaitGroup
   183  }
   184  
   185  func (c *rio) Wait() {
   186  	c.wg.Wait()
   187  	c.IO.Wait()
   188  }
   189  
   190  func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
   191  	return func(iop *cio.DirectIO) (cio.IO, error) {
   192  		if iop.Stdin != nil {
   193  			iop.Stdin.Close()
   194  			// closing stdin shouldn't be needed here, it should never be open
   195  			panic("plugin stdin shouldn't have been created!")
   196  		}
   197  
   198  		rio := &rio{IO: iop}
   199  		rio.wg.Add(2)
   200  		go func() {
   201  			io.Copy(stdout, iop.Stdout)
   202  			stdout.Close()
   203  			rio.wg.Done()
   204  		}()
   205  		go func() {
   206  			io.Copy(stderr, iop.Stderr)
   207  			stderr.Close()
   208  			rio.wg.Done()
   209  		}()
   210  		return rio, nil
   211  	}
   212  }