libnetwork/ipam: assorted cleanup and refactor
### libnetwork/ipams/null: use consts for fixed values

### libnetwork/ipam: Allocator.RequestPool: make parseErr only handle errors

This makes it slightly more readable to see what's returned in each of
the code-paths. Also move validation of pool/subpool earlier in the
function.

### libnetwork/ipam: Allocator.RequestPool: mark options arg as unused

The options are unused, other than for debug-logging, which made it look
as if they were actually consumed anywhere, but they aren't.

### libnetwork/ipam: Allocator.RequestPool: name args, output vars

network.requestPoolHelper and Allocator.RequestPool have many args and
output vars with generic types. Add names for them to make it easier to
grasp what's what.

### libnetwork/ipam: PoolID.String(): don't use fmt.Sprintf

As this function may be called repeatedly to convert to/from a string,
it may be worth optimizing it a bit. Adding a minimal Benchmark for
it as well.

Before/after:

    BenchmarkPoolIDToString-10   2842830   424.3 ns/op   232 B/op  12 allocs/op
    BenchmarkPoolIDToString-10   7176738   166.8 ns/op   112 B/op   7 allocs/op


libnetwork/ipam: move PoolID.FromString() to a PoolIDFromString() func

This makes it easier to consume, without first having to create an empty
PoolID.

Performance is the same:

    BenchmarkPoolIDFromString-10   6100345   196.5 ns/op  112 B/op   3 allocs/op
    BenchmarkPoolIDFromString-10   6252750   192.0 ns/op  112 B/op   3 allocs/op

Note that I opted not to change the return-type to a pointer, as that seems
to perform less;

    BenchmarkPoolIDFromString-10   6252750   192.0 ns/op  112 B/op   3 allocs/op
    BenchmarkPoolIDFromString-10   5288682   226.6 ns/op  192 B/op   4 allocs/op


### libnetwork: network.IpamConfig, network.IpamInfo: name output vars

Both functions have multiple output vars with generic types, which made
it hard to grasp what's what.


### libnetwork: network.requestPoolHelper: don't defer in a loop

This function intentionally holds a lock / lease on address-pools to
prevent trying the same pool repeatedly.

Let's try to make this logic slightly more transparent, and prevent
defining defers in a loop. Releasing all the pools in a singe defer
also allows us to get the network-name once, which prevents locking
and unlocking the network for each iteration.

### libnetwork: network.requestPoolHelper: remove dead code

