Allow remote IPAM driver to express capability
- So that a DHCP based plugin can express it needs the endpoint MAC address when requested for an IP address. - In such case libnetwork will allocate one if not already provided by user Signed-off-by: Alessandro Boch <aboch@docker.com>
This commit is contained in:
parent
5359d01a51
commit
29299b73df
|
@ -121,7 +121,8 @@ type driverData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ipamData struct {
|
type ipamData struct {
|
||||||
driver ipamapi.Ipam
|
driver ipamapi.Ipam
|
||||||
|
capability *ipamapi.Capability
|
||||||
// default address spaces are provided by ipam driver at registration time
|
// default address spaces are provided by ipam driver at registration time
|
||||||
defaultLocalAddressSpace, defaultGlobalAddressSpace string
|
defaultLocalAddressSpace, defaultGlobalAddressSpace string
|
||||||
}
|
}
|
||||||
|
@ -306,7 +307,7 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
|
func (c *controller) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
|
||||||
if !config.IsValidName(name) {
|
if !config.IsValidName(name) {
|
||||||
return ErrInvalidName(name)
|
return ErrInvalidName(name)
|
||||||
}
|
}
|
||||||
|
@ -322,7 +323,7 @@ func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error
|
||||||
return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err)
|
return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err)
|
||||||
}
|
}
|
||||||
c.Lock()
|
c.Lock()
|
||||||
c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS}
|
c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps}
|
||||||
c.Unlock()
|
c.Unlock()
|
||||||
|
|
||||||
log.Debugf("Registering ipam driver: %q", name)
|
log.Debugf("Registering ipam driver: %q", name)
|
||||||
|
@ -330,6 +331,14 @@ func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
|
||||||
|
return c.registerIpamDriver(name, driver, &ipamapi.Capability{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
|
||||||
|
return c.registerIpamDriver(name, driver, caps)
|
||||||
|
}
|
||||||
|
|
||||||
// NewNetwork creates a new network of the specified network type. The options
|
// NewNetwork creates a new network of the specified network type. The options
|
||||||
// are network specific and modeled in a generic way.
|
// are network specific and modeled in a generic way.
|
||||||
func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {
|
func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ Communication protocol is the same as the remote network driver.
|
||||||
|
|
||||||
## Handshake
|
## Handshake
|
||||||
|
|
||||||
During driver registration, libnetwork will query the remote driver about the default local and global address spaces strings.
|
During driver registration, libnetwork will query the remote driver about the default local and global address spaces strings, and about the driver capabilities.
|
||||||
More detailed information can be found in the respective section in this document.
|
More detailed information can be found in the respective section in this document.
|
||||||
|
|
||||||
## Datastore Requirements
|
## Datastore Requirements
|
||||||
|
@ -249,3 +249,27 @@ Where:
|
||||||
* `PoolID` is the pool identifier
|
* `PoolID` is the pool identifier
|
||||||
* `Address` is the IP address to release
|
* `Address` is the IP address to release
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### GetCapabilities
|
||||||
|
|
||||||
|
During the driver registration, libnetwork will query the driver about its capabilities. It is not mandatory for the driver to support this URL endpoint. If driver does not support it, registration will succeed with empty capabilities automatically added to the internal driver handle.
|
||||||
|
|
||||||
|
During registration, the remote driver will receive a POST message to the URL `/IpamDriver.GetCapabilities` with no payload. The driver's response should have the form:
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"RequiresMACAddress": bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
Capabilities are requirements, features the remote ipam driver can express during registration with libnetwork.
|
||||||
|
As of now libnetwork accepts the following capabilities:
|
||||||
|
|
||||||
|
### RequiresMACAddress
|
||||||
|
|
||||||
|
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"`.
|
|
@ -748,11 +748,8 @@ func (ep *endpoint) DataScope() string {
|
||||||
return ep.getNetwork().DataScope()
|
return ep.getNetwork().DataScope()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
|
func (ep *endpoint) assignAddress(ipam ipamapi.Ipam, assignIPv4, assignIPv6 bool) error {
|
||||||
var (
|
var err error
|
||||||
ipam ipamapi.Ipam
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
n := ep.getNetwork()
|
n := ep.getNetwork()
|
||||||
if n.Type() == "host" || n.Type() == "null" {
|
if n.Type() == "host" || n.Type() == "null" {
|
||||||
|
@ -761,11 +758,6 @@ func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
|
||||||
|
|
||||||
log.Debugf("Assigning addresses for endpoint %s's interface on network %s", ep.Name(), n.Name())
|
log.Debugf("Assigning addresses for endpoint %s's interface on network %s", ep.Name(), n.Name())
|
||||||
|
|
||||||
ipam, err = n.getController().getIpamDriver(n.ipamType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if assignIPv4 {
|
if assignIPv4 {
|
||||||
if err = ep.assignAddressVersion(4, ipam); err != nil {
|
if err = ep.assignAddressVersion(4, ipam); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -22,8 +22,10 @@ const (
|
||||||
|
|
||||||
// Callback provides a Callback interface for registering an IPAM instance into LibNetwork
|
// Callback provides a Callback interface for registering an IPAM instance into LibNetwork
|
||||||
type Callback interface {
|
type Callback interface {
|
||||||
// RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a ipam instance
|
// RegisterIpamDriver provides a way for Remote drivers to dynamically register with libnetwork
|
||||||
RegisterIpamDriver(name string, driver Ipam) error
|
RegisterIpamDriver(name string, driver Ipam) error
|
||||||
|
// RegisterIpamDriverWithCapabilities provides a way for Remote drivers to dynamically register with libnetwork and specify cpaabilities
|
||||||
|
RegisterIpamDriverWithCapabilities(name string, driver Ipam, capability *Capability) error
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************
|
/**************
|
||||||
|
@ -70,3 +72,8 @@ type Ipam interface {
|
||||||
// Release the address from the specified pool ID
|
// Release the address from the specified pool ID
|
||||||
ReleaseAddress(string, net.IP) error
|
ReleaseAddress(string, net.IP) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capability represents the requirements and capabilities of the IPAM driver
|
||||||
|
type Capability struct {
|
||||||
|
RequiresMACAddress bool
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// messages between libnetwork and the remote ipam plugin
|
// messages between libnetwork and the remote ipam plugin
|
||||||
package api
|
package api
|
||||||
|
|
||||||
|
import "github.com/docker/libnetwork/ipamapi"
|
||||||
|
|
||||||
// Response is the basic response structure used in all responses
|
// Response is the basic response structure used in all responses
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Error string
|
Error string
|
||||||
|
@ -17,6 +19,17 @@ func (r *Response) GetError() string {
|
||||||
return r.Error
|
return r.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCapabilityResponse is the response of GetCapability request
|
||||||
|
type GetCapabilityResponse struct {
|
||||||
|
Response
|
||||||
|
RequiresMACAddress 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}
|
||||||
|
}
|
||||||
|
|
||||||
// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
|
// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
|
||||||
type GetAddressSpacesResponse struct {
|
type GetAddressSpacesResponse struct {
|
||||||
Response
|
Response
|
||||||
|
|
|
@ -30,8 +30,17 @@ func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
|
||||||
// Init registers a remote ipam when its plugin is activated
|
// Init registers a remote ipam when its plugin is activated
|
||||||
func Init(cb ipamapi.Callback, l, g interface{}) error {
|
func Init(cb ipamapi.Callback, l, g interface{}) error {
|
||||||
plugins.Handle(ipamapi.PluginEndpointType, func(name string, client *plugins.Client) {
|
plugins.Handle(ipamapi.PluginEndpointType, func(name string, client *plugins.Client) {
|
||||||
if err := cb.RegisterIpamDriver(name, newAllocator(name, client)); err != nil {
|
a := newAllocator(name, client)
|
||||||
log.Errorf("error registering remote ipam %s due to %v", name, err)
|
if cps, err := a.(*allocator).getCapabilities(); err == nil {
|
||||||
|
if err := cb.RegisterIpamDriverWithCapabilities(name, a, cps); err != nil {
|
||||||
|
log.Errorf("error registering remote ipam driver %s due to %v", name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("remote ipam driver %s does not support capabilities", name)
|
||||||
|
log.Debug(err)
|
||||||
|
if err := cb.RegisterIpamDriver(name, a); err != nil {
|
||||||
|
log.Errorf("error registering remote ipam driver %s due to %v", name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
|
@ -49,6 +58,14 @@ func (a *allocator) call(methodName string, arg interface{}, retVal PluginRespon
|
||||||
return nil
|
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
|
// GetDefaultAddressSpaces returns the local and global default address spaces
|
||||||
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
|
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
|
||||||
res := &api.GetAddressSpacesResponse{}
|
res := &api.GetAddressSpacesResponse{}
|
||||||
|
|
|
@ -61,6 +61,53 @@ func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetCapabilities(t *testing.T) {
|
||||||
|
var plugin = "test-ipam-driver-capabilities"
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
defer setupPlugin(t, plugin, mux)()
|
||||||
|
|
||||||
|
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"RequiresMACAddress": true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := newAllocator(plugin, p.Client)
|
||||||
|
|
||||||
|
caps, err := d.(*allocator).getCapabilities()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !caps.RequiresMACAddress {
|
||||||
|
t.Fatalf("Unexpected capability: %v", caps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCapabilitiesFromLegacyDriver(t *testing.T) {
|
||||||
|
var plugin = "test-ipam-legacy-driver"
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
defer setupPlugin(t, plugin, mux)()
|
||||||
|
|
||||||
|
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := newAllocator(plugin, p.Client)
|
||||||
|
|
||||||
|
if _, err := d.(*allocator).getCapabilities(); err == nil {
|
||||||
|
t.Fatalf("Expected error, but got Success %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDefaultAddressSpaces(t *testing.T) {
|
func TestGetDefaultAddressSpaces(t *testing.T) {
|
||||||
var plugin = "test-ipam-driver-addr-spaces"
|
var plugin = "test-ipam-driver-addr-spaces"
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,30 @@ func TestDriverRegistration(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIpamDriverRegistration(t *testing.T) {
|
||||||
|
c, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Stop()
|
||||||
|
|
||||||
|
err = c.(*controller).RegisterIpamDriver("", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected failure, but suceeded")
|
||||||
|
}
|
||||||
|
if _, ok := err.(types.BadRequestError); !ok {
|
||||||
|
t.Fatalf("Failed for unexpected reason: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.(*controller).RegisterIpamDriver(ipamapi.DefaultIPAM, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected failure, but suceeded")
|
||||||
|
}
|
||||||
|
if _, ok := err.(types.ForbiddenError); !ok {
|
||||||
|
t.Fatalf("Failed for unexpected reason: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNetworkMarshalling(t *testing.T) {
|
func TestNetworkMarshalling(t *testing.T) {
|
||||||
n := &network{
|
n := &network{
|
||||||
name: "Miao",
|
name: "Miao",
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/docker/libnetwork/etchosts"
|
"github.com/docker/libnetwork/etchosts"
|
||||||
"github.com/docker/libnetwork/ipamapi"
|
"github.com/docker/libnetwork/ipamapi"
|
||||||
"github.com/docker/libnetwork/netlabel"
|
"github.com/docker/libnetwork/netlabel"
|
||||||
|
"github.com/docker/libnetwork/netutils"
|
||||||
"github.com/docker/libnetwork/options"
|
"github.com/docker/libnetwork/options"
|
||||||
"github.com/docker/libnetwork/types"
|
"github.com/docker/libnetwork/types"
|
||||||
)
|
)
|
||||||
|
@ -678,7 +679,22 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ep.assignAddress(true, !n.postIPv6); err != nil {
|
ipam, err := n.getController().getIPAM(n.ipamType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipam.capability.RequiresMACAddress {
|
||||||
|
if ep.iface.mac == nil {
|
||||||
|
ep.iface.mac = netutils.GenerateRandomMAC()
|
||||||
|
}
|
||||||
|
if ep.ipamOptions == nil {
|
||||||
|
ep.ipamOptions = make(map[string]string)
|
||||||
|
}
|
||||||
|
ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ep.assignAddress(ipam.driver, true, !n.postIPv6); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -698,7 +714,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = ep.assignAddress(false, n.postIPv6); err != nil {
|
if err = ep.assignAddress(ipam.driver, false, n.postIPv6); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue