github.com/rsampaio/docker@v0.7.2-0.20150827203920-fdc73cc3fc31/pkg/plugins/client.go (about) 1 package plugins 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "strings" 10 "time" 11 12 "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/pkg/sockets" 14 "github.com/docker/docker/pkg/tlsconfig" 15 ) 16 17 const ( 18 versionMimetype = "application/vnd.docker.plugins.v1.1+json" 19 defaultTimeOut = 30 20 ) 21 22 type remoteError struct { 23 method string 24 err string 25 } 26 27 func (e *remoteError) Error() string { 28 return fmt.Sprintf("Plugin Error: %s, %s", e.err, e.method) 29 } 30 31 // NewClient creates a new plugin client (http). 32 func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) { 33 tr := &http.Transport{} 34 35 c, err := tlsconfig.Client(tlsConfig) 36 if err != nil { 37 return nil, err 38 } 39 tr.TLSClientConfig = c 40 41 protoAndAddr := strings.Split(addr, "://") 42 sockets.ConfigureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1]) 43 return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}, nil 44 } 45 46 // Client represents a plugin client. 47 type Client struct { 48 http *http.Client // http client to use 49 addr string // http address of the plugin 50 } 51 52 // Call calls the specified method with the specified arguments for the plugin. 53 // It will retry for 30 seconds if a failure occurs when calling. 54 func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error { 55 return c.callWithRetry(serviceMethod, args, ret, true) 56 } 57 58 func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error { 59 var buf bytes.Buffer 60 if err := json.NewEncoder(&buf).Encode(args); err != nil { 61 return err 62 } 63 64 req, err := http.NewRequest("POST", "/"+serviceMethod, &buf) 65 if err != nil { 66 return err 67 } 68 req.Header.Add("Accept", versionMimetype) 69 req.URL.Scheme = "http" 70 req.URL.Host = c.addr 71 72 var retries int 73 start := time.Now() 74 75 for { 76 resp, err := c.http.Do(req) 77 if err != nil { 78 if !retry { 79 return err 80 } 81 82 timeOff := backoff(retries) 83 if abort(start, timeOff) { 84 return err 85 } 86 retries++ 87 logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff) 88 time.Sleep(timeOff) 89 continue 90 } 91 92 defer resp.Body.Close() 93 if resp.StatusCode != http.StatusOK { 94 remoteErr, err := ioutil.ReadAll(resp.Body) 95 if err != nil { 96 return &remoteError{err.Error(), serviceMethod} 97 } 98 return &remoteError{string(remoteErr), serviceMethod} 99 } 100 101 return json.NewDecoder(resp.Body).Decode(&ret) 102 } 103 } 104 105 func backoff(retries int) time.Duration { 106 b, max := 1, defaultTimeOut 107 for b < max && retries > 0 { 108 b *= 2 109 retries-- 110 } 111 if b > max { 112 b = max 113 } 114 return time.Duration(b) * time.Second 115 } 116 117 func abort(start time.Time, timeOff time.Duration) bool { 118 return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second 119 }