mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Merge pull request #31434 from johscheuer/quobyte-dynamic-prov
Automatic merge from submit-queue Support Quobyte as StorageClass This PR allows Users to use Quobyte as StorageClass for dynamic volume provisioning and implements the Provisioner/Deleter Interface. @quolix @kubernetes/sig-storage @rootfs
This commit is contained in:
commit
aa0e8b9cc1
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -1907,6 +1907,10 @@
|
||||
"ImportPath": "github.com/prometheus/procfs",
|
||||
"Rev": "454a56f35412459b5e684fd5ec0f9211b94f002a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/quobyte/api",
|
||||
"Rev": "bf713b5a4333f44504fa1ce63690de45cfed6413"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud",
|
||||
"Comment": "v1.0.0-920-g934dbf8",
|
||||
|
36
Godeps/LICENSES
generated
36
Godeps/LICENSES
generated
@ -60578,6 +60578,42 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/quobyte/api licensed under: =
|
||||
|
||||
|
||||
Copyright (c) 2016, Quobyte Inc.
|
||||
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.
|
||||
|
||||
* Neither the name of quobyte-automation nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
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/quobyte/api/LICENSE beacc5ea3bcda24bdcec545022dbb0b4 -
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
= vendor/github.com/rackspace/gophercloud licensed under: =
|
||||
|
||||
|
@ -45,6 +45,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/volume/glusterfs"
|
||||
"k8s.io/kubernetes/pkg/volume/host_path"
|
||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||
"k8s.io/kubernetes/pkg/volume/quobyte"
|
||||
"k8s.io/kubernetes/pkg/volume/rbd"
|
||||
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
|
||||
)
|
||||
@ -105,6 +106,8 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
|
||||
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
|
||||
// add rbd provisioner
|
||||
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
|
||||
|
||||
if cloud != nil {
|
||||
switch {
|
||||
case aws.ProviderName == cloud.ProviderName():
|
||||
|
@ -191,6 +191,94 @@ parameters:
|
||||
* `userId`: Ceph client ID that is used to map the RBD image. Default is the same as `adminId`.
|
||||
* `userSecretName`: The name of Ceph Secret for `userId` to map RBD image. It must exist in the same namespace as PVCs. It is required.
|
||||
|
||||
#### Quobyte
|
||||
|
||||
<!-- BEGIN MUNGE: EXAMPLE quobyte/quobyte-storage-class.yaml -->
|
||||
|
||||
```yaml
|
||||
apiVersion: storage.k8s.io/v1beta1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: slow
|
||||
provisioner: kubernetes.io/quobyte
|
||||
parameters:
|
||||
quobyteAPIServer: "http://138.68.74.142:7860"
|
||||
registry: "138.68.74.142:7861"
|
||||
adminSecretName: "quobyte-admin-secret"
|
||||
adminSecretNamespace: "kube-system"
|
||||
user: "root"
|
||||
group: "root"
|
||||
quobyteConfig: "BASE"
|
||||
quobyteTenant: "DEFAULT"
|
||||
```
|
||||
|
||||
[Download example](quobyte/quobyte-storage-class.yaml?raw=true)
|
||||
<!-- END MUNGE: EXAMPLE quobyte/quobyte-storage-class.yaml -->
|
||||
|
||||
* **quobyteAPIServer** API Server of Quobyte in the format http(s)://api-server:7860
|
||||
* **registry** Quobyte registry to use to mount the volume. You can specifiy the registry as <host>:<port> pair or if you want to specify multiple registries you just have to put a comma between them e.q. <host1>:<port>,<host2>:<port>,<host3>:<port>. The host can be an IP address or if you have a working DNS you can also provide the DNS names.
|
||||
* **adminSecretName** secret that holds information about the Quobyte user and the password to authenticate agains the API server.
|
||||
* **adminSecretNamespace** The namespace for **adminSecretName**. Default is `default`.
|
||||
* **user** maps all access to this user. Default is `root`.
|
||||
* **group** maps all access to this group. Default is `nfsnobody`.
|
||||
* **quobyteConfig** use the specified configuration to create the volume. You can create a new configuration or modify an existing one with the Web console or the quobyte CLI. Default is `BASE`
|
||||
* **quobyteTenant** use the specified tenant ID to create/delete the volume. This Quobyte tenant has to be already present in Quobyte. Default is `DEFAULT`
|
||||
|
||||
First create Quobyte admin's Secret in the system namespace. Here the Secret is created in `kube-system`:
|
||||
|
||||
```
|
||||
$ kubectl create -f examples/experimental/persistent-volume-provisioning/quobyte/quobyte-admin-secret.yaml --namespace=kube-system
|
||||
```
|
||||
|
||||
Then create the Quobyte storage class:
|
||||
|
||||
```
|
||||
$ kubectl create -f examples/experimental/persistent-volume-provisioning/quobyte/quobyte-storage-class.yaml
|
||||
```
|
||||
|
||||
Now create a PVC
|
||||
|
||||
```
|
||||
$ kubectl create -f examples/experimental/persistent-volume-provisioning/claim1.json
|
||||
```
|
||||
|
||||
Check the created PVC:
|
||||
|
||||
```
|
||||
$ kubectl describe pvc
|
||||
Name: claim1
|
||||
Namespace: default
|
||||
Status: Bound
|
||||
Volume: pvc-bdb82652-694a-11e6-b811-080027242396
|
||||
Labels: <none>
|
||||
Capacity: 3Gi
|
||||
Access Modes: RWO
|
||||
No events.
|
||||
|
||||
$ kubectl describe pv
|
||||
Name: pvc-bdb82652-694a-11e6-b811-080027242396
|
||||
Labels: <none>
|
||||
Status: Bound
|
||||
Claim: default/claim1
|
||||
Reclaim Policy: Delete
|
||||
Access Modes: RWO
|
||||
Capacity: 3Gi
|
||||
Message:
|
||||
Source:
|
||||
Type: Quobyte (a Quobyte mount on the host that shares a pod's lifetime)
|
||||
Registry: 138.68.79.14:7861
|
||||
Volume: kubernetes-dynamic-pvc-bdb97c58-694a-11e6-91b6-080027242396
|
||||
ReadOnly: false
|
||||
No events.
|
||||
```
|
||||
|
||||
Create a Pod to use the PVC:
|
||||
|
||||
```
|
||||
$ kubectl create -f examples/experimental/persistent-volume-provisioning/quobyte/example-pod.yaml
|
||||
```
|
||||
|
||||
|
||||
### User provisioning requests
|
||||
|
||||
Users request dynamically provisioned storage by including a storage class in their `PersistentVolumeClaim`.
|
||||
|
@ -0,0 +1,23 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: server
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
role: server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
role: server
|
||||
spec:
|
||||
containers:
|
||||
- name: server
|
||||
image: nginx
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/www/html
|
||||
name: quobytepvc
|
||||
volumes:
|
||||
- name: quobytepvc
|
||||
persistentVolumeClaim:
|
||||
claimName: claim1
|
@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: quobyte-admin-secret
|
||||
data:
|
||||
password: cXVvYnl0ZQ==
|
||||
user: YWRtaW4=
|
@ -0,0 +1,14 @@
|
||||
apiVersion: storage.k8s.io/v1beta1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: slow
|
||||
provisioner: kubernetes.io/quobyte
|
||||
parameters:
|
||||
quobyteAPIServer: "http://138.68.74.142:7860"
|
||||
registry: "138.68.74.142:7861"
|
||||
adminSecretName: "quobyte-admin-secret"
|
||||
adminSecretNamespace: "kube-system"
|
||||
user: "root"
|
||||
group: "root"
|
||||
quobyteConfig: "BASE"
|
||||
quobyteTenant: "DEFAULT"
|
@ -94,11 +94,11 @@ spec:
|
||||
<!-- END MUNGE: EXAMPLE ./quobyte-pod.yaml -->
|
||||
|
||||
Parameters:
|
||||
* **registry** Quobyte registry to use to mount the volume. You can specifiy the registry as <host>:<port> pair or if you want to specify multiple registries you just have to put a semicolon between them e.q. <host1>:<port>,<host2>:<port>,<host3>:<port>. The host can be an IP address or if you have a working DNS you can also provide the DNS names.
|
||||
* **registry** Quobyte registry to use to mount the volume. You can specifiy the registry as <host>:<port> pair or if you want to specify multiple registries you just have to put a comma between them e.q. <host1>:<port>,<host2>:<port>,<host3>:<port>. The host can be an IP address or if you have a working DNS you can also provide the DNS names.
|
||||
* **volume** volume represents a Quobyte volume which must be created before usage.
|
||||
* **readOnly** is the boolean that sets the mountpoint readOnly or readWrite.
|
||||
* **user** maps all access to this user. Default is root.
|
||||
* **group** maps all access to this group. Default is empty.
|
||||
* **user** maps all access to this user. Default is `root`.
|
||||
* **group** maps all access to this group. Default is `nfsnobody`.
|
||||
|
||||
Creating the pod:
|
||||
|
||||
|
@ -20,14 +20,18 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
goStrings "strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/pborman/uuid"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/util/strings"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
|
||||
@ -39,11 +43,27 @@ type quobytePlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
// This user is used to authenticate against the
|
||||
// Quobyte API server and holds all information
|
||||
type quobyteAPIConfig struct {
|
||||
quobyteUser string
|
||||
quobytePassword string
|
||||
quobyteAPIServer string
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &quobytePlugin{}
|
||||
var _ volume.PersistentVolumePlugin = &quobytePlugin{}
|
||||
var _ volume.DeletableVolumePlugin = &quobytePlugin{}
|
||||
var _ volume.ProvisionableVolumePlugin = &quobytePlugin{}
|
||||
var _ volume.Provisioner = &quobyteVolumeProvisioner{}
|
||||
var _ volume.Deleter = &quobyteVolumeDeleter{}
|
||||
|
||||
const (
|
||||
quobytePluginName = "kubernetes.io/quobyte"
|
||||
|
||||
annotationQuobyteAPIServer = "quobyte.kubernetes.io/api"
|
||||
annotationQuobyteAPISecret = "quobyte.kubernetes.io/apiuser"
|
||||
annotationQuobyteAPISecretNamespace = "quobyte.kubernetes.io/apipassword"
|
||||
)
|
||||
|
||||
func (plugin *quobytePlugin) Init(host volume.VolumeHost) error {
|
||||
@ -149,7 +169,8 @@ func (plugin *quobytePlugin) newMounterInternal(spec *volume.Spec, pod *api.Pod,
|
||||
plugin: plugin,
|
||||
},
|
||||
registry: source.Registry,
|
||||
readOnly: readOnly}, nil
|
||||
readOnly: readOnly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
@ -157,12 +178,14 @@ func (plugin *quobytePlugin) NewUnmounter(volName string, podUID types.UID) (vol
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
|
||||
return &quobyteUnmounter{&quobyte{
|
||||
volName: volName,
|
||||
mounter: mounter,
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
|
||||
plugin: plugin,
|
||||
}}, nil
|
||||
return &quobyteUnmounter{
|
||||
&quobyte{
|
||||
volName: volName,
|
||||
mounter: mounter,
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
|
||||
plugin: plugin,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Quobyte volumes represent a bare host directory mount of an quobyte export.
|
||||
@ -172,6 +195,8 @@ type quobyte struct {
|
||||
user string
|
||||
group string
|
||||
volume string
|
||||
tenant string
|
||||
config string
|
||||
mounter mount.Interface
|
||||
plugin *quobytePlugin
|
||||
volume.MetricsNil
|
||||
@ -226,22 +251,22 @@ func (mounter *quobyteMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||
}
|
||||
|
||||
// GetPath returns the path to the user specific mount of a Quobyte volume
|
||||
// Returns a path in the format ../user@volume e.g. ../root@MyVolume
|
||||
// or if a group is set ../user#group@volume
|
||||
// Returns a path in the format ../user#group@volume
|
||||
func (quobyteVolume *quobyte) GetPath() string {
|
||||
user := quobyteVolume.user
|
||||
if len(user) == 0 {
|
||||
user = "root"
|
||||
}
|
||||
|
||||
group := quobyteVolume.group
|
||||
if len(group) == 0 {
|
||||
group = "nfsnobody"
|
||||
}
|
||||
|
||||
// Quobyte has only one mount in the PluginDir where all Volumes are mounted
|
||||
// The Quobyte client does a fixed-user mapping
|
||||
pluginDir := quobyteVolume.plugin.host.GetPluginDir(strings.EscapeQualifiedNameForDisk(quobytePluginName))
|
||||
if len(quobyteVolume.group) > 0 {
|
||||
return path.Join(pluginDir, fmt.Sprintf("%s#%s@%s", user, quobyteVolume.group, quobyteVolume.volume))
|
||||
}
|
||||
|
||||
return path.Join(pluginDir, fmt.Sprintf("%s@%s", user, quobyteVolume.volume))
|
||||
return path.Join(pluginDir, fmt.Sprintf("%s#%s@%s", user, group, quobyteVolume.volume))
|
||||
}
|
||||
|
||||
type quobyteUnmounter struct {
|
||||
@ -258,3 +283,192 @@ func (unmounter *quobyteUnmounter) TearDown() error {
|
||||
func (unmounter *quobyteUnmounter) TearDownAt(dir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type quobyteVolumeDeleter struct {
|
||||
*quobyteMounter
|
||||
pv *api.PersistentVolume
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Quobyte == nil {
|
||||
return nil, fmt.Errorf("spec.PersistentVolumeSource.Spec.Quobyte is nil")
|
||||
}
|
||||
|
||||
return plugin.newDeleterInternal(spec)
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newDeleterInternal(spec *volume.Spec) (volume.Deleter, error) {
|
||||
source, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &quobyteVolumeDeleter{
|
||||
quobyteMounter: &quobyteMounter{
|
||||
quobyte: &quobyte{
|
||||
volName: spec.Name(),
|
||||
user: source.User,
|
||||
group: source.Group,
|
||||
volume: source.Volume,
|
||||
plugin: plugin,
|
||||
},
|
||||
registry: source.Registry,
|
||||
readOnly: readOnly,
|
||||
},
|
||||
pv: spec.PersistentVolume,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
if len(options.AccessModes) == 0 {
|
||||
options.AccessModes = plugin.GetAccessModes()
|
||||
}
|
||||
|
||||
return plugin.newProvisionerInternal(options)
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newProvisionerInternal(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
return &quobyteVolumeProvisioner{
|
||||
quobyteMounter: &quobyteMounter{
|
||||
quobyte: &quobyte{
|
||||
plugin: plugin,
|
||||
},
|
||||
},
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type quobyteVolumeProvisioner struct {
|
||||
*quobyteMounter
|
||||
options volume.VolumeOptions
|
||||
}
|
||||
|
||||
func (provisioner *quobyteVolumeProvisioner) Provision() (*api.PersistentVolume, error) {
|
||||
if provisioner.options.Selector != nil {
|
||||
return nil, fmt.Errorf("claim Selector is not supported")
|
||||
}
|
||||
var apiServer, adminSecretName, quobyteUser, quobytePassword string
|
||||
adminSecretNamespace := "default"
|
||||
provisioner.config = "BASE"
|
||||
provisioner.tenant = "DEFAULT"
|
||||
|
||||
for k, v := range provisioner.options.Parameters {
|
||||
switch goStrings.ToLower(k) {
|
||||
case "registry":
|
||||
provisioner.registry = v
|
||||
case "adminsecretname":
|
||||
adminSecretName = v
|
||||
case "adminsecretnamespace":
|
||||
adminSecretNamespace = v
|
||||
case "quobyteapiserver":
|
||||
apiServer = v
|
||||
case "user":
|
||||
provisioner.user = v
|
||||
case "group":
|
||||
provisioner.group = v
|
||||
case "quobytetenant":
|
||||
provisioner.tenant = v
|
||||
case "quobyteconfig":
|
||||
provisioner.config = v
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, provisioner.plugin.GetPluginName())
|
||||
}
|
||||
}
|
||||
|
||||
secretMap, err := util.GetSecret(adminSecretNamespace, adminSecretName, provisioner.plugin.host.GetKubeClient())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if quobyteUser, ok = secretMap["user"]; !ok {
|
||||
return nil, fmt.Errorf("Missing \"user\" in secret")
|
||||
}
|
||||
|
||||
if quobytePassword, ok = secretMap["password"]; !ok {
|
||||
return nil, fmt.Errorf("Missing \"password\" in secret")
|
||||
}
|
||||
|
||||
if !validateRegistry(provisioner.registry) {
|
||||
return nil, fmt.Errorf("Quoybte registry missing or malformed: must be a host:port pair or multiple pairs seperated by commas")
|
||||
}
|
||||
|
||||
if len(apiServer) == 0 {
|
||||
return nil, fmt.Errorf("Quoybte API server missing or malformed: must be a http(s)://host:port pair or multiple pairs seperated by commas")
|
||||
}
|
||||
|
||||
// create random image name
|
||||
provisioner.volume = fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())
|
||||
|
||||
cfg := &quobyteAPIConfig{
|
||||
quobyteAPIServer: apiServer,
|
||||
quobyteUser: quobyteUser,
|
||||
quobytePassword: quobytePassword,
|
||||
}
|
||||
manager := &quobyteVolumeManager{
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
vol, sizeGB, err := manager.createVolume(provisioner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pv := new(api.PersistentVolume)
|
||||
pv.Spec.PersistentVolumeSource.Quobyte = vol
|
||||
pv.Spec.PersistentVolumeReclaimPolicy = provisioner.options.PersistentVolumeReclaimPolicy
|
||||
pv.Spec.AccessModes = provisioner.options.AccessModes
|
||||
pv.Spec.Capacity = api.ResourceList{
|
||||
api.ResourceName(api.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
|
||||
}
|
||||
|
||||
util.AddVolumeAnnotations(pv, map[string]string{
|
||||
annotationQuobyteAPIServer: apiServer,
|
||||
annotationQuobyteAPISecret: adminSecretName,
|
||||
annotationQuobyteAPISecretNamespace: adminSecretNamespace,
|
||||
})
|
||||
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
func (deleter *quobyteVolumeDeleter) GetPath() string {
|
||||
return deleter.quobyte.GetPath()
|
||||
}
|
||||
|
||||
func (deleter *quobyteVolumeDeleter) Delete() error {
|
||||
var quobyteUser, quobytePassword string
|
||||
annotations, err := util.ParseVolumeAnnotations(deleter.pv, []string{
|
||||
annotationQuobyteAPISecret,
|
||||
annotationQuobyteAPISecretNamespace,
|
||||
annotationQuobyteAPIServer})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secretMap, err := util.GetSecret(
|
||||
annotations[annotationQuobyteAPISecretNamespace],
|
||||
annotations[annotationQuobyteAPISecret],
|
||||
deleter.plugin.host.GetKubeClient())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if quobyteUser, ok = secretMap["user"]; !ok {
|
||||
return fmt.Errorf("Missing \"user\" in secret")
|
||||
}
|
||||
|
||||
if quobytePassword, ok = secretMap["password"]; !ok {
|
||||
return fmt.Errorf("Missing \"password\" in secret")
|
||||
}
|
||||
|
||||
manager := &quobyteVolumeManager{
|
||||
config: &quobyteAPIConfig{
|
||||
quobyteUser: quobyteUser,
|
||||
quobytePassword: quobytePassword,
|
||||
quobyteAPIServer: annotations[annotationQuobyteAPIServer],
|
||||
},
|
||||
}
|
||||
return manager.deleteVolume(deleter)
|
||||
}
|
||||
|
@ -17,12 +17,58 @@ limitations under the License.
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"net"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
|
||||
"github.com/golang/glog"
|
||||
quobyte_api "github.com/quobyte/api"
|
||||
)
|
||||
|
||||
type quobyteVolumeManager struct {
|
||||
config *quobyteAPIConfig
|
||||
}
|
||||
|
||||
func (manager *quobyteVolumeManager) createVolume(provisioner *quobyteVolumeProvisioner) (quobyte *api.QuobyteVolumeSource, size int, err error) {
|
||||
volumeSize := int(volume.RoundUpSize(provisioner.options.Capacity.Value(), 1024*1024*1024))
|
||||
// Quobyte has the concept of Volumes which doen't have a specific size (they can grow unlimited)
|
||||
// to simulate a size constraint we could set here a Quota
|
||||
volumeRequest := &quobyte_api.CreateVolumeRequest{
|
||||
Name: provisioner.volume,
|
||||
RootUserID: provisioner.user,
|
||||
RootGroupID: provisioner.group,
|
||||
TenantID: provisioner.tenant,
|
||||
ConfigurationName: provisioner.config,
|
||||
}
|
||||
|
||||
if _, err := manager.createQuobyteClient().CreateVolume(volumeRequest); err != nil {
|
||||
return &api.QuobyteVolumeSource{}, volumeSize, err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Created Quobyte volume %s", provisioner.volume)
|
||||
return &api.QuobyteVolumeSource{
|
||||
Registry: provisioner.registry,
|
||||
Volume: provisioner.volume,
|
||||
User: provisioner.user,
|
||||
Group: provisioner.group,
|
||||
}, volumeSize, nil
|
||||
}
|
||||
|
||||
func (manager *quobyteVolumeManager) deleteVolume(deleter *quobyteVolumeDeleter) error {
|
||||
return manager.createQuobyteClient().DeleteVolumeByName(deleter.volume, deleter.tenant)
|
||||
}
|
||||
|
||||
func (manager *quobyteVolumeManager) createQuobyteClient() *quobyte_api.QuobyteClient {
|
||||
return quobyte_api.NewQuobyteClient(
|
||||
manager.config.quobyteAPIServer,
|
||||
manager.config.quobyteUser,
|
||||
manager.config.quobytePassword,
|
||||
)
|
||||
}
|
||||
|
||||
func (mounter *quobyteMounter) pluginDirIsMounted(pluginDir string) (bool, error) {
|
||||
mounts, err := mounter.mounter.List()
|
||||
if err != nil {
|
||||
@ -46,3 +92,17 @@ func (mounter *quobyteMounter) pluginDirIsMounted(pluginDir string) (bool, error
|
||||
func (mounter *quobyteMounter) correctTraillingSlash(regStr string) string {
|
||||
return path.Clean(regStr) + "/"
|
||||
}
|
||||
|
||||
func validateRegistry(registry string) bool {
|
||||
if len(registry) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, hostPortPair := range strings.Split(registry, ",") {
|
||||
if _, _, err := net.SplitHostPort(hostPortPair); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
)
|
||||
@ -126,3 +127,33 @@ func GetSecret(namespace, secretName string, kubeClient clientset.Interface) (ma
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// AddVolumeAnnotations adds a golang Map as annotation to a PersistentVolume
|
||||
func AddVolumeAnnotations(pv *api.PersistentVolume, annotations map[string]string) {
|
||||
if pv.Annotations == nil {
|
||||
pv.Annotations = map[string]string{}
|
||||
}
|
||||
|
||||
for k, v := range annotations {
|
||||
pv.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// ParseVolumeAnnotations reads the defined annoations from a PersistentVolume
|
||||
func ParseVolumeAnnotations(pv *api.PersistentVolume, parseAnnotations []string) (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
|
||||
if pv.Annotations == nil {
|
||||
return result, fmt.Errorf("cannot parse volume annotations: no annotations found")
|
||||
}
|
||||
|
||||
for _, annotation := range parseAnnotations {
|
||||
if val, ok := pv.Annotations[annotation]; ok {
|
||||
result[annotation] = val
|
||||
} else {
|
||||
return result, fmt.Errorf("cannot parse volume annotations: annotation %s not found", annotation)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
28
vendor/github.com/quobyte/api/LICENSE
generated
vendored
Normal file
28
vendor/github.com/quobyte/api/LICENSE
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
Copyright (c) 2016, Quobyte Inc.
|
||||
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.
|
||||
|
||||
* Neither the name of quobyte-automation nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
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.
|
35
vendor/github.com/quobyte/api/README.md
generated
vendored
Normal file
35
vendor/github.com/quobyte/api/README.md
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
# Quobyte API Clients
|
||||
|
||||
Get the quoybte api client
|
||||
|
||||
```bash
|
||||
go get github.com/quobyte/api
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
quobyte_api "github.com/quobyte/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := quobyte_api.NewQuobyteClient("http://apiserver:7860", "user", "password")
|
||||
req := &quobyte_api.CreateVolumeRequest{
|
||||
Name: "MyVolume",
|
||||
RootUserID: "root",
|
||||
RootGroupID: "root",
|
||||
ConfigurationName: "base",
|
||||
}
|
||||
|
||||
volume_uuid, err := client.CreateVolume(req)
|
||||
if err != nil {
|
||||
log.Fatalf("Error:", err)
|
||||
}
|
||||
|
||||
log.Printf("%s", volume_uuid)
|
||||
}
|
||||
```
|
79
vendor/github.com/quobyte/api/quobyte.go
generated
vendored
Normal file
79
vendor/github.com/quobyte/api/quobyte.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
// Package quobyte represents a golang API for the Quobyte Storage System
|
||||
package quobyte
|
||||
|
||||
import "net/http"
|
||||
|
||||
type QuobyteClient struct {
|
||||
client *http.Client
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
// NewQuobyteClient creates a new Quobyte API client
|
||||
func NewQuobyteClient(url string, username string, password string) *QuobyteClient {
|
||||
return &QuobyteClient{
|
||||
client: &http.Client{},
|
||||
url: url,
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateVolume creates a new Quobyte volume. Its root directory will be owned by given user and group
|
||||
func (client QuobyteClient) CreateVolume(request *CreateVolumeRequest) (string, error) {
|
||||
var response volumeUUID
|
||||
if err := client.sendRequest("createVolume", request, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.VolumeUUID, nil
|
||||
}
|
||||
|
||||
// ResolveVolumeNameToUUID resolves a volume name to a UUID
|
||||
func (client *QuobyteClient) ResolveVolumeNameToUUID(volumeName, tenant string) (string, error) {
|
||||
request := &resolveVolumeNameRequest{
|
||||
VolumeName: volumeName,
|
||||
TenantDomain: tenant,
|
||||
}
|
||||
var response volumeUUID
|
||||
if err := client.sendRequest("resolveVolumeName", request, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.VolumeUUID, nil
|
||||
}
|
||||
|
||||
// DeleteVolume deletes a Quobyte volume
|
||||
func (client *QuobyteClient) DeleteVolume(UUID string) error {
|
||||
return client.sendRequest(
|
||||
"deleteVolume",
|
||||
&volumeUUID{
|
||||
VolumeUUID: UUID,
|
||||
},
|
||||
nil)
|
||||
}
|
||||
|
||||
// DeleteVolumeByName deletes a volume by a given name
|
||||
func (client *QuobyteClient) DeleteVolumeByName(volumeName, tenant string) error {
|
||||
uuid, err := client.ResolveVolumeNameToUUID(volumeName, tenant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.DeleteVolume(uuid)
|
||||
}
|
||||
|
||||
// GetClientList returns a list of all active clients
|
||||
func (client *QuobyteClient) GetClientList(tenant string) (GetClientListResponse, error) {
|
||||
request := &getClientListRequest{
|
||||
TenantDomain: tenant,
|
||||
}
|
||||
|
||||
var response GetClientListResponse
|
||||
if err := client.sendRequest("getClientListRequest", request, &response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
108
vendor/github.com/quobyte/api/rpc_client.go
generated
vendored
Normal file
108
vendor/github.com/quobyte/api/rpc_client.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
emptyResponse string = "Empty result and no error occured"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"jsonrpc"`
|
||||
Result *json.RawMessage `json:"result"`
|
||||
Error *json.RawMessage `json:"error"`
|
||||
}
|
||||
|
||||
type rpcError struct {
|
||||
Code int64 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (err *rpcError) decodeErrorCode() string {
|
||||
switch err.Code {
|
||||
case -32600:
|
||||
return "ERROR_CODE_INVALID_REQUEST"
|
||||
case -32603:
|
||||
return "ERROR_CODE_JSON_ENCODING_FAILED"
|
||||
case -32601:
|
||||
return "ERROR_CODE_METHOD_NOT_FOUND"
|
||||
case -32700:
|
||||
return "ERROR_CODE_PARSE_ERROR"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func encodeRequest(method string, params interface{}) ([]byte, error) {
|
||||
return json.Marshal(&request{
|
||||
// Generate random ID and convert it to a string
|
||||
ID: strconv.FormatInt(rand.Int63(), 10),
|
||||
Version: "2.0",
|
||||
Method: method,
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
|
||||
func decodeResponse(ioReader io.Reader, reply interface{}) error {
|
||||
var resp response
|
||||
if err := json.NewDecoder(ioReader).Decode(&resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Error != nil {
|
||||
var rpcErr rpcError
|
||||
if err := json.Unmarshal(*resp.Error, &rpcErr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rpcErr.Message != "" {
|
||||
return errors.New(rpcErr.Message)
|
||||
}
|
||||
|
||||
respError := rpcErr.decodeErrorCode()
|
||||
if respError != "" {
|
||||
return errors.New(respError)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Result != nil && reply != nil {
|
||||
return json.Unmarshal(*resp.Result, reply)
|
||||
}
|
||||
|
||||
return errors.New(emptyResponse)
|
||||
}
|
||||
|
||||
func (client QuobyteClient) sendRequest(method string, request interface{}, response interface{}) error {
|
||||
message, err := encodeRequest(method, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", client.url, bytes.NewBuffer(message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.SetBasicAuth(client.username, client.password)
|
||||
resp, err := client.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return decodeResponse(resp.Body, &response)
|
||||
}
|
34
vendor/github.com/quobyte/api/types.go
generated
vendored
Normal file
34
vendor/github.com/quobyte/api/types.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
package quobyte
|
||||
|
||||
// CreateVolumeRequest represents a CreateVolumeRequest
|
||||
type CreateVolumeRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
RootUserID string `json:"root_user_id,omitempty"`
|
||||
RootGroupID string `json:"root_group_id,omitempty"`
|
||||
ReplicaDeviceIDS []uint64 `json:"replica_device_ids,string,omitempty"`
|
||||
ConfigurationName string `json:"configuration_name,omitempty"`
|
||||
AccessMode uint32 `json:"access_mode,string,omitempty"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
}
|
||||
|
||||
type resolveVolumeNameRequest struct {
|
||||
VolumeName string `json:"volume_name,omitempty"`
|
||||
TenantDomain string `json:"tenant_domain,omitempty"`
|
||||
}
|
||||
|
||||
type volumeUUID struct {
|
||||
VolumeUUID string `json:"volume_uuid,omitempty"`
|
||||
}
|
||||
|
||||
type getClientListRequest struct {
|
||||
TenantDomain string `json:"tenant_domain,omitempty"`
|
||||
}
|
||||
|
||||
type GetClientListResponse struct {
|
||||
Clients []Client `json:"client,omitempty"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
MountedUserName string `json:"mount_user_name,omitempty"`
|
||||
MountedVolumeUUID string `json:"mounted_volume_uuid,omitempty"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user