Skip to content

Commit

Permalink
Call plugins with custom transports.
Browse files Browse the repository at this point in the history
Small refactor to be able to use custom transports
to call remote plugins.

Signed-off-by: David Calavera <[email protected]>
  • Loading branch information
calavera committed Mar 2, 2016
1 parent 331d2b3 commit 1a63023
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 22 deletions.
3 changes: 2 additions & 1 deletion pkg/authorization/authz_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import (
"testing"

"bytes"
"strings"

"github.com/docker/docker/pkg/plugins"
"github.com/docker/go-connections/tlsconfig"
"github.com/gorilla/mux"
"strings"
)

const pluginAddress = "authzplugin.sock"
Expand Down
54 changes: 37 additions & 17 deletions pkg/plugins/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import (
"io"
"io/ioutil"
"net/http"
"strings"
"net/url"
"time"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/plugins/transport"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
)

const (
versionMimetype = "application/vnd.docker.plugins.v1.2+json"
defaultTimeOut = 30
defaultTimeOut = 30
)

// NewClient creates a new plugin client (http).
Expand All @@ -29,23 +29,38 @@ func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
}
tr.TLSClientConfig = c

protoAndAddr := strings.Split(addr, "://")
if err := sockets.ConfigureTransport(tr, protoAndAddr[0], protoAndAddr[1]); err != nil {
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
socket := u.Host
if socket == "" {
// valid local socket addresses have the host empty.
socket = u.Path
}
if err := sockets.ConfigureTransport(tr, u.Scheme, socket); err != nil {
return nil, err
}
scheme := httpScheme(u)

scheme := protoAndAddr[0]
if scheme != "https" {
scheme = "http"
clientTransport := transport.NewHTTPTransport(tr, scheme, socket)
return NewClientWithTransport(clientTransport), nil
}

// NewClientWithTransport creates a new plugin client with a given transport.
func NewClientWithTransport(tr transport.Transport) *Client {
return &Client{
http: &http.Client{
Transport: tr,
},
requestFactory: tr,
}
return &Client{&http.Client{Transport: tr}, scheme, protoAndAddr[1]}, nil
}

// Client represents a plugin client.
type Client struct {
http *http.Client // http client to use
scheme string // scheme protocol of the plugin
addr string // http address of the plugin
http *http.Client // http client to use
requestFactory transport.RequestFactory
}

// Call calls the specified method with the specified arguments for the plugin.
Expand Down Expand Up @@ -94,13 +109,10 @@ func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{})
}

func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) {
req, err := http.NewRequest("POST", "/"+serviceMethod, data)
req, err := c.requestFactory.NewRequest(serviceMethod, data)
if err != nil {
return nil, err
}
req.Header.Add("Accept", versionMimetype)
req.URL.Scheme = c.scheme
req.URL.Host = c.addr

var retries int
start := time.Now()
Expand All @@ -117,7 +129,7 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool)
return nil, err
}
retries++
logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", req.URL, timeOff)
time.Sleep(timeOff)
continue
}
Expand Down Expand Up @@ -163,3 +175,11 @@ func backoff(retries int) time.Duration {
func abort(start time.Time, timeOff time.Duration) bool {
return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second
}

func httpScheme(u *url.URL) string {
scheme := u.Scheme
if scheme != "https" {
scheme = "http"
}
return scheme
}
15 changes: 11 additions & 4 deletions pkg/plugins/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"time"

"github.com/docker/docker/pkg/plugins/transport"
"github.com/docker/go-connections/tlsconfig"
)

Expand Down Expand Up @@ -48,7 +50,7 @@ func TestEchoInputOutput(t *testing.T) {
}

header := w.Header()
header.Set("Content-Type", versionMimetype)
header.Set("Content-Type", transport.VersionMimetype)

io.Copy(w, r.Body)
})
Expand Down Expand Up @@ -119,9 +121,14 @@ func TestClientScheme(t *testing.T) {
}

for addr, scheme := range cases {
c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true})
if c.scheme != scheme {
t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, c.scheme)
u, err := url.Parse(addr)
if err != nil {
t.Fatal(err)
}
s := httpScheme(u)

if s != scheme {
t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, s)
}
}
}
36 changes: 36 additions & 0 deletions pkg/plugins/transport/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package transport

import (
"io"
"net/http"
)

// httpTransport holds an http.RoundTripper
// and information about the scheme and address the transport
// sends request to.
type httpTransport struct {
http.RoundTripper
scheme string
addr string
}

// NewHTTPTransport creates a new httpTransport.
func NewHTTPTransport(r http.RoundTripper, scheme, addr string) Transport {
return httpTransport{
RoundTripper: r,
scheme: scheme,
addr: addr,
}
}

// NewRequest creates a new http.Request and sets the URL
// scheme and address with the transport's fields.
func (t httpTransport) NewRequest(path string, data io.Reader) (*http.Request, error) {
req, err := newHTTPRequest(path, data)
if err != nil {
return nil, err
}
req.URL.Scheme = t.scheme
req.URL.Host = t.addr
return req, nil
}
36 changes: 36 additions & 0 deletions pkg/plugins/transport/transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package transport

import (
"io"
"net/http"
"strings"
)

// VersionMimetype is the Content-Type the engine sends to plugins.
const VersionMimetype = "application/vnd.docker.plugins.v1.2+json"

// RequestFactory defines an interface that
// transports can implement to create new requests.
type RequestFactory interface {
NewRequest(path string, data io.Reader) (*http.Request, error)
}

// Transport defines an interface that plugin transports
// must implement.
type Transport interface {
http.RoundTripper
RequestFactory
}

// newHTTPRequest creates a new request with a path and a body.
func newHTTPRequest(path string, data io.Reader) (*http.Request, error) {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
req, err := http.NewRequest("POST", path, data)
if err != nil {
return nil, err
}
req.Header.Add("Accept", VersionMimetype)
return req, nil
}

0 comments on commit 1a63023

Please sign in to comment.