Explorar el Código

Merge pull request #159 from mavenugo/net-plugin

Libnetwork Integration with Plugin and Remote Driver Backend
Jana Radhakrishnan hace 10 años
padre
commit
6429fcc954
Se han modificado 20 ficheros con 924 adiciones y 44 borrados
  1. 20 15
      libnetwork/Godeps/Godeps.json
  2. 125 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go
  3. 47 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go
  4. 24 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go
  5. 1 1
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go
  6. 2 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go
  7. 65 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go
  8. 100 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go
  9. 63 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go
  10. 78 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go
  11. 108 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go
  12. 100 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go
  13. 1 1
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go
  14. 14 0
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go
  15. 13 3
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go
  16. 22 1
      libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go
  17. 22 1
      libnetwork/controller.go
  18. 3 0
      libnetwork/driverapi/driverapi.go
  19. 14 3
      libnetwork/drivers/remote/driver.go
  20. 102 19
      libnetwork/libnetwork_test.go

+ 20 - 15
libnetwork/Godeps/Godeps.json

@@ -1,6 +1,6 @@
 {
 	"ImportPath": "github.com/docker/libnetwork",
-	"GoVersion": "go1.4.2",
+	"GoVersion": "go1.4.1",
 	"Packages": [
 		"./..."
 	],
@@ -12,38 +12,43 @@
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/homedir",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/ioutils",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/mflag",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/parsers/kernel",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
+		},
+		{
+			"ImportPath": "github.com/docker/docker/pkg/plugins",
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/proxy",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/reexec",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/docker/pkg/stringid",
-			"Comment": "v1.4.1-3152-g3e85803",
-			"Rev": "3e85803f311c3883a9b395ad046c894ea255e9be"
+			"Comment": "v1.4.1-3479-ga9172f5",
+			"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
 		},
 		{
 			"ImportPath": "github.com/docker/libcontainer/user",

+ 125 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go

@@ -2,11 +2,92 @@ package ioutils
 
 import (
 	"bytes"
+	"fmt"
 	"io"
 	"io/ioutil"
+	"strings"
 	"testing"
 )
 
+// Implement io.Reader
+type errorReader struct{}
+
+func (r *errorReader) Read(p []byte) (int, error) {
+	return 0, fmt.Errorf("Error reader always fail.")
+}
+
+func TestReadCloserWrapperClose(t *testing.T) {
+	reader := strings.NewReader("A string reader")
+	wrapper := NewReadCloserWrapper(reader, func() error {
+		return fmt.Errorf("This will be called when closing")
+	})
+	err := wrapper.Close()
+	if err == nil || !strings.Contains(err.Error(), "This will be called when closing") {
+		t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.")
+	}
+}
+
+func TestReaderErrWrapperReadOnError(t *testing.T) {
+	called := false
+	reader := &errorReader{}
+	wrapper := NewReaderErrWrapper(reader, func() {
+		called = true
+	})
+	_, err := wrapper.Read([]byte{})
+	if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") {
+		t.Fatalf("readErrWrapper should returned an error")
+	}
+	if !called {
+		t.Fatalf("readErrWrapper should have call the anonymous function on failure")
+	}
+}
+
+func TestReaderErrWrapperRead(t *testing.T) {
+	called := false
+	reader := strings.NewReader("a string reader.")
+	wrapper := NewReaderErrWrapper(reader, func() {
+		called = true // Should not be called
+	})
+	// Read 20 byte (should be ok with the string above)
+	num, err := wrapper.Read(make([]byte, 20))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if num != 16 {
+		t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num)
+	}
+}
+
+func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) {
+	reader, writer := io.Pipe()
+
+	drainBuffer := make([]byte, 1024)
+	buffer := bytes.Buffer{}
+	bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, &buffer)
+
+	// Write everything down to a Pipe
+	// Usually, a pipe should block but because of the buffered reader,
+	// the writes will go through
+	done := make(chan bool)
+	go func() {
+		writer.Write([]byte("hello world"))
+		writer.Close()
+		done <- true
+	}()
+
+	// Drain the reader *after* everything has been written, just to verify
+	// it is indeed buffering
+	<-done
+
+	output, err := ioutil.ReadAll(bufreader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(output, []byte("hello world")) {
+		t.Error(string(output))
+	}
+}
+
 func TestBufReader(t *testing.T) {
 	reader, writer := io.Pipe()
 	bufreader := NewBufReader(reader)
@@ -33,6 +114,50 @@ func TestBufReader(t *testing.T) {
 	}
 }
 
