StorageOS update api library

This commit is contained in:
Sunny
2018-09-14 19:36:57 +05:30
parent 8b98e802ed
commit a4cc754fb4
33 changed files with 795 additions and 1695 deletions

View File

@@ -4,7 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"errors.go",
"multidialer.go",
"netutil.go",
"parsers.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/storageos/go-api/netutil",

View File

@@ -3,11 +3,14 @@ package netutil
import (
"errors"
"fmt"
"github.com/storageos/go-api/serror"
"strings"
"github.com/storageos/go-api/serror"
)
func errAllFailed(addrs []string) error {
// ErrAllFailed produces a typed StorageOS error which should be used to indicate that
// the API is not contactable for all of the supplied node addresses.
func ErrAllFailed(addrs []string) error {
msg := fmt.Sprintf("failed to dial all known cluster members, (%s)", strings.Join(addrs, ","))
help := "ensure that the value of $STORAGEOS_HOST (or the -H flag) is correct, and that there are healthy StorageOS nodes in this cluster"
@@ -21,6 +24,8 @@ func newInvalidNodeError(err error) error {
return serror.NewTypedStorageOSError(serror.InvalidHostConfig, err, msg, help)
}
var errNoAddresses = errors.New("the MultiDialer instance has not been initialised with client addresses")
var errUnsupportedScheme = errors.New("unsupported URL scheme")
var errInvalidPortNumber = errors.New("invalid port number")
var (
errUnsupportedScheme = errors.New("unsupported URL scheme")
errInvalidHostName = errors.New("invalid hostname")
errInvalidPortNumber = errors.New("invalid port number")
)

View File

@@ -1,109 +0,0 @@
package netutil
import (
"context"
"math/rand"
"net"
"time"
)
var DefaultDialPort = "5705"
func init() {
rand.Seed(time.Now().UnixNano())
}
// Dialer is an interface that matches *net.Dialer. The intention is to allow either the stdlib
// dialer or a custom implementation to be passed to the MultiDialer constructor. This also makes
// the component easier to test.
type Dialer interface {
DialContext(context.Context, string, string) (net.Conn, error)
}
// MultiDialer is a custom net Dialer (to be used in a net.Transport field) that attemps to dial
// out to any (potentialy many) of a set of pre-defined addresses. The intended use of this
// function is to extend the functionality of the stdlib http.Client to transparently support
// requests to any member of a given storageos cluster.
type MultiDialer struct {
Addresses []string
Dialer *net.Dialer
}
// NewMultiDialer returns a new MultiDialer instance, configured to dial out to the given set of
// nodes. Nodes can be provided using a URL format (e.g. http://google.com:80), or a host-port pair
// (e.g. localhost:4567).
//
// If a port number is omitted, the value of DefaultDialPort is used.
// Given hostnames are resolved to IP addresses, and IP addresses are used verbatim.
//
// If called with a non-nil dialer, the MultiDialer instance will use this for internall dial
// requests. If this value is nil, the function will initialise one with sane defaults.
func NewMultiDialer(nodes []string, dialer *net.Dialer) (*MultiDialer, error) {
// If a dialer is not provided, initialise one with sane defaults
if dialer == nil {
dialer = &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Second,
}
}
addrs, err := addrsFromNodes(nodes)
if err != nil {
return nil, err
}
return &MultiDialer{
Addresses: addrs,
Dialer: dialer,
}, nil
}
// DialContext will dial each of the MultiDialer's internal addresses in a random order until one
// successfully returns a connection, it has run out of addresses (returning ErrAllFailed), or the
// given context has been closed.
//
// Due to the intrinsic behaviour of this function, any address passed to this function will be
// ignored.
func (m *MultiDialer) DialContext(ctx context.Context, network, ignoredAddress string) (net.Conn, error) {
if len(m.Addresses) == 0 {
return nil, newInvalidNodeError(errNoAddresses)
}
// Shuffle a copy of the addresses (for even load balancing)
addrs := make([]string, len(m.Addresses))
copy(addrs, m.Addresses)
// FisherYates shuffle algorithm
for i := len(addrs) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
addrs[i], addrs[j] = addrs[j], addrs[i]
}
// Try to dial each of these addresses in turn, or return on closed context
for _, addr := range addrs {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
// Create new child context for a single dial
dctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
conn, err := m.Dialer.DialContext(dctx, network, addr)
if err != nil {
continue
}
return conn, nil
}
}
// We failed to dail all of the addresses we have
return nil, errAllFailed(m.Addresses)
}
// Dial returns the result of a call to m.DialContext passing in the background context
func (m *MultiDialer) Dial(network, addr string) (net.Conn, error) {
return m.DialContext(context.Background(), network, addr)
}

View File

@@ -0,0 +1,5 @@
// Package netutil provides network related errors and helper functions.
package netutil
// DefaultDialPort is the default port which the API is contacted on.
const DefaultDialPort = "5705"

