Skip to content

Commit 210a00a

Browse files
authored
Merge pull request ceph#62105 from xhernandez/configure-case-sensitivity
pybind/mgr/volumes: configure case sensitivity Reviewed-by: Avan Thakkar <athakkar@redhat. Reviewed-by: Patrick Donnelly <[email protected]> Reviewed-by: Anoop C S <[email protected]>
2 parents 3a2aac2 + d9704c0 commit 210a00a

File tree

8 files changed

+160
-12
lines changed

8 files changed

+160
-12
lines changed

doc/cephfs/fs-volumes.rst

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ Use a command of the following form to create a subvolume:
287287

288288
.. prompt:: bash #
289289

290-
ceph fs subvolume create <vol_name> <subvol_name> [--size <size_in_bytes>] [--group_name <subvol_group_name>] [--pool_layout <data_pool_name>] [--uid <uid>] [--gid <gid>] [--mode <octal_mode>] [--namespace-isolated] [--earmark <earmark>]
290+
ceph fs subvolume create <vol_name> <subvol_name> [--size <size_in_bytes>] [--group_name <subvol_group_name>] [--pool_layout <data_pool_name>] [--uid <uid>] [--gid <gid>] [--mode <octal_mode>] [--namespace-isolated] [--earmark <earmark>] [--normalization <form>] [--case-insensitive]
291291

292292

293293
The command succeeds even if the subvolume already exists.
@@ -327,6 +327,29 @@ Valid Earmarks
327327
be aware that user permissions and ACLs associated with the previous scope might still apply. Ensure that
328328
any necessary permissions are updated as needed to maintain proper access control.
329329

330+
When creating a subvolume you can also specify an unicode normalization form by
331+
using the ``--normalization`` option. This will be used to internally mangle
332+
file names so that unicode characters that can be represented by different
333+
unicode code point sequences are all mapped to the representation, which means
334+
that they will all access the same file. However, users will continue to see
335+
the same name that they used when the file was created.
336+
337+
The valid values for the unicode normalization form are:
338+
339+
- nfd: canonical decomposition (default)
340+
- nfc: canonical decomposition, followed by canonical composition
341+
- nfkd: compatibility decomposition
342+
- nfkc: compatibility decomposition, followed by canonical composition
343+
344+
To learn more about unicode normalization forms see https://unicode.org/reports/tr15
345+
346+
It's also possible to configure a subvolume for case insensitive access when
347+
the ``--case-insensitive`` option is used. When this option is added, file
348+
names that only differ in the case of its characters will be mapped to the same
349+
file. The case of the file name used when the file was created is preserved.
350+
351+
.. note:: Setting ``--case-insensitive`` option implicitly enables
352+
unicode normalization on the subvolume.
330353

331354
Removing a subvolume
332355
~~~~~~~~~~~~~~~~~~~~

qa/tasks/cephfs/test_volumes.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2686,6 +2686,70 @@ def test_earmark_on_deleted_subvolume_with_retained_snapshot(self):
26862686
except CommandFailedError as ce:
26872687
self.assertEqual(ce.exitstatus, errno.ENOENT, error_message)
26882688

