github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/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 "path/filepath" 7 "sync" 8 "time" 9 10 "github.com/containerd/containerd" 11 "github.com/containerd/containerd/cio" 12 "github.com/containerd/containerd/runtime/linux/runctypes" 13 "github.com/docker/docker/errdefs" 14 "github.com/docker/docker/libcontainerd" 15 "github.com/opencontainers/runtime-spec/specs-go" 16 "github.com/pkg/errors" 17 "github.com/sirupsen/logrus" 18 ) 19 20 // PluginNamespace is the name used for the plugins namespace 21 const PluginNamespace = "plugins.moby" 22 23 // ExitHandler represents an object that is called when the exit event is received from containerd 24 type ExitHandler interface { 25 HandleExitEvent(id string) error 26 } 27 28 // Client is used by the exector to perform operations. 29 // TODO(@cpuguy83): This should really just be based off the containerd client interface. 30 // However right now this whole package is tied to github.com/docker/docker/libcontainerd 31 type Client interface { 32 Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error 33 Restore(ctx context.Context, containerID string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) 34 Status(ctx context.Context, containerID string) (libcontainerd.Status, error) 35 Delete(ctx context.Context, containerID string) error 36 DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) 37 Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) 38 SignalProcess(ctx context.Context, containerID, processID string, signal int) error 39 } 40 41 // New creates a new containerd plugin executor 42 func New(ctx context.Context, rootDir string, cli *containerd.Client, exitHandler ExitHandler) (*Executor, error) { 43 e := &Executor{ 44 rootDir: rootDir, 45 exitHandler: exitHandler, 46 } 47 48 client, err := libcontainerd.NewClient(ctx, cli, rootDir, PluginNamespace, e) 49 if err != nil { 50 return nil, errors.Wrap(err, "error creating containerd exec client") 51 } 52 e.client = client 53 return e, nil 54 } 55 56 // Executor is the containerd client implementation of a plugin executor 57 type Executor struct { 58 rootDir string 59 client Client 60 exitHandler ExitHandler 61 } 62 63 // deleteTaskAndContainer deletes plugin task and then plugin container from containerd 64 func deleteTaskAndContainer(ctx context.Context, cli Client, id string) { 65 _, _, err := cli.DeleteTask(ctx, id) 66 if err != nil && !errdefs.IsNotFound(err) { 67 logrus.WithError(err).WithField("id", id).Error("failed to delete plugin task from containerd") 68 } 69 70 err = cli.Delete(ctx, id) 71 if err != nil && !errdefs.IsNotFound(err) { 72 logrus.WithError(err).WithField("id", id).Error("failed to delete plugin container from containerd") 73 } 74 } 75 76 // Create creates a new container 77 func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error { 78 opts := runctypes.RuncOptions{ 79 RuntimeRoot: filepath.Join(e.rootDir, "runtime-root"), 80 } 81 ctx := context.Background() 82 err := e.client.Create(ctx, id, &spec, &opts) 83 if err != nil { 84 status, err2 := e.client.Status(ctx, id) 85 if err2 != nil { 86 if !errdefs.IsNotFound(err2) { 87 logrus.WithError(err2).WithField("id", id).Warn("Received an error while attempting to read plugin status") 88 } 89 } else { 90 if status != libcontainerd.StatusRunning && status != libcontainerd.StatusUnknown { 91 if err2 := e.client.Delete(ctx, id); err2 != nil && !errdefs.IsNotFound(err2) { 92 logrus.WithError(err2).WithField("plugin", id).Error("Error cleaning up containerd container") 93 } 94 err = e.client.Create(ctx, id, &spec, &opts) 95 } 96 } 97 98 if err != nil { 99 return errors.Wrap(err, "error creating containerd container") 100 } 101 } 102 103 _, err = e.client.Start(ctx, id, "", false, attachStreamsFunc(stdout, stderr)) 104 if err != nil { 105 deleteTaskAndContainer(ctx, e.client, id) 106 } 107 return err 108 } 109 110 // Restore restores a container 111 func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, error) { 112 alive, _, err := e.client.Restore(context.Background(), id, attachStreamsFunc(stdout, stderr)) 113 if err != nil && !errdefs.IsNotFound(err) { 114 return false, err 115 } 116 if !alive { 117 deleteTaskAndContainer(context.Background(), e.client, id) 118 } 119 return alive, nil 120 } 121 122 // IsRunning returns if the container with the given id is running 123 func (e *Executor) IsRunning(id string) (bool, error) { 124 status, err := e.client.Status(context.Background(), id) 125 return status == libcontainerd.StatusRunning, err 126 } 127 128 // Signal sends the specified signal to the container 129 func (e *Executor) Signal(id string, signal int) error { 130 return e.client.SignalProcess(context.Background(), id, libcontainerd.InitProcessName, signal) 131 } 132 133 // ProcessEvent handles events from containerd 134 // All events are ignored except the exit event, which is sent of to the stored handler 135 func (e *Executor) ProcessEvent(id string, et libcontainerd.EventType, ei libcontainerd.EventInfo) error { 136 switch et { 137 case libcontainerd.EventExit: 138 deleteTaskAndContainer(context.Background(), e.client, id) 139 return e.exitHandler.HandleExitEvent(ei.ContainerID) 140 } 141 return nil 142 } 143 144 type rio struct { 145 cio.IO 146 147 wg sync.WaitGroup 148 } 149 150 func (c *rio) Wait() { 151 c.wg.Wait() 152 c.IO.Wait() 153 } 154 155 func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerd.StdioCallback { 156 return func(iop *cio.DirectIO) (cio.IO, error) { 157 if iop.Stdin != nil { 158 iop.Stdin.Close() 159 // closing stdin shouldn't be needed here, it should never be open 160 panic("plugin stdin shouldn't have been created!") 161 } 162 163 rio := &rio{IO: iop} 164 rio.wg.Add(2) 165 go func() { 166 io.Copy(stdout, iop.Stdout) 167 stdout.Close() 168 rio.wg.Done() 169 }() 170 go func() { 171 io.Copy(stderr, iop.Stderr) 172 stderr.Close() 173 rio.wg.Done() 174 }() 175 return rio, nil 176 } 177 }