View File

@@ -1,52 +1,72 @@
package netutil
import (
"net"
"net/url"
"strconv"
"strings"
)
// addrsFromNodes takes a list of node hosts and attempts to return a list of hosts in ip:port
const (
httpScheme = "http"
httpsScheme = "https"
tcpScheme = "tcp"
)
// AddressesFromNodes takes a list of node hosts and attempts to return a list of hosts in host:port
// format along with any error encountered.
//
// The function accepts node hosts in URL, ip, ip:port, resolvable-name and resolvable-name:port
// formats and will append the default port value if needed.
func addrsFromNodes(nodes []string) ([]string, error) {
var addrs []string
// formats and will append the default port value if needed. For hosts where the scheme has been omitted,
// the scheme for the first host will be used. If the first host has no scheme, it will default to http.
func AddressesFromNodes(nodes []string) ([]string, error) {
var addresses []string
for _, n := range nodes {
switch {
// Assume that the node is provided as a URL
case strings.Contains(n, "://"):
newAddrs, err := parseURL(n)
if err != nil {
return nil, newInvalidNodeError(err)
var scheme string
for _, node := range nodes {
address := node
// If no scheme present, set the first scheme
if !strings.Contains(address, "://") {
if scheme == "" {
scheme = httpScheme
}
addrs = append(addrs, newAddrs...)
// Assume the node is in hostname:port or ip:port format
case strings.Contains(n, ":"):
newAddrs, err := parseHostPort(n)
if err != nil {
return nil, newInvalidNodeError(err)
}
addrs = append(addrs, newAddrs...)
// Assume hostname or ip
default:
newAddrs, err := parseHost(n)
if err != nil {
return nil, newInvalidNodeError(err)
}
addrs = append(addrs, newAddrs...)
address = strings.Join([]string{scheme, address}, "://")
}
url, err := url.Parse(address)
if err != nil {
return nil, newInvalidNodeError(err)
}
switch url.Scheme {
case tcpScheme:
url.Scheme = httpScheme
fallthrough
case httpScheme, httpsScheme:
if scheme == "" {
scheme = url.Scheme
}
default:
return nil, newInvalidNodeError(errUnsupportedScheme)
}
host := url.Hostname()
if host == "" {
return nil, newInvalidNodeError(errInvalidHostName)
}
port := url.Port()
if port == "" {
port = DefaultDialPort
}
if !validPort(port) {
return nil, newInvalidNodeError(errInvalidPortNumber)
}
addresses = append(addresses, strings.TrimRight(url.String(), "/"))
}
return addrs, nil
return addresses, nil
}
func validPort(port string) bool {
@@ -56,87 +76,3 @@ func validPort(port string) bool {
(intPort > 0) &&
(intPort <= 65535)
}
// parseURL takes a valid URL and verifies that it is using a correct scheme, has a resolvable
// address (or is an IP) and has a valid port (or adds the default if the port is omitted). The
// function then returns a list of addresses in ip:port format along with any error encountered.
//
// The function may return multiple addresses depending on the dns answer received when resolving
// the host.
func parseURL(node string) ([]string, error) {
url, err := url.Parse(node)
if err != nil {
return nil, err
}
// Verify a valid scheme
switch url.Scheme {
case "tcp", "http", "https":
host, port, err := net.SplitHostPort(url.Host)
if err != nil {
// We could be here as there is no port, lets try one last time with default port added
host, port, err = net.SplitHostPort(url.Host + ":" + DefaultDialPort)
if err != nil {
return nil, err
}
}
if !validPort(port) {
return nil, errInvalidPortNumber
}
// LookupHost works for IP addr too
addrs, err := net.LookupHost(host)
if err != nil {
return nil, err
}
for i, a := range addrs {
addrs[i] = a + ":" + port
}
return addrs, nil
default:
return nil, errUnsupportedScheme
}
}
// parseHostPort takes a string in host:port format and checks it has a resolvable address (or is
// an IP) and a valid port (or adds the default if the port is omitted). The function then returns
// a list of addresses in ip:port format along with any error encountered.
//
// The function may return multiple addresses depending on the dns answer received when resolving
// the host.
func parseHostPort(node string) ([]string, error) {
host, port, err := net.SplitHostPort(node)
if err != nil {
return nil, err
}
if !validPort(port) {
return nil, errInvalidPortNumber
}
// LookupHost works for IP addr too
addrs, err := net.LookupHost(host)
if err != nil {
return nil, err
}
for i, a := range addrs {
addrs[i] = a + ":" + port
}
return addrs, nil
}
// parseHostPort takes a hostname string and checks it is resolvable to an address (or is already
// an IP) The function then returns a list of addresses in ip:port format (where port is the
// default port) along with any error encountered.
//
// The function may return multiple addresses depending on the dns answer received when resolving
// the host.
func parseHost(node string) ([]string, error) {
return parseHostPort(node + ":" + DefaultDialPort)
}