github.com/kobeld/docker@v1.12.0-rc1/daemon/cluster/executor/container/controller.go (about) 1 package container 2 3 import ( 4 "fmt" 5 "strings" 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 "golang.org/x/net/context" 13 ) 14 15 // controller implements agent.Controller against docker's API. 16 // 17 // Most operations against docker's API are done through the container name, 18 // which is unique to the task. 19 type controller struct { 20 backend executorpkg.Backend 21 task *api.Task 22 adapter *containerAdapter 23 closed chan struct{} 24 err error 25 } 26 27 var _ exec.Controller = &controller{} 28 29 // NewController returns a dockerexec runner for the provided task. 30 func newController(b executorpkg.Backend, task *api.Task) (*controller, error) { 31 adapter, err := newContainerAdapter(b, task) 32 if err != nil { 33 return nil, err 34 } 35 36 return &controller{ 37 backend: b, 38 task: task, 39 adapter: adapter, 40 closed: make(chan struct{}), 41 }, nil 42 } 43 44 func (r *controller) Task() (*api.Task, error) { 45 return r.task, nil 46 } 47 48 // ContainerStatus returns the container-specific status for the task. 49 func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus, error) { 50 ctnr, err := r.adapter.inspect(ctx) 51 if err != nil { 52 if isUnknownContainer(err) { 53 return nil, nil 54 } 55 return nil, err 56 } 57 return parseContainerStatus(ctnr) 58 } 59 60 // Update tasks a recent task update and applies it to the container. 61 func (r *controller) Update(ctx context.Context, t *api.Task) error { 62 log.G(ctx).Warnf("task updates not yet supported") 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 for { 88 if err := r.checkClosed(); err != nil { 89 return err 90 } 91 if err := r.adapter.create(ctx, r.backend); err != nil { 92 if isContainerCreateNameConflict(err) { 93 if _, err := r.adapter.inspect(ctx); err != nil { 94 return err 95 } 96 97 // container is already created. success! 98 return exec.ErrTaskPrepared 99 } 100 101 if !strings.Contains(err.Error(), "No such image") { // todo: better error detection 102 return err 103 } 104 if err := r.adapter.pullImage(ctx); err != nil { 105 return err 106 } 107 108 continue // retry to create the container 109 } 110 111 break 112 } 113 114 return nil 115 } 116 117 // Start the container. An error will be returned if the container is already started. 118 func (r *controller) Start(ctx context.Context) error { 119 if err := r.checkClosed(); err != nil { 120 return err 121 } 122 123 ctnr, err := r.adapter.inspect(ctx) 124 if err != nil { 125 return err 126 } 127 128 // Detect whether the container has *ever* been started. If so, we don't 129 // issue the start. 130 // 131 // TODO(stevvooe): This is very racy. While reading inspect, another could 132 // start the process and we could end up starting it twice. 133 if ctnr.State.Status != "created" { 134 return exec.ErrTaskStarted 135 } 136 137 if err := r.adapter.start(ctx); err != nil { 138 return err 139 } 140 141 return nil 142 } 143 144 // Wait on the container to exit. 145 func (r *controller) Wait(pctx context.Context) error { 146 if err := r.checkClosed(); err != nil { 147 return err 148 } 149 150 ctx, cancel := context.WithCancel(pctx) 151 defer cancel() 152 153 err := r.adapter.wait(ctx) 154 if err != nil { 155 return err 156 } 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 } 169 return nil 170 } 171 172 // Shutdown the container cleanly. 173 func (r *controller) Shutdown(ctx context.Context) error { 174 if err := r.checkClosed(); err != nil { 175 return err 176 } 177 178 if err := r.adapter.shutdown(ctx); err != nil { 179 if isUnknownContainer(err) || isStoppedContainer(err) { 180 return nil 181 } 182 183 return err 184 } 185 186 return nil 187 } 188 189 // Terminate the container, with force. 190 func (r *controller) Terminate(ctx context.Context) error { 191 if err := r.checkClosed(); err != nil { 192 return err 193 } 194 195 if err := r.adapter.terminate(ctx); err != nil { 196 if isUnknownContainer(err) { 197 return nil 198 } 199 200 return err 201 } 202 203 return nil 204 } 205 206 // Remove the container and its resources. 207 func (r *controller) Remove(ctx context.Context) error { 208 if err := r.checkClosed(); err != nil { 209 return err 210 } 211 212 // It may be necessary to shut down the task before removing it. 213 if err := r.Shutdown(ctx); err != nil { 214 if isUnknownContainer(err) { 215 return nil 216 } 217 // This may fail if the task was already shut down. 218 log.G(ctx).WithError(err).Debug("shutdown failed on removal") 219 } 220 221 // Try removing networks referenced in this task in case this 222 // task is the last one referencing it 223 if err := r.adapter.removeNetworks(ctx); err != nil { 224 if isUnknownContainer(err) { 225 return nil 226 } 227 return err 228 } 229 230 if err := r.adapter.remove(ctx); err != nil { 231 if isUnknownContainer(err) { 232 return nil 233 } 234 235 return err 236 } 237 return nil 238 } 239 240 // Close the runner and clean up any ephemeral resources. 241 func (r *controller) Close() error { 242 select { 243 case <-r.closed: 244 return r.err 245 default: 246 r.err = exec.ErrControllerClosed 247 close(r.closed) 248 } 249 return nil 250 } 251 252 func (r *controller) checkClosed() error { 253 select { 254 case <-r.closed: 255 return r.err 256 default: 257 return nil 258 } 259 } 260 261 func parseContainerStatus(ctnr types.ContainerJSON) (*api.ContainerStatus, error) { 262 status := &api.ContainerStatus{ 263 ContainerID: ctnr.ID, 264 PID: int32(ctnr.State.Pid), 265 ExitCode: int32(ctnr.State.ExitCode), 266 } 267 268 return status, nil 269 } 270 271 type exitError struct { 272 code int 273 cause error 274 } 275 276 func (e *exitError) Error() string { 277 if e.cause != nil { 278 return fmt.Sprintf("task: non-zero exit (%v): %v", e.code, e.cause) 279 } 280 281 return fmt.Sprintf("task: non-zero exit (%v)", e.code) 282 } 283 284 func (e *exitError) ExitCode() int { 285 return int(e.code) 286 } 287 288 func (e *exitError) Cause() error { 289 return e.cause 290 }