github.com/noxiouz/docker@v0.7.3-0.20160629055221-3d231c78e8c5/daemon/cluster/executor/container/controller.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "os" 6 7 executorpkg "github.com/docker/docker/daemon/cluster/executor" 8 "github.com/docker/engine-api/types" 9 "github.com/docker/swarmkit/agent/exec" 10 "github.com/docker/swarmkit/api" 11 "github.com/docker/swarmkit/log" 12 "github.com/pkg/errors" 13 "golang.org/x/net/context" 14 ) 15 16 // controller implements agent.Controller against docker's API. 17 // 18 // Most operations against docker's API are done through the container name, 19 // which is unique to the task. 20 type controller struct { 21 backend executorpkg.Backend 22 task *api.Task 23 adapter *containerAdapter 24 closed chan struct{} 25 err error 26 } 27 28 var _ exec.Controller = &controller{} 29 30 // NewController returns a dockerexec runner for the provided task. 31 func newController(b executorpkg.Backend, task *api.Task) (*controller, error) { 32 adapter, err := newContainerAdapter(b, task) 33 if err != nil { 34 return nil, err 35 } 36 37 return &controller{ 38 backend: b, 39 task: task, 40 adapter: adapter, 41 closed: make(chan struct{}), 42 }, nil 43 } 44 45 func (r *controller) Task() (*api.Task, error) { 46 return r.task, nil 47 } 48 49 // ContainerStatus returns the container-specific status for the task. 50 func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) { 51 ctnr, err := r.adapter.inspect(ctx) 52 if err != nil { 53 if isUnknownContainer(err) { 54 return nil, nil 55 } 56 return nil, err 57 } 58 return parseContainerStatus(ctnr) 59 } 60 61 // Update tasks a recent task update and applies it to the container. 62 func (r *controller) Update(ctx context.Context, t *api.Task) error { 63 // TODO(stevvooe): While assignment of tasks is idempotent, we do allow 64 // updates of metadata, such as labelling, as well as any other properties 65 // that make sense. 66 return nil 67 } 68 69 // Prepare creates a container and ensures the image is pulled. 70 // 71 // If the container has already be created, exec.ErrTaskPrepared is returned. 72 func (r *controller) Prepare(ctx context.Context) error { 73 if err := r.checkClosed(); err != nil { 74 return err 75 } 76 77 // Make sure all the networks that the task needs are created. 78 if err := r.adapter.createNetworks(ctx); err != nil { 79 return err 80 } 81 82 // Make sure all the volumes that the task needs are created. 83 if err := r.adapter.createVolumes(ctx, r.backend); err != nil { 84 return err 85 } 86 87 if os.Getenv("DOCKER_SERVICE_PREFER_OFFLINE_IMAGE") != "1" { 88 if err := r.adapter.pullImage(ctx); err != nil { 89 // NOTE(stevvooe): We always try to pull the image to make sure we have 90 // the most up to date version. This will return an error, but we only 91 // log it. If the image truly doesn't exist, the create below will 92 // error out. 93 // 94 // This gives us some nice behavior where we use up to date versions of 95 // mutable tags, but will still run if the old image is available but a 96 // registry is down. 97 // 98 // If you don't want this behavior, lock down your image to an 99 // immutable tag or digest. 100 log.G(ctx).WithError(err).Error("pulling image failed") 101 } 102 } 103 104 if err := r.adapter.create(ctx, r.backend); err != nil { 105 if isContainerCreateNameConflict(err) { 106 if _, err := r.adapter.inspect(ctx); err != nil { 107 return err 108 } 109 110 // container is already created. success! 111 return exec.ErrTaskPrepared 112 } 113 114 return err 115 } 116 117 return nil 118 } 119 120 // Start the container. An error will be returned if the container is already started. 121 func (r *controller) Start(ctx context.Context) error { 122 if err := r.checkClosed(); err != nil { 123 return err 124 } 125 126 ctnr, err := r.adapter.inspect(ctx) 127 if err != nil { 128 return err 129 } 130 131 // Detect whether the container has *ever* been started. If so, we don't 132 // issue the start. 133 // 134 // TODO(stevvooe): This is very racy. While reading inspect, another could 135 // start the process and we could end up starting it twice. 136 if ctnr.State.Status != "created" { 137 return exec.ErrTaskStarted 138 } 139 140 if err := r.adapter.start(ctx); err != nil { 141 return errors.Wrap(err, "starting container failed") 142 } 143 144 return nil 145 } 146 147 // Wait on the container to exit. 148 func (r *controller) Wait(pctx context.Context) error { 149 if err := r.checkClosed(); err != nil { 150 return err 151 } 152 153 ctx, cancel := context.WithCancel(pctx) 154 defer cancel() 155 156 err := r.adapter.wait(ctx) 157 if ctx.Err() != nil { 158 return ctx.Err() 159 } 160 if err != nil { 161 ee := &exitError{} 162 if err.Error() != "" { 163 ee.cause = err 164 } 165 if ec, ok := err.(exec.ExitCoder); ok { 166 ee.code = ec.ExitCode() 167 } 168 return ee 169 } 170 return nil 171 } 172 173 // Shutdown the container cleanly. 174 func (r *controller) Shutdown(ctx context.Context) error { 175 if err := r.checkClosed(); err != nil { 176 return err 177 } 178 179 if err := r.adapter.shutdown(ctx); err != nil { 180 if isUnknownContainer(err) || isStoppedContainer(err) { 181 return nil 182 } 183 184 return err 185 } 186 187 return nil 188 } 189 190 // Terminate the container, with force. 191 func (r *controller) Terminate(ctx context.Context) error { 192 if err := r.checkClosed(); err != nil { 193 return err 194 } 195 196 if err := r.adapter.terminate(ctx); err != nil { 197 if isUnknownContainer(err) { 198 return nil 199 } 200 201 return err 202 } 203 204 return nil 205 } 206 207 // Remove the container and its resources. 208 func (r *controller) Remove(ctx context.Context) error { 209 if err := r.checkClosed(); err != nil { 210 return err 211 } 212 213 // It may be necessary to shut down the task before removing it. 214 if err := r.Shutdown(ctx); err != nil { 215 if isUnknownContainer(err) { 216 return nil 217 } 218 // This may fail if the task was already shut down. 219 log.G(ctx).WithError(err).Debug("shutdown failed on removal") 220 } 221 222 // Try removing networks referenced in this task in case this 223 // task is the last one referencing it 224 if err := r.adapter.removeNetworks(ctx); err != nil { 225 if isUnknownContainer(err) { 226 return nil 227 } 228 return err 229 } 230 231 if err := r.adapter.remove(ctx); err != nil { 232 if isUnknownContainer(err) { 233 return nil 234 } 235 236 return err 237 } 238 return nil 239 } 240 241 // Close the runner and clean up any ephemeral resources. 242 func (r *controller) Close() error { 243 select { 244 case <-r.closed: 245 return r.err 246 default: 247 r.err = exec.ErrControllerClosed 248 close(r.closed) 249 } 250 return nil 251 } 252 253 func (r *controller) checkClosed() error { 254 select { 255 case <-r.closed: 256 return r.err 257 default: 258 return nil 259 } 260 } 261 262 func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) { 263 status := &api.ContainerStatus{ 264 ContainerID: ctnr.ID, 265 PID: int32(ctnr.State.Pid), 266 ExitCode: int32(ctnr.State.ExitCode), 267 } 268 269 return status, nil 270 } 271 272 type exitError struct { 273 code int 274 cause error 275 } 276 277 func (e *exitError) Error() string { 278 if e.cause != nil { 279 return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause) 280 } 281 282 return fmt.Sprintf("task: non-zero exit (%v)", e.code) 283 } 284 285 func (e *exitError) ExitCode() int { 286 return int(e.code) 287 } 288 289 func (e *exitError) Cause() error { 290 return e.cause 291 }