github.com/rumpl/bof@v23.0.0-rc.2+incompatible/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  	"sync"
     7  	"syscall"
     8  
     9  	"github.com/containerd/containerd"
    10  	"github.com/containerd/containerd/cio"
    11  	"github.com/docker/docker/api/types"
    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, runtime types.Runtime) (*Executor, error) {
    27  	e := &Executor{
    28  		rootDir:     rootDir,
    29  		exitHandler: exitHandler,
    30  		runtime:     runtime,
    31  	}
    32  
    33  	client, err := libcontainerd.NewClient(ctx, cli, rootDir, ns, e)
    34  	if err != nil {
    35  		return nil, errors.Wrap(err, "error creating containerd exec client")
    36  	}
    37  	e.client = client
    38  	return e, nil
    39  }
    40  
    41  // Executor is the containerd client implementation of a plugin executor
    42  type Executor struct {
    43  	rootDir     string
    44  	client      libcontainerdtypes.Client
    45  	exitHandler ExitHandler
    46  	runtime     types.Runtime
    47  }
    48  
    49  // deleteTaskAndContainer deletes plugin task and then plugin container from containerd
    50  func deleteTaskAndContainer(ctx context.Context, cli libcontainerdtypes.Client, id string, p libcontainerdtypes.Process) {
    51  	if p != nil {
    52  		if _, _, err := p.Delete(ctx); err != nil && !errdefs.IsNotFound(err) {
    53  			logrus.WithError(err).WithField("id", id).Error("failed to delete plugin task from containerd")
    54  		}
    55  	} else {
    56  		if _, _, err := cli.DeleteTask(ctx, id); err != nil && !errdefs.IsNotFound(err) {
    57  			logrus.WithError(err).WithField("id", id).Error("failed to delete plugin task from containerd")
    58  		}
    59  	}
    60  
    61  	if err := cli.Delete(ctx, id); err != nil && !errdefs.IsNotFound(err) {
    62  		logrus.WithError(err).WithField("id", id).Error("failed to delete plugin container from containerd")
    63  	}
    64  }
    65  
    66  // Create creates a new container
    67  func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
    68  	ctx := context.Background()
    69  	err := e.client.Create(ctx, id, &spec, e.runtime.Shim.Binary, e.runtime.Shim.Opts)
    70  	if err != nil {
    71  		status, err2 := e.client.Status(ctx, id)
    72  		if err2 != nil {
    73  			if !errdefs.IsNotFound(err2) {
    74  				logrus.WithError(err2).WithField("id", id).Warn("Received an error while attempting to read plugin status")
    75  			}
    76  		} else {
    77  			if status != containerd.Running && status != containerd.Unknown {
    78  				if err2 := e.client.Delete(ctx, id); err2 != nil && !errdefs.IsNotFound(err2) {
    79  					logrus.WithError(err2).WithField("plugin", id).Error("Error cleaning up containerd container")
    80  				}
    81  				err = e.client.Create(ctx, id, &spec, e.runtime.Shim.Binary, e.runtime.Shim.Opts)
    82  			}
    83  		}
    84  
    85  		if err != nil {
    86  			return errors.Wrap(err, "error creating containerd container")
    87  		}
    88  	}
    89  
    90  	_, err = e.client.Start(ctx, id, "", false, attachStreamsFunc(stdout, stderr))
    91  	if err != nil {
    92  		deleteTaskAndContainer(ctx, e.client, id, nil)
    93  	}
    94  	return err
    95  }
    96  
    97  // Restore restores a container
    98  func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) {
    99  	alive, _, p, err := e.client.Restore(context.Background(), id, attachStreamsFunc(stdout, stderr))
   100  	if err != nil && !errdefs.IsNotFound(err) {
   101  		return false, err
   102  	}
   103  	if !alive {
   104  		deleteTaskAndContainer(context.Background(), e.client, id, p)
   105  	}
   106  	return alive, nil
   107  }
   108  
   109  // IsRunning returns if the container with the given id is running
   110  func (e *Executor) IsRunning(id string) (bool, error) {
   111  	status, err := e.client.Status(context.Background(), id)
   112  	return status == containerd.Running, err
   113  }
   114  
   115  // Signal sends the specified signal to the container
   116  func (e *Executor) Signal(id string, signal syscall.Signal) error {
   117  	return e.client.SignalProcess(context.Background(), id, libcontainerdtypes.InitProcessName, signal)
   118  }
   119  
   120  // ProcessEvent handles events from containerd
   121  // All events are ignored except the exit event, which is sent of to the stored handler
   122  func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
   123  	switch et {
   124  	case libcontainerdtypes.EventExit:
   125  		deleteTaskAndContainer(context.Background(), e.client, id, nil)
   126  		return e.exitHandler.HandleExitEvent(ei.ContainerID)
   127  	}
   128  	return nil
   129  }
   130  
   131  type rio struct {
   132  	cio.IO
   133  
   134  	wg sync.WaitGroup
   135  }
   136  
   137  func (c *rio) Wait() {
   138  	c.wg.Wait()
   139  	c.IO.Wait()
   140  }
   141  
   142  func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
   143  	return func(iop *cio.DirectIO) (cio.IO, error) {
   144  		if iop.Stdin != nil {
   145  			iop.Stdin.Close()
   146  			// closing stdin shouldn't be needed here, it should never be open
   147  			panic("plugin stdin shouldn't have been created!")
   148  		}
   149  
   150  		rio := &rio{IO: iop}
   151  		rio.wg.Add(2)
   152  		go func() {
   153  			io.Copy(stdout, iop.Stdout)
   154  			stdout.Close()
   155  			rio.wg.Done()
   156  		}()
   157  		go func() {
   158  			io.Copy(stderr, iop.Stderr)
   159  			stderr.Close()
   160  			rio.wg.Done()
   161  		}()
   162  		return rio, nil
   163  	}
   164  }