Add StorageOS volume plugin

pull/6/head
Simon Croome 2017-02-24 15:47:40 +00:00
parent 810efa6689
commit 5e2503e71f
113 changed files with 15630 additions and 3205 deletions

8
Godeps/Godeps.json generated
View File

@ -2410,6 +2410,14 @@
"ImportPath": "github.com/square/go-jose/json",
"Rev": "789a4c4bd4c118f7564954f441b29c153ccd6a96"
},
{
"ImportPath": "github.com/storageos/go-api",
"Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f"
},
{
"ImportPath": "github.com/storageos/go-api/types",
"Rev": "74f9beb613cacf0cc282facc2e1550a3231e126f"
},
{
"ImportPath": "github.com/stretchr/objx",
"Rev": "1a9d0bb9f541897e62256577b352fdbc1fb4fd94"

106
Godeps/LICENSES generated
View File

@ -77112,6 +77112,112 @@ SOFTWARE.
================================================================================
================================================================================
= vendor/github.com/storageos/go-api 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: =
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/stretchr/objx licensed under: =

View File

@ -49239,6 +49239,10 @@
"description": "Name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.",
"type": "string"
},
"storageos": {
"description": "StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.StorageOSPersistentVolumeSource"
},
"vsphereVolume": {
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource"
@ -50712,6 +50716,56 @@
}
}
},
"io.k8s.kubernetes.pkg.api.v1.StorageOSPersistentVolumeSource": {
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"fsType": {
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"type": "string"
},
"readOnly": {
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"type": "boolean"
},
"secretRef": {
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.ObjectReference"
},
"volumeName": {
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"type": "string"
},
"volumeNamespace": {
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"type": "string"
}
}
},
"io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource": {
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"fsType": {
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"type": "string"
},
"readOnly": {
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"type": "boolean"
},
"secretRef": {
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.LocalObjectReference"
},
"volumeName": {
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"type": "string"
},
"volumeNamespace": {
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"type": "string"
}
}
},
"io.k8s.kubernetes.pkg.api.v1.TCPSocketAction": {
"description": "TCPSocketAction describes an action based on opening a socket",
"required": [
@ -50893,6 +50947,10 @@
"description": "Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.SecretVolumeSource"
},
"storageos": {
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource"
},
"vsphereVolume": {
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource"

View File

@ -3945,6 +3945,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -4808,6 +4812,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -1692,6 +1692,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -2555,6 +2559,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -2773,6 +2773,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -3636,6 +3640,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -7417,6 +7417,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -8280,6 +8284,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -1557,6 +1557,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -2382,6 +2386,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.VolumeMount": {
"id": "v1.VolumeMount",
"description": "VolumeMount describes a mounting of a Volume within a container.",

View File

@ -18891,6 +18891,10 @@
"$ref": "v1.LocalVolumeSource",
"description": "Local represents directly-attached storage with node affinity"
},
"storageos": {
"$ref": "v1.StorageOSPersistentVolumeSource",
"description": "StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md"
},
"accessModes": {
"type": "array",
"items": {
@ -19482,6 +19486,32 @@
}
}
},
"v1.StorageOSPersistentVolumeSource": {
"id": "v1.StorageOSPersistentVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.ObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.PersistentVolumeStatus": {
"id": "v1.PersistentVolumeStatus",
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
@ -19789,6 +19819,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -20095,6 +20129,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -90,6 +90,7 @@ go_library(
"//pkg/volume/quobyte:go_default_library",
"//pkg/volume/rbd:go_default_library",
"//pkg/volume/scaleio:go_default_library",
"//pkg/volume/storageos:go_default_library",
"//pkg/volume/vsphere_volume:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",

View File

@ -54,6 +54,7 @@ import (
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/scaleio"
"k8s.io/kubernetes/pkg/volume/storageos"
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
)
@ -75,6 +76,7 @@ func ProbeAttachableVolumePlugins(config componentconfig.VolumeConfiguration) []
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
return allPlugins
}
@ -123,6 +125,7 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, local.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
if cloud != nil {
switch {

View File

@ -103,6 +103,7 @@ go_library(
"//pkg/volume/rbd:go_default_library",
"//pkg/volume/scaleio:go_default_library",
"//pkg/volume/secret:go_default_library",
"//pkg/volume/storageos:go_default_library",
"//pkg/volume/vsphere_volume:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",

View File

@ -54,6 +54,7 @@ import (
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/scaleio"
"k8s.io/kubernetes/pkg/volume/secret"
"k8s.io/kubernetes/pkg/volume/storageos"
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
// Cloud providers
_ "k8s.io/kubernetes/pkg/cloudprovider/providers"
@ -97,6 +98,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, local.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
return allPlugins
}

View File

@ -3185,6 +3185,13 @@ The StatefulSet guarantees that a given network identity will always map to the
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -6547,6 +6554,68 @@ Examples:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1beta1_statefulsetupdatestrategy">v1beta1.StatefulSetUpdateStrategy</h3>
@ -6738,7 +6807,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-06 19:37:03 UTC
Last updated 2017-06-07 15:14:00 UTC
</div>
</div>
</body>

View File

@ -2700,6 +2700,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -5569,6 +5576,68 @@ Examples:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_nodeaffinity">v1.NodeAffinity</h3>
@ -5719,7 +5788,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-06 04:25:31 UTC
Last updated 2017-06-07 15:14:25 UTC
</div>
</div>
</body>

View File

@ -2707,6 +2707,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -5472,6 +5479,68 @@ Examples:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_objectreference">v1.ObjectReference</h3>
@ -5815,7 +5884,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-06 04:25:37 UTC
Last updated 2017-06-07 15:14:30 UTC
</div>
</div>
</body>

View File

@ -3874,6 +3874,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -7806,6 +7813,68 @@ Both these may change in the future. Incoming requests are matched against the h
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_nodeaffinity">v1.NodeAffinity</h3>
@ -8155,7 +8224,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-06 04:25:47 UTC
Last updated 2017-06-07 15:14:38 UTC
</div>
</div>
</body>

View File

@ -3460,6 +3460,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -3507,6 +3514,68 @@ When an object is created, the system will populate this list with the current s
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_resourcefieldselector">v1.ResourceFieldSelector</h3>
@ -3957,7 +4026,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-06 04:26:13 UTC
Last updated 2017-06-07 15:14:57 UTC
</div>
</div>
</body>

View File

@ -4332,6 +4332,68 @@ The resulting set of endpoints can be viewed as:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageospersistentvolumesource">v1.StorageOSPersistentVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_objectreference">v1.ObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_volume">v1.Volume</h3>
@ -4545,6 +4607,13 @@ The resulting set of endpoints can be viewed as:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -7404,6 +7473,13 @@ Examples:<br>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume that is attached to the kubelet&#8217;s host machine and mounted into the pod More info: <a href="https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md">https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageospersistentvolumesource">v1.StorageOSPersistentVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">accessModes</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">AccessModes contains all ways the volume can be mounted. More info: <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes">https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
@ -9431,6 +9507,68 @@ Examples:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_objectreference">v1.ObjectReference</h3>
@ -10112,7 +10250,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-08 02:30:43 UTC
Last updated 2017-06-09 12:11:42 UTC
</div>
</div>
</body>

View File

@ -94,6 +94,30 @@ parameters:
For a complete example refer ([Portworx Volume docs](../volumes/portworx/README.md))
#### StorageOS
```yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: sc-fast
provisioner: kubernetes.io/storageos
parameters:
pool: default
description: Kubernetes volume
fsType: ext4
adminSecretNamespace: default
adminSecretName: storageos-secret
```
* `pool`: The name of the StorageOS distributed capacity pool to provision the volume from. Uses the `default` pool which is normally present if not specified.
* `description`: The description to assign to volumes that were created dynamically. All volume descriptions will be the same for the storage class, but different storage classes can be used to allow descriptions for different use cases. Defaults to `Kubernetes volume`.
* `fsType`: The default filesystem type to request. Note that user-defined rules within StorageOS may override this value. Defaults to `ext4`.
* `adminSecretNamespace`: The namespace where the API configuration secret is located. Required if adminSecretName set.
* `adminSecretName`: The name of the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.
For a complete example refer to the ([StorageOS example](../../volumes/storageos/README.md))
#### GLUSTERFS
```yaml

View File

@ -0,0 +1,475 @@
# StorageOS Volume
- [StorageOS](#storageos)
- [Prerequisites](#prerequisites)
- [Examples](#examples)
- [Pre-provisioned Volumes](#pre-provisioned)
- [Pod](#pod)
- [Persistent Volumes](#persistent-volumes)
- [Dynamic Provisioning](#dynamic-provisioning)
- [Storage Class](#storage-class)
- [API Configuration](#api-configuration)
## StorageOS
[StorageOS](https://www.storageos.com) can be used as a storage provider for your Kubernetes cluster. StorageOS runs as a container within your Kubernetes environment, making local storage accessible from any node within the Kubernetes cluster. Data can be replicated to protect against node failure.
At its core, StorageOS provides block storage. You may choose the filesystem type to install to make devices usable from within containers.
## Prerequisites
The StorageOS container must be running on each Kubernetes node that wants to contribute storage or that wants to consume storage. For more information on how you can run StorageOS, consult the [StorageOS documentation](https://docs.storageos.com).
## API Configuration
The StorageOS provider has been pre-configured to use the StorageOS API defaults, and no additional configuration is required for testing. If you have changed the API port, or have removed the default account or changed its password (recommended), you must specify the new settings. This is done using Kubernetes [Secrets](../../../docs/user-guide/secrets/).
API configuration is set by using Kubernetes secrets. The configuration secret supports the following parameters:
* `apiAddress`: The address of the StorageOS API. This is optional and defaults to `tcp://localhost:5705`, which should be correct if the StorageOS container is running using the default settings.
* `apiUsername`: The username to authenticate to the StorageOS API with.
* `apiPassword`: The password to authenticate to the StorageOS API with.
* `apiVersion`: Optional, string value defaulting to `1`. Only set this if requested in StorageOS documentation.
Mutiple credentials can be used by creating different secrets.
For Persistent Volumes, secrets must be created in the Pod namespace. Specify the secret name using the `secretName` parameter when attaching existing volumes in Pods or creating new persistent volumes.
For dynamically provisioned volumes using storage classes, the secret can be created in any namespace. Note that you would want this to be an admin-controlled namespace with restricted access to users. Specify the secret namespace as parameter `adminSecretNamespace` and name as parameter `adminSecretName` in storage classes.
Example spec:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: storageos-secret
type: "kubernetes.io/storageos"
data:
apiAddress: dGNwOi8vMTI3LjAuMC4xOjU3MDU=
apiUsername: c3RvcmFnZW9z
apiPassword: c3RvcmFnZW9z
```
Values for `apiAddress`, `apiUsername` and `apiPassword` can be generated with:
```bash
$ echo -n "tcp://127.0.0.1:5705" | base64
dGNwOi8vMTI3LjAuMC4xOjU3MDU=
```
Create the secret:
```bash
$ kubectl create -f storageos-secret.yaml
secret "storageos-secret" created
```
Verify the secret:
```bash
$ kubectl describe secret storageos-secret
Name: storageos-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: kubernetes.io/storageos
Data
====
apiAddress: 20 bytes
apiPassword: 8 bytes
apiUsername: 8 bytes
```
## Examples
These examples assume you have a running Kubernetes cluster with the StorageOS container running on each node, and that an API configuration secret called `storageos-secret` has been created in the `default` namespace.
### Pre-provisioned Volumes
#### Pod
Pods can be created that access volumes directly.
1. Create a volume using the StorageOS UI, CLI or API. Consult the [StorageOS documentation](https://docs.storageos.com) for details.
1. Create a pod that refers to the new volume. In this case the volume is named `redis-vol01`.
Example spec:
```yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis
role: master
name: test-storageos-redis
spec:
containers:
- name: master
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: redis-data
volumes:
- name: redis-data
storageos:
# This volume must already exist within StorageOS
volumeName: redis-vol01
# volumeNamespace is optional, and specifies the volume scope within
# StorageOS. If no namespace is provided, it will use the namespace
# of the pod. Set to `default` or leave blank if you are not using
# namespaces.
#volumeNamespace: test-storageos
# The filesystem type to format the volume with, if required.
fsType: ext4
# The secret name for API credentials
secretName: storageos-secret
```
[Download example](storageos-pod.yaml?raw=true)
Create the pod:
```bash
$ kubectl create -f examples/volumes/storageos/storageos-pod.yaml
```
Verify that the pod is running:
```bash
$ kubectl get pods test-storageos-redis
NAME READY STATUS RESTARTS AGE
test-storageos-redis 1/1 Running 0 30m
```
### Persistent Volumes
1. Create a volume using the StorageOS UI, CLI or API. Consult the [StorageOS documentation](https://docs.storageos.com) for details.
1. Create the persistent volume `redis-vol01`.
Example spec:
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageos:
# This volume must already exist within StorageOS
volumeName: pv0001
# volumeNamespace is optional, and specifies the volume scope within
# StorageOS. Set to `default` or leave blank if you are not using
# namespaces.
#volumeNamespace: default
# The filesystem type to create on the volume, if required.
fsType: ext4
# The secret name for API credentials
secretName: storageos-secret
```
[Download example](storageos-pv.yaml?raw=true)
Create the persistent volume:
```bash
$ kubectl create -f examples/volumes/storageos/storageos-pv.yaml
```
Verify that the pv has been created:
```bash
$ kubectl describe pv pv0001
Name: pv0001
Labels: <none>
Annotations: <none>
StorageClass: fast
Status: Available
Claim:
Reclaim Policy: Delete
Access Modes: RWO
Capacity: 5Gi
Message:
Source:
Type: StorageOS (a StorageOS Persistent Disk resource)
VolumeName: pv0001
VolumeNamespace:
FSType: ext4
ReadOnly: false
Events: <none>
```
1. Create persistent volume claim
Example spec:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc0001
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: fast
```
[Download example](storageos-pvc.yaml?raw=true)
Create the persistent volume claim:
```bash
$ kubectl create -f examples/volumes/storageos/storageos-pvc.yaml
```
Verify that the pvc has been created:
```bash
$ kubectl describe pvc pvc0001
Name: pvc0001
Namespace: default
StorageClass: fast
Status: Bound
Volume: pv0001
Labels: <none>
Capacity: 5Gi
Access Modes: RWO
No events.
```
1. Create pod which uses the persistent volume claim
Example spec:
```yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis
role: master
name: test-storageos-redis-pvc
spec:
containers:
- name: master
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: redis-data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: pvc0001
```
[Download example](storageos-pvcpod.yaml?raw=true)
Create the pod:
```bash
$ kubectl create -f examples/volumes/storageos/storageos-pvcpod.yaml
```
Verify that the pod has been created:
```bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
test-storageos-redis-pvc 1/1 Running 0 40s
```
### Dynamic Provisioning
Dynamic provisioning can be used to auto-create volumes when needed. They require a Storage Class, a Persistent Volume Claim, and a Pod.
#### Storage Class
Kubernetes administrators can use storage classes to define different types of storage made available within the cluster. Each storage class definition specifies a provisioner type and any parameters needed to access it, as well as any other configuration.
StorageOS supports the following storage class parameters:
* `pool`: The name of the StorageOS distributed capacity pool to provision the volume from. Uses the `default` pool which is normally present if not specified.
* `description`: The description to assign to volumes that were created dynamically. All volume descriptions will be the same for the storage class, but different storage classes can be used to allow descriptions for different use cases. Defaults to `Kubernetes volume`.
* `fsType`: The default filesystem type to request. Note that user-defined rules within StorageOS may override this value. Defaults to `ext4`.
* `adminSecretNamespace`: The namespace where the API configuration secret is located. Required if adminSecretName set.
* `adminSecretName`: The name of the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.
1. Create storage class
Example spec:
```yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: sc-fast
provisioner: kubernetes.io/storageos
parameters:
pool: default
description: Kubernetes volume
fsType: ext4
adminSecretNamespace: default
adminSecretName: storageos-secret
```
[Download example](storageos-sc.yaml?raw=true)
Create the storage class:
```bash
$ kubectl create -f examples/volumes/storageos/storageos-sc.yaml
```
Verify the storage class has been created:
```bash
$ kubectl describe storageclass fast
Name: fast
IsDefaultClass: No
Annotations: <none>
Provisioner: kubernetes.io/storageos
Parameters: description=Kubernetes volume,fsType=ext4,pool=default,secretName=storageos-secret
No events.
```
1. Create persistent volume claim
Example spec:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fast0001
spec:
storageClassName: fast
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
```
Create the persistent volume claim (pvc):
```bash
$ kubectl create -f examples/volumes/storageos/storageos-sc-pvc.yaml
```
Verify the pvc has been created:
```bash
$ kubectl describe pvc fast0001
Name: fast0001
Namespace: default
StorageClass: fast
Status: Bound
Volume: pvc-480952e7-f8e0-11e6-af8c-08002736b526
Labels: <none>
Capacity: 5Gi
Access Modes: RWO
Events:
<snip>
```
A new persistent volume will also be created and bound to the pvc:
```bash
$ kubectl describe pv pvc-480952e7-f8e0-11e6-af8c-08002736b526
Name: pvc-480952e7-f8e0-11e6-af8c-08002736b526
Labels: storageos.driver=filesystem
StorageClass: fast
Status: Bound
Claim: default/fast0001
Reclaim Policy: Delete
Access Modes: RWO
Capacity: 5Gi
Message:
Source:
Type: StorageOS (a StorageOS Persistent Disk resource)
VolumeName: pvc-480952e7-f8e0-11e6-af8c-08002736b526
Namespace: default
FSType: ext4
ReadOnly: false
No events.
```
1. Create pod which uses the persistent volume claim
Example spec:
```yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis
role: master
name: test-storageos-redis-sc-pvc
spec:
containers:
- name: master
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: redis-data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: fast0001
```
[Download example](storageos-sc-pvcpod.yaml?raw=true)
Create the pod:
```bash
$ kubectl create -f examples/volumes/storageos/storageos-sc-pvcpod.yaml
```
Verify that the pod has been created:
```bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
test-storageos-redis-sc-pvc 1/1 Running 0 44s
```
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/examples/volumes/storageos/README.md?pixel)]()
<!-- END MUNGE: GENERATED_ANALYTICS -->

View File

@ -0,0 +1,37 @@
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis
role: master
name: test-storageos-redis
spec:
containers:
- name: master
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: redis-data
volumes:
- name: redis-data
storageos:
# This volume must already exist within StorageOS
volumeName: redis-vol01
# Namespace is optional, and specifies the volume scope within
# StorageOS. If no namespace is provided, it will use the namespace
# of the pod. Set to `default` or leave blank if you are not using
# namespaces.
#namespace: test-storageos
# The name of the storageos pool to use. Will use `default` if not
# specified, which should be available on most StorageOS clusters.
pool: default
# The filesystem type to create on the volume, if required.
fsType: ext4

View File

@ -0,0 +1,22 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: fast
storageos:
# This volume must already exist within StorageOS
volumeName: pv0001
# volumeNamespace is optional, and specifies the volume scope within
# StorageOS. Set to `default` or leave blank if you are not using
# namespaces.
#volumeNamespace: default
# The filesystem type to create on the volume, if required.
fsType: ext4
# The secret name for API credentials
secretName: storageos-secret

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc0001
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: fast

View File

@ -0,0 +1,26 @@
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis
role: master
name: test-storageos-redis-pvc
spec:
containers:
- name: master
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: redis-data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: pvc0001

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fast0001
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 5Gi

View File

@ -0,0 +1,26 @@
apiVersion: v1
kind: Pod
metadata:
labels:
name: redis
role: master
name: test-storageos-redis-sc-pvc
spec:
containers:
- name: master
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: redis-data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: fast0001

View File

@ -0,0 +1,11 @@
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: sc-fast
provisioner: kubernetes.io/storageos
parameters:
pool: default
description: Kubernetes volume
fsType: ext4
adminSecretNamespace: default
adminSecretName: storageos-secret

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: storageos-secret
type: "kubernetes.io/storageos"
data:
apiAddress: dGNwOi8vMTI3LjAuMC4xOjU3MDU=
apiUsername: c3RvcmFnZW9z
apiPassword: c3RvcmFnZW9z

View File

@ -12650,6 +12650,31 @@
}
}
},
"io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource": {
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"fsType": {
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"type": "string"
},
"readOnly": {
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"type": "boolean"
},
"secretRef": {
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.LocalObjectReference"
},
"volumeName": {
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"type": "string"
},
"volumeNamespace": {
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"type": "string"
}
}
},
"io.k8s.kubernetes.pkg.api.v1.TCPSocketAction": {
"description": "TCPSocketAction describes an action based on opening a socket",
"required": [
@ -12804,6 +12829,10 @@
"description": "Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.SecretVolumeSource"
},
"storageos": {
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.StorageOSVolumeSource"
},
"vsphereVolume": {
"description": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine",
"$ref": "#/definitions/io.k8s.kubernetes.pkg.api.v1.VsphereVirtualDiskVolumeSource"

View File

@ -5161,6 +5161,10 @@
"scaleIO": {
"$ref": "v1.ScaleIOVolumeSource",
"description": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes."
},
"storageos": {
"$ref": "v1.StorageOSVolumeSource",
"description": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes."
}
}
},
@ -6024,6 +6028,32 @@
}
}
},
"v1.StorageOSVolumeSource": {
"id": "v1.StorageOSVolumeSource",
"description": "Represents a StorageOS persistent volume resource.",
"properties": {
"volumeName": {
"type": "string",
"description": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace."
},
"volumeNamespace": {
"type": "string",
"description": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
},
"readOnly": {
"type": "boolean",
"description": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts."
},
"secretRef": {
"$ref": "v1.LocalObjectReference",
"description": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -3548,6 +3548,13 @@ When an object is created, the system will populate this list with the current s
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_scaleiovolumesource">v1.ScaleIOVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">storageos</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_storageosvolumesource">v1.StorageOSVolumeSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -6972,6 +6979,68 @@ Both these may change in the future. Incoming requests are matched against the h
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_storageosvolumesource">v1.StorageOSVolumeSource</h3>
<div class="paragraph">
<p>Represents a StorageOS persistent volume resource.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeName</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">volumeNamespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod&#8217;s namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">readOnly</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">boolean</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">secretRef</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_localobjectreference">v1.LocalObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_nodeaffinity">v1.NodeAffinity</h3>
@ -7225,7 +7294,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-06-04 01:49:56 UTC
Last updated 2017-06-07 15:17:51 UTC
</div>
</div>
</body>

View File

@ -20,34 +20,46 @@ import (
"k8s.io/kubernetes/pkg/api"
)
func getClaimRefNamespace(pv *api.PersistentVolume) string {
if pv.Spec.ClaimRef != nil {
return pv.Spec.ClaimRef.Namespace
}
return ""
}
// VisitPVSecretNames invokes the visitor function with the name of every secret
// referenced by the PV spec. If visitor returns false, visiting is short-circuited.
// Returns true if visiting completed, false if visiting was short-circuited.
func VisitPVSecretNames(pv *api.PersistentVolume, visitor func(string) bool) bool {
func VisitPVSecretNames(pv *api.PersistentVolume, visitor func(string, string) bool) bool {
source := &pv.Spec.PersistentVolumeSource
switch {
case source.AzureFile != nil:
if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) {
if len(source.AzureFile.SecretName) > 0 && !visitor(getClaimRefNamespace(pv), source.AzureFile.SecretName) {
return false
}
return true
case source.CephFS != nil:
if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) {
if source.CephFS.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.CephFS.SecretRef.Name) {
return false
}
case source.FlexVolume != nil:
if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) {
if source.FlexVolume.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.FlexVolume.SecretRef.Name) {
return false
}
case source.RBD != nil:
if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) {
if source.RBD.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.RBD.SecretRef.Name) {
return false
}
case source.ScaleIO != nil:
if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) {
if source.ScaleIO.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.ScaleIO.SecretRef.Name) {
return false
}
case source.ISCSI != nil:
if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
if source.ISCSI.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.ISCSI.SecretRef.Name) {
return false
}
case source.StorageOS != nil:
if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Namespace, source.StorageOS.SecretRef.Name) {
return false
}
}

