浏览代码

Merge pull request #1742 from aboch/nlo2

ConnectivityScope capability and configuration networks
Madhu Venugopal 8 年之前
父节点
当前提交
f32eec401b

+ 2 - 2
libnetwork/agent.go

@@ -222,7 +222,7 @@ func (c *controller) agentSetup(clusterProvider cluster.Provider) error {
 			return err
 		}
 		c.drvRegistry.WalkDrivers(func(name string, driver driverapi.Driver, capability driverapi.Capability) bool {
-			if capability.DataScope == datastore.GlobalScope {
+			if capability.ConnectivityScope == datastore.GlobalScope {
 				c.agentDriverNotify(driver)
 			}
 			return false
@@ -507,7 +507,7 @@ func (n *network) Services() map[string]ServiceInfo {
 }
 
 func (n *network) isClusterEligible() bool {
-	if n.driverScope() != datastore.GlobalScope {
+	if n.scope != datastore.SwarmScope || !n.driverIsMultihost() {
 		return false
 	}
 	return n.getController().getAgent() != nil

+ 58 - 4
libnetwork/controller.go

@@ -621,7 +621,7 @@ func (c *controller) pushNodeDiscovery(d driverapi.Driver, cap driverapi.Capabil
 		}
 	}
 
-	if d == nil || cap.DataScope != datastore.GlobalScope || nodes == nil {
+	if d == nil || cap.ConnectivityScope != datastore.GlobalScope || nodes == nil {
 		return
 	}
 
@@ -722,22 +722,46 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
 	}
 
 	network.processOptions(options...)
+	if err := network.validateConfiguration(); err != nil {
+		return nil, err
+	}
+
+	var (
+		cap *driverapi.Capability
+		err error
+	)
+
+	// Reset network types, force local scope and skip allocation and
+	// plumbing for configuration networks. Reset of the config-only
+	// network drivers is needed so that this special network is not
+	// usable by old engine versions.
+	if network.configOnly {
+		network.scope = datastore.LocalScope
+		network.networkType = "null"
+		network.ipamType = ""
+		goto addToStore
+	}
 
-	_, cap, err := network.resolveDriver(networkType, true)
+	_, cap, err = network.resolveDriver(network.networkType, true)
 	if err != nil {
 		return nil, err
 	}
 
+	if network.scope == datastore.LocalScope && cap.DataScope == datastore.GlobalScope {
+		return nil, types.ForbiddenErrorf("cannot downgrade network scope for %s networks", networkType)
+
+	}
 	if network.ingress && cap.DataScope != datastore.GlobalScope {
 		return nil, types.ForbiddenErrorf("Ingress network can only be global scope network")
 	}
 
-	if cap.DataScope == datastore.GlobalScope && !c.isDistributedControl() && !network.dynamic {
+	// At this point the network scope is still unknown if not set by user
+	if (cap.DataScope == datastore.GlobalScope || network.scope == datastore.SwarmScope) &&
+		!c.isDistributedControl() && !network.dynamic {
 		if c.isManager() {
 			// For non-distributed controlled environment, globalscoped non-dynamic networks are redirected to Manager
 			return nil, ManagerRedirectError(name)
 		}
-
 		return nil, types.ForbiddenErrorf("Cannot create a multi-host network from a worker node. Please create the network from a manager node.")
 	}
 
@@ -747,6 +771,26 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
 		return nil, err
 	}
 
+	// From this point on, we need the network specific configuration,
+	// which may come from a configuration-only network
+	if network.configFrom != "" {
+		t, err := c.getConfigNetwork(network.configFrom)
+		if err != nil {
+			return nil, types.NotFoundErrorf("configuration network %q does not exist", network.configFrom)
+		}
+		if err := t.applyConfigurationTo(network); err != nil {
+			return nil, types.InternalErrorf("Failed to apply configuration: %v", err)
+		}
+		defer func() {
+			if err == nil {
+				if err := t.getEpCnt().IncEndpointCnt(); err != nil {
+					logrus.Warnf("Failed to update reference count for configuration network %q on creation of network %q: %v",
+						t.Name(), network.Name(), err)
+				}
+			}
+		}()
+	}
+
 	err = network.ipamAllocate()
 	if err != nil {
 		return nil, err
@@ -769,6 +813,7 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
 		}
 	}()
 
+addToStore:
 	// First store the endpoint count, then the network. To avoid to
 	// end up with a datastore containing a network and not an epCnt,
 	// in case of an ungraceful shutdown during this function call.
@@ -788,6 +833,9 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
 	if err = c.updateToStore(network); err != nil {
 		return nil, err
 	}
+	if network.configOnly {
+		return network, nil
+	}
 
 	joinCluster(network)
 	if !c.isDistributedControl() {
@@ -801,6 +849,9 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
 
 var joinCluster NetworkWalker = func(nw Network) bool {
 	n := nw.(*network)
+	if n.configOnly {
+		return false
+	}
 	if err := n.joinCluster(); err != nil {
 		logrus.Errorf("Failed to join network %s (%s) into agent cluster: %v", n.Name(), n.ID(), err)
 	}
@@ -816,6 +867,9 @@ func (c *controller) reservePools() {
 	}
 
 	for _, n := range networks {
+		if n.configOnly {
+			continue
+		}
 		if !doReplayPoolReserve(n) {
 			continue
 		}

+ 4 - 1
libnetwork/datastore/datastore.go

@@ -115,7 +115,10 @@ const (
 	// LocalScope indicates to store the KV object in local datastore such as boltdb
 	LocalScope = "local"
 	// GlobalScope indicates to store the KV object in global datastore such as consul/etcd/zookeeper
-	GlobalScope   = "global"
+	GlobalScope = "global"
+	// SwarmScope is not indicating a datastore location. It is defined here
+	// along with the other two scopes just for consistency.
+	SwarmScope    = "swarm"
 	defaultPrefix = "/var/lib/docker/network/files"
 )
 

+ 4 - 2
libnetwork/docs/remote.md

@@ -56,10 +56,12 @@ Other entries in the list value are allowed; `"NetworkDriver"` indicates that th
 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:
 
 	{
-		"Scope": "local"
+		"Scope":             "local"
+		"ConnectivityScope": "global"
 	}
 
-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.
+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.
+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
 
 ### Create network
 

+ 2 - 1
libnetwork/driverapi/driverapi.go

@@ -161,7 +161,8 @@ type DriverCallback interface {
 
 // Capability represents the high level capabilities of the drivers which libnetwork can make use of
 type Capability struct {
-	DataScope string
+	DataScope         string
+	ConnectivityScope string
 }
 
 // IPAMData represents the per-network ip related

+ 2 - 1
libnetwork/drivers/bridge/bridge.go

@@ -153,7 +153,8 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	}
 
 	c := driverapi.Capability{
-		DataScope: datastore.LocalScope,
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.LocalScope,
 	}
 	return dc.RegisterDriver(networkType, d, c)
 }

+ 88 - 0
libnetwork/drivers/bridge/brmanager/brmanager.go

@@ -0,0 +1,88 @@
+package brmanager
+
+import (
+	"github.com/docker/libnetwork/datastore"
+	"github.com/docker/libnetwork/discoverapi"
+	"github.com/docker/libnetwork/driverapi"
+	"github.com/docker/libnetwork/types"
+)
+
+const networkType = "bridge"
+
+type driver struct{}
+
+// Init registers a new instance of bridge manager driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+	c := driverapi.Capability{
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.LocalScope,
+	}
+	return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+	return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Leave(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+	return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+	return true
+}
+
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}

+ 2 - 1
libnetwork/drivers/host/host.go

@@ -19,7 +19,8 @@ type driver struct {
 // Init registers a new instance of host driver
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	c := driverapi.Capability{
-		DataScope: datastore.LocalScope,
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.LocalScope,
 	}
 	return dc.RegisterDriver(networkType, &driver{}, c)
 }

+ 2 - 1
libnetwork/drivers/ipvlan/ipvlan.go

@@ -58,7 +58,8 @@ type network struct {
 // Init initializes and registers the libnetwork ipvlan driver
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	c := driverapi.Capability{
-		DataScope: datastore.LocalScope,
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 	d := &driver{
 		networks: networkTable{},

+ 88 - 0
libnetwork/drivers/ipvlan/ivmanager/ivmanager.go

@@ -0,0 +1,88 @@
+package ivmanager
+
+import (
+	"github.com/docker/libnetwork/datastore"
+	"github.com/docker/libnetwork/discoverapi"
+	"github.com/docker/libnetwork/driverapi"
+	"github.com/docker/libnetwork/types"
+)
+
+const networkType = "ipvlan"
+
+type driver struct{}
+
+// Init registers a new instance of ipvlan manager driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+	c := driverapi.Capability{
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.GlobalScope,
+	}
+	return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+	return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Leave(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+	return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+	return true
+}
+
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}

+ 2 - 1
libnetwork/drivers/macvlan/macvlan.go

@@ -60,7 +60,8 @@ type network struct {
 // Init initializes and registers the libnetwork macvlan driver
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	c := driverapi.Capability{
-		DataScope: datastore.LocalScope,
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 	d := &driver{
 		networks: networkTable{},

+ 88 - 0
libnetwork/drivers/macvlan/mvmanager/mvmanager.go

@@ -0,0 +1,88 @@
+package mvmanager
+
+import (
+	"github.com/docker/libnetwork/datastore"
+	"github.com/docker/libnetwork/discoverapi"
+	"github.com/docker/libnetwork/driverapi"
+	"github.com/docker/libnetwork/types"
+)
+
+const networkType = "macvlan"
+
+type driver struct{}
+
+// Init registers a new instance of macvlan manager driver
+func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
+	c := driverapi.Capability{
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.GlobalScope,
+	}
+	return dc.RegisterDriver(networkType, &driver{}, c)
+}
+
+func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) NetworkFree(id string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
+}
+
+func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
+	return "", nil
+}
+
+func (d *driver) DeleteNetwork(nid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) CreateEndpoint(nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DeleteEndpoint(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
+	return nil, types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Leave(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) Type() string {
+	return networkType
+}
+
+func (d *driver) IsBuiltIn() bool {
+	return true
+}
+
+func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string]interface{}) error {
+	return types.NotImplementedErrorf("not implemented")
+}
+
+func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
+	return types.NotImplementedErrorf("not implemented")
+}

+ 2 - 1
libnetwork/drivers/overlay/overlay.go

@@ -56,7 +56,8 @@ type driver struct {
 // Init registers a new instance of overlay driver
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	c := driverapi.Capability{
-		DataScope: datastore.GlobalScope,
+		DataScope:         datastore.GlobalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 	d := &driver{
 		networks: networkTable{},

+ 2 - 1
libnetwork/drivers/overlay/ovmanager/ovmanager.go

@@ -49,7 +49,8 @@ type network struct {
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	var err error
 	c := driverapi.Capability{
-		DataScope: datastore.GlobalScope,
+		DataScope:         datastore.GlobalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 
 	d := &driver{

+ 2 - 1
libnetwork/drivers/remote/api/api.go

@@ -24,7 +24,8 @@ func (r *Response) GetError() string {
 // GetCapabilityResponse is the response of GetCapability request
 type GetCapabilityResponse struct {
 	Response
-	Scope string
+	Scope             string
+	ConnectivityScope string
 }
 
 // AllocateNetworkRequest requests allocation of new network by manager

+ 11 - 0
libnetwork/drivers/remote/driver.go

@@ -74,6 +74,17 @@ func (d *driver) getCapabilities() (*driverapi.Capability, error) {
 		return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
 	}
 
+	switch capResp.ConnectivityScope {
+	case "global":
+		c.ConnectivityScope = datastore.GlobalScope
+	case "local":
+		c.ConnectivityScope = datastore.LocalScope
+	case "":
+		c.ConnectivityScope = c.DataScope
+	default:
+		return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope)
+	}
+
 	return c, nil
 }
 

+ 5 - 2
libnetwork/drivers/remote/driver_test.go

@@ -238,8 +238,9 @@ func TestGetExtraCapabilities(t *testing.T) {
 
 	handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
 		return map[string]interface{}{
-			"Scope": "local",
-			"foo":   "bar",
+			"Scope":             "local",
+			"foo":               "bar",
+			"ConnectivityScope": "global",
 		}
 	})
 
@@ -258,6 +259,8 @@ func TestGetExtraCapabilities(t *testing.T) {
 		t.Fatal(err)
 	} else if c.DataScope != datastore.LocalScope {
 		t.Fatalf("get capability '%s', expecting 'local'", c.DataScope)
+	} else if c.ConnectivityScope != datastore.GlobalScope {
+		t.Fatalf("get capability '%s', expecting %q", c.ConnectivityScope, datastore.GlobalScope)
 	}
 }
 

+ 2 - 1
libnetwork/drivers/solaris/bridge/bridge.go

@@ -159,7 +159,8 @@ func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	}
 
 	c := driverapi.Capability{
-		DataScope: datastore.LocalScope,
+		DataScope:         datastore.LocalScope,
+		ConnectivityScope: datastore.LocalScope,
 	}
 	return dc.RegisterDriver(networkType, d, c)
 }

+ 2 - 1
libnetwork/drivers/solaris/overlay/overlay.go

@@ -57,7 +57,8 @@ type driver struct {
 // Init registers a new instance of overlay driver
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	c := driverapi.Capability{
-		DataScope: datastore.GlobalScope,
+		DataScope:         datastore.GlobalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 	d := &driver{
 		networks: networkTable{},

+ 2 - 1
libnetwork/drivers/solaris/overlay/ovmanager/ovmanager.go

@@ -49,7 +49,8 @@ type network struct {
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	var err error
 	c := driverapi.Capability{
-		DataScope: datastore.GlobalScope,
+		DataScope:         datastore.GlobalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 
 	d := &driver{

+ 2 - 1
libnetwork/drivers/windows/overlay/overlay_windows.go

@@ -36,7 +36,8 @@ type driver struct {
 // Init registers a new instance of overlay driver
 func Init(dc driverapi.DriverCallback, config map[string]interface{}) error {
 	c := driverapi.Capability{
-		DataScope: datastore.GlobalScope,
+		DataScope:         datastore.GlobalScope,
+		ConnectivityScope: datastore.GlobalScope,
 	}
 
 	d := &driver{

+ 2 - 1
libnetwork/drivers/windows/windows.go

@@ -104,7 +104,8 @@ func GetInit(networkType string) func(dc driverapi.DriverCallback, config map[st
 		}
 
 		return dc.RegisterDriver(networkType, newDriver(networkType), driverapi.Capability{
-			DataScope: datastore.LocalScope,
+			DataScope:         datastore.LocalScope,
+			ConnectivityScope: datastore.LocalScope,
 		})
 	}
 }

+ 3 - 0
libnetwork/endpoint.go

@@ -1152,6 +1152,9 @@ func (c *controller) cleanupLocalEndpoints() {
 	}
 
 	for _, n := range nl {
+		if n.ConfigOnly() {
+			continue
+		}
 		epl, err := n.getEndpointsFromStore()
 		if err != nil {
 			logrus.Warnf("Could not get list of endpoints in network %s during endpoint cleanup: %v", n.name, err)

+ 4 - 1
libnetwork/libnetwork_internal_test.go

@@ -25,6 +25,8 @@ func TestNetworkMarshalling(t *testing.T) {
 		networkType: "bridge",
 		enableIPv6:  true,
 		persist:     true,
+		configOnly:  true,
+		configFrom:  "configOnlyX",
 		ipamOptions: map[string]string{
 			netlabel.MacAddress: "a:b:c:d:e:f",
 			"primary":           "",
@@ -136,7 +138,8 @@ func TestNetworkMarshalling(t *testing.T) {
 		!compareIpamInfoList(n.ipamV6Info, nn.ipamV6Info) ||
 		!compareStringMaps(n.ipamOptions, nn.ipamOptions) ||
 		!compareStringMaps(n.labels, nn.labels) ||
-		!n.created.Equal(nn.created) {
+		!n.created.Equal(nn.created) ||
+		n.configOnly != nn.configOnly || n.configFrom != nn.configFrom {
 		t.Fatalf("JSON marsh/unmarsh failed."+
 			"\nOriginal:\n%#v\nDecoded:\n%#v"+
 			"\nOriginal ipamV4Conf: %#v\n\nDecoded ipamV4Conf: %#v"+

+ 103 - 0
libnetwork/libnetwork_test.go

@@ -359,6 +359,109 @@ func TestDeleteNetworkWithActiveEndpoints(t *testing.T) {
 	}
 }
 
+func TestNetworkConfig(t *testing.T) {
+	if !testutils.IsRunningInContainer() {
+		defer testutils.SetupTestOSContext(t)()
+	}
+
+	// Verify config network cannot inherit another config network
+	configNetwork, err := controller.NewNetwork("bridge", "config_network0", "",
+		libnetwork.NetworkOptionConfigOnly(),
+		libnetwork.NetworkOptionConfigFrom("anotherConfigNw"))
+
+	if err == nil {
+		t.Fatal("Expected to fail. But instead succeeded")
+	}
+	if _, ok := err.(types.ForbiddenError); !ok {
+		t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+	}
+
+	// Create supported config network
+	netOption := options.Generic{
+		"EnableICC": false,
+	}
+	option := options.Generic{
+		netlabel.GenericData: netOption,
+	}
+	ipamV4ConfList := []*libnetwork.IpamConf{{PreferredPool: "192.168.100.0/24", SubPool: "192.168.100.128/25", Gateway: "192.168.100.1"}}
+	ipamV6ConfList := []*libnetwork.IpamConf{{PreferredPool: "2001:db8:abcd::/64", SubPool: "2001:db8:abcd::ef99/80", Gateway: "2001:db8:abcd::22"}}
+
+	netOptions := []libnetwork.NetworkOption{
+		libnetwork.NetworkOptionConfigOnly(),
+		libnetwork.NetworkOptionEnableIPv6(true),
+		libnetwork.NetworkOptionGeneric(option),
+		libnetwork.NetworkOptionIpam("default", "", ipamV4ConfList, ipamV6ConfList, nil),
+	}
+
+	configNetwork, err = controller.NewNetwork(bridgeNetType, "config_network0", "", netOptions...)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Verify a config-only network cannot be created with network operator configurations
+	for i, opt := range []libnetwork.NetworkOption{
+		libnetwork.NetworkOptionInternalNetwork(),
+		libnetwork.NetworkOptionAttachable(true),
+		libnetwork.NetworkOptionIngress(true),
+	} {
+		_, err = controller.NewNetwork(bridgeNetType, "testBR", "",
+			libnetwork.NetworkOptionConfigOnly(), opt)
+		if err == nil {
+			t.Fatalf("Expected to fail. But instead succeeded for option: %d", i)
+		}
+		if _, ok := err.(types.ForbiddenError); !ok {
+			t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+		}
+	}
+
+	// Verify a network cannot be created with both config-from and network specific configurations
+	for i, opt := range []libnetwork.NetworkOption{
+		libnetwork.NetworkOptionEnableIPv6(true),
+		libnetwork.NetworkOptionIpam("my-ipam", "", nil, nil, nil),
+		libnetwork.NetworkOptionIpam("", "", ipamV4ConfList, nil, nil),
+		libnetwork.NetworkOptionIpam("", "", nil, ipamV6ConfList, nil),
+		libnetwork.NetworkOptionLabels(map[string]string{"number": "two"}),
+		libnetwork.NetworkOptionDriverOpts(map[string]string{"com.docker.network.mtu": "1600"}),
+	} {
+		_, err = controller.NewNetwork(bridgeNetType, "testBR", "",
+			libnetwork.NetworkOptionConfigFrom("config_network0"), opt)
+		if err == nil {
+			t.Fatalf("Expected to fail. But instead succeeded for option: %d", i)
+		}
+		if _, ok := err.(types.ForbiddenError); !ok {
+			t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+		}
+	}
+
+	// Create a valid network
+	network, err := controller.NewNetwork(bridgeNetType, "testBR", "",
+		libnetwork.NetworkOptionConfigFrom("config_network0"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Verify the config network cannot be removed
+	err = configNetwork.Delete()
+	if err == nil {
+		t.Fatal("Expected to fail. But instead succeeded")
+	}
+
+	if _, ok := err.(types.ForbiddenError); !ok {
+		t.Fatalf("Did not fail with expected error. Actual error: %v", err)
+	}
+
+	// Delete network
+	if err := network.Delete(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Verify the config network can now be removed
+	if err := configNetwork.Delete(); err != nil {
+		t.Fatal(err)
+	}
+
+}
+
 func TestUnknownNetwork(t *testing.T) {
 	if !testutils.IsRunningInContainer() {
 		defer testutils.SetupTestOSContext(t)()

+ 207 - 8
libnetwork/network.go

@@ -67,6 +67,8 @@ type NetworkInfo interface {
 	Internal() bool
 	Attachable() bool
 	Ingress() bool
+	ConfigFrom() string
+	ConfigOnly() bool
 	Labels() map[string]string
 	Dynamic() bool
 	Created() time.Time
@@ -193,7 +195,7 @@ type network struct {
 	networkType  string
 	id           string
 	created      time.Time
-	scope        string
+	scope        string // network data scope
 	labels       map[string]string
 	ipamType     string
 	ipamOptions  map[string]string
@@ -219,6 +221,8 @@ type network struct {
 	ingress      bool
 	driverTables []networkDBTable
 	dynamic      bool
+	configOnly   bool
+	configFrom   string
 	sync.Mutex
 }
 
@@ -348,6 +352,95 @@ func (i *IpamInfo) CopyTo(dstI *IpamInfo) error {
 	return nil
 }
 
+func (n *network) validateConfiguration() error {
+	if n.configOnly {
+		// Only supports network specific configurations.
+		// Network operator configurations are not supported.
+		if n.ingress || n.internal || n.attachable {
+			return types.ForbiddenErrorf("configuration network can only contain network " +
+				"specific fields. Network operator fields like " +
+				"[ ingress | internal | attachable ] are not supported.")
+		}
+	}
+	if n.configFrom != "" {
+		if n.configOnly {
+			return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network")
+		}
+		if n.ipamType != "" &&
+			n.ipamType != defaultIpamForNetworkType(n.networkType) ||
+			n.enableIPv6 ||
+			len(n.labels) > 0 || len(n.ipamOptions) > 0 ||
+			len(n.ipamV4Config) > 0 || len(n.ipamV6Config) > 0 {
+			return types.ForbiddenErrorf("user specified configurations are not supported if the network depends on a configuration network")
+		}
+		if len(n.generic) > 0 {
+			if data, ok := n.generic[netlabel.GenericData]; ok {
+				var (
+					driverOptions map[string]string
+					opts          interface{}
+				)
+				switch data.(type) {
+				case map[string]interface{}:
+					opts = data.(map[string]interface{})
+				case map[string]string:
+					opts = data.(map[string]string)
+				}
+				ba, err := json.Marshal(opts)
+				if err != nil {
+					return fmt.Errorf("failed to validate network configuration: %v", err)
+				}
+				if err := json.Unmarshal(ba, &driverOptions); err != nil {
+					return fmt.Errorf("failed to validate network configuration: %v", err)
+				}
+				if len(driverOptions) > 0 {
+					return types.ForbiddenErrorf("network driver options are not supported if the network depends on a configuration network")
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// Applies network specific configurations
+func (n *network) applyConfigurationTo(to *network) error {
+	to.enableIPv6 = n.enableIPv6
+	if len(n.labels) > 0 {
+		to.labels = make(map[string]string, len(n.labels))
+		for k, v := range n.labels {
+			if _, ok := to.labels[k]; !ok {
+				to.labels[k] = v
+			}
+		}
+	}
+	if len(n.ipamOptions) > 0 {
+		to.ipamOptions = make(map[string]string, len(n.ipamOptions))
+		for k, v := range n.ipamOptions {
+			if _, ok := to.ipamOptions[k]; !ok {
+				to.ipamOptions[k] = v
+			}
+		}
+	}
+	if len(n.ipamV4Config) > 0 {
+		to.ipamV4Config = make([]*IpamConf, 0, len(n.ipamV4Config))
+		for _, v4conf := range n.ipamV4Config {
+			to.ipamV4Config = append(to.ipamV4Config, v4conf)
+		}
+	}
+	if len(n.ipamV6Config) > 0 {
+		to.ipamV6Config = make([]*IpamConf, 0, len(n.ipamV6Config))
+		for _, v6conf := range n.ipamV6Config {
+			to.ipamV6Config = append(to.ipamV6Config, v6conf)
+		}
+	}
+	if len(n.generic) > 0 {
+		to.generic = options.Generic{}
+		for k, v := range n.generic {
+			to.generic[k] = v
+		}
+	}
+	return nil
+}
+
 func (n *network) CopyTo(o datastore.KVObject) error {
 	n.Lock()
 	defer n.Unlock()
@@ -370,6 +463,8 @@ func (n *network) CopyTo(o datastore.KVObject) error {
 	dstN.attachable = n.attachable
 	dstN.inDelete = n.inDelete
 	dstN.ingress = n.ingress
+	dstN.configOnly = n.configOnly
+	dstN.configFrom = n.configFrom
 
 	// copy labels
 	if dstN.labels == nil {
@@ -419,7 +514,12 @@ func (n *network) CopyTo(o datastore.KVObject) error {
 }
 
 func (n *network) DataScope() string {
-	return n.Scope()
+	s := n.Scope()
+	// All swarm scope networks have local datascope
+	if s == datastore.SwarmScope {
+		s = datastore.LocalScope
+	}
+	return s
 }
 
 func (n *network) getEpCnt() *endpointCnt {
@@ -479,6 +579,8 @@ func (n *network) MarshalJSON() ([]byte, error) {
 	netMap["attachable"] = n.attachable
 	netMap["inDelete"] = n.inDelete
 	netMap["ingress"] = n.ingress
+	netMap["configOnly"] = n.configOnly
+	netMap["configFrom"] = n.configFrom
 	return json.Marshal(netMap)
 }
 
@@ -583,6 +685,12 @@ func (n *network) UnmarshalJSON(b []byte) (err error) {
 	if v, ok := netMap["ingress"]; ok {
 		n.ingress = v.(bool)
 	}
+	if v, ok := netMap["configOnly"]; ok {
+		n.configOnly = v.(bool)
+	}
+	if v, ok := netMap["configFrom"]; ok {
+		n.configFrom = v.(string)
+	}
 	// Reconcile old networks with the recently added `--ipv6` flag
 	if !n.enableIPv6 {
 		n.enableIPv6 = len(n.ipamV6Info) > 0
@@ -659,6 +767,14 @@ func NetworkOptionAttachable(attachable bool) NetworkOption {
 	}
 }
 
+// NetworkOptionScope returns an option setter to overwrite the network's scope.
+// By default the network's scope is set to the network driver's datascope.
+func NetworkOptionScope(scope string) NetworkOption {
+	return func(n *network) {
+		n.scope = scope
+	}
+}
+
 // NetworkOptionIpam function returns an option setter for the ipam configuration for this network
 func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption {
 	return func(n *network) {
@@ -713,6 +829,23 @@ func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption {
 	}
 }
 
+// NetworkOptionConfigOnly tells controller this network is
+// a configuration only network. It serves as a configuration
+// for other networks.
+func NetworkOptionConfigOnly() NetworkOption {
+	return func(n *network) {
+		n.configOnly = true
+	}
+}
+
+// NetworkOptionConfigFrom tells controller to pick the
+// network configuration from a configuration only network
+func NetworkOptionConfigFrom(name string) NetworkOption {
+	return func(n *network) {
+		n.configFrom = name
+	}
+}
+
 func (n *network) processOptions(options ...NetworkOption) {
 	for _, opt := range options {
 		if opt != nil {
@@ -757,6 +890,14 @@ func (n *network) driverScope() string {
 	return cap.DataScope
 }
 
+func (n *network) driverIsMultihost() bool {
+	_, cap, err := n.resolveDriver(n.networkType, true)
+	if err != nil {
+		return false
+	}
+	return cap.ConnectivityScope == datastore.GlobalScope
+}
+
 func (n *network) driver(load bool) (driverapi.Driver, error) {
 	d, cap, err := n.resolveDriver(n.networkType, load)
 	if err != nil {
@@ -767,14 +908,14 @@ func (n *network) driver(load bool) (driverapi.Driver, error) {
 	isAgent := c.isAgent()
 	n.Lock()
 	// If load is not required, driver, cap and err may all be nil
-	if cap != nil {
+	if n.scope == "" && cap != nil {
 		n.scope = cap.DataScope
 	}
-	if isAgent || n.dynamic {
-		// If we are running in agent mode then all networks
-		// in libnetwork are local scope regardless of the
-		// backing driver.
-		n.scope = datastore.LocalScope
+	if isAgent && n.dynamic {
+		// If we are running in agent mode and the network
+		// is dynamic, then the networks are swarm scoped
+		// regardless of the backing driver.
+		n.scope = datastore.SwarmScope
 	}
 	n.Unlock()
 	return d, nil
@@ -797,6 +938,9 @@ func (n *network) delete(force bool) error {
 	}
 
 	if !force && n.getEpCnt().EndpointCnt() != 0 {
+		if n.configOnly {
+			return types.ForbiddenErrorf("configuration network %q is in use", n.Name())
+		}
 		return &ActiveEndpointsError{name: n.name, id: n.id}
 	}
 
@@ -806,6 +950,21 @@ func (n *network) delete(force bool) error {
 		return fmt.Errorf("error marking network %s (%s) for deletion: %v", n.Name(), n.ID(), err)
 	}
 
+	if n.ConfigFrom() != "" {
+		if t, err := c.getConfigNetwork(n.ConfigFrom()); err == nil {
+			if err := t.getEpCnt().DecEndpointCnt(); err != nil {
+				logrus.Warnf("Failed to update reference count for configuration network %q on removal of network %q: %v",
+					t.Name(), n.Name(), err)
+			}
+		} else {
+			logrus.Warnf("Could not find configuration network %q during removal of network %q", n.configOnly, n.Name())
+		}
+	}
+
+	if n.configOnly {
+		goto removeFromStore
+	}
+
 	if err = n.deleteNetwork(); err != nil {
 		if !force {
 			return err
@@ -831,6 +990,7 @@ func (n *network) delete(force bool) error {
 
 	c.cleanupServiceBindings(n.ID())
 
+removeFromStore:
 	// deleteFromStore performs an atomic delete operation and the
 	// network.epCnt will help prevent any possible
 	// race between endpoint join and network delete
@@ -892,6 +1052,10 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
 		return nil, ErrInvalidName(name)
 	}
 
+	if n.ConfigOnly() {
+		return nil, types.ForbiddenErrorf("cannot create endpoint on configuration-only network")
+	}
+
 	if _, err = n.EndpointByName(name); err == nil {
 		return nil, types.ForbiddenErrorf("endpoint with name %s already exists in network %s", name, n.Name())
 	}
@@ -1611,6 +1775,20 @@ func (n *network) IPv6Enabled() bool {
 	return n.enableIPv6
 }
 
+func (n *network) ConfigFrom() string {
+	n.Lock()
+	defer n.Unlock()
+
+	return n.configFrom
+}
+
+func (n *network) ConfigOnly() bool {
+	n.Lock()
+	defer n.Unlock()
+
+	return n.configOnly
+}
+
 func (n *network) Labels() map[string]string {
 	n.Lock()
 	defer n.Unlock()
@@ -1778,3 +1956,24 @@ func (n *network) ExecFunc(f func()) error {
 func (n *network) NdotsSet() bool {
 	return false
 }
+
+// config-only network is looked up by name
+func (c *controller) getConfigNetwork(name string) (*network, error) {
+	var n Network
+
+	s := func(current Network) bool {
+		if current.Info().ConfigOnly() && current.Name() == name {
+			n = current
+			return true
+		}
+		return false
+	}
+
+	c.WalkNetworks(s)
+
+	if n == nil {
+		return nil, types.NotFoundErrorf("configuration network %q not found", name)
+	}
+
+	return n.(*network), nil
+}

+ 15 - 13
libnetwork/store.go

@@ -350,17 +350,18 @@ func (c *controller) networkWatchLoop(nw *netWatch, ep *endpoint, ecCh <-chan da
 }
 
 func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoint) {
-	if !c.isDistributedControl() && ep.getNetwork().driverScope() == datastore.GlobalScope {
+	n := ep.getNetwork()
+	if !c.isDistributedControl() && n.Scope() == datastore.SwarmScope && n.driverIsMultihost() {
 		return
 	}
 
 	c.Lock()
-	nw, ok := nmap[ep.getNetwork().ID()]
+	nw, ok := nmap[n.ID()]
 	c.Unlock()
 
 	if ok {
 		// Update the svc db for the local endpoint join right away
-		ep.getNetwork().updateSvcRecord(ep, c.getLocalEps(nw), true)
+		n.updateSvcRecord(ep, c.getLocalEps(nw), true)
 
 		c.Lock()
 		nw.localEps[ep.ID()] = ep
@@ -381,15 +382,15 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi
 	// Update the svc db for the local endpoint join right away
 	// Do this before adding this ep to localEps so that we don't
 	// try to update this ep's container's svc records
-	ep.getNetwork().updateSvcRecord(ep, c.getLocalEps(nw), true)
+	n.updateSvcRecord(ep, c.getLocalEps(nw), true)
 
 	c.Lock()
 	nw.localEps[ep.ID()] = ep
-	nmap[ep.getNetwork().ID()] = nw
+	nmap[n.ID()] = nw
 	nw.stopCh = make(chan struct{})
 	c.Unlock()
 
-	store := c.getStore(ep.getNetwork().DataScope())
+	store := c.getStore(n.DataScope())
 	if store == nil {
 		return
 	}
@@ -398,7 +399,7 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi
 		return
 	}
 
-	ch, err := store.Watch(ep.getNetwork().getEpCnt(), nw.stopCh)
+	ch, err := store.Watch(n.getEpCnt(), nw.stopCh)
 	if err != nil {
 		logrus.Warnf("Error creating watch for network: %v", err)
 		return
@@ -408,12 +409,13 @@ func (c *controller) processEndpointCreate(nmap map[string]*netWatch, ep *endpoi
 }
 
 func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoint) {
-	if !c.isDistributedControl() && ep.getNetwork().driverScope() == datastore.GlobalScope {
+	n := ep.getNetwork()
+	if !c.isDistributedControl() && n.Scope() == datastore.SwarmScope && n.driverIsMultihost() {
 		return
 	}
 
 	c.Lock()
-	nw, ok := nmap[ep.getNetwork().ID()]
+	nw, ok := nmap[n.ID()]
 
 	if ok {
 		delete(nw.localEps, ep.ID())
@@ -422,7 +424,7 @@ func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoi
 		// Update the svc db about local endpoint leave right away
 		// Do this after we remove this ep from localEps so that we
 		// don't try to remove this svc record from this ep's container.
-		ep.getNetwork().updateSvcRecord(ep, c.getLocalEps(nw), false)
+		n.updateSvcRecord(ep, c.getLocalEps(nw), false)
 
 		c.Lock()
 		if len(nw.localEps) == 0 {
@@ -430,9 +432,9 @@ func (c *controller) processEndpointDelete(nmap map[string]*netWatch, ep *endpoi
 
 			// This is the last container going away for the network. Destroy
 			// this network's svc db entry
-			delete(c.svcRecords, ep.getNetwork().ID())
+			delete(c.svcRecords, n.ID())
 
-			delete(nmap, ep.getNetwork().ID())
+			delete(nmap, n.ID())
 		}
 	}
 	c.Unlock()
@@ -478,7 +480,7 @@ func (c *controller) networkCleanup() {
 }
 
 var populateSpecial NetworkWalker = func(nw Network) bool {
-	if n := nw.(*network); n.hasSpecialDriver() {
+	if n := nw.(*network); n.hasSpecialDriver() && !n.ConfigOnly() {
 		if err := n.getController().addNetwork(n); err != nil {
 			logrus.Warnf("Failed to populate network %q with driver %q", nw.Name(), nw.Type())
 		}