Merge pull request #69782 from darkowlzz/storageos-updates

StorageOS attach device before volume attach
pull/564/head
Kubernetes Prow Robot 2019-02-20 12:51:57 -08:00 committed by GitHub
commit 23a085062c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 844 additions and 1722 deletions

16
Godeps/Godeps.json generated
View File

@ -3158,23 +3158,23 @@
},
{
"ImportPath": "github.com/storageos/go-api",
"Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
"Comment": "1.0.0-rc1-2-g343b3eff91fcc8",
"Rev": "343b3eff91fcc84b0165e252eb843f5fd720fa4e"
},
{
"ImportPath": "github.com/storageos/go-api/netutil",
"Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
"Comment": "1.0.0-rc1-2-g343b3eff91fcc8",
"Rev": "343b3eff91fcc84b0165e252eb843f5fd720fa4e"
},
{
"ImportPath": "github.com/storageos/go-api/serror",
"Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
"Comment": "1.0.0-rc1-2-g343b3eff91fcc8",
"Rev": "343b3eff91fcc84b0165e252eb843f5fd720fa4e"
},
{
"ImportPath": "github.com/storageos/go-api/types",
"Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
"Comment": "1.0.0-rc1-2-g343b3eff91fcc8",
"Rev": "343b3eff91fcc84b0165e252eb843f5fd720fa4e"
},
{
"ImportPath": "github.com/stretchr/objx",

16
Godeps/LICENSES generated
View File

@ -91176,7 +91176,7 @@ SOFTWARE.
MIT License
Copyright (c) 2015-2017 StorageOS
Copyright (c) 2015-2018 StorageOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -91220,7 +91220,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/github.com/storageos/go-api/LICENCE d8f852a0f38554263e64363f57b07fc4
= vendor/github.com/storageos/go-api/LICENCE 10d8703157b5fd9422b1813bae555905
================================================================================
@ -91229,7 +91229,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
Copyright (c) 2015-2017 StorageOS
Copyright (c) 2015-2018 StorageOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -91273,7 +91273,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/github.com/storageos/go-api/LICENCE d8f852a0f38554263e64363f57b07fc4
= vendor/github.com/storageos/go-api/LICENCE 10d8703157b5fd9422b1813bae555905
================================================================================
@ -91282,7 +91282,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
Copyright (c) 2015-2017 StorageOS
Copyright (c) 2015-2018 StorageOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -91326,7 +91326,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/github.com/storageos/go-api/LICENCE d8f852a0f38554263e64363f57b07fc4
= vendor/github.com/storageos/go-api/LICENCE 10d8703157b5fd9422b1813bae555905
================================================================================
@ -91335,7 +91335,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
Copyright (c) 2015-2017 StorageOS
Copyright (c) 2015-2018 StorageOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -91379,7 +91379,7 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/github.com/storageos/go-api/LICENCE d8f852a0f38554263e64363f57b07fc4
= vendor/github.com/storageos/go-api/LICENCE 10d8703157b5fd9422b1813bae555905
================================================================================

View File

@ -281,6 +281,8 @@ type storageosManager interface {
CreateVolume(provisioner *storageosProvisioner) (*storageosVolume, error)
// Attaches the disk to the kubelet's host machine.
AttachVolume(mounter *storageosMounter) (string, error)
// Attaches the device to the host at a mount path.
AttachDevice(mounter *storageosMounter, deviceMountPath string) error
// Detaches the disk from the kubelet's host machine.
DetachVolume(unmounter *storageosUnmounter, dir string) error
// Mounts the disk on the Kubelet's host machine.
@ -351,6 +353,14 @@ func (b *storageosMounter) SetUp(fsGroup *int64) error {
b.volNamespace = b.podNamespace
}
targetPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName)
// Attach the device to the host.
if err := b.manager.AttachDevice(b, targetPath); err != nil {
klog.Errorf("Failed to attach device at %s: %s", targetPath, err.Error())
return err
}
// Attach the StorageOS volume as a block device
devicePath, err := b.manager.AttachVolume(b)
if err != nil {
@ -359,8 +369,7 @@ func (b *storageosMounter) SetUp(fsGroup *int64) error {
}
// Mount the loop device into the plugin's disk global mount dir.
globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.podNamespace, b.volName)
err = b.manager.MountVolume(b, devicePath, globalPDPath)
err = b.manager.MountVolume(b, devicePath, targetPath)
if err != nil {
return err
}

View File

@ -75,13 +75,14 @@ func TestGetAccessModes(t *testing.T) {
}
type fakePDManager struct {
api apiImplementer
attachCalled bool
detachCalled bool
mountCalled bool
unmountCalled bool
createCalled bool
deleteCalled bool
api apiImplementer
attachCalled bool
attachDeviceCalled bool
detachCalled bool
mountCalled bool
unmountCalled bool
createCalled bool
deleteCalled bool
}
func (fake *fakePDManager) NewAPI(apiCfg *storageosAPIConfig) error {
@ -108,6 +109,11 @@ func (fake *fakePDManager) AttachVolume(b *storageosMounter) (string, error) {
return "", nil
}
func (fake *fakePDManager) AttachDevice(b *storageosMounter, dir string) error {
fake.attachDeviceCalled = true
return nil
}
func (fake *fakePDManager) DetachVolume(b *storageosUnmounter, loopDevice string) error {
fake.detachCalled = true
return nil
@ -213,6 +219,9 @@ func TestPlugin(t *testing.T) {
}
}
if !fakeManager.attachDeviceCalled {
t.Errorf("AttachDevice not called")
}
if !fakeManager.attachCalled {
t.Errorf("Attach not called")
}

View File

@ -69,7 +69,7 @@ type apiImplementer interface {
VolumeMount(opts storageostypes.VolumeMountOptions) error
VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error
VolumeDelete(opt storageostypes.DeleteOptions) error
Controller(ref string) (*storageostypes.Controller, error)
Node(ref string) (*storageostypes.Node, error)
}
// storageosUtil is the utility structure to interact with the StorageOS API.
@ -203,6 +203,25 @@ func (u *storageosUtil) DetachVolume(b *storageosUnmounter, devicePath string) e
return removeLoopDevice(devicePath, b.exec)
}
// AttachDevice attaches the volume device to the host at a given mount path.
func (u *storageosUtil) AttachDevice(b *storageosMounter, deviceMountPath string) error {
if err := u.NewAPI(b.apiCfg); err != nil {
return err
}
opts := storageostypes.VolumeMountOptions{
Name: b.volName,
Namespace: b.volNamespace,
FsType: b.fsType,
Mountpoint: deviceMountPath,
Client: b.plugin.host.GetHostName(),
}
if err := u.api.VolumeMount(opts); err != nil {
return err
}
return nil
}
// Mount mounts the volume on the host.
func (u *storageosUtil) MountVolume(b *storageosMounter, mntDevice, deviceMountPath string) error {
notMnt, err := b.mounter.IsLikelyNotMountPoint(deviceMountPath)
@ -231,22 +250,7 @@ func (u *storageosUtil) MountVolume(b *storageosMounter, mntDevice, deviceMountP
return err
}
}
if err != nil {
return err
}
if err := u.NewAPI(b.apiCfg); err != nil {
return err
}
opts := storageostypes.VolumeMountOptions{
Name: b.volName,
Namespace: b.volNamespace,
FsType: b.fsType,
Mountpoint: deviceMountPath,
Client: b.plugin.host.GetHostName(),
}
return u.api.VolumeMount(opts)
return err
}
// Unmount removes the mount reference from the volume allowing it to be
@ -289,7 +293,7 @@ func (u *storageosUtil) DeleteVolume(d *storageosDeleter) error {
// specified.
func (u *storageosUtil) DeviceDir(b *storageosMounter) string {
ctrl, err := u.api.Controller(b.plugin.host.GetHostName())
ctrl, err := u.api.Node(b.plugin.host.GetHostName())
if err != nil {
klog.Warningf("node device path lookup failed: %v", err)
return defaultDeviceDir

View File

@ -108,8 +108,8 @@ func (f fakeAPI) VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error {
func (f fakeAPI) VolumeDelete(opts storageostypes.DeleteOptions) error {
return nil
}
func (f fakeAPI) Controller(ref string) (*storageostypes.Controller, error) {
return &storageostypes.Controller{}, nil
func (f fakeAPI) Node(ref string) (*storageostypes.Node, error) {
return &storageostypes.Node{}, nil
}
func TestCreateVolume(t *testing.T) {

View File

@ -4,12 +4,12 @@ go_library(
name = "go_default_library",
srcs = [
"client.go",
"controller.go",
"event.go",
"health.go",
"logger.go",
"login.go",
"namespace.go",
"network_diagnostics.go",
"node.go",
"policy.go",
"pool.go",
"rule.go",
@ -24,7 +24,6 @@ go_library(
importpath = "github.com/storageos/go-api",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/gorilla/websocket:go_default_library",
"//vendor/github.com/storageos/go-api/netutil:go_default_library",
"//vendor/github.com/storageos/go-api/serror:go_default_library",
"//vendor/github.com/storageos/go-api/types:go_default_library",

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2015-2017 StorageOS
Copyright (c) 2015-2018 StorageOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -3,27 +3,32 @@ package storageos
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/storageos/go-api/netutil"
"github.com/storageos/go-api/serror"
"io"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/storageos/go-api/netutil"
"github.com/storageos/go-api/serror"
)
const (
userAgent = "go-storageosclient"
// DefaultUserAgent is the default User-Agent header to include in HTTP requests.
DefaultUserAgent = "go-storageosclient"
// DefaultVersionStr is the string value of the default API version.
DefaultVersionStr = "1"
DefaultVersion = 1
// DefaultVersion is the default API version.
DefaultVersion = 1
)
var (
@ -36,14 +41,17 @@ 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
// ErrProxyNotSupported is returned when a client is unable to set a proxy for http requests.
ErrProxyNotSupported = errors.New("client does not support http proxy")
// DefaultPort is the default API port.
DefaultPort = "5705"
// DataplaneHealthPort is the the port used by the dataplane health-check service
// DataplaneHealthPort is the the port used by the dataplane health-check service.
DataplaneHealthPort = "5704"
// DefaultHost is the default API host
DefaultHost = "tcp://localhost:" + DefaultPort
// DefaultHost is the default API host.
DefaultHost = "http://localhost:" + DefaultPort
)
// APIVersion is an internal representation of a version of the Remote API.
@ -70,16 +78,21 @@ func (version APIVersion) String() string {
// Client is the basic type of this package. It provides methods for
// interaction with the API.
type Client struct {
httpClient *http.Client
addresses []string
username string
secret string
userAgent string
configLock *sync.RWMutex // Lock for config changes
addressLock *sync.Mutex // Lock used to copy/update the address slice
requestedAPIVersion APIVersion
serverAPIVersion APIVersion
expectedAPIVersion APIVersion
SkipServerVersionCheck bool
HTTPClient *http.Client
TLSConfig *tls.Config
username string
secret string
requestedAPIVersion APIVersion
serverAPIVersion APIVersion
expectedAPIVersion APIVersion
nativeHTTPClient *http.Client
useTLS bool
}
// ClientVersion returns the API version of the client
@ -103,6 +116,7 @@ func NewClient(nodes string) (*Client, error) {
return nil, err
}
client.SkipServerVersionCheck = true
client.userAgent = DefaultUserAgent
return client, nil
}
@ -110,22 +124,24 @@ func NewClient(nodes string) (*Client, error) {
// the given server endpoint, using a specific remote API version.
func NewVersionedClient(nodestring string, apiVersionString string) (*Client, error) {
nodes := strings.Split(nodestring, ",")
d, err := netutil.NewMultiDialer(nodes, nil)
addresses, err := netutil.AddressesFromNodes(nodes)
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
}
if len(addresses) > 1 {
// Shuffle returned addresses in attempt to spread the load
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
rnd.Shuffle(len(addresses), func(i, j int) {
addresses[i], addresses[j] = addresses[j], addresses[i]
})
}
c := &Client{
HTTPClient: defaultClient(d),
useTLS: useTLS,
client := &Client{
httpClient: defaultClient(),
addresses: addresses,
configLock: &sync.RWMutex{},
addressLock: &sync.Mutex{},
}
if apiVersionString != "" {
@ -133,15 +149,24 @@ func NewVersionedClient(nodestring string, apiVersionString string) (*Client, er
if err != nil {
return nil, err
}
c.requestedAPIVersion = APIVersion(version)
client.requestedAPIVersion = APIVersion(version)
}
return c, nil
return client, nil
}
// SetUserAgent sets the client useragent.
func (c *Client) SetUserAgent(useragent string) {
c.configLock.Lock()
defer c.configLock.Unlock()
c.userAgent = useragent
}
// SetAuth sets the API username and secret to be used for all API requests.
// It should not be called concurrently with any other Client methods.
func (c *Client) SetAuth(username string, secret string) {
c.configLock.Lock()
defer c.configLock.Unlock()
if username != "" {
c.username = username
}
@ -150,15 +175,31 @@ func (c *Client) SetAuth(username string, secret string) {
}
}
// SetProxy will set the proxy URL for both the HTTPClient.
// If the transport method does not support usage
// of proxies, an error will be returned.
func (c *Client) SetProxy(proxy *url.URL) error {
c.configLock.Lock()
defer c.configLock.Unlock()
if client := c.httpClient; client != nil {
transport, supported := client.Transport.(*http.Transport)
if !supported {
return ErrProxyNotSupported
}
transport.Proxy = http.ProxyURL(proxy)
}
return nil
}
// SetTimeout takes a timeout and applies it to both the HTTPClient and
// nativeHTTPClient. It should not be called concurrently with any other Client
// methods.
func (c *Client) SetTimeout(t time.Duration) {
if c.HTTPClient != nil {
c.HTTPClient.Timeout = t
}
if c.nativeHTTPClient != nil {
c.nativeHTTPClient.Timeout = t
c.configLock.Lock()
defer c.configLock.Unlock()
if c.httpClient != nil {
c.httpClient.Timeout = t
}
}
@ -171,6 +212,8 @@ func (c *Client) checkAPIVersion() error {
if err != nil {
return err
}
c.configLock.Lock()
defer c.configLock.Unlock()
if c.requestedAPIVersion == 0 {
c.expectedAPIVersion = c.serverAPIVersion
} else {
@ -191,8 +234,7 @@ func (c *Client) Ping() error {
if resp.StatusCode != http.StatusOK {
return newError(resp)
}
resp.Body.Close()
return nil
return resp.Body.Close()
}
func (c *Client) getServerAPIVersionString() (version string, err error) {
@ -204,16 +246,19 @@ func (c *Client) getServerAPIVersionString() (version string, err error) {
}
type doOptions struct {
data interface{}
context context.Context
data interface{}
values url.Values
headers map[string]string
fieldSelector string
labelSelector string
namespace string
forceJSON bool
force bool
values url.Values
headers map[string]string
unversioned bool
context context.Context
forceJSON bool
force bool
unversioned bool
}
func (c *Client) do(method, urlpath string, doOptions doOptions) (*http.Response, error) {
@ -247,76 +292,118 @@ func (c *Client) do(method, urlpath string, doOptions doOptions) (*http.Response
query.Add("force", "1")
}
httpClient := c.HTTPClient
u := c.getAPIPath(urlpath, query, doOptions.unversioned)
// Obtain a reader lock to prevent the http client from being
// modified underneath us during a do().
c.configLock.RLock()
defer c.configLock.RUnlock() // This defer matches both the initial and the above lock
req, err := http.NewRequest(method, u, params)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if doOptions.data != nil {
req.Header.Set("Content-Type", "application/json")
} else if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
if c.username != "" && c.secret != "" {
req.SetBasicAuth(c.username, c.secret)
}
for k, v := range doOptions.headers {
req.Header.Set(k, v)
}
httpClient := c.httpClient
endpoint := c.getAPIPath(urlpath, query, doOptions.unversioned)
// The doOptions Context is shared for every attempted request in the do.
ctx := doOptions.context
if ctx == nil {
ctx = context.Background()
}
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) {
var failedAddresses = map[string]struct{}{}
c.addressLock.Lock()
var addresses = make([]string, len(c.addresses))
copy(addresses, c.addresses)
c.addressLock.Unlock()
for _, address := range addresses {
target := address + endpoint
req, err := http.NewRequest(method, target, params)
if err != nil {
// Probably should not try and continue if we're unable
// to create the request.
return nil, err
}
if strings.Contains(err.Error(), "connection refused") {
return nil, ErrConnectionRefused
req.Header.Set("User-Agent", c.userAgent)
if doOptions.data != nil {
req.Header.Set("Content-Type", "application/json")
} else if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
if c.username != "" && c.secret != "" {
req.SetBasicAuth(c.username, c.secret)
}
return nil, chooseError(ctx, err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, newError(resp)
}
return resp, nil
}
// if error in context, return that instead of generic http error
func chooseError(ctx context.Context, err error) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return err
for k, v := range doOptions.headers {
req.Header.Set(k, v)
}
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) {
switch serror.ErrorKind(err) {
case serror.APIUncontactable:
// If API isn't contactable we should try the next address
failedAddresses[address] = struct{}{}
continue
case serror.InvalidHostConfig:
// If invalid host or unknown error, we should report back
fallthrough
case serror.UnknownError:
return nil, err
}
}
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
if _, ok := err.(net.Error); ok {
// Be optimistic and try the next endpoint
failedAddresses[address] = struct{}{}
continue
}
return nil, err
}
}
// If we get to the point of response, we should move any failed
// addresses to the back.
failed := len(failedAddresses)
if failed > 0 {
// Copy addresses we think are okay into the head of the list
newOrder := make([]string, 0, len(addresses)-failed)
for _, addr := range addresses {
if _, exists := failedAddresses[addr]; !exists {
newOrder = append(newOrder, addr)
}
}
for addr := range failedAddresses {
newOrder = append(newOrder, addr)
}
c.addressLock.Lock()
// Bring in the new order
c.addresses = newOrder
c.addressLock.Unlock()
}
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, newError(resp) // These status codes are likely to be fatal
}
return resp, nil
}
return nil, netutil.ErrAllFailed(addresses)
}
func (c *Client) getAPIPath(path string, query url.Values, unversioned bool) string {
// 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 = strings.TrimLeft(path, "/")
var apiPath string
path = strings.TrimLeft(path, "/")
if unversioned {
apiPath = fmt.Sprintf("%s/%s", urlStr, path)
if !unversioned {
apiPath = fmt.Sprintf("/%s/%s", c.requestedAPIVersion, apiPath)
} else {
apiPath = fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
apiPath = fmt.Sprintf("/%s", apiPath)
}
if len(query) > 0 {
@ -441,23 +528,14 @@ func (e *Error) Error() string {
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(d Dialer) *http.Transport {
transport := defaultPooledTransport(d)
transport.DisableKeepAlives = true
transport.MaxIdleConnsPerHost = -1
return transport
}
// defaultPooledTransport returns a new http.Transport with similar default
// 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(d Dialer) *http.Transport {
func defaultPooledTransport(dialer Dialer) *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: d.Dial,
Dial: dialer.Dial,
TLSHandshakeTimeout: 5 * time.Second,
DisableKeepAlives: false,
MaxIdleConnsPerHost: 1,
@ -469,15 +547,13 @@ func defaultPooledTransport(d Dialer) *http.Transport {
// http.Client, but with a non-shared Transport, idle connections disabled, and
// keepalives disabled.
// 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,
}
func defaultClient() *http.Client {
dialer := &net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 5 * time.Second,
}
return &http.Client{
Transport: defaultTransport(d),
Transport: defaultPooledTransport(dialer),
}
}

View File

@ -1,110 +0,0 @@
package storageos
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// ControllerAPIPrefix is a partial path to the HTTP endpoint.
ControllerAPIPrefix = "controllers"
// ErrNoSuchController is the error returned when the controller does not exist.
ErrNoSuchController = errors.New("no such controller")
// ErrControllerInUse is the error returned when the controller requested to be removed is still in use.
ErrControllerInUse = errors.New("controller in use and cannot be removed")
)
// ControllerList returns the list of available controllers.
func (c *Client) ControllerList(opts types.ListOptions) ([]*types.Controller, error) {
listOpts := doOptions{
fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector,
context: opts.Context,
}
if opts.LabelSelector != "" {
query := url.Values{}
query.Add("labelSelector", opts.LabelSelector)
listOpts.values = query
}
resp, err := c.do("GET", ControllerAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var controllers []*types.Controller
if err := json.NewDecoder(resp.Body).Decode(&controllers); err != nil {
return nil, err
}
return controllers, nil
}
// Controller returns a controller by its reference.
func (c *Client) Controller(ref string) (*types.Controller, error) {
resp, err := c.do("GET", ControllerAPIPrefix+"/"+ref, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchController
}
return nil, err
}
defer resp.Body.Close()
var controller types.Controller
if err := json.NewDecoder(resp.Body).Decode(&controller); err != nil {
return nil, err
}
return &controller, nil
}
// ControllerUpdate updates a controller on the server.
func (c *Client) ControllerUpdate(opts types.ControllerUpdateOptions) (*types.Controller, error) {
ref := opts.Name
if IsUUID(opts.ID) {
ref = opts.ID
}
resp, err := c.do("PUT", ControllerAPIPrefix+"/"+ref, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var controller types.Controller
if err := json.NewDecoder(resp.Body).Decode(&controller); err != nil {
return nil, err
}
return &controller, nil
}
// ControllerDelete removes a controller by its reference.
func (c *Client) ControllerDelete(opts types.DeleteOptions) error {
deleteOpts := doOptions{
namespace: opts.Namespace,
force: opts.Force,
context: opts.Context,
}
resp, err := c.do("DELETE", ControllerAPIPrefix+"/"+opts.Name, deleteOpts)
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchController
}
if e.Status == http.StatusConflict {
return ErrControllerInUse
}
}
return err
}
defer resp.Body.Close()
return nil
}

View File

@ -1,189 +0,0 @@
package storageos
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"time"
"github.com/gorilla/websocket"
"github.com/storageos/go-api/types"
)
var (
// EventAPIPrefix is a partial path to the HTTP endpoint.
EventAPIPrefix = "event"
// ErrNoSuchEvent is the error returned when the event does not exist.
ErrNoSuchEvent = errors.New("no such event")
)
// EventList returns the list of available events.
func (c *Client) EventList(opts types.ListOptions) ([]*types.Event, error) {
listOpts := doOptions{
fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector,
context: opts.Context,
}
resp, err := c.do("GET", EventAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var events []*types.Event
if err := json.NewDecoder(resp.Body).Decode(&events); err != nil {
return nil, err
}
return events, nil
}
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an io.EOF error will
// be sent over the error channel. If an error is sent all processing will be stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (c *Client) Events(ctx context.Context, opts types.ListOptions) (<-chan types.Request, <-chan error) {
// listOpts := doOptions{
// fieldSelector: opts.FieldSelector,
// labelSelector: opts.LabelSelector,
// context: ctx,
// }
messages := make(chan types.Request)
errs := make(chan error, 1)
// started := make(chan struct{})
ws, _, err := websocket.DefaultDialer.Dial("ws://10.245.103.2:8000/v1/ws/event", nil)
if err != nil {
// close(started)
// errs <- err
log.Fatal(err)
}
// defer ws.Close()
done := make(chan struct{})
go func() {
defer ws.Close()
defer close(done)
for {
_, message, err := ws.ReadMessage()
if err != nil {
log.Println("read:", err)
errs <- err
return
}
// log.Printf("recv: %s", message)
var request types.Request
if err := json.Unmarshal(message, &request); err != nil {
log.Printf("decode error: %s", message)
errs <- err
return
}
messages <- request
}
}()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
go func() {
for {
select {
case t := <-ticker.C:
log.Printf("tick: %s\n", t.String())
err := ws.WriteMessage(websocket.TextMessage, []byte(t.String()))
if err != nil {
log.Println("write:", err)
return
}
case <-ctx.Done():
log.Println("done")
err := ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
errs <- ctx.Err()
select {
case <-done:
case <-time.After(time.Second):
}
ws.Close()
return
}
}
}()
// go func() {
// defer ws.Close()
// defer close(errs)
//
// // query, err := buildEventsQueryParams(cli.version, options)
// // if err != nil {
// // close(started)
// // errs <- err
// // return
// // }
//
// // resp, err := cli.get(ctx, "/events", query, nil)
//
// // decoder := json.NewDecoder(resp.Body)
//
// close(started)
// for {
// select {
// case <-ctx.Done():
// log.Println("done")
// errs <- ctx.Err()
// return
// default:
// log.Println("default")
// _, message, err := ws.ReadMessage()
// if err != nil {
// log.Println("read:", err)
// return
// }
// log.Printf("recv: %s", message)
// var event types.Event
// if err := json.Unmarshal(message, &event); err != nil {
// log.Printf("decode error: %s", message)
// errs <- err
// return
// }
// log.Printf("sent: %v", event)
// messages <- event
//
// // select {
// // case messages <- event:
// // case <-ctx.Done():
// // errs <- ctx.Err()
// // return
// // }
// }
// }
// }()
// <-started
log.Println("returning")
return messages, errs
}
// Event returns a event by its reference.
func (c *Client) Event(ref string) (*types.Event, error) {
resp, err := c.do("GET", EventAPIPrefix+"/"+ref, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchEvent
}
return nil, err
}
defer resp.Body.Close()
var event types.Event
if err := json.NewDecoder(resp.Body).Decode(&event); err != nil {
return nil, err
}
return &event, nil
}

View File

@ -23,12 +23,14 @@ func (c *Client) CPHealth(ctx context.Context, hostname string) (*types.CPHealth
return nil, err
}
req.Header.Set("User-Agent", userAgent)
req.Header.Set("User-Agent", c.userAgent)
if c.username != "" && c.secret != "" {
req.SetBasicAuth(c.username, c.secret)
}
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
c.configLock.RLock()
resp, err := c.httpClient.Do(req.WithContext(ctx))
c.configLock.RUnlock()
if err != nil {
return nil, err
}
@ -51,12 +53,14 @@ func (c *Client) DPHealth(ctx context.Context, hostname string) (*types.DPHealth
return nil, err
}
req.Header.Set("User-Agent", userAgent)
req.Header.Set("User-Agent", c.userAgent)
if c.username != "" && c.secret != "" {
req.SetBasicAuth(c.username, c.secret)
}
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
c.configLock.RLock()
resp, err := c.httpClient.Do(req.WithContext(ctx))
c.configLock.RUnlock()
if err != nil {
return nil, err
}

View File

@ -8,7 +8,8 @@ import (
var (
// LoginAPIPrefix is a partial path to the HTTP endpoint.
LoginAPIPrefix = "auth/login"
ErrLoginFailed = errors.New("Failed to get token from API endpoint")
// ErrLoginFailed is the error returned on an unsuccessful login.
ErrLoginFailed = errors.New("failed to get token from API endpoint")
)
// Login attemps to get a token from the API

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)
}

View File

@ -0,0 +1,35 @@
package storageos
import (
"encoding/json"
"net/http"
"path"
"github.com/storageos/go-api/types"
)
var (
// NetworkDiagnosticsAPIPrefix is a partial path to the HTTP endpoint for
// the node connectivity diagnostics report.
NetworkDiagnosticsAPIPrefix = "diagnostics/network"
)
// NetworkDiagnostics returns a collection of network connectivity reports. If
// a reference to a node is given, it will only check connectivity from that
// node. Otherwise, connectivity between all cluster nodes will be returned.
func (c *Client) NetworkDiagnostics(ref string) (types.ConnectivityResults, error) {
resp, err := c.do("GET", path.Join(NetworkDiagnosticsAPIPrefix, ref), doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchNode
}
return nil, err
}
defer resp.Body.Close()
var results types.ConnectivityResults
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, err
}
return results, nil
}

110
vendor/github.com/storageos/go-api/node.go generated vendored Normal file
View File

@ -0,0 +1,110 @@
package storageos
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// NodeAPIPrefix is a partial path to the HTTP endpoint.
NodeAPIPrefix = "nodes"
// ErrNoSuchNode is the error returned when the node does not exist.
ErrNoSuchNode = errors.New("no such node")
// ErrNodeInUse is the error returned when the node requested to be removed is still in use.
ErrNodeInUse = errors.New("node in use and cannot be removed")
)
// NodeList returns the list of available nodes.
func (c *Client) NodeList(opts types.ListOptions) ([]*types.Node, error) {
listOpts := doOptions{
fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector,
context: opts.Context,
}
if opts.LabelSelector != "" {
query := url.Values{}
query.Add("labelSelector", opts.LabelSelector)
listOpts.values = query
}
resp, err := c.do("GET", NodeAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var nodes []*types.Node
if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil {
return nil, err
}
return nodes, nil
}
// Node returns a node by its reference.
func (c *Client) Node(ref string) (*types.Node, error) {
resp, err := c.do("GET", NodeAPIPrefix+"/"+ref, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchNode
}
return nil, err
}
defer resp.Body.Close()
var node types.Node
if err := json.NewDecoder(resp.Body).Decode(&node); err != nil {
return nil, err
}
return &node, nil
}
// NodeUpdate updates a node on the server.
func (c *Client) NodeUpdate(opts types.NodeUpdateOptions) (*types.Node, error) {
ref := opts.Name
if IsUUID(opts.ID) {
ref = opts.ID
}
resp, err := c.do("PUT", NodeAPIPrefix+"/"+ref, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var node types.Node
if err := json.NewDecoder(resp.Body).Decode(&node); err != nil {
return nil, err
}
return &node, nil
}
// NodeDelete removes a node by its reference.
func (c *Client) NodeDelete(opts types.DeleteOptions) error {
deleteOpts := doOptions{
namespace: opts.Namespace,
force: opts.Force,
context: opts.Context,
}
resp, err := c.do("DELETE", NodeAPIPrefix+"/"+opts.Name, deleteOpts)
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchNode
}
if e.Status == http.StatusConflict {
return ErrNodeInUse
}
}
return err
}
defer resp.Body.Close()
return nil
}

View File

@ -5,16 +5,15 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/storageos/go-api/types"
"net/http"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// PolicyAPIPrefix is a partial path to the HTTP endpoint.
PolicyAPIPrefix = "policies"
// ErrNoSuchPolicy is the error returned when the policy does not exist.
ErrNoSuchPolicy = errors.New("no such policy")
)

View File

@ -41,7 +41,7 @@ func (c *Client) PoolList(opts types.ListOptions) ([]*types.Pool, error) {
}
// PoolCreate creates a pool on the server and returns the new object.
func (c *Client) PoolCreate(opts types.PoolCreateOptions) (*types.Pool, error) {
func (c *Client) PoolCreate(opts types.PoolOptions) (*types.Pool, error) {
resp, err := c.do("POST", PoolAPIPrefix, doOptions{
data: opts,
context: opts.Context,
@ -56,6 +56,27 @@ func (c *Client) PoolCreate(opts types.PoolCreateOptions) (*types.Pool, error) {
return &pool, nil
}
// PoolUpdate - update pool
func (c *Client) PoolUpdate(opts types.PoolOptions) (*types.Pool, error) {
ref := opts.Name
if IsUUID(opts.ID) {
ref = opts.ID
}
resp, err := c.do("PUT", PoolAPIPrefix+"/"+ref, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
var pool types.Pool
if err := json.NewDecoder(resp.Body).Decode(&pool); err != nil {
return nil, err
}
return &pool, nil
}
// Pool returns a pool by its reference.
func (c *Client) Pool(ref string) (*types.Pool, error) {
resp, err := c.do("GET", PoolAPIPrefix+"/"+ref, doOptions{})

View File

@ -3,7 +3,6 @@ package storageos
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
@ -98,7 +97,7 @@ func (c *Client) RuleUpdate(opts types.RuleUpdateOptions) (*types.Rule, error) {
if IsUUID(opts.ID) {
ref = opts.ID
}
fmt.Printf("%#v\n", opts)
path, err := namespacedRefPath(opts.Namespace, RuleAPIPrefix, ref)
if err != nil {
return nil, err

View File

@ -1,769 +0,0 @@
docs.storageos.com - API webpage
================================
TODO: Openind paragraph of text has multiple typos and grammar errors. Need to fix this. [DONE]
TODO: hard to see the text for the example JSON post messages on the RHS. Possibly a Safari thing?!
TODO: some of the JSON examples on the RHS of the webpage look wrong?! Safari issue?
TODO: docs should use all lowercase for consistency?
VOLUMES
=======
List volumes
============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - no provisioned volumes.
- No auth username and password supplied.
-->
GET: 172.28.128.3:5705/v1/namespaces/default/volumes
<--
401 UNAUTHORIZED
Unauthorized
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - no provisioned volumes.
- bad namespace used.
-->
GET: 172.28.128.3:5705/v1/namespaces/defaultxxx/volumes
<--
404 NOT FOUND
JSON: "message": "Not found"
Unauthorized
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - no provisioned volumes.
- Dropped the {namespace} path parameter.
-->
GET: 172.28.128.3:5705/v1/namespaces/volumes
<--
404 NOT FOUND
Not found
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - no provisioned volumes.
- One volume has already been created successfully.
-->
GET: 172.28.128.3:5705/v1/namespaces/volumes
<--
JSON: valid volume/inode state data (note: this time the master data is correctly set i.e. has non-zero data)
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster - single provisioned volumes.
-->
GET: 172.28.128.3:5705/v1/namespaces/volumes
<--
JSON: valid volume/inode state data.
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster - multiple provisioned volumes.
-->
GET: 172.28.128.3:5705/v1/namespaces/volumes
<--
JSON: an array of valid volume/inode state data for multiple volumes.
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster - multiple provisioned volumes.
- labelSelector
-->
GET: 172.28.128.3:5705/v1/namespaces/volumes?labelSelector=com.example.some-label
<--
JSON: an array of valid volume/inode state data for multiple volumes.
-FAILED- doesn't appear to filter.
XXX:
===============================================================================
SCENARIO:
- 3 node cluster - multiple provisioned volumes.
- fieldSelector
-->
GET: 172.28.128.3:5705/v1/namespaces/volumes?fieldSelector=<????>
<--
JSON: an array of valid volume/inode state data for multiple volumes.
-UNTESTED- don't know what to put for fieldSelector's value??
XXX:
===============================================================================
Create volumes
==============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - no provisioned volumes.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"name": "redis-vol01",
"size": 1
}
<--
JSON: valid volume/inode state data (note: master data is all zerod at this point)
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with a single volume already provisioned.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"name": "redis-vol02",
"size": 2
}
<--
JSON: valid volume/inode state data (note: master data is all zerod at this point)
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with two volumes already provisioned.
- Now trying to provision a third with bad JSON body -- using CAPITAL first letters for Name and Size.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"Name": "redis-vol03",
"Size": 3
}
<--
JSON: valid volume/inode state data (note: master data is all zerod at this point)
-EXPECTED- WORKS?! This implies that the JSON keys are non-case sensitive.
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with two volumes already provisioned.
- Now trying to provision a third with bad JSON body -- using all CAPITALS for NAME and SIZE.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"NAME": "redis-vol03",
"SIZE": 3
}
<--
JSON: valid volume/inode state data (note: master data is all zerod at this point)
-EXPECTED- WORKS?! This implies that the JSON keys are non-case sensitive.
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with two volumes already provisioned.
- Now trying to provision a volume with bad JSON body -- missing size parameter.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"name": "redis-vol05",
}
<--
JSON: valid volume/inode state data (note: master data is all zerod at this point)
-EXPECTED- Size defaults to 10
TODO: update documentation to reflect this. [DONE]
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with two volumes already provisioned.
- Now trying to provision a volume with bad JSON body -- with size (0) parameter.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"name": "redis-vol05",
"size" 0
}
<--
JSON: valid volume/inode state data (note: master data is all zerod at this point)
-EXPECTED- Size defaults to 10
TODO: update documentation to reflect this. [DONE]
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with two volumes already provisioned.
- Now trying to provision a volume with bad JSON body -- empty JSON object.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
}
<--
volume name not valid
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - with two volumes already provisioned.
- Now trying to provision a volume with no JSON body -- empty
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
<--
JSON: "message": "EOF"
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster.
- Now trying to provision a volume with no JSON name field
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"size": 2
}
<--
volume name not valid
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster.
- Now trying to provision a volume with no JSON name field
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"size": 2
}
<--
volume name not valid
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster.
- Now trying to provision a volume with same name as one that has already been provisioned.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"name": "myvol1",
"size": 5
}
<--
volume with name 'myvol1' already exists
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster.
- Now trying to provision a volume with pool name.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"name": "myvol1",
"size": 5,
"pool": "mypool1"
}
<--
JSON: valid volume/inode state data with correct pool field.
-EXPECTED-
===============================================================================
SCENARIO:
- 3 node cluster.
- Now trying to provision a volume with optional labels.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes
BODY (JSON):
{
"Name": "vol01",
"Size": 5,
"Labels": {
"com.example.some-label": "some-value",
"com.example.some-other-label": "some-other-value"
}
}
<--
JSON: valid volume/inode state data with correct labels.
-EXPECTED-
===============================================================================
Get volumes
===========
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Get by name.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol03
<--
JSON: correct volume/inode state data.
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Get by id.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes/270c1fc2-c578-77f8-2d7c-1515e626b6c3
<--
JSON: correct volume/inode state data.
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Passing bad name/id.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes/this-volume-does-not-exist
<--
Not Found
-EXPECTED-
===============================================================================
Update volumes
==============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Update a volume
-->
PUT: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol03
JSON:
{
"Description": "string",
"Size": 5,
"Labels": {
"property1": "string",
"property2": "string"
}
}
<--
200 OK
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Update a volume, trying with bad JSON, missing opening curly brace!
-->
PUT: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol03
JSON:
"Description": "string",
"Size": 5,
"Labels": {
"property1": "string",
"property2": "string"
}
}
<--
400 BAD REQUEST
Request decode failed: json: cannot unmarshal string into Go value of type types.Volume
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Update a volume, trying with size (0) and same property1 and new property3.
-->
PUT: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol03
JSON:
{
"Description": "string",
"Size": 0,
"Labels": {
"property1": "string",
"property3": "string3"
}
}
<--
200 OK
-NOT EXPECTED-
The old labels are completely overwritten anew (hence the previous property2 label is not present). I assume this is the desired behaviour?!
TODO: However size is now zero?! Check this is ok! Probably not; since the Create volume API defaults to 10 when 0 is passed or omitted.
XXX:
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Update a volume, trying with omitting size parameter
-->
PUT: 172.28.128.3:5705/v1/namespaces/default/volumes/volxyz
JSON:
{
"Description": "string",
"Labels": {
"property1": "string",
"property3": "string3"
}
}
<--
200 OK
-NOT EXPECTED-
XXX: size is now zero when not passing size in JSON body of PUT request.
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Update a volume, trying with omitting description parameter
-->
PUT: 172.28.128.3:5705/v1/namespaces/default/volumes/volxyz
JSON:
{
"Labels": {
"property1": "string",
"property3": "string3"
}
}
<--
200 OK
-NOT EXPECTED-
XXX: description string is empty i.e. "" when not passing description in JSON body of PUT request.
The above implies that the update volume PUT request receiving side interprets missing update parameters as their null-value counterparts. So it's is not possible to update just specific parameters.
===============================================================================
Delete volumes
==============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Delete a volume. But not specifying a name in the path.
-->
DELETE: 172.28.128.3:5705/v1/namespaces/default/volumes
<--
404 NOT FOUND
404 page not found
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Delete a volume by specifying the volume's name.
-->
DELETE: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol05
<--
200 OK
-EXPECTED-
TODO: But when doing a GET ~volumes/redis-vol05 it is still present So DELETE volumes doesn't appear to be working.
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Delete a volume by specifying the volume's id.
-->
DELETE: 172.28.128.3:5705/v1/namespaces/default/volumes/5233930b-b77f-2863-0895-b1eb5d73ec45
<--
200 OK
-EXPECTED-
TODO: But when doing a GET ~volumes/5233930b-b77f-2863-0895-b1eb5d73ec45 it is still present So DELETE volumes doesn't appear to be working.
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Trying to delete a mounted volume
-->
DELETE: 172.28.128.3:5705/v1/namespaces/default/volumes/volxyz
<--
412 PRECONDITION FAILED
cannot delete mounted volume
-EXPECTED-
TODO: seems correct, this hints that the mount is working which is in conflict with my observation below for MOUNT and UNMOUNT. Q. Is it checking the mount status via the OS, or by some other means i.e. locally cached value?
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Trying to delete a unmounted volume
-->
DELETE: 172.28.128.3:5705/v1/namespaces/default/volumes/volxyz
<--
200 OK
-EXPECTED-
===============================================================================
Mount volumes
=============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Mount a volume. But not specifying a name in the path.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol02/mount
JSON:
{
"client": "storageos-1-68228"
}
<--
200 OK
-EXPECTED-
NOTE: in the JSON response, no_of_mounts does increase correctly by one each time. And mounted is set to true correctly.
TODO: although this worked, the documentation doesn't give the proper JSON request body.
TODO: no_of_mounts is still 0 in /var/lib/storageos/state/inode/178101 (should increase by 1 for every mount.)
BUG^
TODO: Also not sure if really mounted this volume, since running the storageos cli e.g.:
$ ./storageos volume ls
The MOUNTED BY column is always empty ?? Either cli doesn't show this info, yet. Or the volume isn't mounted.
===============================================================================
Unmount volumes
===============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Mount a volume. But not specifying a name in the path.
-->
POST: 172.28.128.3:5705/v1/namespaces/default/volumes/redis-vol02/unmount
JSON:
{
"can-be-anything": "storageos-1-68228"
}
<--
200 OK
-EXPECTED-
NOTE: in the JSON response, mounted is set back to false correctly.
TODO: although this worked, the documentation doesn't give the proper JSON request body. The
===============================================================================
POOLS
=====
List pools
==========
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster.
- No auth username and password supplied.
-->
GET: 172.28.128.3:5705/v1/pools
<--
401 UNAUTHORIZED
Unauthorized
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster. No pools.
-->
GET: 172.28.128.3:5705/v1/pools
<--
200 OK
JSON: an array of pools.
-EXPECTED-
===============================================================================
Create pools
============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster.
-->
POST: 172.28.128.3:5705/v1/volumes
BODY (JSON):
{
"name": "mypool1"
}
<--
201 CREATED
JSON: valid pool data.
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster.
- Trying to create a pool with the same name as another already created.
-->
POST: 172.28.128.3:5705/v1/volumes
BODY (JSON):
{
"name": "mypool1"
}
<--
409 CONFLICT
Pool with name 'mypool1' already exists
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster.
- Trying to create a pool with the same name as another already created.
-->
POST: 172.28.128.3:5705/v1/volumes
BODY (JSON):
{
"name": "mypool2",
"description": "hello world!"
}
<--
201 CREATED
JSON: valid pool data.
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster.
- Trying to create a pool with the defaultDriver string set.
-->
POST: 172.28.128.3:5705/v1/volumes
BODY (JSON):
{
"name": "mypool6",
"description": "hello world again!",
"default": true,
"defautDriver": "I'm the default driver :)"
}
<--
201 CREATED
JSON: Is correct for the most part, but defaultDriver is an empty string??
-NOT EXPECTED-
XXX
===============================================================================
Get Pools
=========
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Get by name.
-->
POST: 172.28.128.3:5705/v1/pools/mypool1
<--
200 OK
JSON: correct pool data.
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Get by id.
-->
POST: 172.28.128.3:5705/v1/pools/ea477d68-8193-1179-d889-aa6ea8797082
<--
200 OK
JSON: correct pool data.
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned volumes.
- Get by name. Try passing invalid name.
-->
POST: 172.28.128.3:5705/v1/pools/mypool1xxx
<--
404 NOT FOUND
Not Found
-EXPECTED-
===============================================================================
Delete Pools
============
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned pools.
- Trying to delete without specifying name etc. in the path.
-->
POST: 172.28.128.3:5705/v1/pools
<--
404 NOT FOUND
404 page not found
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned pools.
- Delete by name.
-->
POST: 172.28.128.3:5705/v1/pools/mypool2
<--
200 OK
-EXPECTED-
===============================================================================
SCENARIO:
- Fresh start - 3 node cluster - N provisioned pools.
- Delete by id.
-->
POST: 172.28.128.3:5705/v1/pools/9a10bbfe-eaaa-af3c-2a9b-d78e0790efb4
<--
200 OK
-EXPECTED-
===============================================================================