+func TestBufReaderCloseWithNonReaderCloser(t *testing.T) {
+	reader := strings.NewReader("buffer")
+	bufreader := NewBufReader(reader)
+
+	if err := bufreader.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+}
+
+// implements io.ReadCloser
+type simpleReaderCloser struct{}
+
+func (r *simpleReaderCloser) Read(p []byte) (n int, err error) {
+	return 0, nil
+}
+
+func (r *simpleReaderCloser) Close() error {
+	return nil
+}
+
+func TestBufReaderCloseWithReaderCloser(t *testing.T) {
+	reader := &simpleReaderCloser{}
+	bufreader := NewBufReader(reader)
+
+	err := bufreader.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+}
+
+func TestHashData(t *testing.T) {
+	reader := strings.NewReader("hash-me")
+	actual, err := HashData(reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa"
+	if actual != expected {
+		t.Fatalf("Expecting %s, got %s", expected, actual)
+	}
+}
+
 type repeatedReader struct {
 	readCount int
 	maxReads  int

+ 47 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go

@@ -0,0 +1,47 @@
+package ioutils
+
+import (
+	"io"
+	"net/http"
+	"sync"
+)
+
+type WriteFlusher struct {
+	sync.Mutex
+	w       io.Writer
+	flusher http.Flusher
+	flushed bool
+}
+
+func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
+	wf.Lock()
+	defer wf.Unlock()
+	n, err = wf.w.Write(b)
+	wf.flushed = true
+	wf.flusher.Flush()
+	return n, err
+}
+
+// Flush the stream immediately.
+func (wf *WriteFlusher) Flush() {
+	wf.Lock()
+	defer wf.Unlock()
+	wf.flushed = true
+	wf.flusher.Flush()
+}
+
+func (wf *WriteFlusher) Flushed() bool {
+	wf.Lock()
+	defer wf.Unlock()
+	return wf.flushed
+}
+
+func NewWriteFlusher(w io.Writer) *WriteFlusher {
+	var flusher http.Flusher
+	if f, ok := w.(http.Flusher); ok {
+		flusher = f
+	} else {
+		flusher = &NopFlusher{}
+	}
+	return &WriteFlusher{w: w, flusher: flusher}
+}

+ 24 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go

@@ -6,6 +6,30 @@ import (
 	"testing"
 )
 
+func TestWriteCloserWrapperClose(t *testing.T) {
+	called := false
+	writer := bytes.NewBuffer([]byte{})
+	wrapper := NewWriteCloserWrapper(writer, func() error {
+		called = true
+		return nil
+	})
+	if err := wrapper.Close(); err != nil {
+		t.Fatal(err)
+	}
+	if !called {
+		t.Fatalf("writeCloserWrapper should have call the anonymous function.")
+	}
+}
+
+func TestNopWriteCloser(t *testing.T) {
+	writer := bytes.NewBuffer([]byte{})
+	wrapper := NopWriteCloser(writer)
+	if err := wrapper.Close(); err != nil {
+		t.Fatal("NopWriteCloser always return nil on Close.")
+	}
+
+}
+
 func TestNopWriter(t *testing.T) {
 	nw := &NopWriter{}
 	l, err := nw.Write([]byte{'c'})

+ 1 - 1
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go

@@ -1085,7 +1085,7 @@ func (cmd *FlagSet) ReportError(str string, withHelp bool) {
 			str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'"
 		}
 	}
-	fmt.Fprintf(cmd.Out(), "docker: %s.\n", str)
+	fmt.Fprintf(cmd.Out(), "docker: %s\n", str)
 	os.Exit(1)
 }
 

+ 2 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go

