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