github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/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 args != nil {
    64  		if err := json.NewEncoder(&buf).Encode(args); err != nil {
    65  			return err
    66  		}
    67  	}
    68  	body, err := c.callWithRetry(serviceMethod, &buf, true)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	defer body.Close()
    73  	if ret != nil {
    74  		if err := json.NewDecoder(body).Decode(&ret); err != nil {
    75  			logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
    76  			return err
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  // Stream calls the specified method with the specified arguments for the plugin and returns the response body
    83  func (c *Client) Stream(serviceMethod string, args interface{}) (io.ReadCloser, error) {
    84  	var buf bytes.Buffer
    85  	if err := json.NewEncoder(&buf).Encode(args); err != nil {
    86  		return nil, err
    87  	}
    88  	return c.callWithRetry(serviceMethod, &buf, true)
    89  }
    90  
    91  // SendFile calls the specified method, and passes through the IO stream
    92  func (c *Client) SendFile(serviceMethod string, data io.Reader, ret interface{}) error {
    93  	body, err := c.callWithRetry(serviceMethod, data, true)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if err := json.NewDecoder(body).Decode(&ret); err != nil {
    98  		logrus.Errorf("%s: error reading plugin resp: %v", serviceMethod, err)
    99  		return err
   100  	}
   101  	return nil
   102  }
   103  
   104  func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) (io.ReadCloser, error) {
   105  	req, err := http.NewRequest("POST", "/"+serviceMethod, data)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	req.Header.Add("Accept", versionMimetype)
   110  	req.URL.Scheme = c.scheme
   111  	req.URL.Host = c.addr
   112  
   113  	var retries int
   114  	start := time.Now()
   115  
   116  	for {
   117  		resp, err := c.http.Do(req)
   118  		if err != nil {
   119  			if !retry {
   120  				return nil, err
   121  			}
   122  
   123  			timeOff := backoff(retries)
   124  			if abort(start, timeOff) {
   125  				return nil, err
   126  			}
   127  			retries++
   128  			logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
   129  			time.Sleep(timeOff)
   130  			continue
   131  		}
   132  
   133  		if resp.StatusCode != http.StatusOK {
   134  			b, err := ioutil.ReadAll(resp.Body)
   135  			if err != nil {
   136  				return nil, &remoteError{method: serviceMethod, err: err.Error()}
   137  			}
   138  
   139  			// Plugins' Response(s) should have an Err field indicating what went
   140  			// wrong. Try to unmarshal into ResponseErr. Otherwise fallback to just
   141  			// return the string(body)
   142  			type responseErr struct {
   143  				Err string
   144  			}
   145  			remoteErr := responseErr{}
   146  			if err := json.Unmarshal(b, &remoteErr); err != nil {
   147  				return nil, &remoteError{method: serviceMethod, err: err.Error()}
   148  			}
   149  			if remoteErr.Err != "" {
   150  				return nil, &remoteError{method: serviceMethod, err: remoteErr.Err}
   151  			}
   152  			// old way...
   153  			return nil, &remoteError{method: serviceMethod, err: string(b)}
   154  		}
   155  		return resp.Body, nil
   156  	}
   157  }
   158  
   159  func backoff(retries int) time.Duration {
   160  	b, max := 1, defaultTimeOut
   161  	for b < max && retries > 0 {
   162  		b *= 2
   163  		retries--
   164  	}
   165  	if b > max {
   166  		b = max
   167  	}
   168  	return time.Duration(b) * time.Second
   169  }
   170  
   171  func abort(start time.Time, timeOff time.Duration) bool {
   172  	return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second
   173  }