Skip to content
This repository has been archived by the owner on Aug 14, 2021. It is now read-only.

Commit

Permalink
Merge pull request #84 from wongma7/mountoptions
Browse files Browse the repository at this point in the history
Add mountOptions StorageClass parameter for NFS
  • Loading branch information
wongma7 authored Apr 26, 2017
2 parents de00fb4 + 35cf999 commit 55099f7
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 33 deletions.
1 change: 1 addition & 0 deletions nfs/docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
82 changes: 56 additions & 26 deletions nfs/pkg/volume/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -172,19 +176,22 @@ var _ controller.Provisioner = &nfsProvisioner{}
// Provision creates a volume i.e. the storage asset and returns a PV object for
// the volume.
func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
server, path, supGroup, exportBlock, exportID, projectBlock, projectID, err := p.createVolume(options)
volume, err := p.createVolume(options)
if err != nil {
return nil, err
}

annotations := make(map[string]string)
annotations[annCreatedBy] = createdBy
annotations[annExportBlock] = exportBlock
annotations[annExportID] = strconv.FormatUint(uint64(exportID), 10)
annotations[annProjectBlock] = projectBlock
annotations[annProjectID] = strconv.FormatUint(uint64(projectID), 10)
if supGroup != 0 {
annotations[VolumeGidAnnotationKey] = strconv.FormatUint(supGroup, 10)
annotations[annExportBlock] = volume.exportBlock
annotations[annExportID] = strconv.FormatUint(uint64(volume.exportID), 10)
annotations[annProjectBlock] = volume.projectBlock
annotations[annProjectID] = strconv.FormatUint(uint64(volume.projectID), 10)
if volume.supGroup != 0 {
annotations[VolumeGidAnnotationKey] = strconv.FormatUint(volume.supGroup, 10)
}
if volume.mountOptions != "" {
annotations[MountOptionAnnotation] = volume.mountOptions
}
annotations[annProvisionerID] = string(p.identity)

Expand All @@ -202,8 +209,8 @@ func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
},
PersistentVolumeSource: v1.PersistentVolumeSource{
NFS: &v1.NFSVolumeSource{
Server: server,
Path: path,
Server: volume.server,
Path: volume.path,
ReadOnly: false,
},
},
Expand All @@ -213,46 +220,67 @@ func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
return pv, nil
}

type volume struct {
server string
path string
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
// directory under /export and exports it. Returns the server IP, the path, a
// zero/non-zero supplemental group, the block it added to either the ganesha
// config or /etc/exports, and the exportID
// TODO return values
func (p *nfsProvisioner) createVolume(options controller.VolumeOptions) (string, string, uint64, string, uint16, string, uint16, error) {
gid, err := p.validateOptions(options)
func (p *nfsProvisioner) createVolume(options controller.VolumeOptions) (volume, error) {
gid, mountOptions, err := p.validateOptions(options)
if err != nil {
return "", "", 0, "", 0, "", 0, fmt.Errorf("error validating options for volume: %v", err)
return volume{}, fmt.Errorf("error validating options for volume: %v", err)
}

server, err := p.getServer()
if err != nil {
return "", "", 0, "", 0, "", 0, fmt.Errorf("error getting NFS server IP for volume: %v", err)
return volume{}, fmt.Errorf("error getting NFS server IP for volume: %v", err)
}

path := path.Join(p.exportDir, options.PVName)

err = p.createDirectory(options.PVName, gid)
if err != nil {
return "", "", 0, "", 0, "", 0, fmt.Errorf("error creating directory for volume: %v", err)
return volume{}, fmt.Errorf("error creating directory for volume: %v", err)
}

exportBlock, exportID, err := p.createExport(options.PVName)
if err != nil {
os.RemoveAll(path)
return "", "", 0, "", 0, "", 0, fmt.Errorf("error creating export for volume: %v", err)
return volume{}, fmt.Errorf("error creating export for volume: %v", err)
}

projectBlock, projectID, err := p.createQuota(options.PVName, options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)])
if err != nil {
os.RemoveAll(path)
return "", "", 0, "", 0, "", 0, fmt.Errorf("error creating quota for volume: %v", err)
}

return server, path, 0, exportBlock, exportID, projectBlock, projectID, nil
return volume{}, fmt.Errorf("error creating quota for volume: %v", err)
}

return volume{
server: server,
path: path,
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":
Expand All @@ -261,32 +289,34 @@ 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)
}
}

// TODO implement options.ProvisionerSelector parsing
// 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.
Expand Down
23 changes: 16 additions & 7 deletions nfs/pkg/volume/provision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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",
Expand All @@ -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")
}
Expand Down

0 comments on commit 55099f7

Please sign in to comment.