diff --git a/libnetwork/docs/remote.md b/libnetwork/docs/remote.md index 4916a9f3d2..317f7d8caa 100644 --- a/libnetwork/docs/remote.md +++ b/libnetwork/docs/remote.md @@ -49,6 +49,16 @@ When loaded, a remote driver process receives an HTTP POST on the URL `/Plugin.A Other entries in the list value are allowed; `"NetworkDriver"` indicates that the plugin should be registered with LibNetwork as a driver. +### Set capability + +After Handshake, the remote driver will receive another POST message to the URL `/NetworkDriver.GetCapabilities` with no payload. The driver's response should have the form: + +{ + "Scope": "local" +} + +Value of "Scope" should be either "local" or "global" which indicates the capability of remote driver, values beyond these will fail driver's registration and return an error to the caller. + ### Create network When the proxy is asked to create a network, the remote process shall receive a POST to the URL `/NetworkDriver.CreateNetwork` of the form diff --git a/libnetwork/drivers/remote/api/api.go b/libnetwork/drivers/remote/api/api.go index 2a0d297ce5..2d441ab7d2 100644 --- a/libnetwork/drivers/remote/api/api.go +++ b/libnetwork/drivers/remote/api/api.go @@ -16,6 +16,12 @@ func (r *Response) GetError() string { return r.Err } +// GetCapabilityResponse is the response of GetCapability request +type GetCapabilityResponse struct { + Response + Scope string +} + // CreateNetworkRequest requests a new network. type CreateNetworkRequest struct { // A network ID that remote plugins are expected to store for future diff --git a/libnetwork/drivers/remote/driver.go b/libnetwork/drivers/remote/driver.go index 88afd53903..4d1a134f09 100644 --- a/libnetwork/drivers/remote/driver.go +++ b/libnetwork/drivers/remote/driver.go @@ -28,16 +28,40 @@ func newDriver(name string, client *plugins.Client) driverapi.Driver { // plugin is activated. func Init(dc driverapi.DriverCallback) error { plugins.Handle(driverapi.NetworkPluginEndpointType, func(name string, client *plugins.Client) { - c := driverapi.Capability{ - Scope: driverapi.GlobalScope, + // negotiate driver capability with client + d := newDriver(name, client) + c, err := d.(*driver).getCapabilities() + if err != nil { + log.Errorf("error getting capability for %s due to %v", name, err) + return } - if err := dc.RegisterDriver(name, newDriver(name, client), c); err != nil { + if err = dc.RegisterDriver(name, d, *c); err != nil { log.Errorf("error registering driver for %s due to %v", name, err) } }) return nil } +// Get capability from client +func (d *driver) getCapabilities() (*driverapi.Capability, error) { + var capResp api.GetCapabilityResponse + if err := d.call("GetCapabilities", nil, &capResp); err != nil { + return nil, err + } + + c := &driverapi.Capability{} + switch capResp.Scope { + case "global": + c.Scope = driverapi.GlobalScope + case "local": + c.Scope = driverapi.LocalScope + default: + return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope) + } + + return c, nil +} + // Config is not implemented for remote drivers, since it is assumed // to be supplied to the remote process out-of-band (e.g., as command // line arguments). diff --git a/libnetwork/drivers/remote/driver_test.go b/libnetwork/drivers/remote/driver_test.go index eb1d506241..98d9bee074 100644 --- a/libnetwork/drivers/remote/driver_test.go +++ b/libnetwork/drivers/remote/driver_test.go @@ -153,6 +153,91 @@ func (test *testEndpoint) AddStaticRoute(destination *net.IPNet, routeType int, return nil } +func TestGetEmptyCapabilities(t *testing.T) { + var plugin = "test-net-driver-empty-cap" + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{} + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + + d := newDriver(plugin, p.Client) + if d.Type() != plugin { + t.Fatal("Driver type does not match that given") + } + + _, err = d.(*driver).getCapabilities() + if err == nil { + t.Fatal("There should be error reported when get empty capability") + } +} + +func TestGetExtraCapabilities(t *testing.T) { + var plugin = "test-net-driver-extra-cap" + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{ + "Scope": "local", + "foo": "bar", + } + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + + d := newDriver(plugin, p.Client) + if d.Type() != plugin { + t.Fatal("Driver type does not match that given") + } + + c, err := d.(*driver).getCapabilities() + if err != nil { + t.Fatal(err) + } else if c.Scope != driverapi.LocalScope { + t.Fatalf("get capability '%s', expecting 'local'", c.Scope) + } +} + +func TestGetInvalidCapabilities(t *testing.T) { + var plugin = "test-net-driver-invalid-cap" + + mux := http.NewServeMux() + defer setupPlugin(t, plugin, mux)() + + handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{ + "Scope": "fake", + } + }) + + p, err := plugins.Get(plugin, driverapi.NetworkPluginEndpointType) + if err != nil { + t.Fatal(err) + } + + d := newDriver(plugin, p.Client) + if d.Type() != plugin { + t.Fatal("Driver type does not match that given") + } + + _, err = d.(*driver).getCapabilities() + if err == nil { + t.Fatal("There should be error reported when get invalid capability") + } +} + func TestRemoteDriver(t *testing.T) { var plugin = "test-net-driver" @@ -177,6 +262,11 @@ func TestRemoteDriver(t *testing.T) { var networkID string + handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} { + return map[string]interface{}{ + "Scope": "global", + } + }) handle(t, mux, "CreateNetwork", func(msg map[string]interface{}) interface{} { nid := msg["NetworkID"] var ok bool @@ -245,38 +335,45 @@ func TestRemoteDriver(t *testing.T) { t.Fatal(err) } - driver := newDriver(plugin, p.Client) - if driver.Type() != plugin { + d := newDriver(plugin, p.Client) + if d.Type() != plugin { t.Fatal("Driver type does not match that given") } + c, err := d.(*driver).getCapabilities() + if err != nil { + t.Fatal(err) + } else if c.Scope != driverapi.GlobalScope { + t.Fatalf("get capability '%s', expecting 'global'", c.Scope) + } + netID := "dummy-network" - err = driver.CreateNetwork(netID, map[string]interface{}{}) + err = d.CreateNetwork(netID, map[string]interface{}{}) if err != nil { t.Fatal(err) } endID := "dummy-endpoint" - err = driver.CreateEndpoint(netID, endID, ep, map[string]interface{}{}) + err = d.CreateEndpoint(netID, endID, ep, map[string]interface{}{}) if err != nil { t.Fatal(err) } joinOpts := map[string]interface{}{"foo": "fooValue"} - err = driver.Join(netID, endID, "sandbox-key", ep, joinOpts) + err = d.Join(netID, endID, "sandbox-key", ep, joinOpts) if err != nil { t.Fatal(err) } - if _, err = driver.EndpointOperInfo(netID, endID); err != nil { + if _, err = d.EndpointOperInfo(netID, endID); err != nil { t.Fatal(err) } - if err = driver.Leave(netID, endID); err != nil { + if err = d.Leave(netID, endID); err != nil { t.Fatal(err) } - if err = driver.DeleteEndpoint(netID, endID); err != nil { + if err = d.DeleteEndpoint(netID, endID); err != nil { t.Fatal(err) } - if err = driver.DeleteNetwork(netID); err != nil { + if err = d.DeleteNetwork(netID); err != nil { t.Fatal(err) } }