View File

@ -54,11 +54,21 @@ func TestPVSecrets(t *testing.T) {
ISCSI: &api.ISCSIVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.PersistentVolumeSource.ISCSI.SecretRef"}}}}},
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
StorageOS: &api.StorageOSPersistentVolumeSource{
SecretRef: &api.ObjectReference{
Name: "Spec.PersistentVolumeSource.StorageOS.SecretRef",
Namespace: "Spec.PersistentVolumeSource.StorageOS.SecretRef"}}}}},
}
extractedNames := sets.NewString()
extractedNamespaces := sets.NewString()
for _, pv := range pvs {
VisitPVSecretNames(pv, func(name string) bool {
VisitPVSecretNames(pv, func(namespace, name string) bool {
extractedNames.Insert(name)
if namespace != "" {
extractedNamespaces.Insert(namespace)
}
return true
})
}
@ -76,6 +86,7 @@ func TestPVSecrets(t *testing.T) {
"Spec.PersistentVolumeSource.RBD.SecretRef",
"Spec.PersistentVolumeSource.ScaleIO.SecretRef",
"Spec.PersistentVolumeSource.ISCSI.SecretRef",
"Spec.PersistentVolumeSource.StorageOS.SecretRef",
)
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.PersistentVolume{}))
secretPaths = secretPaths.Difference(excludedSecretPaths)
@ -96,6 +107,14 @@ func TestPVSecrets(t *testing.T) {
t.Logf("Extra secret names:\n%s", strings.Join(extraNames.List(), "\n"))
t.Error("Extra secret names extracted. Verify VisitPVSecretNames() is correctly extracting secret names")
}
expectedSecretNamespaces := sets.NewString(
"Spec.PersistentVolumeSource.StorageOS.SecretRef",
)
if len(expectedSecretNamespaces.Difference(extractedNamespaces)) > 0 {
t.Errorf("Missing expected secret namespace")
}
}
// collectSecretPaths traverses the object, computing all the struct paths that lead to fields with "secret" in the name.

View File

@ -84,6 +84,10 @@ func VisitPodSecretNames(pod *api.Pod, visitor Visitor) bool {
if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
return false
}
case source.StorageOS != nil:
if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Name) {
return false
}
}
}
return true

View File

@ -89,7 +89,11 @@ func TestPodSecrets(t *testing.T) {
VolumeSource: api.VolumeSource{
ISCSI: &api.ISCSIVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}},
Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, {
VolumeSource: api.VolumeSource{
StorageOS: &api.StorageOSVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}},
},
}
extractedNames := sets.NewString()
@ -119,6 +123,7 @@ func TestPodSecrets(t *testing.T) {
"Spec.Volumes[*].VolumeSource.Secret.SecretName",
"Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef",
"Spec.Volumes[*].VolumeSource.ISCSI.SecretRef",
"Spec.Volumes[*].VolumeSource.StorageOS.SecretRef",
)
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.Pod{}))
secretPaths = secretPaths.Difference(excludedSecretPaths)

View File

@ -313,6 +313,9 @@ type VolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional
ScaleIO *ScaleIOVolumeSource
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// +optional
StorageOS *StorageOSVolumeSource
}
// Similar to VolumeSource but meant for the administrator who creates PVs.
@ -384,6 +387,10 @@ type PersistentVolumeSource struct {
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
StorageOS *StorageOSPersistentVolumeSource
}
type PersistentVolumeClaimVolumeSource struct {
@ -1149,6 +1156,62 @@ type ScaleIOVolumeSource struct {
ReadOnly bool
}
// Represents a StorageOS persistent volume resource.
type StorageOSVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *LocalObjectReference
}
// Represents a StorageOS persistent volume resource.
type StorageOSPersistentVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *ObjectReference
}
// Adapts a ConfigMap into a volume.
//
// The contents of the target ConfigMap's Data field will be presented in a

File diff suppressed because it is too large Load Diff

View File

@ -2222,6 +2222,11 @@ message PersistentVolumeSource {
// Local represents directly-attached storage with node affinity
// +optional
optional LocalVolumeSource local = 20;
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
optional StorageOSPersistentVolumeSource storageos = 21;
}
// PersistentVolumeSpec is the specification of a persistent volume.
@ -3760,6 +3765,70 @@ message ServiceStatus {
optional LoadBalancerStatus loadBalancer = 1;
}
// Represents a StorageOS persistent volume resource.
message StorageOSPersistentVolumeSource {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
optional string volumeName = 1;
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
optional string volumeNamespace = 2;
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
optional string fsType = 3;
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
optional bool readOnly = 4;
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
optional ObjectReference secretRef = 5;
}
// Represents a StorageOS persistent volume resource.
message StorageOSVolumeSource {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
optional string volumeName = 1;
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
optional string volumeNamespace = 2;
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
optional string fsType = 3;
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
optional bool readOnly = 4;
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
optional LocalObjectReference secretRef = 5;
}
// Sysctl defines a kernel parameter to be set
message Sysctl {
// Name of a property to set
@ -4011,6 +4080,10 @@ message VolumeSource {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional
optional ScaleIOVolumeSource scaleIO = 25;
// StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.
// +optional
optional StorageOSVolumeSource storageos = 27;
}
// Represents a vSphere volume resource.

View File

@ -174,6 +174,10 @@ func VisitPodSecretNames(pod *v1.Pod, visitor Visitor) bool {
if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
return false
}
case source.StorageOS != nil:
if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Name) {
return false
}
}
}
return true

View File

@ -260,7 +260,11 @@ func TestPodSecrets(t *testing.T) {
VolumeSource: v1.VolumeSource{
ISCSI: &v1.ISCSIVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}},
Name: "Spec.Volumes[*].VolumeSource.ISCSI.SecretRef"}}}}, {
VolumeSource: v1.VolumeSource{
StorageOS: &v1.StorageOSVolumeSource{
SecretRef: &v1.LocalObjectReference{
Name: "Spec.Volumes[*].VolumeSource.StorageOS.SecretRef"}}}}},
},
}
extractedNames := sets.NewString()
@ -290,6 +294,7 @@ func TestPodSecrets(t *testing.T) {
"Spec.Volumes[*].VolumeSource.Secret.SecretName",
"Spec.Volumes[*].VolumeSource.ScaleIO.SecretRef",
"Spec.Volumes[*].VolumeSource.ISCSI.SecretRef",
"Spec.Volumes[*].VolumeSource.StorageOS.SecretRef",
)
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&v1.Pod{}))
secretPaths = secretPaths.Difference(excludedSecretPaths)

File diff suppressed because it is too large Load Diff

View File

@ -348,6 +348,9 @@ type VolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional
ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,25,opt,name=scaleIO"`
// StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.
// +optional
StorageOS *StorageOSVolumeSource `json:"storageos,omitempty" protobuf:"bytes,27,opt,name=storageos"`
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
@ -442,6 +445,10 @@ type PersistentVolumeSource struct {
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource `json:"local,omitempty" protobuf:"bytes,20,opt,name=local"`
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
StorageOS *StorageOSPersistentVolumeSource `json:"storageos,omitempty" protobuf:"bytes,21,opt,name=storageos"`
}
const (
@ -1228,6 +1235,62 @@ type ScaleIOVolumeSource struct {
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,10,opt,name=readOnly"`
}
// Represents a StorageOS persistent volume resource.
type StorageOSVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,1,opt,name=volumeName"`
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string `json:"volumeNamespace,omitempty" protobuf:"bytes,2,opt,name=volumeNamespace"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string `json:"fsType,omitempty" protobuf:"bytes,3,opt,name=fsType"`
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"`
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *LocalObjectReference `json:"secretRef,omitempty" protobuf:"bytes,5,opt,name=secretRef"`
}
// Represents a StorageOS persistent volume resource.
type StorageOSPersistentVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,1,opt,name=volumeName"`
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string `json:"volumeNamespace,omitempty" protobuf:"bytes,2,opt,name=volumeNamespace"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string `json:"fsType,omitempty" protobuf:"bytes,3,opt,name=fsType"`
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"`
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *ObjectReference `json:"secretRef,omitempty" protobuf:"bytes,5,opt,name=secretRef"`
}
// Adapts a ConfigMap into a volume.
//
// The contents of the target ConfigMap's Data field will be presented in a

View File

