Update storageos api dependency to 0.3.4

This commit is contained in:
Simon Croome
2018-02-06 13:28:00 +00:00
parent 68da45623f
commit 8d0ba4a978
37 changed files with 1414 additions and 1201 deletions

View File

@@ -4,16 +4,16 @@ import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"github.com/storageos/go-api/netutil"
"github.com/storageos/go-api/serror"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
@@ -22,17 +22,11 @@ import (
const (
userAgent = "go-storageosclient"
unixProtocol = "unix"
namedPipeProtocol = "npipe"
DefaultVersionStr = "1"
DefaultVersion = 1
defaultNamespace = "default"
)
var (
// ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL.
ErrInvalidEndpoint = errors.New("invalid endpoint")
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
ErrConnectionRefused = errors.New("cannot connect to StorageOS API endpoint")
@@ -42,8 +36,14 @@ var (
// ErrInvalidVersion is returned when a versioned client was requested but no version specified.
ErrInvalidVersion = errors.New("invalid version")
// DefaultPort is the default API port
DefaultPort = "5705"
// DataplaneHealthPort is the the port used by the dataplane health-check service
DataplaneHealthPort = "5704"
// DefaultHost is the default API host
DefaultHost = "tcp://localhost:5705"
DefaultHost = "tcp://localhost:" + DefaultPort
)
// APIVersion is an internal representation of a version of the Remote API.
@@ -73,15 +73,13 @@ type Client struct {
SkipServerVersionCheck bool
HTTPClient *http.Client
TLSConfig *tls.Config
Dialer Dialer
endpoint string
endpointURL *url.URL
username string
secret string
requestedAPIVersion APIVersion
serverAPIVersion APIVersion
expectedAPIVersion APIVersion
nativeHTTPClient *http.Client
useTLS bool
}
// ClientVersion returns the API version of the client
@@ -99,20 +97,8 @@ type Dialer interface {
// NewClient returns a Client instance ready for communication with the given
// server endpoint. It will use the latest remote API version available in the
// server.
func NewClient(endpoint string) (*Client, error) {
client, err := NewVersionedClient(endpoint, "")
if err != nil {
return nil, err
}
client.SkipServerVersionCheck = true
return client, nil
}
// NewTLSClient returns a Client instance ready for TLS communications with the given
// server endpoint, key and certificates . It will use the latest remote API version
// available in the server.
func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "")
func NewClient(nodes string) (*Client, error) {
client, err := NewVersionedClient(nodes, "")
if err != nil {
return nil, err
}
@@ -122,17 +108,24 @@ func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
// NewVersionedClient returns a Client instance ready for communication with
// the given server endpoint, using a specific remote API version.
func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint, false)
func NewVersionedClient(nodestring string, apiVersionString string) (*Client, error) {
nodes := strings.Split(nodestring, ",")
d, err := netutil.NewMultiDialer(nodes, nil)
if err != nil {
return nil, err
}
var useTLS bool
if len(nodes) > 0 {
if u, err := url.Parse(nodes[0]); err != nil && u.Scheme == "https" {
useTLS = true
}
}
c := &Client{
HTTPClient: defaultClient(),
Dialer: &net.Dialer{},
endpoint: endpoint,
endpointURL: u,
HTTPClient: defaultClient(d),
useTLS: useTLS,
}
if apiVersionString != "" {
@@ -143,85 +136,6 @@ func NewVersionedClient(endpoint string, apiVersionString string) (*Client, erro
c.requestedAPIVersion = APIVersion(version)
}
c.initializeNativeClient()
return c, nil
}
// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates, using a specific remote API version.
func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
var certPEMBlock []byte
var keyPEMBlock []byte
var caPEMCert []byte
if _, err := os.Stat(cert); !os.IsNotExist(err) {
certPEMBlock, err = ioutil.ReadFile(cert)
if err != nil {
return nil, err
}
}
if _, err := os.Stat(key); !os.IsNotExist(err) {
keyPEMBlock, err = ioutil.ReadFile(key)
if err != nil {
return nil, err
}
}
if _, err := os.Stat(ca); !os.IsNotExist(err) {
caPEMCert, err = ioutil.ReadFile(ca)
if err != nil {
return nil, err
}
}
return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString)
}
// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates (passed inline to the function as opposed to being
// read from a local file), using a specific remote API version.
func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint, true)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{}
if certPEMBlock != nil && keyPEMBlock != nil {
tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{tlsCert}
}
if caPEMCert == nil {
tlsConfig.InsecureSkipVerify = true
} else {
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(caPEMCert) {
return nil, errors.New("Could not add RootCA pem")
}
tlsConfig.RootCAs = caPool
}
tr := defaultTransport()
tr.TLSClientConfig = tlsConfig
if err != nil {
return nil, err
}
c := &Client{
HTTPClient: &http.Client{Transport: tr},
TLSConfig: tlsConfig,
Dialer: &net.Dialer{},
endpoint: endpoint,
endpointURL: u,
}
if apiVersionString != "" {
version, err := strconv.Atoi(apiVersionString)
if err != nil {
return nil, err
}
c.requestedAPIVersion = APIVersion(version)
}
c.initializeNativeClient()
return c, nil
}
@@ -265,13 +179,6 @@ func (c *Client) checkAPIVersion() error {
return nil
}
// Endpoint returns the current endpoint. It's useful for getting the endpoint
// when using functions that get this data from the environment (like
// NewClientFromEnv.
func (c *Client) Endpoint() string {
return c.endpoint
}
// Ping pings the API server
//
// See https://goo.gl/wYfgY1 for more details.
@@ -341,15 +248,7 @@ func (c *Client) do(method, urlpath string, doOptions doOptions) (*http.Response
}
httpClient := c.HTTPClient
protocol := c.endpointURL.Scheme
var u string
switch protocol {
case unixProtocol, namedPipeProtocol:
httpClient = c.nativeHTTPClient
u = c.getFakeNativeURL(urlpath, doOptions.unversioned)
default:
u = c.getAPIPath(urlpath, query, doOptions.unversioned)
}
u := c.getAPIPath(urlpath, query, doOptions.unversioned)
req, err := http.NewRequest(method, u, params)
if err != nil {
@@ -376,6 +275,11 @@ func (c *Client) do(method, urlpath string, doOptions doOptions) (*http.Response
resp, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
// If it is a custom error, return it. It probably knows more than us
if serror.IsStorageOSError(err) {
return nil, err
}
if strings.Contains(err.Error(), "connection refused") {
return nil, ErrConnectionRefused
}
@@ -397,27 +301,18 @@ func chooseError(ctx context.Context, err error) error {
}
}
func (c *Client) getURL(path string, unversioned bool) string {
urlStr := strings.TrimRight(c.endpointURL.String(), "/")
path = strings.TrimLeft(path, "/")
if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol {
urlStr = ""
}
if unversioned {
return fmt.Sprintf("%s/%s", urlStr, path)
}
return fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
}
func (c *Client) getAPIPath(path string, query url.Values, unversioned bool) string {
var apiPath string
urlStr := strings.TrimRight(c.endpointURL.String(), "/")
path = strings.TrimLeft(path, "/")
if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol {
urlStr = ""
// The custom dialer contacts the hosts for us, making this hosname irrelevant
var urlStr string
if c.useTLS {
urlStr = "https://storageos-cluster"
} else {
urlStr = "http://storageos-cluster"
}
var apiPath string
path = strings.TrimLeft(path, "/")
if unversioned {
apiPath = fmt.Sprintf("%s/%s", urlStr, path)
} else {
@@ -431,30 +326,6 @@ func (c *Client) getAPIPath(path string, query url.Values, unversioned bool) str
return apiPath
}
// getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX
// domain socket to the given path.
func (c *Client) getFakeNativeURL(path string, unversioned bool) string {
u := *c.endpointURL // Copy.
// Override URL so that net/http will not complain.
u.Scheme = "http"
u.Host = "unix.sock" // Doesn't matter what this is - it's not used.
u.Path = ""
urlStr := strings.TrimRight(u.String(), "/")
path = strings.TrimLeft(path, "/")
if unversioned {
return fmt.Sprintf("%s/%s", urlStr, path)
}
return fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
}
type jsonMessage struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
Error string `json:"error,omitempty"`
Stream string `json:"stream,omitempty"`
}
func queryString(opts interface{}) string {
if opts == nil {
return ""
@@ -530,63 +401,50 @@ type Error struct {
}
func newError(resp *http.Response) *Error {
type jsonError struct {
Message string `json:"message"`
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
}
return &Error{Status: resp.StatusCode, Message: string(data)}
// attempt to unmarshal the error if in json format
jerr := &jsonError{}
err = json.Unmarshal(data, jerr)
if err != nil {
return &Error{Status: resp.StatusCode, Message: string(data)} // Failed, just return string
}
return &Error{Status: resp.StatusCode, Message: jerr.Message}
}
func (e *Error) Error() string {
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
}
var niceStatus string
func parseEndpoint(endpoint string, tls bool) (*url.URL, error) {
if endpoint != "" && !strings.Contains(endpoint, "://") {
endpoint = "tcp://" + endpoint
switch e.Status {
case 400, 500:
niceStatus = "Server failed to process your request. Was the data correct?"
case 401:
niceStatus = "Unauthenticated access of secure endpoint, please retry after authentication"
case 403:
niceStatus = "Forbidden request. Your user cannot perform this action"
case 404:
niceStatus = "Requested object not found. Does this item exist?"
}
u, err := url.Parse(endpoint)
if err != nil {
return nil, ErrInvalidEndpoint
}
if tls && u.Scheme != "unix" {
u.Scheme = "https"
}
switch u.Scheme {
case unixProtocol, namedPipeProtocol:
return u, nil
case "http", "https", "tcp":
_, port, err := net.SplitHostPort(u.Host)
if err != nil {
if e, ok := err.(*net.AddrError); ok {
if e.Err == "missing port in address" {
return u, nil
}
}
return nil, ErrInvalidEndpoint
}
number, err := strconv.ParseInt(port, 10, 64)
if err == nil && number > 0 && number < 65536 {
if u.Scheme == "tcp" {
if tls {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
}
return u, nil
}
return nil, ErrInvalidEndpoint
default:
return nil, ErrInvalidEndpoint
if niceStatus != "" {
return fmt.Sprintf("API error (%s): %s", niceStatus, e.Message)
}
return fmt.Sprintf("API error (%s): %s", http.StatusText(e.Status), e.Message)
}
// defaultTransport returns a new http.Transport with the same default values
// as http.DefaultTransport, but with idle connections and keepalives disabled.
func defaultTransport() *http.Transport {
transport := defaultPooledTransport()
func defaultTransport(d Dialer) *http.Transport {
transport := defaultPooledTransport(d)
transport.DisableKeepAlives = true
transport.MaxIdleConnsPerHost = -1
return transport
@@ -596,14 +454,11 @@ func defaultTransport() *http.Transport {
// values to http.DefaultTransport. Do not use this for transient transports as
// it can leak file descriptors over time. Only use this for transports that
// will be re-used for the same host(s).
func defaultPooledTransport() *http.Transport {
func defaultPooledTransport(d Dialer) *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
Proxy: http.ProxyFromEnvironment,
Dial: d.Dial,
TLSHandshakeTimeout: 5 * time.Second,
DisableKeepAlives: false,
MaxIdleConnsPerHost: 1,
}
@@ -613,8 +468,16 @@ func defaultPooledTransport() *http.Transport {
// defaultClient returns a new http.Client with similar default values to
// http.Client, but with a non-shared Transport, idle connections disabled, and
// keepalives disabled.
func defaultClient() *http.Client {
// If a custom dialer is not provided, one with sane defaults will be created.
func defaultClient(d Dialer) *http.Client {
if d == nil {
d = &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Second,
}
}
return &http.Client{
Transport: defaultTransport(),
Transport: defaultTransport(d),
}
}