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  }