Browse code

Add support for docker run in swarm mode overlay

This PR adds support for running regular containers to be connected to
swarm mode multi-host network so that:
- containers connected to the same network across the cluster can
discover and connect to each other.
- Get access to services(and their associated loadbalancers)
connected to the same network

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>

Jana Radhakrishnan authored on 2016/08/24 08:50:15
Showing 23 changed files
... ...
@@ -23,6 +23,7 @@ type createOptions struct {
23 23
 	labels     []string
24 24
 	internal   bool
25 25
 	ipv6       bool
26
+	attachable bool
26 27
 
27 28
 	ipamDriver  string
28 29
 	ipamSubnet  []string
... ...
@@ -55,6 +56,7 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
55 55
 	flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
56 56
 	flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
57 57
 	flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
58
+	flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment")
58 59
 
59 60
 	flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
60 61
 	flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
... ...
@@ -87,6 +89,7 @@ func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
87 87
 		CheckDuplicate: true,
88 88
 		Internal:       opts.internal,
89 89
 		EnableIPv6:     opts.ipv6,
90
+		Attachable:     opts.attachable,
90 91
 		Labels:         runconfigopts.ConvertKVStringsToMap(opts.labels),
91 92
 	}
92 93
 
... ...
@@ -451,6 +451,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
451 451
 				Mounts:          opts.mounts.Value(),
452 452
 				StopGracePeriod: opts.stopGrace.Value(),
453 453
 			},
454
+			Networks:      convertNetworks(opts.networks),
454 455
 			Resources:     opts.resources.ToResourceRequirements(),
455 456
 			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
456 457
 			Placement: &swarm.Placement{
... ...
@@ -458,13 +459,13 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
458 458
 			},
459 459
 			LogDriver: opts.logDriver.toLogDriver(),
460 460
 		},
461
-		Mode: swarm.ServiceMode{},
461
+		Networks: convertNetworks(opts.networks),
462
+		Mode:     swarm.ServiceMode{},
462 463
 		UpdateConfig: &swarm.UpdateConfig{
463 464
 			Parallelism:   opts.update.parallelism,
464 465
 			Delay:         opts.update.delay,
465 466
 			FailureAction: opts.update.onFailure,
466 467
 		},
467
-		Networks:     convertNetworks(opts.networks),
468 468
 		EndpointSpec: opts.endpoint.ToEndpointSpec(),
469 469
 	}
470 470
 
... ...
@@ -2,7 +2,6 @@ package network
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"fmt"
6 5
 	"net/http"
7 6
 
8 7
 	"golang.org/x/net/context"
... ...
@@ -11,7 +10,6 @@ import (
11 11
 	"github.com/docker/docker/api/types"
12 12
 	"github.com/docker/docker/api/types/filters"
13 13
 	"github.com/docker/docker/api/types/network"
14
-	"github.com/docker/docker/errors"
15 14
 	"github.com/docker/libnetwork"
16 15
 )
17 16
 
... ...
@@ -116,17 +114,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
116 116
 		return err
117 117
 	}
118 118
 
119
-	nw, err := n.backend.FindNetwork(vars["id"])
120
-	if err != nil {
121
-		return err
122
-	}
123
-
124
-	if nw.Info().Dynamic() {
125
-		err := fmt.Errorf("operation not supported for swarm scoped networks")
126
-		return errors.NewRequestForbiddenError(err)
127
-	}
128
-
129
-	return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
119
+	return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
130 120
 }
131 121
 
132 122
 func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -143,13 +131,6 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
143 143
 		return err
144 144
 	}
145 145
 
146
-	nw, _ := n.backend.FindNetwork(vars["id"])
147
-
148
-	if nw != nil && nw.Info().Dynamic() {
149
-		err := fmt.Errorf("operation not supported for swarm scoped networks")
150
-		return errors.NewRequestForbiddenError(err)
151
-	}
152
-
153 146
 	return n.backend.DisconnectContainerFromNetwork(disconnect.Container, vars["id"], disconnect.Force)
154 147
 }
155 148
 
... ...
@@ -700,7 +700,9 @@ func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwor
700 700
 	}
701 701
 
702 702
 	if _, ok := networkSettings.Networks[n.Name()]; !ok {
703
-		networkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
703
+		networkSettings.Networks[n.Name()] = &network.EndpointSettings{
704
+			EndpointSettings: &networktypes.EndpointSettings{},
705
+		}
704 706
 	}
705 707
 	networkSettings.Networks[n.Name()].NetworkID = n.ID()
706 708
 	networkSettings.Networks[n.Name()].EndpointID = ep.ID()
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/Sirupsen/logrus"
17 17
 	apitypes "github.com/docker/docker/api/types"
18 18
 	"github.com/docker/docker/api/types/filters"
19
+	"github.com/docker/docker/api/types/network"
19 20
 	types "github.com/docker/docker/api/types/swarm"
20 21
 	"github.com/docker/docker/daemon/cluster/convert"
21 22
 	executorpkg "github.com/docker/docker/daemon/cluster/executor"
... ...
@@ -126,6 +127,18 @@ type Cluster struct {
126 126
 	stop            bool
127 127
 	err             error
128 128
 	cancelDelay     func()
129
+	attachers       map[string]*attacher
130
+}
131
+
132
+// attacher manages the in-memory attachment state of a container
133
+// attachment to a global scope network managed by swarm manager. It
134
+// helps in identifying the attachment ID via the taskID and the
135
+// corresponding attachment configuration obtained from the manager.
136
+type attacher struct {
137
+	taskID       string
138
+	config       *network.NetworkingConfig
139
+	attachWaitCh chan *network.NetworkingConfig
140
+	detachWaitCh chan struct{}
129 141
 }
130 142
 
131 143
 type node struct {
... ...
@@ -154,6 +167,7 @@ func New(config Config) (*Cluster, error) {
154 154
 		config:      config,
155 155
 		configEvent: make(chan struct{}, 10),
156 156
 		runtimeRoot: config.RuntimeRoot,
157
+		attachers:   make(map[string]*attacher),
157 158
 	}
158 159
 
159 160
 	st, err := c.loadState()
... ...
@@ -1212,6 +1226,120 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
1212 1212
 	return networks, nil
1213 1213
 }
1214 1214
 