This code was only run if no preferred pool was specified, however,
since [libnetwork#1162][2], the function would already return early
if a preferred pools was set (and the overlap check to be skipped),
so this was now just dead code.

[2]: https://github.com/moby/libnetwork/commit/9cc3385f4421ff00a5b7dfc2d9ad44289aa21c85


### libnetwork: network.requestPoolHelper: slightly optimize order of checks

Check the preferredPool first, as other checks could be doing more
(such as locking, or validating / parsing). Also adding a note, as
it's unclear why we're ignoring invalid pools here.

The "invalid" conditions was added in [libnetwork#1095][1], which
moved code to reduce os-specific dependencies in the ipam package,
but also introduced a types.IsIPNetValid() function, which considers
"0.0.0.0/0" invalid, and added it to the condition to return early.

Unfortunately review does not mention this change, so there's no
context why. Possibly this was done to prevent errors further down
the line (when checking for overlaps), but returning an error here
instead would likely have avoided that as well, so we can only guess.

To make this code slightly more transparent, this patch also inlines
the "types.IsIPNetValid" function, as it's not used anywhere else,
and inlining it makes it more visible.

[1]: https://github.com/moby/libnetwork/commit/5ca79d6b87873264516323a7b76f0af7d0298492#diff-bdcd879439d041827d334846f9aba01de6e3683ed8fdd01e63917dae6df23846



**- How to verify it**

**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->


**- A picture of a cute animal (not mandatory but encouraged)**


libnetwork/ipams/remote/remote.go
package remote

import (
	"context"
	"fmt"
	"net"

	"github.com/containerd/containerd/log"
	"github.com/docker/docker/libnetwork/ipamapi"
	"github.com/docker/docker/libnetwork/ipams/remote/api"
	"github.com/docker/docker/libnetwork/types"
	"github.com/docker/docker/pkg/plugingetter"
	"github.com/docker/docker/pkg/plugins"
	"github.com/pkg/errors"
)

type allocator struct {
	endpoint *plugins.Client
	name     string
}

// PluginResponse is the interface for the plugin request responses
type PluginResponse interface {
	IsSuccess() bool
	GetError() string
}

func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
	a := &allocator{name: name, endpoint: client}
	return a
}

// Register registers a remote ipam when its plugin is activated.
func Register(cb ipamapi.Registerer, pg plugingetter.PluginGetter) error {
	newPluginHandler := func(name string, client *plugins.Client) {
		a := newAllocator(name, client)
		if cps, err := a.(*allocator).getCapabilities(); err == nil {
			if err := cb.RegisterIpamDriverWithCapabilities(name, a, cps); err != nil {
				log.G(context.TODO()).Errorf("error registering remote ipam driver %s due to %v", name, err)
			}
		} else {
			log.G(context.TODO()).Infof("remote ipam driver %s does not support capabilities", name)
			log.G(context.TODO()).Debug(err)
			if err := cb.RegisterIpamDriver(name, a); err != nil {
				log.G(context.TODO()).Errorf("error registering remote ipam driver %s due to %v", name, err)
			}
		}
	}

	// Unit test code is unaware of a true PluginStore. So we fall back to v1 plugins.
	handleFunc := plugins.Handle
	if pg != nil {
		handleFunc = pg.Handle
		activePlugins := pg.GetAllManagedPluginsByCap(ipamapi.PluginEndpointType)
		for _, ap := range activePlugins {
			client, err := getPluginClient(ap)
			if err != nil {
				return err
			}
			newPluginHandler(ap.Name(), client)
		}
	}
	handleFunc(ipamapi.PluginEndpointType, newPluginHandler)
	return nil
}

func getPluginClient(p plugingetter.CompatPlugin) (*plugins.Client, error) {
	if v1, ok := p.(plugingetter.PluginWithV1Client); ok {
		return v1.Client(), nil
	}

	pa, ok := p.(plugingetter.PluginAddr)
	if !ok {
		return nil, errors.Errorf("unknown plugin type %T", p)
	}

	if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 {
		return nil, errors.Errorf("unsupported plugin protocol %s", pa.Protocol())
	}

	addr := pa.Addr()
	client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
	if err != nil {
		return nil, errors.Wrap(err, "error creating plugin client")
	}
	return client, nil
}

func (a *allocator) call(methodName string, arg interface{}, retVal PluginResponse) error {
	method := ipamapi.PluginEndpointType + "." + methodName
	err := a.endpoint.Call(method, arg, retVal)
	if err != nil {
		return err
	}
	if !retVal.IsSuccess() {
		return fmt.Errorf("remote: %s", retVal.GetError())
	}
	return nil
}

func (a *allocator) getCapabilities() (*ipamapi.Capability, error) {
	var res api.GetCapabilityResponse
	if err := a.call("GetCapabilities", nil, &res); err != nil {
		return nil, err
	}
	return res.ToCapability(), nil
}

// GetDefaultAddressSpaces returns the local and global default address spaces
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
	res := &api.GetAddressSpacesResponse{}
	if err := a.call("GetDefaultAddressSpaces", nil, res); err != nil {
		return "", "", err
	}
	return res.LocalDefaultAddressSpace, res.GlobalDefaultAddressSpace, nil
}

// RequestPool requests an address pool in the specified address space
func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
	req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: pool, SubPool: subPool, Options: options, V6: v6}
	res := &api.RequestPoolResponse{}
	if err := a.call("RequestPool", req, res); err != nil {
		return "", nil, nil, err
	}
	retPool, err := types.ParseCIDR(res.Pool)
	return res.PoolID, retPool, res.Data, err
}

// ReleasePool removes an address pool from the specified address space
func (a *allocator) ReleasePool(poolID string) error {
	req := &api.ReleasePoolRequest{PoolID: poolID}
	res := &api.ReleasePoolResponse{}
	return a.call("ReleasePool", req, res)
}

// RequestAddress requests an address from the address pool
func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
	var (
		prefAddress string
		retAddress  *net.IPNet
		err         error
	)
	if address != nil {
		prefAddress = address.String()
	}
	req := &api.RequestAddressRequest{PoolID: poolID, Address: prefAddress, Options: options}
	res := &api.RequestAddressResponse{}
	if err := a.call("RequestAddress", req, res); err != nil {
		return nil, nil, err
	}
	if res.Address != "" {
		retAddress, err = types.ParseCIDR(res.Address)
	} else {
		return nil, nil, ipamapi.ErrNoIPReturned
	}
	return retAddress, res.Data, err
}

// ReleaseAddress releases the address from the specified address pool
func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
	var relAddress string
	if address != nil {
		relAddress = address.String()
	}
	req := &api.ReleaseAddressRequest{PoolID: poolID, Address: relAddress}
	res := &api.ReleaseAddressResponse{}
	return a.call("ReleaseAddress", req, res)
}

func (a *allocator) IsBuiltIn() bool {
	return false
}

package remote

import (
	"context"
	"fmt"
	"net"

	"github.com/containerd/containerd/log"
	"github.com/docker/docker/libnetwork/ipamapi"
	"github.com/docker/docker/libnetwork/ipams/remote/api"
	"github.com/docker/docker/libnetwork/types"
	"github.com/docker/docker/pkg/plugingetter"
	"github.com/docker/docker/pkg/plugins"
	"github.com/pkg/errors"
)

type allocator struct {
	endpoint *plugins.Client
	name     string
}

// PluginResponse is the interface for the plugin request responses
type PluginResponse interface {
	IsSuccess() bool
	GetError() string
}

