Skip to content

Commit 4adee01

Browse files
authored
Merge pull request ceph#54277 from rhcs-dashboard/nfs-export-apply-fix
mgr/nfs: generate user_id & access_key for apply_export(CephFS) Reviewed-by: Adam King <[email protected]> Reviewed-by: Dhairya Parmar <[email protected]> Reviewed-by: John Mulligan <[email protected]>
2 parents 08cec69 + b895e59 commit 4adee01

File tree

7 files changed

+392
-136
lines changed

7 files changed

+392
-136
lines changed

PendingReleaseNotes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
279279
* RGW: in bucket notifications, the `principalId` inside `ownerIdentity` now contains
280280
complete user id, prefixed with tenant id
281281

282+
* NFS: The export create/apply of CephFS based exports will now have a additional parameter `cmount_path` under the FSAL block,
283+
which specifies the path within the CephFS to mount this export on. If this and the other
284+
`EXPORT { FSAL {} }` options are the same between multiple exports, those exports will share a single CephFS client. If not specified, the default is `/`.
285+
282286
>=18.0.0
283287

284288
* The RGW policy parser now rejects unknown principals by default. If you are

doc/mgr/nfs.rst

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ Create CephFS Export
283283

284284
.. code:: bash
285285
286-
$ ceph nfs export create cephfs --cluster-id <cluster_id> --pseudo-path <pseudo_path> --fsname <fsname> [--readonly] [--path=/path/in/cephfs] [--client_addr <value>...] [--squash <value>] [--sectype <value>...]
286+
$ ceph nfs export create cephfs --cluster-id <cluster_id> --pseudo-path <pseudo_path> --fsname <fsname> [--readonly] [--path=/path/in/cephfs] [--client_addr <value>...] [--squash <value>] [--sectype <value>...] [--cmount_path <value>]
287287
288288
This creates export RADOS objects containing the export block, where
289289

@@ -318,6 +318,12 @@ values may be separated by a comma (example: ``--sectype krb5p,krb5i``). The
318318
server will negotatiate a supported security type with the client preferring
319319
the supplied methods left-to-right.
320320

321+
``<cmount_path>`` specifies the path within the CephFS to mount this export on. It is
322+
allowed to be any complete path hierarchy between ``/`` and the ``EXPORT {path}``. (i.e. if ``EXPORT { Path }`` parameter is ``/foo/bar`` then cmount_path could be ``/``, ``/foo`` or ``/foo/bar``).
323+
324+
.. note:: If this and the other ``EXPORT { FSAL {} }`` options are the same between multiple exports, those exports will share a single CephFS client.
325+
If not specified, the default is ``/``.
326+
321327
.. note:: Specifying values for sectype that require Kerberos will only function on servers
322328
that are configured to support Kerberos. Setting up NFS-Ganesha to support Kerberos
323329
is outside the scope of this document.
@@ -477,9 +483,9 @@ For example,::
477483
],
478484
"fsal": {
479485
"name": "CEPH",
480-
"user_id": "nfs.mynfs.1",
481486
"fs_name": "a",
482-
"sec_label_xattr": ""
487+
"sec_label_xattr": "",
488+
"cmount_path": "/"
483489
},
484490
"clients": []
485491
}
@@ -494,6 +500,9 @@ as when creating a new export), with the exception of the
494500
authentication credentials, which will be carried over from the
495501
previous state of the export where possible.
496502

503+
!! NOTE: The ``user_id`` in the ``fsal`` block should not be modified or mentioned in the JSON file as it is auto-generated for CephFS exports.
504+
It's auto-generated in the format ``nfs.<cluster_id>.<fs_name>.<hash_id>``.
505+
497506
::
498507

499508
$ ceph nfs export apply mynfs -i update_cephfs_export.json
@@ -514,9 +523,9 @@ previous state of the export where possible.
514523
],
515524
"fsal": {
516525
"name": "CEPH",
517-
"user_id": "nfs.mynfs.1",
518526
"fs_name": "a",
519-
"sec_label_xattr": ""
527+
"sec_label_xattr": "",
528+
"cmount_path": "/"
520529
},
521530
"clients": []
522531
}

