PageRenderTime 1339ms CodeModel.GetById 34ms RepoModel.GetById 10ms app.codeStats 0ms

/vendor/github.com/storageos/go-api/client.go

https://gitlab.com/unofficial-mirrors/kubernetes
Go | 483 lines | 380 code | 57 blank | 46 comment | 115 complexity | 515fc22a43446c3cf7a0c6445467ac8c MD5 | raw file
  1. package storageos
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/tls"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "github.com/storageos/go-api/netutil"
  10. "github.com/storageos/go-api/serror"
  11. "io"
  12. "io/ioutil"
  13. "net"
  14. "net/http"
  15. "net/url"
  16. "reflect"
  17. "strconv"
  18. "strings"
  19. "time"
  20. )
  21. const (
  22. userAgent = "go-storageosclient"
  23. DefaultVersionStr = "1"
  24. DefaultVersion = 1
  25. )
  26. var (
  27. // ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
  28. ErrConnectionRefused = errors.New("cannot connect to StorageOS API endpoint")
  29. // ErrInactivityTimeout is returned when a streamable call has been inactive for some time.
  30. ErrInactivityTimeout = errors.New("inactivity time exceeded timeout")
  31. // ErrInvalidVersion is returned when a versioned client was requested but no version specified.
  32. ErrInvalidVersion = errors.New("invalid version")
  33. // DefaultPort is the default API port
  34. DefaultPort = "5705"
  35. // DataplaneHealthPort is the the port used by the dataplane health-check service
  36. DataplaneHealthPort = "5704"
  37. // DefaultHost is the default API host
  38. DefaultHost = "tcp://localhost:" + DefaultPort
  39. )
  40. // APIVersion is an internal representation of a version of the Remote API.
  41. type APIVersion int
  42. // NewAPIVersion returns an instance of APIVersion for the given string.
  43. //
  44. // The given string must be in the form <major>
  45. func NewAPIVersion(input string) (APIVersion, error) {
  46. if input == "" {
  47. return DefaultVersion, ErrInvalidVersion
  48. }
  49. version, err := strconv.Atoi(input)
  50. if err != nil {
  51. return 0, fmt.Errorf("Unable to parse version %q", input)
  52. }
  53. return APIVersion(version), nil
  54. }
  55. func (version APIVersion) String() string {
  56. return fmt.Sprintf("v%d", version)
  57. }
  58. // Client is the basic type of this package. It provides methods for
  59. // interaction with the API.
  60. type Client struct {
  61. SkipServerVersionCheck bool
  62. HTTPClient *http.Client
  63. TLSConfig *tls.Config
  64. username string
  65. secret string
  66. requestedAPIVersion APIVersion
  67. serverAPIVersion APIVersion
  68. expectedAPIVersion APIVersion
  69. nativeHTTPClient *http.Client
  70. useTLS bool
  71. }
  72. // ClientVersion returns the API version of the client
  73. func (c *Client) ClientVersion() string {
  74. return DefaultVersionStr
  75. }
  76. // Dialer is an interface that allows network connections to be dialed
  77. // (net.Dialer fulfills this interface) and named pipes (a shim using
  78. // winio.DialPipe)
  79. type Dialer interface {
  80. Dial(network, address string) (net.Conn, error)
  81. }
  82. // NewClient returns a Client instance ready for communication with the given
  83. // server endpoint. It will use the latest remote API version available in the
  84. // server.
  85. func NewClient(nodes string) (*Client, error) {
  86. client, err := NewVersionedClient(nodes, "")
  87. if err != nil {
  88. return nil, err
  89. }
  90. client.SkipServerVersionCheck = true
  91. return client, nil
  92. }
  93. // NewVersionedClient returns a Client instance ready for communication with
  94. // the given server endpoint, using a specific remote API version.
  95. func NewVersionedClient(nodestring string, apiVersionString string) (*Client, error) {
  96. nodes := strings.Split(nodestring, ",")
  97. d, err := netutil.NewMultiDialer(nodes, nil)
  98. if err != nil {
  99. return nil, err
  100. }
  101. var useTLS bool
  102. if len(nodes) > 0 {
  103. if u, err := url.Parse(nodes[0]); err != nil && u.Scheme == "https" {
  104. useTLS = true
  105. }
  106. }
  107. c := &Client{
  108. HTTPClient: defaultClient(d),
  109. useTLS: useTLS,
  110. }
  111. if apiVersionString != "" {
  112. version, err := strconv.Atoi(apiVersionString)
  113. if err != nil {
  114. return nil, err
  115. }
  116. c.requestedAPIVersion = APIVersion(version)
  117. }
  118. return c, nil
  119. }
  120. // SetAuth sets the API username and secret to be used for all API requests.
  121. // It should not be called concurrently with any other Client methods.
  122. func (c *Client) SetAuth(username string, secret string) {
  123. if username != "" {
  124. c.username = username
  125. }
  126. if secret != "" {
  127. c.secret = secret
  128. }
  129. }
  130. // SetTimeout takes a timeout and applies it to both the HTTPClient and
  131. // nativeHTTPClient. It should not be called concurrently with any other Client
  132. // methods.
  133. func (c *Client) SetTimeout(t time.Duration) {
  134. if c.HTTPClient != nil {
  135. c.HTTPClient.Timeout = t
  136. }
  137. if c.nativeHTTPClient != nil {
  138. c.nativeHTTPClient.Timeout = t
  139. }
  140. }
  141. func (c *Client) checkAPIVersion() error {
  142. serverAPIVersionString, err := c.getServerAPIVersionString()
  143. if err != nil {
  144. return err
  145. }
  146. c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString)
  147. if err != nil {
  148. return err
  149. }
  150. if c.requestedAPIVersion == 0 {
  151. c.expectedAPIVersion = c.serverAPIVersion
  152. } else {
  153. c.expectedAPIVersion = c.requestedAPIVersion
  154. }
  155. return nil
  156. }
  157. // Ping pings the API server
  158. //
  159. // See https://goo.gl/wYfgY1 for more details.
  160. func (c *Client) Ping() error {
  161. urlpath := "/_ping"
  162. resp, err := c.do("GET", urlpath, doOptions{})
  163. if err != nil {
  164. return err
  165. }
  166. if resp.StatusCode != http.StatusOK {
  167. return newError(resp)
  168. }
  169. resp.Body.Close()
  170. return nil
  171. }
  172. func (c *Client) getServerAPIVersionString() (version string, err error) {
  173. v, err := c.ServerVersion(context.Background())
  174. if err != nil {
  175. return "", err
  176. }
  177. return v.APIVersion, nil
  178. }
  179. type doOptions struct {
  180. data interface{}
  181. fieldSelector string
  182. labelSelector string
  183. namespace string
  184. forceJSON bool
  185. force bool
  186. values url.Values
  187. headers map[string]string
  188. unversioned bool
  189. context context.Context
  190. }
  191. func (c *Client) do(method, urlpath string, doOptions doOptions) (*http.Response, error) {
  192. var params io.Reader
  193. if doOptions.data != nil || doOptions.forceJSON {
  194. buf, err := json.Marshal(doOptions.data)
  195. if err != nil {
  196. return nil, err
  197. }
  198. params = bytes.NewBuffer(buf)
  199. }
  200. // Prefix the path with the namespace if given. The caller should only set
  201. // the namespace if this is desired.
  202. if doOptions.namespace != "" {
  203. urlpath = "/" + NamespaceAPIPrefix + "/" + doOptions.namespace + "/" + urlpath
  204. }
  205. if !c.SkipServerVersionCheck && !doOptions.unversioned {
  206. err := c.checkAPIVersion()
  207. if err != nil {
  208. return nil, err
  209. }
  210. }
  211. query := url.Values{}
  212. if doOptions.values != nil {
  213. query = doOptions.values
  214. }
  215. if doOptions.force {
  216. query.Add("force", "1")
  217. }
  218. httpClient := c.HTTPClient
  219. u := c.getAPIPath(urlpath, query, doOptions.unversioned)
  220. req, err := http.NewRequest(method, u, params)
  221. if err != nil {
  222. return nil, err
  223. }
  224. req.Header.Set("User-Agent", userAgent)
  225. if doOptions.data != nil {
  226. req.Header.Set("Content-Type", "application/json")
  227. } else if method == "POST" {
  228. req.Header.Set("Content-Type", "plain/text")
  229. }
  230. if c.username != "" && c.secret != "" {
  231. req.SetBasicAuth(c.username, c.secret)
  232. }
  233. for k, v := range doOptions.headers {
  234. req.Header.Set(k, v)
  235. }
  236. ctx := doOptions.context
  237. if ctx == nil {
  238. ctx = context.Background()
  239. }
  240. resp, err := httpClient.Do(req.WithContext(ctx))
  241. if err != nil {
  242. // If it is a custom error, return it. It probably knows more than us
  243. if serror.IsStorageOSError(err) {
  244. return nil, err
  245. }
  246. if strings.Contains(err.Error(), "connection refused") {
  247. return nil, ErrConnectionRefused
  248. }
  249. return nil, chooseError(ctx, err)
  250. }
  251. if resp.StatusCode < 200 || resp.StatusCode >= 400 {
  252. return nil, newError(resp)
  253. }
  254. return resp, nil
  255. }
  256. // if error in context, return that instead of generic http error
  257. func chooseError(ctx context.Context, err error) error {
  258. select {
  259. case <-ctx.Done():
  260. return ctx.Err()
  261. default:
  262. return err
  263. }
  264. }
  265. func (c *Client) getAPIPath(path string, query url.Values, unversioned bool) string {
  266. // The custom dialer contacts the hosts for us, making this hosname irrelevant
  267. var urlStr string
  268. if c.useTLS {
  269. urlStr = "https://storageos-cluster"
  270. } else {
  271. urlStr = "http://storageos-cluster"
  272. }
  273. var apiPath string
  274. path = strings.TrimLeft(path, "/")
  275. if unversioned {
  276. apiPath = fmt.Sprintf("%s/%s", urlStr, path)
  277. } else {
  278. apiPath = fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
  279. }
  280. if len(query) > 0 {
  281. apiPath = apiPath + "?" + query.Encode()
  282. }
  283. return apiPath
  284. }
  285. func queryString(opts interface{}) string {
  286. if opts == nil {
  287. return ""
  288. }
  289. value := reflect.ValueOf(opts)
  290. if value.Kind() == reflect.Ptr {
  291. value = value.Elem()
  292. }
  293. if value.Kind() != reflect.Struct {
  294. return ""
  295. }
  296. items := url.Values(map[string][]string{})
  297. for i := 0; i < value.NumField(); i++ {
  298. field := value.Type().Field(i)
  299. if field.PkgPath != "" {
  300. continue
  301. }
  302. key := field.Tag.Get("qs")
  303. if key == "" {
  304. key = strings.ToLower(field.Name)
  305. } else if key == "-" {
  306. continue
  307. }
  308. addQueryStringValue(items, key, value.Field(i))
  309. }
  310. return items.Encode()
  311. }
  312. func addQueryStringValue(items url.Values, key string, v reflect.Value) {
  313. switch v.Kind() {
  314. case reflect.Bool:
  315. if v.Bool() {
  316. items.Add(key, "1")
  317. }
  318. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  319. if v.Int() > 0 {
  320. items.Add(key, strconv.FormatInt(v.Int(), 10))
  321. }
  322. case reflect.Float32, reflect.Float64:
  323. if v.Float() > 0 {
  324. items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
  325. }
  326. case reflect.String:
  327. if v.String() != "" {
  328. items.Add(key, v.String())
  329. }
  330. case reflect.Ptr:
  331. if !v.IsNil() {
  332. if b, err := json.Marshal(v.Interface()); err == nil {
  333. items.Add(key, string(b))
  334. }
  335. }
  336. case reflect.Map:
  337. if len(v.MapKeys()) > 0 {
  338. if b, err := json.Marshal(v.Interface()); err == nil {
  339. items.Add(key, string(b))
  340. }
  341. }
  342. case reflect.Array, reflect.Slice:
  343. vLen := v.Len()
  344. if vLen > 0 {
  345. for i := 0; i < vLen; i++ {
  346. addQueryStringValue(items, key, v.Index(i))
  347. }
  348. }
  349. }
  350. }
  351. // Error represents failures in the API. It represents a failure from the API.
  352. type Error struct {
  353. Status int
  354. Message string
  355. }
  356. func newError(resp *http.Response) *Error {
  357. type jsonError struct {
  358. Message string `json:"message"`
  359. }
  360. defer resp.Body.Close()
  361. data, err := ioutil.ReadAll(resp.Body)
  362. if err != nil {
  363. return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
  364. }
  365. // attempt to unmarshal the error if in json format
  366. jerr := &jsonError{}
  367. err = json.Unmarshal(data, jerr)
  368. if err != nil {
  369. return &Error{Status: resp.StatusCode, Message: string(data)} // Failed, just return string
  370. }
  371. return &Error{Status: resp.StatusCode, Message: jerr.Message}
  372. }
  373. func (e *Error) Error() string {
  374. var niceStatus string
  375. switch e.Status {
  376. case 400, 500:
  377. niceStatus = "Server failed to process your request. Was the data correct?"
  378. case 401:
  379. niceStatus = "Unauthenticated access of secure endpoint, please retry after authentication"
  380. case 403:
  381. niceStatus = "Forbidden request. Your user cannot perform this action"
  382. case 404:
  383. niceStatus = "Requested object not found. Does this item exist?"
  384. }
  385. if niceStatus != "" {
  386. return fmt.Sprintf("API error (%s): %s", niceStatus, e.Message)
  387. }
  388. return fmt.Sprintf("API error (%s): %s", http.StatusText(e.Status), e.Message)
  389. }
  390. // defaultTransport returns a new http.Transport with the same default values
  391. // as http.DefaultTransport, but with idle connections and keepalives disabled.
  392. func defaultTransport(d Dialer) *http.Transport {
  393. transport := defaultPooledTransport(d)
  394. transport.DisableKeepAlives = true
  395. transport.MaxIdleConnsPerHost = -1
  396. return transport
  397. }
  398. // defaultPooledTransport returns a new http.Transport with similar default
  399. // values to http.DefaultTransport. Do not use this for transient transports as
  400. // it can leak file descriptors over time. Only use this for transports that
  401. // will be re-used for the same host(s).
  402. func defaultPooledTransport(d Dialer) *http.Transport {
  403. transport := &http.Transport{
  404. Proxy: http.ProxyFromEnvironment,
  405. Dial: d.Dial,
  406. TLSHandshakeTimeout: 5 * time.Second,
  407. DisableKeepAlives: false,
  408. MaxIdleConnsPerHost: 1,
  409. }
  410. return transport
  411. }
  412. // defaultClient returns a new http.Client with similar default values to
  413. // http.Client, but with a non-shared Transport, idle connections disabled, and
  414. // keepalives disabled.
  415. // If a custom dialer is not provided, one with sane defaults will be created.
  416. func defaultClient(d Dialer) *http.Client {
  417. if d == nil {
  418. d = &net.Dialer{
  419. Timeout: 5 * time.Second,
  420. KeepAlive: 5 * time.Second,
  421. }
  422. }
  423. return &http.Client{
  424. Transport: defaultTransport(d),
  425. }
  426. }