Unverified Commit 9a75b4d7 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #58816 from croomes/storageos_containerized_kubelet

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. StorageOS configurable device directory and mount options **What this PR does / why we need it**: This allows StorageOS volumes to be mounted when the kubelet is running in a container and we are unable to use the default device location (/var/lib/storageos/volumes). With this PR, the node's device location is requested via the StorageOS api, falling back to the current behaviour if not configured. The node's device location can be supplied as an environment variable (DEVICE_DIR) to the StorageOS container. This is backwards-compatible and no changes are needed to existing deployments. The PR also allows Mount options to be set for StorageOS volumes in the same way they're enabled for other volume plugins. The StorageOS API dependency was updated to the latest version, but no functionality changes besides adding the DeviceDir property to the Controller object. There is also a small refactor of the loopback device handling code in storageos_utils.go to capture stderr output. **Release note**: ```release-note StorageOS volume plugin updated to support mount options and environments where the kubelet runs in a container and the device location should be specified. ``` Not sure why godep changed the comments of unrelated packages in Godeps.json... /sig storage
parents 948f28a7 1a91403e
...@@ -2579,11 +2579,23 @@ ...@@ -2579,11 +2579,23 @@
}, },
{ {
"ImportPath": "github.com/storageos/go-api", "ImportPath": "github.com/storageos/go-api",
"Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f" "Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
},
{
"ImportPath": "github.com/storageos/go-api/netutil",
"Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
},
{
"ImportPath": "github.com/storageos/go-api/serror",
"Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
}, },
{ {
"ImportPath": "github.com/storageos/go-api/types", "ImportPath": "github.com/storageos/go-api/types",
"Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f" "Comment": "0.3.4",
"Rev": "3a4032328d99c1b43fbda3d85bd3c80fa06e1707"
}, },
{ {
"ImportPath": "github.com/stretchr/objx", "ImportPath": "github.com/stretchr/objx",
......
...@@ -81241,6 +81241,112 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ...@@ -81241,6 +81241,112 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================ ================================================================================
= vendor/github.com/storageos/go-api/netutil licensed under: =
MIT License
Copyright (c) 2015-2017 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2013-2017, go-dockerclient authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
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/serror licensed under: =
MIT License
Copyright (c) 2015-2017 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2013-2017, go-dockerclient authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
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/types licensed under: = = vendor/github.com/storageos/go-api/types licensed under: =
MIT License MIT License
...@@ -54,7 +54,7 @@ var _ volume.ProvisionableVolumePlugin = &storageosPlugin{} ...@@ -54,7 +54,7 @@ var _ volume.ProvisionableVolumePlugin = &storageosPlugin{}
const ( const (
storageosPluginName = "kubernetes.io/storageos" storageosPluginName = "kubernetes.io/storageos"
storageosDevicePath = "/var/lib/storageos/volumes" defaultDeviceDir = "/var/lib/storageos/volumes"
defaultAPIAddress = "tcp://localhost:5705" defaultAPIAddress = "tcp://localhost:5705"
defaultAPIUser = "storageos" defaultAPIUser = "storageos"
defaultAPIPassword = "storageos" defaultAPIPassword = "storageos"
...@@ -136,8 +136,8 @@ func (plugin *storageosPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod ...@@ -136,8 +136,8 @@ func (plugin *storageosPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod
plugin: plugin, plugin: plugin,
MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, volNamespace, volName, spec.Name(), plugin.host)), MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, volNamespace, volName, spec.Name(), plugin.host)),
}, },
devicePath: storageosDevicePath, diskMounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec},
diskMounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}, mountOptions: volume.MountOptionFromSpec(spec),
}, nil }, nil
} }
...@@ -249,7 +249,7 @@ func (plugin *storageosPlugin) ConstructVolumeSpec(volumeName, mountPath string) ...@@ -249,7 +249,7 @@ func (plugin *storageosPlugin) ConstructVolumeSpec(volumeName, mountPath string)
} }
func (plugin *storageosPlugin) SupportsMountOption() bool { func (plugin *storageosPlugin) SupportsMountOption() bool {
return false return true
} }
func (plugin *storageosPlugin) SupportsBulkVolumeVerification() bool { func (plugin *storageosPlugin) SupportsBulkVolumeVerification() bool {
...@@ -286,6 +286,8 @@ type storageosManager interface { ...@@ -286,6 +286,8 @@ type storageosManager interface {
UnmountVolume(unounter *storageosUnmounter) error UnmountVolume(unounter *storageosUnmounter) error
// Deletes the storageos volume. All data will be lost. // Deletes the storageos volume. All data will be lost.
DeleteVolume(deleter *storageosDeleter) error DeleteVolume(deleter *storageosDeleter) error
// Gets the node's device path.
DeviceDir(mounter *storageosMounter) string
} }
// storageos volumes represent a bare host directory mount of an StorageOS export. // storageos volumes represent a bare host directory mount of an StorageOS export.
...@@ -312,9 +314,13 @@ type storageos struct { ...@@ -312,9 +314,13 @@ type storageos struct {
type storageosMounter struct { type storageosMounter struct {
*storageos *storageos
devicePath string
// The directory containing the StorageOS devices
deviceDir string
// Interface used to mount the file or block device // Interface used to mount the file or block device
diskMounter *mount.SafeFormatAndMount diskMounter *mount.SafeFormatAndMount
mountOptions []string
} }
var _ volume.Mounter = &storageosMounter{} var _ volume.Mounter = &storageosMounter{}
...@@ -383,11 +389,12 @@ func (b *storageosMounter) SetUpAt(dir string, fsGroup *int64) error { ...@@ -383,11 +389,12 @@ func (b *storageosMounter) SetUpAt(dir string, fsGroup *int64) error {
if b.readOnly { if b.readOnly {
options = append(options, "ro") options = append(options, "ro")
} }
mountOptions := volume.JoinMountOptions(b.mountOptions, options)
globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName) globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName)
glog.V(4).Infof("Attempting to bind mount to pod volume at %s", dir) glog.V(4).Infof("Attempting to bind mount to pod volume at %s", dir)
err = b.mounter.Mount(globalPDPath, dir, "", options) err = b.mounter.Mount(globalPDPath, dir, "", mountOptions)
if err != nil { if err != nil {
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir) notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil { if mntErr != nil {
...@@ -636,6 +643,7 @@ func (c *storageosProvisioner) Provision() (*v1.PersistentVolume, error) { ...@@ -636,6 +643,7 @@ func (c *storageosProvisioner) Provision() (*v1.PersistentVolume, error) {
}, },
}, },
}, },
MountOptions: c.options.MountOptions,
}, },
} }
if len(c.options.PVC.Spec.AccessModes) == 0 { if len(c.options.PVC.Spec.AccessModes) == 0 {
......
...@@ -131,6 +131,10 @@ func (fake *fakePDManager) DeleteVolume(d *storageosDeleter) error { ...@@ -131,6 +131,10 @@ func (fake *fakePDManager) DeleteVolume(d *storageosDeleter) error {
return nil return nil
} }
func (fake *fakePDManager) DeviceDir(mounter *storageosMounter) string {
return defaultDeviceDir
}
func TestPlugin(t *testing.T) { func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test") tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil { if err != nil {
...@@ -249,6 +253,7 @@ func TestPlugin(t *testing.T) { ...@@ -249,6 +253,7 @@ func TestPlugin(t *testing.T) {
// Test Provisioner // Test Provisioner
fakeManager = &fakePDManager{} fakeManager = &fakePDManager{}
mountOptions := []string{"sync", "noatime"}
options := volume.VolumeOptions{ options := volume.VolumeOptions{
PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}), PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
// PVName: "test-volume-name", // PVName: "test-volume-name",
...@@ -257,6 +262,7 @@ func TestPlugin(t *testing.T) { ...@@ -257,6 +262,7 @@ func TestPlugin(t *testing.T) {
"VolumeNamespace": "test-volume-namespace", "VolumeNamespace": "test-volume-namespace",
"adminSecretName": secretName, "adminSecretName": secretName,
}, },
MountOptions: mountOptions,
} }
provisioner, err := plug.(*storageosPlugin).newProvisionerInternal(options, fakeManager) provisioner, err := plug.(*storageosPlugin).newProvisionerInternal(options, fakeManager)
if err != nil { if err != nil {
...@@ -282,6 +288,9 @@ func TestPlugin(t *testing.T) { ...@@ -282,6 +288,9 @@ func TestPlugin(t *testing.T) {
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType != "ext2" { if persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType != "ext2" {
t.Errorf("Provision() returned unexpected volume FSType: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType) t.Errorf("Provision() returned unexpected volume FSType: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType)
} }
if len(persistentSpec.Spec.MountOptions) != 2 {
t.Errorf("Provision() returned unexpected volume mount options: %v", persistentSpec.Spec.MountOptions)
}
if persistentSpec.Labels["fakepdmanager"] != "yes" { if persistentSpec.Labels["fakepdmanager"] != "yes" {
t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels) t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels)
} }
......
...@@ -69,6 +69,7 @@ type apiImplementer interface { ...@@ -69,6 +69,7 @@ type apiImplementer interface {
VolumeMount(opts storageostypes.VolumeMountOptions) error VolumeMount(opts storageostypes.VolumeMountOptions) error
VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error
VolumeDelete(opt storageostypes.DeleteOptions) error VolumeDelete(opt storageostypes.DeleteOptions) error
Controller(ref string) (*storageostypes.Controller, error)
} }
// storageosUtil is the utility structure to interact with the StorageOS API. // storageosUtil is the utility structure to interact with the StorageOS API.
...@@ -148,6 +149,12 @@ func (u *storageosUtil) AttachVolume(b *storageosMounter) (string, error) { ...@@ -148,6 +149,12 @@ func (u *storageosUtil) AttachVolume(b *storageosMounter) (string, error) {
return "", err return "", err
} }
// Get the node's device path from the API, falling back to the default if
// not set on the node.
if b.deviceDir == "" {
b.deviceDir = u.DeviceDir(b)
}
vol, err := u.api.Volume(b.volNamespace, b.volName) vol, err := u.api.Volume(b.volNamespace, b.volName)
if err != nil { if err != nil {
glog.Warningf("volume retrieve failed for volume %q with namespace %q (%v)", b.volName, b.volNamespace, err) glog.Warningf("volume retrieve failed for volume %q with namespace %q (%v)", b.volName, b.volNamespace, err)
...@@ -167,12 +174,13 @@ func (u *storageosUtil) AttachVolume(b *storageosMounter) (string, error) { ...@@ -167,12 +174,13 @@ func (u *storageosUtil) AttachVolume(b *storageosMounter) (string, error) {
} }
} }
srcPath := path.Join(b.devicePath, vol.ID) srcPath := path.Join(b.deviceDir, vol.ID)
dt, err := pathDeviceType(srcPath) dt, err := pathDeviceType(srcPath)
if err != nil { if err != nil {
glog.Warningf("volume source path %q for volume %q not ready (%v)", srcPath, b.volName, err) glog.Warningf("volume source path %q for volume %q not ready (%v)", srcPath, b.volName, err)
return "", err return "", err
} }
switch dt { switch dt {
case modeBlock: case modeBlock:
return srcPath, nil return srcPath, nil
...@@ -277,6 +285,22 @@ func (u *storageosUtil) DeleteVolume(d *storageosDeleter) error { ...@@ -277,6 +285,22 @@ func (u *storageosUtil) DeleteVolume(d *storageosDeleter) error {
return u.api.VolumeDelete(opts) return u.api.VolumeDelete(opts)
} }
// Get the node's device path from the API, falling back to the default if not
// specified.
func (u *storageosUtil) DeviceDir(b *storageosMounter) string {
ctrl, err := u.api.Controller(b.plugin.host.GetHostName())
if err != nil {
glog.Warningf("node device path lookup failed: %v", err)
return defaultDeviceDir
}
if ctrl == nil || ctrl.DeviceDir == "" {
glog.Warningf("node device path not set, using default: %s", defaultDeviceDir)
return defaultDeviceDir
}
return ctrl.DeviceDir
}
// pathMode returns the FileMode for a path. // pathMode returns the FileMode for a path.
func pathDeviceType(path string) (deviceType, error) { func pathDeviceType(path string) (deviceType, error) {
fi, err := os.Stat(path) fi, err := os.Stat(path)
...@@ -332,7 +356,7 @@ func getLoopDevice(path string, exec mount.Exec) (string, error) { ...@@ -332,7 +356,7 @@ func getLoopDevice(path string, exec mount.Exec) (string, error) {
} }
func makeLoopDevice(path string, exec mount.Exec) (string, error) { func makeLoopDevice(path string, exec mount.Exec) (string, error) {
args := []string{"-f", "--show", path} args := []string{"-f", "-P", "--show", path}
out, err := exec.Run(losetupPath, args...) out, err := exec.Run(losetupPath, args...)
if err != nil { if err != nil {
glog.V(2).Infof("Failed device create command for path %s: %v", path, err) glog.V(2).Infof("Failed device create command for path %s: %v", path, err)
......
...@@ -108,6 +108,9 @@ func (f fakeAPI) VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error { ...@@ -108,6 +108,9 @@ func (f fakeAPI) VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error {
func (f fakeAPI) VolumeDelete(opts storageostypes.DeleteOptions) error { func (f fakeAPI) VolumeDelete(opts storageostypes.DeleteOptions) error {
return nil return nil
} }
func (f fakeAPI) Controller(ref string) (*storageostypes.Controller, error) {
return &storageostypes.Controller{}, nil
}
func TestCreateVolume(t *testing.T) { func TestCreateVolume(t *testing.T) {
...@@ -224,7 +227,7 @@ func TestAttachVolume(t *testing.T) { ...@@ -224,7 +227,7 @@ func TestAttachVolume(t *testing.T) {
mounter: &mount.FakeMounter{}, mounter: &mount.FakeMounter{},
plugin: plug.(*storageosPlugin), plugin: plug.(*storageosPlugin),
}, },
devicePath: tmpDir, deviceDir: tmpDir,
} }
if err != nil { if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err) t.Errorf("Failed to make a new Mounter: %v", err)
......
...@@ -6,61 +6,28 @@ go_library( ...@@ -6,61 +6,28 @@ go_library(
"client.go", "client.go",
"controller.go", "controller.go",
"event.go", "event.go",
"health.go",
"logger.go",
"login.go",
"namespace.go", "namespace.go",
"policy.go",
"pool.go", "pool.go",
"rule.go", "rule.go",
"server_version.go", "server_version.go",
"template.go", "template.go",
"user.go",
"util.go", "util.go",
"validation.go", "validation.go",
"volume.go", "volume.go",
] + select({ ],
"@io_bazel_rules_go//go/platform:android": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:darwin": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:linux": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:nacl": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:plan9": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:solaris": [
"client_unix.go",
],
"@io_bazel_rules_go//go/platform:windows": [
"client_windows.go",
],
"//conditions:default": [],
}),
importpath = "github.com/storageos/go-api", importpath = "github.com/storageos/go-api",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/gorilla/websocket:go_default_library", "//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", "//vendor/github.com/storageos/go-api/types:go_default_library",
] + select({ ],
"@io_bazel_rules_go//go/platform:windows": [
"//vendor/github.com/Microsoft/go-winio:go_default_library",
],
"//conditions:default": [],
}),
) )
filegroup( filegroup(
...@@ -74,6 +41,8 @@ filegroup( ...@@ -74,6 +41,8 @@ filegroup(
name = "all-srcs", name = "all-srcs",
srcs = [ srcs = [
":package-srcs", ":package-srcs",
"//vendor/github.com/storageos/go-api/netutil:all-srcs",
"//vendor/github.com/storageos/go-api/serror:all-srcs",
"//vendor/github.com/storageos/go-api/types:all-srcs", "//vendor/github.com/storageos/go-api/types:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],
......
# StorageOS API client library # StorageOS API client library
## Swagger Spec
Swagger specification for this repo is available in the [StorageOS public documentation](https://github.com/storageos/storageos.github.io/blob/master/swagger.yaml).
// +build !windows
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package storageos
import (
"net"
"net/http"
)
// initializeNativeClient initializes the native Unix domain socket client on
// Unix-style operating systems
func (c *Client) initializeNativeClient() {
if c.endpointURL.Scheme != unixProtocol {
return
}
socketPath := c.endpointURL.Path
tr := defaultTransport()
tr.Dial = func(network, addr string) (net.Conn, error) {
return c.Dialer.Dial(unixProtocol, socketPath)
}
c.nativeHTTPClient = &http.Client{Transport: tr}
}
// +build windows
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package storageos
import (
"net"
"net/http"
"time"
"github.com/Microsoft/go-winio"
)
const namedPipeConnectTimeout = 2 * time.Second
type pipeDialer struct {
dialFunc func(network, addr string) (net.Conn, error)
}
func (p pipeDialer) Dial(network, address string) (net.Conn, error) {
return p.dialFunc(network, address)
}
// initializeNativeClient initializes the native Named Pipe client for Windows
func (c *Client) initializeNativeClient() {
if c.endpointURL.Scheme != namedPipeProtocol {
return
}
namedPipePath := c.endpointURL.Path
dialFunc := func(network, addr string) (net.Conn, error) {
timeout := namedPipeConnectTimeout
return winio.DialPipe(namedPipePath, &timeout)
}
tr := defaultTransport()
tr.Dial = dialFunc
c.Dialer = &pipeDialer{dialFunc}
c.nativeHTTPClient = &http.Client{Transport: tr}
}
...@@ -26,7 +26,6 @@ func (c *Client) ControllerList(opts types.ListOptions) ([]*types.Controller, er ...@@ -26,7 +26,6 @@ func (c *Client) ControllerList(opts types.ListOptions) ([]*types.Controller, er
listOpts := doOptions{ listOpts := doOptions{
fieldSelector: opts.FieldSelector, fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector, labelSelector: opts.LabelSelector,
namespace: opts.Namespace,
context: opts.Context, context: opts.Context,
} }
......
package storageos
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/storageos/go-api/types"
)
var (
// HealthAPIPrefix is a partial path to the HTTP endpoint.
HealthAPIPrefix = "health"
)
// CPHealth returns the health of the control plane server at a given url.
func (c *Client) CPHealth(ctx context.Context, hostname string) (*types.CPHealthStatus, error) {
url := fmt.Sprintf("http://%s:%s/v1/%s", hostname, DefaultPort, HealthAPIPrefix)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if c.username != "" && c.secret != "" {
req.SetBasicAuth(c.username, c.secret)
}
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var status *types.CPHealthStatus
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
return nil, err
}
return status, nil
}
// DPHealth returns the health of the data plane server at a given url.
func (c *Client) DPHealth(ctx context.Context, hostname string) (*types.DPHealthStatus, error) {
url := fmt.Sprintf("http://%s:%s/v1/%s", hostname, DataplaneHealthPort, HealthAPIPrefix)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if c.username != "" && c.secret != "" {
req.SetBasicAuth(c.username, c.secret)
}
resp, err := c.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var status *types.DPHealthStatus
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
return nil, err
}
return status, nil
}
package storageos
import (
"context"
"encoding/json"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// LoggerAPIPrefix is a partial path to the HTTP endpoint.
LoggerAPIPrefix = "logs"
)
// LoggerConfig returns every cluster node's logging configuration.
func (c *Client) LoggerConfig(opts types.ListOptions) ([]*types.Logger, 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", LoggerAPIPrefix+"/cluster/config", listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var loggers []*types.Logger
if err := json.NewDecoder(resp.Body).Decode(&loggers); err != nil {
return nil, err
}
return loggers, nil
}
// LoggerUpdate patches updates to logging configuration. Fields to update must
// be listed in the Fields value, and if a list of Nodes is given it will only
// apply to the nodes listed. Returns the updated configuration.
func (c *Client) LoggerUpdate(opts types.LoggerUpdateOptions) ([]*types.Logger, error) {
resp, err := c.do("PATCH", LoggerAPIPrefix+"/cluster/config", doOptions{
data: opts,
context: context.Background(),
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var loggers []*types.Logger
if err := json.NewDecoder(resp.Body).Decode(&loggers); err != nil {
return nil, err
}
return loggers, nil
}
package storageos
import (
"encoding/json"
"errors"
)
var (
// LoginAPIPrefix is a partial path to the HTTP endpoint.
LoginAPIPrefix = "auth/login"
ErrLoginFailed = errors.New("Failed to get token from API endpoint")
)
// Login attemps to get a token from the API
func (c *Client) Login() (token string, err error) {
resp, err := c.do("POST", LoginAPIPrefix, doOptions{data: struct {
User string `json:"username"`
Pass string `json:"password"`
}{c.username, c.secret}})
if err != nil {
if _, ok := err.(*Error); ok {
return "", ErrLoginFailed
}
return "", err
}
if resp.StatusCode != 200 {
return "", ErrLoginFailed
}
unmarsh := struct {
Token string `json:"token"`
}{}
if err := json.NewDecoder(resp.Body).Decode(&unmarsh); err != nil {
return "", err
}
if unmarsh.Token == "" {
return "", ErrLoginFailed
}
return unmarsh.Token, nil
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"errors.go",
"multidialer.go",
"parsers.go",
],
importpath = "github.com/storageos/go-api/netutil",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/storageos/go-api/serror:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package netutil
import (
"errors"
"fmt"
"github.com/storageos/go-api/serror"
"strings"
)
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"
return serror.NewTypedStorageOSError(serror.APIUncontactable, nil, msg, help)
}
func newInvalidNodeError(err error) error {
msg := fmt.Sprintf("invalid node format: %s", err)
help := "please check the format of $STORAGEOS_HOST (or the -H flag) complies with the StorageOS JOIN format"
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")
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)
// Fisher–Yates 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)
}
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
// 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
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)
}
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...)
}
}
return addrs, nil
}
func validPort(port string) bool {
intPort, err := strconv.Atoi(port)
return (err == nil) &&
(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)
}
package storageos
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/storageos/go-api/types"
"net/http"
"net/url"
)
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")
)
// nopMarshaler is an alias to a []byte that implements json.Marshaler
// it bypasses the base64 encoded string representation that json will give byte slices.
// It should only be used to wrap []byte types containing pre-rendered valid json that will later
// (out of the caller's control) be run through json.Marshal
type nopMarshaler []byte
func (n *nopMarshaler) MarshalJSON() ([]byte, error) {
return *n, nil
}
// PolicyCreate creates a policy on the server.
func (c *Client) PolicyCreate(jsonl []byte, ctx context.Context) error {
nopm := nopMarshaler(jsonl)
_, err := c.do("POST", PolicyAPIPrefix, doOptions{
data: &nopm,
context: ctx,
headers: map[string]string{"Content-Type": "application/x-jsonlines"},
})
return err
}
// Policy returns a policy on the server by ID.
func (c *Client) Policy(id string) (*types.Policy, error) {
path := fmt.Sprintf("%s/%s", PolicyAPIPrefix, id)
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchPolicy
}
return nil, err
}
defer resp.Body.Close()
var policy *types.Policy
if err := json.NewDecoder(resp.Body).Decode(&policy); err != nil {
return nil, err
}
return policy, nil
}
// PolicyList returns the list of policies on the server.
func (c *Client) PolicyList(opts types.ListOptions) (types.PolicySet, error) {
listOpts := doOptions{
fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector,
namespace: opts.Namespace,
context: opts.Context,
}
if opts.LabelSelector != "" {
query := url.Values{}
query.Add("labelSelector", opts.LabelSelector)
listOpts.values = query
}
resp, err := c.do("GET", PolicyAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var policies types.PolicySet
if err := json.NewDecoder(resp.Body).Decode(&policies); err != nil {
return nil, err
}
return policies, nil
}
// PolicyDelete deletes a policy on the server by ID.
func (c *Client) PolicyDelete(opts types.DeleteOptions) error {
resp, err := c.do("DELETE", PolicyAPIPrefix+"/"+opts.Name, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchPolicy
}
}
return err
}
defer resp.Body.Close()
return nil
}
...@@ -72,10 +72,15 @@ func (c *Client) Rule(namespace string, ref string) (*types.Rule, error) { ...@@ -72,10 +72,15 @@ func (c *Client) Rule(namespace string, ref string) (*types.Rule, error) {
// RuleCreate creates a rule on the server and returns the new object. // RuleCreate creates a rule on the server and returns the new object.
func (c *Client) RuleCreate(opts types.RuleCreateOptions) (*types.Rule, error) { func (c *Client) RuleCreate(opts types.RuleCreateOptions) (*types.Rule, error) {
resp, err := c.do("POST", RuleAPIPrefix, doOptions{ path, err := namespacedPath(opts.Namespace, RuleAPIPrefix)
data: opts, if err != nil {
namespace: opts.Namespace, return nil, err
context: opts.Context, }
resp, err := c.do("POST", path, doOptions{
data: opts,
// namespace: opts.Namespace,
context: opts.Context,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"error_kind.go",
"kind_lookup_map.go",
"storageos_error.go",
"storageoserrorkind_string.go",
"typed_error.go",
],
importpath = "github.com/storageos/go-api/serror",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
package serror
//go:generate stringer -type=StorageOSErrorKind error_kind.go
type StorageOSErrorKind int
// Known error kinds
const (
UnknownError StorageOSErrorKind = iota
APIUncontactable
InvalidHostConfig
)
package serror
import (
"encoding/json"
"fmt"
"strings"
)
var kindLookupMap map[string]StorageOSErrorKind
func init() {
kindLookupMap = make(map[string]StorageOSErrorKind)
// Populate the lookup map with all the known constants
for i := StorageOSErrorKind(0); !strings.HasPrefix(i.String(), "StorageOSErrorKind("); i++ {
kindLookupMap[i.String()] = i
}
}
func (s *StorageOSErrorKind) UnmarshalJSON(b []byte) error {
str := ""
if err := json.Unmarshal(b, &str); err != nil {
return err
}
v, ok := kindLookupMap[str]
if !ok {
return fmt.Errorf("Failed to unmarshal ErrorKind %s", s)
}
*s = v
return nil
}
func (s *StorageOSErrorKind) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}
package serror
import (
"encoding/json"
)
type StorageOSError interface {
// embedding error provides compatibility with standard error handling code
error
// Encoding/decoding methods to help errors traverse API boundaries
json.Marshaler
json.Unmarshaler
Err() error // Returns the underlying error that caused this event
String() string // A short string representing the error (for logging etc)
Help() string // A larger string that should provide informative debug instruction to users
Kind() StorageOSErrorKind // A type representing a set of known error conditions, helpful to switch on
Extra() map[string]string // A container for error specific information
// TODO: should we include callstack traces here? We could have a debug mode for it.
}
func ErrorKind(err error) StorageOSErrorKind {
if serr, ok := err.(StorageOSError); ok {
return serr.Kind()
}
return UnknownError
}
func IsStorageOSError(err error) bool {
_, ok := err.(StorageOSError)
return ok
}
// Code generated by "stringer -type=StorageOSErrorKind error_kind.go"; DO NOT EDIT.
package serror
import "strconv"
const _StorageOSErrorKind_name = "UnknownErrorAPIUncontactableInvalidHostConfig"
var _StorageOSErrorKind_index = [...]uint8{0, 12, 28, 45}
func (i StorageOSErrorKind) String() string {
if i < 0 || i >= StorageOSErrorKind(len(_StorageOSErrorKind_index)-1) {
return "StorageOSErrorKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _StorageOSErrorKind_name[_StorageOSErrorKind_index[i]:_StorageOSErrorKind_index[i+1]]
}
package serror
import (
"encoding/json"
)
func NewTypedStorageOSError(kind StorageOSErrorKind, err error, msg string, help string) StorageOSError {
return &typedStorageOSError{
internal: &internal_TypedStorageOSError{
ErrorKind: &kind,
Cause: err,
ErrMessage: msg,
HelpMessage: help,
},
}
}
func NewUntypedStorageOSError(err error, msg string, help string) StorageOSError {
var kind StorageOSErrorKind = UnknownError
return &typedStorageOSError{
internal: &internal_TypedStorageOSError{
ErrorKind: &kind,
Cause: err,
ErrMessage: msg,
HelpMessage: help,
},
}
}
type internal_TypedStorageOSError struct {
ErrorKind *StorageOSErrorKind `json:"error_kind"`
Cause error `json:"caused_by"`
ErrMessage string `json:"error_message"`
HelpMessage string `json:"help_message"`
ExtraMap map[string]string `json:"extra"`
}
type typedStorageOSError struct {
internal *internal_TypedStorageOSError
}
func (t *typedStorageOSError) MarshalJSON() ([]byte, error) {
return json.Marshal(t.internal)
}
func (t *typedStorageOSError) UnmarshalJSON(d []byte) error {
internal := &internal_TypedStorageOSError{}
err := json.Unmarshal(d, internal)
if err != nil {
return err
}
t.internal = internal
return nil
}
func (t *typedStorageOSError) Error() string { return t.String() }
func (t *typedStorageOSError) Err() error { return t.internal.Cause }
func (t *typedStorageOSError) String() string { return t.internal.ErrMessage }
func (t *typedStorageOSError) Help() string { return t.internal.HelpMessage }
func (t *typedStorageOSError) Kind() StorageOSErrorKind { return *t.internal.ErrorKind }
func (t *typedStorageOSError) Extra() map[string]string { return t.internal.ExtraMap }
layout:
models:
- name: definition
source: asset:model
target: "{{ joinFilePath .Target .ModelPackage }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
operations:
- name: handler
source: asset:serverOperation
target: "{{ joinFilePath .Target .APIPackage .Package }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
...@@ -13,13 +13,17 @@ go_library( ...@@ -13,13 +13,17 @@ go_library(
"error_response.go", "error_response.go",
"events.go", "events.go",
"list_options.go", "list_options.go",
"logger.go",
"namespace.go", "namespace.go",
"node.go",
"operator.go", "operator.go",
"policy.go",
"pool.go", "pool.go",
"pool_create_options.go", "pool_create_options.go",
"rule.go", "rule.go",
"template.go", "template.go",
"template_create_options.go", "template_create_options.go",
"user.go",
"version.go", "version.go",
"volume.go", "volume.go",
"volume_create_options.go", "volume_create_options.go",
......
...@@ -41,6 +41,7 @@ type Controller struct { ...@@ -41,6 +41,7 @@ type Controller struct {
Scheduler bool `json:"scheduler"` Scheduler bool `json:"scheduler"`
Name string `json:"name"` Name string `json:"name"`
Address string `json:"address"` Address string `json:"address"`
DeviceDir string `json:"deviceDir"`
APIPort int `json:"apiPort"` APIPort int `json:"apiPort"`
NatsPort int `json:"natsPort"` NatsPort int `json:"natsPort"`
NatsClusterPort int `json:"natsClusterPort"` NatsClusterPort int `json:"natsClusterPort"`
...@@ -59,6 +60,9 @@ type Controller struct { ...@@ -59,6 +60,9 @@ type Controller struct {
VersionInfo map[string]VersionInfo `json:"versionInfo"` VersionInfo map[string]VersionInfo `json:"versionInfo"`
Version string `json:"version"` 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 // high level stats that combine info from all driver instances
CapacityStats CapacityStats `json:"capacityStats"` CapacityStats CapacityStats `json:"capacityStats"`
} }
......
...@@ -19,6 +19,9 @@ type ControllerUpdateOptions struct { ...@@ -19,6 +19,9 @@ type ControllerUpdateOptions struct {
// Labels are user-defined key/value metadata. // Labels are user-defined key/value metadata.
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
// Cordon sets the controler into an unschedulable state if true
Cordon bool `json:"unschedulable"`
// Context can be set with a timeout or can be used to cancel a request. // Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"` Context context.Context `json:"-"`
} }
...@@ -18,6 +18,10 @@ type Deployment struct { ...@@ -18,6 +18,10 @@ type Deployment struct {
// Read Only: true // Read Only: true
Controller string `json:"controller"` Controller string `json:"controller"`
// Controller name
// Read Only: true
ControllerName string `json:"controllerName"`
// Health // Health
// Read Only: true // Read Only: true
Health string `json:"health"` Health string `json:"health"`
......
package types
import "context"
// Logger is the runtime configuration of the node's logging services.
// swagger:model Logger
type Logger struct {
// Node name
Node string `json:"node"`
// Log level
Level string `json:"level"`
// Log filter
Filter string `json:"filter"`
// Log filters by category
// Read Only: true
Categories map[string]string `json:"categories"`
}
// LoggerUpdateOptions are the available parameters for updating loggers.
type LoggerUpdateOptions struct {
// Log level
Level string `json:"level"`
// Log filter
Filter string `json:"filter"`
// List of nodes to update. All if not set.
Nodes []string `json:"nodes"`
// List of fields to update. Must be set.
Fields []string `json:"fields"`
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"`
}
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
}
package types
import (
"encoding/json"
)
type Policy struct {
Spec struct {
User string `json:"user,omitempty"`
Group string `json:"group,omitempty"`
Readonly bool `json:"readonly,omitempty"`
APIGroup string `json:"apiGroup,omitempty"`
Resource string `json:"resource,omitempty"`
Namespace string `json:"namespace,omitempty"`
NonResourcePath string `json:"nonResourcePath,omitempty"`
} `json:"spec"`
}
// PolicyWithId is used as an internal type to render table formated versions of the json response
type PolicyWithID struct {
Policy
ID string
}
// MarshalJSON returns a marshaled copy of the internal policy object, so it is still valid to use
// with the REST API
func (p *PolicyWithID) MarshalJSON() ([]byte, error) {
return json.Marshal(p.Policy)
}
// PolicySet is a representation of the data structure returned from the REST API
type PolicySet map[string]Policy
func (p PolicySet) GetPoliciesWithID() []*PolicyWithID {
rtn := make([]*PolicyWithID, 0, len(p))
for k, v := range p {
rtn = append(rtn, &PolicyWithID{
Policy: v,
ID: k,
})
}
return rtn
}
package types
import (
"context"
"encoding/json"
"strings"
)
type User struct {
UUID string `json:"id"`
Username string `json:"username"`
Groups []string `json:"groups"`
Password string `json:"password,omitempty"`
Role string `json:"role"`
}
func (u *User) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
UUID string `json:"id"`
Username string `json:"username"`
Groups string `json:"groups"`
Password string `json:"password,omitempty"`
Role string `json:"role"`
}{
UUID: u.UUID,
Username: u.Username,
Groups: strings.Join(u.Groups, ","),
Password: u.Password,
Role: u.Role,
})
}
func (u *User) UnmarshalJSON(data []byte) error {
temp := &struct {
UUID string `json:"id"`
Username string `json:"username"`
Groups string `json:"groups"`
Password string `json:"password"`
Role string `json:"role"`
}{}
if err := json.Unmarshal(data, temp); err != nil {
return err
}
u.UUID = temp.UUID
u.Username = temp.Username
u.Password = temp.Password
u.Role = temp.Role
u.Groups = strings.Split(temp.Groups, ",")
return nil
}
type UserCreateOptions struct {
Username string `json:"username"`
Groups []string `json:"groups"`
Password string `json:"password"`
Role string `json:"role"`
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"`
}
func (u UserCreateOptions) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Username string `json:"username"`
Groups string `json:"groups"`
Password string `json:"password"`
Role string `json:"role"`
}{
Username: u.Username,
Groups: strings.Join(u.Groups, ","),
Password: u.Password,
Role: u.Role,
})
}
...@@ -43,6 +43,9 @@ type Volume struct { ...@@ -43,6 +43,9 @@ type Volume struct {
// Namespace is the object name and authentication scope, such as for teams and projects. // Namespace is the object name and authentication scope, such as for teams and projects.
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
// node selector (where volumes should land)
NodeSelector string `json:"nodeSelector"`
// Volume deployment information for the master volume. // Volume deployment information for the master volume.
// Read Only: true // Read Only: true
Master *Deployment `json:"master,omitempty"` Master *Deployment `json:"master,omitempty"`
...@@ -51,6 +54,9 @@ type Volume struct { ...@@ -51,6 +54,9 @@ type Volume struct {
// Read Only: true // Read Only: true
Mounted bool `json:"mounted"` Mounted bool `json:"mounted"`
// MountDevice, where the device is located
MountDevice string `json:"mountDevice"`
// Mountpoint, where the volume is mounted // Mountpoint, where the volume is mounted
Mountpoint string `json:"mountpoint"` Mountpoint string `json:"mountpoint"`
...@@ -78,6 +84,10 @@ type Volume struct { ...@@ -78,6 +84,10 @@ type Volume struct {
// Read Only: true // Read Only: true
StatusMessage string `json:"statusMessage"` StatusMessage string `json:"statusMessage"`
// mkfs performed on new volumes
MkfsDone bool `json:"mkfsDone"`
MkfsDoneAt time.Time `json:"mkfsDoneAt"`
// When the volume was created. // When the volume was created.
// Read Only: true // Read Only: true
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
......
...@@ -25,6 +25,9 @@ type VolumeCreateOptions struct { ...@@ -25,6 +25,9 @@ type VolumeCreateOptions struct {
// Namespace is the object scope, such as for teams and projects. // Namespace is the object scope, such as for teams and projects.
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
// node selector (where volumes should land)
NodeSelector string `json:"nodeSelector"`
// Labels are user-defined key/value metadata. // Labels are user-defined key/value metadata.
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
......
...@@ -23,6 +23,9 @@ type VolumeUpdateOptions struct { ...@@ -23,6 +23,9 @@ type VolumeUpdateOptions struct {
// Namespace is the object scope, such as for teams and projects. // Namespace is the object scope, such as for teams and projects.
Namespace string `json:"namespace"` Namespace string `json:"namespace"`
// node selector (where volumes should land)
NodeSelector string `json:"nodeSelector"`
// Labels are user-defined key/value metadata. // Labels are user-defined key/value metadata.
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
......
package storageos
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// UserAPIPrefix is a partial path to the HTTP endpoint.
UserAPIPrefix = "users"
// ErrNoSuchUser is the error returned when the user does not exist.
ErrNoSuchUser = errors.New("no such user")
)
// UserList returns the list of available users.
func (c *Client) UserList(opts types.ListOptions) ([]*types.User, error) {
listOpts := doOptions{
fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector,
namespace: opts.Namespace,
context: opts.Context,
}
if opts.LabelSelector != "" {
query := url.Values{}
query.Add("labelSelector", opts.LabelSelector)
listOpts.values = query
}
resp, err := c.do("GET", UserAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
users := make([]*types.User, 0)
if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
return nil, err
}
return users, nil
}
// User returns a user by its username/id.
func (c *Client) User(username string) (*types.User, error) {
path := fmt.Sprintf("%s/%s", UserAPIPrefix, username)
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchUser
}
return nil, err
}
defer resp.Body.Close()
var user *types.User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, err
}
return user, nil
}
// UserCreate creates a user on the server.
func (c *Client) UserCreate(opts types.UserCreateOptions) error {
_, err := c.do("POST", UserAPIPrefix, doOptions{
data: opts,
context: opts.Context,
})
return err
}
// UserUpdate updates a user on the server.
func (c *Client) UserUpdate(user *types.User, ctx context.Context) error {
var ref string
switch {
case user.UUID != "":
ref = user.UUID
case user.Username != "":
ref = user.Username
default:
return ErrNoSuchUser
}
path := fmt.Sprintf("%s/%s", UserAPIPrefix, ref)
resp, err := c.do("POST", path, doOptions{
data: user,
context: ctx,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return ErrNoSuchUser
}
return err
}
defer resp.Body.Close()
return nil
}
// UserDelete removes a user by its reference.
func (c *Client) UserDelete(opts types.DeleteOptions) error {
resp, err := c.do("DELETE", UserAPIPrefix+"/"+opts.Name, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchUser
}
}
return err
}
defer resp.Body.Close()
return nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment