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