@ -1161,6 +1161,7 @@ var map_PersistentVolumeSource = map[string]string{
"portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine",
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"local": "Local represents directly-attached storage with node affinity",
"storageos": "StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md",
}
func (PersistentVolumeSource) SwaggerDoc() map[string]string {
@ -1875,6 +1876,32 @@ func (ServiceStatus) SwaggerDoc() map[string]string {
return map_ServiceStatus
}
var map_StorageOSPersistentVolumeSource = map[string]string{
"": "Represents a StorageOS persistent volume resource.",
"volumeName": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"volumeNamespace": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"readOnly": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"secretRef": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
}
func (StorageOSPersistentVolumeSource) SwaggerDoc() map[string]string {
return map_StorageOSPersistentVolumeSource
}
var map_StorageOSVolumeSource = map[string]string{
"": "Represents a StorageOS persistent volume resource.",
"volumeName": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"volumeNamespace": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"readOnly": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"secretRef": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
}
func (StorageOSVolumeSource) SwaggerDoc() map[string]string {
return map_StorageOSVolumeSource
}
var map_Sysctl = map[string]string{
"": "Sysctl defines a kernel parameter to be set",
"Name": "Name of a property to set",
@ -1980,6 +2007,7 @@ var map_VolumeSource = map[string]string{
"projected": "Items for all in one resources secrets, configmaps, and downward API",
"portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine",
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"storageos": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.",
}
func (VolumeSource) SwaggerDoc() map[string]string {

View File

@ -355,6 +355,10 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_ServiceSpec_To_v1_ServiceSpec,
Convert_v1_ServiceStatus_To_api_ServiceStatus,
Convert_api_ServiceStatus_To_v1_ServiceStatus,
Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource,
Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource,
Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource,
Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource,
Convert_v1_Sysctl_To_api_Sysctl,
Convert_api_Sysctl_To_v1_Sysctl,
Convert_v1_TCPSocketAction_To_api_TCPSocketAction,
@ -3027,6 +3031,7 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per
out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*api.LocalVolumeSource)(unsafe.Pointer(in.Local))
out.StorageOS = (*api.StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}
@ -3056,6 +3061,7 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api
out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*LocalVolumeSource)(unsafe.Pointer(in.Local))
out.StorageOS = (*StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}
@ -4807,6 +4813,62 @@ func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *S
return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s)
}
func autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*api.ObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource is an autogenerated conversion function.
func Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error {
return autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in, out, s)
}
func autoConvert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *api.StorageOSPersistentVolumeSource, out *StorageOSPersistentVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*ObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource is an autogenerated conversion function.
func Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *api.StorageOSPersistentVolumeSource, out *StorageOSPersistentVolumeSource, s conversion.Scope) error {
return autoConvert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in, out, s)
}
func autoConvert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in *StorageOSVolumeSource, out *api.StorageOSVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource is an autogenerated conversion function.
func Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in *StorageOSVolumeSource, out *api.StorageOSVolumeSource, s conversion.Scope) error {
return autoConvert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in, out, s)
}
func autoConvert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *api.StorageOSVolumeSource, out *StorageOSVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*LocalObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource is an autogenerated conversion function.
func Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *api.StorageOSVolumeSource, out *StorageOSVolumeSource, s conversion.Scope) error {
return autoConvert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in, out, s)
}
func autoConvert_v1_Sysctl_To_api_Sysctl(in *Sysctl, out *api.Sysctl, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
@ -5008,6 +5070,7 @@ func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.
out.Projected = (*api.ProjectedVolumeSource)(unsafe.Pointer(in.Projected))
out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.StorageOS = (*api.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}
@ -5043,6 +5106,7 @@ func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *
out.Projected = (*ProjectedVolumeSource)(unsafe.Pointer(in.Projected))
out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.StorageOS = (*StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}

View File

@ -195,6 +195,8 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_ServiceProxyOptions, InType: reflect.TypeOf(&ServiceProxyOptions{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_ServiceSpec, InType: reflect.TypeOf(&ServiceSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_ServiceStatus, InType: reflect.TypeOf(&ServiceStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_StorageOSPersistentVolumeSource, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_StorageOSVolumeSource, InType: reflect.TypeOf(&StorageOSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Sysctl, InType: reflect.TypeOf(&Sysctl{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_TCPSocketAction, InType: reflect.TypeOf(&TCPSocketAction{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Taint, InType: reflect.TypeOf(&Taint{})},
@ -2182,6 +2184,13 @@ func DeepCopy_v1_PersistentVolumeSource(in interface{}, out interface{}, c *conv
*out = new(LocalVolumeSource)
**out = **in
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSPersistentVolumeSource)
if err := DeepCopy_v1_StorageOSPersistentVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}
@ -3436,6 +3445,36 @@ func DeepCopy_v1_ServiceStatus(in interface{}, out interface{}, c *conversion.Cl
}
}
// DeepCopy_v1_StorageOSPersistentVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_StorageOSPersistentVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSPersistentVolumeSource)
out := out.(*StorageOSPersistentVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(ObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_v1_StorageOSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_StorageOSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSVolumeSource)
out := out.(*StorageOSVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(LocalObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_v1_Sysctl is an autogenerated deepcopy function.
func DeepCopy_v1_Sysctl(in interface{}, out interface{}, c *conversion.Cloner) error {
{
@ -3696,6 +3735,13 @@ func DeepCopy_v1_VolumeSource(in interface{}, out interface{}, c *conversion.Clo
return err
}
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSVolumeSource)
if err := DeepCopy_v1_StorageOSVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}

View File

@ -591,6 +591,14 @@ func validateVolumeSource(source *api.VolumeSource, fldPath *field.Path) field.E
allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...)
}
}
if source.StorageOS != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("storageos"), "may not specify more than 1 volume type"))
} else {
numVolumes++
allErrs = append(allErrs, validateStorageOSVolumeSource(source.StorageOS, fldPath.Child("storageos"))...)
}
}
if source.Projected != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("projected"), "may not specify more than 1 volume type"))
@ -1116,6 +1124,45 @@ func validateLocalVolumeSource(ls *api.LocalVolumeSource, fldPath *field.Path) f
return allErrs
}
func validateStorageOSVolumeSource(storageos *api.StorageOSVolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(storageos.VolumeName) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), ""))
} else {
allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...)
}
if len(storageos.VolumeNamespace) > 0 {
allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...)
}
if storageos.SecretRef != nil {
if len(storageos.SecretRef.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
}
}
return allErrs
}
func validateStorageOSPersistentVolumeSource(storageos *api.StorageOSPersistentVolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(storageos.VolumeName) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), ""))
} else {
allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeName, fldPath.Child("volumeName"))...)
}
if len(storageos.VolumeNamespace) > 0 {
allErrs = append(allErrs, ValidateDNS1123Label(storageos.VolumeNamespace, fldPath.Child("volumeNamespace"))...)
}
if storageos.SecretRef != nil {
if len(storageos.SecretRef.Name) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "name"), ""))
}
if len(storageos.SecretRef.Namespace) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("secretRef", "namespace"), ""))
}
}
return allErrs
}
// ValidatePersistentVolumeName checks that a name is appropriate for a
// PersistentVolumeName object.
var ValidatePersistentVolumeName = NameIsDNSSubdomain
@ -1325,6 +1372,14 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList {
}
}
}
if pv.Spec.StorageOS != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(specPath.Child("storageos"), "may not specify more than 1 volume type"))
} else {
numVolumes++
allErrs = append(allErrs, validateStorageOSPersistentVolumeSource(pv.Spec.StorageOS, specPath.Child("storageos"))...)
}
}
if numVolumes == 0 {
allErrs = append(allErrs, field.Required(specPath, "must specify a volume type"))

View File

@ -197,6 +197,8 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ServiceProxyOptions, InType: reflect.TypeOf(&ServiceProxyOptions{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ServiceSpec, InType: reflect.TypeOf(&ServiceSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ServiceStatus, InType: reflect.TypeOf(&ServiceStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_StorageOSPersistentVolumeSource, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_StorageOSVolumeSource, InType: reflect.TypeOf(&StorageOSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Sysctl, InType: reflect.TypeOf(&Sysctl{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_TCPSocketAction, InType: reflect.TypeOf(&TCPSocketAction{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Taint, InType: reflect.TypeOf(&Taint{})},
@ -2200,6 +2202,13 @@ func DeepCopy_api_PersistentVolumeSource(in interface{}, out interface{}, c *con
*out = new(LocalVolumeSource)
**out = **in
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSPersistentVolumeSource)
if err := DeepCopy_api_StorageOSPersistentVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}
@ -3442,6 +3451,36 @@ func DeepCopy_api_ServiceStatus(in interface{}, out interface{}, c *conversion.C
}
}
// DeepCopy_api_StorageOSPersistentVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_StorageOSPersistentVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSPersistentVolumeSource)
out := out.(*StorageOSPersistentVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(ObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_api_StorageOSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_StorageOSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSVolumeSource)
out := out.(*StorageOSVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(LocalObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_api_Sysctl is an autogenerated deepcopy function.
func DeepCopy_api_Sysctl(in interface{}, out interface{}, c *conversion.Cloner) error {
{
@ -3702,6 +3741,13 @@ func DeepCopy_api_VolumeSource(in interface{}, out interface{}, c *conversion.Cl
return err
}
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSVolumeSource)
if err := DeepCopy_api_StorageOSVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}

View File

@ -945,6 +945,7 @@ var (
Quobyte FSType = "quobyte"
AzureDisk FSType = "azureDisk"
PhotonPersistentDisk FSType = "photonPersistentDisk"
StorageOS FSType = "storageos"
Projected FSType = "projected"
PortworxVolume FSType = "portworxVolume"
ScaleIO FSType = "scaleIO"

View File

@ -742,6 +742,8 @@ func describeVolumes(volumes []api.Volume, w PrefixWriter, space string) {
printScaleIOVolumeSource(volume.VolumeSource.ScaleIO, w)
case volume.VolumeSource.CephFS != nil:
printCephFSVolumeSource(volume.VolumeSource.CephFS, w)
case volume.VolumeSource.StorageOS != nil:
printStorageOSVolumeSource(volume.VolumeSource.StorageOS, w)
default:
w.Write(LEVEL_1, "<unknown>\n")
}
@ -940,6 +942,24 @@ func printCephFSVolumeSource(cephfs *api.CephFSVolumeSource, w PrefixWriter) {
cephfs.Monitors, cephfs.Path, cephfs.User, cephfs.SecretFile, cephfs.SecretRef, cephfs.ReadOnly)
}
func printStorageOSVolumeSource(storageos *api.StorageOSVolumeSource, w PrefixWriter) {
w.Write(LEVEL_2, "Type:\tStorageOS (a StorageOS Persistent Disk resource)\n"+
" VolumeName:\t%v\n"+
" VolumeNamespace:\t%v\n"+
" FSType:\t%v\n"+
" ReadOnly:\t%v\n",
storageos.VolumeName, storageos.VolumeNamespace, storageos.FSType, storageos.ReadOnly)
}
func printStorageOSPersistentVolumeSource(storageos *api.StorageOSPersistentVolumeSource, w PrefixWriter) {
w.Write(LEVEL_2, "Type:\tStorageOS (a StorageOS Persistent Disk resource)\n"+
" VolumeName:\t%v\n"+
" VolumeNamespace:\t%v\n"+
" FSType:\t%v\n"+
" ReadOnly:\t%v\n",
storageos.VolumeName, storageos.VolumeNamespace, storageos.FSType, storageos.ReadOnly)
}
type PersistentVolumeDescriber struct {
clientset.Interface
}
@ -1013,6 +1033,8 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) (
printLocalVolumeSource(pv.Spec.Local, w)
case pv.Spec.CephFS != nil:
printCephFSVolumeSource(pv.Spec.CephFS, w)
case pv.Spec.StorageOS != nil:
printStorageOSPersistentVolumeSource(pv.Spec.StorageOS, w)
}
if events != nil {

View File

@ -64,6 +64,7 @@ func GetAllFSTypesAsSet() sets.String {
string(extensions.Quobyte),
string(extensions.AzureDisk),
string(extensions.PhotonPersistentDisk),
string(extensions.StorageOS),
string(extensions.Projected),
string(extensions.PortworxVolume),
string(extensions.ScaleIO),
@ -120,6 +121,8 @@ func GetVolumeFSType(v api.Volume) (extensions.FSType, error) {
return extensions.AzureDisk, nil
case v.PhotonPersistentDisk != nil:
return extensions.PhotonPersistentDisk, nil
case v.StorageOS != nil:
return extensions.StorageOS, nil
case v.Projected != nil:
return extensions.Projected, nil
case v.PortworxVolume != nil:

View File

@ -115,6 +115,7 @@ filegroup(
"//pkg/volume/rbd:all-srcs",
"//pkg/volume/scaleio:all-srcs",
"//pkg/volume/secret:all-srcs",
"//pkg/volume/storageos:all-srcs",
"//pkg/volume/testing:all-srcs",
"//pkg/volume/util:all-srcs",
"//pkg/volume/validation:all-srcs",

View File

@ -0,0 +1,69 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"storageos.go",
"storageos_util.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//pkg/volume/util/volumehelper:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/storageos/go-api:go_default_library",
"//vendor/github.com/storageos/go-api/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"storageos_test.go",
"storageos_util_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/client/clientset_generated/clientset/fake:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//vendor/github.com/storageos/go-api/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,4 @@
maintainers:
- croomes
- rusenask
- chira001

View File

@ -0,0 +1,19 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package storageos contains the internal representation of StorageOS
// PersistentDisk volumes.
package storageos // import "k8s.io/kubernetes/pkg/volume/storageos"

View File

@ -0,0 +1,741 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storageos
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount"
kstrings "k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
)
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&storageosPlugin{nil}}
}
type storageosPlugin struct {
host volume.VolumeHost
}
var _ volume.VolumePlugin = &storageosPlugin{}
var _ volume.PersistentVolumePlugin = &storageosPlugin{}
var _ volume.DeletableVolumePlugin = &storageosPlugin{}
var _ volume.ProvisionableVolumePlugin = &storageosPlugin{}
const (
storageosPluginName = "kubernetes.io/storageos"
storageosDevicePath = "/var/lib/storageos/volumes"
defaultAPIAddress = "tcp://localhost:5705"
defaultAPIUser = "storageos"
defaultAPIPassword = "storageos"
defaultAPIVersion = "1"
defaultFSType = "ext4"
defaultNamespace = "default"
)
func getPath(uid types.UID, volNamespace string, volName string, pvName string, host volume.VolumeHost) string {
if len(volNamespace) != 0 && len(volName) != 0 && strings.Count(volName, ".") == 0 {
return host.GetPodVolumeDir(uid, kstrings.EscapeQualifiedNameForDisk(storageosPluginName), pvName+"."+volNamespace+"."+volName)
}
return host.GetPodVolumeDir(uid, kstrings.EscapeQualifiedNameForDisk(storageosPluginName), pvName)
}
func (plugin *storageosPlugin) Init(host volume.VolumeHost) error {
plugin.host = host
return nil
}
func (plugin *storageosPlugin) GetPluginName() string {
return storageosPluginName
}
func (plugin *storageosPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return fmt.Sprintf("%s/%s", volumeSource.VolumeNamespace, volumeSource.VolumeName), nil
}
func (plugin *storageosPlugin) CanSupport(spec *volume.Spec) bool {
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS != nil) ||
(spec.Volume != nil && spec.Volume.StorageOS != nil)
}
func (plugin *storageosPlugin) RequiresRemount() bool {
return false
}
func (plugin *storageosPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
v1.ReadOnlyMany,
}
}
func (plugin *storageosPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
apiCfg, err := getAPICfg(spec, pod, plugin.host.GetKubeClient())
if err != nil {
return nil, err
}
return plugin.newMounterInternal(spec, pod, apiCfg, &storageosUtil{}, plugin.host.GetMounter())
}
func (plugin *storageosPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, apiCfg *storageosAPIConfig, manager storageosManager, mounter mount.Interface) (volume.Mounter, error) {
volName, volNamespace, fsType, readOnly, err := getVolumeInfoFromSpec(spec)
if err != nil {
return nil, err
}
return &storageosMounter{
storageos: &storageos{
podUID: pod.UID,
podNamespace: pod.GetNamespace(),
pvName: spec.Name(),
volName: volName,
volNamespace: volNamespace,
fsType: fsType,
readOnly: readOnly,
apiCfg: apiCfg,
manager: manager,
mounter: mounter,
plugin: plugin,
MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, volNamespace, volName, spec.Name(), plugin.host)),
},
devicePath: storageosDevicePath,
diskMounter: &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()},
}, nil
}
func (plugin *storageosPlugin) NewUnmounter(pvName string, podUID types.UID) (volume.Unmounter, error) {
return plugin.newUnmounterInternal(pvName, podUID, &storageosUtil{}, plugin.host.GetMounter())
}
func (plugin *storageosPlugin) newUnmounterInternal(pvName string, podUID types.UID, manager storageosManager, mounter mount.Interface) (volume.Unmounter, error) {
// Parse volume namespace & name from mountpoint if mounted
volNamespace, volName, err := getVolumeInfo(pvName, podUID, plugin.host)
if err != nil {
return nil, err
}
return &storageosUnmounter{
storageos: &storageos{
podUID: podUID,
pvName: pvName,
volName: volName,
volNamespace: volNamespace,
manager: manager,
mounter: mounter,
plugin: plugin,
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volNamespace, volName, pvName, plugin.host)),
},
}, nil
}
func (plugin *storageosPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS == nil {
return nil, fmt.Errorf("spec.PersistentVolumeSource.StorageOS is nil")
}
class, err := util.GetClassForVolume(plugin.host.GetKubeClient(), spec.PersistentVolume)
if err != nil {
return nil, err
}
var adminSecretName, adminSecretNamespace string
for k, v := range class.Parameters {
switch strings.ToLower(k) {
case "adminsecretname":
adminSecretName = v
case "adminsecretnamespace":
adminSecretNamespace = v
}
}
apiCfg, err := parsePVSecret(adminSecretNamespace, adminSecretName, plugin.host.GetKubeClient())
if err != nil {
return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err)
}
return plugin.newDeleterInternal(spec, apiCfg, &storageosUtil{})
}
func (plugin *storageosPlugin) newDeleterInternal(spec *volume.Spec, apiCfg *storageosAPIConfig, manager storageosManager) (volume.Deleter, error) {
return &storageosDeleter{
storageosMounter: &storageosMounter{
storageos: &storageos{
pvName: spec.Name(),
volName: spec.PersistentVolume.Spec.StorageOS.VolumeName,
volNamespace: spec.PersistentVolume.Spec.StorageOS.VolumeNamespace,
apiCfg: apiCfg,
manager: manager,
plugin: plugin,
},
},
pvUID: spec.PersistentVolume.UID,
}, nil
}
func (plugin *storageosPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
return plugin.newProvisionerInternal(options, &storageosUtil{})
}
func (plugin *storageosPlugin) newProvisionerInternal(options volume.VolumeOptions, manager storageosManager) (volume.Provisioner, error) {
return &storageosProvisioner{
storageosMounter: &storageosMounter{
storageos: &storageos{
manager: manager,
plugin: plugin,
},
},
options: options,
}, nil
}
func (plugin *storageosPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
volNamespace, volName, err := getVolumeFromRef(volumeName)
if err != nil {
volNamespace = defaultNamespace
volName = volumeName
}
storageosVolume := &v1.Volume{
Name: volumeName,
VolumeSource: v1.VolumeSource{
StorageOS: &v1.StorageOSVolumeSource{
VolumeName: volName,
VolumeNamespace: volNamespace,
},
},
}
return volume.NewSpecFromVolume(storageosVolume), nil
}
func (plugin *storageosPlugin) SupportsMountOption() bool {
return false
}
func (plugin *storageosPlugin) SupportsBulkVolumeVerification() bool {
return false
}
func getVolumeSource(spec *volume.Spec) (*v1.StorageOSVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.StorageOS != nil {
return spec.Volume.StorageOS, spec.Volume.StorageOS.ReadOnly, nil
}
return nil, false, fmt.Errorf("Spec does not reference a StorageOS volume type")
}
func getPersistentVolumeSource(spec *volume.Spec) (*v1.StorageOSPersistentVolumeSource, bool, error) {
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS != nil {
return spec.PersistentVolume.Spec.StorageOS, spec.ReadOnly, nil
}
return nil, false, fmt.Errorf("Spec does not reference a StorageOS persistent volume type")
}
// storageosManager is the abstract interface to StorageOS volume ops.
type storageosManager interface {
// Connects to the StorageOS API using the supplied configuration.
NewAPI(apiCfg *storageosAPIConfig) error
// Creates a StorageOS volume.
CreateVolume(provisioner *storageosProvisioner) (*storageosVolume, error)
// Attaches the disk to the kubelet's host machine.
AttachVolume(mounter *storageosMounter) (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.
MountVolume(mounter *storageosMounter, mnt, dir string) error
// Unmounts the disk from the Kubelet's host machine.
UnmountVolume(unounter *storageosUnmounter) error
// Deletes the storageos volume. All data will be lost.
DeleteVolume(deleter *storageosDeleter) error
}
// storageos volumes represent a bare host directory mount of an StorageOS export.
type storageos struct {
podUID types.UID
podNamespace string
pvName string
volName string
volNamespace string
secretName string
readOnly bool
description string
pool string
fsType string
sizeGB int
labels map[string]string
apiCfg *storageosAPIConfig
manager storageosManager
mounter mount.Interface
plugin *storageosPlugin
volume.MetricsProvider
}
type storageosMounter struct {
*storageos
devicePath string
// Interface used to mount the file or block device
diskMounter *mount.SafeFormatAndMount
}
var _ volume.Mounter = &storageosMounter{}
func (b *storageosMounter) GetAttributes() volume.Attributes {
return volume.Attributes{
ReadOnly: b.readOnly,
Managed: !b.readOnly,
SupportsSELinux: true,
}
}
// Checks prior to mount operations to verify that the required components (binaries, etc.)
// to mount the volume are available on the underlying node.
// If not, it returns an error
func (b *storageosMounter) CanMount() error {
return nil
}
// SetUp attaches the disk and bind mounts to the volume path.
func (b *storageosMounter) SetUp(fsGroup *types.UnixGroupID) error {
// Need a namespace to find the volume, try pod's namespace if not set.
if b.volNamespace == "" {
glog.V(2).Infof("Setting StorageOS volume namespace to pod namespace: %s", b.podNamespace)
b.volNamespace = b.podNamespace
}
// Attach the StorageOS volume as a block device
devicePath, err := b.manager.AttachVolume(b)
if err != nil {
glog.Errorf("Failed to attach StorageOS volume %s: %s", b.volName, err.Error())
return err
}
// 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)
if err != nil {
return err
}
glog.V(4).Infof("Successfully mounted StorageOS volume %s into global mount directory", b.volName)
// Bind mount the volume into the pod
return b.SetUpAt(b.GetPath(), fsGroup)
}
// SetUp bind mounts the disk global mount to the give volume path.
func (b *storageosMounter) SetUpAt(dir string, fsGroup *types.UnixGroupID) error {
notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
glog.V(4).Infof("StorageOS volume set up: %s %v %v", dir, !notMnt, err)
if err != nil && !os.IsNotExist(err) {
glog.Errorf("Cannot validate mount point: %s %v", dir, err)
return err
}
if !notMnt {
return nil
}
if err = os.MkdirAll(dir, 0750); err != nil {
glog.Errorf("mkdir failed on disk %s (%v)", dir, err)
return err
}
// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
options := []string{"bind"}
if b.readOnly {
options = append(options, "ro")
}
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)
err = b.mounter.Mount(globalPDPath, dir, "", options)
if err != nil {
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil {
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return err
}
if !notMnt {
if mntErr = b.mounter.Unmount(dir); mntErr != nil {
glog.Errorf("Failed to unmount: %v", mntErr)
return err
}
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil {
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return err
}
if !notMnt {
glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
return err
}
}
os.Remove(dir)
glog.Errorf("Mount of disk %s failed: %v", dir, err)
return err
}
if !b.readOnly {
volume.SetVolumeOwnership(b, fsGroup)
}
glog.V(4).Infof("StorageOS volume setup complete on %s", dir)
return nil
}
func makeGlobalPDName(host volume.VolumeHost, pvName, volNamespace, volName string) string {
return path.Join(host.GetPluginDir(kstrings.EscapeQualifiedNameForDisk(storageosPluginName)), mount.MountsInGlobalPDPath, pvName+"."+volNamespace+"."+volName)
}
// Given the pod id and PV name, finds the volume's namespace and name from the
// name or volume mount. We mount as volNamespace.pvName, but k8s will specify
// only the pvName to unmount.
// Will return empty volNamespace/pvName if the volume is not mounted.
func getVolumeInfo(pvName string, podUID types.UID, host volume.VolumeHost) (string, string, error) {
if volNamespace, volName, err := getVolumeFromRef(pvName); err == nil {
return volNamespace, volName, nil
}
volumeDir := filepath.Dir(host.GetPodVolumeDir(podUID, kstrings.EscapeQualifiedNameForDisk(storageosPluginName), pvName))
files, err := ioutil.ReadDir(volumeDir)
if err != nil {
return "", "", fmt.Errorf("Could not read mounts from pod volume dir: %s", err)
}
for _, f := range files {
if f.Mode().IsDir() && strings.HasPrefix(f.Name(), pvName+".") {
if volNamespace, volName, err := getVolumeFromRef(f.Name()); err == nil {
return volNamespace, volName, nil
}
}
}
return "", "", fmt.Errorf("Could not get info from unmounted pv %q at %q", pvName, volumeDir)
}
// Splits the volume ref on "." to return the volNamespace and pvName. Neither
// namespaces nor service names allow "." in their names.
func getVolumeFromRef(ref string) (volNamespace string, volName string, err error) {
refParts := strings.Split(ref, ".")
switch len(refParts) {
case 2:
return refParts[0], refParts[1], nil
case 3:
return refParts[1], refParts[2], nil
}
return "", "", fmt.Errorf("ref not in format volNamespace.volName or pvName.volNamespace.volName")
}
// GetPath returns the path to the user specific mount of a StorageOS volume
func (storageosVolume *storageos) GetPath() string {
return getPath(storageosVolume.podUID, storageosVolume.volNamespace, storageosVolume.volName, storageosVolume.pvName, storageosVolume.plugin.host)
}
type storageosUnmounter struct {
*storageos
}
var _ volume.Unmounter = &storageosUnmounter{}
func (b *storageosUnmounter) GetPath() string {
return getPath(b.podUID, b.volNamespace, b.volName, b.pvName, b.plugin.host)
}
// Unmounts the bind mount, and detaches the disk only if the PD
// resource was the last reference to that disk on the kubelet.
func (b *storageosUnmounter) TearDown() error {
if len(b.volNamespace) == 0 || len(b.volName) == 0 {
glog.Warningf("volNamespace: %q, volName: %q not set, skipping TearDown", b.volNamespace, b.volName)
return fmt.Errorf("pvName not specified for TearDown, waiting for next sync loop")
}
// Unmount from pod
mountPath := b.GetPath()
err := b.TearDownAt(mountPath)
if err != nil {
glog.Errorf("Unmount from pod failed: %v", err)
return err
}
// Find device name from global mount
globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName)
devicePath, _, err := mount.GetDeviceNameFromMount(b.mounter, globalPDPath)
if err != nil {
glog.Errorf("Detach failed when getting device from global mount: %v", err)
return err
}
// Unmount from plugin's disk global mount dir.
err = b.TearDownAt(globalPDPath)
if err != nil {
glog.Errorf("Detach failed during unmount: %v", err)
return err
}
// Detach loop device
err = b.manager.DetachVolume(b, devicePath)
if err != nil {
glog.Errorf("Detach device %s failed for volume %s: %v", devicePath, b.pvName, err)
return err
}
glog.V(4).Infof("Successfully unmounted StorageOS volume %s and detached devices", b.pvName)
return nil
}
// Unmounts the bind mount, and detaches the disk only if the PD
// resource was the last reference to that disk on the kubelet.
func (b *storageosUnmounter) TearDownAt(dir string) error {
if err := util.UnmountPath(dir, b.mounter); err != nil {
glog.V(4).Infof("Unmounted StorageOS volume %s failed with: %v", b.pvName, err)
}
if err := b.manager.UnmountVolume(b); err != nil {
glog.V(4).Infof("Mount reference for volume %s could not be removed from StorageOS: %v", b.pvName, err)
}
return nil
}
type storageosDeleter struct {
*storageosMounter
pvUID types.UID
}
var _ volume.Deleter = &storageosDeleter{}
func (d *storageosDeleter) GetPath() string {
return getPath(d.podUID, d.volNamespace, d.volName, d.pvName, d.plugin.host)
}
func (d *storageosDeleter) Delete() error {
return d.manager.DeleteVolume(d)
}
type storageosProvisioner struct {
*storageosMounter
options volume.VolumeOptions
}
var _ volume.Provisioner = &storageosProvisioner{}
func (c *storageosProvisioner) Provision() (*v1.PersistentVolume, error) {
var adminSecretName, adminSecretNamespace string
// Apply ProvisionerParameters (case-insensitive). We leave validation of
// the values to the cloud provider.
for k, v := range c.options.Parameters {
switch strings.ToLower(k) {
case "adminsecretname":
adminSecretName = v
case "adminsecretnamespace":
adminSecretNamespace = v
case "volumenamespace":
c.volNamespace = v
case "description":
c.description = v
case "pool":
c.pool = v
case "fstype":
c.fsType = v
default:
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName())
}
}
// Set from PVC
c.podNamespace = c.options.PVC.Namespace
c.volName = c.options.PVName
if c.volNamespace == "" {
c.volNamespace = c.options.PVC.Namespace
}
c.labels = make(map[string]string)
for k, v := range c.options.PVC.Labels {
c.labels[k] = v
}
capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
c.sizeGB = int(volume.RoundUpSize(capacity.Value(), 1024*1024*1024))
apiCfg, err := parsePVSecret(adminSecretNamespace, adminSecretName, c.plugin.host.GetKubeClient())
if err != nil {
return nil, err
}
c.apiCfg = apiCfg
vol, err := c.manager.CreateVolume(c)
if err != nil {
glog.Errorf("failed to create volume: %v", err)
return nil, err
}
if vol.FSType == "" {
vol.FSType = defaultFSType
}
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: vol.Name,
Labels: map[string]string{},
Annotations: map[string]string{
volumehelper.VolumeDynamicallyCreatedByKey: "storageos-dynamic-provisioner",
},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
AccessModes: c.options.PVC.Spec.AccessModes,
Capacity: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", vol.SizeGB)),
},
PersistentVolumeSource: v1.PersistentVolumeSource{
StorageOS: &v1.StorageOSPersistentVolumeSource{
VolumeName: vol.Name,
VolumeNamespace: vol.Namespace,
FSType: vol.FSType,
ReadOnly: false,
SecretRef: &v1.ObjectReference{
Name: adminSecretName,
Namespace: adminSecretNamespace,
},
},
},
},
}
if len(c.options.PVC.Spec.AccessModes) == 0 {
pv.Spec.AccessModes = c.plugin.GetAccessModes()
}
if len(vol.Labels) != 0 {
if pv.Labels == nil {
pv.Labels = make(map[string]string)
}
for k, v := range vol.Labels {
pv.Labels[k] = v
}
}
return pv, nil
}
// Returns StorageOS volume name, namespace, fstype and readonly from spec
func getVolumeInfoFromSpec(spec *volume.Spec) (string, string, string, bool, error) {
if spec.PersistentVolume != nil {
source, readOnly, err := getPersistentVolumeSource(spec)
if err != nil {
return "", "", "", false, err
}
return source.VolumeName, source.VolumeNamespace, source.FSType, readOnly, nil
}
if spec.Volume != nil {
source, readOnly, err := getVolumeSource(spec)
if err != nil {
return "", "", "", false, err
}
return source.VolumeName, source.VolumeNamespace, source.FSType, readOnly, nil
}
return "", "", "", false, fmt.Errorf("spec not Volume or PersistentVolume")
}
// Returns API config if secret set, otherwise empty struct so defaults can be
// attempted.
func getAPICfg(spec *volume.Spec, pod *v1.Pod, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
if spec.PersistentVolume != nil {
source, _, err := getPersistentVolumeSource(spec)
if err != nil {
return nil, err
}
if source.SecretRef == nil {
return nil, nil
}
return parsePVSecret(source.SecretRef.Namespace, source.SecretRef.Name, kubeClient)
}
if spec.Volume != nil {
source, _, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
if source.SecretRef == nil {
return nil, nil
}
return parsePodSecret(pod, source.SecretRef.Name, kubeClient)
}
return nil, fmt.Errorf("spec not Volume or PersistentVolume")
}
func parsePodSecret(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
secret, err := util.GetSecretForPod(pod, secretName, kubeClient)
if err != nil {
glog.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName)
return nil, fmt.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName)
}
return parseAPIConfig(secret)
}
// Important: Only to be called with data from a PV to avoid secrets being
// loaded from a user-suppler namespace.
func parsePVSecret(namespace, secretName string, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
secret, err := util.GetSecretForPV(namespace, secretName, storageosPluginName, kubeClient)
if err != nil {
glog.Errorf("failed to get secret from [%q/%q]", namespace, secretName)
return nil, fmt.Errorf("failed to get secret from [%q/%q]", namespace, secretName)
}
return parseAPIConfig(secret)
}
// Parse API configuration from parameters or secret
func parseAPIConfig(params map[string]string) (*storageosAPIConfig, error) {
if len(params) == 0 {
return nil, fmt.Errorf("empty API config")
}
c := &storageosAPIConfig{}
for name, data := range params {
switch strings.ToLower(name) {
case "apiaddress":
c.apiAddr = string(data)
case "apiusername":
c.apiUser = string(data)
case "apipassword":
c.apiPass = string(data)
case "apiversion":
c.apiVersion = string(data)
}
}
return c, nil
}

