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