Skip to content

Commit 2ef5dc8

Browse files
silvacarlossmoonek
andauthored
[manila-csi-plugin] support muilple share rules (#2915)
Signed-off-by: moonek <[email protected]> Signed-off-by: Carlos da Silva <[email protected]> Co-authored-by: moonek <[email protected]>
1 parent 300137a commit 2ef5dc8

File tree

10 files changed

+140
-97
lines changed

10 files changed

+140
-97
lines changed

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

Lines changed: 4 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,8 @@ 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+
`shareAccessID` | _no_ | The UUID of the access rule for the share. This parameter is being deprecated and replaced by `shareAccessIDs`.
75+
`shareAccessIDs` | _yes_ | Comma separated UUIDs of access rules for the share
7576
`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.
7677
`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.
7778
`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
Share: share,

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: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -34,82 +34,77 @@ type Cephfs struct{}
3434

3535
var _ ShareAdapter = &Cephfs{}
3636

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

40-
var rights []shares.AccessRight
41-
42-
accessTo := args.Options.CephfsClientID
43-
if accessTo == "" {
44-
accessTo = args.Share.Name
45-
}
46-
47-
rights, err = args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
40+
rights, err := args.ManilaClient.GetAccessRights(ctx, args.Share.ID)
4841
if err != nil {
4942
if _, ok := err.(gophercloud.ErrResourceNotFound); !ok {
5043
return nil, fmt.Errorf("failed to list access rights: %v", err)
5144
}
52-
} else {
53-
// Try to find the access right
45+
}
5446

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

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

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

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

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

pkg/csi/manila/shareadapters/nfs.go

Lines changed: 25 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,33 @@ 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+
for _, at := range accessToList {
49+
// Try to find the access right
50+
found := false
51+
for _, r := range rights {
52+
if r.AccessTo == at && r.AccessType == "ip" && r.AccessLevel == "rw" {
53+
klog.V(4).Infof("IP access right %s for share %s already exists", at, args.Share.Name)
54+
found = true
55+
break
56+
}
57+
}
58+
// Not found, create it
59+
if !found {
60+
right, err := args.ManilaClient.GrantAccess(ctx, args.Share.ID, shares.GrantAccessOpts{
61+
AccessType: "ip",
62+
AccessLevel: "rw",
63+
AccessTo: at,
64+
})
65+
if err != nil {
66+
return nil, fmt.Errorf("failed to grant access right: %v", err)
67+
}
68+
rights = append(rights, *right)
5269
}
5370
}
5471

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-
})
72+
return rights, nil
6273
}
6374

6475
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
@@ -46,10 +46,11 @@ type SecretArgs struct {
4646
}
4747

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

5455
// BuildVolumeContext builds a volume context map that's passed to NodeStageVolumeRequest and NodePublishVolumeRequest
5556
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)