Skip to content

Commit 29299b7

Browse files
committed
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>
1 parent 5359d01 commit 29299b7

File tree

9 files changed

+168
-19
lines changed

9 files changed

+168
-19
lines changed

libnetwork/controller.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ type driverData struct {
121121
}
122122

123123
type ipamData struct {
124-
driver ipamapi.Ipam
124+
driver ipamapi.Ipam
125+
capability *ipamapi.Capability
125126
// default address spaces are provided by ipam driver at registration time
126127
defaultLocalAddressSpace, defaultGlobalAddressSpace string
127128
}
@@ -306,7 +307,7 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver,
306307
return nil
307308
}
308309

309-
func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
310+
func (c *controller) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
310311
if !config.IsValidName(name) {
311312
return ErrInvalidName(name)
312313
}
@@ -322,14 +323,22 @@ func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error
322323
return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err)
323324
}
324325
c.Lock()
325-
c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS}
326+
c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps}
326327
c.Unlock()
327328

328329
log.Debugf("Registering ipam driver: %q", name)
329330

330331
return nil
331332
}
332333

334+
func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
335+
return c.registerIpamDriver(name, driver, &ipamapi.Capability{})
336+
}
337+
338+
func (c *controller) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
339+
return c.registerIpamDriver(name, driver, caps)
340+
}
341+
333342
// NewNetwork creates a new network of the specified network type. The options
334343
// are network specific and modeled in a generic way.
335344
func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) {

libnetwork/docs/ipam.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Communication protocol is the same as the remote network driver.
1515

1616
## Handshake
1717

18-
During driver registration, libnetwork will query the remote driver about the default local and global address spaces strings.
18+
During driver registration, libnetwork will query the remote driver about the default local and global address spaces strings, and about the driver capabilities.
1919
More detailed information can be found in the respective section in this document.
2020

2121
## Datastore Requirements
@@ -249,3 +249,27 @@ Where:
249249
* `PoolID` is the pool identifier
250250
* `Address` is the IP address to release
251251

252+
253+
254+
### GetCapabilities
255+
256+
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.
257+
258+
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:
259+
260+
261+
{
262+
"RequiresMACAddress": bool
263+
}
264+
265+
266+
267+
## Capabilities
268+
269+
Capabilities are requirements, features the remote ipam driver can express during registration with libnetwork.
270+
As of now libnetwork accepts the following capabilities:
271+
272+
### RequiresMACAddress
273+
274+
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.
275+
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"`.

libnetwork/endpoint.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -748,11 +748,8 @@ func (ep *endpoint) DataScope() string {
748748
return ep.getNetwork().DataScope()
749749
}
750750

751-
func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
752-
var (
753-
ipam ipamapi.Ipam
754-
err error
755-
)
751+
func (ep *endpoint) assignAddress(ipam ipamapi.Ipam, assignIPv4, assignIPv6 bool) error {
752+
var err error
756753

757754
n := ep.getNetwork()
758755
if n.Type() == "host" || n.Type() == "null" {
@@ -761,11 +758,6 @@ func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
761758

762759
log.Debugf("Assigning addresses for endpoint %s's interface on network %s", ep.Name(), n.Name())
763760

764-
ipam, err = n.getController().getIpamDriver(n.ipamType)
765-
if err != nil {
766-
return err
767-
}
768-
769761
if assignIPv4 {
770762
if err = ep.assignAddressVersion(4, ipam); err != nil {
771763
return err

libnetwork/ipamapi/contract.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ const (
2222

2323
// Callback provides a Callback interface for registering an IPAM instance into LibNetwork
2424
type Callback interface {
25-
// RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a ipam instance
25+
// RegisterIpamDriver provides a way for Remote drivers to dynamically register with libnetwork
2626
RegisterIpamDriver(name string, driver Ipam) error
27+
// RegisterIpamDriverWithCapabilities provides a way for Remote drivers to dynamically register with libnetwork and specify cpaabilities
28+
RegisterIpamDriverWithCapabilities(name string, driver Ipam, capability *Capability) error
2729
}
2830

2931
/**************
@@ -70,3 +72,8 @@ type Ipam interface {
7072
// Release the address from the specified pool ID
7173
ReleaseAddress(string, net.IP) error
7274
}
75+
76+
// Capability represents the requirements and capabilities of the IPAM driver
77+
type Capability struct {
78+
RequiresMACAddress bool
79+
}

libnetwork/ipams/remote/api/api.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// messages between libnetwork and the remote ipam plugin
33
package api
44

5+
import "github.com/docker/libnetwork/ipamapi"
6+
57
// Response is the basic response structure used in all responses
68
type Response struct {
79
Error string
@@ -17,6 +19,17 @@ func (r *Response) GetError() string {
1719
return r.Error
1820
}
1921

22+
// GetCapabilityResponse is the response of GetCapability request
23+
type GetCapabilityResponse struct {
24+
Response
25+
RequiresMACAddress bool
26+
}
27+
28+
// ToCapability converts the capability response into the internal ipam driver capaility structure
29+
func (capRes GetCapabilityResponse) ToCapability() *ipamapi.Capability {
30+
return &ipamapi.Capability{RequiresMACAddress: capRes.RequiresMACAddress}
31+
}
32+
2033
// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
2134
type GetAddressSpacesResponse struct {
2235
Response

libnetwork/ipams/remote/remote.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,17 @@ func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
3030
// Init registers a remote ipam when its plugin is activated
3131
func Init(cb ipamapi.Callback, l, g interface{}) error {
3232
plugins.Handle(ipamapi.PluginEndpointType, func(name string, client *plugins.Client) {
33-
if err := cb.RegisterIpamDriver(name, newAllocator(name, client)); err != nil {
34-
log.Errorf("error registering remote ipam %s due to %v", name, err)
33+
a := newAllocator(name, client)
34+
if cps, err := a.(*allocator).getCapabilities(); err == nil {
35+
if err := cb.RegisterIpamDriverWithCapabilities(name, a, cps); err != nil {
36+
log.Errorf("error registering remote ipam driver %s due to %v", name, err)
37+
}
38+
} else {
39+
log.Infof("remote ipam driver %s does not support capabilities", name)
40+
log.Debug(err)
41+
if err := cb.RegisterIpamDriver(name, a); err != nil {
42+
log.Errorf("error registering remote ipam driver %s due to %v", name, err)
43+
}
3544
}
3645
})
3746
return nil
@@ -49,6 +58,14 @@ func (a *allocator) call(methodName string, arg interface{}, retVal PluginRespon
4958
return nil
5059
}
5160

61+
func (a *allocator) getCapabilities() (*ipamapi.Capability, error) {
62+
var res api.GetCapabilityResponse
63+
if err := a.call("GetCapabilities", nil, &res); err != nil {
64+
return nil, err
65+
}
66+
return res.ToCapability(), nil
67+
}
68+
5269
// GetDefaultAddressSpaces returns the local and global default address spaces
5370
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
5471
res := &api.GetAddressSpacesResponse{}

libnetwork/ipams/remote/remote_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,53 @@ func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
6161
}
6262
}
6363

64+
func TestGetCapabilities(t *testing.T) {
65+
var plugin = "test-ipam-driver-capabilities"
66+
67+
mux := http.NewServeMux()
68+
defer setupPlugin(t, plugin, mux)()
69+
70+
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
71+
return map[string]interface{}{
72+
"RequiresMACAddress": true,
73+
}
74+
})
75+
76+
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
81+
d := newAllocator(plugin, p.Client)
82+
83+
caps, err := d.(*allocator).getCapabilities()
84+
if err != nil {
85+
t.Fatal(err)
86+
}
87+
88+
if !caps.RequiresMACAddress {
89+
t.Fatalf("Unexpected capability: %v", caps)
90+
}
91+
}
92+
93+
func TestGetCapabilitiesFromLegacyDriver(t *testing.T) {
94+
var plugin = "test-ipam-legacy-driver"
95+
96+
mux := http.NewServeMux()
97+
defer setupPlugin(t, plugin, mux)()
98+
99+
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
100+
if err != nil {
101+
t.Fatal(err)
102+
}
103+
104+
d := newAllocator(plugin, p.Client)
105+
106+
if _, err := d.(*allocator).getCapabilities(); err == nil {
107+
t.Fatalf("Expected error, but got Success %v", err)
108+
}
109+
}
110+
64111
func TestGetDefaultAddressSpaces(t *testing.T) {
65112
var plugin = "test-ipam-driver-addr-spaces"
66113

libnetwork/libnetwork_internal_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,30 @@ func TestDriverRegistration(t *testing.T) {
3434
}
3535
}
3636

37+
func TestIpamDriverRegistration(t *testing.T) {
38+
c, err := New()
39+
if err != nil {
40+
t.Fatal(err)
41+
}
42+
defer c.Stop()
43+
44+
err = c.(*controller).RegisterIpamDriver("", nil)
45+
if err == nil {
46+
t.Fatalf("Expected failure, but suceeded")
47+
}
48+
if _, ok := err.(types.BadRequestError); !ok {
49+
t.Fatalf("Failed for unexpected reason: %v", err)
50+
}
51+
52+
err = c.(*controller).RegisterIpamDriver(ipamapi.DefaultIPAM, nil)
53+
if err == nil {
54+
t.Fatalf("Expected failure, but suceeded")
55+
}
56+
if _, ok := err.(types.ForbiddenError); !ok {
57+
t.Fatalf("Failed for unexpected reason: %v", err)
58+
}
59+
}
60+
3761
func TestNetworkMarshalling(t *testing.T) {
3862
n := &network{
3963
name: "Miao",

libnetwork/network.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/docker/libnetwork/etchosts"
1717
"github.com/docker/libnetwork/ipamapi"
1818
"github.com/docker/libnetwork/netlabel"
19+
"github.com/docker/libnetwork/netutils"
1920
"github.com/docker/libnetwork/options"
2021
"github.com/docker/libnetwork/types"
2122
)
@@ -678,7 +679,22 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
678679
}
679680
}
680681

681-
if err = ep.assignAddress(true, !n.postIPv6); err != nil {
682+
ipam, err := n.getController().getIPAM(n.ipamType)
683+
if err != nil {
684+
return nil, err
685+
}
686+
687+
if ipam.capability.RequiresMACAddress {
688+
if ep.iface.mac == nil {
689+
ep.iface.mac = netutils.GenerateRandomMAC()
690+
}
691+
if ep.ipamOptions == nil {
692+
ep.ipamOptions = make(map[string]string)
693+
}
694+
ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
695+
}
696+
697+
if err = ep.assignAddress(ipam.driver, true, !n.postIPv6); err != nil {
682698
return nil, err
683699
}
684700
defer func() {
@@ -698,7 +714,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
698714
}
699715
}()
700716

701-
if err = ep.assignAddress(false, n.postIPv6); err != nil {
717+
if err = ep.assignAddress(ipam.driver, false, n.postIPv6); err != nil {
702718
return nil, err
703719
}
704720

0 commit comments

Comments
 (0)