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>
... | ... |
@@ -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 |
+} |