View File

@ -5,21 +5,22 @@ go_library(
srcs = [
"auth.go",
"capacity_stats.go",
"controller.go",
"controller_update_options.go",
"connectivity.go",
"delete_options.go",
"deployment.go",
"driver_instance.go",
"error_response.go",
"events.go",
"health.go",
"list_options.go",
"logger.go",
"namespace.go",
"node.go",
"node_update_options.go",
"operator.go",
"policy.go",
"pool.go",
"pool_create_options.go",
"pool_options.go",
"rule.go",
"template.go",
"template_create_options.go",
@ -27,6 +28,7 @@ go_library(
"version.go",
"volume.go",
"volume_create_options.go",
"volume_stats.go",
"volume_update_options.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/storageos/go-api/types",

View File

@ -0,0 +1,41 @@
package types
import "time"
// ConnectivityResult capture's a node connectivity report to a given target.
type ConnectivityResult struct {
// Label is a human-readable reference for the service being tested.
Label string `json:"label"`
// Address is the host:port of the service being tested.
Address string `json:"address"`
// Source is a human-readable reference for the source host where the tests
// were run from.
Source string `json:"source"`
// LatencyNS is the duration in nanoseconds that the check took to complete.
// Will also be set on unsuccessful attempts.
LatencyNS time.Duration `json:"latency_ns"`
// Error is set if the test returned an error.
Error string `json:"error"`
}
// IsOK returns true iff no error
func (r ConnectivityResult) IsOK() bool {
return len(r.Error) == 0
}
// ConnectivityResults is a collection of connectivty reports.
type ConnectivityResults []ConnectivityResult
// IsOK returns true iff no error in any result.
func (r ConnectivityResults) IsOK() bool {
for _, result := range r {
if !result.IsOK() {
return false
}
}
return true
}

View File

@ -1,78 +0,0 @@
package types
// Versions and Prefixes used in API and KV URLs
import "time"
const (
ControllerAPIPrefix = "controller"
ControllerDefaultPort = "3260"
ControllerScanAPIPrefix = "config/scan"
)
// ControllerCurrent - current controller
var ControllerCurrent = ""
// Controller status phases
const (
ControllerStatusPending = "pending"
ControllerStatusEvaluating = "evaluating"
ControllerStatusDeploying = "deploying"
ControllerStatusActive = "active"
ControllerStatusFailed = "failed"
ControllerStatusDeleting = "deleting"
ControllerHealthStarting = "starting"
ControllerHealthOK = "healthy"
ControllerHealthDegraded = "degraded"
ControllerHealthOffline = "offline"
)
// Errors for controller related things
const (
ErrControllerHostIDAllocation string = "error, could not allocate hostid"
ErrControllerIDNotSet = "error, controller ID not set"
ErrControllerNotFound = "controller not found"
)
// Controller is used to represent a storage node in a cluster
type Controller struct {
ID string `json:"id,omitempty"`
HostID uint16 `json:"hostID"`
Scheduler bool `json:"scheduler"`
Name string `json:"name"`
Address string `json:"address"`
DeviceDir string `json:"deviceDir"`
APIPort int `json:"apiPort"`
NatsPort int `json:"natsPort"`
NatsClusterPort int `json:"natsClusterPort"`
SerfPort int `json:"serfPort"`
DFSPort int `json:"dfsPort"`
Description string `json:"description"`
ControllerGroups []string `json:"controllerGroups"`
Tags []string `json:"tags"`
Labels map[string]string `json:"labels"`
VolumeStats VolumeStats `json:"volumeStats"`
PoolStats map[string]DriverStats `json:"poolStats"`
// health is updated by the
Health string `json:"health"`
HealthUpdatedAt time.Time `json:"healthUpdatedAt"`
VersionInfo map[string]VersionInfo `json:"versionInfo"`
Version string `json:"version"`
// Cordon true if in an unschedulable state
Cordon bool `json:"unschedulable"`
// high level stats that combine info from all driver instances
CapacityStats CapacityStats `json:"capacityStats"`
}
// DriverStats is used to report stats for all drivers in a pool.
type DriverStats map[string]CapacityStats
// VolumeStats - volume stats (volume counts, looking forward to capacity)
type VolumeStats struct {
MasterVolumeCount int `json:"masterVolumeCount"`
ReplicaVolumeCount int `json:"replicaVolumeCount"`
VirtualVolumeCount int `json:"virtualVolumeCount"`
}

View File

@ -14,13 +14,13 @@ type Deployment struct {
// Read Only: true
Inode uint32 `json:"inode"`
// Controller ID
// Node ID
// Read Only: true
Controller string `json:"controller"`
Node string `json:"node"`
// Controller name
// Node name
// Read Only: true
ControllerName string `json:"controllerName"`
NodeName string `json:"nodeName"`
// Health
// Read Only: true

134
vendor/github.com/storageos/go-api/types/health.go generated vendored Normal file
View File

@ -0,0 +1,134 @@
package types
import "encoding/json"
type SubModuleStatus struct {
Status string `json:"status"`
UpdatedAt string `json:"updatedAt"`
ChangedAt string `json:"changedAt"`
Message string `json:"message"`
}
type NamedSubModuleStatus struct {
Name string
SubModuleStatus
}
type CPHealthStatus struct {
KV SubModuleStatus
KVWrite SubModuleStatus
NATS SubModuleStatus
Scheduler SubModuleStatus
}
func (c *CPHealthStatus) ToNamedSubmodules() []NamedSubModuleStatus {
return []NamedSubModuleStatus{
{Name: "nats", SubModuleStatus: c.NATS},
{Name: "kv", SubModuleStatus: c.KV},
{Name: "kv_write", SubModuleStatus: c.KVWrite},
{Name: "scheduler", SubModuleStatus: c.Scheduler},
}
}
func (c *CPHealthStatus) UnmarshalJSON(data []byte) error {
unmarsh := struct {
Submodules struct {
KV SubModuleStatus `json:"kv"`
KVWrite SubModuleStatus `json:"kv_write"`
NATS SubModuleStatus `json:"nats"`
Scheduler SubModuleStatus `json:"scheduler"`
} `json:"submodules"`
}{}
if err := json.Unmarshal(data, &unmarsh); err != nil {
return err
}
c.KV = unmarsh.Submodules.KV
c.KVWrite = unmarsh.Submodules.KVWrite
c.NATS = unmarsh.Submodules.NATS
c.Scheduler = unmarsh.Submodules.Scheduler
return nil
}
type DPHealthStatus struct {
DirectFSClient SubModuleStatus
DirectFSServer SubModuleStatus
Director SubModuleStatus
FSDriver SubModuleStatus
FS SubModuleStatus
}
func (d *DPHealthStatus) ToNamedSubmodules() []NamedSubModuleStatus {
return []NamedSubModuleStatus{
{Name: "dfs_client", SubModuleStatus: d.DirectFSClient},
{Name: "dfs_server", SubModuleStatus: d.DirectFSServer},
{Name: "director", SubModuleStatus: d.Director},
{Name: "fs_driver", SubModuleStatus: d.FSDriver},
{Name: "fs", SubModuleStatus: d.FS},
}
}
func (d *DPHealthStatus) UnmarshalJSON(data []byte) error {
unmarsh := struct {
Submodules struct {
DirectFSClient SubModuleStatus `json:"directfs-client"`
DirectFSServer SubModuleStatus `json:"directfs-server"`
Director SubModuleStatus `json:"director"`
FSDriver SubModuleStatus `json:"filesystem-driver"`
FS SubModuleStatus `json:"fs"`
} `json:"submodules"`
}{}
if err := json.Unmarshal(data, &unmarsh); err != nil {
return err
}
d.DirectFSClient = unmarsh.Submodules.DirectFSClient
d.DirectFSServer = unmarsh.Submodules.DirectFSServer
d.Director = unmarsh.Submodules.Director
d.FSDriver = unmarsh.Submodules.FSDriver
d.FS = unmarsh.Submodules.FS
return nil
}
// HealthStatus is the health status json object.
type HealthStatus struct {
Submodules HealthSubmodules `json:"submodules"`
}
// HealthSubmodules is the "submodules" attribuet of HealthStatus.
type HealthSubmodules struct {
KV SubModuleStatus `json:"kv,omitempty"`
KVWrite SubModuleStatus `json:"kv_write,omitempty"`
NATS SubModuleStatus `json:"nats,omitempty"`
Scheduler SubModuleStatus `json:"scheduler,omitempty"`
DirectFSClient SubModuleStatus `json:"directfs_initiator,omitempty"`
DirectFSServer SubModuleStatus `json:"directfs_responder,omitempty"`
Director SubModuleStatus `json:"director,omitempty"`
FSDriver SubModuleStatus `json:"rdb,omitempty"`
FS SubModuleStatus `json:"presentation,omitempty"`
}
// ToCPHealthStatus returns only CPHealthStatus from the HealthStatus.
func (h *HealthStatus) ToCPHealthStatus() *CPHealthStatus {
return &CPHealthStatus{
KV: h.Submodules.KV,
KVWrite: h.Submodules.KVWrite,
NATS: h.Submodules.KVWrite,
Scheduler: h.Submodules.Scheduler,
}
}
// ToDPHealthStatus returns only DPHealthStatus from the HealthStatus.
func (h *HealthStatus) ToDPHealthStatus() *DPHealthStatus {
return &DPHealthStatus{
DirectFSClient: h.Submodules.DirectFSClient,
DirectFSServer: h.Submodules.DirectFSServer,
Director: h.Submodules.Director,
FSDriver: h.Submodules.FSDriver,
FS: h.Submodules.FS,
}
}

View File

@ -1,97 +1,104 @@
package types
import (
"encoding/json"
"time"
)
type SubModuleStatus struct {
Status string `json:"status"`
UpdatedAt string `json:"updatedAt"`
ChangedAt string `json:"changedAt"`
Message string `json:"message"`
// Node represents a StorageOS cluster node.
type Node struct {
NodeConfig
HostID uint32 `json:"hostID"`
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Health string `json:"health"`
HealthUpdatedAt time.Time `json:"healthUpdatedAt"`
VersionInfo map[string]VersionInfo `json:"versionInfo"`
Version string `json:"version"`
Revision string // the GitCommit this maps to
Scheduler bool `json:"scheduler"`
Cordon bool `json:"cordon"`
Drain bool `json:"drain"`
VolumeStats VolumeStats `json:"volumeStats"`
// PoolStats map[string]map[string]CapacityStats `json:"poolStats"`
CapacityStats CapacityStats `json:"capacityStats"`
}
type NamedSubModuleStatus struct {
Name string
SubModuleStatus
// NodeConfig is a read-only representation of the node's configuration, set at
// start time by environment variables passed to the container or using defaults.
type NodeConfig struct {
// UUID is the unique identifier of the node. It cannot be changed once set.
ID string `json:"id,omitempty"`
// Hostname of the node.
Hostname string `json:"hostname"`
// Address is is used for communication between nodes.
// Nodes will fail to start if the address they first registered with
// changes. This protects against the container being re-scheduled on a
// different host. Nodes will typically use the host server's ip address,
// running the docker container in -net host mode.
Address string `json:"address"`
// KvAddr is the address of the KV store to use for storing configuration.
// It can include the address or FQDN with optional port. Defaults to
// Address/ADVERTISE_IP.
KvAddr string `json:"kvAddr"`
// Port allocations
APIPort int `json:"apiPort"`
NatsPort int `json:"natsPort"`
NatsClusterPort int `json:"natsClusterPort"`
SerfPort int `json:"serfPort"`
DFSPort int `json:"dfsPort"`
KVPeerPort int `json:"kvPeerPort"`
KVClientPort int `json:"kvClientPort"`
Labels map[string]string `json:"labels"`
LogLevel string `json:"logLevel"` // the level of the logs to outout
LogFormat string `json:"logFormat"` // either text or json
LogFilter string `json:"logFilter"` // used to discard messages based on the message's category
// BindAddr is used to control the default address StorageOS binds to. This
// should always be set to 0.0.0.0 (all interfaces).
BindAddr string `json:"bindAddr"`
// DeviceDir is where the volumes are exported. This directory must be
// shared into the container using the rshared volume mount option.
DeviceDir string `json:"deviceDir"`
// Join existing cluster
Join string `json:"join"`
// Backend selects the KV backend, either embedded (testing only) or etcd.
Backend string `json:"kvBackend"`
// EnableDebug is used to enable various debugging features. Used by http
// to enable debug endpoints and as a shortcut to enable debug logging.
EnableDebug bool `json:"debug"`
// Devices specify all devices that are available on the node.
Devices []Device `json:"devices"`
}
type CPHealthStatus struct {
KV SubModuleStatus
KVWrite SubModuleStatus
NATS SubModuleStatus
Scheduler SubModuleStatus
}
func (c *CPHealthStatus) ToNamedSubmodules() []NamedSubModuleStatus {
return []NamedSubModuleStatus{
{Name: "nats", SubModuleStatus: c.NATS},
{Name: "kv", SubModuleStatus: c.KV},
{Name: "kv_write", SubModuleStatus: c.KVWrite},
{Name: "scheduler", SubModuleStatus: c.Scheduler},
}
}
func (c *CPHealthStatus) UnmarshalJSON(data []byte) error {
unmarsh := struct {
Submodules struct {
KV SubModuleStatus `json:"kv"`
KVWrite SubModuleStatus `json:"kv_write"`
NATS SubModuleStatus `json:"nats"`
Scheduler SubModuleStatus `json:"scheduler"`
} `json:"submodules"`
}{}
if err := json.Unmarshal(data, &unmarsh); err != nil {
return err
}
c.KV = unmarsh.Submodules.KV
c.KVWrite = unmarsh.Submodules.KVWrite
c.NATS = unmarsh.Submodules.NATS
c.Scheduler = unmarsh.Submodules.Scheduler
return nil
}
type DPHealthStatus struct {
DirectFSClient SubModuleStatus
DirectFSServer SubModuleStatus
Director SubModuleStatus
FSDriver SubModuleStatus
FS SubModuleStatus
}
func (d *DPHealthStatus) ToNamedSubmodules() []NamedSubModuleStatus {
return []NamedSubModuleStatus{
{Name: "dfs_client", SubModuleStatus: d.DirectFSClient},
{Name: "dfs_server", SubModuleStatus: d.DirectFSServer},
{Name: "director", SubModuleStatus: d.Director},
{Name: "fs_driver", SubModuleStatus: d.FSDriver},
{Name: "fs", SubModuleStatus: d.FS},
}
}
func (d *DPHealthStatus) UnmarshalJSON(data []byte) error {
unmarsh := struct {
Submodules struct {
DirectFSClient SubModuleStatus `json:"directfs-client"`
DirectFSServer SubModuleStatus `json:"directfs-server"`
Director SubModuleStatus `json:"director"`
FSDriver SubModuleStatus `json:"filesystem-driver"`
FS SubModuleStatus `json:"fs"`
} `json:"submodules"`
}{}
if err := json.Unmarshal(data, &unmarsh); err != nil {
return err
}
d.DirectFSClient = unmarsh.Submodules.DirectFSClient
d.DirectFSServer = unmarsh.Submodules.DirectFSServer
d.Director = unmarsh.Submodules.Director
d.FSDriver = unmarsh.Submodules.FSDriver
d.FS = unmarsh.Submodules.FS
return nil
// Device - device type
type Device struct {
ID string
Labels map[string]string `json:"labels"`
Status string `json:"status"`
Identifier string `json:"identifier"`
Class string `json:"class"`
CapacityStats CapacityStats `json:"capacityStats"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}

View File

@ -2,25 +2,26 @@ package types
import "context"
// ControllerUpdateOptions are available parameters for updating existing controllers.
type ControllerUpdateOptions struct {
// NodeUpdateOptions are available parameters for updating existing nodes.
type NodeUpdateOptions struct {
// Controller unique ID.
// Node unique ID.
// Read Only: true
ID string `json:"id"`
// Controller name.
// Node name.
// Read Only: true
Name string `json:"name"`
// Description of the controller.
// Description of the node.
Description string `json:"description"`
// Labels are user-defined key/value metadata.
Labels map[string]string `json:"labels"`
// Cordon sets the controler into an unschedulable state if true
Cordon bool `json:"unschedulable"`
// Cordon marks the node as unschedulable if true
Cordon bool `json:"cordon"`
Drain bool `json:"drain"`
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"`

View File

@ -18,35 +18,19 @@ type Pool struct {
// provisioned without a pool specified. There can only be one default pool.
Default bool `json:"default"`
// DefaultDriver specifies the storage driver to use by default if there are
// multiple drivers in the pool and no driver was specified in the
// provisioning request or assigned by rules. If no driver was specified and
// no default set, driver weight is used to determine the default.
DefaultDriver string `json:"defaultDriver"`
NodeSelector string `json:"nodeSelector"`
// ControllerNames is a list of controller names that are participating in the
// storage pool.
ControllerNames []string `json:"controllerNames"`
// DeviceSelector - specifies a selector to filter node devices based on their labels.
// Only devices from nodes that are in the 'NodeNames' list can be selected
DeviceSelector string `json:"deviceSelector"`
// DriverNames is a list of backend storage drivers that are available in the
// storage pool.
DriverNames []string `json:"driverNames"`
// DriverInstances is used to track instances of each driver. Drivers have a
// default configuration, which can then be customised for each pool where
// they are used, which is representated as a DriverInstance.
// Read Only: true
DriverInstances []*DriverInstance `json:"driverInstances"`
// Flag describing whether the template is active.
// Default: false
Active bool `json:"active"`
// CapacityStats are used to track aggregate capacity usage information across
// all controllers and driver instances.
// Read Only: true
// Populated by the system. Read-only.
CapacityStats CapacityStats `json:"capacityStats"`
// This field is computed based on NodeSelector value
// Populated by the system. Read-only.
Nodes []*Node `json:"nodes"`
// Labels define a list of labels that describe the pool.
Labels map[string]string `json:"labels"`
}

View File

@ -1,42 +0,0 @@
package types
import "context"
// PoolCreateOptions are available parameters for creating new pools.
type PoolCreateOptions struct {
// Pool name.
// Required: true
Name string `json:"name"`
// Pool description.
Description string `json:"description"`
// Default determines whether this pool is the default if a volume is
// provisioned without a pool specified. There can only be one default pool.
Default bool `json:"default"`
// DefaultDriver specifies the storage driver to use by default if there are
// multiple drivers in the pool and no driver was specified in the
// provisioning request or assigned by rules. If no driver was specified and
// no default set, driver weight is used to determine the default.
DefaultDriver string `json:"defaultDriver"`
// ControllerNames is a list of controller names that are participating in the
// storage pool.
ControllerNames []string `json:"controllerNames"`
// DriverNames is a list of backend storage drivers that are available in the
// storage pool.
DriverNames []string `json:"driverNames"`
// Flag describing whether the template is active.
// Default: false
Active bool `json:"active"`
// Labels define a list of labels that describe the pool.
Labels map[string]string `json:"labels"`
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"`
}

View File

@ -0,0 +1,28 @@
package types
import "context"
// PoolOptions are available parameters for creating or updating pools.
type PoolOptions struct {
ID string `json:"id"`
Name string `json:"name"`
// Pool description.
Description string `json:"description"`
// Default determines whether this pool is the default if a volume is
// provisioned without a pool specified. There can only be one default pool.
Default bool `json:"default"`
NodeSelector string `json:"nodeSelector"`
// DeviceSelector - specifies a selector to filter node devices based on their labels.
// Only devices from nodes that are in the 'NodeNames' list can be selected
DeviceSelector string `json:"deviceSelector"`
// Labels define a list of labels that describe the pool.
Labels map[string]string `json:"labels"`
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"`
}

View File

@ -0,0 +1,8 @@
package types
// VolumeStats - volume stats (volume counts, looking forward to capacity)
type VolumeStats struct {
MasterVolumeCount int `json:"masterVolumeCount"`
ReplicaVolumeCount int `json:"replicaVolumeCount"`
VirtualVolumeCount int `json:"virtualVolumeCount"`
}

View File

@ -22,7 +22,9 @@ var (
// collection of restricted characters.
NamePattern = regexp.MustCompile(`^` + NameFormat + `$`)
ErrNoRef = errors.New("no ref provided or incorrect format")
// ErrNoRef is given when the reference given is invalid.
ErrNoRef = errors.New("no ref provided or incorrect format")
// ErrNoNamespace is given when the namespace given is invalid.
ErrNoNamespace = errors.New("no namespace provided or incorrect format")
)