diff --git a/libnetwork/controller.go b/libnetwork/controller.go index fa14b1cf1f..78b21a769e 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -200,6 +200,8 @@ func New(cfgOptions ...config.Option) (NetworkController, error) { c.cleanupLocalEndpoints() c.networkCleanup() + c.reservePools() + if err := c.startExternalKeyListener(); err != nil { return nil, err } @@ -546,6 +548,52 @@ func (c *controller) NewNetwork(networkType, name string, id string, options ... return network, nil } +func (c *controller) reservePools() { + networks, err := c.getNetworksForScope(datastore.LocalScope) + if err != nil { + log.Warnf("Could not retrieve networks from local store during ipam allocation for existing networks: %v", err) + return + } + + for _, n := range networks { + if !doReplayPoolReserve(n) { + continue + } + // Construct pseudo configs for the auto IP case + autoIPv4 := (len(n.ipamV4Config) == 0 || (len(n.ipamV4Config) == 1 && n.ipamV4Config[0].PreferredPool == "")) && len(n.ipamV4Info) > 0 + autoIPv6 := (len(n.ipamV6Config) == 0 || (len(n.ipamV6Config) == 1 && n.ipamV6Config[0].PreferredPool == "")) && len(n.ipamV6Info) > 0 + if autoIPv4 { + n.ipamV4Config = []*IpamConf{{PreferredPool: n.ipamV4Info[0].Pool.String()}} + } + if n.enableIPv6 && autoIPv6 { + n.ipamV6Config = []*IpamConf{{PreferredPool: n.ipamV6Info[0].Pool.String()}} + } + // Account current network gateways + for i, c := range n.ipamV4Config { + if c.Gateway == "" && n.ipamV4Info[i].Gateway != nil { + c.Gateway = n.ipamV4Info[i].Gateway.IP.String() + } + } + for i, c := range n.ipamV6Config { + if c.Gateway == "" && n.ipamV6Info[i].Gateway != nil { + c.Gateway = n.ipamV6Info[i].Gateway.IP.String() + } + } + if err := n.ipamAllocate(); err != nil { + log.Warnf("Failed to allocate ipam pool(s) for network %q (%s): %v", n.Name(), n.ID(), err) + } + } +} + +func doReplayPoolReserve(n *network) bool { + _, caps, err := n.getController().getIPAMDriver(n.ipamType) + if err != nil { + log.Warnf("Failed to retrieve ipam driver for network %q (%s): %v", n.Name(), n.ID(), err) + return false + } + return caps.RequiresRequestReplay +} + func (c *controller) addNetwork(n *network) error { d, err := n.driver(true) if err != nil { diff --git a/libnetwork/docs/ipam.md b/libnetwork/docs/ipam.md index 84e5b9bf79..2fa06d0b19 100644 --- a/libnetwork/docs/ipam.md +++ b/libnetwork/docs/ipam.md @@ -249,6 +249,7 @@ During registration, the remote driver will receive a POST message to the URL `/ { "RequiresMACAddress": bool + "RequiresRequestReplay": bool } @@ -263,6 +264,10 @@ As of now libnetwork accepts the following capabilities: It is a boolean value which tells libnetwork whether the ipam driver needs to know the interface MAC address in order to properly process the `RequestAddress()` call. If true, on `CreateEndpoint()` request, libnetwork will generate a random MAC address for the endpoint (if an explicit MAC address was not already provided by the user) and pass it to `RequestAddress()` when requesting the IP address inside the options map. The key will be the `netlabel.MacAddress` constant: `"com.docker.network.endpoint.macaddress"`. +### RequiresRequestReplay + +It is a boolean value which tells libnetwork whether the ipam driver needs to receive the replay of the `RequestPool()` and `RequestAddress()` requests on daemon reload. When libnetwork controller is initializing, it retrieves from local store the list of current local scope networks and, if this capability flag is set, it allows the IPAM driver to reconstruct the database of pools by replaying the `RequestPool()` requests for each pool and the `RequestAddress()` for each network gateway owned by the local networks. This can be useful to ipam drivers which decide not to persist the pools allocated to local scope networks. + ## Appendix diff --git a/libnetwork/drvregistry/drvregistry.go b/libnetwork/drvregistry/drvregistry.go index 6c1804dfa9..f12da15c0b 100644 --- a/libnetwork/drvregistry/drvregistry.go +++ b/libnetwork/drvregistry/drvregistry.go @@ -163,7 +163,7 @@ func (r *DrvRegistry) initIPAMs(lDs, gDs interface{}) error { remoteIpam.Init, nullIpam.Init, } { - if err := fn(r, lDs, gDs); err != nil { + if err := fn(r, nil, gDs); err != nil { return err } } diff --git a/libnetwork/ipamapi/contract.go b/libnetwork/ipamapi/contract.go index 513e482349..e7845f28e7 100644 --- a/libnetwork/ipamapi/contract.go +++ b/libnetwork/ipamapi/contract.go @@ -80,5 +80,10 @@ type Ipam interface { // Capability represents the requirements and capabilities of the IPAM driver type Capability struct { + // Whether on address request, libnetwork must + // specify the endpoint MAC address RequiresMACAddress bool + // Whether of daemon start, libnetwork must replay the pool + // request and the address request for current local networks + RequiresRequestReplay bool } diff --git a/libnetwork/ipams/builtin/builtin_unix.go b/libnetwork/ipams/builtin/builtin_unix.go index 1c9e852602..5baf515534 100644 --- a/libnetwork/ipams/builtin/builtin_unix.go +++ b/libnetwork/ipams/builtin/builtin_unix.go @@ -37,5 +37,7 @@ func Init(ic ipamapi.Callback, l, g interface{}) error { return err } - return ic.RegisterIpamDriver(ipamapi.DefaultIPAM, a) + cps := &ipamapi.Capability{RequiresRequestReplay: true} + + return ic.RegisterIpamDriverWithCapabilities(ipamapi.DefaultIPAM, a, cps) } diff --git a/libnetwork/ipams/remote/api/api.go b/libnetwork/ipams/remote/api/api.go index e357630cbb..394f2c5b18 100644 --- a/libnetwork/ipams/remote/api/api.go +++ b/libnetwork/ipams/remote/api/api.go @@ -22,12 +22,16 @@ func (r *Response) GetError() string { // GetCapabilityResponse is the response of GetCapability request type GetCapabilityResponse struct { Response - RequiresMACAddress bool + RequiresMACAddress bool + RequiresRequestReplay bool } // ToCapability converts the capability response into the internal ipam driver capaility structure func (capRes GetCapabilityResponse) ToCapability() *ipamapi.Capability { - return &ipamapi.Capability{RequiresMACAddress: capRes.RequiresMACAddress} + return &ipamapi.Capability{ + RequiresMACAddress: capRes.RequiresMACAddress, + RequiresRequestReplay: capRes.RequiresRequestReplay, + } } // GetAddressSpacesResponse is the response to the ``get default address spaces`` request message diff --git a/libnetwork/ipams/remote/remote_test.go b/libnetwork/ipams/remote/remote_test.go index 3c6cc4bfa9..8e5264f0de 100644 --- a/libnetwork/ipams/remote/remote_test.go +++ b/libnetwork/ipams/remote/remote_test.go @@ -86,7 +86,7 @@ func TestGetCapabilities(t *testing.T) { t.Fatal(err) } - if !caps.RequiresMACAddress { + if !caps.RequiresMACAddress || caps.RequiresRequestReplay { t.Fatalf("Unexpected capability: %v", caps) } }