From 35cf9991084d459bf81dd47859d11a13b70372c9 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Tue, 25 Apr 2017 16:25:12 -0400 Subject: [PATCH] Add mountOptions StorageClass parameter --- nfs/docs/usage.md | 1 + nfs/pkg/volume/provision.go | 32 ++++++++++++++++++++++---------- nfs/pkg/volume/provision_test.go | 23 ++++++++++++++++------- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/nfs/docs/usage.md b/nfs/docs/usage.md index 376c21d8371..63371275795 100644 --- a/nfs/docs/usage.md +++ b/nfs/docs/usage.md @@ -6,6 +6,7 @@ Edit the `provisioner` field in `deploy/kubernetes/class.yaml` to be the provisi ### Parameters * `gid`: `"none"` or a [supplemental group](http://kubernetes.io/docs/user-guide/security-context/) like `"1001"`. NFS shares will be created with permissions such that only pods running with the supplemental group can read & write to the share. Or if `"none"`, anybody can write to the share. This will only work in conjunction with the `root-squash` flag set true. Default (if omitted) `"none"`. +* `mountOptions`: a comma separated list of [mount options](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options) for every PV of this class to be mounted with. The list is inserted directly into every PV's mount options annotation/field without any validation. Default blank `""`. Name the `StorageClass` however you like; the name is how claims will request this class. Create the class. diff --git a/nfs/pkg/volume/provision.go b/nfs/pkg/volume/provision.go index d05d7bda854..37e7aa25540 100644 --- a/nfs/pkg/volume/provision.go +++ b/nfs/pkg/volume/provision.go @@ -63,6 +63,10 @@ const ( // object that specifies a supplemental GID. VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid" + // MountOptionAnnotation is the annotation on a PV object that specifies a + // comma separated list of mount options + MountOptionAnnotation = "volume.beta.kubernetes.io/mount-options" + // A PV annotation for the identity of the nfsProvisioner that provisioned it annProvisionerID = "Provisioner_Id" @@ -186,6 +190,9 @@ func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis if volume.supGroup != 0 { annotations[VolumeGidAnnotationKey] = strconv.FormatUint(volume.supGroup, 10) } + if volume.mountOptions != "" { + annotations[MountOptionAnnotation] = volume.mountOptions + } annotations[annProvisionerID] = string(p.identity) pv := &v1.PersistentVolume{ @@ -216,11 +223,12 @@ func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis type volume struct { server string path string - supGroup uint64 exportBlock string exportID uint16 projectBlock string projectID uint16 + supGroup uint64 + mountOptions string } // createVolume creates a volume i.e. the storage asset. It creates a unique @@ -229,7 +237,7 @@ type volume struct { // config or /etc/exports, and the exportID // TODO return values func (p *nfsProvisioner) createVolume(options controller.VolumeOptions) (volume, error) { - gid, err := p.validateOptions(options) + gid, mountOptions, err := p.validateOptions(options) if err != nil { return volume{}, fmt.Errorf("error validating options for volume: %v", err) } @@ -261,16 +269,18 @@ func (p *nfsProvisioner) createVolume(options controller.VolumeOptions) (volume, return volume{ server: server, path: path, - supGroup: 0, exportBlock: exportBlock, exportID: exportID, projectBlock: projectBlock, projectID: projectID, + supGroup: 0, + mountOptions: mountOptions, }, nil } -func (p *nfsProvisioner) validateOptions(options controller.VolumeOptions) (string, error) { +func (p *nfsProvisioner) validateOptions(options controller.VolumeOptions) (string, string, error) { gid := "none" + mountOptions := "" for k, v := range options.Parameters { switch strings.ToLower(k) { case "gid": @@ -279,10 +289,12 @@ func (p *nfsProvisioner) validateOptions(options controller.VolumeOptions) (stri } else if i, err := strconv.ParseUint(v, 10, 64); err == nil && i != 0 { gid = v } else { - return "", fmt.Errorf("invalid value for parameter gid: %v. valid values are: 'none' or a non-zero integer", v) + return "", "", fmt.Errorf("invalid value for parameter gid: %v. valid values are: 'none' or a non-zero integer", v) } + case "mountoptions": + mountOptions = v default: - return "", fmt.Errorf("invalid parameter: %q", k) + return "", "", fmt.Errorf("invalid parameter: %q", k) } } @@ -290,21 +302,21 @@ func (p *nfsProvisioner) validateOptions(options controller.VolumeOptions) (stri // pv.Labels MUST be set to match claim.spec.selector // gid selector? with or without pv annotation? if options.PVC.Spec.Selector != nil { - return "", fmt.Errorf("claim.Spec.Selector is not supported") + return "", "", fmt.Errorf("claim.Spec.Selector is not supported") } var stat syscall.Statfs_t if err := syscall.Statfs(p.exportDir, &stat); err != nil { - return "", fmt.Errorf("error calling statfs on %v: %v", p.exportDir, err) + return "", "", fmt.Errorf("error calling statfs on %v: %v", p.exportDir, err) } capacity := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] requestBytes := capacity.Value() available := int64(stat.Bavail) * int64(stat.Bsize) if requestBytes > available { - return "", fmt.Errorf("insufficient available space %v bytes to satisfy claim for %v bytes", available, requestBytes) + return "", "", fmt.Errorf("insufficient available space %v bytes to satisfy claim for %v bytes", available, requestBytes) } - return gid, nil + return gid, mountOptions, nil } // getServer gets the server IP to put in a provisioned PV's spec. diff --git a/nfs/pkg/volume/provision_test.go b/nfs/pkg/volume/provision_test.go index 58bd5029fd7..0e44d8a5751 100644 --- a/nfs/pkg/volume/provision_test.go +++ b/nfs/pkg/volume/provision_test.go @@ -161,13 +161,13 @@ func TestCreateVolume(t *testing.T) { for _, test := range tests { os.Setenv(test.envKey, "1.1.1.1") - server, path, supGroup, block, exportID, _, _, err := p.createVolume(test.options) + volume, err := p.createVolume(test.options) - evaluate(t, test.name, test.expectError, err, test.expectedServer, server, "server") - evaluate(t, test.name, test.expectError, err, test.expectedPath, path, "path") - evaluate(t, test.name, test.expectError, err, test.expectedGroup, supGroup, "group") - evaluate(t, test.name, test.expectError, err, test.expectedBlock, block, "block") - evaluate(t, test.name, test.expectError, err, test.expectedExportID, exportID, "export id") + evaluate(t, test.name, test.expectError, err, test.expectedServer, volume.server, "server") + evaluate(t, test.name, test.expectError, err, test.expectedPath, volume.path, "path") + evaluate(t, test.name, test.expectError, err, test.expectedGroup, volume.supGroup, "group") + evaluate(t, test.name, test.expectError, err, test.expectedBlock, volume.exportBlock, "block") + evaluate(t, test.name, test.expectError, err, test.expectedExportID, volume.exportID, "export id") os.Unsetenv(test.envKey) } @@ -234,6 +234,15 @@ func TestValidateOptions(t *testing.T) { expectedGid: "", expectError: true, }, + { + name: "mount options parameter key", + options: controller.VolumeOptions{ + Parameters: map[string]string{"mountOptions": "asdf"}, + PVC: newClaim(resource.MustParse("1Ki"), nil, nil), + }, + expectedGid: "none", + expectError: false, + }, // TODO implement options.ProvisionerSelector parsing { name: "non-nil selector", @@ -257,7 +266,7 @@ func TestValidateOptions(t *testing.T) { p := newNFSProvisionerInternal(tmpDir+"/", client, false, &testExporter{}, newDummyQuotaer(), "") for _, test := range tests { - gid, err := p.validateOptions(test.options) + gid, _, err := p.validateOptions(test.options) evaluate(t, test.name, test.expectError, err, test.expectedGid, gid, "gid") }