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 }