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