Browse Source

Add negotiation process for driver scope

Add one capability negotiation interaction after plugin handshake, use
this to determine plugin's capability instead of default "global" scope.

Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
Zhang Wei 9 years ago
parent
commit
304bfd6261

+ 10 - 0
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

+ 6 - 0
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

+ 27 - 3
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).

+ 106 - 9
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)
 	}
 }