github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/daemon/cluster/executor/container/adapter.go (about) 1 package container 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/distribution/reference" 16 "github.com/docker/docker/api/types" 17 "github.com/docker/docker/api/types/backend" 18 containertypes "github.com/docker/docker/api/types/container" 19 "github.com/docker/docker/api/types/events" 20 "github.com/docker/docker/daemon/cluster/convert" 21 executorpkg "github.com/docker/docker/daemon/cluster/executor" 22 "github.com/docker/libnetwork" 23 "github.com/docker/swarmkit/agent/exec" 24 "github.com/docker/swarmkit/api" 25 "github.com/docker/swarmkit/log" 26 gogotypes "github.com/gogo/protobuf/types" 27 "github.com/opencontainers/go-digest" 28 "golang.org/x/net/context" 29 "golang.org/x/time/rate" 30 ) 31 32 // containerAdapter conducts remote operations for a container. All calls 33 // are mostly naked calls to the client API, seeded with information from 34 // containerConfig. 35 type containerAdapter struct { 36 backend executorpkg.Backend 37 container *containerConfig 38 secrets exec.SecretGetter 39 } 40 41 func newContainerAdapter(b executorpkg.Backend, task *api.Task, secrets exec.SecretGetter) (*containerAdapter, error) { 42 ctnr, err := newContainerConfig(task) 43 if err != nil { 44 return nil, err 45 } 46 47 return &containerAdapter{ 48 container: ctnr, 49 backend: b, 50 secrets: secrets, 51 }, nil 52 } 53 54 func (c *containerAdapter) pullImage(ctx context.Context) error { 55 spec := c.container.spec() 56 57 // Skip pulling if the image is referenced by image ID. 58 if _, err := digest.Parse(spec.Image); err == nil { 59 return nil 60 } 61 62 // Skip pulling if the image is referenced by digest and already 63 // exists locally. 64 named, err := reference.ParseNormalizedNamed(spec.Image) 65 if err == nil { 66 if _, ok := named.(reference.Canonical); ok { 67 _, err := c.backend.LookupImage(spec.Image) 68 if err == nil { 69 return nil 70 } 71 } 72 } 73 74 // if the image needs to be pulled, the auth config will be retrieved and updated 75 var encodedAuthConfig string 76 if spec.PullOptions != nil { 77 encodedAuthConfig = spec.PullOptions.RegistryAuth 78 } 79 80 authConfig := &types.AuthConfig{} 81 if encodedAuthConfig != "" { 82 if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil { 83 logrus.Warnf("invalid authconfig: %v", err) 84 } 85 } 86 87 pr, pw := io.Pipe() 88 metaHeaders := map[string][]string{} 89 go func() { 90 err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw) 91 pw.CloseWithError(err) 92 }() 93 94 dec := json.NewDecoder(pr) 95 dec.UseNumber() 96 m := map[string]interface{}{} 97 spamLimiter := rate.NewLimiter(rate.Every(time.Second), 1) 98 99 lastStatus := "" 100 for { 101 if err := dec.Decode(&m); err != nil { 102 if err == io.EOF { 103 break 104 } 105 return err 106 } 107 l := log.G(ctx) 108 // limit pull progress logs unless the status changes 109 if spamLimiter.Allow() || lastStatus != m["status"] { 110 // if we have progress details, we have everything we need 111 if progress, ok := m["progressDetail"].(map[string]interface{}); ok { 112 // first, log the image and status 113 l = l.WithFields(logrus.Fields{ 114 "image": c.container.image(), 115 "status": m["status"], 116 }) 117 // then, if we have progress, log the progress 118 if progress["current"] != nil && progress["total"] != nil { 119 l = l.WithFields(logrus.Fields{ 120 "current": progress["current"], 121 "total": progress["total"], 122 }) 123 } 124 } 125 l.Debug("pull in progress") 126 } 127 // sometimes, we get no useful information at all, and add no fields 128 if status, ok := m["status"].(string); ok { 129 lastStatus = status 130 } 131 } 132 133 // if the final stream object contained an error, return it 134 if errMsg, ok := m["error"]; ok { 135 return fmt.Errorf("%v", errMsg) 136 } 137 return nil 138 } 139 140 func (c *containerAdapter) createNetworks(ctx context.Context) error { 141 for _, network := range c.container.networks() { 142 ncr, err := c.container.networkCreateRequest(network) 143 if err != nil { 144 return err 145 } 146 147 if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing 148 if _, ok := err.(libnetwork.NetworkNameError); ok { 149 continue 150 } 151 152 return err 153 } 154 } 155 156 return nil 157 } 158 159 func (c *containerAdapter) removeNetworks(ctx context.Context) error { 160 for _, nid := range c.container.networks() { 161 if err := c.backend.DeleteManagedNetwork(nid); err != nil { 162 switch err.(type) { 163 case *libnetwork.ActiveEndpointsError: 164 continue 165 case libnetwork.ErrNoSuchNetwork: 166 continue 167 default: 168 log.G(ctx).Errorf("network %s remove failed: %v", nid, err) 169 return err 170 } 171 } 172 } 173 174 return nil 175 } 176 177 func (c *containerAdapter) networkAttach(ctx context.Context) error { 178 config := c.container.createNetworkingConfig() 179 180 var ( 181 networkName string 182 networkID string 183 ) 184 185 if config != nil { 186 for n, epConfig := range config.EndpointsConfig { 187 networkName = n 188 networkID = epConfig.NetworkID 189 break 190 } 191 } 192 193 return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config) 194 } 195 196 func (c *containerAdapter) waitForDetach(ctx context.Context) error { 197 config := c.container.createNetworkingConfig() 198 199 var ( 200 networkName string 201 networkID string 202 ) 203 204 if config != nil { 205 for n, epConfig := range config.EndpointsConfig { 206 networkName = n 207 networkID = epConfig.NetworkID 208 break 209 } 210 } 211 212 return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id()) 213 } 214 215 func (c *containerAdapter) create(ctx context.Context) error { 216 var cr containertypes.ContainerCreateCreatedBody 217 var err error 218 219 if cr, err = c.backend.CreateManagedContainer(types.ContainerCreateConfig{ 220 Name: c.container.name(), 221 Config: c.container.config(), 222 HostConfig: c.container.hostConfig(), 223 // Use the first network in container create 224 NetworkingConfig: c.container.createNetworkingConfig(), 225 }); err != nil { 226 return err 227 } 228 229 // Docker daemon currently doesn't support multiple networks in container create 230 // Connect to all other networks 231 nc := c.container.connectNetworkingConfig() 232 233 if nc != nil { 234 for n, ep := range nc.EndpointsConfig { 235 if err := c.backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil { 236 return err 237 } 238 } 239 } 240 241 container := c.container.task.Spec.GetContainer() 242 if container == nil { 243 return errors.New("unable to get container from task spec") 244 } 245 246 // configure secrets 247 if err := c.backend.SetContainerSecretStore(cr.ID, c.secrets); err != nil { 248 return err 249 } 250 251 refs := convert.SecretReferencesFromGRPC(container.Secrets) 252 if err := c.backend.SetContainerSecretReferences(cr.ID, refs); err != nil { 253 return err 254 } 255 256 if err := c.backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil { 257 return err 258 } 259 260 return nil 261 } 262 263 // checkMounts ensures that the provided mounts won't have any host-specific 264 // problems at start up. For example, we disallow bind mounts without an 265 // existing path, which slightly different from the container API. 266 func (c *containerAdapter) checkMounts() error { 267 spec := c.container.spec() 268 for _, mount := range spec.Mounts { 269 switch mount.Type { 270 case api.MountTypeBind: 271 if _, err := os.Stat(mount.Source); os.IsNotExist(err) { 272 return fmt.Errorf("invalid bind mount source, source path not found: %s", mount.Source) 273 } 274 } 275 } 276 277 return nil 278 } 279 280 func (c *containerAdapter) start(ctx context.Context) error { 281 if err := c.checkMounts(); err != nil { 282 return err 283 } 284 285 return c.backend.ContainerStart(c.container.name(), nil, "", "") 286 } 287 288 func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) { 289 cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false) 290 if ctx.Err() != nil { 291 return types.ContainerJSON{}, ctx.Err() 292 } 293 if err != nil { 294 return types.ContainerJSON{}, err 295 } 296 return *cs, nil 297 } 298 299 // events issues a call to the events API and returns a channel with all 300 // events. The stream of events can be shutdown by cancelling the context. 301 func (c *containerAdapter) events(ctx context.Context) <-chan events.Message { 302 log.G(ctx).Debugf("waiting on events") 303 buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter()) 304 eventsq := make(chan events.Message, len(buffer)) 305 306 for _, event := range buffer { 307 eventsq <- event 308 } 309 310 go func() { 311 defer c.backend.UnsubscribeFromEvents(l) 312 313 for { 314 select { 315 case ev := <-l: 316 jev, ok := ev.(events.Message) 317 if !ok { 318 log.G(ctx).Warnf("unexpected event message: %q", ev) 319 continue 320 } 321 select { 322 case eventsq <- jev: 323 case <-ctx.Done(): 324 return 325 } 326 case <-ctx.Done(): 327 return 328 } 329 } 330 }() 331 332 return eventsq 333 } 334 335 func (c *containerAdapter) wait(ctx context.Context) error { 336 return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID()) 337 } 338 339 func (c *containerAdapter) shutdown(ctx context.Context) error { 340 // Default stop grace period to nil (daemon will use the stopTimeout of the container) 341 var stopgrace *int 342 spec := c.container.spec() 343 if spec.StopGracePeriod != nil { 344 stopgraceValue := int(spec.StopGracePeriod.Seconds) 345 stopgrace = &stopgraceValue 346 } 347 return c.backend.ContainerStop(c.container.name(), stopgrace) 348 } 349 350 func (c *containerAdapter) terminate(ctx context.Context) error { 351 return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL)) 352 } 353 354 func (c *containerAdapter) remove(ctx context.Context) error { 355 return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{ 356 RemoveVolume: true, 357 ForceRemove: true, 358 }) 359 } 360 361 func (c *containerAdapter) createVolumes(ctx context.Context) error { 362 // Create plugin volumes that are embedded inside a Mount 363 for _, mount := range c.container.task.Spec.GetContainer().Mounts { 364 if mount.Type != api.MountTypeVolume { 365 continue 366 } 367 368 if mount.VolumeOptions == nil { 369 continue 370 } 371 372 if mount.VolumeOptions.DriverConfig == nil { 373 continue 374 } 375 376 req := c.container.volumeCreateRequest(&mount) 377 378 // Check if this volume exists on the engine 379 if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil { 380 // TODO(amitshukla): Today, volume create through the engine api does not return an error 381 // when the named volume with the same parameters already exists. 382 // It returns an error if the driver name is different - that is a valid error 383 return err 384 } 385 386 } 387 388 return nil 389 } 390 391 func (c *containerAdapter) activateServiceBinding() error { 392 return c.backend.ActivateContainerServiceBinding(c.container.name()) 393 } 394 395 func (c *containerAdapter) deactivateServiceBinding() error { 396 return c.backend.DeactivateContainerServiceBinding(c.container.name()) 397 } 398 399 func (c *containerAdapter) logs(ctx context.Context, options api.LogSubscriptionOptions) (io.ReadCloser, error) { 400 reader, writer := io.Pipe() 401 402 apiOptions := &backend.ContainerLogsConfig{ 403 ContainerLogsOptions: types.ContainerLogsOptions{ 404 Follow: options.Follow, 405 406 // TODO(stevvooe): Parse timestamp out of message. This 407 // absolutely needs to be done before going to production with 408 // this, at it is completely redundant. 409 Timestamps: true, 410 Details: false, // no clue what to do with this, let's just deprecate it. 411 }, 412 OutStream: writer, 413 } 414 415 if options.Since != nil { 416 since, err := gogotypes.TimestampFromProto(options.Since) 417 if err != nil { 418 return nil, err 419 } 420 apiOptions.Since = since.Format(time.RFC3339Nano) 421 } 422 423 if options.Tail < 0 { 424 // See protobuf documentation for details of how this works. 425 apiOptions.Tail = fmt.Sprint(-options.Tail - 1) 426 } else if options.Tail > 0 { 427 return nil, errors.New("tail relative to start of logs not supported via docker API") 428 } 429 430 if len(options.Streams) == 0 { 431 // empty == all 432 apiOptions.ShowStdout, apiOptions.ShowStderr = true, true 433 } else { 434 for _, stream := range options.Streams { 435 switch stream { 436 case api.LogStreamStdout: 437 apiOptions.ShowStdout = true 438 case api.LogStreamStderr: 439 apiOptions.ShowStderr = true 440 } 441 } 442 } 443 444 chStarted := make(chan struct{}) 445 go func() { 446 defer writer.Close() 447 c.backend.ContainerLogs(ctx, c.container.name(), apiOptions, chStarted) 448 }() 449 450 return reader, nil 451 } 452 453 // todo: typed/wrapped errors 454 func isContainerCreateNameConflict(err error) bool { 455 return strings.Contains(err.Error(), "Conflict. The name") 456 } 457 458 func isUnknownContainer(err error) bool { 459 return strings.Contains(err.Error(), "No such container:") 460 } 461 462 func isStoppedContainer(err error) bool { 463 return strings.Contains(err.Error(), "is already stopped") 464 }