View File

@ -0,0 +1,370 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storageos
import (
"fmt"
"os"
"path"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
)
func TestCanSupport(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/storageos")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.GetPluginName() != "kubernetes.io/storageos" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{StorageOS: &v1.StorageOSVolumeSource{}}}}) {
t.Errorf("Expected true")
}
if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{StorageOS: &v1.StorageOSPersistentVolumeSource{}}}}}) {
t.Errorf("Expected true")
}
}
func TestGetAccessModes(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/storageos")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if !contains(plug.GetAccessModes(), v1.ReadWriteOnce) || !contains(plug.GetAccessModes(), v1.ReadOnlyMany) {
t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany)
}
}
type fakePDManager struct {
api apiImplementer
attachCalled bool
detachCalled bool
mountCalled bool
unmountCalled bool
createCalled bool
deleteCalled bool
}
func (fake *fakePDManager) NewAPI(apiCfg *storageosAPIConfig) error {
fake.api = fakeAPI{}
return nil
}
func (fake *fakePDManager) CreateVolume(p *storageosProvisioner) (*storageosVolume, error) {
fake.createCalled = true
labels := make(map[string]string)
labels["fakepdmanager"] = "yes"
return &storageosVolume{
Name: "test-storageos-name",
Namespace: "test-storageos-namespace",
Pool: "test-storageos-pool",
SizeGB: 100,
Labels: labels,
FSType: "ext2",
}, nil
}
func (fake *fakePDManager) AttachVolume(b *storageosMounter) (string, error) {
fake.attachCalled = true
return "", nil
}
func (fake *fakePDManager) DetachVolume(b *storageosUnmounter, loopDevice string) error {
fake.detachCalled = true
return nil
}
func (fake *fakePDManager) MountVolume(b *storageosMounter, mntDevice, deviceMountPath string) error {
fake.mountCalled = true
return nil
}
func (fake *fakePDManager) UnmountVolume(b *storageosUnmounter) error {
fake.unmountCalled = true
return nil
}
func (fake *fakePDManager) DeleteVolume(d *storageosDeleter) error {
fake.deleteCalled = true
if d.volName != "test-storageos-name" {
return fmt.Errorf("Deleter got unexpected volume name: %s", d.volName)
}
return nil
}
func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/storageos")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
secretName := "very-secret"
spec := &v1.Volume{
Name: "vol1-pvname",
VolumeSource: v1.VolumeSource{
StorageOS: &v1.StorageOSVolumeSource{
VolumeName: "vol1",
VolumeNamespace: "ns1",
FSType: "ext3",
SecretRef: &v1.LocalObjectReference{
Name: secretName,
},
},
},
}
client := fake.NewSimpleClientset()
client.Core().Secrets("default").Create(&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: "default",
},
Type: "kubernetes.io/storageos",
Data: map[string][]byte{
"apiUsername": []byte("storageos"),
"apiPassword": []byte("storageos"),
"apiAddr": []byte("tcp://localhost:5705"),
}})
plug.(*storageosPlugin).host = volumetest.NewFakeVolumeHost(tmpDir, client, nil)
// Test Mounter
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid"), Namespace: "default"}}
fakeManager := &fakePDManager{}
apiCfg, err := parsePodSecret(pod, secretName, plug.(*storageosPlugin).host.GetKubeClient())
if err != nil {
t.Errorf("Couldn't get secret from %v/%v", pod.Namespace, secretName)
}
mounter, err := plug.(*storageosPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), pod, apiCfg, fakeManager, &mount.FakeMounter{})
if err != nil {
t.Fatalf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Fatalf("Got a nil Mounter")
}
expectedPath := path.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~storageos/vol1-pvname.ns1.vol1")
volPath := mounter.GetPath()
if volPath != expectedPath {
t.Errorf("Expected path: '%s' got: '%s'", expectedPath, volPath)
}
if err := mounter.SetUp(nil); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(volPath); err != nil {
if os.IsNotExist(err) {
t.Errorf("SetUp() failed, volume path not created: %s", volPath)
} else {
t.Errorf("SetUp() failed: %v", err)
}
}
if !fakeManager.attachCalled {
t.Errorf("Attach not called")
}
if !fakeManager.mountCalled {
t.Errorf("Mount not called")
}
// Test Unmounter
fakeManager = &fakePDManager{}
unmounter, err := plug.(*storageosPlugin).newUnmounterInternal("vol1-pvname", types.UID("poduid"), fakeManager, &mount.FakeMounter{})
if err != nil {
t.Errorf("Failed to make a new Unmounter: %v", err)
}
if unmounter == nil {
t.Errorf("Got a nil Unmounter")
}
volPath = unmounter.GetPath()
if volPath != expectedPath {
t.Errorf("Expected path: '%s' got: '%s'", expectedPath, volPath)
}
if err := unmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(volPath); err == nil {
t.Errorf("TearDown() failed, volume path still exists: %s", volPath)
} else if !os.IsNotExist(err) {
t.Errorf("SetUp() failed: %v", err)
}
if !fakeManager.unmountCalled {
t.Errorf("Unmount not called")
}
if !fakeManager.detachCalled {
t.Errorf("Detach not called")
}
// Test Provisioner
fakeManager = &fakePDManager{}
options := volume.VolumeOptions{
PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
// PVName: "test-volume-name",
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
Parameters: map[string]string{
"VolumeNamespace": "test-volume-namespace",
"adminSecretName": secretName,
},
}
provisioner, err := plug.(*storageosPlugin).newProvisionerInternal(options, fakeManager)
if err != nil {
t.Errorf("newProvisionerInternal() failed: %v", err)
}
persistentSpec, err := provisioner.Provision()
if err != nil {
t.Fatalf("Provision() failed: %v", err)
}
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeName != "test-storageos-name" {
t.Errorf("Provision() returned unexpected volume Name: %s, expected test-storageos-name", persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeName)
}
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeNamespace != "test-storageos-namespace" {
t.Errorf("Provision() returned unexpected volume Namespace: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeNamespace)
}
cap := persistentSpec.Spec.Capacity[v1.ResourceStorage]
size := cap.Value()
if size != 100*1024*1024*1024 {
t.Errorf("Provision() returned unexpected volume size: %v", size)
}
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType != "ext2" {
t.Errorf("Provision() returned unexpected volume FSType: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType)
}
if persistentSpec.Labels["fakepdmanager"] != "yes" {
t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels)
}
if !fakeManager.createCalled {
t.Errorf("Create not called")
}
// Test Deleter
fakeManager = &fakePDManager{}
volSpec := &volume.Spec{
PersistentVolume: persistentSpec,
}
deleter, err := plug.(*storageosPlugin).newDeleterInternal(volSpec, apiCfg, fakeManager)
if err != nil {
t.Errorf("newDeleterInternal() failed: %v", err)
}
err = deleter.Delete()
if err != nil {
t.Errorf("Deleter() failed: %v", err)
}
if !fakeManager.deleteCalled {
t.Errorf("Delete not called")
}
}
func contains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
for _, m := range modes {
if m == mode {
return true
}
}
return false
}
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvA",
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
StorageOS: &v1.StorageOSPersistentVolumeSource{VolumeName: "pvA", VolumeNamespace: "vnsA", ReadOnly: false},
},
ClaimRef: &v1.ObjectReference{
Name: "claimA",
},
},
}
claim := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "claimA",
Namespace: "nsA",
},
Spec: v1.PersistentVolumeClaimSpec{
VolumeName: "pvA",
},
Status: v1.PersistentVolumeClaimStatus{
Phase: v1.ClaimBound,
},
}
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plug, _ := plugMgr.FindPluginByName(storageosPluginName)
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
spec := volume.NewSpecFromPersistentVolume(pv, true)
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", UID: types.UID("poduid")}}
fakeManager := &fakePDManager{}
fakeConfig := &fakeConfig{}
apiCfg := fakeConfig.GetAPIConfig()
mounter, err := plug.(*storageosPlugin).newMounterInternal(spec, pod, apiCfg, fakeManager, &mount.FakeMounter{})
if !mounter.GetAttributes().ReadOnly {
t.Errorf("Expected true for mounter.IsReadOnly")
}
}

View File

