From 0011d46ead7b658fdec9d010d86d1c11478dd0f0 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Wed, 6 May 2015 16:57:38 -0700 Subject: [PATCH] Remote Driver Registration This commits brings in a functionality for remote drivers to register with LibNetwork. The Built-In remote driver is responsible for the actual "remote" plugin to be made available. Having such a mechanism makes libnetwork core not dependent on any external plugin mechanism and also the Libnetwork NB apis are free of Driver interface. Signed-off-by: Madhu Venugopal --- controller.go | 20 +++++++- docs/remote.md | 2 +- driverapi/driverapi.go | 17 +++++++ drivers.go | 8 +-- drivers/bridge/bridge.go | 2 +- drivers/bridge/bridge_test.go | 14 +++--- drivers/bridge/network_test.go | 8 +-- drivers/bridge/port_mapping_test.go | 2 +- drivers/host/host.go | 2 +- drivers/null/null.go | 2 +- drivers/remote/driver.go | 75 +++++++++++++++++++++++++++++ drivers/remote/driver_test.go | 29 +++++++++++ libnetwork_internal_test.go | 23 +++++++++ 13 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 drivers/remote/driver.go create mode 100644 drivers/remote/driver_test.go create mode 100644 libnetwork_internal_test.go diff --git a/controller.go b/controller.go index 275994c..51c5a16 100644 --- a/controller.go +++ b/controller.go @@ -49,6 +49,7 @@ import ( "sync" "github.com/docker/docker/pkg/stringid" + "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/sandbox" "github.com/docker/libnetwork/types" ) @@ -98,22 +99,39 @@ type controller struct { // New creates a new instance of network controller. func New() NetworkController { - return &controller{networkTable{}, enumerateDrivers(), sandboxTable{}, sync.Mutex{}} + c := &controller{networks: networkTable{}, sandboxes: sandboxTable{}} + c.drivers = enumerateDrivers(c) + return c + } func (c *controller) ConfigureNetworkDriver(networkType string, options map[string]interface{}) error { + c.Lock() d, ok := c.drivers[networkType] + c.Unlock() if !ok { return NetworkTypeError(networkType) } return d.Config(options) } +func (c *controller) RegisterDriver(networkType string, driver driverapi.Driver) error { + c.Lock() + defer c.Unlock() + if _, ok := c.drivers[networkType]; ok { + return driverapi.ErrActiveRegistration(networkType) + } + c.drivers[networkType] = driver + return nil +} + // NewNetwork creates a new network of the specified network type. The options // are network specific and modeled in a generic way. func (c *controller) NewNetwork(networkType, name string, options ...NetworkOption) (Network, error) { // Check if a driver for the specified network type is available + c.Lock() d, ok := c.drivers[networkType] + c.Unlock() if !ok { return nil, ErrInvalidNetworkDriver } diff --git a/docs/remote.md b/docs/remote.md index 8f7b174..96e9172 100644 --- a/docs/remote.md +++ b/docs/remote.md @@ -5,7 +5,7 @@ Remote driver is a special built-in driver. This driver in itself doesn't provid ## LibNetwork Integration -When LibNetwork creates an instance of the Built-in `Remote` Driver via the `New()` function, it provides a `DriverCallback` which implements the `RegisterDynamicDriver()` to let the Built-in Remote Driver to register any of the `Dynamic` Drivers/Plugins with LibNetwork's `NetworkController` +When LibNetwork creates an instance of the Built-in `Remote` Driver via the `New()` function, it provides a `DriverCallback` which implements the `RegisterDriver()` to let the Built-in Remote Driver to register any of the `Dynamic` Drivers/Plugins with LibNetwork's `NetworkController` Refer to [Remote Driver Test](https://github.com/docker/libnetwork/blob/drivers/remote/driver_test.go) which provides an example of how the Built-In Remote driver can register any Dynamic driver with LibNetwork. diff --git a/driverapi/driverapi.go b/driverapi/driverapi.go index 5f5637b..a6b7bfc 100644 --- a/driverapi/driverapi.go +++ b/driverapi/driverapi.go @@ -2,6 +2,7 @@ package driverapi import ( "errors" + "fmt" "github.com/docker/libnetwork/sandbox" "github.com/docker/libnetwork/types" @@ -14,6 +15,8 @@ var ( ErrNoNetwork = errors.New("No network exists") // ErrNoEndpoint is returned if no endpoint with the specified id exists ErrNoEndpoint = errors.New("No endpoint exists") + // ErrNotImplemented is returned when a Driver has not implemented an API yet + ErrNotImplemented = errors.New("The API is not implemneted yet") ) // Driver is an interface that every plugin driver needs to implement. @@ -58,3 +61,17 @@ type Driver interface { type JoinInfo struct { HostsPath string } + +// ErrActiveRegistration represents an error when a driver is registered to a networkType that is previously registered +type ErrActiveRegistration string + +// Error interface for ErrActiveRegistration +func (ar ErrActiveRegistration) Error() string { + return fmt.Sprintf("Driver already registered for type %q", string(ar)) +} + +// DriverCallback provides a Callback interface for Drivers into LibNetwork +type DriverCallback interface { + // RegisterDriver provides a way for Remote drivers to dynamically register new NetworkType and associate with a driver instance + RegisterDriver(name string, driver Driver) error +} diff --git a/drivers.go b/drivers.go index da1da77..febc298 100644 --- a/drivers.go +++ b/drivers.go @@ -5,19 +5,21 @@ import ( "github.com/docker/libnetwork/drivers/bridge" "github.com/docker/libnetwork/drivers/host" "github.com/docker/libnetwork/drivers/null" + "github.com/docker/libnetwork/drivers/remote" ) type driverTable map[string]driverapi.Driver -func enumerateDrivers() driverTable { +func enumerateDrivers(dc driverapi.DriverCallback) driverTable { drivers := make(driverTable) - for _, fn := range [](func() (string, driverapi.Driver)){ + for _, fn := range [](func(driverapi.DriverCallback) (string, driverapi.Driver)){ bridge.New, host.New, null.New, + remote.New, } { - name, driver := fn() + name, driver := fn(dc) drivers[name] = driver } diff --git a/drivers/bridge/bridge.go b/drivers/bridge/bridge.go index c3cf1e1..353fd7f 100644 --- a/drivers/bridge/bridge.go +++ b/drivers/bridge/bridge.go @@ -93,7 +93,7 @@ func init() { } // New provides a new instance of bridge driver -func New() (string, driverapi.Driver) { +func New(dc driverapi.DriverCallback) (string, driverapi.Driver) { return networkType, &driver{} } diff --git a/drivers/bridge/bridge_test.go b/drivers/bridge/bridge_test.go index 04f9a19..2e3eceb 100644 --- a/drivers/bridge/bridge_test.go +++ b/drivers/bridge/bridge_test.go @@ -15,7 +15,7 @@ import ( func TestCreateFullOptions(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &Configuration{ EnableIPForwarding: true, @@ -46,7 +46,7 @@ func TestCreateFullOptions(t *testing.T) { func TestCreate(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{BridgeName: DefaultBridgeName} genericOption := make(map[string]interface{}) @@ -59,7 +59,7 @@ func TestCreate(t *testing.T) { func TestCreateFail(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{BridgeName: "dummy0"} genericOption := make(map[string]interface{}) @@ -73,7 +73,7 @@ func TestCreateFail(t *testing.T) { func TestQueryEndpointInfo(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{ BridgeName: DefaultBridgeName, @@ -130,7 +130,7 @@ func TestQueryEndpointInfo(t *testing.T) { func TestCreateLinkWithOptions(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{BridgeName: DefaultBridgeName} netOptions := make(map[string]interface{}) @@ -180,7 +180,7 @@ func getPortMapping() []netutils.PortBinding { func TestLinkContainers(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{ BridgeName: DefaultBridgeName, @@ -385,7 +385,7 @@ func TestValidateConfig(t *testing.T) { func TestSetDefaultGw(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) _, subnetv6, _ := net.ParseCIDR("2001:db8:ea9:9abc:b0c4::/80") gw4 := bridgeNetworks[0].IP.To4() diff --git a/drivers/bridge/network_test.go b/drivers/bridge/network_test.go index 73135b7..8f0b3a6 100644 --- a/drivers/bridge/network_test.go +++ b/drivers/bridge/network_test.go @@ -11,7 +11,7 @@ import ( func TestLinkCreate(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) dr := d.(*driver) mtu := 1490 @@ -97,7 +97,7 @@ func TestLinkCreate(t *testing.T) { func TestLinkCreateTwo(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{ BridgeName: DefaultBridgeName, @@ -127,7 +127,7 @@ func TestLinkCreateTwo(t *testing.T) { func TestLinkCreateNoEnableIPv6(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{ BridgeName: DefaultBridgeName} @@ -156,7 +156,7 @@ func TestLinkCreateNoEnableIPv6(t *testing.T) { func TestLinkDelete(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) config := &NetworkConfiguration{ BridgeName: DefaultBridgeName, diff --git a/drivers/bridge/port_mapping_test.go b/drivers/bridge/port_mapping_test.go index 23c7b05..93c5788 100644 --- a/drivers/bridge/port_mapping_test.go +++ b/drivers/bridge/port_mapping_test.go @@ -18,7 +18,7 @@ func TestMain(m *testing.M) { func TestPortMappingConfig(t *testing.T) { defer netutils.SetupTestNetNS(t)() - _, d := New() + _, d := New(nil) binding1 := netutils.PortBinding{Proto: netutils.UDP, Port: uint16(400), HostPort: uint16(54000)} binding2 := netutils.PortBinding{Proto: netutils.TCP, Port: uint16(500), HostPort: uint16(65000)} diff --git a/drivers/host/host.go b/drivers/host/host.go index c218fbd..cd39ad0 100644 --- a/drivers/host/host.go +++ b/drivers/host/host.go @@ -11,7 +11,7 @@ const networkType = "host" type driver struct{} // New provides a new instance of host driver -func New() (string, driverapi.Driver) { +func New(dc driverapi.DriverCallback) (string, driverapi.Driver) { return networkType, &driver{} } diff --git a/drivers/null/null.go b/drivers/null/null.go index 0f9cb2d..e2ba62b 100644 --- a/drivers/null/null.go +++ b/drivers/null/null.go @@ -11,7 +11,7 @@ const networkType = "null" type driver struct{} // New provides a new instance of null driver -func New() (string, driverapi.Driver) { +func New(dc driverapi.DriverCallback) (string, driverapi.Driver) { return networkType, &driver{} } diff --git a/drivers/remote/driver.go b/drivers/remote/driver.go new file mode 100644 index 0000000..ae1de3d --- /dev/null +++ b/drivers/remote/driver.go @@ -0,0 +1,75 @@ +package remote + +import ( + "errors" + + "github.com/docker/libnetwork/driverapi" + "github.com/docker/libnetwork/sandbox" + "github.com/docker/libnetwork/types" +) + +var errNoCallback = errors.New("No Callback handler registered with Driver") + +const networkType = "remote" + +type driver struct { + callback driverapi.DriverCallback +} + +// New instance of remote driver returned to LibNetwork +func New(dc driverapi.DriverCallback) (string, driverapi.Driver) { + d := &driver{} + d.callback = dc + return networkType, d +} + +// Internal Convenience method to register a remote driver. +// The implementation of this method will change based on the dynamic driver registration design +func (d *driver) registerRemoteDriver(networkType string) (driverapi.Driver, error) { + newDriver := &driver{} + if d.callback == nil { + return nil, errNoCallback + } + if err := d.callback.RegisterDriver(networkType, newDriver); err != nil { + return nil, err + } + return newDriver, nil +} + +func (d *driver) Config(option map[string]interface{}) error { + return driverapi.ErrNotImplemented +} + +func (d *driver) CreateNetwork(id types.UUID, option map[string]interface{}) error { + return driverapi.ErrNotImplemented +} + +func (d *driver) DeleteNetwork(nid types.UUID) error { + return driverapi.ErrNotImplemented +} + +func (d *driver) CreateEndpoint(nid, eid types.UUID, epOptions map[string]interface{}) (*sandbox.Info, error) { + return nil, driverapi.ErrNotImplemented +} + +func (d *driver) DeleteEndpoint(nid, eid types.UUID) error { + return driverapi.ErrNotImplemented +} + +func (d *driver) EndpointInfo(nid, eid types.UUID) (map[string]interface{}, error) { + return nil, driverapi.ErrNotImplemented +} + +// Join method is invoked when a Sandbox is attached to an endpoint. +func (d *driver) Join(nid, eid types.UUID, sboxKey string, options map[string]interface{}) (*driverapi.JoinInfo, error) { + return nil, driverapi.ErrNotImplemented +} + +// Leave method is invoked when a Sandbox detaches from an endpoint. +func (d *driver) Leave(nid, eid types.UUID, options map[string]interface{}) error { + return driverapi.ErrNotImplemented +} + +func (d *driver) Type() string { + return networkType +} diff --git a/drivers/remote/driver_test.go b/drivers/remote/driver_test.go new file mode 100644 index 0000000..42c7992 --- /dev/null +++ b/drivers/remote/driver_test.go @@ -0,0 +1,29 @@ +package remote + +import ( + "testing" + + "github.com/docker/libnetwork/driverapi" +) + +type testCallbackStruct struct { + networkType string +} + +func (t *testCallbackStruct) RegisterDriver(networkType string, driver driverapi.Driver) error { + t.networkType = networkType + return nil +} + +func TestCallback(t *testing.T) { + tc := &testCallbackStruct{} + _, d := New(tc) + expected := "test-dummy" + _, err := d.(*driver).registerRemoteDriver(expected) + if err != nil { + t.Fatalf("Remote Driver callback registration failed with Error : %v", err) + } + if tc.networkType != expected { + t.Fatal("Remote Driver Callback Registration failed") + } +} diff --git a/libnetwork_internal_test.go b/libnetwork_internal_test.go new file mode 100644 index 0000000..2eeda80 --- /dev/null +++ b/libnetwork_internal_test.go @@ -0,0 +1,23 @@ +package libnetwork + +import ( + "testing" + + "github.com/docker/libnetwork/driverapi" +) + +func TestDriverRegistration(t *testing.T) { + bridgeNetType := "bridge" + c := New() + err := c.(*controller).RegisterDriver(bridgeNetType, nil) + if err == nil { + t.Fatalf("Expecting the RegisterDriver to fail for %s", bridgeNetType) + } + if _, ok := err.(driverapi.ErrActiveRegistration); !ok { + t.Fatalf("Failed for unexpected reason: %v", err) + } + err = c.(*controller).RegisterDriver("test-dummy", nil) + if err != nil { + t.Fatalf("Test failed with an error %v", err) + } +}