qa/tasks/cephfs/test_nfs.py

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
NFS_POOL_NAME = '.nfs' # should match mgr_module.py
1515

1616
# TODO Add test for cluster update when ganesha can be deployed on multiple ports.
17+
18+
1719
class TestNFS(MgrTestCase):
1820
def _cmd(self, *args):
1921
return self.get_ceph_cmd_stdout(args)
@@ -59,8 +61,9 @@ def setUp(self):
5961
],
6062
"fsal": {
6163
"name": "CEPH",
62-
"user_id": "nfs.test.1",
64+
"user_id": "nfs.test.nfs-cephfs.3746f603",
6365
"fs_name": self.fs_name,
66+
"cmount_path": "/",
6467
},
6568
"clients": []
6669
}
@@ -118,18 +121,20 @@ def _check_nfs_cluster_status(self, expected_status, fail_msg):
118121
return
119122
self.fail(fail_msg)
120123

121-
def _check_auth_ls(self, export_id=1, check_in=False):
124+
def _check_auth_ls(self, fs_name, check_in=False, user_id=None):
122125
'''
123126
Tests export user id creation or deletion.
124127
:param export_id: Denotes export number
125128
:param check_in: Check specified export id
126129
'''
127130
output = self._cmd('auth', 'ls')
128131
client_id = f'client.nfs.{self.cluster_id}'
132+
search_id = f'client.{user_id}' if user_id else f'{client_id}.{fs_name}'
133+
129134
if check_in:
130-
self.assertIn(f'{client_id}.{export_id}', output)
135+
self.assertIn(search_id, output)
131136
else:
132-
self.assertNotIn(f'{client_id}.{export_id}', output)
137+
self.assertNotIn(search_id, output)
133138

134139
def _test_idempotency(self, cmd_func, cmd_args):
135140
'''
@@ -216,7 +221,7 @@ def _create_export(self, export_id, create_fs=False, extra_cmd=None):
216221
# Runs the nfs export create command
217222
self._cmd(*export_cmd)
218223
# Check if user id for export is created
219-
self._check_auth_ls(export_id, check_in=True)
224+
self._check_auth_ls(self.fs_name, check_in=True)
220225
res = self._sys_cmd(['rados', '-p', NFS_POOL_NAME, '-N', self.cluster_id, 'get',
221226
f'export-{export_id}', '-'])
222227
# Check if export object is created
@@ -230,12 +235,12 @@ def _create_default_export(self):
230235
self._test_create_cluster()
231236
self._create_export(export_id='1', create_fs=True)
232237

233-
def _delete_export(self):
238+
def _delete_export(self, pseduo_path=None, check_in=False, user_id=None):
234239
'''
235240
Delete an export.
236241
'''
237-
self._nfs_cmd('export', 'rm', self.cluster_id, self.pseudo_path)
238-
self._check_auth_ls()
242+
self._nfs_cmd('export', 'rm', self.cluster_id, pseduo_path if pseduo_path else self.pseudo_path)
243+
self._check_auth_ls(self.fs_name, check_in, user_id)
239244

240245
def _test_list_export(self):
241246
'''
@@ -256,26 +261,27 @@ def _test_list_detailed(self, sub_vol_path):
256261
self.sample_export['export_id'] = 2
257262
self.sample_export['pseudo'] = self.pseudo_path + '1'
258263
self.sample_export['access_type'] = 'RO'
259-
self.sample_export['fsal']['user_id'] = f'{self.expected_name}.2'
264+
self.sample_export['fsal']['user_id'] = f'{self.expected_name}.{self.fs_name}.3746f603'
260265
self.assertDictEqual(self.sample_export, nfs_output[1])
261266
# Export-3 for subvolume with r only
262267
self.sample_export['export_id'] = 3
263268
self.sample_export['path'] = sub_vol_path
264269
self.sample_export['pseudo'] = self.pseudo_path + '2'
265-
self.sample_export['fsal']['user_id'] = f'{self.expected_name}.3'
270+
self.sample_export['fsal']['user_id'] = f'{self.expected_name}.{self.fs_name}.3746f603'
266271
self.assertDictEqual(self.sample_export, nfs_output[2])
267272
# Export-4 for subvolume
268273
self.sample_export['export_id'] = 4
269274
self.sample_export['pseudo'] = self.pseudo_path + '3'
270275
self.sample_export['access_type'] = 'RW'
271-
self.sample_export['fsal']['user_id'] = f'{self.expected_name}.4'
276+
self.sample_export['fsal']['user_id'] = f'{self.expected_name}.{self.fs_name}.3746f603'
272277
self.assertDictEqual(self.sample_export, nfs_output[3])
273278

