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