1215
+func attacherKey(target, containerID string) string {
1216
+	return containerID + ":" + target
1217
+}
1218
+
1219
+// UpdateAttachment signals the attachment config to the attachment
1220
+// waiter who is trying to start or attach the container to the
1221
+// network.
1222
+func (c *Cluster) UpdateAttachment(target, containerID string, config *network.NetworkingConfig) error {
1223
+	c.RLock()
1224
+	attacher, ok := c.attachers[attacherKey(target, containerID)]
1225
+	c.RUnlock()
1226
+	if !ok || attacher == nil {
1227
+		return fmt.Errorf("could not find attacher for container %s to network %s", containerID, target)
1228
+	}
1229
+
1230
+	attacher.attachWaitCh <- config
1231
+	close(attacher.attachWaitCh)
1232
+	return nil
1233
+}
1234
+
1235
+// WaitForDetachment waits for the container to stop or detach from
1236
+// the network.
1237
+func (c *Cluster) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
1238
+	c.RLock()
1239
+	attacher, ok := c.attachers[attacherKey(networkName, containerID)]
1240
+	if !ok {
1241
+		attacher, ok = c.attachers[attacherKey(networkID, containerID)]
1242
+	}
1243
+	if c.node == nil || c.node.Agent() == nil {
1244
+		c.RUnlock()
1245
+		return fmt.Errorf("invalid cluster node while waiting for detachment")
1246
+	}
1247
+
1248
+	agent := c.node.Agent()
1249
+	c.RUnlock()
1250
+
1251
+	if ok && attacher != nil && attacher.detachWaitCh != nil {
1252
+		select {
1253
+		case <-attacher.detachWaitCh:
1254
+		case <-ctx.Done():
1255
+			return ctx.Err()
1256
+		}
1257
+	}
1258
+
1259
+	return agent.ResourceAllocator().DetachNetwork(ctx, taskID)
1260
+}
1261
+
1262
+// AttachNetwork generates an attachment request towards the manager.
1263
+func (c *Cluster) AttachNetwork(target string, containerID string, addresses []string) (*network.NetworkingConfig, error) {
1264
+	aKey := attacherKey(target, containerID)
1265
+	c.Lock()
1266
+	if c.node == nil || c.node.Agent() == nil {
1267
+		c.Unlock()
1268
+		return nil, fmt.Errorf("invalid cluster node while attaching to network")
1269
+	}
1270
+	if attacher, ok := c.attachers[aKey]; ok {
1271
+		c.Unlock()
1272
+		return attacher.config, nil
1273
+	}
1274
+
1275
+	agent := c.node.Agent()
1276
+	attachWaitCh := make(chan *network.NetworkingConfig)
1277
+	detachWaitCh := make(chan struct{})
1278
+	c.attachers[aKey] = &attacher{
1279
+		attachWaitCh: attachWaitCh,
1280
+		detachWaitCh: detachWaitCh,
1281
+	}
1282
+	c.Unlock()
1283
+
1284
+	ctx, cancel := c.getRequestContext()
1285
+	defer cancel()
1286
+
1287
+	taskID, err := agent.ResourceAllocator().AttachNetwork(ctx, containerID, target, addresses)
1288
+	if err != nil {
1289
+		c.Lock()
1290
+		delete(c.attachers, aKey)
1291
+		c.Unlock()
1292
+		return nil, fmt.Errorf("Could not attach to network %s: %v", target, err)
1293
+	}
1294
+
1295
+	logrus.Debugf("Successfully attached to network %s with tid %s", target, taskID)
1296
+
1297
+	var config *network.NetworkingConfig
1298
+	select {
1299
+	case config = <-attachWaitCh:
1300
+	case <-ctx.Done():
1301
+		return nil, fmt.Errorf("attaching to network failed, make sure your network options are correct and check manager logs: %v", ctx.Err())
1302
+	}
1303
+
1304
+	c.Lock()
1305
+	c.attachers[aKey].taskID = taskID
1306
+	c.attachers[aKey].config = config
1307
+	c.Unlock()
1308
+	return config, nil
1309
+}
1310
+
1311
+// DetachNetwork unblocks the waiters waiting on WaitForDetachment so
1312
+// that a request to detach can be generated towards the manager.
1313
+func (c *Cluster) DetachNetwork(target string, containerID string) error {
1314
+	aKey := attacherKey(target, containerID)
1315
+
1316
+	c.Lock()
1317
+	attacher, ok := c.attachers[aKey]
1318
+	delete(c.attachers, aKey)
1319
+	c.Unlock()
1320
+
1321
+	if !ok {
1322
+		return fmt.Errorf("could not find network attachment for container %s to network %s", containerID, target)
1323
+	}
1324
+
1325
+	close(attacher.detachWaitCh)
1326
+	return nil
1327
+}
1328
+
1215 1329
 // CreateNetwork creates a new cluster managed network.
1216 1330
 func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
1217 1331
 	c.RLock()
... ...
@@ -1262,7 +1390,14 @@ func (c *Cluster) RemoveNetwork(input string) error {
1262 1262
 }
1263 1263
 
1264 1264
 func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.ControlClient, s *types.ServiceSpec) error {
1265
-	for i, n := range s.Networks {
1265
+	// Always prefer NetworkAttachmentConfigs from TaskTemplate
1266
+	// but fallback to service spec for backward compatibility
1267
+	networks := s.TaskTemplate.Networks
1268
+	if len(networks) == 0 {
1269
+		networks = s.Networks
1270
+	}
1271
+
1272
+	for i, n := range networks {
1266 1273
 		apiNetwork, err := getNetwork(ctx, client, n.Target)
1267 1274
 		if err != nil {
1268 1275
 			if ln, _ := c.config.Backend.FindNetwork(n.Target); ln != nil && !ln.Info().Dynamic() {
... ...
@@ -1271,7 +1406,7 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
1271 1271
 			}
1272 1272
 			return err
1273 1273
 		}
1274
-		s.Networks[i].Target = apiNetwork.ID
1274
+		networks[i].Target = apiNetwork.ID
1275 1275
 	}
1276 1276
 	return nil
1277 1277
 }
... ...
@@ -27,6 +27,7 @@ func networkFromGRPC(n *swarmapi.Network) types.Network {
27 27
 			Spec: types.NetworkSpec{
28 28
 				IPv6Enabled: n.Spec.Ipv6Enabled,
29 29
 				Internal:    n.Spec.Internal,
30
+				Attachable:  n.Spec.Attachable,
30 31
 				IPAMOptions: ipamFromGRPC(n.Spec.IPAM),
31 32
 			},
32 33
 			IPAMOptions: ipamFromGRPC(n.IPAM),
... ...
@@ -155,6 +156,7 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
155 155
 		EnableIPv6: spec.Ipv6Enabled,
156 156
 		IPAM:       ipam,
157 157
 		Internal:   spec.Internal,
158
+		Attachable: spec.Attachable,
158 159
 		Labels:     n.Spec.Annotations.Labels,
159 160
 	}
160 161
 
... ...
@@ -179,6 +181,7 @@ func BasicNetworkCreateToGRPC(create basictypes.NetworkCreateRequest) swarmapi.N
179 179
 		},
