Browse code

Add ConnectivityScope capability for network drivers along with scope network option

- It specifies whether the network driver can
provide containers connectivity across hosts.
- As of now, the data scope of the driver was
being overloaded with this notion.
- The driver scope information is still valid
and it defines whether the data allocation
of the network resources can be done globally
or only locally.
- With the scope network option, user can now
force a network as swarm scoped
regardless of the driver data scope.
- In case the network is configured as swarm scoped,
and the network driver is multihost capable,
a network DB instance will be launched for it.

Signed-off-by: Alessandro Boch <aboch@docker.com>

Alessandro Boch authored on 2017/04/08 05:31:44
Showing 21 changed files
... ...
@@ -222,7 +222,7 @@ func (c *controller) agentSetup(clusterProvider cluster.Provider) error {
222 222
 			return err
223 223
 		}
224 224
 		c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
225
-			if capability.DataScope == datastore.GlobalScope {
225
+			if capability.ConnectivityScope == datastore.GlobalScope {
226 226
 				c.agentDriverNotify(driver)
227 227
 			}
228 228
 			return false
... ...
@@ -507,7 +507,7 @@ func (n *network) Services() map[string]ServiceInfo {
507 507
 }
508 508
 
509 509
 func (n *network) isClusterEligible() bool {
510
-	if n.driverScope() != datastore.GlobalScope {
510
+	if n.scope != datastore.SwarmScope || !n.driverIsMultihost() {
511 511
 		return false
512 512
 	}
513 513
 	return n.getController().getAgent() != nil
... ...
@@ -621,7 +621,7 @@ func (c *controller) pushNodeDiscovery(d driverapi.Driver, cap driverapi.Capabil
621 621
 		}
622 622
 	}
623 623
 
624
-	if d == nil || cap.DataScope != datastore.GlobalScope || nodes == nil {
624
+	if d == nil || cap.ConnectivityScope != datastore.GlobalScope || nodes == nil {
625 625
 		return
626 626
 	}
627 627
 
... ...
@@ -747,11 +747,17 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
747 747
 		return nil, err
748 748
 	}
749 749
 
750
+	if network.scope == datastore.LocalScope && cap.DataScope == datastore.GlobalScope {
751
+		return nil, types.ForbiddenErrorf("cannot downgrade network scope for %s networks", networkType)
752
+
753
+	}
750 754
 	if network.ingress && cap.DataScope != datastore.GlobalScope {
751 755
 		return nil, types.ForbiddenErrorf("Ingress network can only be global scope network")
752 756
 	}
753 757
 
754
-	if cap.DataScope == datastore.GlobalScope && !c.isDistributedControl() && !network.dynamic {
758
+	// At this point the network scope is still unknown if not set by user
759
+	if (cap.DataScope == datastore.GlobalScope || network.scope == datastore.SwarmScope) &&
760
+		!c.isDistributedControl() && !network.dynamic {
755 761
 		if c.isManager() {
756 762
 			// For non-distributed controlled environment, globalscoped non-dynamic networks are redirected to Manager
757 763
 			return nil, ManagerRedirectError(name)
... ...
@@ -115,7 +115,10 @@ const (
115 115
 	// LocalScope indicates to store the KV object in local datastore such as boltdb
116 116
 	LocalScope = "local"
117 117
 	// GlobalScope indicates to store the KV object in global datastore such as consul/etcd/zookeeper
118
-	GlobalScope   = "global"
118
+	GlobalScope = "global"
119
+	// SwarmScope is not indicating a datastore location. It is defined here
120
+	// along with the other two scopes just for consistency.
121
+	SwarmScope    = "swarm"
119 122
 	defaultPrefix = "/var/lib/docker/network/files"
120 123
 )
121 124
 
... ...
@@ -56,10 +56,12 @@ Other entries in the list value are allowed; `"NetworkDriver"` indicates that th
56 56
 After Handshake, the remote driver will receive another POST message to the URL `/NetworkDriver.GetCapabilities` with no payload. The driver's response should have the form:
57 57
 
58 58
 	{
59
-		"Scope": "local"
59
+		"Scope":             "local"
60
+		"ConnectivityScope": "global"
60 61
 	}
61 62
 
62
-Value of "Scope" should be either "local" or "global" which indicates the capability of remote driver, values beyond these will fail driver's registration and return an error to the caller.
63
+Value of "Scope" should be either "local" or "global" which indicates whether the resource allocations for this driver's network can be done only locally to the node or globally across the cluster of nodes. Any other value will fail driver's registration and return an error to the caller.
64
+Similarly, value of "ConnectivityScope" should be either "local" or "global" which indicates whether the driver's network can provide connectivity only locally to this node or globally across the cluster of nodes. If the value is missing, libnetwork will set it to the value of "Scope". should be either "local" or "global" which indicates
63 65
 
64 66
 ### Create network
65 67
 
... ...
@@ -161,7 +161,8 @@ type DriverCallback interface {
161 161
 
162 162
 // Capability represents the high level capabilities of the drivers which libnetwork can make use of
163 163
 type Capability struct {
164
-	DataScope string
164
+	DataScope         string
165
+	ConnectivityScope string
165 166
 }
166 167
 
167 168
 // IPAMData represents the per-network ip related
... ...
@@ -153,7 +153,8 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
153 153
 	}
154 154
 
155 155
 	c := driverapi.Capability{
156
-		DataScope: datastore.LocalScope,
156
+		DataScope:         datastore.LocalScope,
157
+		ConnectivityScope: datastore.LocalScope,
157 158
 	}
158 159
 	return dc.RegisterDriver(networkType, d, c)
159 160
 }
... ...
@@ -19,7 +19,8 @@ type driver struct {
19 19
 // Init registers a new instance of host driver
20 20
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
21 21
 	c := driverapi.Capability{
22
-		DataScope: datastore.LocalScope,
22
+		DataScope:         datastore.LocalScope,
23
+		ConnectivityScope: datastore.LocalScope,
23 24
 	}
24 25
 	return dc.RegisterDriver(networkType, &driver{}, c)
25 26
 }
... ...
@@ -58,7 +58,8 @@ type network struct {
58 58
 // Init initializes and registers the libnetwork ipvlan driver
59 59
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
60 60
 	c := driverapi.Capability{
61
-		DataScope: datastore.LocalScope,
61
+		DataScope:         datastore.LocalScope,
62
+		ConnectivityScope: datastore.GlobalScope,
62 63
 	}
63 64
 	d := &driver{
64 65
 		networks: networkTable{},
... ...
@@ -60,7 +60,8 @@ type network struct {
60 60
 // Init initializes and registers the libnetwork macvlan driver
61 61
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
62 62
 	c := driverapi.Capability{
63
-		DataScope: datastore.LocalScope,
63
+		DataScope:         datastore.LocalScope,
64
+		ConnectivityScope: datastore.GlobalScope,
64 65
 	}
65 66
 	d := &driver{
66 67
 		networks: networkTable{},
... ...
@@ -56,7 +56,8 @@ type driver struct {
56 56
 // Init registers a new instance of overlay driver
57 57
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
58 58
 	c := driverapi.Capability{
59
-		DataScope: datastore.GlobalScope,
59
+		DataScope:         datastore.GlobalScope,
60
+		ConnectivityScope: datastore.GlobalScope,
60 61
 	}
61 62
 	d := &driver{
62 63
 		networks: networkTable{},
... ...
@@ -49,7 +49,8 @@ type network struct {
49 49
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
50 50
 	var err error
51 51
 	c := driverapi.Capability{
52
-		DataScope: datastore.GlobalScope,
52
+		DataScope:         datastore.GlobalScope,
53
+		ConnectivityScope: datastore.GlobalScope,
53 54
 	}
54 55
 
55 56
 	d := &driver{
... ...
@@ -24,7 +24,8 @@ func (r *Response) GetError() string {
24 24
 // GetCapabilityResponse is the response of GetCapability request
25 25
 type GetCapabilityResponse struct {
26 26
 	Response
27
-	Scope string
27
+	Scope             string
28
+	ConnectivityScope string
28 29
 }
29 30
 
30 31
 // AllocateNetworkRequest requests allocation of new network by manager
... ...
@@ -74,6 +74,17 @@ func (d *driver) getCapabilities() (*driverapi.Capability, error) {
74 74
 		return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
75 75
 	}
76 76
 
77
+	switch capResp.ConnectivityScope {
78
+	case "global":
79
+		c.ConnectivityScope = datastore.GlobalScope
80
+	case "local":
81
+		c.ConnectivityScope = datastore.LocalScope
82
+	case "":
83
+		c.ConnectivityScope = c.DataScope
84
+	default:
85
+		return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
86
+	}
87
+
77 88
 	return c, nil
78 89
 }
79 90
 
... ...
@@ -238,8 +238,9 @@ func TestGetExtraCapabilities(t *testing.T) {
238 238
 
239 239
 	handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
240 240
 		return map[string]interface{}{
241
-			"Scope": "local",
242
-			"foo":   "bar",
241
+			"Scope":             "local",
242
+			"foo":               "bar",
243
+			"ConnectivityScope": "global",
243 244
 		}
244 245
 	})
245 246
 
... ...
@@ -258,6 +259,8 @@ func TestGetExtraCapabilities(t *testing.T) {
258 258
 		t.Fatal(err)
259 259
 	} else if c.DataScope != datastore.LocalScope {
260 260
 		t.Fatalf("get capability '%s', expecting 'local'", c.DataScope)
261
+	} else if c.ConnectivityScope != datastore.GlobalScope {
262
+		t.Fatalf("get capability '%s', expecting %q", c.ConnectivityScope, datastore.GlobalScope)
261 263
 	}
262 264
 }
263 265
 
... ...
@@ -159,7 +159,8 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
159 159
 	}
160 160
 
161 161
 	c := driverapi.Capability{
162
-		DataScope: datastore.LocalScope,
162
+		DataScope:         datastore.LocalScope,
163
+		ConnectivityScope: datastore.LocalScope,
163 164
 	}
164 165
 	return dc.RegisterDriver(networkType, d, c)
165 166
 }
... ...
@@ -57,7 +57,8 @@ type driver struct {
57 57
 // Init registers a new instance of overlay driver
58 58
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
59 59
 	c := driverapi.Capability{
60
-		DataScope: datastore.GlobalScope,
60
+		DataScope:         datastore.GlobalScope,
61
+		ConnectivityScope: datastore.GlobalScope,
61 62
 	}
62 63
 	d := &driver{
63 64
 		networks: networkTable{},
... ...
@@ -49,7 +49,8 @@ type network struct {
49 49
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
50 50
 	var err error
51 51
 	c := driverapi.Capability{
52
-		DataScope: datastore.GlobalScope,
52
+		DataScope:         datastore.GlobalScope,
53
+		ConnectivityScope: datastore.GlobalScope,
53 54
 	}
54 55
 
55 56
 	d := &driver{
... ...
@@ -36,7 +36,8 @@ type driver struct {
36 36
 // Init registers a new instance of overlay driver
37 37
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
38 38
 	c := driverapi.Capability{
39
-		DataScope: datastore.GlobalScope,
39
+		DataScope:         datastore.GlobalScope,
40
+		ConnectivityScope: datastore.GlobalScope,
40 41
 	}
41 42
 
42 43
 	d := &driver{
... ...
@@ -104,7 +104,8 @@ func GetInit(networkType string) func(dc driverapi.DriverCallback, config map[st
104 104
 		}
105 105
 
106 106
 		return dc.RegisterDriver(networkType, newDriver(networkType), driverapi.Capability{
107
-			DataScope: datastore.LocalScope,
107
+			DataScope:         datastore.LocalScope,
108
+			ConnectivityScope: datastore.LocalScope,
108 109
 		})
109 110
 	}
110 111
 }
... ...
@@ -195,7 +195,7 @@ type network struct {
195 195
 	networkType  string
196 196
 	id           string
197 197
 	created      time.Time
198
-	scope        string
198
+	scope        string // network data scope
199 199
 	labels       map[string]string
200 200
 	ipamType     string
201 201
 	ipamOptions  map[string]string
... ...
@@ -514,7 +514,12 @@ func (n *network) CopyTo(o datastore.KVObject) error {
514 514
 }
515 515
 
516 516
 func (n *network) DataScope() string {
517
-	return n.Scope()
517
+	s := n.Scope()
518
+	// All swarm scope networks have local datascope
519
+	if s == datastore.SwarmScope {
520
+		s = datastore.LocalScope
521
+	}
522
+	return s
518 523
 }
519 524
 
520 525
 func (n *network) getEpCnt() *endpointCnt {
... ...
@@ -762,6 +767,14 @@ func NetworkOptionAttachable(attachable bool) NetworkOption {
762 762
 	}
763 763
 }
764 764
 
765
+// NetworkOptionScope returns an option setter to overwrite the network's scope.
766
+// By default the network's scope is set to the network driver's datascope.
767
+func NetworkOptionScope(scope string) NetworkOption {
768
+	return func(n *network) {
769
+		n.scope = scope
770
+	}
771
+}
772
+
765 773
 // NetworkOptionIpam function returns an option setter for the ipam configuration for this network
766 774
 func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption {
767 775
 	return func(n *network) {
... ...
@@ -877,6 +890,14 @@ func (n *network) driverScope() string {
877 877
 	return cap.DataScope
878 878
 }
879 879
 
880
+func (n *network) driverIsMultihost() bool {
881
+	_, cap, err := n.resolveDriver(n.networkType, true)
882
+	if err != nil {
883
+		return false
884
+	}
885
+	return cap.ConnectivityScope == datastore.GlobalScope
886
+}
887
+
880 888
 func (n *network) driver(load bool) (driverapi.Driver, error) {
881 889
 	d, cap, err := n.resolveDriver(n.networkType, load)
882 890
 	if err != nil {
... ...
@@ -887,14 +908,14 @@ func (n *network) driver(load bool) (driverapi.Driver, error) {
887 887
 	isAgent := c.isAgent()
888 888
 	n.Lock()
889 889
 	// If load is not required, driver, cap and err may all be nil
890
-	if cap != nil {
890
+	if n.scope == "" && cap != nil {
891 891
 		n.scope = cap.DataScope
892 892
 	}
893
-	if isAgent || n.dynamic {
894
-		// If we are running in agent mode then all networks
895
-		// in libnetwork are local scope regardless of the
896
-		// backing driver.
897
-		n.scope = datastore.LocalScope
893
+	if isAgent && n.dynamic {
894
+		// If we are running in agent mode and the network
895
+		// is dynamic, then the networks are swarm scoped
896
+		// regardless of the backing driver.
897
+		n.scope = datastore.SwarmScope
898 898
 	}
899 899
 	n.Unlock()
900 900
 	return d, nil
... ...
@@ -350,17 +350,18 @@ func (c *controller) networkWatchLoop(nw *netWatch, ep *endpoint, ecCh <-chan da
350 350
 }
351 351
 
352 352
 func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoint) {
353
-	if !c.isDistributedControl() && ep.getNetwork().driverScope() == datastore.GlobalScope {
353
+	n := ep.getNetwork()
354
+	if !c.isDistributedControl() && n.Scope() == datastore.SwarmScope && n.driverIsMultihost() {
354 355
 		return
355 356
 	}
356 357
 
357 358
 	c.Lock()
358
-	nw, ok := nmap[ep.getNetwork().ID()]
359
+	nw, ok := nmap[n.ID()]
359 360
 	c.Unlock()
360 361
 
361 362
 	if ok {
362 363
 		// Update the svc db for the local endpoint join right away
363
-		ep.getNetwork().updateSvcRecord(ep, c.getLocalEps(nw), true)
364
+		n.updateSvcRecord(ep, c.getLocalEps(nw), true)
364 365
 
365 366
 		c.Lock()
366 367
 		nw.localEps[ep.ID()] = ep
... ...
@@ -381,15 +382,15 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi
381 381
 	// Update the svc db for the local endpoint join right away
382 382
 	// Do this before adding this ep to localEps so that we don't
383 383
 	// try to update this ep's container's svc records
384
-	ep.getNetwork().updateSvcRecord(ep, c.getLocalEps(nw), true)
384
+	n.updateSvcRecord(ep, c.getLocalEps(nw), true)
385 385
 
386 386
 	c.Lock()
387 387
 	nw.localEps[ep.ID()] = ep
388
-	nmap[ep.getNetwork().ID()] = nw
388
+	nmap[n.ID()] = nw
389 389
 	nw.stopCh = make(chan struct{})
390 390
 	c.Unlock()
391 391
 
392
-	store := c.getStore(ep.getNetwork().DataScope())
392
+	store := c.getStore(n.DataScope())
393 393
 	if store == nil {
394 394
 		return
395 395
 	}
... ...
@@ -398,7 +399,7 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi
398 398
 		return
399 399
 	}
400 400
 
401
-	ch, err := store.Watch(ep.getNetwork().getEpCnt(), nw.stopCh)
401
+	ch, err := store.Watch(n.getEpCnt(), nw.stopCh)
402 402
 	if err != nil {
403 403
 		logrus.Warnf("Error creating watch for network: %v", err)
404 404
 		return
... ...
@@ -408,12 +409,13 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi
408 408
 }
409 409
 
410 410
 func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoint) {
411
-	if !c.isDistributedControl() && ep.getNetwork().driverScope() == datastore.GlobalScope {
411
+	n := ep.getNetwork()
412
+	if !c.isDistributedControl() && n.Scope() == datastore.SwarmScope && n.driverIsMultihost() {
412 413
 		return
413 414
 	}
414 415
 
415 416
 	c.Lock()
416
-	nw, ok := nmap[ep.getNetwork().ID()]
417
+	nw, ok := nmap[n.ID()]
417 418
 
418 419
 	if ok {
419 420
 		delete(nw.localEps, ep.ID())
... ...
@@ -422,7 +424,7 @@ func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoi
422 422
 		// Update the svc db about local endpoint leave right away
423 423
 		// Do this after we remove this ep from localEps so that we
424 424
 		// don't try to remove this svc record from this ep's container.
425
-		ep.getNetwork().updateSvcRecord(ep, c.getLocalEps(nw), false)
425
+		n.updateSvcRecord(ep, c.getLocalEps(nw), false)
426 426
 
427 427
 		c.Lock()
428 428
 		if len(nw.localEps) == 0 {
... ...
@@ -430,9 +432,9 @@ func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoi
430 430
 
431 431
 			// This is the last container going away for the network. Destroy
432 432
 			// this network's svc db entry
433
-			delete(c.svcRecords, ep.getNetwork().ID())
433
+			delete(c.svcRecords, n.ID())
434 434
 
435
-			delete(nmap, ep.getNetwork().ID())
435
+			delete(nmap, n.ID())
436 436
 		}
437 437
 	}
438 438
 	c.Unlock()