func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
	a := &allocator{name: name, endpoint: client}
	return a
}

// Register registers a remote ipam when its plugin is activated.
func Register(cb ipamapi.Registerer, pg plugingetter.PluginGetter) error {
	newPluginHandler := func(name string, client *plugins.Client) {
		a := newAllocator(name, client)
		if cps, err := a.(*allocator).getCapabilities(); err == nil {
			if err := cb.RegisterIpamDriverWithCapabilities(name, a, cps); err != nil {
				log.G(context.TODO()).Errorf("error registering remote ipam driver %s due to %v", name, err)
			}
		} else {
			log.G(context.TODO()).Infof("remote ipam driver %s does not support capabilities", name)
			log.G(context.TODO()).Debug(err)
			if err := cb.RegisterIpamDriver(name, a); err != nil {
				log.G(context.TODO()).Errorf("error registering remote ipam driver %s due to %v", name, err)
			}
		}
	}

	// Unit test code is unaware of a true PluginStore. So we fall back to v1 plugins.
	handleFunc := plugins.Handle
	if pg != nil {
		handleFunc = pg.Handle
		activePlugins := pg.GetAllManagedPluginsByCap(ipamapi.PluginEndpointType)
		for _, ap := range activePlugins {
			client, err := getPluginClient(ap)
			if err != nil {
				return err
			}
			newPluginHandler(ap.Name(), client)
		}
	}
	handleFunc(ipamapi.PluginEndpointType, newPluginHandler)
	return nil
}

func getPluginClient(p plugingetter.CompatPlugin) (*plugins.Client, error) {
	if v1, ok := p.(plugingetter.PluginWithV1Client); ok {
		return v1.Client(), nil
	}

	pa, ok := p.(plugingetter.PluginAddr)
	if !ok {
		return nil, errors.Errorf("unknown plugin type %T", p)
	}

	if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 {
		return nil, errors.Errorf("unsupported plugin protocol %s", pa.Protocol())
	}

	addr := pa.Addr()
	client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout())
	if err != nil {
		return nil, errors.Wrap(err, "error creating plugin client")
	}
	return client, nil
}

func (a *allocator) call(methodName string, arg interface{}, retVal PluginResponse) error {
	method := ipamapi.PluginEndpointType + "." + methodName
	err := a.endpoint.Call(method, arg, retVal)
	if err != nil {
		return err
	}
	if !retVal.IsSuccess() {
		return fmt.Errorf("remote: %s", retVal.GetError())
	}
	return nil
}

func (a *allocator) getCapabilities() (*ipamapi.Capability, error) {
	var res api.GetCapabilityResponse
	if err := a.call("GetCapabilities", nil, &res); err != nil {
		return nil, err
	}
	return res.ToCapability(), nil
}

// GetDefaultAddressSpaces returns the local and global default address spaces
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
	res := &api.GetAddressSpacesResponse{}
	if err := a.call("GetDefaultAddressSpaces", nil, res); err != nil {
		return "", "", err
	}
	return res.LocalDefaultAddressSpace, res.GlobalDefaultAddressSpace, nil
}

// RequestPool requests an address pool in the specified address space
func (a *allocator) RequestPool(addressSpace, requestedPool, requestedSubPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
	req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: requestedPool, SubPool: requestedSubPool, Options: options, V6: v6}
	res := &api.RequestPoolResponse{}
	if err := a.call("RequestPool", req, res); err != nil {
		return "", nil, nil, err
	}
	retPool, err := types.ParseCIDR(res.Pool)
	return res.PoolID, retPool, res.Data, err
}

// ReleasePool removes an address pool from the specified address space
func (a *allocator) ReleasePool(poolID string) error {
	req := &api.ReleasePoolRequest{PoolID: poolID}
	res := &api.ReleasePoolResponse{}
	return a.call("ReleasePool", req, res)
}

// RequestAddress requests an address from the address pool
func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
	var (
		prefAddress string
		retAddress  *net.IPNet
		err         error
	)
	if address != nil {
		prefAddress = address.String()
	}
	req := &api.RequestAddressRequest{PoolID: poolID, Address: prefAddress, Options: options}
	res := &api.RequestAddressResponse{}
	if err := a.call("RequestAddress", req, res); err != nil {
		return nil, nil, err
	}
	if res.Address != "" {
		retAddress, err = types.ParseCIDR(res.Address)
	} else {
		return nil, nil, ipamapi.ErrNoIPReturned
	}
	return retAddress, res.Data, err
}

// ReleaseAddress releases the address from the specified address pool
func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
	var relAddress string
	if address != nil {
		relAddress = address.String()
	}
	req := &api.ReleaseAddressRequest{PoolID: poolID, Address: relAddress}
	res := &api.ReleaseAddressResponse{}
	return a.call("ReleaseAddress", req, res)
}

func (a *allocator) IsBuiltIn() bool {
	return false
}

Same here: could you rename `preferredPool` and `subPool` please?