From cb8bbd3ded9a51b7416614a66217c5bc1d19820c Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Fri, 15 May 2015 19:53:40 -0700 Subject: [PATCH 1/2] Upgrading Godep to the Latest Docker Pacakages that brings in the Plugins infra Signed-off-by: Madhu Venugopal --- libnetwork/Godeps/Godeps.json | 35 ++--- .../docker/docker/pkg/ioutils/readers_test.go | 125 ++++++++++++++++++ .../docker/docker/pkg/ioutils/writeflusher.go | 47 +++++++ .../docker/docker/pkg/ioutils/writers_test.go | 24 ++++ .../docker/docker/pkg/mflag/flag.go | 2 +- .../docker/pkg/parsers/kernel/kernel.go | 2 + .../pkg/parsers/kernel/kernel_windows.go | 65 +++++++++ .../docker/docker/pkg/plugins/client.go | 100 ++++++++++++++ .../docker/docker/pkg/plugins/client_test.go | 63 +++++++++ .../docker/docker/pkg/plugins/discovery.go | 78 +++++++++++ .../docker/pkg/plugins/discovery_test.go | 108 +++++++++++++++ .../docker/docker/pkg/plugins/plugins.go | 100 ++++++++++++++ .../docker/pkg/reexec/command_unsupported.go | 2 +- .../docker/pkg/reexec/command_windows.go | 14 ++ .../docker/docker/pkg/stringid/stringid.go | 16 ++- .../docker/pkg/stringid/stringid_test.go | 23 +++- 16 files changed, 783 insertions(+), 21 deletions(-) create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go create mode 100644 libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go diff --git a/libnetwork/Godeps/Godeps.json b/libnetwork/Godeps/Godeps.json index 5a0e638ceb..dc6fc525e3 100644 --- a/libnetwork/Godeps/Godeps.json +++ b/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", diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go index 0af978e068..b4cbfd95f6 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go +++ b/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 diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go new file mode 100644 index 0000000000..25095474df --- /dev/null +++ b/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} +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go index 80d7f7f795..564b1cd4f5 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go +++ b/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'}) diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go index f0d20d99b0..11dfb75d8c 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go +++ b/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) } diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go index 70d09003a3..5f7930684a 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go +++ b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel.go @@ -1,3 +1,5 @@ +// +build !windows + package kernel import ( diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go new file mode 100644 index 0000000000..399d63e5f0 --- /dev/null +++ b/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 +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go new file mode 100644 index 0000000000..00ca105cd2 --- /dev/null +++ b/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 + } +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go new file mode 100644 index 0000000000..b414ecb5d9 --- /dev/null +++ b/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) + } +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go new file mode 100644 index 0000000000..3a42ba6d17 --- /dev/null +++ b/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 +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go new file mode 100644 index 0000000000..b6e66e289c --- /dev/null +++ b/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) + } +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go new file mode 100644 index 0000000000..47519486bd --- /dev/null +++ b/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 +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go index a579318e82..4adcd8f13e 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go +++ b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!windows package reexec diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go new file mode 100644 index 0000000000..124d42fc62 --- /dev/null +++ b/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, + } +} diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go index bf39df9b73..6a683b686a 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go +++ b/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 diff --git a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go b/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go index 21f8f8a2fb..bcb1365495 100644 --- a/libnetwork/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go +++ b/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) + } +} From 80ca3c23309dcdc84f9cd29737291dfc122a8885 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Fri, 15 May 2015 18:14:36 -0700 Subject: [PATCH 2/2] Remote Driver integration with Plugin Framework This commit brings in Remote driver integrated with the newly introduced Plugin framework as a Docker Package. The Plugin framework is designed as a Package and has no runtime dependancy on Docker platform. It stands on its own and is a good candidate for getting the remote drivers hooked to libnetwork Signed-off-by: Madhu Venugopal --- libnetwork/controller.go | 23 +++++- libnetwork/driverapi/driverapi.go | 3 + libnetwork/drivers/remote/driver.go | 17 +++- libnetwork/libnetwork_test.go | 121 +++++++++++++++++++++++----- 4 files changed, 141 insertions(+), 23 deletions(-) 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