180 180
 		Ipv6Enabled: create.EnableIPv6,
181 181
 		Internal:    create.Internal,
182
+		Attachable:  create.Attachable,
182 183
 	}
183 184
 	if create.IPAM != nil {
184 185
 		ns.IPAM = &swarmapi.IPAMOptions{
... ...
@@ -15,10 +15,16 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
15 15
 	spec := s.Spec
16 16
 	containerConfig := spec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container
17 17
 
18
-	networks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
18
+	serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
19 19
 	for _, n := range spec.Networks {
20
-		networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
20
+		serviceNetworks = append(serviceNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
21 21
 	}
22
+
23
+	taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Task.Networks))
24
+	for _, n := range spec.Task.Networks {
25
+		taskNetworks = append(taskNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
26
+	}
27
+
22 28
 	service := types.Service{
23 29
 		ID: s.ID,
24 30
 
... ...
@@ -29,9 +35,10 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
29 29
 				RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
30 30
 				Placement:     placementFromGRPC(s.Spec.Task.Placement),
31 31
 				LogDriver:     driverFromGRPC(s.Spec.Task.LogDriver),
32
+				Networks:      taskNetworks,
32 33
 			},
33 34
 
34
-			Networks:     networks,
35
+			Networks:     serviceNetworks,
35 36
 			EndpointSpec: endpointSpecFromGRPC(s.Spec.Endpoint),
36 37
 		},
37 38
 		Endpoint: endpointFromGRPC(s.Endpoint),
... ...
@@ -99,9 +106,14 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
99 99
 		name = namesgenerator.GetRandomName(0)
100 100
 	}
101 101
 
