Merge pull request #38579 from humblec/gluster-volume-type

Automatic merge from submit-queue

Let admin configure the volume type and parameters for gluster DP volumes
This commit is contained in:
Kubernetes Submit Queue 2017-01-06 01:35:37 -08:00 committed by GitHub
commit 7171f6fd5f
5 changed files with 169 additions and 13 deletions

View File

@ -80,6 +80,7 @@ parameters:
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"
```
* `resturl` : Gluster REST service/Heketi service url which provision gluster volumes on demand. The general format should be `IPaddress:Port` and this is a mandatory parameter for GlusterFS dynamic provisioner. If Heketi service is exposed as a routable service in openshift/kubernetes setup, this can have a format similar to
@ -96,6 +97,17 @@ Example of a secret can be found in [glusterfs-provisioning-secret.yaml](gluster
* `gidMin` + `gidMax` : The minimum and maximum value of GID range for the storage class. A unique value (GID) in this range ( gidMin-gidMax ) will be used for dynamically provisioned volumes. These are optional values. If not specified, the volume will be provisioned with a value between 2000-2147483647 which are defaults for gidMin and gidMax respectively.
* `volumetype` : The volume type and it's parameters can be configured with this optional value. If the volume type is not mentioned, it's up to the provisioner to decide the volume type.
For example:
'Replica volume':
`volumetype: replicate:3` where '3' is replica count.
'Disperse/EC volume':
`volumetype: disperse:4:2` where '4' is data and '2' is the redundancy count.
'Distribute volume':
`volumetype: none`
For available volume types and it's administration options refer: ([Administration Guide](https://access.redhat.com/documentation/en-US/Red_Hat_Storage/3.1/html/Administration_Guide/part-Overview.html))
Reference : ([How to configure Heketi](https://github.com/heketi/heketi/wiki/Setting-up-the-topology))
When the persistent volumes are dynamically provisioned, the Gluster plugin automatically create an endpoint and a headless service in the name `gluster-dynamic-<claimname>`. This dynamic endpoint and service will be deleted automatically when the persistent volume claim is deleted.

View File

@ -11,3 +11,4 @@ parameters:
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"

View File

@ -57,6 +57,7 @@ go_test(
"//pkg/util/testing:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//vendor:github.com/heketi/heketi/pkg/glusterfs/api",
],
)

View File

@ -378,6 +378,7 @@ type provisioningConfig struct {
clusterId string
gidMin int
gidMax int
volumeType gapi.VolumeDurabilityInfo
}
type glusterfsVolumeProvisioner struct {
@ -402,6 +403,19 @@ func convertGid(gidString string) (int, error) {
return gid, nil
}
func convertVolumeParam(volumeString string) (int, error) {
count, err := strconv.Atoi(volumeString)
if err != nil {
return 0, fmt.Errorf("failed to parse %q", volumeString)
}
if count < 0 {
return 0, fmt.Errorf("negative values are not allowed")
}
return count, nil
}
func (plugin *glusterfsPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
return plugin.newDeleterInternal(spec)
}
@ -849,6 +863,7 @@ func parseClassParameters(params map[string]string, kubeClient clientset.Interfa
cfg.gidMax = defaultGidMax
authEnabled := true
parseVolumeType := ""
for k, v := range params {
switch dstrings.ToLower(k) {
case "resturl":
@ -891,6 +906,9 @@ func parseClassParameters(params map[string]string, kubeClient clientset.Interfa
return nil, fmt.Errorf("glusterfs: gidMax must be <= %v", absoluteGidMax)
}
cfg.gidMax = parseGidMax
case "volumetype":
parseVolumeType = v
default:
return nil, fmt.Errorf("glusterfs: invalid option %q for volume plugin %s", k, glusterfsPluginName)
}
@ -900,6 +918,42 @@ func parseClassParameters(params map[string]string, kubeClient clientset.Interfa
return nil, fmt.Errorf("StorageClass for provisioner %s must contain 'resturl' parameter", glusterfsPluginName)
}
if len(parseVolumeType) == 0 {
cfg.volumeType = gapi.VolumeDurabilityInfo{Type: gapi.DurabilityReplicate, Replicate: gapi.ReplicaDurability{Replica: replicaCount}}
} else {
parseVolumeTypeInfo := dstrings.Split(parseVolumeType, ":")
switch parseVolumeTypeInfo[0] {
case "replicate":
if len(parseVolumeTypeInfo) >= 2 {
newReplicaCount, err := convertVolumeParam(parseVolumeTypeInfo[1])
if err != nil {
return nil, fmt.Errorf("error [%v] when parsing value %q of option '%s' for volume plugin %s.", err, parseVolumeTypeInfo[1], "volumetype", glusterfsPluginName)
}
cfg.volumeType = gapi.VolumeDurabilityInfo{Type: gapi.DurabilityReplicate, Replicate: gapi.ReplicaDurability{Replica: newReplicaCount}}
} else {
cfg.volumeType = gapi.VolumeDurabilityInfo{Type: gapi.DurabilityReplicate, Replicate: gapi.ReplicaDurability{Replica: replicaCount}}
}
case "disperse":
if len(parseVolumeTypeInfo) >= 3 {
newDisperseData, err := convertVolumeParam(parseVolumeTypeInfo[1])
if err != nil {
return nil, fmt.Errorf("error [%v] when parsing value %q of option '%s' for volume plugin %s.", parseVolumeTypeInfo[1], err, "volumetype", glusterfsPluginName)
}
newDisperseRedundancy, err := convertVolumeParam(parseVolumeTypeInfo[2])
if err != nil {
return nil, fmt.Errorf("error [%v] when parsing value %q of option '%s' for volume plugin %s.", err, parseVolumeTypeInfo[2], "volumetype", glusterfsPluginName)
}
cfg.volumeType = gapi.VolumeDurabilityInfo{Type: gapi.DurabilityEC, Disperse: gapi.DisperseDurability{Data: newDisperseData, Redundancy: newDisperseRedundancy}}
} else {
return nil, fmt.Errorf("StorageClass for provisioner %q must have data:redundancy count set for disperse volumes in storage class option '%s'", glusterfsPluginName, "volumetype")
}
case "none":
cfg.volumeType = gapi.VolumeDurabilityInfo{Type: gapi.DurabilityDistributeOnly}
default:
return nil, fmt.Errorf("error parsing value for option 'volumetype' for volume plugin %s", glusterfsPluginName)
}
}
if !authEnabled {
cfg.user = ""
cfg.secretName = ""

View File

@ -22,6 +22,7 @@ import (
"reflect"
"testing"
gapi "github.com/heketi/heketi/pkg/glusterfs/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
@ -247,7 +248,6 @@ func TestParseClassParameters(t *testing.T) {
"data": []byte("mypassword"),
},
}
tests := []struct {
name string
parameters map[string]string
@ -271,6 +271,7 @@ func TestParseClassParameters(t *testing.T) {
secretValue: "password",
gidMin: 2000,
gidMax: 2147483647,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 3}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
@ -291,6 +292,7 @@ func TestParseClassParameters(t *testing.T) {
secretValue: "mypassword",
gidMin: 2000,
gidMax: 2147483647,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 3}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
@ -302,9 +304,10 @@ func TestParseClassParameters(t *testing.T) {
&secret,
false, // expect error
&provisioningConfig{
url: "https://localhost:8080",
gidMin: 2000,
gidMax: 2147483647,
url: "https://localhost:8080",
gidMin: 2000,
gidMax: 2147483647,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 3}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
@ -438,9 +441,10 @@ func TestParseClassParameters(t *testing.T) {
&secret,
false, // expect error
&provisioningConfig{
url: "https://localhost:8080",
gidMin: 4000,
gidMax: 2147483647,
url: "https://localhost:8080",
gidMin: 4000,
gidMax: 2147483647,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 3}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
@ -453,9 +457,10 @@ func TestParseClassParameters(t *testing.T) {
&secret,
false, // expect error
&provisioningConfig{
url: "https://localhost:8080",
gidMin: 2000,
gidMax: 5000,
url: "https://localhost:8080",
gidMin: 2000,
gidMax: 5000,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 3}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
@ -469,11 +474,94 @@ func TestParseClassParameters(t *testing.T) {
&secret,
false, // expect error
&provisioningConfig{
url: "https://localhost:8080",
gidMin: 4000,
gidMax: 5000,
url: "https://localhost:8080",
gidMin: 4000,
gidMax: 5000,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 3}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
"valid volumetype: replicate",
map[string]string{
"resturl": "https://localhost:8080",
"restauthenabled": "false",
"gidMin": "4000",
"gidMax": "5000",
"volumetype": "replicate:4",
},
&secret,
false, // expect error
&provisioningConfig{
url: "https://localhost:8080",
gidMin: 4000,
gidMax: 5000,
volumeType: gapi.VolumeDurabilityInfo{Type: "replicate", Replicate: gapi.ReplicaDurability{Replica: 4}, Disperse: gapi.DisperseDurability{Data: 0, Redundancy: 0}},
},
},
{
"valid volumetype: disperse",
map[string]string{
"resturl": "https://localhost:8080",
"restauthenabled": "false",
"gidMin": "4000",
"gidMax": "5000",
"volumetype": "disperse:4:2",
},
&secret,
false, // expect error
&provisioningConfig{
url: "https://localhost:8080",
gidMin: 4000,
gidMax: 5000,
volumeType: gapi.VolumeDurabilityInfo{Type: "disperse", Replicate: gapi.ReplicaDurability{Replica: 0}, Disperse: gapi.DisperseDurability{Data: 4, Redundancy: 2}},
},
},
{
"invalid volumetype (disperse) parameter",
map[string]string{
"resturl": "https://localhost:8080",
"restauthenabled": "false",
"volumetype": "disperse:4:asd",
},
&secret,
true, // expect error
nil,
},
{
"invalid volumetype (replicate) parameter",
map[string]string{
"resturl": "https://localhost:8080",
"restauthenabled": "false",
"volumetype": "replicate:asd",
},
&secret,
true, // expect error
nil,
},
{
"invalid volumetype: unknown volumetype",
map[string]string{
"resturl": "https://localhost:8080",
"restauthenabled": "false",
"volumetype": "dispersereplicate:4:2",
},
&secret,
true, // expect error
nil,
},
{
"invalid volumetype : negative value",
map[string]string{
"resturl": "https://localhost:8080",
"restauthenabled": "false",
"volumetype": "replicate:-1000",
},
&secret,
true, // expect error
nil,
},
}
for _, test := range tests {