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