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