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