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