Merge pull request #1742 from aboch/nlo2

ConnectivityScope capability and configuration networks
This commit is contained in:
Madhu Venugopal 2017-05-13 06:21:00 -07:00 committed by GitHub
commit f32eec401b
27 changed files with 706 additions and 46 deletions

View file

@ -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

View file

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

View file

@ -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"
)

View file

@ -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

View file

@ -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

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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{},

View file

@ -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")
}

View file

@ -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{},

View file

@ -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")
}

View file

@ -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{},

View file

@ -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{

View file

@ -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

View file

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

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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{},

View file

@ -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{

View file

@ -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{

View file

@ -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,
})
}
}

View file

@ -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)

View file

@ -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"+

View file

@ -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)()

View file

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

View file

@ -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())
}