diff --git a/libnetwork/controller.go b/libnetwork/controller.go index 7f9ad28412..8b4d48e861 100644 --- a/libnetwork/controller.go +++ b/libnetwork/controller.go @@ -48,6 +48,7 @@ package libnetwork import ( "sync" + "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/stringid" "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/sandbox" @@ -140,7 +141,11 @@ func (c *controller) NewNetwork(networkType, name string, options ...NetworkOpti d, ok := c.drivers[networkType] c.Unlock() if !ok { - return nil, ErrInvalidNetworkDriver + var err error + d, err = c.loadDriver(networkType) + if err != nil { + return nil, err + } } // Check if a network already exists with the specified network name @@ -275,3 +280,19 @@ func (c *controller) sandboxGet(key string) sandbox.Sandbox { return sData.sandbox } + +func (c *controller) loadDriver(networkType string) (driverapi.Driver, error) { + // Plugins pkg performs lazy loading of plugins that acts as remote drivers. + // As per the design, this Get call will result in remote driver discovery if there is a corresponding plugin available. + _, err := plugins.Get(networkType, driverapi.NetworkPluginEndpointType) + if err != nil { + return nil, err + } + c.Lock() + defer c.Unlock() + d, ok := c.drivers[networkType] + if !ok { + return nil, ErrInvalidNetworkDriver + } + return d, nil +} diff --git a/libnetwork/driverapi/driverapi.go b/libnetwork/driverapi/driverapi.go index 50c39279e4..45e4611ac9 100644 --- a/libnetwork/driverapi/driverapi.go +++ b/libnetwork/driverapi/driverapi.go @@ -19,6 +19,9 @@ var ( ErrNotImplemented = errors.New("The API is not implemented yet") ) +// NetworkPluginEndpointType represents the Endpoint Type used by Plugin system +const NetworkPluginEndpointType = "NetworkDriver" + // Driver is an interface that every plugin driver needs to implement. type Driver interface { // Push driver specific config to the driver diff --git a/libnetwork/drivers/remote/driver.go b/libnetwork/drivers/remote/driver.go index 9ff7c37cd6..33664aa947 100644 --- a/libnetwork/drivers/remote/driver.go +++ b/libnetwork/drivers/remote/driver.go @@ -3,6 +3,8 @@ package remote import ( "errors" + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/plugins" "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/sandbox" "github.com/docker/libnetwork/types" @@ -10,13 +12,22 @@ import ( var errNoCallback = errors.New("No Callback handler registered with Driver") -const remoteNetworkType = "remote" - type driver struct { + endpoint *plugins.Client + networkType string } // Init does the necessary work to register remote drivers func Init(dc driverapi.DriverCallback) error { + plugins.Handle(driverapi.NetworkPluginEndpointType, func(name string, client *plugins.Client) { + + // TODO : Handhake with the Remote Plugin goes here + + newDriver := &driver{networkType: name, endpoint: client} + if err := dc.RegisterDriver(name, newDriver); err != nil { + log.Errorf("Error registering Driver for %s due to %v", name, err) + } + }) return nil } @@ -55,5 +66,5 @@ func (d *driver) Leave(nid, eid types.UUID, options map[string]interface{}) erro } func (d *driver) Type() string { - return remoteNetworkType + return d.networkType } diff --git a/libnetwork/libnetwork_test.go b/libnetwork/libnetwork_test.go index e0bb040090..bbb09fbf75 100644 --- a/libnetwork/libnetwork_test.go +++ b/libnetwork/libnetwork_test.go @@ -6,6 +6,8 @@ import ( "fmt" "io/ioutil" "net" + "net/http" + "net/http/httptest" "os" "runtime" "strconv" @@ -13,8 +15,10 @@ import ( "testing" log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/reexec" "github.com/docker/libnetwork" + "github.com/docker/libnetwork/driverapi" "github.com/docker/libnetwork/netlabel" "github.com/docker/libnetwork/netutils" "github.com/docker/libnetwork/options" @@ -226,7 +230,7 @@ func TestUnknownDriver(t *testing.T) { } } -func TestNilDriver(t *testing.T) { +func TestNilRemoteDriver(t *testing.T) { controller, err := libnetwork.New() if err != nil { t.Fatal(err) @@ -238,24 +242,7 @@ func TestNilDriver(t *testing.T) { t.Fatal("Expected to fail. But instead succeeded") } - if err != libnetwork.ErrInvalidNetworkDriver { - t.Fatalf("Did not fail with expected error. Actual error: %v", err) - } -} - -func TestNoInitDriver(t *testing.T) { - controller, err := libnetwork.New() - if err != nil { - t.Fatal(err) - } - - _, err = controller.NewNetwork("ppp", "dummy", - libnetwork.NetworkOptionGeneric(getEmptyGenericOption())) - if err == nil { - t.Fatal("Expected to fail. But instead succeeded") - } - - if err != libnetwork.ErrInvalidNetworkDriver { + if err != plugins.ErrNotFound { t.Fatalf("Did not fail with expected error. Actual error: %v", err) } } @@ -1134,6 +1121,102 @@ func TestResolvConf(t *testing.T) { } } +func TestInvalidRemoteDriver(t *testing.T) { + if !netutils.IsRunningInContainer() { + t.Skip("Skipping test when not running inside a Container") + } + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + if server == nil { + t.Fatal("Failed to start a HTTP Server") + } + defer server.Close() + + type pluginRequest struct { + name string + } + + mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + fmt.Fprintln(w, `{"Implements": ["InvalidDriver"]}`) + }) + + if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { + t.Fatal(err) + } + defer func() { + if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil { + t.Fatal(err) + } + }() + + if err := ioutil.WriteFile("/usr/share/docker/plugins/invalid-network-driver.spec", []byte(server.URL), 0644); err != nil { + t.Fatal(err) + } + + controller, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + + _, err = controller.NewNetwork("invalid-network-driver", "dummy", + libnetwork.NetworkOptionGeneric(getEmptyGenericOption())) + if err == nil { + t.Fatal("Expected to fail. But instead succeeded") + } + + if err != plugins.ErrNotImplements { + t.Fatalf("Did not fail with expected error. Actual error: %v", err) + } +} + +func TestValidRemoteDriver(t *testing.T) { + if !netutils.IsRunningInContainer() { + t.Skip("Skipping test when not running inside a Container") + } + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + if server == nil { + t.Fatal("Failed to start a HTTP Server") + } + defer server.Close() + + type pluginRequest struct { + name string + } + + mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") + fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType) + }) + + if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { + t.Fatal(err) + } + defer func() { + if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil { + t.Fatal(err) + } + }() + + if err := ioutil.WriteFile("/usr/share/docker/plugins/valid-network-driver.spec", []byte(server.URL), 0644); err != nil { + t.Fatal(err) + } + + controller, err := libnetwork.New() + if err != nil { + t.Fatal(err) + } + + _, err = controller.NewNetwork("valid-network-driver", "dummy", + libnetwork.NetworkOptionGeneric(getEmptyGenericOption())) + if err != nil && err != driverapi.ErrNotImplemented { + t.Fatal(err) + } +} + var ( once sync.Once ctrlr libnetwork.NetworkController