Skip to content

Commit d308dc2

Browse files
mooneksilvacarloss
authored andcommitted
[manila-csi-plugin] support muilple share rules
Signed-off-by: moonek <[email protected]> Signed-off-by: Carlos da Silva <[email protected]>
1 parent 41a6359 commit d308dc2

File tree

10 files changed

+150
-97
lines changed

10 files changed

+150
-97
lines changed

docs/manila-csi-plugin/using-manila-csi-plugin.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ Parameter | Required | Description
6060
`cephfs-mounter` | _no_ | Relevant for CephFS Manila shares. Specifies which mounting method to use with the CSI CephFS driver. Available options are `kernel` and `fuse`, defaults to `fuse`. See [CSI CephFS docs](https://github.com/ceph/ceph-csi/blob/csi-v1.0/docs/deploy-cephfs.md#configuration) for further information.
6161
`cephfs-kernelMountOptions` | _no_ | Relevant for CephFS Manila shares. Specifies mount options for CephFS kernel client. See [CSI CephFS docs](https://github.com/ceph/ceph-csi/blob/csi-v1.0/docs/deploy-cephfs.md#configuration) for further information.
6262
`cephfs-fuseMountOptions` | _no_ | Relevant for CephFS Manila shares. Specifies mount options for CephFS FUSE client. See [CSI CephFS docs](https://github.com/ceph/ceph-csi/blob/csi-v1.0/docs/deploy-cephfs.md#configuration) for further information.
63-
`cephfs-clientID` | _no_ | Relevant for CephFS Manila shares. Specifies the cephx client ID when creating an access rule for the provisioned share. The same cephx client ID may be shared with multiple Manila shares. If no value is provided, client ID for the provisioned Manila share will be set to some unique value (PersistentVolume name).
64-
`nfs-shareClient` | _no_ | Relevant for NFS Manila shares. Specifies what address has access to the NFS share. Defaults to `0.0.0.0/0`, i.e. anyone.
63+
`cephfs-clientID` | _no_ | Relevant for CephFS Manila shares. Specifies the cephx client ID when creating an access rule for the provisioned share. The same cephx client ID may be shared with multiple Manila shares. If providing access to multiple cephx client IDs, set it as a comma separated list. If no value is provided, client ID for the provisioned Manila share will be set to some unique value (PersistentVolume name).
64+
`nfs-shareClient` | _no_ | Relevant for NFS Manila shares. Specifies what address has access to the NFS share. Use a comma separated list for granting access to multiple IP addresses or subnets. Defaults to `0.0.0.0/0`, i.e. anyone.
6565

6666
### Node Service volume context
6767

@@ -71,7 +71,7 @@ Parameter | Required | Description
7171
----------|----------|------------
7272
`shareID` | if `shareName` is not given | The UUID of the share
7373
`shareName` | if `shareID` is not given | The name of the share
74-
`shareAccessID` | _yes_ | The UUID of the access rule for the share
74+
`shareAccessIDs` | _yes_ | Comma separated UUIDs of access rules for the share
7575
`cephfs-mounter` | _no_ | Relevant for CephFS Manila shares. Specifies which mounting method to use with the CSI CephFS driver. Available options are `kernel` and `fuse`, defaults to `fuse`. See [CSI CephFS docs](https://github.com/ceph/ceph-csi/blob/csi-v1.0/docs/deploy-cephfs.md#configuration) for further information.
7676
`cephfs-kernelMountOptions` | _no_ | Relevant for CephFS Manila shares. Specifies mount options for CephFS kernel client. See [CSI CephFS docs](https://github.com/ceph/ceph-csi/blob/csi-v1.0/docs/deploy-cephfs.md#configuration) for further information.
7777
`cephfs-fuseMountOptions` | _no_ | Relevant for CephFS Manila shares. Specifies mount options for CephFS FUSE client. See [CSI CephFS docs](https://github.com/ceph/ceph-csi/blob/csi-v1.0/docs/deploy-cephfs.md#configuration) for further information.

examples/manila-csi-plugin/nfs/static-provisioning/preprovisioned-pvc.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ spec:
2020
namespace: default
2121
volumeAttributes:
2222
shareID: SHARE-UUID-GOES-HERE
23-
shareAccessID: ACCESS-UUID-OF-THE-SHARE
23+
shareAccessIDs: COMMA-SEPARATED-ACCESS-UUIDS-OF-THE-SHARE
2424
---
2525
apiVersion: v1
2626
kind: PersistentVolumeClaim

pkg/csi/manila/adapters.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package manila
1919
import (
2020
"strings"
2121

22+
"github.com/gophercloud/gophercloud/v2/openstack/sharedfilesystems/v2/shares"
23+
"k8s.io/cloud-provider-openstack/pkg/csi/manila/options"
2224
"k8s.io/cloud-provider-openstack/pkg/csi/manila/shareadapters"
2325
"k8s.io/klog/v2"
2426
)
@@ -35,3 +37,40 @@ func getShareAdapter(proto string) shareadapters.ShareAdapter {
3537

3638
return nil
3739
}
40+
41+
func getAccessIDs(shareOpts *options.NodeVolumeContext) []string {
42+
if shareOpts.ShareAccessIDs != "" {
43+
// Split by comma if multiple
44+
return strings.Split(shareOpts.ShareAccessIDs, ",")
45+
} else if shareOpts.ShareAccessID != "" {
46+
// Backwards compatibility: treat as single-element list
47+
return []string{shareOpts.ShareAccessID}
48+
}
49+
return nil
50+
}
51+
52+
func getAccessRightBasedOnShareAdapter(shareAdapter shareadapters.ShareAdapter, accessRights []shares.AccessRight, shareOpts *options.NodeVolumeContext) (accessRight *shares.AccessRight) {
53+
switch shareAdapter.(type) {
54+
case *shareadapters.Cephfs:
55+
shareAccessIDs := getAccessIDs(shareOpts)
56+
for _, accessRightID := range shareAccessIDs {
57+
for _, accessRight := range accessRights {
58+
if accessRight.ID == accessRightID {
59+
// TODO: we should add support for getting the node's own IP or Ceph
60+
// user to avoid unnecessary access rights processing. All the node
61+
// needs is one cephx user/key to mount the share, so we can return
62+
// the first access right that matches the share access IDs list.
63+
return &accessRight
64+
}
65+
}
66+
}
67+
klog.Fatalf("failed to find access rights %s for cephfs share", shareAccessIDs)
68+
case *shareadapters.NFS:
69+
// For NFS, we don't need to use an access right specifically. The controller is
70+
// already making sure the access rules are properly created.
71+
return nil
72+
default:
73+
klog.Fatalf("unknown share adapter type %T", shareAdapter)
74+
}
75+
return nil
76+
}

pkg/csi/manila/controllerserver.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,18 +194,24 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
194194

195195
ad := getShareAdapter(shareOpts.Protocol)
196196

197-
accessRight, err := ad.GetOrGrantAccess(ctx, &shareadapters.GrantAccessArgs{Share: share, ManilaClient: manilaClient, Options: shareOpts})
197+
accessRights, err := ad.GetOrGrantAccesses(ctx, &shareadapters.GrantAccessArgs{Share: share, ManilaClient: manilaClient, Options: shareOpts})
198198
if err != nil {
199199
if wait.Interrupted(err) {
200-
return nil, status.Errorf(codes.DeadlineExceeded, "deadline exceeded while waiting for access rule %s for volume %s to become available", accessRight.ID, share.Name)
200+
return nil, status.Errorf(codes.DeadlineExceeded, "deadline exceeded while waiting for access rules for volume %s to become available", share.Name)
201201
}
202202

203203
return nil, status.Errorf(codes.Internal, "failed to grant access to volume %s: %v", share.Name, err)
204204
}
205205

206+
var accessRightIDs []string
207+
for _, ar := range accessRights {
208+
accessRightIDs = append(accessRightIDs, ar.ID)
209+
}
210+
shareAccessIDs := strings.Join(accessRightIDs, ",")
211+
206212
volCtx := filterParametersForVolumeContext(params, options.NodeVolumeContextFields())
207213
volCtx = util.SetMapIfNotEmpty(volCtx, "shareID", share.ID)
208-
volCtx = util.SetMapIfNotEmpty(volCtx, "shareAccessID", accessRight.ID)
214+
volCtx = util.SetMapIfNotEmpty(volCtx, "shareAccessIDs", shareAccessIDs)
209215
volCtx = util.SetMapIfNotEmpty(volCtx, "groupID", share.ShareGroupID)
210216
volCtx = util.SetMapIfNotEmpty(volCtx, "affinity", shareOpts.Affinity)
211217
volCtx = util.SetMapIfNotEmpty(volCtx, "antiAffinity", shareOpts.AntiAffinity)

pkg/csi/manila/nodeserver.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,18 +108,6 @@ func (ns *nodeServer) buildVolumeContext(ctx context.Context, volID volumeID, sh
108108
return nil, nil, status.Errorf(codes.Internal, "failed to list access rights for volume %s: %v", volID, err)
109109
}
110110

111-
for i := range accessRights {
112-
if accessRights[i].ID == shareOpts.ShareAccessID {
113-
accessRight = &accessRights[i]
114-
break
115-
}
116-
}
117-
118-
if accessRight == nil {
119-
return nil, nil, status.Errorf(codes.InvalidArgument, "cannot find access right %s for volume %s",
120-
shareOpts.ShareAccessID, volID)
121-
}
122-
123111
// Retrieve list of all export locations for this share.
124112
// Share adapter will try to choose the correct one for mounting.
125113

@@ -131,6 +119,7 @@ func (ns *nodeServer) buildVolumeContext(ctx context.Context, volID volumeID, sh
131119
// Build volume context for fwd plugin
132120

133121
sa := getShareAdapter(ns.d.shareProto)
122+
accessRight = getAccessRightBasedOnShareAdapter(sa, accessRights, shareOpts)
134123
opts := &shareadapters.VolumeContextArgs{
135124
Locations: availableExportLocations,
136125
Options: shareOpts,

pkg/csi/manila/options/shareoptions.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ type ControllerVolumeContext struct {
4141
}
4242

4343
type NodeVolumeContext struct {
44-
ShareID string `name:"shareID" value:"optionalIf:shareName=." precludes:"shareName"`
45-
ShareName string `name:"shareName" value:"optionalIf:shareID=." precludes:"shareID"`
46-
ShareAccessID string `name:"shareAccessID"`
44+
ShareID string `name:"shareID" value:"optionalIf:shareName=." precludes:"shareName"`
45+
ShareName string `name:"shareName" value:"optionalIf:shareID=." precludes:"shareID"`
46+
ShareAccessID string `name:"shareAccessID" value:"optionalIf:shareAccessIDs=." precludes:"shareAccessIDs"` // Keep this for backwards compatibility
47+
ShareAccessIDs string `name:"shareAccessIDs" value:"optionalIf:shareAccessID=." precludes:"shareAccessID"`
4748

4849
// Adapter options
4950

pkg/csi/manila/shareadapters/cephfs.go

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package shareadapters
1919
import (
2020
"context"
2121
"fmt"
22+
"strings"
2223
"time"
2324

2425
"github.com/gophercloud/gophercloud/v2"
@@ -32,82 +33,77 @@ type Cephfs struct{}
3233

3334
var _ ShareAdapter = &Cephfs{}
3435

35-
func (Cephfs) GetOrGrantAccess(ctx context.Context, args *GrantAccessArgs) (accessRight *shares.AccessRight, err error) {
36+
func (Cephfs) GetOrGrantAccesses(ctx context.Context, args *GrantAccessArgs) ([]shares.AccessRight, error) {
3637
// First, check if the access right exists or needs to be created
3738

38-
var rights []shares.AccessRight
39-
40-
accessTo := args.Options.CephfsClientID
41-
if accessTo == "" {
42-
accessTo = args.Share.Name
43-
}
44-
45-
rights, err = args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
39+
rights, err := args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
4640
if err != nil {
4741
if _, ok := err.(gophercloud.ErrResourceNotFound); !ok {
4842
return nil, fmt.Errorf("failed to list access rights: %v", err)
4943
}
50-
} else {
51-
// Try to find the access right
44+
}
5245

53-
for _, r := range rights {
54-
if r.AccessTo == accessTo && r.AccessType == "cephx" && r.AccessLevel == "rw" {
55-
klog.V(4).Infof("cephx access right for share %s already exists", args.Share.Name)
46+
accessToList := []string{args.Share.Name}
47+
if args.Options.CephfsClientID != "" {
48+
accessToList = strings.Split(args.Options.CephfsClientID, ",")
49+
}
5650

57-
accessRight = &r
58-
break
59-
}
51+
// TODO: add support for getting the exact client ID that the node will use.
52+
// For now, we use the first client ID in the list and it should be enough,
53+
// considering our context with the nodes.
54+
accessRightClient := accessToList[0]
55+
var accessRight *shares.AccessRight
56+
57+
// Try to find the access right.
58+
for _, r := range rights {
59+
if r.AccessTo == accessRightClient && r.AccessType == "cephx" && r.AccessLevel == "rw" {
60+
klog.V(4).Infof("cephx access right for share %s already exists", args.Share.Name)
61+
accessRight = &r
62+
break
6063
}
6164
}
6265

66+
// Not found, create it
6367
if accessRight == nil {
64-
// Not found, create it
65-
66-
accessRight, err = args.ManilaClient.GrantAccess(ctx, args.Share.ID, shares.GrantAccessOpts{
68+
result, err := args.ManilaClient.GrantAccess(ctx, args.Share.ID, shares.GrantAccessOpts{
6769
AccessType: "cephx",
6870
AccessLevel: "rw",
69-
AccessTo: accessTo,
71+
AccessTo: accessRightClient,
7072
})
71-
7273
if err != nil {
73-
return
74+
return nil, fmt.Errorf("failed to grant access right: %v", err)
7475
}
75-
}
76-
77-
if accessRight.AccessKey != "" {
78-
// The access right is ready
79-
return
80-
}
81-
82-
// Wait till a ceph key is assigned to the access right
83-
84-
backoff := wait.Backoff{
85-
Duration: time.Second * 5,
86-
Factor: 1.2,
87-
Steps: 10,
88-
}
89-
90-
return accessRight, wait.ExponentialBackoff(backoff, func() (bool, error) {
91-
rights, err := args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
92-
if err != nil {
93-
return false, err
94-
}
95-
96-
var accessRight *shares.AccessRight
97-
98-
for i := range rights {
99-
if rights[i].AccessTo == accessTo {
100-
accessRight = &rights[i]
101-
break
76+
if result.AccessKey == "" {
77+
// Wait till a ceph key is assigned to the access right
78+
backoff := wait.Backoff{
79+
Duration: time.Second * 5,
80+
Factor: 1.2,
81+
Steps: 10,
82+
}
83+
wait_err := wait.ExponentialBackoff(backoff, func() (bool, error) {
84+
rights, err := args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
85+
if err != nil {
86+
return false, fmt.Errorf("error get access rights for share %s: %v", args.Share.ID, err)
87+
}
88+
if len(rights) == 0 {
89+
return false, fmt.Errorf("cannot find the access right we've just created")
90+
}
91+
for _, r := range rights {
92+
if r.AccessTo == accessRightClient && r.AccessKey != "" {
93+
accessRight = &r
94+
return true, nil
95+
}
96+
}
97+
klog.V(4).Infof("Access key for %s is not set yet, retrying...", accessRightClient)
98+
return false, nil
99+
})
100+
if wait_err != nil {
101+
return nil, fmt.Errorf("timed out while attempting to get access rights for share %s: %v", args.Share.ID, err)
102102
}
103103
}
104+
}
105+
return []shares.AccessRight{*accessRight}, nil
104106

105-
if accessRight == nil {
106-
return false, fmt.Errorf("cannot find the access right we've just created")
107-
}
108-
109-
return accessRight.AccessKey != "", nil
110-
})
111107
}
112108

113109
func (Cephfs) BuildVolumeContext(args *VolumeContextArgs) (volumeContext map[string]string, err error) {

pkg/csi/manila/shareadapters/nfs.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type NFS struct{}
3333

3434
var _ ShareAdapter = &NFS{}
3535

36-
func (NFS) GetOrGrantAccess(ctx context.Context, args *GrantAccessArgs) (*shares.AccessRight, error) {
36+
func (NFS) GetOrGrantAccesses(ctx context.Context, args *GrantAccessArgs) ([]shares.AccessRight, error) {
3737
// First, check if the access right exists or needs to be created
3838

3939
rights, err := args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
@@ -43,22 +43,43 @@ func (NFS) GetOrGrantAccess(ctx context.Context, args *GrantAccessArgs) (*shares
4343
}
4444
}
4545

46-
// Try to find the access right
47-
48-
for _, r := range rights {
49-
if r.AccessTo == args.Options.NFSShareClient && r.AccessType == "ip" && r.AccessLevel == "rw" {
50-
klog.V(4).Infof("IP access right for share %s already exists", args.Share.Name)
51-
return &r, nil
46+
accessToList := strings.Split(args.Options.NFSShareClient, ",")
47+
48+
created := false
49+
for _, at := range accessToList {
50+
// Try to find the access right
51+
found := false
52+
for _, r := range rights {
53+
if r.AccessTo == at && r.AccessType == "ip" && r.AccessLevel == "rw" {
54+
klog.V(4).Infof("IP access right %s for share %s already exists", at, args.Share.Name)
55+
found = true
56+
break
57+
}
58+
}
59+
// Not found, create it
60+
if !found {
61+
_, err = args.ManilaClient.GrantAccess(ctx, args.Share.ID, shares.GrantAccessOpts{
62+
AccessType: "ip",
63+
AccessLevel: "rw",
64+
AccessTo: at,
65+
})
66+
if err != nil {
67+
return nil, fmt.Errorf("failed to grant access right: %v", err)
68+
}
69+
created = true
5270
}
5371
}
5472

55-
// Not found, create it
56-
57-
return args.ManilaClient.GrantAccess(ctx, args.Share.ID, shares.GrantAccessOpts{
58-
AccessType: "ip",
59-
AccessLevel: "rw",
60-
AccessTo: args.Options.NFSShareClient,
61-
})
73+
// Search again because access rights have changed
74+
if created {
75+
rights, err = args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
76+
if err != nil {
77+
if _, ok := err.(gophercloud.ErrResourceNotFound); !ok {
78+
return nil, fmt.Errorf("failed to list access rights: %v", err)
79+
}
80+
}
81+
}
82+
return rights, nil
6283
}
6384

6485
func (NFS) BuildVolumeContext(args *VolumeContextArgs) (volumeContext map[string]string, err error) {

pkg/csi/manila/shareadapters/shareadapter.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ type SecretArgs struct {
4343
}
4444

4545
type ShareAdapter interface {
46-
// GetOrGrantAccess first tries to retrieve an access right for args.Share.
47-
// An access right is created for the share in case it doesn't exist yet.
46+
// GetOrGrantAccess first tries to retrieve the list of access rights for args.Share.
47+
// It iterates over the list of access clients that should have access to the share considering nfs-shareClient or cephfs-clientID.
48+
// The access right is created for the share in case it doesn't exist yet.
4849
// Returns an existing or new access right for args.Share.
49-
GetOrGrantAccess(ctx context.Context, args *GrantAccessArgs) (accessRight *shares.AccessRight, err error)
50+
GetOrGrantAccesses(ctx context.Context, args *GrantAccessArgs) (accessRights []shares.AccessRight, err error)
5051

5152
// BuildVolumeContext builds a volume context map that's passed to NodeStageVolumeRequest and NodePublishVolumeRequest
5253
BuildVolumeContext(args *VolumeContextArgs) (volumeContext map[string]string, err error)

tests/e2e/csi/manila/testdriver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ func (d *manilaTestDriver) GetPersistentVolumeSource(readOnly bool, fsType strin
197197
ReadOnly: readOnly,
198198
FSType: fsType,
199199
VolumeAttributes: map[string]string{
200-
"shareID": v.shareID,
201-
"shareAccessID": v.accessID,
200+
"shareID": v.shareID,
201+
"shareAccessIDs": v.accessID,
202202
},
203203
NodeStageSecretRef: &v1.SecretReference{
204204
Name: manilaSecretName,

0 commit comments

Comments
 (0)