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