@@ -1,3 +1,5 @@
+// +build !windows
+
 package kernel
 
 import (

+ 65 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go

@@ -0,0 +1,65 @@
+package kernel
+
+import (
+	"fmt"
+	"syscall"
+	"unsafe"
+)
+
+type KernelVersionInfo struct {
+	kvi   string
+	major int
+	minor int
+	build int
+}
+
+func (k *KernelVersionInfo) String() string {
+	return fmt.Sprintf("%d.%d %d (%s)", k.major, k.minor, k.build, k.kvi)
+}
+
+func GetKernelVersion() (*KernelVersionInfo, error) {
+
+	var (
+		h         syscall.Handle
+		dwVersion uint32
+		err       error
+	)
+
+	KVI := &KernelVersionInfo{"Unknown", 0, 0, 0}
+
+	if err = syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
+		syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
+		0,
+		syscall.KEY_READ,
+		&h); err != nil {
+		return KVI, err
+	}
+	defer syscall.RegCloseKey(h)
+
+	var buf [1 << 10]uint16
+	var typ uint32
+	n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
+
+	if err = syscall.RegQueryValueEx(h,
+		syscall.StringToUTF16Ptr("BuildLabEx"),
+		nil,
+		&typ,
+		(*byte)(unsafe.Pointer(&buf[0])),
+		&n); err != nil {
+		return KVI, err
+	}
+
+	KVI.kvi = syscall.UTF16ToString(buf[:])
+
+	// Important - docker.exe MUST be manifested for this API to return
+	// the correct information.
+	if dwVersion, err = syscall.GetVersion(); err != nil {
+		return KVI, err
+	}
+
+	KVI.major = int(dwVersion & 0xFF)
+	KVI.minor = int((dwVersion & 0XFF00) >> 8)
+	KVI.build = int((dwVersion & 0xFFFF0000) >> 16)
+
+	return KVI, nil
+}

+ 100 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go

@@ -0,0 +1,100 @@
+package plugins
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"strings"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+)
+
+const (
+	versionMimetype = "application/vnd.docker.plugins.v1+json"
+	defaultTimeOut  = 30
+)
+
+func NewClient(addr string) *Client {
+	tr := &http.Transport{}
+	protoAndAddr := strings.Split(addr, "://")
+	configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
+	return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}
+}
+
+type Client struct {
+	http *http.Client
+	addr string
+}
+
+func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
+	var buf bytes.Buffer
+	if err := json.NewEncoder(&buf).Encode(args); err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
+	if err != nil {
+		return err
+	}
+	req.Header.Add("Accept", versionMimetype)
+	req.URL.Scheme = "http"
+	req.URL.Host = c.addr
+
+	var retries int
+	start := time.Now()
+
+	for {
+		resp, err := c.http.Do(req)
+		if err != nil {
+			timeOff := backoff(retries)
+			if timeOff+time.Since(start) > defaultTimeOut {
+				return err
+			}
+			retries++
+			logrus.Warn("Unable to connect to plugin: %s, retrying in %ds\n", c.addr, timeOff)
+			time.Sleep(timeOff)
+			continue
+		}
+
+		if resp.StatusCode != http.StatusOK {
+			remoteErr, err := ioutil.ReadAll(resp.Body)
+			if err != nil {
+				return nil
+			}
+			return fmt.Errorf("Plugin Error: %s", remoteErr)
+		}
+
+		return json.NewDecoder(resp.Body).Decode(&ret)
+	}
+}
+
+func backoff(retries int) time.Duration {
+	b, max := float64(1), float64(defaultTimeOut)
+	for b < max && retries > 0 {
+		b *= 2
+		retries--
+	}
+	if b > max {
+		b = max
+	}
+	return time.Duration(b)
+}
+
+func configureTCPTransport(tr *http.Transport, proto, addr string) {
+	// Why 32? See https://github.com/docker/docker/pull/8035.
+	timeout := 32 * time.Second
+	if proto == "unix" {
+		// No need for compression in local communications.
+		tr.DisableCompression = true
+		tr.Dial = func(_, _ string) (net.Conn, error) {
+			return net.DialTimeout(proto, addr, timeout)
+		}
+	} else {
+		tr.Proxy = http.ProxyFromEnvironment
+		tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+	}
+}

+ 63 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go

@@ -0,0 +1,63 @@
+package plugins
+
+import (
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"reflect"
+	"testing"
+)
+
+var (
+	mux    *http.ServeMux
+	server *httptest.Server
+)
+
+func setupRemotePluginServer() string {
+	mux = http.NewServeMux()
+	server = httptest.NewServer(mux)
+	return server.URL
+}
+
+func teardownRemotePluginServer() {
+	if server != nil {
+		server.Close()
+	}
+}
+
+func TestFailedConnection(t *testing.T) {
+	c := NewClient("tcp://127.0.0.1:1")
+	err := c.Call("Service.Method", nil, nil)
+	if err == nil {
+		t.Fatal("Unexpected successful connection")
+	}
+}
+
+func TestEchoInputOutput(t *testing.T) {
+	addr := setupRemotePluginServer()
+	defer teardownRemotePluginServer()
+
+	m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}}
+
+	mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) {
+		if r.Method != "POST" {
+			t.Fatalf("Expected POST, got %s\n", r.Method)
+		}
+
+		header := w.Header()
+		header.Set("Content-Type", versionMimetype)
+
+		io.Copy(w, r.Body)
+	})
+
+	c := NewClient(addr)
+	var output Manifest
+	err := c.Call("Test.Echo", m, &output)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(output, m) {
+		t.Fatalf("Expected %v, was %v\n", m, output)
+	}
+}