102
-	networks := make([]*swarmapi.ServiceSpec_NetworkAttachmentConfig, 0, len(s.Networks))
102
+	serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
103 103
 	for _, n := range s.Networks {
104
-		networks = append(networks, &swarmapi.ServiceSpec_NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
104
+		serviceNetworks = append(serviceNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
105
+	}
106
+
107
+	taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
108
+	for _, n := range s.TaskTemplate.Networks {
109
+		taskNetworks = append(taskNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
105 110
 	}
106 111
 
107 112
 	spec := swarmapi.ServiceSpec{
... ...
@@ -112,8 +124,9 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
112 112
 		Task: swarmapi.TaskSpec{
113 113
 			Resources: resourcesToGRPC(s.TaskTemplate.Resources),
114 114
 			LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
115
+			Networks:  taskNetworks,
115 116
 		},
116
-		Networks: networks,
117
+		Networks: serviceNetworks,
117 118
 	}
118 119
 
119 120
 	containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)
... ...
@@ -12,6 +12,11 @@ import (
12 12
 func TaskFromGRPC(t swarmapi.Task) types.Task {
13 13
 	containerConfig := t.Spec.Runtime.(*swarmapi.TaskSpec_Container).Container
14 14
 	containerStatus := t.Status.GetContainer()
15
+	networks := make([]types.NetworkAttachmentConfig, 0, len(t.Spec.Networks))
16
+	for _, n := range t.Spec.Networks {
17
+		networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
18
+	}
19
+
15 20
 	task := types.Task{
16 21
 		ID:        t.ID,
17 22
 		ServiceID: t.ServiceID,
... ...
@@ -23,6 +28,7 @@ func TaskFromGRPC(t swarmapi.Task) types.Task {
23 23
 			RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
24 24
 			Placement:     placementFromGRPC(t.Spec.Placement),
25 25
 			LogDriver:     driverFromGRPC(t.Spec.LogDriver),
26
+			Networks:      networks,
26 27
 		},
27 28
 		Status: types.TaskStatus{
28 29
 			State:   types.TaskState(strings.ToLower(t.Status.State.String())),
... ...
@@ -40,4 +40,6 @@ type Backend interface {
40 40
 	IsSwarmCompatible() error
41 41
 	SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{})
42 42
 	UnsubscribeFromEvents(listener chan interface{})
43
+	UpdateAttachment(string, string, string, *network.NetworkingConfig) error
44
+	WaitForDetachment(context.Context, string, string, string, string) error
43 45
 }
... ...
@@ -144,6 +144,44 @@ func (c *containerAdapter) removeNetworks(ctx context.Context) error {
144 144
 	return nil
145 145
 }
146 146
 
147
+func (c *containerAdapter) networkAttach(ctx context.Context) error {
148
+	config := c.container.createNetworkingConfig()
149
+
150
+	var (
151
+		networkName string
152
+		networkID   string
153
+	)
154
+
155
+	if config != nil {
156
+		for n, epConfig := range config.EndpointsConfig {
157
+			networkName = n
158
+			networkID = epConfig.NetworkID
159
+			break
160
+		}
161
+	}
162
+
163
+	return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config)
164
+}
165
+
166
+func (c *containerAdapter) waitForDetach(ctx context.Context) error {
167
+	config := c.container.createNetworkingConfig()
168
+
169
+	var (
170
+		networkName string
171
+		networkID   string
172
+	)
173
+
174
+	if config != nil {
175
+		for n, epConfig := range config.EndpointsConfig {
176
+			networkName = n
177
+			networkID = epConfig.NetworkID
178
+			break
179
+		}
180
+	}
181
+
182
+	return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id())
183
+}
184
+
147 185
 func (c *containerAdapter) create(ctx context.Context) error {
148 186
 	var cr types.ContainerCreateResponse
149 187
 	var err error
... ...
@@ -233,7 +271,7 @@ func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
233 233
 }
234 234
 
235 235
 func (c *containerAdapter) wait(ctx context.Context) error {
236
-	return c.backend.ContainerWaitWithContext(ctx, c.container.name())
236
+	return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID())
237 237
 }
238 238
 
239 239
 func (c *containerAdapter) shutdown(ctx context.Context) error {
240 240
new file mode 100644
... ...
@@ -0,0 +1,80 @@
0
+package container
1
+
2
+import (
3
+	executorpkg "github.com/docker/docker/daemon/cluster/executor"
4
+	"github.com/docker/swarmkit/api"
5
+	"golang.org/x/net/context"
6
+)
7
+
8
+// networkAttacherController implements agent.Controller against docker's API.
9
+//
10
+// networkAttacherController manages the lifecycle of network
11
+// attachment of a docker unmanaged container managed as a task from
12
+// agent point of view. It provides network attachment information to
13
+// the unmanaged container for it to attach to the network and run.
14
+type networkAttacherController struct {
15
+	backend executorpkg.Backend
16
+	task    *api.Task
17
+	adapter *containerAdapter
18
+	closed  chan struct{}
19
+}
20
+
21
+func newNetworkAttacherController(b executorpkg.Backend, task *api.Task) (*networkAttacherController, error) {
22
+	adapter, err := newContainerAdapter(b, task)
23
+	if err != nil {
24
+		return nil, err
25
+	}
26
+
27
+	return &networkAttacherController{
28
+		backend: b,
29
+		task:    task,
30
+		adapter: adapter,
31
+		closed:  make(chan struct{}),
32
+	}, nil
33
+}
34
+
35
+func (nc *networkAttacherController) Update(ctx context.Context, t *api.Task) error {
36
+	return nil
37
+}
38
+
39
+func (nc *networkAttacherController) Prepare(ctx context.Context) error {
40
+	// Make sure all the networks that the task needs are created.
41
+	if err := nc.adapter.createNetworks(ctx); err != nil {
42
+		return err
43
+	}
44
+
45
+	return nil
46
+}
47
+
48
+func (nc *networkAttacherController) Start(ctx context.Context) error {
49
+	return nc.adapter.networkAttach(ctx)
50
+}
51
+
52
+func (nc *networkAttacherController) Wait(pctx context.Context) error {
53
+	ctx, cancel := context.WithCancel(pctx)
54
+	defer cancel()
55
+
56
+	return nc.adapter.waitForDetach(ctx)
57
+}
58
+
59
+func (nc *networkAttacherController) Shutdown(ctx context.Context) error {
60
+	return nil
61
+}
62
+
63
+func (nc *networkAttacherController) Terminate(ctx context.Context) error {
64
+	return nil
65
+}
66
+
67
+func (nc *networkAttacherController) Remove(ctx context.Context) error {
68
+	// Try removing the network referenced in this task in case this
69
+	// task is the last one referencing it
70
+	if err := nc.adapter.removeNetworks(ctx); err != nil {
71
+		return err
72
+	}
73
+
74
+	return nil
75
+}
76
+
77
+func (nc *networkAttacherController) Close() error {
78
+	return nil
79
+}
... ...
@@ -44,17 +44,19 @@ func newContainerConfig(t *api.Task) (*containerConfig, error) {
44 44
 }
45 45
 
46 46
 func (c *containerConfig) setTask(t *api.Task) error {
47
-	container := t.Spec.GetContainer()
48
-	if container == nil {
47
+	if t.Spec.GetContainer() == nil && t.Spec.GetAttachment() == nil {
49 48
 		return exec.ErrRuntimeUnsupported
50 49
 	}
51 50
 
52
-	if container.Image == "" {
53
-		return ErrImageRequired
54
-	}
51
+	container := t.Spec.GetContainer()
52
+	if container != nil {
53
+		if container.Image == "" {
54
+			return ErrImageRequired
55
+		}
55 56
 
56
-	if err := validateMounts(container.Mounts); err != nil {
57
-		return err
57
+		if err := validateMounts(container.Mounts); err != nil {
58
+			return err
59
+		}
58 60
 	}
59 61
 
60 62
 	// index the networks by name
... ...
@@ -67,6 +69,19 @@ func (c *containerConfig) setTask(t *api.Task) error {
67 67
 	return nil
68 68
 }
69 69
 
70
+func (c *containerConfig) id() string {
71
+	attachment := c.task.Spec.GetAttachment()
72
+	if attachment == nil {
73
+		return ""
74
+	}
75
+
76
+	return attachment.ContainerID
77
+}
78
+
79
+func (c *containerConfig) taskID() string {
80
+	return c.task.ID
81
+}
82
+
70 83
 func (c *containerConfig) endpoint() *api.Endpoint {
71 84
 	return c.task.Endpoint
72 85
 }
... ...
@@ -75,6 +90,14 @@ func (c *containerConfig) spec() *api.ContainerSpec {
75 75
 	return c.task.Spec.GetContainer()
76 76
 }
77 77
 
78
+func (c *containerConfig) nameOrID() string {
79
+	if c.task.Spec.GetContainer() != nil {
80
+		return c.name()
81
+	}
82
+
83
+	return c.id()
84
+}
85
+
78 86
 func (c *containerConfig) name() string {
79 87
 	if c.task.Annotations.Name != "" {
80 88
 		// if set, use the container Annotations.Name field, set in the orchestrator.
... ...
@@ -342,7 +365,7 @@ func (c *containerConfig) resources() enginecontainer.Resources {
342 342
 // Docker daemon supports just 1 network during container create.
343 343
 func (c *containerConfig) createNetworkingConfig() *network.NetworkingConfig {
344 344
 	var networks []*api.NetworkAttachment
345
-	if c.task.Spec.GetContainer() != nil {
345
+	if c.task.Spec.GetContainer() != nil || c.task.Spec.GetAttachment() != nil {
346 346
 		networks = c.task.Networks
347 347
 	}
348 348
 
... ...
@@ -392,6 +415,7 @@ func getEndpointConfig(na *api.NetworkAttachment) *network.EndpointSettings {
392 392
 	}
393 393
 
394 394
 	return &network.EndpointSettings{
395
+		NetworkID: na.Network.ID,
395 396
 		IPAMConfig: &network.EndpointIPAMConfig{
396 397
 			IPv4Address: ipv4,
397 398
 			IPv6Address: ipv6,
... ...
@@ -121,6 +121,10 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error {
121 121
 
122 122
 // Controller returns a docker container runner.
123 123
 func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
124
+	if t.Spec.GetAttachment() != nil {
125
+		return newNetworkAttacherController(e.backend, t)
126
+	}
127
+
124 128
 	ctlr, err := newController(e.backend, t)
125 129
 	if err != nil {
126 130
 		return nil, err
... ...
@@ -178,7 +178,7 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
178 178
 	// return if this call to build join options is not for default bridge network
179 179
 	// Legacy Link is only supported by docker run --link
180 180
 	bridgeSettings, ok := container.NetworkSettings.Networks[defaultNetName]
181
-	if !ok {
181
+	if !ok || bridgeSettings.EndpointSettings == nil {
182 182
 		return sboxOptions, nil
183 183
 	}
184 184
 
... ...
@@ -238,9 +238,9 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
238 238
 	return sboxOptions, nil
239 239
 }
240 240
 
241
-func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network) error {
241
+func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network, endpointConfig *networktypes.EndpointSettings) error {
242 242
 	if container.NetworkSettings == nil {
243
-		container.NetworkSettings = &network.Settings{Networks: make(map[string]*networktypes.EndpointSettings)}
243
+		container.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)}
244 244
 	}
245 245
 
246 246
 	if !container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() {
... ...
@@ -268,7 +268,9 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
268 268
 	}
269 269
 
270 270
 	if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
271
-		container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
271
+		container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
272
+			EndpointSettings: endpointConfig,
273
+		}
272 274
 	}
273 275
 
274 276
 	return nil
... ...
@@ -331,12 +333,63 @@ func errClusterNetworkOnRun(n string) error {
331 331
 	return fmt.Errorf("swarm-scoped network (%s) is not compatible with `docker create` or `docker run`. This network can only be used by a docker service", n)
332 332
 }
333 333
 
334
+func (daemon *Daemon) findAndAttachNetwork(container *container.Container, idOrName string, epConfig *networktypes.EndpointSettings) (libnetwork.Network, *networktypes.NetworkingConfig, error) {
335
+	n, err := daemon.FindNetwork(idOrName)
336
+	if err != nil {
337
+		// We should always be able to find the network for a
338
+		// managed container.
339
+		if container.Managed {
340
+			return nil, nil, err
341
+		}
342
+	}
343
+
344
+	// If we found a network and if it is not dynamically created
345
+	// we should never attempt to attach to that network here.
346
+	if n != nil {
347
+		if container.Managed || !n.Info().Dynamic() {
348
+			return n, nil, nil
349
+		}
350
+	}
351
+
352
+	var addresses []string
353
+	if epConfig != nil && epConfig.IPAMConfig != nil {
354
+		if epConfig.IPAMConfig.IPv4Address != "" {
355
+			addresses = append(addresses, epConfig.IPAMConfig.IPv4Address)
356
+		}
357
+
358
+		if epConfig.IPAMConfig.IPv6Address != "" {
359
+			addresses = append(addresses, epConfig.IPAMConfig.IPv6Address)
360
+		}
361
+	}
362
+
363
+	// In all other cases, attempt to attach to the network to
364
+	// trigger attachment in the swarm cluster manager.
365
+	var config *networktypes.NetworkingConfig
366
+	if daemon.clusterProvider != nil {
367
+		var err error
368
+		config, err = daemon.clusterProvider.AttachNetwork(idOrName, container.ID, addresses)
369
+		if err != nil {
370
+			return nil, nil, err
371
+		}
372
+	}
373
+
374
+	n, err = daemon.FindNetwork(idOrName)
375
+	if err != nil {
376
+		if daemon.clusterProvider != nil {
377
+			if err := daemon.clusterProvider.DetachNetwork(idOrName, container.ID); err != nil {
378
+				logrus.Warnf("Could not rollback attachment for container %s to network %s: %v", container.ID, idOrName, err)
379
+			}
380
+		}
381
+
382
+		return nil, nil, err
383
+	}
384
+
385
+	return n, config, nil
386
+}
387
+
334 388
 // updateContainerNetworkSettings update the network settings
335 389
 func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
336
-	var (
337
-		n   libnetwork.Network
338
-		err error
339
-	)
390
+	var n libnetwork.Network
340 391
 
341 392
 	mode := container.HostConfig.NetworkMode
342 393
 	if container.Config.NetworkDisabled || mode.IsContainer() {
... ...
@@ -347,26 +400,48 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
347 347
 	if mode.IsDefault() {
348 348
 		networkName = daemon.netController.Config().Daemon.DefaultNetwork
349 349
 	}
350
+
350 351
 	if mode.IsUserDefined() {
352
+		var err error
353
+
351 354
 		n, err = daemon.FindNetwork(networkName)
352
-		if err != nil {
353
-			return err
354
-		}
355
-		if !container.Managed && n.Info().Dynamic() {
356
-			return errClusterNetworkOnRun(networkName)
355
+		if err == nil {
356
+			networkName = n.Name()
357 357
 		}
358
-		networkName = n.Name()
359 358
 	}
359
+
360 360
 	if container.NetworkSettings == nil {
361 361
 		container.NetworkSettings = &network.Settings{}
362 362
 	}
363
+
363 364
 	if len(endpointsConfig) > 0 {
364
-		container.NetworkSettings.Networks = endpointsConfig
365
+		if container.NetworkSettings.Networks == nil {
366
+			container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
367
+		}
368
+
369
+		for name, epConfig := range endpointsConfig {
370
+			container.NetworkSettings.Networks[name] = &network.EndpointSettings{
371
+				EndpointSettings: epConfig,
372
+			}
373
+		}
365 374
 	}
375
+
366 376
 	if container.NetworkSettings.Networks == nil {
367
-		container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
368
-		container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
377
+		container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
378
+		container.NetworkSettings.Networks[networkName] = &network.EndpointSettings{
379
+			EndpointSettings: &networktypes.EndpointSettings{},
380
+		}
369 381
 	}
382
+
383
+	// Convert any settings added by client in default name to
384
+	// engine's default network name key
385
+	if mode.IsDefault() {
386
+		if nConf, ok := container.NetworkSettings.Networks[mode.NetworkName()]; ok {
387
+			container.NetworkSettings.Networks[networkName] = nConf
388
+			delete(container.NetworkSettings.Networks, mode.NetworkName())
389
+		}
390
+	}
391
+
370 392
 	if !mode.IsUserDefined() {
371 393
 		return nil
372 394
 	}
... ...
@@ -374,10 +449,13 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
374 374
 	if _, ok := container.NetworkSettings.Networks[networkName]; ok {
375 375
 		return nil
376 376
 	}
377
-	if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
378
-		container.NetworkSettings.Networks[networkName] = nwConfig
379
-		delete(container.NetworkSettings.Networks, n.ID())
380
-		return nil
377
+
378
+	if n != nil {
379
+		if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
380
+			container.NetworkSettings.Networks[networkName] = nwConfig
381
+			delete(container.NetworkSettings.Networks, n.ID())
382
+			return nil
383
+		}
381 384
 	}
382 385
 
383 386
 	return nil
... ...
@@ -414,16 +492,27 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
414 414
 	// on first network connecting.
415 415
 	defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
416 416
 	if nConf, ok := container.NetworkSettings.Networks[defaultNetName]; ok {
417
-		if err := daemon.connectToNetwork(container, defaultNetName, nConf, updateSettings); err != nil {
417
+		if err := daemon.connectToNetwork(container, defaultNetName, nConf.EndpointSettings, updateSettings); err != nil {
418 418
 			return err
419 419
 		}
420 420
 
421 421
 	}
422
-	for n, nConf := range container.NetworkSettings.Networks {
422
+	var (
423
+		networks  []string
424
+		epConfigs []*network.EndpointSettings
425
+	)
426
+
427
+	for n, epConf := range container.NetworkSettings.Networks {
423 428
 		if n == defaultNetName {
424 429
 			continue
425 430
 		}
426
-		if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
431
+
432
+		networks = append(networks, n)
433
+		epConfigs = append(epConfigs, epConf)
434
+	}
435
+
436
+	for i, epConf := range epConfigs {
437
+		if err := daemon.connectToNetwork(container, networks[i], epConf.EndpointSettings, updateSettings); err != nil {
427 438
 			return err
428 439
 		}
429 440
 	}
... ...
@@ -488,7 +577,7 @@ func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.Endpo
488 488
 }
489 489
 
490 490
 // cleanOperationalData resets the operational data from the passed endpoint settings
491
-func cleanOperationalData(es *networktypes.EndpointSettings) {
491
+func cleanOperationalData(es *network.EndpointSettings) {
492 492
 	es.EndpointID = ""
493 493
 	es.Gateway = ""
494 494
 	es.IPAddress = ""
... ...
@@ -497,25 +586,18 @@ func cleanOperationalData(es *networktypes.EndpointSettings) {
497 497
 	es.GlobalIPv6Address = ""
498 498
 	es.GlobalIPv6PrefixLen = 0
499 499
 	es.MacAddress = ""
500
-}
501
-
502
-func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (libnetwork.Network, error) {
503
-	if container.HostConfig.NetworkMode.IsContainer() {
504
-		return nil, runconfig.ErrConflictSharedNetwork
505
-	}
506
-
507
-	if containertypes.NetworkMode(idOrName).IsBridge() &&
508
-		daemon.configStore.DisableBridge {
509
-		container.Config.NetworkDisabled = true
510
-		return nil, nil
500
+	if es.IPAMOperational {
501
+		es.IPAMConfig = nil
511 502
 	}
503
+}
512 504
 
513
-	if !containertypes.NetworkMode(idOrName).IsUserDefined() {
505
+func (daemon *Daemon) updateNetworkConfig(container *container.Container, n libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error {
506
+	if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
514 507
 		if hasUserDefinedIPAddress(endpointConfig) && !enableIPOnPredefinedNetwork() {
515
-			return nil, runconfig.ErrUnsupportedNetworkAndIP
508
+			return runconfig.ErrUnsupportedNetworkAndIP
516 509
 		}
517 510
 		if endpointConfig != nil && len(endpointConfig.Aliases) > 0 {
518
-			return nil, runconfig.ErrUnsupportedNetworkAndAlias
511
+			return runconfig.ErrUnsupportedNetworkAndAlias
519 512
 		}
520 513
 	} else {
521 514
 		addShortID := true
... ...
@@ -531,28 +613,34 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrNa
531 531
 		}
532 532
 	}
533 533
 
534
-	n, err := daemon.FindNetwork(idOrName)
535
-	if err != nil {
536
-		return nil, err
537
-	}
538
-
539 534
 	if err := validateNetworkingConfig(n, endpointConfig); err != nil {
540
-		return nil, err
535
+		return err
541 536
 	}
542 537
 
543 538
 	if updateSettings {
544
-		if err := daemon.updateNetworkSettings(container, n); err != nil {
545
-			return nil, err
539
+		if err := daemon.updateNetworkSettings(container, n, endpointConfig); err != nil {
540
+			return err
546 541
 		}
547 542
 	}
548
-	return n, nil
543
+	return nil
549 544
 }
550 545
 
551 546
 func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
547
+	if container.HostConfig.NetworkMode.IsContainer() {
548
+		return runconfig.ErrConflictSharedNetwork
549
+	}
550
+
551
+	if containertypes.NetworkMode(idOrName).IsBridge() &&
552
+		daemon.configStore.DisableBridge {
553
+		container.Config.NetworkDisabled = true
554
+		return nil
555
+	}
556
+
552 557
 	if endpointConfig == nil {
553 558
 		endpointConfig = &networktypes.EndpointSettings{}
554 559
 	}
555
-	n, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, updateSettings)
560
+
561
+	n, config, err := daemon.findAndAttachNetwork(container, idOrName, endpointConfig)
556 562
 	if err != nil {
557 563
 		return err
558 564
 	}
... ...
@@ -560,6 +648,25 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
560 560
 		return nil
561 561
 	}
562 562
 
563
+	var operIPAM bool
564
+	if config != nil {
565
+		if epConfig, ok := config.EndpointsConfig[n.Name()]; ok {
566
+			if endpointConfig.IPAMConfig == nil ||
567
+				(endpointConfig.IPAMConfig.IPv4Address == "" &&
568
+					endpointConfig.IPAMConfig.IPv6Address == "" &&
569
+					len(endpointConfig.IPAMConfig.LinkLocalIPs) == 0) {
570
+				operIPAM = true
571
+			}
572
+
573
+			endpointConfig = epConfig
574
+		}
575
+	}
576
+
577
+	err = daemon.updateNetworkConfig(container, n, endpointConfig, updateSettings)
578
+	if err != nil {
579
+		return err
580
+	}
581
+
563 582
 	controller := daemon.netController
564 583
 
565 584
 	sb := daemon.getNetworkSandbox(container)
... ...
@@ -580,7 +687,13 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
580 580
 			}
581 581
 		}
582 582
 	}()
583
-	container.NetworkSettings.Networks[n.Name()] = endpointConfig
583
+	container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
584
+		EndpointSettings: endpointConfig,
585
+		IPAMOperational:  operIPAM,
586
+	}
587
+	if _, ok := container.NetworkSettings.Networks[n.ID()]; ok {
588
+		delete(container.NetworkSettings.Networks, n.ID())
589
+	}
584 590
 
585 591
 	if err := daemon.updateEndpointNetworkSettings(container, n, ep); err != nil {
586 592
 		return err
... ...
@@ -632,7 +745,7 @@ func (daemon *Daemon) ForceEndpointDelete(name string, networkName string) error
632 632
 	return ep.Delete(true)
633 633
 }
634 634
 
635
-func disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
635
+func (daemon *Daemon) disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
636 636
 	var (
637 637
 		ep   libnetwork.Endpoint
638 638
 		sbox libnetwork.Sandbox
... ...
@@ -678,6 +791,13 @@ func disconnectFromNetwork(container *container.Container, n libnetwork.Network,
678 678
 	}
679 679
 
680 680
 	delete(container.NetworkSettings.Networks, n.Name())
681
+
682
+	if daemon.clusterProvider != nil && n.Info().Dynamic() && !container.Managed {
683
+		if err := daemon.clusterProvider.DetachNetwork(n.Name(), container.ID); err != nil {
684
+			logrus.Warnf("error detaching from network %s: %v", n, err)
685
+		}
686
+	}
687
+
681 688
 	return nil
682 689
 }
683 690
 
... ...
@@ -751,6 +871,11 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
751 751
 		if nw, err := daemon.FindNetwork(n); err == nil {
752 752
 			networks = append(networks, nw)
753 753
 		}
754
+
755
+		if epSettings.EndpointSettings == nil {
756
+			continue
757
+		}
758
+
754 759
 		cleanOperationalData(epSettings)
755 760
 	}
756 761
 
... ...
@@ -765,6 +890,12 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
765 765
 	}
766 766
 
767 767
 	for _, nw := range networks {
768
+		if daemon.clusterProvider != nil && nw.Info().Dynamic() && !container.Managed {
769
+			if err := daemon.clusterProvider.DetachNetwork(nw.Name(), container.ID); err != nil {
770
+				logrus.Warnf("error detaching from network %s: %v", nw.Name(), err)
771
+			}
772
+		}
773
+
768 774
 		attributes := map[string]string{
769 775
 			"container": container.ID,
770 776
 		}
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	networktypes "github.com/docker/docker/api/types/network"
17 17
 	"github.com/docker/docker/container"
18 18
 	"github.com/docker/docker/daemon/links"
19
+	"github.com/docker/docker/daemon/network"
19 20
 	"github.com/docker/docker/pkg/fileutils"
20 21
 	"github.com/docker/docker/pkg/idtools"
21 22
 	"github.com/docker/docker/pkg/mount"
... ...
@@ -35,7 +36,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
35 35
 	children := daemon.children(container)
36 36
 
37 37
 	bridgeSettings := container.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()]
38
-	if bridgeSettings == nil {
38
+	if bridgeSettings == nil || bridgeSettings.EndpointSettings == nil {
39 39
 		return nil, nil
40 40
 	}
41 41
 
... ...
@@ -45,7 +46,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
45 45
 		}
46 46
 
47 47
 		childBridgeSettings := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()]
48
-		if childBridgeSettings == nil {
48
+		if childBridgeSettings == nil || childBridgeSettings.EndpointSettings == nil {
49 49
 			return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID)
50 50
 		}
51 51
 
... ...
@@ -107,10 +108,17 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
107 107
 		if container.RemovalInProgress || container.Dead {
108 108
 			return errRemovalContainer(container.ID)
109 109
 		}
110
-		if _, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, true); err != nil {
111
-			return err
110
+
111
+		n, err := daemon.FindNetwork(idOrName)
112
+		if err == nil && n != nil {
113
+			if err := daemon.updateNetworkConfig(container, n, endpointConfig, true); err != nil {
114
+				return err
115
+			}
116
+		} else {
117
+			container.NetworkSettings.Networks[idOrName] = &network.EndpointSettings{
118
+				EndpointSettings: endpointConfig,
119
+			}
112 120
 		}
113
-		container.NetworkSettings.Networks[idOrName] = endpointConfig
114 121
 	} else {
115 122
 		if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
116 123
 			return err
... ...
@@ -143,7 +151,7 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, netw
143 143
 			return runconfig.ErrConflictHostNetwork
144 144
 		}
145 145
 
146
-		if err := disconnectFromNetwork(container, n, false); err != nil {
146
+		if err := daemon.disconnectFromNetwork(container, n, false); err != nil {
147 147
 			return err
148 148
 		}
149 149
 	} else {
... ...
@@ -252,7 +252,7 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
252 252
 	}
253 253
 	if len(nwConfig.EndpointsConfig) == 1 {
254 254
 		for _, v := range nwConfig.EndpointsConfig {
255
-			if v.IPAMConfig != nil {
255
+			if v != nil && v.IPAMConfig != nil {
256 256
 				if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
257 257
 					return errors.NewBadRequestError(fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address))
258 258
 				}
... ...
@@ -42,6 +42,13 @@ func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.Co
42 42
 		return nil, err
43 43
 	}
44 44
 
45
+	apiNetworks := make(map[string]*networktypes.EndpointSettings)
46
+	for name, epConf := range container.NetworkSettings.Networks {
47
+		if epConf.EndpointSettings != nil {
48
+			apiNetworks[name] = epConf.EndpointSettings
49
+		}
50
+	}
51
+
45 52
 	mountPoints := addMountPoints(container)
46 53
 	networkSettings := &types.NetworkSettings{
47 54
 		NetworkSettingsBase: types.NetworkSettingsBase{
... ...
@@ -56,7 +63,7 @@ func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.Co
56 56
 			SecondaryIPv6Addresses: container.NetworkSettings.SecondaryIPv6Addresses,
57 57
 		},
58 58
 		DefaultNetworkSettings: daemon.getDefaultNetworkSettings(container.NetworkSettings.Networks),
59
-		Networks:               container.NetworkSettings.Networks,
59
+		Networks:               apiNetworks,
60 60
 	}
61 61
 
62 62
 	return &types.ContainerJSON{
... ...
@@ -236,10 +243,10 @@ func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Se
236 236
 
237 237
 // getDefaultNetworkSettings creates the deprecated structure that holds the information
238 238
 // about the bridge network for a container.
239
-func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*networktypes.EndpointSettings) types.DefaultNetworkSettings {
239
+func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings {
240 240
 	var settings types.DefaultNetworkSettings
241 241
 
242
-	if defaultNetwork, ok := networks["bridge"]; ok {
242
+	if defaultNetwork, ok := networks["bridge"]; ok && defaultNetwork.EndpointSettings != nil {
243 243
 		settings.EndpointID = defaultNetwork.EndpointID
244 244
 		settings.Gateway = defaultNetwork.Gateway
245 245
 		settings.GlobalIPv6Address = defaultNetwork.GlobalIPv6Address
... ...
@@ -400,6 +400,9 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
400 400
 				return networkExist
401 401
 			}
402 402
 			for _, nw := range container.NetworkSettings.Networks {
403
+				if nw.EndpointSettings == nil {
404
+					continue
405
+				}
403 406
 				if nw.NetworkID == value {
404 407
 					return networkExist
405 408
 				}
... ...
@@ -460,7 +463,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
460 460
 	// copy networks to avoid races
461 461
 	networks := make(map[string]*networktypes.EndpointSettings)
462 462
 	for name, network := range container.NetworkSettings.Networks {
463
-		if network == nil {
463
+		if network == nil || network.EndpointSettings == nil {
464 464
 			continue
465 465
 		}
466 466
 		networks[name] = &networktypes.EndpointSettings{
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/runconfig"
15 15
 	"github.com/docker/libnetwork"
16 16
 	networktypes "github.com/docker/libnetwork/types"
17
+	"golang.org/x/net/context"
17 18
 )
18 19
 
19 20
 // NetworkControllerEnabled checks if the networking stack is enabled.
... ...
@@ -186,6 +187,29 @@ func (daemon *Daemon) SetNetworkBootstrapKeys(keys []*networktypes.EncryptionKey
186 186
 	return daemon.netController.SetKeys(keys)
187 187
 }
188 188
 
189
+// UpdateAttachment notifies the attacher about the attachment config.
190
+func (daemon *Daemon) UpdateAttachment(networkName, networkID, containerID string, config *network.NetworkingConfig) error {
191
+	if daemon.clusterProvider == nil {
192
+		return fmt.Errorf("cluster provider is not initialized")
193
+	}
194
+
195
+	if err := daemon.clusterProvider.UpdateAttachment(networkName, containerID, config); err != nil {
196
+		return daemon.clusterProvider.UpdateAttachment(networkID, containerID, config)
197
+	}
198
+
199
+	return nil
200
+}
201
+
202
+// WaitForDetachment makes the cluster manager wait for detachment of
203
+// the container from the network.
204
+func (daemon *Daemon) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
205
+	if daemon.clusterProvider == nil {
206
+		return fmt.Errorf("cluster provider is not initialized")
207
+	}
208
+
209
+	return daemon.clusterProvider.WaitForDetachment(ctx, networkName, networkID, taskID, containerID)
210
+}
211
+
189 212
 // CreateManagedNetwork creates an agent network.
190 213
 func (daemon *Daemon) CreateManagedNetwork(create clustertypes.NetworkCreateRequest) error {
191 214
 	_, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true)
... ...
@@ -14,7 +14,7 @@ type Settings struct {
14 14
 	HairpinMode            bool
15 15
 	LinkLocalIPv6Address   string
16 16
 	LinkLocalIPv6PrefixLen int
17
-	Networks               map[string]*networktypes.EndpointSettings
17
+	Networks               map[string]*EndpointSettings
18 18
 	Service                *clustertypes.ServiceConfig
19 19
 	Ports                  nat.PortMap
20 20
 	SandboxKey             string
... ...
@@ -22,3 +22,11 @@ type Settings struct {
22 22
 	SecondaryIPv6Addresses []networktypes.Address
23 23
 	IsAnonymousEndpoint    bool
24 24
 }
25
+
26
+// EndpointSettings is a package local wrapper for
27
+// networktypes.EndpointSettings which stores Endpoint state that
28
+// needs to be persisted to disk but not exposed in the api.
29
+type EndpointSettings struct {
30
+	*networktypes.EndpointSettings
31
+	IPAMOperational bool
32
+}
... ...
@@ -298,6 +298,8 @@ func (s *DockerSwarmSuite) TearDownTest(c *check.C) {
298 298
 		if err := os.RemoveAll(walDir); err != nil {
299 299
 			c.Logf("error removing %v: %v", walDir, err)
300 300
 		}
301
+
302
+		cleanupExecRoot(c, d.execRoot)
301 303
 	}
302 304
 	s.daemons = nil
303 305
 	s.daemonsLock.Unlock()
... ...
@@ -2,7 +2,29 @@
2 2
 
3 3
 package main
4 4
 
5
-import "syscall"
5
+import (
6
+	"os"
7
+	"path/filepath"
8
+	"syscall"
9
+
10
+	"github.com/go-check/check"
11
+)
12
+
13
+func cleanupExecRoot(c *check.C, execRoot string) {
14
+	// Cleanup network namespaces in the exec root of this
15
+	// daemon because this exec root is specific to this
16
+	// daemon instance and has no chance of getting
17
+	// cleaned up when a new daemon is instantiated with a
18
+	// new exec root.
19
+	netnsPath := filepath.Join(execRoot, "netns")
20
+	filepath.Walk(netnsPath, func(path string, info os.FileInfo, err error) error {
21
+		if err := syscall.Unmount(path, syscall.MNT_FORCE); err != nil {
22
+			c.Logf("unmount of %s failed: %v", path, err)
23
+		}
24
+		os.Remove(path)
25
+		return nil
26
+	})
27
+}
6 28
 
7 29
 func signalDaemonDump(pid int) {
8 30
 	syscall.Kill(pid, syscall.SIGQUIT)
... ...
@@ -5,6 +5,8 @@ import (
5 5
 	"strconv"
6 6
 	"syscall"
7 7
 	"unsafe"
8
+
9
+	"github.com/go-check/check"
8 10
 )
9 11
 
10 12
 func openEvent(desiredAccess uint32, inheritHandle bool, name string, proc *syscall.LazyProc) (handle syscall.Handle, err error) {
... ...
@@ -45,3 +47,6 @@ func signalDaemonDump(pid int) {
45 45
 func signalDaemonReload(pid int) error {
46 46
 	return fmt.Errorf("daemon reload not supported")
47 47
 }
48
+
49
+func cleanupExecRoot(c *check.C, execRoot string) {
50
+}