Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support access points on the same file system #185

Merged
merged 1 commit into from
Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions examples/kubernetes/access_points/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ In this case, the separation is managed on the EFS side rather than the kubernet

### Create Access Points (in EFS)
Following [this doc](https://docs.aws.amazon.com/efs/latest/ug/create-access-point.html), create a separate access point for each independent data store you wish to expose in your cluster, tailoring the ownership and permissions as desired.

**Note**: Until [issue #167](https://github.com/kubernetes-sigs/aws-efs-csi-driver/issues/167) is resolved, you must use a separate file system for each access point if they are to be mounted in the same pod.
There is no need to use different EFS volumes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this sentence seems out of place now, i would remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this is actually the crux of the change. Previously, if you wanted multiple shared stores in the same pod, you had to use separate volumes; and if you didn't, things broke in mysterious ways. Now this is possible, and it may be quite desirable for consumers to avoid the extra overhead.

Is there some way I can rephrase to make that clearer, or somewhere else I can put it so the context is better?


**Note**: Although it is possible to [configure IAM policies for access points](https://docs.aws.amazon.com/efs/latest/ug/efs-access-points.html#access-points-iam-policy), by default no additional IAM permissions are necessary.

Expand All @@ -28,12 +27,9 @@ spec:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: efs-sc
mountOptions:
- tls
- accesspoint=[AccessPointId]
csi:
driver: efs.csi.aws.com
volumeHandle: [FileSystemId]
volumeHandle: [FileSystemId]::[AccessPointId]
---
apiVersion: v1
kind: PersistentVolume
Expand All @@ -47,20 +43,20 @@ spec:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: efs-sc
mountOptions:
- tls
- accesspoint=[AccessPointId]
csi:
driver: efs.csi.aws.com
volumeHandle: [FileSystemId]
volumeHandle: [FileSystemId]::[AccessPointId]
```
In each PersistentVolume, replace both the `[FileSystemId]` in `spec.csi.volumeHandle` and the `[AccessPointId]` value of the `accesspoint` option in `spec.mountOptions`.
You can find these values using the AWS CLI:
```sh
>> aws efs describe-access-points --query 'AccessPoints[*].{"FileSystemId": FileSystemId, "AccessPointId": AccessPointId}'
```
If you are using the same underlying EFS volume, the `FileSystemId` will be the same in both PersistentVolume specs, but the `AccessPointId` will differ.

**Note**: Until [issue #167](https://github.com/kubernetes-sigs/aws-efs-csi-driver/issues/167) is resolved, both the `FileSystemId` and the `AccessPointId` must be unique in each PersistentVolume.
**Note:** The double colon in the `volumeHandle` is intentional.
The middle field indicates the subpath; if omitted, no subpath is used.
See [below](#other-details) for more information.

### Deploy the Example Application
Create PVs, persistent volume claims (PVCs), and storage class:
Expand All @@ -81,3 +77,24 @@ Also you can verify that data is written into the EFS filesystems:
>> kubectl exec -ti efs-app -- tail -f /data-dir1/out.txt
>> kubectl exec -ti efs-app -- ls /data-dir2
```

### Other Details
- Using access points via `volumeHandle` automatically adds the `tls` mount option.
- It is **deprecated** to specify access points via `mountOptions`, e.g.
```
spec:
mountOptions:
- tls
- accesspoint=fsap-068c22f0246419f75
```
as this could subject you to
[issue #167](https://github.com/kubernetes-sigs/aws-efs-csi-driver/issues/167).

- It is possible to use a combination of [volume path](../volume_path) and access points
with a `volumeHandle` of the form `[FileSystemId]:[Subpath]:[AccessPointId]`, e.g.
`volumeHandle: fs-e8a95a42:/my/subpath:fsap-19f752f0068c22464`. In this case:
- The `[Subpath]` will be _under_ the configured access point directory. For example,
if you configured your access point with `/ap1`, the above would mount to
`/ap1/my/subpath`.
- As with normal volume path, the `[Subpath]` must already exist prior to consuming
the volume from a pod.
10 changes: 2 additions & 8 deletions examples/kubernetes/access_points/specs/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ spec:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: efs-sc
mountOptions:
- tls
- accesspoint=fsap-068c22f0246419f75
csi:
driver: efs.csi.aws.com
volumeHandle: fs-e8a95a42
volumeHandle: fs-e8a95a42::fsap-068c22f0246419f75
---
apiVersion: v1
kind: PersistentVolume
Expand All @@ -35,12 +32,9 @@ spec:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: efs-sc
mountOptions:
- tls
- accesspoint=fsap-19f752f0068c22464
csi:
driver: efs.csi.aws.com
volumeHandle: fs-2e48aa59
volumeHandle: fs-e8a95a42::fsap-19f752f0068c22464
---
apiVersion: v1
kind: PersistentVolumeClaim
Expand Down
103 changes: 89 additions & 14 deletions pkg/driver/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (d *Driver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolu

func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
klog.V(4).Infof("NodePublishVolume: called with args %+v", req)
mountOptions := []string{}

target := req.GetTargetPath()
if len(target) == 0 {
Expand Down Expand Up @@ -80,23 +81,27 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
}
}

volumeId := req.GetVolumeId()
if !isValidFileSystemId(volumeId) {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("volume ID %s is invalid", volumeId))
fsid, vpath, apid, err := parseVolumeId(req.GetVolumeId())
if err != nil {
// parseVolumeId returns the appropriate error
return nil, err
}

var source string
tokens := strings.Split(volumeId, ":")
if len(tokens) == 1 {
// fs-xxxxxx
source = fmt.Sprintf("%s:%s", volumeId, subpath)
} else if len(tokens) == 2 {
// fs-xxxxxx:/a/b/c
cleanPath := path.Clean(tokens[1])
source = fmt.Sprintf("%s:%s", tokens[0], cleanPath)
// The `vpath` takes precedence if specified. If not specified, we'll either use the
// (deprecated) `path` from the volContext, or default to "/" from above.
if vpath != "" {
subpath = vpath
}
source := fmt.Sprintf("%s:%s", fsid, subpath)

// If an access point was specified, we need to include two things in the mountOptions:
// - The access point ID, properly prefixed. (Below, we'll check whether an access point was
// also specified in the incoming mount options and react appropriately.)
// - The TLS option. Access point mounts won't work without it. (For ease of use, we won't
// require this to be present in the mountOptions already, but we won't complain if it is.)
if apid != "" {
mountOptions = append(mountOptions, fmt.Sprintf("accesspoint=%s", apid), "tls")
}

mountOptions := []string{}
if req.GetReadonly() {
mountOptions = append(mountOptions, "ro")
}
Expand All @@ -111,6 +116,26 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
return false
}
for _, f := range m.MountFlags {
// Special-case check for access point
// Not sure if `accesspoint` is allowed to have mixed case, but this shouldn't hurt,
// and it simplifies both matches (HasPrefix, hasOption) below.
f = strings.ToLower(f)
if strings.HasPrefix(f, "accesspoint=") {
// The MountOptions Access Point ID
moapid := f[12:]
// No matter what, warn that this is not the right way to specify an access point
klog.Warning(fmt.Sprintf(
"Use of 'accesspoint' under mountOptions is deprecated with this driver. "+
"Specify the access point in the volumeHandle instead, e.g. 'volumeHandle: %s:%s:%s'",
fsid, subpath, moapid))
// If they specified the same access point in both places, let it slide; otherwise, fail.
if apid != "" && moapid != apid {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf(
"Found conflicting access point IDs in mountOptions (%s) and volumeHandle (%s)", moapid, apid))
}
// Fall through; the code below will uniq for us.
}

if !hasOption(mountOptions, f) {
mountOptions = append(mountOptions, f)
}
Expand Down Expand Up @@ -217,6 +242,56 @@ func (d *Driver) isValidVolumeCapabilities(volCaps []*csi.VolumeCapability) bool
return foundAll
}

// parseVolumeId accepts a NodePublishVolumeRequest.VolumeId as a colon-delimited string of the
// form `{fileSystemID}:{mountPath}:{accessPointID}`.
// - The `{fileSystemID}` is required, and expected to be of the form `fs-...`.
// - The other two fields are optional -- they may be empty or omitted entirely. For example,
// `fs-abcd1234::`, `fs-abcd1234:`, and `fs-abcd1234` are equivalent.
// - The `{mountPath}`, if specified, is not required to be absolute.
// - The `{accessPointID}` is expected to be of the form `fsap-...`.
// parseVolumeId returns the parsed values, of which `subpath` and `apid` may be empty; and an
// error, which will be a `status.Error` with `codes.InvalidArgument`, or `nil` if the `volumeId`
// was parsed successfully.
// See the following issues for some background:
// - https://github.com/kubernetes-sigs/aws-efs-csi-driver/issues/100
// - https://github.com/kubernetes-sigs/aws-efs-csi-driver/issues/167
func parseVolumeId(volumeId string) (fsid, subpath, apid string, err error) {
// Might as well do this up front, since the FSID is required and first in the string
if !isValidFileSystemId(volumeId) {
err = status.Error(codes.InvalidArgument, fmt.Sprintf("volume ID '%s' is invalid: Expected a file system ID of the form 'fs-...'", volumeId))
return
}

tokens := strings.Split(volumeId, ":")
if len(tokens) > 3 {
err = status.Error(codes.InvalidArgument, fmt.Sprintf("volume ID '%s' is invalid: Expected at most three fields separated by ':'", volumeId))
return
}

// Okay, we know we have a FSID
fsid = tokens[0]

// Do we have a subpath?
if len(tokens) >= 2 && tokens[1] != "" {
subpath = path.Clean(tokens[1])
}

// Do we have an access point ID?
if len(tokens) == 3 && tokens[2] != "" {
apid = tokens[2]
if !isValidAccessPointId(apid) {
err = status.Error(codes.InvalidArgument, fmt.Sprintf("volume ID '%s' has an invalid access point ID '%s': Expected it to be of the form 'fsap-...'", volumeId, apid))
return
}
}

return
}

func isValidFileSystemId(filesystemId string) bool {
return strings.HasPrefix(filesystemId, "fs-")
}

func isValidAccessPointId(accesspointId string) bool {
return strings.HasPrefix(accesspointId, "fsap-")
}
Loading