@ -0,0 +1,374 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storageos
import (
"errors"
"fmt"
"os"
"path"
"strings"
"k8s.io/kubernetes/pkg/util/exec"
"github.com/golang/glog"
storageosapi "github.com/storageos/go-api"
storageostypes "github.com/storageos/go-api/types"
)
const (
losetupPath = "losetup"
modeBlock deviceType = iota
modeFile
modeUnsupported
ErrDeviceNotFound = "device not found"
ErrDeviceNotSupported = "device not supported"
ErrNotAvailable = "not available"
)
type deviceType int
// storageosVolume describes a provisioned volume
type storageosVolume struct {
ID string
Name string
Namespace string
Description string
Pool string
SizeGB int
Labels map[string]string
FSType string
}
type storageosAPIConfig struct {
apiAddr string
apiUser string
apiPass string
apiVersion string
}
type apiImplementer interface {
Volume(namespace string, ref string) (*storageostypes.Volume, error)
VolumeCreate(opts storageostypes.VolumeCreateOptions) (*storageostypes.Volume, error)
VolumeMount(opts storageostypes.VolumeMountOptions) error
VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error
VolumeDelete(opt storageostypes.DeleteOptions) error
}
// storageosUtil is the utility structure to interact with the StorageOS API.
type storageosUtil struct {
api apiImplementer
}
func (u *storageosUtil) NewAPI(apiCfg *storageosAPIConfig) error {
if u.api != nil {
return nil
}
if apiCfg == nil {
apiCfg = &storageosAPIConfig{
apiAddr: defaultAPIAddress,
apiUser: defaultAPIUser,
apiPass: defaultAPIPassword,
apiVersion: defaultAPIVersion,
}
glog.V(4).Infof("Using default StorageOS API settings: addr %s, version: %s", apiCfg.apiAddr, defaultAPIVersion)
}
api, err := storageosapi.NewVersionedClient(apiCfg.apiAddr, defaultAPIVersion)
if err != nil {
return err
}
api.SetAuth(apiCfg.apiUser, apiCfg.apiPass)
u.api = api
return nil
}
// Creates a new StorageOS volume and makes it available as a device within
// /var/lib/storageos/volumes.
func (u *storageosUtil) CreateVolume(p *storageosProvisioner) (*storageosVolume, error) {
if err := u.NewAPI(p.apiCfg); err != nil {
return nil, err
}
if p.labels == nil {
p.labels = make(map[string]string)
}
opts := storageostypes.VolumeCreateOptions{
Name: p.volName,
Size: p.sizeGB,
Description: p.description,
Pool: p.pool,
FSType: p.fsType,
Namespace: p.volNamespace,
Labels: p.labels,
}
vol, err := u.api.VolumeCreate(opts)
if err != nil {
glog.Errorf("volume create failed for volume %q (%v)", opts.Name, err)
return nil, err
}
return &storageosVolume{
ID: vol.ID,
Name: vol.Name,
Namespace: vol.Namespace,
Description: vol.Description,
Pool: vol.Pool,
FSType: vol.FSType,
SizeGB: int(vol.Size),
Labels: vol.Labels,
}, nil
}
// Attach exposes a volume on the host as a block device. StorageOS uses a
// global namespace, so if the volume exists, it should already be available as
// a device within `/var/lib/storageos/volumes/<id>`.
//
// Depending on the host capabilities, the device may be either a block device
// or a file device. Block devices can be used directly, but file devices must
// be made accessible as a block device before using.
func (u *storageosUtil) AttachVolume(b *storageosMounter) (string, error) {
if err := u.NewAPI(b.apiCfg); err != nil {
return "", err
}
vol, err := u.api.Volume(b.volNamespace, b.volName)
if err != nil {
glog.Warningf("volume retrieve failed for volume %q with namespace %q (%v)", b.volName, b.volNamespace, err)
return "", err
}
// Clear any existing mount reference from the API. These may be leftover
// from previous mounts where the unmount operation couldn't get access to
// the API credentials.
if vol.Mounted {
opts := storageostypes.VolumeUnmountOptions{
Name: vol.Name,
Namespace: vol.Namespace,
}
if err := u.api.VolumeUnmount(opts); err != nil {
glog.Warningf("Couldn't clear existing StorageOS mount reference: %v", err)
}
}
srcPath := path.Join(b.devicePath, vol.ID)
dt, err := pathDeviceType(srcPath)
if err != nil {
glog.Warningf("volume source path %q for volume %q not ready (%v)", srcPath, b.volName, err)
return "", err
}
switch dt {
case modeBlock:
return srcPath, nil
case modeFile:
return attachFileDevice(srcPath)
default:
return "", fmt.Errorf(ErrDeviceNotSupported)
}
}
// Detach detaches a volume from the host. This is only needed when NBD is not
// enabled and loop devices are used to simulate a block device.
func (u *storageosUtil) DetachVolume(b *storageosUnmounter, devicePath string) error {
if !isLoopDevice(devicePath) {
return nil
}
if _, err := os.Stat(devicePath); os.IsNotExist(err) {
return nil
}
return removeLoopDevice(devicePath)
}
// Mount mounts the volume on the host.
func (u *storageosUtil) MountVolume(b *storageosMounter, mntDevice, deviceMountPath string) error {
notMnt, err := b.mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil {
if os.IsNotExist(err) {
if err = os.MkdirAll(deviceMountPath, 0750); err != nil {
return err
}
notMnt = true
} else {
return err
}
}
if err = os.MkdirAll(deviceMountPath, 0750); err != nil {
glog.Errorf("mkdir failed on disk %s (%v)", deviceMountPath, err)
return err
}
options := []string{}
if b.readOnly {
options = append(options, "ro")
}
if notMnt {
err = b.diskMounter.FormatAndMount(mntDevice, deviceMountPath, b.fsType, options)
if err != nil {
os.Remove(deviceMountPath)
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)
}
// Unmount removes the mount reference from the volume allowing it to be
// re-mounted elsewhere.
func (u *storageosUtil) UnmountVolume(b *storageosUnmounter) error {
if err := u.NewAPI(b.apiCfg); err != nil {
// We can't always get the config we need, so allow the unmount to
// succeed even if we can't remove the mount reference from the API.
glog.V(4).Infof("Could not remove mount reference in the StorageOS API as no credentials available to the unmount operation")
return nil
}
opts := storageostypes.VolumeUnmountOptions{
Name: b.volName,
Namespace: b.volNamespace,
Client: b.plugin.host.GetHostName(),
}
return u.api.VolumeUnmount(opts)
}
// Deletes a StorageOS volume. Assumes it has already been unmounted and detached.
func (u *storageosUtil) DeleteVolume(d *storageosDeleter) error {
if err := u.NewAPI(d.apiCfg); err != nil {
return err
}
// Deletes must be forced as the StorageOS API will not normally delete
// volumes that it thinks are mounted. We can't be sure the unmount was
// registered via the API so we trust k8s to only delete volumes it knows
// are unmounted.
opts := storageostypes.DeleteOptions{
Name: d.volName,
Namespace: d.volNamespace,
Force: true,
}
return u.api.VolumeDelete(opts)
}
// pathMode returns the FileMode for a path.
func pathDeviceType(path string) (deviceType, error) {
fi, err := os.Stat(path)
if err != nil {
return modeUnsupported, err
}
switch mode := fi.Mode(); {
case mode&os.ModeDevice != 0:
return modeBlock, nil
case mode.IsRegular():
return modeFile, nil
default:
return modeUnsupported, nil
}
}
// attachFileDevice takes a path to a regular file and makes it available as an
// attached block device.
func attachFileDevice(path string) (string, error) {
blockDevicePath, err := getLoopDevice(path)
if err != nil && err.Error() != ErrDeviceNotFound {
return "", err
}
// If no existing loop device for the path, create one
if blockDevicePath == "" {
glog.V(4).Infof("Creating device for path: %s", path)
blockDevicePath, err = makeLoopDevice(path)
if err != nil {
return "", err
}
}
return blockDevicePath, nil
}
// Returns the full path to the loop device associated with the given path.
func getLoopDevice(path string) (string, error) {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return "", errors.New(ErrNotAvailable)
}
if err != nil {
return "", fmt.Errorf("not attachable: %v", err)
}
exec := exec.New()
args := []string{"-j", path}
out, err := exec.Command(losetupPath, args...).CombinedOutput()
if err != nil {
glog.V(2).Infof("Failed device discover command for path %s: %v", path, err)
return "", err
}
return parseLosetupOutputForDevice(out)
}
func makeLoopDevice(path string) (string, error) {
exec := exec.New()
args := []string{"-f", "--show", path}
out, err := exec.Command(losetupPath, args...).CombinedOutput()
if err != nil {
glog.V(2).Infof("Failed device create command for path %s: %v", path, err)
return "", err
}
return parseLosetupOutputForDevice(out)
}
func removeLoopDevice(device string) error {
exec := exec.New()
args := []string{"-d", device}
out, err := exec.Command(losetupPath, args...).CombinedOutput()
if err != nil {
if !strings.Contains(string(out), "No such device or address") {
return err
}
}
return nil
}
func isLoopDevice(device string) bool {
return strings.HasPrefix(device, "/dev/loop")
}
func parseLosetupOutputForDevice(output []byte) (string, error) {
if len(output) == 0 {
return "", errors.New(ErrDeviceNotFound)
}
// losetup returns device in the format:
// /dev/loop1: [0073]:148662 (/var/lib/storageos/volumes/308f14af-cf0a-08ff-c9c3-b48104318e05)
device := strings.TrimSpace(strings.SplitN(string(output), ":", 2)[0])
if len(device) == 0 {
return "", errors.New(ErrDeviceNotFound)
}
return device, nil
}

View File