274-
def _get_export(self):
279+
def _get_export(self, pseudo_path=None):
275280
'''
276281
Returns export block in json format
277282
'''
278-
return json.loads(self._nfs_cmd('export', 'info', self.cluster_id, self.pseudo_path))
283+
return json.loads(self._nfs_cmd('export', 'info', self.cluster_id,
284+
pseudo_path if pseudo_path else self.pseudo_path))
279285

280286
def _test_get_export(self):
281287
'''
@@ -506,7 +512,7 @@ def test_create_multiple_exports(self):
506512
self._test_delete_cluster()
507513
# Check if rados ganesha conf object is deleted
508514
self._check_export_obj_deleted(conf_obj=True)
509-
self._check_auth_ls()
515+
self._check_auth_ls(self.fs_name)
510516

511517
def test_exports_on_mgr_restart(self):
512518
'''
@@ -935,7 +941,7 @@ def test_nfs_export_apply_multiple_exports(self):
935941
"protocols": [4],
936942
"fsal": {
937943
"name": "CEPH",
938-
"user_id": "nfs.test.1",
944+
"user_id": "nfs.test.nfs-cephfs.3746f603",
939945
"fs_name": self.fs_name
940946
}
941947
},
@@ -948,7 +954,7 @@ def test_nfs_export_apply_multiple_exports(self):
948954
"protocols": [4],
949955
"fsal": {
950956
"name": "CEPH",
951-
"user_id": "nfs.test.2",
957+
"user_id": "nfs.test.nfs-cephfs.3746f603",
952958
"fs_name": "invalid_fs_name" # invalid fs
953959
}
954960
},
@@ -961,7 +967,7 @@ def test_nfs_export_apply_multiple_exports(self):
961967
"protocols": [4],
962968
"fsal": {
963969
"name": "CEPH",
964-
"user_id": "nfs.test.3",
970+
"user_id": "nfs.test.nfs-cephfs.3746f603",
965971
"fs_name": self.fs_name
966972
}
967973
}
@@ -1008,7 +1014,7 @@ def test_nfs_export_apply_single_export(self):
10081014
"protocols": [4],
10091015
"fsal": {
10101016
"name": "CEPH",
1011-
"user_id": "nfs.test.1",
1017+
"user_id": "nfs.test.nfs-cephfs.3746f603",
10121018
"fs_name": "invalid_fs_name" # invalid fs
10131019
}
10141020
}
@@ -1048,7 +1054,7 @@ def test_nfs_export_apply_json_output_states(self):
10481054
"protocols": [4],
10491055
"fsal": {
10501056
"name": "CEPH",
1051-
"user_id": "nfs.test.1",
1057+
"user_id": "nfs.test.nfs-cephfs.3746f603",
10521058
"fs_name": self.fs_name
10531059
}
10541060
},
@@ -1061,7 +1067,7 @@ def test_nfs_export_apply_json_output_states(self):
10611067
"protocols": [4],
10621068
"fsal": {
10631069
"name": "CEPH",
1064-
"user_id": "nfs.test.2",
1070+
"user_id": "nfs.test.nfs-cephfs.3746f603",
10651071
"fs_name": self.fs_name
10661072
}
10671073
},
@@ -1075,7 +1081,7 @@ def test_nfs_export_apply_json_output_states(self):
10751081
"protocols": [4],
10761082
"fsal": {
10771083
"name": "CEPH",
1078-
"user_id": "nfs.test.3",
1084+
"user_id": "nfs.test.nfs-cephfs.3746f603",
10791085
"fs_name": "invalid_fs_name"
10801086
}
10811087
}
@@ -1211,3 +1217,65 @@ def test_cephfs_export_update_at_non_dir_path(self):
12111217
finally:
12121218
self.ctx.cluster.run(args=['rm', '-rf', f'{mnt_pt}/*'])
12131219
self._delete_cluster_with_fs(self.fs_name, mnt_pt, preserve_mode)
1220+
1221+
def test_nfs_export_creation_without_cmount_path(self):
1222+
"""
1223+
Test that ensure cmount_path is present in FSAL block
1224+
"""
1225+
self._create_cluster_with_fs(self.fs_name)
1226+
1227+
pseudo_path = '/test_without_cmount'
1228+
self._create_export(export_id='1',
1229+
extra_cmd=['--pseudo-path', pseudo_path])
1230+
nfs_output = self._get_export(pseudo_path)
1231+
self.assertIn('cmount_path', nfs_output['fsal'])
1232+
1233+
self._delete_export(pseudo_path)
1234+
1235+
def test_nfs_exports_with_same_and_diff_user_id(self):
1236+
"""
1237+
Test that exports with same FSAL share same user_id
1238+
"""
1239+
self._create_cluster_with_fs(self.fs_name)
1240+
1241+
pseudo_path_1 = '/test1'
1242+
pseudo_path_2 = '/test2'
1243+
pseudo_path_3 = '/test3'
1244+
1245+
# Create subvolumes
1246+
self._cmd('fs', 'subvolume', 'create', self.fs_name, 'sub_vol_1')
1247+
self._cmd('fs', 'subvolume', 'create', self.fs_name, 'sub_vol_2')
1248+
1249+
fs_path_1 = self._cmd('fs', 'subvolume', 'getpath', self.fs_name, 'sub_vol_1').strip()
1250+
fs_path_2 = self._cmd('fs', 'subvolume', 'getpath', self.fs_name, 'sub_vol_2').strip()
1251+
# Both exports should have same user_id(since cmount_path=/ & fs_name is same)
1252+
self._create_export(export_id='1',
1253+
extra_cmd=['--pseudo-path', pseudo_path_1,
1254+
'--path', fs_path_1])
1255+
self._create_export(export_id='2',
1256+
extra_cmd=['--pseudo-path', pseudo_path_2,
1257+
'--path', fs_path_2])
1258+
1259+
nfs_output_1 = self._get_export(pseudo_path_1)
1260+
nfs_output_2 = self._get_export(pseudo_path_2)
1261+
# Check if both exports have same user_id
1262+
self.assertEqual(nfs_output_2['fsal']['user_id'], nfs_output_1['fsal']['user_id'])
1263+
self.assertEqual(nfs_output_1['fsal']['user_id'], 'nfs.test.nfs-cephfs.3746f603')
1264+
1265+
cmount_path = '/volumes'
1266+
self._create_export(export_id='3',
1267+
extra_cmd=['--pseudo-path', pseudo_path_3,
1268+
'--path', fs_path_1,
1269+
'--cmount-path', cmount_path])
1270+
1271+
nfs_output_3 = self._get_export(pseudo_path_3)
1272+
self.assertNotEqual(nfs_output_3['fsal']['user_id'], nfs_output_1['fsal']['user_id'])
1273+
self.assertEqual(nfs_output_3['fsal']['user_id'], 'nfs.test.nfs-cephfs.32cd8545')
1274+
1275+
# Deleting export with same user_id should not delete the user_id
1276+
self._delete_export(pseudo_path_1, True, nfs_output_1['fsal']['user_id'])
1277+
# Deleting export 22 should delete the user_id since it's only export left with that user_id
1278+
self._delete_export(pseudo_path_2, False, nfs_output_2['fsal']['user_id'])
1279+
1280+
# Deleting export 23 should delete the user_id since it's only export with that user_id
1281+
self._delete_export(pseudo_path_3, False, nfs_output_3['fsal']['user_id'])

0 commit comments

Comments
 (0)