github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/daemon/cluster/executor/container/adapter.go (about) 1 package container 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "io" 8 "strings" 9 "syscall" 10 "time" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/api/server/httputils" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/events" 16 "github.com/docker/docker/api/types/versions" 17 executorpkg "github.com/docker/docker/daemon/cluster/executor" 18 "github.com/docker/libnetwork" 19 "github.com/docker/swarmkit/api" 20 "github.com/docker/swarmkit/log" 21 "golang.org/x/net/context" 22 "golang.org/x/time/rate" 23 ) 24 25 // containerAdapter conducts remote operations for a container. All calls 26 // are mostly naked calls to the client API, seeded with information from 27 // containerConfig. 28 type containerAdapter struct { 29 backend executorpkg.Backend 30 container *containerConfig 31 } 32 33 func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapter, error) { 34 ctnr, err := newContainerConfig(task) 35 if err != nil { 36 return nil, err 37 } 38 39 return &containerAdapter{ 40 container: ctnr, 41 backend: b, 42 }, nil 43 } 44 45 func (c *containerAdapter) pullImage(ctx context.Context) error { 46 spec := c.container.spec() 47 48 // if the image needs to be pulled, the auth config will be retrieved and updated 49 var encodedAuthConfig string 50 if spec.PullOptions != nil { 51 encodedAuthConfig = spec.PullOptions.RegistryAuth 52 } 53 54 authConfig := &types.AuthConfig{} 55 if encodedAuthConfig != "" { 56 if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil { 57 logrus.Warnf("invalid authconfig: %v", err) 58 } 59 } 60 61 pr, pw := io.Pipe() 62 metaHeaders := map[string][]string{} 63 go func() { 64 err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw) 65 pw.CloseWithError(err) 66 }() 67 68 dec := json.NewDecoder(pr) 69 dec.UseNumber() 70 m := map[string]interface{}{} 71 spamLimiter := rate.NewLimiter(rate.Every(time.Second), 1) 72 73 lastStatus := "" 74 for { 75 if err := dec.Decode(&m); err != nil { 76 if err == io.EOF { 77 break 78 } 79 return err 80 } 81 l := log.G(ctx) 82 // limit pull progress logs unless the status changes 83 if spamLimiter.Allow() || lastStatus != m["status"] { 84 // if we have progress details, we have everything we need 85 if progress, ok := m["progressDetail"].(map[string]interface{}); ok { 86 // first, log the image and status 87 l = l.WithFields(logrus.Fields{ 88 "image": c.container.image(), 89 "status": m["status"], 90 }) 91 // then, if we have progress, log the progress 92 if progress["current"] != nil && progress["total"] != nil { 93 l = l.WithFields(logrus.Fields{ 94 "current": progress["current"], 95 "total": progress["total"], 96 }) 97 } 98 } 99 l.Debug("pull in progress") 100 } 101 // sometimes, we get no useful information at all, and add no fields 102 if status, ok := m["status"].(string); ok { 103 lastStatus = status 104 } 105 } 106 107 // if the final stream object contained an error, return it 108 if errMsg, ok := m["error"]; ok { 109 return fmt.Errorf("%v", errMsg) 110 } 111 return nil 112 } 113 114 func (c *containerAdapter) createNetworks(ctx context.Context) error { 115 for _, network := range c.container.networks() { 116 ncr, err := c.container.networkCreateRequest(network) 117 if err != nil { 118 return err 119 } 120 121 if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing 122 if _, ok := err.(libnetwork.NetworkNameError); ok { 123 continue 124 } 125 126 return err 127 } 128 } 129 130 return nil 131 } 132 133 func (c *containerAdapter) removeNetworks(ctx context.Context) error { 134 for _, nid := range c.container.networks() { 135 if err := c.backend.DeleteManagedNetwork(nid); err != nil { 136 switch err.(type) { 137 case *libnetwork.ActiveEndpointsError: 138 continue 139 case libnetwork.ErrNoSuchNetwork: 140 continue 141 default: 142 log.G(ctx).Errorf("network %s remove failed: %v", nid, err) 143 return err 144 } 145 } 146 } 147 148 return nil 149 } 150 151 func (c *containerAdapter) networkAttach(ctx context.Context) error { 152 config := c.container.createNetworkingConfig() 153 154 var ( 155 networkName string 156 networkID string 157 ) 158 159 if config != nil { 160 for n, epConfig := range config.EndpointsConfig { 161 networkName = n 162 networkID = epConfig.NetworkID 163 break 164 } 165 } 166 167 return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config) 168 } 169 170 func (c *containerAdapter) waitForDetach(ctx context.Context) error { 171 config := c.container.createNetworkingConfig() 172 173 var ( 174 networkName string 175 networkID string 176 ) 177 178 if config != nil { 179 for n, epConfig := range config.EndpointsConfig { 180 networkName = n 181 networkID = epConfig.NetworkID 182 break 183 } 184 } 185 186 return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id()) 187 } 188 189 func (c *containerAdapter) create(ctx context.Context) error { 190 var cr types.ContainerCreateResponse 191 var err error 192 version := httputils.VersionFromContext(ctx) 193 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 194 195 if cr, err = c.backend.CreateManagedContainer(types.ContainerCreateConfig{ 196 Name: c.container.name(), 197 Config: c.container.config(), 198 HostConfig: c.container.hostConfig(), 199 // Use the first network in container create 200 NetworkingConfig: c.container.createNetworkingConfig(), 201 }, validateHostname); err != nil { 202 return err 203 } 204 205 // Docker daemon currently doesn't support multiple networks in container create 206 // Connect to all other networks 207 nc := c.container.connectNetworkingConfig() 208 209 if nc != nil { 210 for n, ep := range nc.EndpointsConfig { 211 if err := c.backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil { 212 return err 213 } 214 } 215 } 216 217 if err := c.backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil { 218 return err 219 } 220 221 return nil 222 } 223 224 func (c *containerAdapter) start(ctx context.Context) error { 225 version := httputils.VersionFromContext(ctx) 226 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 227 return c.backend.ContainerStart(c.container.name(), nil, validateHostname, "", "") 228 } 229 230 func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) { 231 cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false) 232 if ctx.Err() != nil { 233 return types.ContainerJSON{}, ctx.Err() 234 } 235 if err != nil { 236 return types.ContainerJSON{}, err 237 } 238 return *cs, nil 239 } 240 241 // events issues a call to the events API and returns a channel with all 242 // events. The stream of events can be shutdown by cancelling the context. 243 func (c *containerAdapter) events(ctx context.Context) <-chan events.Message { 244 log.G(ctx).Debugf("waiting on events") 245 buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter()) 246 eventsq := make(chan events.Message, len(buffer)) 247 248 for _, event := range buffer { 249 eventsq <- event 250 } 251 252 go func() { 253 defer c.backend.UnsubscribeFromEvents(l) 254 255 for { 256 select { 257 case ev := <-l: 258 jev, ok := ev.(events.Message) 259 if !ok { 260 log.G(ctx).Warnf("unexpected event message: %q", ev) 261 continue 262 } 263 select { 264 case eventsq <- jev: 265 case <-ctx.Done(): 266 return 267 } 268 case <-ctx.Done(): 269 return 270 } 271 } 272 }() 273 274 return eventsq 275 } 276 277 func (c *containerAdapter) wait(ctx context.Context) error { 278 return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID()) 279 } 280 281 func (c *containerAdapter) shutdown(ctx context.Context) error { 282 // Default stop grace period to nil (daemon will use the stopTimeout of the container) 283 var stopgrace *int 284 spec := c.container.spec() 285 if spec.StopGracePeriod != nil { 286 stopgraceValue := int(spec.StopGracePeriod.Seconds) 287 stopgrace = &stopgraceValue 288 } 289 return c.backend.ContainerStop(c.container.name(), stopgrace) 290 } 291 292 func (c *containerAdapter) terminate(ctx context.Context) error { 293 return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL)) 294 } 295 296 func (c *containerAdapter) remove(ctx context.Context) error { 297 return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{ 298 RemoveVolume: true, 299 ForceRemove: true, 300 }) 301 } 302 303 func (c *containerAdapter) createVolumes(ctx context.Context) error { 304 // Create plugin volumes that are embedded inside a Mount 305 for _, mount := range c.container.task.Spec.GetContainer().Mounts { 306 if mount.Type != api.MountTypeVolume { 307 continue 308 } 309 310 if mount.VolumeOptions == nil { 311 continue 312 } 313 314 if mount.VolumeOptions.DriverConfig == nil { 315 continue 316 } 317 318 req := c.container.volumeCreateRequest(&mount) 319 320 // Check if this volume exists on the engine 321 if _, err := c.backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil { 322 // TODO(amitshukla): Today, volume create through the engine api does not return an error 323 // when the named volume with the same parameters already exists. 324 // It returns an error if the driver name is different - that is a valid error 325 return err 326 } 327 328 } 329 330 return nil 331 } 332 333 // todo: typed/wrapped errors 334 func isContainerCreateNameConflict(err error) bool { 335 return strings.Contains(err.Error(), "Conflict. The name") 336 } 337 338 func isUnknownContainer(err error) bool { 339 return strings.Contains(err.Error(), "No such container:") 340 } 341 342 func isStoppedContainer(err error) bool { 343 return strings.Contains(err.Error(), "is already stopped") 344 }