client.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. package plugins
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/Sirupsen/logrus"
  11. "github.com/docker/docker/pkg/sockets"
  12. "github.com/docker/docker/pkg/tlsconfig"
  13. )
  14. const (
  15. versionMimetype = "application/vnd.docker.plugins.v1.1+json"
  16. defaultTimeOut = 30
  17. )
  18. type remoteError struct {
  19. method string
  20. err string
  21. }
  22. func (e *remoteError) Error() string {
  23. return fmt.Sprintf("Plugin Error: %s, %s", e.err, e.method)
  24. }
  25. // NewClient creates a new plugin client (http).
  26. func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
  27. tr := &http.Transport{}
  28. c, err := tlsconfig.Client(tlsConfig)
  29. if err != nil {
  30. return nil, err
  31. }
  32. tr.TLSClientConfig = c
  33. protoAndAddr := strings.Split(addr, "://")
  34. sockets.ConfigureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
  35. return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}, nil
  36. }
  37. // Client represents a plugin client.
  38. type Client struct {
  39. http *http.Client // http client to use
  40. addr string // http address of the plugin
  41. }
  42. // Call calls the specified method with the specified arguments for the plugin.
  43. // It will retry for 30 seconds if a failure occurs when calling.
  44. func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
  45. return c.callWithRetry(serviceMethod, args, ret, true)
  46. }
  47. func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error {
  48. var buf bytes.Buffer
  49. if err := json.NewEncoder(&buf).Encode(args); err != nil {
  50. return err
  51. }
  52. req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
  53. if err != nil {
  54. return err
  55. }
  56. req.Header.Add("Accept", versionMimetype)
  57. req.URL.Scheme = "http"
  58. req.URL.Host = c.addr
  59. var retries int
  60. start := time.Now()
  61. for {
  62. resp, err := c.http.Do(req)
  63. if err != nil {
  64. if !retry {
  65. return err
  66. }
  67. timeOff := backoff(retries)
  68. if abort(start, timeOff) {
  69. return err
  70. }
  71. retries++
  72. logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
  73. time.Sleep(timeOff)
  74. continue
  75. }
  76. defer resp.Body.Close()
  77. if resp.StatusCode != http.StatusOK {
  78. remoteErr, err := ioutil.ReadAll(resp.Body)
  79. if err != nil {
  80. return &remoteError{err.Error(), serviceMethod}
  81. }
  82. return &remoteError{string(remoteErr), serviceMethod}
  83. }
  84. return json.NewDecoder(resp.Body).Decode(&ret)
  85. }
  86. }
  87. func backoff(retries int) time.Duration {
  88. b, max := 1, defaultTimeOut
  89. for b < max && retries > 0 {
  90. b *= 2
  91. retries--
  92. }
  93. if b > max {
  94. b = max
  95. }
  96. return time.Duration(b) * time.Second
  97. }
  98. func abort(start time.Time, timeOff time.Duration) bool {
  99. return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second
  100. }