github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/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/docker/docker/api/types" 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 "github.com/sirupsen/logrus" 19 ) 20 21 // ExitHandler represents an object that is called when the exit event is received from containerd 22 type ExitHandler interface { 23 HandleExitEvent(id string) error 24 } 25 26 // New creates a new containerd plugin executor 27 func New(ctx context.Context, rootDir string, cli *containerd.Client, ns string, exitHandler ExitHandler, runtime types.Runtime) (*Executor, error) { 28 e := &Executor{ 29 rootDir: rootDir, 30 exitHandler: exitHandler, 31 runtime: runtime, 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 runtime types.Runtime 49 50 mu sync.Mutex // Guards plugins map 51 plugins map[string]*c8dPlugin 52 } 53 54 type c8dPlugin struct { 55 log *logrus.Entry 56 ctr libcontainerdtypes.Container 57 tsk libcontainerdtypes.Task 58 } 59 60 // deleteTaskAndContainer deletes plugin task and then plugin container from containerd 61 func (p c8dPlugin) deleteTaskAndContainer(ctx context.Context) { 62 if p.tsk != nil { 63 if err := p.tsk.ForceDelete(ctx); err != nil && !errdefs.IsNotFound(err) { 64 p.log.WithError(err).Error("failed to delete plugin task from containerd") 65 } 66 } 67 if p.ctr != nil { 68 if err := p.ctr.Delete(ctx); err != nil && !errdefs.IsNotFound(err) { 69 p.log.WithError(err).Error("failed to delete plugin container from containerd") 70 } 71 } 72 } 73 74 // Create creates a new container 75 func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { 76 ctx := context.Background() 77 log := logrus.WithField("plugin", id) 78 ctr, err := libcontainerd.ReplaceContainer(ctx, e.client, id, &spec, e.runtime.Shim.Binary, e.runtime.Shim.Opts) 79 if err != nil { 80 return errors.Wrap(err, "error creating containerd container for plugin") 81 } 82 83 p := c8dPlugin{log: log, ctr: ctr} 84 p.tsk, err = ctr.Start(ctx, "", false, attachStreamsFunc(stdout, stderr)) 85 if err != nil { 86 p.deleteTaskAndContainer(ctx) 87 return err 88 } 89 e.mu.Lock() 90 defer e.mu.Unlock() 91 e.plugins[id] = &p 92 return nil 93 } 94 95 // Restore restores a container 96 func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) { 97 ctx := context.Background() 98 p := c8dPlugin{log: logrus.WithField("plugin", id)} 99 ctr, err := e.client.LoadContainer(ctx, id) 100 if err != nil { 101 if errdefs.IsNotFound(err) { 102 return false, nil 103 } 104 return false, err 105 } 106 p.tsk, err = ctr.AttachTask(ctx, attachStreamsFunc(stdout, stderr)) 107 if err != nil { 108 if errdefs.IsNotFound(err) { 109 p.deleteTaskAndContainer(ctx) 110 return false, nil 111 } 112 return false, err 113 } 114 s, err := p.tsk.Status(ctx) 115 if err != nil { 116 if errdefs.IsNotFound(err) { 117 // Task vanished after attaching? 118 p.tsk = nil 119 p.deleteTaskAndContainer(ctx) 120 return false, nil 121 } 122 return false, err 123 } 124 if s.Status == containerd.Stopped { 125 p.deleteTaskAndContainer(ctx) 126 return false, nil 127 } 128 e.mu.Lock() 129 defer e.mu.Unlock() 130 e.plugins[id] = &p 131 return true, nil 132 } 133 134 // IsRunning returns if the container with the given id is running 135 func (e *Executor) IsRunning(id string) (bool, error) { 136 e.mu.Lock() 137 p := e.plugins[id] 138 e.mu.Unlock() 139 if p == nil { 140 return false, errdefs.NotFound(fmt.Errorf("unknown plugin %q", id)) 141 } 142 status, err := p.tsk.Status(context.Background()) 143 return status.Status == containerd.Running, err 144 } 145 146 // Signal sends the specified signal to the container 147 func (e *Executor) Signal(id string, signal syscall.Signal) error { 148 e.mu.Lock() 149 p := e.plugins[id] 150 e.mu.Unlock() 151 if p == nil { 152 return errdefs.NotFound(fmt.Errorf("unknown plugin %q", id)) 153 } 154 return p.tsk.Kill(context.Background(), signal) 155 } 156 157 // ProcessEvent handles events from containerd 158 // All events are ignored except the exit event, which is sent of to the stored handler 159 func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error { 160 switch et { 161 case libcontainerdtypes.EventExit: 162 e.mu.Lock() 163 p := e.plugins[id] 164 e.mu.Unlock() 165 if p == nil { 166 logrus.WithField("id", id).Warn("Received exit event for an unknown plugin") 167 } else { 168 p.deleteTaskAndContainer(context.Background()) 169 } 170 return e.exitHandler.HandleExitEvent(ei.ContainerID) 171 } 172 return nil 173 } 174 175 type rio struct { 176 cio.IO 177 178 wg sync.WaitGroup 179 } 180 181 func (c *rio) Wait() { 182 c.wg.Wait() 183 c.IO.Wait() 184 } 185 186 func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback { 187 return func(iop *cio.DirectIO) (cio.IO, error) { 188 if iop.Stdin != nil { 189 iop.Stdin.Close() 190 // closing stdin shouldn't be needed here, it should never be open 191 panic("plugin stdin shouldn't have been created!") 192 } 193 194 rio := &rio{IO: iop} 195 rio.wg.Add(2) 196 go func() { 197 io.Copy(stdout, iop.Stdout) 198 stdout.Close() 199 rio.wg.Done() 200 }() 201 go func() { 202 io.Copy(stderr, iop.Stderr) 203 stderr.Close() 204 rio.wg.Done() 205 }() 206 return rio, nil 207 } 208 }