Browse code

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>

Alessandro Boch authored on 2015/12/10 11:09:58
Showing 9 changed files
... ...
@@ -121,7 +121,8 @@ type driverData struct {
121 121
 }
122 122
 
123 123
 type ipamData struct {
124
-	driver ipamapi.Ipam
124
+	driver     ipamapi.Ipam
125
+	capability *ipamapi.Capability
125 126
 	// default address spaces are provided by ipam driver at registration time
126 127
 	defaultLocalAddressSpace, defaultGlobalAddressSpace string
127 128
 }
... ...
@@ -306,7 +307,7 @@ func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver,
306 306
 	return nil
307 307
 }
308 308
 
309
-func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
309
+func (c *controller) registerIpamDriver(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
310 310
 	if !config.IsValidName(name) {
311 311
 		return ErrInvalidName(name)
312 312
 	}
... ...
@@ -322,7 +323,7 @@ func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error
322 322
 		return types.InternalErrorf("ipam driver %q failed to return default address spaces: %v", name, err)
323 323
 	}
324 324
 	c.Lock()
325
-	c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS}
325
+	c.ipamDrivers[name] = &ipamData{driver: driver, defaultLocalAddressSpace: locAS, defaultGlobalAddressSpace: glbAS, capability: caps}
326 326
 	c.Unlock()
327 327
 
328 328
 	log.Debugf("Registering ipam driver: %q", name)
... ...
@@ -330,6 +331,14 @@ func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error
330 330
 	return nil
331 331
 }
332 332
 
333
+func (c *controller) RegisterIpamDriver(name string, driver ipamapi.Ipam) error {
334
+	return c.registerIpamDriver(name, driver, &ipamapi.Capability{})
335
+}
336
+
337
+func (c *controller) RegisterIpamDriverWithCapabilities(name string, driver ipamapi.Ipam, caps *ipamapi.Capability) error {
338
+	return c.registerIpamDriver(name, driver, caps)
339
+}
340
+
333 341
 // NewNetwork creates a new network of the specified network type. The options
334 342
 // are network specific and modeled in a generic way.
335 343
 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.
15 15
 
16 16
 ## Handshake
17 17
 
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.
19 19
 More detailed information can be found in the respective section in this document.
20 20
 
21 21
 ## Datastore Requirements
... ...
@@ -249,3 +249,27 @@ Where:
249 249
 * `PoolID` is the pool identifier
250 250
 * `Address` is the IP address to release
251 251
 
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"`.
252 276
\ No newline at end of file
... ...
@@ -748,11 +748,8 @@ func (ep *endpoint) DataScope() string {
748 748
 	return ep.getNetwork().DataScope()
749 749
 }
750 750
 
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
756 753
 
757 754
 	n := ep.getNetwork()