@ -0,0 +1,235 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package storageos
import (
"fmt"
"os"
storageostypes "github.com/storageos/go-api/types"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"testing"
)
var testApiSecretName = "storageos-api"
var testVolName = "storageos-test-vol"
var testPVName = "storageos-test-pv"
var testNamespace = "storageos-test-namespace"
var testSize = 1
var testDesc = "testdescription"
var testPool = "testpool"
var testFSType = "ext2"
var testVolUUID = "01c43d34-89f8-83d3-422b-43536a0f25e6"
type fakeConfig struct {
apiAddr string
apiUser string
apiPass string
apiVersion string
}
func (c fakeConfig) GetAPIConfig() *storageosAPIConfig {
return &storageosAPIConfig{
apiAddr: "http://5.6.7.8:9999",
apiUser: "abc",
apiPass: "123",
apiVersion: "10",
}
}
func TestClient(t *testing.T) {
util := storageosUtil{}
cfg := fakeConfig{}
err := util.NewAPI(cfg.GetAPIConfig())
if err != nil {
t.Fatalf("error getting api config: %v", err)
}
if util.api == nil {
t.Errorf("client() unexpectedly returned nil")
}
}
type fakeAPI struct{}
func (f fakeAPI) Volume(namespace string, ref string) (*storageostypes.Volume, error) {
if namespace == testNamespace && ref == testVolName {
return &storageostypes.Volume{
ID: "01c43d34-89f8-83d3-422b-43536a0f25e6",
Name: ref,
Pool: "default",
Namespace: namespace,
Size: 5,
}, nil
}
return nil, fmt.Errorf("not found")
}
func (f fakeAPI) VolumeCreate(opts storageostypes.VolumeCreateOptions) (*storageostypes.Volume, error) {
// Append a label from the api
labels := opts.Labels
labels["labelfromapi"] = "apilabel"
return &storageostypes.Volume{
ID: testVolUUID,
Name: opts.Name,
Namespace: opts.Namespace,
Description: opts.Description,
Pool: opts.Pool,
Size: opts.Size,
FSType: opts.FSType,
Labels: labels,
}, nil
}
func (f fakeAPI) VolumeMount(opts storageostypes.VolumeMountOptions) error {
return nil
}
func (f fakeAPI) VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error {
return nil
}
func (f fakeAPI) VolumeDelete(opts storageostypes.DeleteOptions) error {
return nil
}
func TestCreateVolume(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, _ := plugMgr.FindPluginByName("kubernetes.io/storageos")
// Use real util with stubbed api
util := &storageosUtil{}
util.api = fakeAPI{}
labels := map[string]string{
"labelA": "valueA",
"labelB": "valueB",
}
options := volume.VolumeOptions{
PVName: testPVName,
PVC: volumetest.CreateTestPVC(fmt.Sprintf("%dGi", testSize), []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
}
provisioner := &storageosProvisioner{
storageosMounter: &storageosMounter{
storageos: &storageos{
pvName: testPVName,
volName: testVolName,
volNamespace: testNamespace,
sizeGB: testSize,
pool: testPool,
description: testDesc,
fsType: testFSType,
labels: labels,
manager: util,
plugin: plug.(*storageosPlugin),
},
},
options: options,
}
vol, err := util.CreateVolume(provisioner)
if err != nil {
t.Errorf("CreateVolume() returned error: %v", err)
}
if vol == nil {
t.Fatalf("CreateVolume() vol is empty")
}
if vol.ID == "" {
t.Error("CreateVolume() vol ID is empty")
}
if vol.Name != testVolName {
t.Errorf("CreateVolume() returned unexpected Name %s", vol.Name)
}
if vol.Namespace != testNamespace {
t.Errorf("CreateVolume() returned unexpected Namespace %s", vol.Namespace)
}
if vol.Pool != testPool {
t.Errorf("CreateVolume() returned unexpected Pool %s", vol.Pool)
}
if vol.FSType != testFSType {
t.Errorf("CreateVolume() returned unexpected FSType %s", vol.FSType)
}
if vol.SizeGB != testSize {
t.Errorf("CreateVolume() returned unexpected Size %d", vol.SizeGB)
}
if len(vol.Labels) == 0 {
t.Error("CreateVolume() Labels are empty")
} else {
for k, v := range labels {
var val string
var ok bool
if val, ok = vol.Labels[k]; !ok {
t.Errorf("CreateVolume() Label %s not set", k)
}
if val != v {
t.Errorf("CreateVolume() returned unexpected Label value %s", val)
}
}
var val string
var ok bool
if val, ok = vol.Labels["labelfromapi"]; !ok {
t.Error("CreateVolume() Label from api not set")
}
if val != "apilabel" {
t.Errorf("CreateVolume() returned unexpected Label value %s", val)
}
}
}
func TestAttachVolume(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, _ := plugMgr.FindPluginByName("kubernetes.io/storageos")
// Use real util with stubbed api
util := &storageosUtil{}
util.api = fakeAPI{}
mounter := &storageosMounter{
storageos: &storageos{
volName: testVolName,
volNamespace: testNamespace,
manager: util,
mounter: &mount.FakeMounter{},
plugin: plug.(*storageosPlugin),
},
devicePath: tmpDir,
}
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
}

View File

@ -251,9 +251,9 @@ func (g *Graph) AddPV(pv *api.PersistentVolume) {
// since we don't know the other end of the pvc -> pod -> node chain (or it may not even exist yet), we can't decorate these edges with kubernetes node info
g.graph.SetEdge(simple.Edge{F: pvVertex, T: g.getOrCreateVertex_locked(pvcVertexType, pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)})
pvutil.VisitPVSecretNames(pv, func(secret string) bool {
pvutil.VisitPVSecretNames(pv, func(namespace, secret string) bool {
// This grants access to the named secret in the same namespace as the bound PVC
g.graph.SetEdge(simple.Edge{F: g.getOrCreateVertex_locked(secretVertexType, pv.Spec.ClaimRef.Namespace, secret), T: pvVertex})
g.graph.SetEdge(simple.Edge{F: g.getOrCreateVertex_locked(secretVertexType, namespace, secret), T: pvVertex})
return true
})
}

View File

@ -313,6 +313,9 @@ type VolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional
ScaleIO *ScaleIOVolumeSource
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// +optional
StorageOS *StorageOSVolumeSource
}
// Similar to VolumeSource but meant for the administrator who creates PVs.
@ -384,6 +387,10 @@ type PersistentVolumeSource struct {
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
StorageOS *StorageOSPersistentVolumeSource
}
type PersistentVolumeClaimVolumeSource struct {
@ -1149,6 +1156,62 @@ type ScaleIOVolumeSource struct {
ReadOnly bool
}
// Represents a StorageOS persistent volume resource.
type StorageOSVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *LocalObjectReference
}
// Represents a StorageOS persistent volume resource.
type StorageOSPersistentVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *ObjectReference
}
// Adapts a ConfigMap into a volume.
//
// The contents of the target ConfigMap's Data field will be presented in a

File diff suppressed because it is too large Load Diff

View File

@ -2222,6 +2222,11 @@ message PersistentVolumeSource {
// Local represents directly-attached storage with node affinity
// +optional
optional LocalVolumeSource local = 20;
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
optional StorageOSPersistentVolumeSource storageos = 21;
}
// PersistentVolumeSpec is the specification of a persistent volume.
@ -3760,6 +3765,70 @@ message ServiceStatus {
optional LoadBalancerStatus loadBalancer = 1;
}
// Represents a StorageOS persistent volume resource.
message StorageOSPersistentVolumeSource {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
optional string volumeName = 1;
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
optional string volumeNamespace = 2;
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
optional string fsType = 3;
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
optional bool readOnly = 4;
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
optional ObjectReference secretRef = 5;
}
// Represents a StorageOS persistent volume resource.
message StorageOSVolumeSource {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
optional string volumeName = 1;
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
optional string volumeNamespace = 2;
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
optional string fsType = 3;
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
optional bool readOnly = 4;
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
optional LocalObjectReference secretRef = 5;
}
// Sysctl defines a kernel parameter to be set
message Sysctl {
// Name of a property to set
@ -4011,6 +4080,10 @@ message VolumeSource {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional
optional ScaleIOVolumeSource scaleIO = 25;
// StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.
// +optional
optional StorageOSVolumeSource storageos = 27;
}
// Represents a vSphere volume resource.

File diff suppressed because it is too large Load Diff

View File

@ -348,6 +348,9 @@ type VolumeSource struct {
// ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.
// +optional
ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,25,opt,name=scaleIO"`
// StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.
// +optional
StorageOS *StorageOSVolumeSource `json:"storageos,omitempty" protobuf:"bytes,27,opt,name=storageos"`
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
@ -442,6 +445,10 @@ type PersistentVolumeSource struct {
// Local represents directly-attached storage with node affinity
// +optional
Local *LocalVolumeSource `json:"local,omitempty" protobuf:"bytes,20,opt,name=local"`
// StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod
// More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md
// +optional
StorageOS *StorageOSPersistentVolumeSource `json:"storageos,omitempty" protobuf:"bytes,21,opt,name=storageos"`
}
const (
@ -1228,6 +1235,62 @@ type ScaleIOVolumeSource struct {
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,10,opt,name=readOnly"`
}
// Represents a StorageOS persistent volume resource.
type StorageOSVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,1,opt,name=volumeName"`
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string `json:"volumeNamespace,omitempty" protobuf:"bytes,2,opt,name=volumeNamespace"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string `json:"fsType,omitempty" protobuf:"bytes,3,opt,name=fsType"`
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"`
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *LocalObjectReference `json:"secretRef,omitempty" protobuf:"bytes,5,opt,name=secretRef"`
}
// Represents a StorageOS persistent volume resource.
type StorageOSPersistentVolumeSource struct {
// VolumeName is the human-readable name of the StorageOS volume. Volume
// names are only unique within a namespace.
VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,1,opt,name=volumeName"`
// VolumeNamespace specifies the scope of the volume within StorageOS. If no
// namespace is specified then the Pod's namespace will be used. This allows the
// Kubernetes name scoping to be mirrored within StorageOS for tighter integration.
// Set VolumeName to any name to override the default behaviour.
// Set to "default" if you are not using namespaces within StorageOS.
// Namespaces that do not pre-exist within StorageOS will be created.
// +optional
VolumeNamespace string `json:"volumeNamespace,omitempty" protobuf:"bytes,2,opt,name=volumeNamespace"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
// +optional
FSType string `json:"fsType,omitempty" protobuf:"bytes,3,opt,name=fsType"`
// Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,4,opt,name=readOnly"`
// SecretRef specifies the secret to use for obtaining the StorageOS API
// credentials. If not specified, default values will be attempted.
// +optional
SecretRef *ObjectReference `json:"secretRef,omitempty" protobuf:"bytes,5,opt,name=secretRef"`
}
// Adapts a ConfigMap into a volume.
//
// The contents of the target ConfigMap's Data field will be presented in a

View File

@ -1161,6 +1161,7 @@ var map_PersistentVolumeSource = map[string]string{
"portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine",
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"local": "Local represents directly-attached storage with node affinity",
"storageos": "StorageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md",
}
func (PersistentVolumeSource) SwaggerDoc() map[string]string {
@ -1875,6 +1876,32 @@ func (ServiceStatus) SwaggerDoc() map[string]string {
return map_ServiceStatus
}
var map_StorageOSPersistentVolumeSource = map[string]string{
"": "Represents a StorageOS persistent volume resource.",
"volumeName": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"volumeNamespace": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"readOnly": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"secretRef": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
}
func (StorageOSPersistentVolumeSource) SwaggerDoc() map[string]string {
return map_StorageOSPersistentVolumeSource
}
var map_StorageOSVolumeSource = map[string]string{
"": "Represents a StorageOS persistent volume resource.",
"volumeName": "VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.",
"volumeNamespace": "VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.",
"fsType": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.",
"readOnly": "Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.",
"secretRef": "SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.",
}
func (StorageOSVolumeSource) SwaggerDoc() map[string]string {
return map_StorageOSVolumeSource
}
var map_Sysctl = map[string]string{
"": "Sysctl defines a kernel parameter to be set",
"Name": "Name of a property to set",
@ -1980,6 +2007,7 @@ var map_VolumeSource = map[string]string{
"projected": "Items for all in one resources secrets, configmaps, and downward API",
"portworxVolume": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine",
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"storageos": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.",
}
func (VolumeSource) SwaggerDoc() map[string]string {

View File

@ -355,6 +355,10 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_api_ServiceSpec_To_v1_ServiceSpec,
Convert_v1_ServiceStatus_To_api_ServiceStatus,
Convert_api_ServiceStatus_To_v1_ServiceStatus,
Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource,
Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource,
Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource,
Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource,
Convert_v1_Sysctl_To_api_Sysctl,
Convert_api_Sysctl_To_v1_Sysctl,
Convert_v1_TCPSocketAction_To_api_TCPSocketAction,
@ -3027,6 +3031,7 @@ func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Per
out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*api.LocalVolumeSource)(unsafe.Pointer(in.Local))
out.StorageOS = (*api.StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}
@ -3056,6 +3061,7 @@ func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api
out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.Local = (*LocalVolumeSource)(unsafe.Pointer(in.Local))
out.StorageOS = (*StorageOSPersistentVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}
@ -4807,6 +4813,62 @@ func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *S
return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s)
}
func autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*api.ObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource is an autogenerated conversion function.
func Convert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in *StorageOSPersistentVolumeSource, out *api.StorageOSPersistentVolumeSource, s conversion.Scope) error {
return autoConvert_v1_StorageOSPersistentVolumeSource_To_api_StorageOSPersistentVolumeSource(in, out, s)
}
func autoConvert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *api.StorageOSPersistentVolumeSource, out *StorageOSPersistentVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*ObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource is an autogenerated conversion function.
func Convert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in *api.StorageOSPersistentVolumeSource, out *StorageOSPersistentVolumeSource, s conversion.Scope) error {
return autoConvert_api_StorageOSPersistentVolumeSource_To_v1_StorageOSPersistentVolumeSource(in, out, s)
}
func autoConvert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in *StorageOSVolumeSource, out *api.StorageOSVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*api.LocalObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource is an autogenerated conversion function.
func Convert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in *StorageOSVolumeSource, out *api.StorageOSVolumeSource, s conversion.Scope) error {
return autoConvert_v1_StorageOSVolumeSource_To_api_StorageOSVolumeSource(in, out, s)
}
func autoConvert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *api.StorageOSVolumeSource, out *StorageOSVolumeSource, s conversion.Scope) error {
out.VolumeName = in.VolumeName
out.VolumeNamespace = in.VolumeNamespace
out.FSType = in.FSType
out.ReadOnly = in.ReadOnly
out.SecretRef = (*LocalObjectReference)(unsafe.Pointer(in.SecretRef))
return nil
}
// Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource is an autogenerated conversion function.
func Convert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in *api.StorageOSVolumeSource, out *StorageOSVolumeSource, s conversion.Scope) error {
return autoConvert_api_StorageOSVolumeSource_To_v1_StorageOSVolumeSource(in, out, s)
}
func autoConvert_v1_Sysctl_To_api_Sysctl(in *Sysctl, out *api.Sysctl, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
@ -5008,6 +5070,7 @@ func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.
out.Projected = (*api.ProjectedVolumeSource)(unsafe.Pointer(in.Projected))
out.PortworxVolume = (*api.PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*api.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.StorageOS = (*api.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}
@ -5043,6 +5106,7 @@ func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *
out.Projected = (*ProjectedVolumeSource)(unsafe.Pointer(in.Projected))
out.PortworxVolume = (*PortworxVolumeSource)(unsafe.Pointer(in.PortworxVolume))
out.ScaleIO = (*ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.StorageOS = (*StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
return nil
}

View File

@ -195,6 +195,8 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_ServiceProxyOptions, InType: reflect.TypeOf(&ServiceProxyOptions{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_ServiceSpec, InType: reflect.TypeOf(&ServiceSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_ServiceStatus, InType: reflect.TypeOf(&ServiceStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_StorageOSPersistentVolumeSource, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_StorageOSVolumeSource, InType: reflect.TypeOf(&StorageOSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Sysctl, InType: reflect.TypeOf(&Sysctl{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_TCPSocketAction, InType: reflect.TypeOf(&TCPSocketAction{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_Taint, InType: reflect.TypeOf(&Taint{})},
@ -2182,6 +2184,13 @@ func DeepCopy_v1_PersistentVolumeSource(in interface{}, out interface{}, c *conv
*out = new(LocalVolumeSource)
**out = **in
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSPersistentVolumeSource)
if err := DeepCopy_v1_StorageOSPersistentVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}
@ -3436,6 +3445,36 @@ func DeepCopy_v1_ServiceStatus(in interface{}, out interface{}, c *conversion.Cl
}
}
// DeepCopy_v1_StorageOSPersistentVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_StorageOSPersistentVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSPersistentVolumeSource)
out := out.(*StorageOSPersistentVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(ObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_v1_StorageOSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_v1_StorageOSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSVolumeSource)
out := out.(*StorageOSVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(LocalObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_v1_Sysctl is an autogenerated deepcopy function.
func DeepCopy_v1_Sysctl(in interface{}, out interface{}, c *conversion.Cloner) error {
{
@ -3696,6 +3735,13 @@ func DeepCopy_v1_VolumeSource(in interface{}, out interface{}, c *conversion.Clo
return err
}
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSVolumeSource)
if err := DeepCopy_v1_StorageOSVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}

View File

@ -197,6 +197,8 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ServiceProxyOptions, InType: reflect.TypeOf(&ServiceProxyOptions{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ServiceSpec, InType: reflect.TypeOf(&ServiceSpec{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_ServiceStatus, InType: reflect.TypeOf(&ServiceStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_StorageOSPersistentVolumeSource, InType: reflect.TypeOf(&StorageOSPersistentVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_StorageOSVolumeSource, InType: reflect.TypeOf(&StorageOSVolumeSource{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Sysctl, InType: reflect.TypeOf(&Sysctl{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_TCPSocketAction, InType: reflect.TypeOf(&TCPSocketAction{})},
conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_Taint, InType: reflect.TypeOf(&Taint{})},
@ -2200,6 +2202,13 @@ func DeepCopy_api_PersistentVolumeSource(in interface{}, out interface{}, c *con
*out = new(LocalVolumeSource)
**out = **in
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSPersistentVolumeSource)
if err := DeepCopy_api_StorageOSPersistentVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}
@ -3442,6 +3451,36 @@ func DeepCopy_api_ServiceStatus(in interface{}, out interface{}, c *conversion.C
}
}
// DeepCopy_api_StorageOSPersistentVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_StorageOSPersistentVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSPersistentVolumeSource)
out := out.(*StorageOSPersistentVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(ObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_api_StorageOSVolumeSource is an autogenerated deepcopy function.
func DeepCopy_api_StorageOSVolumeSource(in interface{}, out interface{}, c *conversion.Cloner) error {
{
in := in.(*StorageOSVolumeSource)
out := out.(*StorageOSVolumeSource)
*out = *in
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(LocalObjectReference)
**out = **in
}
return nil
}
}
// DeepCopy_api_Sysctl is an autogenerated deepcopy function.
func DeepCopy_api_Sysctl(in interface{}, out interface{}, c *conversion.Cloner) error {
{
@ -3702,6 +3741,13 @@ func DeepCopy_api_VolumeSource(in interface{}, out interface{}, c *conversion.Cl
return err
}
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
*out = new(StorageOSVolumeSource)
if err := DeepCopy_api_StorageOSVolumeSource(*in, *out, c); err != nil {
return err
}
}
return nil
}
}

View File

@ -945,6 +945,7 @@ var (
Quobyte FSType = "quobyte"
AzureDisk FSType = "azureDisk"
PhotonPersistentDisk FSType = "photonPersistentDisk"
StorageOS FSType = "storageos"
Projected FSType = "projected"
PortworxVolume FSType = "portworxVolume"
ScaleIO FSType = "scaleIO"

1
vendor/BUILD vendored
View File

@ -301,6 +301,7 @@ filegroup(
"//vendor/github.com/spf13/pflag:all-srcs",
"//vendor/github.com/spf13/viper:all-srcs",
"//vendor/github.com/square/go-jose:all-srcs",
"//vendor/github.com/storageos/go-api:all-srcs",
"//vendor/github.com/stretchr/objx:all-srcs",
"//vendor/github.com/stretchr/testify/assert:all-srcs",
"//vendor/github.com/stretchr/testify/mock:all-srcs",

24
vendor/github.com/storageos/go-api/.gitignore generated vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

47
vendor/github.com/storageos/go-api/BUILD generated vendored Normal file
View File

@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"client_unix.go",
"controller.go",
"event.go",
"namespace.go",
"pool.go",
"rule.go",
"server_version.go",
"template.go",
"util.go",
"validation.go",
"volume.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/gorilla/websocket:go_default_library",
"//vendor/github.com/storageos/go-api/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//vendor/github.com/storageos/go-api/types:all-srcs",
],
tags = ["automanaged"],
)

45
vendor/github.com/storageos/go-api/LICENCE generated vendored Normal file
View File

@ -0,0 +1,45 @@
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.

2
vendor/github.com/storageos/go-api/README.md generated vendored Normal file
View File

@ -0,0 +1,2 @@
# StorageOS API client library

620
vendor/github.com/storageos/go-api/client.go generated vendored Normal file
View File

@ -0,0 +1,620 @@
package storageos
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"
)
const (
userAgent = "go-storageosclient"
unixProtocol = "unix"
namedPipeProtocol = "npipe"
DefaultVersionStr = "1"
DefaultVersion = 1
defaultNamespace = "default"
)
var (
// ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL.
ErrInvalidEndpoint = errors.New("invalid endpoint")
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
ErrConnectionRefused = errors.New("cannot connect to StorageOS API endpoint")
// ErrInactivityTimeout is returned when a streamable call has been inactive for some time.
ErrInactivityTimeout = errors.New("inactivity time exceeded timeout")
// ErrInvalidVersion is returned when a versioned client was requested but no version specified.
ErrInvalidVersion = errors.New("invalid version")
// DefaultHost is the default API host
DefaultHost = "tcp://localhost:5705"
)
// APIVersion is an internal representation of a version of the Remote API.
type APIVersion int
// NewAPIVersion returns an instance of APIVersion for the given string.
//
// The given string must be in the form <major>
func NewAPIVersion(input string) (APIVersion, error) {
if input == "" {
return DefaultVersion, ErrInvalidVersion
}
version, err := strconv.Atoi(input)
if err != nil {
return 0, fmt.Errorf("Unable to parse version %q", input)
}
return APIVersion(version), nil
}
func (version APIVersion) String() string {
return fmt.Sprintf("v%d", version)
}
// Client is the basic type of this package. It provides methods for
// interaction with the API.
type Client struct {
SkipServerVersionCheck bool
HTTPClient *http.Client
TLSConfig *tls.Config
Dialer Dialer
endpoint string
endpointURL *url.URL
username string
secret string
requestedAPIVersion APIVersion
serverAPIVersion APIVersion
expectedAPIVersion APIVersion
nativeHTTPClient *http.Client
}
// ClientVersion returns the API version of the client
func (c *Client) ClientVersion() string {
return DefaultVersionStr
}
// Dialer is an interface that allows network connections to be dialed
// (net.Dialer fulfills this interface) and named pipes (a shim using
// winio.DialPipe)
type Dialer interface {
Dial(network, address string) (net.Conn, error)
}
// NewClient returns a Client instance ready for communication with the given
// server endpoint. It will use the latest remote API version available in the
// server.
func NewClient(endpoint string) (*Client, error) {
client, err := NewVersionedClient(endpoint, "")
if err != nil {
return nil, err
}
client.SkipServerVersionCheck = true
return client, nil
}
// NewTLSClient returns a Client instance ready for TLS communications with the given
// server endpoint, key and certificates . It will use the latest remote API version
// available in the server.
func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) {
client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "")
if err != nil {
return nil, err
}
client.SkipServerVersionCheck = true
return client, nil
}
// NewVersionedClient returns a Client instance ready for communication with
// the given server endpoint, using a specific remote API version.
func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint, false)
if err != nil {
return nil, err
}
c := &Client{
HTTPClient: defaultClient(),
Dialer: &net.Dialer{},
endpoint: endpoint,
endpointURL: u,
}
if apiVersionString != "" {
version, err := strconv.Atoi(apiVersionString)
if err != nil {
return nil, err
}
c.requestedAPIVersion = APIVersion(version)
}
c.initializeNativeClient()
return c, nil
}
// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates, using a specific remote API version.
func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) {
var certPEMBlock []byte
var keyPEMBlock []byte
var caPEMCert []byte
if _, err := os.Stat(cert); !os.IsNotExist(err) {
certPEMBlock, err = ioutil.ReadFile(cert)
if err != nil {
return nil, err
}
}
if _, err := os.Stat(key); !os.IsNotExist(err) {
keyPEMBlock, err = ioutil.ReadFile(key)
if err != nil {
return nil, err
}
}
if _, err := os.Stat(ca); !os.IsNotExist(err) {
caPEMCert, err = ioutil.ReadFile(ca)
if err != nil {
return nil, err
}
}
return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString)
}
// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens
// server endpoint, key and certificates (passed inline to the function as opposed to being
// read from a local file), using a specific remote API version.
func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) {
u, err := parseEndpoint(endpoint, true)
if err != nil {
return nil, err
}
tlsConfig := &tls.Config{}
if certPEMBlock != nil && keyPEMBlock != nil {
tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{tlsCert}
}
if caPEMCert == nil {
tlsConfig.InsecureSkipVerify = true
} else {
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(caPEMCert) {
return nil, errors.New("Could not add RootCA pem")
}
tlsConfig.RootCAs = caPool
}
tr := defaultTransport()
tr.TLSClientConfig = tlsConfig
if err != nil {
return nil, err
}
c := &Client{
HTTPClient: &http.Client{Transport: tr},
TLSConfig: tlsConfig,
Dialer: &net.Dialer{},
endpoint: endpoint,
endpointURL: u,
}
if apiVersionString != "" {
version, err := strconv.Atoi(apiVersionString)
if err != nil {
return nil, err
}
c.requestedAPIVersion = APIVersion(version)
}
c.initializeNativeClient()
return c, nil
}
// 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) {
if username != "" {
c.username = username
}
if secret != "" {
c.secret = secret
}
}
// 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
}
}
func (c *Client) checkAPIVersion() error {
serverAPIVersionString, err := c.getServerAPIVersionString()
if err != nil {
return err
}
c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString)
if err != nil {
return err
}
if c.requestedAPIVersion == 0 {
c.expectedAPIVersion = c.serverAPIVersion
} else {
c.expectedAPIVersion = c.requestedAPIVersion
}
return nil
}
// Endpoint returns the current endpoint. It's useful for getting the endpoint
// when using functions that get this data from the environment (like
// NewClientFromEnv.
func (c *Client) Endpoint() string {
return c.endpoint
}
// Ping pings the API server
//
// See https://goo.gl/wYfgY1 for more details.
func (c *Client) Ping() error {
urlpath := "/_ping"
resp, err := c.do("GET", urlpath, doOptions{})
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return newError(resp)
}
resp.Body.Close()
return nil
}
func (c *Client) getServerAPIVersionString() (version string, err error) {
v, err := c.ServerVersion(context.Background())
if err != nil {
return "", err
}
return v.APIVersion, nil
}
type doOptions struct {
data interface{}
fieldSelector string
labelSelector string
namespace string
forceJSON bool
force bool
values url.Values
headers map[string]string
unversioned bool
context context.Context
}
func (c *Client) do(method, urlpath string, doOptions doOptions) (*http.Response, error) {
var params io.Reader
if doOptions.data != nil || doOptions.forceJSON {
buf, err := json.Marshal(doOptions.data)
if err != nil {
return nil, err
}
params = bytes.NewBuffer(buf)
}
// Prefix the path with the namespace if given. The caller should only set
// the namespace if this is desired.
if doOptions.namespace != "" {
urlpath = "/" + NamespaceAPIPrefix + "/" + doOptions.namespace + "/" + urlpath
}
if !c.SkipServerVersionCheck && !doOptions.unversioned {
err := c.checkAPIVersion()
if err != nil {
return nil, err
}
}
query := url.Values{}
if doOptions.values != nil {
query = doOptions.values
}
if doOptions.force {
query.Add("force", "1")
}
httpClient := c.HTTPClient
protocol := c.endpointURL.Scheme
var u string
switch protocol {
case unixProtocol, namedPipeProtocol:
httpClient = c.nativeHTTPClient
u = c.getFakeNativeURL(urlpath, doOptions.unversioned)
default:
u = c.getAPIPath(urlpath, query, doOptions.unversioned)
}
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)
}
ctx := doOptions.context
if ctx == nil {
ctx = context.Background()
}
resp, err := httpClient.Do(req.WithContext(ctx))
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, ErrConnectionRefused
}
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
}
}
func (c *Client) getURL(path string, unversioned bool) string {
urlStr := strings.TrimRight(c.endpointURL.String(), "/")
path = strings.TrimLeft(path, "/")
if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol {
urlStr = ""
}
if unversioned {
return fmt.Sprintf("%s/%s", urlStr, path)
}
return fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
}
func (c *Client) getAPIPath(path string, query url.Values, unversioned bool) string {
var apiPath string
urlStr := strings.TrimRight(c.endpointURL.String(), "/")
path = strings.TrimLeft(path, "/")
if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol {
urlStr = ""
}
if unversioned {
apiPath = fmt.Sprintf("%s/%s", urlStr, path)
} else {
apiPath = fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
}
if len(query) > 0 {
apiPath = apiPath + "?" + query.Encode()
}
return apiPath
}
// getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX
// domain socket to the given path.
func (c *Client) getFakeNativeURL(path string, unversioned bool) string {
u := *c.endpointURL // Copy.
// Override URL so that net/http will not complain.
u.Scheme = "http"
u.Host = "unix.sock" // Doesn't matter what this is - it's not used.
u.Path = ""
urlStr := strings.TrimRight(u.String(), "/")
path = strings.TrimLeft(path, "/")
if unversioned {
return fmt.Sprintf("%s/%s", urlStr, path)
}
return fmt.Sprintf("%s/%s/%s", urlStr, c.requestedAPIVersion, path)
}
type jsonMessage struct {
Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"`
Error string `json:"error,omitempty"`
Stream string `json:"stream,omitempty"`
}
func queryString(opts interface{}) string {
if opts == nil {
return ""
}
value := reflect.ValueOf(opts)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
if value.Kind() != reflect.Struct {
return ""
}
items := url.Values(map[string][]string{})
for i := 0; i < value.NumField(); i++ {
field := value.Type().Field(i)
if field.PkgPath != "" {
continue
}
key := field.Tag.Get("qs")
if key == "" {
key = strings.ToLower(field.Name)
} else if key == "-" {
continue
}
addQueryStringValue(items, key, value.Field(i))
}
return items.Encode()
}
func addQueryStringValue(items url.Values, key string, v reflect.Value) {
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
items.Add(key, "1")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.Int() > 0 {
items.Add(key, strconv.FormatInt(v.Int(), 10))
}
case reflect.Float32, reflect.Float64:
if v.Float() > 0 {
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
}
case reflect.String:
if v.String() != "" {
items.Add(key, v.String())
}
case reflect.Ptr:
if !v.IsNil() {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
}
case reflect.Map:
if len(v.MapKeys()) > 0 {
if b, err := json.Marshal(v.Interface()); err == nil {
items.Add(key, string(b))
}
}
case reflect.Array, reflect.Slice:
vLen := v.Len()
if vLen > 0 {
for i := 0; i < vLen; i++ {
addQueryStringValue(items, key, v.Index(i))
}
}
}
}
// Error represents failures in the API. It represents a failure from the API.
type Error struct {
Status int
Message string
}
func newError(resp *http.Response) *Error {
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)}
}
return &Error{Status: resp.StatusCode, Message: string(data)}
}
func (e *Error) Error() string {
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
}
func parseEndpoint(endpoint string, tls bool) (*url.URL, error) {
if endpoint != "" && !strings.Contains(endpoint, "://") {
endpoint = "tcp://" + endpoint
}
u, err := url.Parse(endpoint)
if err != nil {
return nil, ErrInvalidEndpoint
}
if tls && u.Scheme != "unix" {
u.Scheme = "https"
}
switch u.Scheme {
case unixProtocol, namedPipeProtocol:
return u, nil
case "http", "https", "tcp":
_, port, err := net.SplitHostPort(u.Host)
if err != nil {
if e, ok := err.(*net.AddrError); ok {
if e.Err == "missing port in address" {
return u, nil
}
}
return nil, ErrInvalidEndpoint
}
number, err := strconv.ParseInt(port, 10, 64)
if err == nil && number > 0 && number < 65536 {
if u.Scheme == "tcp" {
if tls {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
}
return u, nil
}
return nil, ErrInvalidEndpoint
default:
return nil, ErrInvalidEndpoint
}
}
// defaultTransport returns a new http.Transport with the same default values
// as http.DefaultTransport, but with idle connections and keepalives disabled.
func defaultTransport() *http.Transport {
transport := defaultPooledTransport()
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() *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
DisableKeepAlives: false,
MaxIdleConnsPerHost: 1,
}
return transport
}
// defaultClient returns a new http.Client with similar default values to
// http.Client, but with a non-shared Transport, idle connections disabled, and
// keepalives disabled.
func defaultClient() *http.Client {
return &http.Client{
Transport: defaultTransport(),
}
}

