Browse Source

Merge pull request #159 from mavenugo/net-plugin

Libnetwork Integration with Plugin and Remote Driver Backend
Jana Radhakrishnan 10 năm trước cách đây
mục cha
commit
6429fcc954
20 tập tin đã thay đổi với 924 bổ sung44 xóa
  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