+ 78 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go

@@ -0,0 +1,78 @@
+package plugins
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/url"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+const defaultLocalRegistry = "/usr/share/docker/plugins"
+
+var (
+	ErrNotFound = errors.New("Plugin not found")
+)
+
+type Registry interface {
+	Plugins() ([]*Plugin, error)
+	Plugin(name string) (*Plugin, error)
+}
+
+type LocalRegistry struct {
+	path string
+}
+
+func newLocalRegistry(path string) *LocalRegistry {
+	if len(path) == 0 {
+		path = defaultLocalRegistry
+	}
+
+	return &LocalRegistry{path}
+}
+
+func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
+	filepath := filepath.Join(l.path, name)
+	specpath := filepath + ".spec"
+	if fi, err := os.Stat(specpath); err == nil {
+		return readPluginInfo(specpath, fi)
+	}
+	socketpath := filepath + ".sock"
+	if fi, err := os.Stat(socketpath); err == nil {
+		return readPluginInfo(socketpath, fi)
+	}
+	return nil, ErrNotFound
+}
+
+func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
+	name := strings.Split(fi.Name(), ".")[0]
+
+	if fi.Mode()&os.ModeSocket != 0 {
+		return &Plugin{
+			Name: name,
+			Addr: "unix://" + path,
+		}, nil
+	}
+
+	content, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, err
+	}
+	addr := strings.TrimSpace(string(content))
+
+	u, err := url.Parse(addr)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(u.Scheme) == 0 {
+		return nil, fmt.Errorf("Unknown protocol")
+	}
+
+	return &Plugin{
+		Name: name,
+		Addr: addr,
+	}, nil
+}

+ 108 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go