25
vendor/github.com/storageos/go-api/client_unix.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// +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}
}

40
vendor/github.com/storageos/go-api/client_windows.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
// +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}
}

111
vendor/github.com/storageos/go-api/controller.go generated vendored Normal file
View File

@ -0,0 +1,111 @@
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,
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", 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
}

189
vendor/github.com/storageos/go-api/event.go generated vendored Normal file
View File

@ -0,0 +1,189 @@
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
}

125
vendor/github.com/storageos/go-api/namespace.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
package storageos
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// NamespaceAPIPrefix is a partial path to the HTTP endpoint.
NamespaceAPIPrefix = "namespaces"
// ErrNoSuchNamespace is the error returned when the namespace does not exist.
ErrNoSuchNamespace = errors.New("no such namespace")
// ErrNamespaceInUse is the error returned when the namespace requested to be removed is still in use.
ErrNamespaceInUse = errors.New("namespace in use and cannot be removed")
)
// NamespaceList returns the list of available namespaces.
func (c *Client) NamespaceList(opts types.ListOptions) ([]*types.Namespace, 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", NamespaceAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var namespaces []*types.Namespace
if err := json.NewDecoder(resp.Body).Decode(&namespaces); err != nil {
return nil, err
}
return namespaces, nil
}
// Namespace returns a namespace by its reference.
func (c *Client) Namespace(ref string) (*types.Namespace, error) {
resp, err := c.do("GET", NamespaceAPIPrefix+"/"+ref, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchNamespace
}
return nil, err
}
defer resp.Body.Close()
var namespace types.Namespace
if err := json.NewDecoder(resp.Body).Decode(&namespace); err != nil {
return nil, err
}
return &namespace, nil
}
// NamespaceCreate creates a namespace on the server and returns the new object.
func (c *Client) NamespaceCreate(opts types.NamespaceCreateOptions) (*types.Namespace, error) {
resp, err := c.do("POST", NamespaceAPIPrefix, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
var namespace types.Namespace
if err := json.NewDecoder(resp.Body).Decode(&namespace); err != nil {
return nil, err
}
return &namespace, nil
}
// NamespaceUpdate updates a namespace on the server and returns the updated object.
func (c *Client) NamespaceUpdate(opts types.NamespaceCreateOptions) (*types.Namespace, error) {
resp, err := c.do("PUT", NamespaceAPIPrefix+"/"+opts.Name, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
var namespace types.Namespace
if err := json.NewDecoder(resp.Body).Decode(&namespace); err != nil {
return nil, err
}
return &namespace, nil
}
// NamespaceDelete removes a namespace by its reference.
func (c *Client) NamespaceDelete(opts types.DeleteOptions) error {
deleteOpts := doOptions{
force: opts.Force,
context: opts.Context,
}
resp, err := c.do("DELETE", NamespaceAPIPrefix+"/"+opts.Name, deleteOpts)
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchNamespace
}
if e.Status == http.StatusConflict {
return ErrNamespaceInUse
}
// namespace can't be deleted yet, unless force is supplied
if e.Status == http.StatusPreconditionFailed {
return err
}
}
return err
}
defer resp.Body.Close()
return nil
}

96
vendor/github.com/storageos/go-api/pool.go generated vendored Normal file
View File

@ -0,0 +1,96 @@
package storageos
import (
"encoding/json"
"errors"
"net/http"
"github.com/storageos/go-api/types"
)
var (
// PoolAPIPrefix is a partial path to the HTTP endpoint.
PoolAPIPrefix = "pools"
// ErrNoSuchPool is the error returned when the pool does not exist.
ErrNoSuchPool = errors.New("no such pool")
// ErrPoolInUse is the error returned when the pool requested to be removed is still in use.
ErrPoolInUse = errors.New("pool in use and cannot be removed")
)
// PoolList returns the list of available pools.
func (c *Client) PoolList(opts types.ListOptions) ([]*types.Pool, error) {
listOpts := doOptions{
fieldSelector: opts.FieldSelector,
labelSelector: opts.LabelSelector,
namespace: opts.Namespace,
context: opts.Context,
}
resp, err := c.do("GET", PoolAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var pools []*types.Pool
if err := json.NewDecoder(resp.Body).Decode(&pools); err != nil {
return nil, err
}
return pools, nil
}
// PoolCreate creates a pool on the server and returns the new object.
func (c *Client) PoolCreate(opts types.PoolCreateOptions) (*types.Pool, error) {
resp, err := c.do("POST", PoolAPIPrefix, 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{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchPool
}
return nil, err
}
defer resp.Body.Close()
var pool types.Pool
if err := json.NewDecoder(resp.Body).Decode(&pool); err != nil {
return nil, err
}
return &pool, nil
}
// PoolDelete removes a pool by its reference.
func (c *Client) PoolDelete(opts types.DeleteOptions) error {
deleteOpts := doOptions{
force: opts.Force,
context: opts.Context,
}
resp, err := c.do("DELETE", PoolAPIPrefix+"/"+opts.Name, deleteOpts)
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchPool
}
if e.Status == http.StatusConflict {
return ErrPoolInUse
}
}
return nil
}
defer resp.Body.Close()
return nil
}

137
vendor/github.com/storageos/go-api/rule.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package storageos
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/storageos/go-api/types"
)
var (
// RuleAPIPrefix is a partial path to the HTTP endpoint.
RuleAPIPrefix = "rules"
// ErrNoSuchRule is the error returned when the rule does not exist.
ErrNoSuchRule = errors.New("no such rule")
// ErrRuleInUse is the error returned when the rule requested to be removed is still in use.
ErrRuleInUse = errors.New("rule in use and cannot be removed")
)
// RuleList returns the list of available rules.
func (c *Client) RuleList(opts types.ListOptions) ([]*types.Rule, 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", RuleAPIPrefix, listOpts)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var rules []*types.Rule
if err := json.NewDecoder(resp.Body).Decode(&rules); err != nil {
return nil, err
}
return rules, nil
}
// Rule returns a rule by its reference.
func (c *Client) Rule(namespace string, ref string) (*types.Rule, error) {
path, err := namespacedRefPath(namespace, RuleAPIPrefix, ref)
if err != nil {
return nil, err
}
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchRule
}
return nil, err
}
defer resp.Body.Close()
var rule types.Rule
if err := json.NewDecoder(resp.Body).Decode(&rule); err != nil {
return nil, err
}
return &rule, nil
}
// RuleCreate creates a rule on the server and returns the new object.
func (c *Client) RuleCreate(opts types.RuleCreateOptions) (*types.Rule, error) {
resp, err := c.do("POST", RuleAPIPrefix, doOptions{
data: opts,
namespace: opts.Namespace,
context: opts.Context,
})
if err != nil {
return nil, err
}
var rule types.Rule
if err := json.NewDecoder(resp.Body).Decode(&rule); err != nil {
return nil, err
}
return &rule, nil
}
// RuleUpdate updates a rule on the server.
func (c *Client) RuleUpdate(opts types.RuleUpdateOptions) (*types.Rule, error) {
ref := opts.Name
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
}
resp, err := c.do("PUT", path, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var rule types.Rule
if err := json.NewDecoder(resp.Body).Decode(&rule); err != nil {
return nil, err
}
return &rule, nil
}
// RuleDelete removes a rule by its reference.
func (c *Client) RuleDelete(opts types.DeleteOptions) error {
deleteOpts := doOptions{
namespace: opts.Namespace,
force: opts.Force,
context: opts.Context,
}
resp, err := c.do("DELETE", RuleAPIPrefix+"/"+opts.Name, deleteOpts)
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchRule
}
if e.Status == http.StatusConflict {
return ErrRuleInUse
}
}
return nil
}
defer resp.Body.Close()
return nil
}

28
vendor/github.com/storageos/go-api/server_version.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package storageos
import (
"context"
"encoding/json"
"net/http"
"github.com/storageos/go-api/types"
)
// ServerVersion returns the server's version and runtime info.
func (c *Client) ServerVersion(ctx context.Context) (*types.VersionInfo, error) {
// Send as unversioned
resp, err := c.do("GET", "version", doOptions{context: ctx, unversioned: true})
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, newError(resp)
}
defer resp.Body.Close()
var version types.VersionInfo
if err := json.NewDecoder(resp.Body).Decode(&version); err != nil {
return nil, err
}
return &version, nil
}

12
vendor/github.com/storageos/go-api/swagger-gen.yaml generated vendored Normal file
View File

@ -0,0 +1,12 @@
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"

854
vendor/github.com/storageos/go-api/swagger.yaml generated vendored Normal file
View File

@ -0,0 +1,854 @@
# A Swagger 2.0 (a.k.a. OpenAPI) definition of the StorageOS API.
#
# This is used for generating API documentation and the types used by the
# client/server. See api/README.md for more information.
#
# Some style notes:
# - This file is used by ReDoc, which allows GitHub Flavored Markdown in
# descriptions.
# - There is no maximum line length, for ease of editing and pretty diffs.
# - operationIds are in the format "NounVerb", with a singular noun.
swagger: "2.0"
schemes:
- "http"
- "https"
produces:
- "application/json"
- "text/plain"
consumes:
- "application/json"
- "text/plain"
basePath: "/v1"
info:
title: "StorageOS API"
version: "0.7"
x-logo:
url: "http://storageos.wpengine.com/wp-content/uploads/2017/03/cropped-logo-1.png"
description: |
The StorageOS API is an HTTP API used for managing volumes and StorageOS services. It is the API that the StorageOS UI, CLI and platform integrations use to communicate with the StorageOS backend.
# Errors
The API uses standard HTTP status codes to indicate the success or failure of the API call. The body of the response will be JSON in the following format:
```
{
"message": "page not found"
}
```
# The tags on paths define the menu sections in the ReDoc documentation, so
# the usage of tags must make sense for that:
# - They should be singular, not plural.
# - There should not be too many tags, or the menu becomes unwieldy. For
# example, it is preferable to add a path to the "System" tag instead of
# creating a tag with a single path in it.
# - The order of tags in this list defines the order in the menu.
tags:
# Primary objects
- name: "Volume"
x-displayName: "Volumes"
description: |
Create and manage volumes.
- name: "Pool"
x-displayName: "Pools"
description: |
Create and manage distributed capacity pools.
definitions:
ErrorResponse:
description: "Represents an error."
type: "object"
required: ["message"]
properties:
message:
description: "The error message."
type: "string"
x-nullable: false
example:
message: "Something went wrong."
Deployment:
type: "object"
description: "Volume master or replica deployment details."
properties:
ID:
type: "string"
readOnly: true
Controller:
type: "string"
readOnly: true
Inode:
type: "integer"
format: "uint32"
readOnly: true
Status:
type: "string"
readOnly: true
Health:
type: "string"
readOnly: true
CreatedAt:
type: "string"
format: "datetime"
readOnly: true
VolumeCreateOptions:
type: "object"
description: "Parameters available for creating new volumes."
required: [Name]
properties:
Name:
description: "Volume name."
type: "string"
x-nullable: false
Description:
type: "string"
x-nullable: false
description: "Volume description."
Size:
type: "integer"
description: "Size in GB (if 0 or not specified, then defaults to 10 GB)."
x-nullable: false
Pool:
type: "string"
description: "Name of capacity pool to provision the volume in, or the name of the current pool."
Labels:
type: "object"
description: "User-defined key/value metadata."
x-nullable: false
additionalProperties:
type: "string"
VolumeUpdateOptions:
type: "object"
description: "Parameters available for updating existing volumes."
properties:
Description:
type: "string"
x-nullable: false
description: "Volume description."
Size:
type: "integer"
description: "Size in GB."
x-nullable: false
Labels:
type: "object"
description: "User-defined key/value metadata."
x-nullable: false
additionalProperties:
type: "string"
# VolumeMountOptions:
# type: "object"
# description: "Parameters available for mounting volumes."
# properties:
# ID:
# type: "string"
# x-nullable: false
# description: "Volume unique ID."
# Name:
# description: "Volume name."
# type: "string"
# x-nullable: false
# Namespace:
# description: "The object scope, such as for teams and projects."
# type: "string"
# x-nullable: false
# Client:
# type: "string"
# x-nullable: false
# description: "Hostname of the client performing the mount."
#
# VolumeUnmountOptions:
# type: "object"
# description: "Parameters available for unmounting volumes."
# properties:
# ID:
# type: "string"
# x-nullable: false
# description: "Volume unique ID."
# Name:
# description: "Volume name."
# type: "string"
# x-nullable: false
# Namespace:
# description: "The object scope, such as for teams and projects."
# type: "string"
# x-nullable: false
# Client:
# type: "string"
# x-nullable: false
# description: "Hostname of the client performing the unmount."
# ListOptions:
# type: "object"
# description: "Parameters for finding volumes."
# properties:
# LabelSelector:
# description: "A selector to restrict the list of returned objects by their labels. Defaults to everything."
# type: "string"
# FieldSelector:
# type: "string"
# description: "A selector to restrict the list of returned objects by their fields. Defaults to everything."
# TimeoutSeconds:
# type: "integer"
# description: "Timeout for the list call."
# Namespace:
# type: "string"
# description: "Object name and auth scope, such as for teams and projects"
Volume:
type: "object"
description: "A storage volume."
required: [Name, Size]
properties:
ID:
description: "Volume unique ID."
type: "string"
x-nullable: false
readOnly: true
Name:
description: "Volume name."
type: "string"
x-nullable: false
Description:
type: "string"
x-nullable: false
description: "Volume description."
Size:
type: integer
description: "Size in GB."
x-nullable: false
Pool:
type: "string"
description: "Name of capacity pool to provision the volume in, or the name of the current pool."
Labels:
type: "object"
description: "User-defined key/value metadata."
x-nullable: false
additionalProperties:
type: "string"
Master:
$ref: "#/definitions/Deployment"
Replicas:
type: "array"
description: "Volume deployment information for the replica volumes."
items:
$ref: "#/definitions/Deployment"
readOnly: true
Status:
type: "string"
description: "Short status, one of: pending, evaluating, deploying, active, unavailable, failed, updating, deleting."
readOnly: true
StatusMessage:
type: "string"
description: "Status message explaining current status."
readOnly: true
Health:
type: "string"
description: "Volume health, one of: healthy, degraded or dead."
readOnly: true
Inode:
type: "integer"
format: "uint32"
description: "Block device inode."
readOnly: true
Deleted:
type: "boolean"
description: "Flag indicating if the volume has been deleted and is waiting for scrubbing."
readOnly: true
Mounted:
type: "boolean"
description: "Flag indicating if the volume is mounted and in use."
readOnly: true
MountedBy:
type: "string"
description: "Reference to the node that has the volume mounted."
readOnly: true
Mountpoint:
type: "string"
description: "Mountpoint where the volume was mounted."
readOnly: true
MountedAt:
type: "string"
format: "dateTime"
description: "When the volume was mounted."
readOnly: true
CreatedBy:
type: "string"
description: "User that created the volume."
readOnly: true
CreatedAt:
type: "string"
format: "dateTime"
description: "When the volume was created."
readOnly: true
example:
Name: vol01
Size: 5
Labels:
com.example.some-label: "some-value"
com.example.some-other-label: "some-other-value"
PoolCreateOptions:
type: "object"
description: "Parameters available for creating new pools."
required: [Name]
properties:
Name:
description: "Pool name."
type: "string"
x-nullable: false
Description:
type: "string"
x-nullable: false
description: "Pool description."
Default:
type: "boolean"
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."
x-nullable: false
DefaultDriver:
type: "string"
x-nullable: false
description: "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."
ControllerNames:
type: "array"
description: "ControllerNames is a list of controller names that are participating in the storage pool."
items:
type: "string"
DriverNames:
type: "array"
description: "DriverNames is a list of backend storage drivers that are available in the storage pool."
items:
type: "string"
Active:
type: "boolean"
x-nullable: false
description: "Flag describing whether rule is active."
default: false
Labels:
type: "object"
description: "Labels define a list of labels that describe the pool."
additionalProperties:
type: "string"
Pool:
type: "object"
description: |
Pools are used to define distributed capacity that can be used to provision
volumes from. Typically, each server that makes storage available will be
added to one or more pools.
Capacity drivers are also added to the pool to determine which backend
storage driver to use. Currently this is limited to a single type of
driver per pool, but in the future we will allow multiple, allowing for
dynamic tiering and snapshots from one driver type to another.
required: [Name]
properties:
ID:
description: "Pool unique ID."
type: "string"
x-nullable: false
readOnly: true
Name:
description: "Pool name."
type: "string"
x-nullable: false
Description:
type: "string"
x-nullable: false
description: "Pool description."
Default:
type: "boolean"
x-nullable: false
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.
DefaultDriver:
type: "string"
description: |
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.
ControllerNames:
type: "array"
description: "ControllerNames is a list of controller names that are participating in the storage pool."
items:
type: "string"
DriverNames:
type: "array"
description: "DriverNames is a list of backend storage drivers that are available in the storage pool."
items:
type: "string"
DriverInstances:
$ref: "#/definitions/DriverInstances"
Active:
type: "boolean"
x-nullable: false
description: "Flag describing whether rule is active."
default: false
CapacityStats:
$ref: "#/definitions/CapacityStats"
Labels:
type: "object"
description: "Labels define a list of labels that describe the pool."
additionalProperties:
type: "string"
Rule:
type: "object"
description: "A policy rule."
required: [Name]
properties:
ID:
description: "Rule unique ID."
type: "string"
x-nullable: false
readOnly: true
Name:
description: "Rule name."
type: "string"
x-nullable: false
Description:
type: "string"
x-nullable: false
description: "Rule description."
Active:
type: "boolean"
x-nullable: false
description: "Flag describing whether rule is active."
default: false
Weight:
type: "integer"
x-nullable: false
description: |
"Weight is used to determine order during rule processing. Rules with heavier weights are processed later."
default: 0
Operator:
type: "string"
description: "Operator is used to compare objects or labels."
enum:
- "!"
- "="
- "=="
- "in"
- "!="
- "notin"
- "exists"
- "gt"
- "lt"
RuleAction:
type: "string"
description: "RuleAction controls whether the action is to add or remove a label from the matching object(s)."
enum:
- "add"
- "remove"
default: "add"
Selectors:
type: "object"
description: "Selectors defines the list of labels that should trigger a rule."
additionalProperties:
type: "string"
Labels:
type: "object"
description: "Labels define the list of labels that will be added or removed from the matching object(s).."
additionalProperties:
type: "string"
CapacityStats:
type: "object"
description: "CapacityStats is used to report capacity statistics on pools and controllers."
properties:
TotalCapacityBytes:
description: "TotalCapacityBytes is the object's total capacity in bytes."
type: "integer"
readOnly: true
AvailableCapacityBytes:
description: "AvailableCapacityBytes is the object's available capacity in bytes."
type: "integer"
readOnly: true
ProvisionedCapacityBytes:
description: "ProvisionedCapacityBytes is the object's provisioned capacity in bytes."
type: "integer"
readOnly: true
DriverInstances:
type: "object"
description: "DriverInstances shows the internal configuration and state of each driver on all the nodes in the pool. Data within DriverInstances can not be modified directly."
properties:
ID:
description: "Instance unique ID."
type: "string"
x-nullable: false
readOnly: true
Name:
description: "Instance name."
type: "string"
x-nullable: false
readOnly: true
Description:
description: "Instance description."
type: "string"
x-nullable: false
readOnly: true
Active:
description: "Flag describing whether the template is active."
type: "boolean"
x-nullable: false
readOnly: true
Config:
description: "Config is JSON struct that is passed directly to the driver. There is no specific format, and the driver is responsible for validation."
type: "object"
readOnly: true
additionalProperties:
type: "string"
Labels:
description: "Labels define a list of labels that describe the driver instance. These are inherited from the pool when the driver instance is created."
type: "object"
readOnly: true
additionalProperties:
type: "string"
ControllerName:
description: "ControllerName specifies the controller that this instance is running on."
type: "string"
x-nullable: false
readOnly: true
PoolID:
description: "PoolID refers to the pool that this driver instance relates to."
type: "string"
x-nullable: false
readOnly: true
DriverName:
description: "DriverName specifies which capacity driver this is an instance of."
type: "string"
x-nullable: false
readOnly: true
CapacityStats:
$ref: "#/definitions/CapacityStats"
parameters:
Name:
name: "name"
in: "path"
type: "string"
description: "Volume name or ID."
required: true
Namespace:
name: "namespace"
in: "path"
type: "string"
description: "Object name and auth scope, such as for teams and projects."
required: true
NamespaceQuery:
name: "namespace"
in: "query"
type: "string"
description: "Object name and auth scope, such as for teams and projects."
default: "default"
LabelSelector:
name: "labelSelector"
in: "query"
description: "A selector to restrict the list of returned objects by their labels. Defaults to everything."
type: "string"
FieldSelector:
name: "fieldSelector"
in: "query"
type: "string"
description: "A selector to restrict the list of returned objects by their fields. Defaults to everything."
TimeoutSeconds:
name: "timeoutSeconds"
in: "query"
type: "integer"
description: "Timeout for the list call."
paths:
/namespaces/{namespace}/volumes:
get:
summary: "List volumes"
description: "List of volumes that match the query."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- name: "namespace"
in: "path"
required: true
description: "The object scope, such as for teams and projects."
type: "string"
- $ref: "#/parameters/LabelSelector"
- $ref: "#/parameters/FieldSelector"
- $ref: "#/parameters/TimeoutSeconds"
- $ref: "#/parameters/NamespaceQuery"
responses:
200:
description: "Success"
schema:
type: "array"
items:
$ref: "#/definitions/Volume"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
post:
summary: "Create volume"
description: "Provisions a new volume."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- $ref: "#/parameters/Namespace"
- name: "VolumeCreateOptions"
in: "body"
schema:
$ref: "#/definitions/VolumeCreateOptions"
responses:
201:
description: "Volume created successfully"
schema:
$ref: "#/definitions/Volume"
401:
description: "Unauthorized"
409:
description: "Volume with name already exists"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
/namespaces/{namespace}/volumes/{name}:
get:
summary: "Get a volume"
description: "Gets a volume by name or ID. Returns to whole volume object."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- $ref: "#/parameters/Name"
- $ref: "#/parameters/Namespace"
responses:
200:
description: "Success"
schema:
$ref: "#/definitions/Volume"
401:
description: "Unauthorized"
404:
description: "Not found"
schema:
$ref: "#/definitions/ErrorResponse"
407:
description: "Volume already exists"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
put:
summary: "Update volume"
description: "Updates an existing volume."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- $ref: "#/parameters/Name"
- $ref: "#/parameters/Namespace"
- name: "VolumeUpdateOptions"
in: "body"
schema:
$ref: "#/definitions/VolumeUpdateOptions"
responses:
200:
description: "Success"
schema:
$ref: "#/definitions/Volume"
401:
description: "Unauthorized"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
delete:
summary: "Delete volume"
description: "Deletes a volume."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- name: "namespace"
in: "path"
required: true
description: "The object scope, such as for teams and projects."
type: "string"
- name: "name"
in: "path"
required: true
description: "Volume name or ID."
type: "string"
responses:
200:
description: "Success"
401:
description: "Unauthorized"
407:
description: "Volume in use"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
/namespaces/{namespace}/volumes/{name}/mount:
post:
summary: "Mount volume"
description: "Updates the mount reference for the volume."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- $ref: "#/parameters/Name"
- $ref: "#/parameters/Namespace"
- name: "client"
in: "body"
description: "Hostname of the client mounting the volume"
required: true
schema:
type: "string"
responses:
200:
description: "Success"
schema:
$ref: "#/definitions/Volume"
401:
description: "Unauthorized"
407:
description: "Volume already mounted"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
/namespaces/{namespace}/volumes/{name}/unmount:
post:
summary: "Mount volume"
description: "Updates the mount reference for the volume."
produces:
- "application/json"
tags: ["Volume"]
parameters:
- $ref: "#/parameters/Name"
- $ref: "#/parameters/Namespace"
- name: "client"
in: "body"
description: "Hostname of the client mounting the volume"
required: true
schema:
type: "string"
responses:
200:
description: "Success"
schema:
$ref: "#/definitions/Volume"
401:
description: "Unauthorized"
407:
description: "Volume not mounted"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
/pools:
get:
summary: "List pools"
description: "List of pools that match the query."
produces:
- "application/json"
tags: ["Pool"]
#parameters:
# - $ref: "#/parameters/LabelSelector"
# - $ref: "#/parameters/FieldSelector"
# - $ref: "#/parameters/TimeoutSeconds"
# - $ref: "#/parameters/NamespaceQuery"
responses:
200:
description: "Success"
schema:
type: "array"
items:
$ref: "#/definitions/Pool"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
post:
summary: "Create pool"
description: "Provisions a new pool."
produces:
- "application/json"
tags: ["Pool"]
parameters:
- name: "PoolCreateOptions"
in: "body"
schema:
$ref: "#/definitions/PoolCreateOptions"
responses:
201:
description: "Pool created successfully"
schema:
$ref: "#/definitions/Pool"
401:
description: "Unauthorized"
409:
description: "Pool with name already exists"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
/pools/{name}:
get:
summary: "Get a pool"
description: "Gets a pool by name or ID. Returns to whole pool object."
produces:
- "application/json"
tags: ["Pool"]
parameters:
- $ref: "#/parameters/Name"
responses:
200:
description: "Success"
schema:
$ref: "#/definitions/Pool"
401:
description: "Unauthorized"
404:
description: "Not found"
schema:
$ref: "#/definitions/ErrorResponse"
407:
description: "Pool already exists"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"
delete:
summary: "Delete pool"
description: "Deletes a pool."
produces:
- "application/json"
tags: ["Pool"]
parameters:
- name: "name"
in: "path"
required: true
description: "Pool name or ID."
type: "string"
responses:
200:
description: "Success"
401:
description: "Unauthorized"
407:
description: "Pool in use"
500:
description: "Server error"
schema:
$ref: "#/definitions/ErrorResponse"

