github.com/portworx/docker@v1.12.1/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 executorpkg "github.com/docker/docker/daemon/cluster/executor" 15 "github.com/docker/engine-api/types" 16 "github.com/docker/engine-api/types/events" 17 "github.com/docker/engine-api/types/versions" 18 "github.com/docker/libnetwork" 19 "github.com/docker/swarmkit/api" 20 "github.com/docker/swarmkit/log" 21 "golang.org/x/net/context" 22 ) 23 24 // containerAdapter conducts remote operations for a container. All calls 25 // are mostly naked calls to the client API, seeded with information from 26 // containerConfig. 27 type containerAdapter struct { 28 backend executorpkg.Backend 29 container *containerConfig 30 } 31 32 func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapter, error) { 33 ctnr, err := newContainerConfig(task) 34 if err != nil { 35 return nil, err 36 } 37 38 return &containerAdapter{ 39 container: ctnr, 40 backend: b, 41 }, nil 42 } 43 44 func (c *containerAdapter) pullImage(ctx context.Context) error { 45 spec := c.container.spec() 46 47 // if the image needs to be pulled, the auth config will be retrieved and updated 48 var encodedAuthConfig string 49 if spec.PullOptions != nil { 50 encodedAuthConfig = spec.PullOptions.RegistryAuth 51 } 52 53 authConfig := &types.AuthConfig{} 54 if encodedAuthConfig != "" { 55 if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuthConfig))).Decode(authConfig); err != nil { 56 logrus.Warnf("invalid authconfig: %v", err) 57 } 58 } 59 60 pr, pw := io.Pipe() 61 metaHeaders := map[string][]string{} 62 go func() { 63 err := c.backend.PullImage(ctx, c.container.image(), "", metaHeaders, authConfig, pw) 64 pw.CloseWithError(err) 65 }() 66 67 dec := json.NewDecoder(pr) 68 m := map[string]interface{}{} 69 for { 70 if err := dec.Decode(&m); err != nil { 71 if err == io.EOF { 72 break 73 } 74 return err 75 } 76 // TODO(stevvooe): Report this status somewhere. 77 logrus.Debugln("pull progress", m) 78 } 79 // if the final stream object contained an error, return it 80 if errMsg, ok := m["error"]; ok { 81 return fmt.Errorf("%v", errMsg) 82 } 83 return nil 84 } 85 86 func (c *containerAdapter) createNetworks(ctx context.Context) error { 87 for _, network := range c.container.networks() { 88 ncr, err := c.container.networkCreateRequest(network) 89 if err != nil { 90 return err 91 } 92 93 if err := c.backend.CreateManagedNetwork(ncr); err != nil { // todo name missing 94 if _, ok := err.(libnetwork.NetworkNameError); ok { 95 continue 96 } 97 98 return err 99 } 100 } 101 102 return nil 103 } 104 105 func (c *containerAdapter) removeNetworks(ctx context.Context) error { 106 for _, nid := range c.container.networks() { 107 if err := c.backend.DeleteManagedNetwork(nid); err != nil { 108 if _, ok := err.(*libnetwork.ActiveEndpointsError); ok { 109 continue 110 } 111 log.G(ctx).Errorf("network %s remove failed: %v", nid, err) 112 return err 113 } 114 } 115 116 return nil 117 } 118 119 func (c *containerAdapter) create(ctx context.Context, backend executorpkg.Backend) error { 120 var cr types.ContainerCreateResponse 121 var err error 122 version := httputils.VersionFromContext(ctx) 123 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 124 125 if cr, err = backend.CreateManagedContainer(types.ContainerCreateConfig{ 126 Name: c.container.name(), 127 Config: c.container.config(), 128 HostConfig: c.container.hostConfig(), 129 // Use the first network in container create 130 NetworkingConfig: c.container.createNetworkingConfig(), 131 }, validateHostname); err != nil { 132 return err 133 } 134 135 // Docker daemon currently doesn't support multiple networks in container create 136 // Connect to all other networks 137 nc := c.container.connectNetworkingConfig() 138 139 if nc != nil { 140 for n, ep := range nc.EndpointsConfig { 141 if err := backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil { 142 return err 143 } 144 } 145 } 146 147 if err := backend.UpdateContainerServiceConfig(cr.ID, c.container.serviceConfig()); err != nil { 148 return err 149 } 150 151 return nil 152 } 153 154 func (c *containerAdapter) start(ctx context.Context) error { 155 version := httputils.VersionFromContext(ctx) 156 validateHostname := versions.GreaterThanOrEqualTo(version, "1.24") 157 return c.backend.ContainerStart(c.container.name(), nil, validateHostname) 158 } 159 160 func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, error) { 161 cs, err := c.backend.ContainerInspectCurrent(c.container.name(), false) 162 if ctx.Err() != nil { 163 return types.ContainerJSON{}, ctx.Err() 164 } 165 if err != nil { 166 return types.ContainerJSON{}, err 167 } 168 return *cs, nil 169 } 170 171 // events issues a call to the events API and returns a channel with all 172 // events. The stream of events can be shutdown by cancelling the context. 173 func (c *containerAdapter) events(ctx context.Context) <-chan events.Message { 174 log.G(ctx).Debugf("waiting on events") 175 buffer, l := c.backend.SubscribeToEvents(time.Time{}, time.Time{}, c.container.eventFilter()) 176 eventsq := make(chan events.Message, len(buffer)) 177 178 for _, event := range buffer { 179 eventsq <- event 180 } 181 182 go func() { 183 defer c.backend.UnsubscribeFromEvents(l) 184 185 for { 186 select { 187 case ev := <-l: 188 jev, ok := ev.(events.Message) 189 if !ok { 190 log.G(ctx).Warnf("unexpected event message: %q", ev) 191 continue 192 } 193 select { 194 case eventsq <- jev: 195 case <-ctx.Done(): 196 return 197 } 198 case <-ctx.Done(): 199 return 200 } 201 } 202 }() 203 204 return eventsq 205 } 206 207 func (c *containerAdapter) wait(ctx context.Context) error { 208 return c.backend.ContainerWaitWithContext(ctx, c.container.name()) 209 } 210 211 func (c *containerAdapter) shutdown(ctx context.Context) error { 212 // Default stop grace period to 10s. 213 stopgrace := 10 214 spec := c.container.spec() 215 if spec.StopGracePeriod != nil { 216 stopgrace = int(spec.StopGracePeriod.Seconds) 217 } 218 return c.backend.ContainerStop(c.container.name(), stopgrace) 219 } 220 221 func (c *containerAdapter) terminate(ctx context.Context) error { 222 return c.backend.ContainerKill(c.container.name(), uint64(syscall.SIGKILL)) 223 } 224 225 func (c *containerAdapter) remove(ctx context.Context) error { 226 return c.backend.ContainerRm(c.container.name(), &types.ContainerRmConfig{ 227 RemoveVolume: true, 228 ForceRemove: true, 229 }) 230 } 231 232 func (c *containerAdapter) createVolumes(ctx context.Context, backend executorpkg.Backend) error { 233 // Create plugin volumes that are embedded inside a Mount 234 for _, mount := range c.container.task.Spec.GetContainer().Mounts { 235 if mount.Type != api.MountTypeVolume { 236 continue 237 } 238 239 if mount.VolumeOptions == nil { 240 continue 241 } 242 243 if mount.VolumeOptions.DriverConfig == nil { 244 continue 245 } 246 247 req := c.container.volumeCreateRequest(&mount) 248 249 // Check if this volume exists on the engine 250 if _, err := backend.VolumeCreate(req.Name, req.Driver, req.DriverOpts, req.Labels); err != nil { 251 // TODO(amitshukla): Today, volume create through the engine api does not return an error 252 // when the named volume with the same parameters already exists. 253 // It returns an error if the driver name is different - that is a valid error 254 return err 255 } 256 257 } 258 259 return nil 260 } 261 262 // todo: typed/wrapped errors 263 func isContainerCreateNameConflict(err error) bool { 264 return strings.Contains(err.Error(), "Conflict. The name") 265 } 266 267 func isUnknownContainer(err error) bool { 268 return strings.Contains(err.Error(), "No such container:") 269 } 270 271 func isStoppedContainer(err error) bool { 272 return strings.Contains(err.Error(), "is already stopped") 273 }