2689+
def test_subvolume_create_without_normalization(self):
2690+
# create subvolume
2691+
subvolume = self._gen_subvol_name()
2692+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2693+
2694+
# make sure it exists
2695+
subvolpath = self._get_subvolume_path(self.volname, subvolume)
2696+
self.assertNotEqual(subvolpath, None)
2697+
2698+
# check normalization
2699+
try:
2700+
self._fs_cmd("subvolume", "charmap", "get", self.volname, subvolume, "normalization")
2701+
except CommandFailedError as ce:
2702+
self.assertEqual(ce.exitstatus, errno.ENODATA)
2703+
else:
2704+
self.fail("expected the 'fs subvolume charmap' command to fail")
2705+
2706+
def test_subvolume_create_with_normalization(self):
2707+
# create subvolume
2708+
subvolume = self._gen_subvol_name()
2709+
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--normalization", "nfc")
2710+
2711+
# make sure it exists
2712+
subvolpath = self._get_subvolume_path(self.volname, subvolume)
2713+
self.assertNotEqual(subvolpath, None)
2714+
2715+
# check normalization
2716+
normalization = self._fs_cmd("subvolume", "charmap", "get", self.volname, subvolume, "normalization")
2717+
self.assertEqual(normalization.strip(), "nfc")
2718+
2719+
def test_subvolume_create_without_case_sensitivity(self):
2720+
# create subvolume
2721+
subvolume = self._gen_subvol_name()
2722+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2723+
2724+
# make sure it exists
2725+
subvolpath = self._get_subvolume_path(self.volname, subvolume)
2726+
self.assertNotEqual(subvolpath, None)
2727+
2728+
# check case sensitivity
2729+
try:
2730+
self._fs_cmd("subvolume", "charmap", "get", self.volname, subvolume, "casesensitive")
2731+
except CommandFailedError as ce:
2732+
self.assertEqual(ce.exitstatus, errno.ENODATA)
2733+
else:
2734+
self.fail("expected the 'fs subvolume charmap' command to fail")
2735+
2736+
def test_subvolume_create_with_case_insensitive(self):
2737+
# create subvolume
2738+
subvolume = self._gen_subvol_name()
2739+
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--case-insensitive")
2740+
2741+
# make sure it exists
2742+
subvolpath = self._get_subvolume_path(self.volname, subvolume)
2743+
self.assertNotEqual(subvolpath, None)
2744+
2745+
# check case sensitivity
2746+
case_sensitive = self._fs_cmd("subvolume", "charmap", "get", self.volname, subvolume, "casesensitive")
2747+
self.assertEqual(case_sensitive.strip(), "0")
2748+
2749+
# check normalization (it's implicitly enabled by --case-insensitive, with default value 'nfd')
2750+
normalization = self._fs_cmd("subvolume", "charmap", "get", self.volname, subvolume, "normalization")
2751+
self.assertEqual(normalization.strip(), "nfd")
2752+
26892753
def test_subvolume_expand(self):
26902754
"""
26912755
That a subvolume can be expanded in size and its quota matches the expected size.

src/pybind/mgr/volumes/fs/operations/subvolume.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .template import SubvolumeOpType
66
from .versions import loaded_subvolumes
77

8-
def create_subvol(mgr, fs, vol_spec, group, subvolname, size, isolate_nspace, pool, mode, uid, gid, earmark):
8+
def create_subvol(mgr, fs, vol_spec, group, subvolname, size, isolate_nspace, pool, mode, uid, gid, earmark, normalization, case_insensitive):
99
"""
1010
create a subvolume (create a subvolume with the max known version).
1111
@@ -19,10 +19,12 @@ def create_subvol(mgr, fs, vol_spec, group, subvolname, size, isolate_nspace, po
1919
:param uid: the user identifier
2020
:param gid: the group identifier
2121
:param earmark: metadata string to identify if subvolume is associated with nfs/smb
22+
:param normalization: the unicode normalization form to use (nfd, nfc, nfkd or nfkc)
23+
:param case_insensitive: whether to make the subvolume case insensitive or not
2224
:return: None
2325
"""
2426
subvolume = loaded_subvolumes.get_subvolume_object_max(mgr, fs, vol_spec, group, subvolname)
25-
subvolume.create(size, isolate_nspace, pool, mode, uid, gid, earmark)
27+
subvolume.create(size, isolate_nspace, pool, mode, uid, gid, earmark, normalization, case_insensitive)
2628

2729

2830
def create_clone(mgr, fs, vol_spec, group, subvolname, pool, source_volume, source_subvolume, snapname):

src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,19 @@ def get_attrs(self, pathname):
203203
except EarmarkException:
204204
attrs["earmark"] = ''
205205

206+
try:
207+
attrs["normalization"] = self.fs.getxattr(pathname,
208+
'ceph.dir.normalization'
209+
).decode('utf-8')
210+
except cephfs.NoData:
211+
attrs["normalization"] = None
212+
213+
try:
214+
case_insensitive = self.fs.getxattr(pathname, 'ceph.dir.caseinsensitive').decode('utf-8')
215+
attrs["case_insensitive"] = case_insensitive == "0"
216+
except cephfs.NoData:
217+
attrs["case_insensitive"] = False
218+
206219
return attrs
207220

208221
def set_attrs(self, path, attrs):
@@ -294,6 +307,20 @@ def set_attrs(self, path, attrs):
294307
fs_earmark = CephFSVolumeEarmarking(self.fs, path)
295308
fs_earmark.set_earmark(earmark)
296309

310+
normalization = attrs.get("normalization")
311+
if normalization is not None:
312+
try:
313+
self.fs.setxattr(path, "ceph.dir.normalization", normalization.encode('utf-8'), 0)
314+
except cephfs.Error as e:
315+
raise VolumeException(-e.args[0], e.args[1])
316+
317+
case_insensitive = attrs.get("case_insensitive")
318+
if case_insensitive:
319+
try:
320+
self.fs.setxattr(path, "ceph.dir.casesensitive", "0".encode('utf-8'), 0)
321+
except cephfs.Error as e:
322+
raise VolumeException(-e.args[0], e.args[1])
323+
297324
def _resize(self, path, newsize, noshrink):
298325
try:
299326
newsize = int(newsize)
@@ -452,6 +479,21 @@ def info(self):
452479
except EarmarkException:
453480
earmark = ''
454481

482+
try:
483+
normalization = self.fs.getxattr(subvolpath,
484+
'ceph.dir.normalization'
485+
).decode('utf-8')
486+
except cephfs.NoData:
487+
normalization = "none"
488+
489+
try:
490+
case_insensitive = self.fs.getxattr(subvolpath,
491+
'ceph.dir.casesensitive'
492+
).decode('utf-8')
493+
case_insensitive = case_insensitive == "0"
494+
except cephfs.NoData:
495+
case_insensitive = False
496+
455497
return {'path': subvolpath,
456498
'type': etype.value,
457499
'uid': int(st["uid"]),
@@ -470,7 +512,10 @@ def info(self):
470512
'pool_namespace': pool_namespace,
471513
'features': self.features,
472514
'state': self.state.value,
473-
'earmark': earmark}
515+
'earmark': earmark,
516+
'normalization': normalization,
517+
'case_insensitive': case_insensitive,
518+
}
474519

475520
def set_user_metadata(self, keyname, value):
476521
try:

src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def snapshot_data_path(self, snapname):
8585
""" Path to user data directory within a subvolume snapshot named 'snapname' """
8686
return self.snapshot_path(snapname)
8787

88-
def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark):
88+
def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark, normalization, case_insensitive):
8989
subvolume_type = SubvolumeTypes.TYPE_NORMAL
9090
try:
9191
initial_state = SubvolumeOpSm.get_init_state(subvolume_type)
@@ -104,7 +104,9 @@ def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark):
104104
'data_pool': pool,
105105
'pool_namespace': self.namespace if isolate_nspace else None,
106106
'quota': size,
107-
'earmark': earmark
107+
'earmark': earmark,
108+
'normalization': normalization,
109+
'case_insensitive': case_insensitive,
108110
}
109111
self.set_attrs(subvol_path, attrs)
110112

src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def _set_incarnation_metadata(self, subvolume_type, qpath, initial_state):
154154
self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_PATH, qpath)
155155
self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_STATE, initial_state.value)
156156

157-
def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark):
157+
def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark, normalization, case_insensitive):
158158
subvolume_type = SubvolumeTypes.TYPE_NORMAL
159159
try:
160160
initial_state = SubvolumeOpSm.get_init_state(subvolume_type)
@@ -176,7 +176,9 @@ def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark):
176176
'data_pool': pool,
177177
'pool_namespace': self.namespace if isolate_nspace else None,
178178
'quota': size,
179-
'earmark': earmark
179+
'earmark': earmark,
180+
'normalization': normalization,
181+
'case_insensitive': case_insensitive,
180182
}
181183
self.set_attrs(subvol_path, attrs)
182184

src/pybind/mgr/volumes/fs/volume.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,14 @@ def _create_subvolume(self, fs_handle, volname, group, subvolname, **kwargs):
232232
mode = kwargs['mode']
233233
isolate_nspace = kwargs['namespace_isolated']
234234
earmark = kwargs['earmark'] or '' # if not set, default to empty string --> no earmark
235+
normalization = kwargs['normalization']
236+
case_insensitive = kwargs['case_insensitive']
235237

236238
oct_mode = octal_str_to_decimal_int(mode)
237239

238240
try:
239241
create_subvol(
240-
self.mgr, fs_handle, self.volspec, group, subvolname, size, isolate_nspace, pool, oct_mode, uid, gid, earmark)
242+
self.mgr, fs_handle, self.volspec, group, subvolname, size, isolate_nspace, pool, oct_mode, uid, gid, earmark, normalization, case_insensitive)
241243
except VolumeException as ve:
242244
# kick the purge threads for async removal -- note that this
243245
# assumes that the subvolume is moved to trashcan for cleanup on error.
@@ -256,6 +258,8 @@ def create_subvolume(self, **kwargs):
256258
mode = kwargs['mode']
257259
isolate_nspace = kwargs['namespace_isolated']
258260
earmark = kwargs['earmark'] or '' # if not set, default to empty string --> no earmark
261+
normalization = kwargs['normalization']
262+
case_insensitive = kwargs['case_insensitive']
259263

260264
try:
261265
with open_volume(self, volname) as fs_handle:
@@ -270,7 +274,9 @@ def create_subvolume(self, **kwargs):
270274
'data_pool': pool,
271275
'pool_namespace': subvolume.namespace if isolate_nspace else None,
272276
'quota': size,
273-
'earmark': earmark
277+
'earmark': earmark,
278+
'normalization': normalization,
279+
'case_insensitive': case_insensitive,
274280
}
275281
subvolume.set_attrs(subvolume.path, attrs)
276282
except VolumeException as ve:

src/pybind/mgr/volumes/module.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
144144
'name=gid,type=CephInt,req=false '
145145
'name=mode,type=CephString,req=false '
146146
'name=namespace_isolated,type=CephBool,req=false '
147-
'name=earmark,type=CephString,req=false ',
147+
'name=earmark,type=CephString,req=false '
148+
'name=normalization,type=CephChoices,strings=nfd|nfc|nfkd|nfkc,req=false '
149+
'name=case_insensitive,type=CephBool,req=false ',
148150
'desc': "Create a CephFS subvolume in a volume, and optionally, "
149151
"with a specific size (in bytes), a specific data pool layout, "
150152
"a specific mode, in a specific subvolume group and in separate "
@@ -759,7 +761,9 @@ def _cmd_fs_subvolume_create(self, inbuf, cmd):
759761
gid=cmd.get('gid', None),
760762
mode=cmd.get('mode', '755'),
761763
namespace_isolated=cmd.get('namespace_isolated', False),
762-
earmark=cmd.get('earmark', None))
764+
earmark=cmd.get('earmark', None),
765+
normalization=cmd.get('normalization', None),
766+
case_insensitive=cmd.get('case_insensitive', False))
763767

764768
@mgr_cmd_wrap
765769
def _cmd_fs_subvolume_rm(self, inbuf, cmd):

0 commit comments

Comments
 (0)