@@ -0,0 +1,108 @@
+package plugins
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"path"
+	"path/filepath"
+	"reflect"
+	"testing"
+)
+
+func TestUnknownLocalPath(t *testing.T) {
+	tmpdir, err := ioutil.TempDir("", "docker-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpdir)
+
+	l := newLocalRegistry(filepath.Join(tmpdir, "unknown"))
+	_, err = l.Plugin("foo")
+	if err == nil || err != ErrNotFound {
+		t.Fatalf("Expected error for unknown directory")
+	}
+}
+
+func TestLocalSocket(t *testing.T) {
+	tmpdir, err := ioutil.TempDir("", "docker-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpdir)
+	l, err := net.Listen("unix", filepath.Join(tmpdir, "echo.sock"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer l.Close()
+
+	r := newLocalRegistry(tmpdir)
+	p, err := r.Plugin("echo")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pp, err := r.Plugin("echo")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(p, pp) {
+		t.Fatalf("Expected %v, was %v\n", p, pp)
+	}
+
+	if p.Name != "echo" {
+		t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
+	}
+
+	addr := fmt.Sprintf("unix://%s/echo.sock", tmpdir)
+	if p.Addr != addr {
+		t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
+	}
+}
+
+func TestFileSpecPlugin(t *testing.T) {
+	tmpdir, err := ioutil.TempDir("", "docker-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cases := []struct {
+		path string
+		name string
+		addr string
+		fail bool
+	}{
+		{filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false},
+		{filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false},
+		{filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport
+	}
+
+	for _, c := range cases {
+		if err = os.MkdirAll(path.Dir(c.path), 0755); err != nil {
+			t.Fatal(err)
+		}
+		if err = ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
+			t.Fatal(err)
+		}
+
+		r := newLocalRegistry(tmpdir)
+		p, err := r.Plugin(c.name)
+		if c.fail && err == nil {
+			continue
+		}
+
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		if p.Name != c.name {
+			t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
+		}
+
+		if p.Addr != c.addr {
+			t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr)
+		}
+		os.Remove(c.path)
+	}
+}

+ 100 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go

@@ -0,0 +1,100 @@
+package plugins
+
+import (
+	"errors"
+	"sync"
+
+	"github.com/Sirupsen/logrus"
+)
+
+var (
+	ErrNotImplements = errors.New("Plugin does not implement the requested driver")
+)
+
+type plugins struct {
+	sync.Mutex
+	plugins map[string]*Plugin
+}
+
+var (
+	storage          = plugins{plugins: make(map[string]*Plugin)}
+	extpointHandlers = make(map[string]func(string, *Client))
+)
+
+type Manifest struct {
+	Implements []string
+}
+
+type Plugin struct {
+	Name     string
+	Addr     string
+	Client   *Client
+	Manifest *Manifest
+}
+
+func (p *Plugin) activate() error {
+	m := new(Manifest)
+	p.Client = NewClient(p.Addr)
+	err := p.Client.Call("Plugin.Activate", nil, m)
+	if err != nil {
+		return err
+	}
+
+	logrus.Debugf("%s's manifest: %v", p.Name, m)
+	p.Manifest = m
+	for _, iface := range m.Implements {
+		handler, handled := extpointHandlers[iface]
+		if !handled {
+			continue
+		}
+		handler(p.Name, p.Client)
+	}
+	return nil
+}
+
+func load(name string) (*Plugin, error) {
+	registry := newLocalRegistry("")
+	pl, err := registry.Plugin(name)
+	if err != nil {
+		return nil, err
+	}
+	if err := pl.activate(); err != nil {
+		return nil, err
+	}
+	return pl, nil
+}
+
+func get(name string) (*Plugin, error) {
+	storage.Lock()
+	defer storage.Unlock()
+	pl, ok := storage.plugins[name]
+	if ok {
+		return pl, nil
+	}
+	pl, err := load(name)
+	if err != nil {
+		return nil, err
+	}
+
+	logrus.Debugf("Plugin: %v", pl)
+	storage.plugins[name] = pl
+	return pl, nil
+}
+
+func Get(name, imp string) (*Plugin, error) {
+	pl, err := get(name)
+	if err != nil {
+		return nil, err
+	}
+	for _, driver := range pl.Manifest.Implements {
+		logrus.Debugf("%s implements: %s", name, driver)
+		if driver == imp {
+			return pl, nil
+		}
+	}
+	return nil, ErrNotImplements
+}
+
+func Handle(iface string, fn func(string, *Client)) {
+	extpointHandlers[iface] = fn
+}

+ 1 - 1
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go

@@ -1,4 +1,4 @@
-// +build !linux
+// +build !linux,!windows
 
 package reexec
 

+ 14 - 0
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go

@@ -0,0 +1,14 @@
+// +build windows
+
+package reexec
+
+import (
+	"os/exec"
+)
+
+func Command(args ...string) *exec.Cmd {
+	return &exec.Cmd{
+		Path: Self(),
+		Args: args,
+	}
+}

+ 13 - 3
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go

@@ -4,19 +4,29 @@ import (
 	"crypto/rand"
 	"encoding/hex"
 	"io"
+	"regexp"
 	"strconv"
 )
 
+const shortLen = 12
+
+var validShortID = regexp.MustCompile("^[a-z0-9]{12}$")
+
+// Determine if an arbitrary string *looks like* a short ID.
+func IsShortID(id string) bool {
+	return validShortID.MatchString(id)
+}
+
 // TruncateID returns a shorthand version of a string identifier for convenience.
 // A collision with other shorthands is very unlikely, but possible.
 // In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
 // will need to use a langer prefix, or the full-length Id.
 func TruncateID(id string) string {
-	shortLen := 12
+	trimTo := shortLen
 	if len(id) < shortLen {
-		shortLen = len(id)
+		trimTo = len(id)
 	}
-	return id[:shortLen]
+	return id[:trimTo]
 }
 
 // GenerateRandomID returns an unique id

+ 22 - 1
libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go

@@ -1,6 +1,9 @@
 package stringid
 
-import "testing"
+import (
+	"strings"
+	"testing"
+)
 
 func TestGenerateRandomID(t *testing.T) {
 	id := GenerateRandomID()
@@ -33,3 +36,21 @@ func TestShortenIdInvalid(t *testing.T) {
 		t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
 	}
 }
+
+func TestIsShortIDNonHex(t *testing.T) {
+	id := "some non-hex value"
+	if IsShortID(id) {
+		t.Fatalf("%s is not a short ID", id)
+	}
+}
+
+func TestIsShortIDNotCorrectSize(t *testing.T) {
+	id := strings.Repeat("a", shortLen+1)
+	if IsShortID(id) {
+		t.Fatalf("%s is not a short ID", id)
+	}
+	id = strings.Repeat("a", shortLen-1)
+	if IsShortID(id) {
+		t.Fatalf("%s is not a short ID", id)
+	}
+}

+ 22 - 1
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
+}

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

+ 14 - 3
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
 }

+ 102 - 19
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