diff --git a/libnetwork/agent.go b/libnetwork/agent.go index b4b7bdf693..d67d446fe8 100644 --- a/libnetwork/agent.go +++ b/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 diff --git a/libnetwork/controller.go b/libnetwork/controller.go index 8b2a983d51..159e40f646 100644 --- a/libnetwork/controller.go +++ b/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 + } - _, cap, err := network.resolveDriver(networkType, true) + 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(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 } diff --git a/libnetwork/datastore/datastore.go b/libnetwork/datastore/datastore.go index 19bf0b026b..82feef1c84 100644 --- a/libnetwork/datastore/datastore.go +++ b/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" ) diff --git a/libnetwork/docs/remote.md b/libnetwork/docs/remote.md index 48fd5e58e9..56125080fe 100644 --- a/libnetwork/docs/remote.md +++ b/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 diff --git a/libnetwork/driverapi/driverapi.go b/libnetwork/driverapi/driverapi.go index 074438ef88..48a14ae57a 100644 --- a/libnetwork/driverapi/driverapi.go +++ b/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 diff --git a/libnetwork/drivers/bridge/bridge.go b/libnetwork/drivers/bridge/bridge.go index e681b8f7c4..dd79f04d91 100644 --- a/libnetwork/drivers/bridge/bridge.go +++ b/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) } diff --git a/libnetwork/drivers/bridge/brmanager/brmanager.go b/libnetwork/drivers/bridge/brmanager/brmanager.go new file mode 100644 index 0000000000..74bb95c001 --- /dev/null +++ b/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") +} diff --git a/libnetwork/drivers/host/host.go b/libnetwork/drivers/host/host.go index 7b4a986e6c..a71d461380 100644 --- a/libnetwork/drivers/host/host.go +++ b/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) } diff --git a/libnetwork/drivers/ipvlan/ipvlan.go b/libnetwork/drivers/ipvlan/ipvlan.go index 296804dc1a..c64ad555a3 100644 --- a/libnetwork/drivers/ipvlan/ipvlan.go +++ b/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{}, diff --git a/libnetwork/drivers/ipvlan/ivmanager/ivmanager.go b/libnetwork/drivers/ipvlan/ivmanager/ivmanager.go new file mode 100644 index 0000000000..519f1e8795 --- /dev/null +++ b/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") +} diff --git a/libnetwork/drivers/macvlan/macvlan.go b/libnetwork/drivers/macvlan/macvlan.go index 49b9fbae00..872e6f3ec1 100644 --- a/libnetwork/drivers/macvlan/macvlan.go +++ b/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{}, diff --git a/libnetwork/drivers/macvlan/mvmanager/mvmanager.go b/libnetwork/drivers/macvlan/mvmanager/mvmanager.go new file mode 100644 index 0000000000..0f811ac36f --- /dev/null +++ b/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") +} diff --git a/libnetwork/drivers/overlay/overlay.go b/libnetwork/drivers/overlay/overlay.go index 88e1010c43..8a932cf370 100644 --- a/libnetwork/drivers/overlay/overlay.go +++ b/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{}, diff --git a/libnetwork/drivers/overlay/ovmanager/ovmanager.go b/libnetwork/drivers/overlay/ovmanager/ovmanager.go index dce8f98b8e..3c3c0bb257 100644 --- a/libnetwork/drivers/overlay/ovmanager/ovmanager.go +++ b/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{ diff --git a/libnetwork/drivers/remote/api/api.go b/libnetwork/drivers/remote/api/api.go index f9a341c540..d24f190162 100644 --- a/libnetwork/drivers/remote/api/api.go +++ b/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 diff --git a/libnetwork/drivers/remote/driver.go b/libnetwork/drivers/remote/driver.go index 49a7fb4951..ffe0730c95 100644 --- a/libnetwork/drivers/remote/driver.go +++ b/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 } diff --git a/libnetwork/drivers/remote/driver_test.go b/libnetwork/drivers/remote/driver_test.go index e559a61299..8a97bacffd 100644 --- a/libnetwork/drivers/remote/driver_test.go +++ b/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) } } diff --git a/libnetwork/drivers/solaris/bridge/bridge.go b/libnetwork/drivers/solaris/bridge/bridge.go index 13dd5f14bc..2547016a4b 100644 --- a/libnetwork/drivers/solaris/bridge/bridge.go +++ b/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) } diff --git a/libnetwork/drivers/solaris/overlay/overlay.go b/libnetwork/drivers/solaris/overlay/overlay.go index b001f58af7..0a5a1bfdce 100644 --- a/libnetwork/drivers/solaris/overlay/overlay.go +++ b/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{}, diff --git a/libnetwork/drivers/solaris/overlay/ovmanager/ovmanager.go b/libnetwork/drivers/solaris/overlay/ovmanager/ovmanager.go index f053ff9784..a3a7cd4cf8 100644 --- a/libnetwork/drivers/solaris/overlay/ovmanager/ovmanager.go +++ b/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{ diff --git a/libnetwork/drivers/windows/overlay/overlay_windows.go b/libnetwork/drivers/windows/overlay/overlay_windows.go index 2ae2cec77e..111e517dbd 100644 --- a/libnetwork/drivers/windows/overlay/overlay_windows.go +++ b/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{ diff --git a/libnetwork/drivers/windows/windows.go b/libnetwork/drivers/windows/windows.go index b6591a1d6d..84089a5284 100644 --- a/libnetwork/drivers/windows/windows.go +++ b/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, }) } } diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index 8e70c38d76..e607eddaf2 100644 --- a/libnetwork/endpoint.go +++ b/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) diff --git a/libnetwork/libnetwork_internal_test.go b/libnetwork/libnetwork_internal_test.go index bfd026d1e2..927d623daf 100644 --- a/libnetwork/libnetwork_internal_test.go +++ b/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"+ diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index 22c1b6fc1e..c9c645f114 100644 --- a/libnetwork/libnetwork_test.go +++ b/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)() diff --git a/libnetwork/network.go b/libnetwork/network.go index 8077770018..5fcca95fc4 100644 --- a/libnetwork/network.go +++ b/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 +} diff --git a/libnetwork/store.go b/libnetwork/store.go index 58e1d852f1..1a8dceca78 100644 --- a/libnetwork/store.go +++ b/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()) }