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