mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
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:
commit
7171f6fd5f
@ -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.
|
||||
|
@ -11,3 +11,4 @@ parameters:
|
||||
secretName: "heketi-secret"
|
||||
gidMin: "40000"
|
||||
gidMax: "50000"
|
||||
volumetype: "replicate:3"
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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 = ""
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user