90
vendor/github.com/storageos/go-api/template.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
package storageos
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strconv"
"github.com/storageos/go-api/types"
)
var (
// TemplateAPIPrefix is a partial path to the HTTP endpoint.
TemplateAPIPrefix = "/templates"
// ErrNoSuchTemplate is the error returned when the template does not exist.
ErrNoSuchTemplate = errors.New("no such template")
// ErrTemplateInUse is the error returned when the template requested to be removed is still in use.
ErrTemplateInUse = errors.New("template in use and cannot be removed")
)
// TemplateList returns the list of available templates.
func (c *Client) TemplateList(opts types.ListOptions) ([]types.Template, error) {
path := TemplateAPIPrefix + "?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var templates []types.Template
if err := json.NewDecoder(resp.Body).Decode(&templates); err != nil {
return nil, err
}
return templates, nil
}
// TemplateCreate creates a template on the server and returns the new object.
func (c *Client) TemplateCreate(opts types.TemplateCreateOptions) (string, error) {
resp, err := c.do("POST", TemplateAPIPrefix, doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return "", err
}
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strconv.Unquote(string(out))
}
// Template returns a template by its reference.
func (c *Client) Template(ref string) (*types.Template, error) {
resp, err := c.do("GET", TemplateAPIPrefix+"/"+ref, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchTemplate
}
return nil, err
}
defer resp.Body.Close()
var template types.Template
if err := json.NewDecoder(resp.Body).Decode(&template); err != nil {
return nil, err
}
return &template, nil
}
// TemplateDelete removes a template by its reference.
func (c *Client) TemplateDelete(ref string) error {
resp, err := c.do("DELETE", TemplateAPIPrefix+"/"+ref, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchTemplate
}
if e.Status == http.StatusConflict {
return ErrTemplateInUse
}
}
return nil
}
defer resp.Body.Close()
return nil
}

769
vendor/github.com/storageos/go-api/testing_notes.txt generated vendored Normal file
View File

@ -0,0 +1,769 @@
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-
===============================================================================

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

@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"auth.go",
"capacity_stats.go",
"controller.go",
"controller_update_options.go",
"delete_options.go",
"deployment.go",
"driver_instance.go",
"error_response.go",
"events.go",
"list_options.go",
"namespace.go",
"operator.go",
"pool.go",
"pool_create_options.go",
"rule.go",
"template.go",
"template_create_options.go",
"version.go",
"volume.go",
"volume_create_options.go",
"volume_update_options.go",
],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

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

@ -0,0 +1,14 @@
package types
// AuthConfig contains authorization information for connecting to a Registry
type AuthConfig struct {
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
}

View File

@ -0,0 +1,25 @@
package types
// ErrCapacityStatsUnchanged can be used when comparing stats
const ErrCapacityStatsUnchanged = "no changes"
// CapacityStats is used to report capacity statistics on pools and controllers.
type CapacityStats struct {
// TotalCapacityBytes is the object's total capacity in bytes.
TotalCapacityBytes uint64 `json:"totalCapacityBytes"`
// AvailableCapacityBytes is the object's available capacity in bytes.
AvailableCapacityBytes uint64 `json:"availableCapacityBytes"`
// ProvisionedCapacityBytes is the object's provisioned capacity in bytes.
ProvisionedCapacityBytes uint64 `json:"provisionedCapacityBytes"`
}
// IsEqual checks if capacity values are the same
func (c CapacityStats) IsEqual(n CapacityStats) bool {
if c == n {
return true
}
return false
}

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

@ -0,0 +1,74 @@
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"`
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"`
// 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

@ -0,0 +1,24 @@
package types
import "context"
// ControllerUpdateOptions are available parameters for updating existing controllers.
type ControllerUpdateOptions struct {
// Controller unique ID.
// Read Only: true
ID string `json:"id"`
// Controller name.
// Read Only: true
Name string `json:"name"`
// Description of the controller.
Description string `json:"description"`
// Labels are user-defined key/value metadata.
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,24 @@
package types
import "context"
// DeleteOptions are available parameters for deleting existing volumes.
type DeleteOptions struct {
// Volume unique ID.
// Read Only: true
ID string `json:"id"`
// Volume name.
// Read Only: true
Name string `json:"name"`
// Namespace is the object scope, such as for teams and projects.
Namespace string `json:"namespace"`
// Force will cause the volume to be deleted even if it's in use.
Force bool `json:"force"`
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context `json:"-"`
}

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

@ -0,0 +1,32 @@
package types
import "time"
// Deployment Volume master or replica deployment details.
// swagger:model Deployment
type Deployment struct {
// Deployment unique ID
// Read Only: true
ID string `json:"id"`
// Inode number
// Read Only: true
Inode uint32 `json:"inode"`
// Controller ID
// Read Only: true
Controller string `json:"controller"`
// Health
// Read Only: true
Health string `json:"health"`
// Status
// Read Only: true
Status string `json:"status"`
// Created at
// Read Only: true
CreatedAt time.Time `json:"createdAt"`
}

View File

@ -0,0 +1,86 @@
package types
import "encoding/gob"
// DriverInstance is used to define an instance of a storage capacity driver.
type DriverInstance struct {
// Instance unique ID.
// Read Only: true
ID string `json:"id"`
// Instance name.
Name string `json:"name"`
// Instance description.
Description string `json:"description"`
// Flag describing whether the template is active.
// Default: false
Active bool `json:"active"`
// Config is JSON struct that is passed directly to the driver. There is no
// specific format, and the driver is responsible for validation.
Config interface{} `json:"config"`
// Labels define a list of labels that describe the driver instance. These
// are inherited from the pool when the driver instance is created.
Labels []string `json:"labels"`
// ControllerName specifies the controller that this instance is running on.
ControllerName string `json:"controllerName"`
// PoolID refers to the pool that this driver instance relates to.
PoolID string `json:"poolID"`
// DriverName specifies which capacity driver this is an instance of.
DriverName string `json:"driverName"`
// CapacityStats tracks that capacity usage of this driver instance on the
// current controller.
CapacityStats CapacityStats `json:"capacityStats"`
}
// DriverInstances is a collection of Driver instance objects.
type DriverInstances []*DriverInstance
func init() {
gob.Register(DriverInstance{})
gob.Register([]interface{}{})
}
// Find an instance matching the parameters.
func (i *DriverInstances) Find(pool string, driver string, controller string) *DriverInstance {
for _, inst := range *i {
if inst.PoolID == pool && inst.DriverName == driver && inst.ControllerName == controller {
return inst
}
}
return nil
}
// Add a new instance to the list of instances.
func (i *DriverInstances) Add(new *DriverInstance) {
for _, inst := range *i {
// Skip if it already exists
if inst.PoolID == new.PoolID && inst.DriverName == new.DriverName && inst.ControllerName == new.ControllerName {
return
}
}
*i = append(*i, new)
}
// Remove an instance to the list of instances.
func (i *DriverInstances) Remove(id string) {
// TODO: not working
// for ndx, inst := range *i {
// if inst.ID == id {
// // splice out the item to remove
// *i = append(*i[:ndx], *i[ndx+1:]...)
// return
// }
// }
}

View File

@ -0,0 +1,13 @@
package types
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// ErrorResponse Represents an error.
// swagger:model ErrorResponse
type ErrorResponse struct {
// The error message.
// Required: true
Message string `json:"message"`
}

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

@ -0,0 +1,60 @@
package types
import "time"
// EventType describes the type of event
type EventType string
// EventTypes are added to events to assist with type assertions
const (
RequestType EventType = "request"
ResponseType = "response"
HeartbeatType = "heartbeat"
BackupType = "backup"
)
// Event describes the fields that all events should implement. Event is
// intended to be inherherited in more specific Event types.
type Event struct {
ID string `json:"id"`
// Parent is used to specify parent event
Parent string `json:"parent"`
EventType EventType `json:"eventType"`
Action string `json:"action"`
Timestamp int64 `json:"timestamp"`
Status string `json:"status"`
Message string `json:"message"`
Log []string `json:"log"`
ProgressPercent int `json:"progressPercent"`
CreatedBy string `json:"createdBy"`
Target string `json:"target"`
ActionPayload interface{} `json:"actionPayload"`
// payload can be encoded into bytes as well
ActionPayloadBytes []byte `json:"actionPayloadBts"`
UpdatedAt time.Time `json:"updatedAt"`
CreatedAt time.Time `json:"createdAt"`
// retry related value
Retry bool `json:"retry"`
RetriedAt time.Time `json:"retriedAt"`
Attempts int `json:"attempts"`
// optional parameter
Deadline time.Time `json:"deadline"`
// optional events to dispatch
Rollback []*Request `json:"rollback"`
RollbackDone bool `json:"rollbackDone"`
Subject string `json:"subject"` // or "queue"
// controller ID which created this event
OriginController string `json:"originController"`
}
// Request is the message structure used for sending request events
type Request struct {
Event
}

View File

@ -0,0 +1,19 @@
package types
import "context"
// ListOptions are optional parameters for finding and listing most objects.
type ListOptions struct {
// FieldSelector restricts the list of returned objects by their fields. Defaults to everything.
FieldSelector string
// LabelSelector restricts the list of returned objects by their labels. Defaults to everything.
LabelSelector string
// Namespace is the object scope, such as for teams and projects.
Namespace string
// Context can be set with a timeout or can be used to cancel a request.
Context context.Context
}

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

@ -0,0 +1,59 @@
package types
import (
"context"
"time"
)
// Namespace is used to as a container to isolate namespace and rule obects.
type Namespace struct {
// Namespace unique ID.
// Read Only: true
ID string `json:"id"`
// Namespace name.
// Required: true
Name string `json:"name"`
// The optional DisplayName is how the project is displayed in the web console (defaults to name).
DisplayName string `json:"displayName"`
// Namespcae description.
Description string `json:"description"`
// User-defined key/value metadata.
Labels map[string]string `json:"labels"`
// When the namespace was created.
// Read Only: true
CreatedAt time.Time `json:"createdAt"`
// User that created the namespace.
// Read Only: true
CreatedBy string `json:"createdBy"`
// When the namespace was created.
// Read Only: true
UpdatedAt time.Time `json:"updatedAt"`
}
// NamespaceCreateOptions are available parameters for creating new namespaces.
type NamespaceCreateOptions struct {
// Name is the name of the namespace to create.
// Required: true
Name string `json:"name"`
// The optional DisplayName is how the project is displayed in the web console (defaults to name).
DisplayName string `json:"displayName"`
// Description describes the namespace.
Description string `json:"description"`
// Labels are user-defined key/value metadata.
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:"-"`
}

Some files were not shown because too many files have changed in this diff Show More