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