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  }