758 755
 	if n.Type() == "host" || n.Type() == "null" {
... ...
@@ -761,11 +758,6 @@ func (ep *endpoint) assignAddress(assignIPv4, assignIPv6 bool) error {
761 761
 
762 762
 	log.Debugf("Assigning addresses for endpoint %s's interface on network %s", ep.Name(), n.Name())
763 763
 
764
-	ipam, err = n.getController().getIpamDriver(n.ipamType)
765
-	if err != nil {
766
-		return err
767
-	}
768
-
769 764
 	if assignIPv4 {
770 765
 		if err = ep.assignAddressVersion(4, ipam); err != nil {
771 766
 			return err
... ...
@@ -22,8 +22,10 @@ const (
22 22
 
23 23
 // Callback provides a Callback interface for registering an IPAM instance into LibNetwork
24 24
 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
26 26
 	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
27 29
 }
28 30
 
29 31
 /**************
... ...
@@ -70,3 +72,8 @@ type Ipam interface {
70 70
 	// Release the address from the specified pool ID
71 71
 	ReleaseAddress(string, net.IP) error
72 72
 }
73
+
74
+// Capability represents the requirements and capabilities of the IPAM driver
75
+type Capability struct {
76
+	RequiresMACAddress bool
77
+}
... ...
@@ -2,6 +2,8 @@
2 2
 // messages between libnetwork and the remote ipam plugin
3 3
 package api
4 4
 
5
+import "github.com/docker/libnetwork/ipamapi"
6
+
5 7
 // Response is the basic response structure used in all responses
6 8
 type Response struct {
7 9
 	Error string
... ...
@@ -17,6 +19,17 @@ func (r *Response) GetError() string {
17 17
 	return r.Error
18 18
 }
19 19
 
20
+// GetCapabilityResponse is the response of GetCapability request
21
+type GetCapabilityResponse struct {
22
+	Response
23
+	RequiresMACAddress bool
24
+}
25
+
26
+// ToCapability converts the capability response into the internal ipam driver capaility structure
27
+func (capRes GetCapabilityResponse) ToCapability() *ipamapi.Capability {
28
+	return &ipamapi.Capability{RequiresMACAddress: capRes.RequiresMACAddress}
29
+}
30
+
20 31
 // GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
21 32
 type GetAddressSpacesResponse struct {
22 33
 	Response
... ...
@@ -30,8 +30,17 @@ func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
30 30
 // Init registers a remote ipam when its plugin is activated
31 31
 func Init(cb ipamapi.Callback, l, g interface{}) error {
32 32
 	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
+			}
35 44
 		}
36 45
 	})
37 46
 	return nil
... ...
@@ -49,6 +58,14 @@ func (a *allocator) call(methodName string, arg interface{}, retVal PluginRespon
49 49
 	return nil
50 50
 }
51 51
 
52
+func (a *allocator) getCapabilities() (*ipamapi.Capability, error) {
53
+	var res api.GetCapabilityResponse
54
+	if err := a.call("GetCapabilities", nil, &res); err != nil {
55
+		return nil, err
56
+	}
57
+	return res.ToCapability(), nil
58
+}
59
+
52 60
 // GetDefaultAddressSpaces returns the local and global default address spaces
53 61
 func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
54 62
 	res := &api.GetAddressSpacesResponse{}
... ...
@@ -61,6 +61,53 @@ func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
61 61
 	}
62 62
 }
63 63
 
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
+
64 111
 func TestGetDefaultAddressSpaces(t *testing.T) {
65 112
 	var plugin = "test-ipam-driver-addr-spaces"
66 113
 
... ...
@@ -34,6 +34,30 @@ func TestDriverRegistration(t *testing.T) {
34 34
 	}
35 35
 }
36 36
 
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
+
37 61
 func TestNetworkMarshalling(t *testing.T) {
38 62
 	n := &network{
39 63
 		name:        "Miao",
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/docker/libnetwork/etchosts"
17 17
 	"github.com/docker/libnetwork/ipamapi"
18 18
 	"github.com/docker/libnetwork/netlabel"
19
+	"github.com/docker/libnetwork/netutils"
19 20
 	"github.com/docker/libnetwork/options"
20 21
 	"github.com/docker/libnetwork/types"
21 22
 )
... ...
@@ -678,7 +679,22 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
678 678
 		}
679 679
 	}
680 680
 
681
-	if err = ep.assignAddress(true, !n.postIPv6); err != nil {
681
+	ipam, err := n.getController().getIPAM(n.ipamType)
682
+	if err != nil {
683
+		return nil, err
684
+	}
685
+
686
+	if ipam.capability.RequiresMACAddress {
687
+		if ep.iface.mac == nil {
688
+			ep.iface.mac = netutils.GenerateRandomMAC()
689
+		}
690
+		if ep.ipamOptions == nil {
691
+			ep.ipamOptions = make(map[string]string)
692
+		}
693
+		ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
694
+	}
695
+
696
+	if err = ep.assignAddress(ipam.driver, true, !n.postIPv6); err != nil {
682 697
 		return nil, err
683 698
 	}
684 699
 	defer func() {
... ...
@@ -698,7 +714,7 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
698 698
 		}
699 699
 	}()
700 700
 
701
-	if err = ep.assignAddress(false, n.postIPv6); err != nil {
701
+	if err = ep.assignAddress(ipam.driver, false, n.postIPv6); err != nil {
702 702
 		return nil, err
703 703
 	}
704 704