Support configuration networks

- They are configuration-only networks which
  can be used to supply the configuration
  when creating regular networks.
- They do not get allocated and do net get plumbed.
  Drivers do not get to know about them.
- They can be removed, once no other network is
  using them.
- When user creates a network specifying a
  configuration network for the config, no
  other network specific configuration field
  is are accepted. User can only specify
  network operator fields (attachable, internal,...)
- They do not need to have a driver field, that
  field gets actually reset upon creation.

Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
Alessandro Boch 2017-04-07 10:51:39 -07:00
parent 6786135bf7
commit 16406c8dbd
6 changed files with 339 additions and 4 deletions

View File

@ -722,8 +722,27 @@ 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
}
@ -737,7 +756,6 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ...
// 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 +765,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 +807,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 +827,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 +843,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 +861,9 @@ func (c *controller) reservePools() {
}
for _, n := range networks {
if n.configOnly {
continue
}
if !doReplayPoolReserve(n) {
continue
}

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
@ -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 {
@ -479,6 +574,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 +680,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
@ -713,6 +816,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 {
@ -797,6 +917,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 +929,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 +969,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 +1031,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 +1754,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 +1935,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

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