github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/daemon/cluster/executor/container/controller.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "os" 6 7 "github.com/docker/docker/api/types" 8 "github.com/docker/docker/api/types/events" 9 executorpkg "github.com/docker/docker/daemon/cluster/executor" 10 "github.com/docker/libnetwork" 11 "github.com/docker/swarmkit/agent/exec" 12 "github.com/docker/swarmkit/api" 13 "github.com/docker/swarmkit/log" 14 "github.com/pkg/errors" 15 "golang.org/x/net/context" 16 ) 17 18 // controller implements agent.Controller against docker's API. 19 // 20 // Most operations against docker's API are done through the container name, 21 // which is unique to the task. 22 type controller struct { 23 task *api.Task 24 adapter *containerAdapter 25 closed chan struct{} 26 err error 27 28 pulled chan struct{} // closed after pull 29 cancelPull func() // cancels pull context if not nil 30 pullErr error // pull error, only read after pulled closed 31 } 32 33 var _ exec.Controller = &controller{} 34 35 // NewController returns a docker exec runner for the provided task. 36 func newController(b executorpkg.Backend, task *api.Task) (*controller, error) { 37 adapter, err := newContainerAdapter(b, task) 38 if err != nil { 39 return nil, err 40 } 41 42 return &controller{ 43 task: task, 44 adapter: adapter, 45 closed: make(chan struct{}), 46 }, nil 47 } 48 49 func (r *controller) Task() (*api.Task, error) { 50 return r.task, nil 51 } 52 53 // ContainerStatus returns the container-specific status for the task. 54 func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) { 55 ctnr, err := r.adapter.inspect(ctx) 56 if err != nil { 57 if isUnknownContainer(err) { 58 return nil, nil 59 } 60 return nil, err 61 } 62 return parseContainerStatus(ctnr) 63 } 64 65 // Update tasks a recent task update and applies it to the container. 66 func (r *controller) Update(ctx context.Context, t *api.Task) error { 67 // TODO(stevvooe): While assignment of tasks is idempotent, we do allow 68 // updates of metadata, such as labelling, as well as any other properties 69 // that make sense. 70 return nil 71 } 72 73 // Prepare creates a container and ensures the image is pulled. 74 // 75 // If the container has already be created, exec.ErrTaskPrepared is returned. 76 func (r *controller) Prepare(ctx context.Context) error { 77 if err := r.checkClosed(); err != nil { 78 return err 79 } 80 81 // Make sure all the networks that the task needs are created. 82 if err := r.adapter.createNetworks(ctx); err != nil { 83 return err 84 } 85 86 // Make sure all the volumes that the task needs are created. 87 if err := r.adapter.createVolumes(ctx); err != nil { 88 return err 89 } 90 91 if os.Getenv("DOCKER_SERVICE_PREFER_OFFLINE_IMAGE") != "1" { 92 if r.pulled == nil { 93 // Fork the pull to a different context to allow pull to continue 94 // on re-entrant calls to Prepare. This ensures that Prepare can be 95 // idempotent and not incur the extra cost of pulling when 96 // cancelled on updates. 97 var pctx context.Context 98 99 r.pulled = make(chan struct{}) 100 pctx, r.cancelPull = context.WithCancel(context.Background()) // TODO(stevvooe): Bind a context to the entire controller. 101 102 go func() { 103 defer close(r.pulled) 104 r.pullErr = r.adapter.pullImage(pctx) // protected by closing r.pulled 105 }() 106 } 107 108 select { 109 case <-ctx.Done(): 110 return ctx.Err() 111 case <-r.pulled: 112 if r.pullErr != nil { 113 // NOTE(stevvooe): We always try to pull the image to make sure we have 114 // the most up to date version. This will return an error, but we only 115 // log it. If the image truly doesn't exist, the create below will 116 // error out. 117 // 118 // This gives us some nice behavior where we use up to date versions of 119 // mutable tags, but will still run if the old image is available but a 120 // registry is down. 121 // 122 // If you don't want this behavior, lock down your image to an 123 // immutable tag or digest. 124 log.G(ctx).WithError(r.pullErr).Error("pulling image failed") 125 } 126 } 127 } 128 129 if err := r.adapter.create(ctx); err != nil { 130 if isContainerCreateNameConflict(err) { 131 if _, err := r.adapter.inspect(ctx); err != nil { 132 return err 133 } 134 135 // container is already created. success! 136 return exec.ErrTaskPrepared 137 } 138 139 return err 140 } 141 142 return nil 143 } 144 145 // Start the container. An error will be returned if the container is already started. 146 func (r *controller) Start(ctx context.Context) error { 147 if err := r.checkClosed(); err != nil { 148 return err 149 } 150 151 ctnr, err := r.adapter.inspect(ctx) 152 if err != nil { 153 return err 154 } 155 156 // Detect whether the container has *ever* been started. If so, we don't 157 // issue the start. 158 // 159 // TODO(stevvooe): This is very racy. While reading inspect, another could 160 // start the process and we could end up starting it twice. 161 if ctnr.State.Status != "created" { 162 return exec.ErrTaskStarted 163 } 164 165 for { 166 if err := r.adapter.start(ctx); err != nil { 167 if _, ok := err.(libnetwork.ErrNoSuchNetwork); ok { 168 // Retry network creation again if we 169 // failed because some of the networks 170 // were not found. 171 if err := r.adapter.createNetworks(ctx); err != nil { 172 return err 173 } 174 175 continue 176 } 177 178 return errors.Wrap(err, "starting container failed") 179 } 180 181 break 182 } 183 184 // no health check 185 if ctnr.Config == nil || ctnr.Config.Healthcheck == nil { 186 return nil 187 } 188 189 healthCmd := ctnr.Config.Healthcheck.Test 190 191 if len(healthCmd) == 0 || healthCmd[0] == "NONE" { 192 return nil 193 } 194 195 // wait for container to be healthy 196 eventq := r.adapter.events(ctx) 197 198 var healthErr error 199 for { 200 select { 201 case event := <-eventq: 202 if !r.matchevent(event) { 203 continue 204 } 205 206 switch event.Action { 207 case "die": // exit on terminal events 208 ctnr, err := r.adapter.inspect(ctx) 209 if err != nil { 210 return errors.Wrap(err, "die event received") 211 } else if ctnr.State.ExitCode != 0 { 212 return &exitError{code: ctnr.State.ExitCode, cause: healthErr} 213 } 214 215 return nil 216 case "destroy": 217 // If we get here, something has gone wrong but we want to exit 218 // and report anyways. 219 return ErrContainerDestroyed 220 case "health_status: unhealthy": 221 // in this case, we stop the container and report unhealthy status 222 if err := r.Shutdown(ctx); err != nil { 223 return errors.Wrap(err, "unhealthy container shutdown failed") 224 } 225 // set health check error, and wait for container to fully exit ("die" event) 226 healthErr = ErrContainerUnhealthy 227 case "health_status: healthy": 228 return nil 229 } 230 case <-ctx.Done(): 231 return ctx.Err() 232 case <-r.closed: 233 return r.err 234 } 235 } 236 } 237 238 // Wait on the container to exit. 239 func (r *controller) Wait(pctx context.Context) error { 240 if err := r.checkClosed(); err != nil { 241 return err 242 } 243 244 ctx, cancel := context.WithCancel(pctx) 245 defer cancel() 246 247 healthErr := make(chan error, 1) 248 go func() { 249 ectx, cancel := context.WithCancel(ctx) // cancel event context on first event 250 defer cancel() 251 if err := r.checkHealth(ectx); err == ErrContainerUnhealthy { 252 healthErr <- ErrContainerUnhealthy 253 if err := r.Shutdown(ectx); err != nil { 254 log.G(ectx).WithError(err).Debug("shutdown failed on unhealthy") 255 } 256 } 257 }() 258 259 err := r.adapter.wait(ctx) 260 if ctx.Err() != nil { 261 return ctx.Err() 262 } 263 264 if err != nil { 265 ee := &exitError{} 266 if ec, ok := err.(exec.ExitCoder); ok { 267 ee.code = ec.ExitCode() 268 } 269 select { 270 case e := <-healthErr: 271 ee.cause = e 272 default: 273 if err.Error() != "" { 274 ee.cause = err 275 } 276 } 277 return ee 278 } 279 280 return nil 281 } 282 283 // Shutdown the container cleanly. 284 func (r *controller) Shutdown(ctx context.Context) error { 285 if err := r.checkClosed(); err != nil { 286 return err 287 } 288 289 if r.cancelPull != nil { 290 r.cancelPull() 291 } 292 293 if err := r.adapter.shutdown(ctx); err != nil { 294 if isUnknownContainer(err) || isStoppedContainer(err) { 295 return nil 296 } 297 298 return err 299 } 300 301 return nil 302 } 303 304 // Terminate the container, with force. 305 func (r *controller) Terminate(ctx context.Context) error { 306 if err := r.checkClosed(); err != nil { 307 return err 308 } 309 310 if r.cancelPull != nil { 311 r.cancelPull() 312 } 313 314 if err := r.adapter.terminate(ctx); err != nil { 315 if isUnknownContainer(err) { 316 return nil 317 } 318 319 return err 320 } 321 322 return nil 323 } 324 325 // Remove the container and its resources. 326 func (r *controller) Remove(ctx context.Context) error { 327 if err := r.checkClosed(); err != nil { 328 return err 329 } 330 331 if r.cancelPull != nil { 332 r.cancelPull() 333 } 334 335 // It may be necessary to shut down the task before removing it. 336 if err := r.Shutdown(ctx); err != nil { 337 if isUnknownContainer(err) { 338 return nil 339 } 340 // This may fail if the task was already shut down. 341 log.G(ctx).WithError(err).Debug("shutdown failed on removal") 342 } 343 344 // Try removing networks referenced in this task in case this 345 // task is the last one referencing it 346 if err := r.adapter.removeNetworks(ctx); err != nil { 347 if isUnknownContainer(err) { 348 return nil 349 } 350 return err 351 } 352 353 if err := r.adapter.remove(ctx); err != nil { 354 if isUnknownContainer(err) { 355 return nil 356 } 357 358 return err 359 } 360 return nil 361 } 362 363 // Close the runner and clean up any ephemeral resources. 364 func (r *controller) Close() error { 365 select { 366 case <-r.closed: 367 return r.err 368 default: 369 if r.cancelPull != nil { 370 r.cancelPull() 371 } 372 373 r.err = exec.ErrControllerClosed 374 close(r.closed) 375 } 376 return nil 377 } 378 379 func (r *controller) matchevent(event events.Message) bool { 380 if event.Type != events.ContainerEventType { 381 return false 382 } 383 384 // TODO(stevvooe): Filter based on ID matching, in addition to name. 385 386 // Make sure the events are for this container. 387 if event.Actor.Attributes["name"] != r.adapter.container.name() { 388 return false 389 } 390 391 return true 392 } 393 394 func (r *controller) checkClosed() error { 395 select { 396 case <-r.closed: 397 return r.err 398 default: 399 return nil 400 } 401 } 402 403 func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) { 404 status := &api.ContainerStatus{ 405 ContainerID: ctnr.ID, 406 PID: int32(ctnr.State.Pid), 407 ExitCode: int32(ctnr.State.ExitCode), 408 } 409 410 return status, nil 411 } 412 413 type exitError struct { 414 code int 415 cause error 416 } 417 418 func (e *exitError) Error() string { 419 if e.cause != nil { 420 return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause) 421 } 422 423 return fmt.Sprintf("task: non-zero exit (%v)", e.code) 424 } 425 426 func (e *exitError) ExitCode() int { 427 return int(e.code) 428 } 429 430 func (e *exitError) Cause() error { 431 return e.cause 432 } 433 434 // checkHealth blocks until unhealthy container is detected or ctx exits 435 func (r *controller) checkHealth(ctx context.Context) error { 436 eventq := r.adapter.events(ctx) 437 438 for { 439 select { 440 case <-ctx.Done(): 441 return nil 442 case <-r.closed: 443 return nil 444 case event := <-eventq: 445 if !r.matchevent(event) { 446 continue 447 } 448 449 switch event.Action { 450 case "health_status: unhealthy": 451 return ErrContainerUnhealthy 452 } 453 } 454 } 455 }