diff --git a/libnetwork/agent.go b/libnetwork/agent.go index b4b7bdf693a8500cd32aff57f4e3865919e2ad21..d67d446fe8b50218eaaa73cdce9eeebe809878c7 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 8b2a983d51f38c5c86e3b6d1cc6b7b768b04e4c3..159e40f646dca948d82c263820fc0b404bc4859f 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 + } + + 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 } diff --git a/libnetwork/datastore/datastore.go b/libnetwork/datastore/datastore.go index 19bf0b026b9d31e0770b5ece1bc2f03ed7e32739..82feef1c84ebaf76a308a2eed821baaa94783622 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 48fd5e58e94df0dfdd93835a3f0d671c53bf1576..56125080fe7e1ef1796318df453741dbcc205742 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 074438ef88a27c33fa574679412d6e8c278aaae7..48a14ae57a8baf01cf4c90e2313a45a56c1eeef6 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 e681b8f7c4668f11833ac0198fb44f400a26e0d1..dd79f04d912ca6ee22e56779f386ce756f122f88 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 0000000000000000000000000000000000000000..74bb95c001806ef999ded02a4add62d3145bbd51 --- /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 7b4a986e6c586941a4d27f48609730f624a97a16..a71d461380f8e3c1cc9a0b5f281e477b7289b83f 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 296804dc1a5dd984087c09d0dc73875a594be33b..c64ad555a31cfd711f080178cae8336aebbb6fef 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 0000000000000000000000000000000000000000..519f1e8795d45f828a310e1afebffffabbb8a846 --- /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 49b9fbae0030f0dbd1f5b1d2803ef389dfbd1ef9..872e6f3ec163739ba47c6bedfedac1636f48dfb2 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 0000000000000000000000000000000000000000..0f811ac36f8572b243c38878b51d88ae8a2c0092 --- /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 88e1010c437c992fbd99ce9042a0722fffd477b8..8a932cf370bb2354ff44179c34da2ddb2d89aa5c 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 dce8f98b8e1126fafdf84c679545c7e343b4eb5f..3c3c0bb257f3b5dcd1542dd4c26863d80105cf44 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 f9a341c540273ffaeccdda9349f2f691fd3b7aa0..d24f19016226f132a4526384a34db6a1cab75361 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 49a7fb49518af47f65a6f15676740fc55e076265..ffe0730c9524ccab22e18a7b52293d440fffae62 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 e559a61299e9dde723a8699371dfb9c3dad86abe..8a97bacffd0dd217cdd3e073b7cc78ef7031de9c 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 13dd5f14bc664fd29d75df985ac87b1e9820ac9d..2547016a4b994d540615f91d685738cb281ab00c 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 b001f58af770375ce5d4a9fcbe1e987dcc9af379..0a5a1bfdceb68db2b430a8a8d8c037157f363dd2 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 f053ff9784d52bdc2a5bdf222dd49bcb7234583d..a3a7cd4cf8f64fd636cabfe076ee2c9b54cf28e9 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 2ae2cec77e8cea8c4c6259e54861f97a61e0ac93..111e517dbdd08e3cc72421cfa61accad09b8bc28 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 b6591a1d6d861bf661b6f54fcaed21c5f69a4712..84089a5284c810ba0f500b7024bc54a5165cd4b8 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 8e70c38d76441547a81ca5502809e77f1b3ced8d..e607eddaf27f868ae7948158f1f7e476570a5b69 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 bfd026d1e295c13284edb163c027d92de34a7096..927d623dafc1f9c29bde44bcffb690cbe750614b 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 22c1b6fc1edf74e1860ecde1835fbaee9a064a4f..c9c645f11421d53b22a2a008a88e61e6cb9d6463 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 807777001856fe3790070bcd9355f55be2d6c0c7..5fcca95fc405be3a6d797a77e888769748952784 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 58e1d852f14e3bc003ca3d14273071b21b3c478e..1a8dceca78b146366f60b8df1d223566f636458f 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()) }