Skip to content

Commit d0a3655

Browse files
committed
Merge PR ceph#59111 into main
* refs/pull/59111/head: doc: document earmark option for subvolume and new commands qa/cephfs: update tests for test_volumes & unit-test for earmarking mgr/volumes: add earmarking for subvol Reviewed-by: Venky Shankar <[email protected]> Reviewed-by: Kotresh Hiremath Ravishankar <[email protected]> Reviewed-by: John Mulligan <[email protected]>
2 parents ee9bb3d + 174b9d4 commit d0a3655

File tree

12 files changed

+522
-15
lines changed

12 files changed

+522
-15
lines changed

doc/cephfs/fs-volumes.rst

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

277277
.. prompt:: bash #
278278

279-
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]
279+
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>]
280280

281281

282282
The command succeeds even if the subvolume already exists.
@@ -289,6 +289,15 @@ The subvolume can be created in a separate RADOS namespace by specifying the
289289
default subvolume group with an octal file mode of ``755``, a uid of its
290290
subvolume group, a gid of its subvolume group, a data pool layout of its parent
291291
directory, and no size limit.
292+
You can also assign an earmark to a subvolume using the ``--earmark`` option.
293+
The earmark is a unique identifier that tags the subvolume for specific purposes,
294+
such as NFS or SMB services. By default, no earmark is set, allowing for flexible
295+
assignment based on administrative needs. An empty string ("") can be used to remove
296+
any existing earmark from a subvolume.
297+
298+
The earmarking mechanism ensures that subvolumes are correctly tagged and managed,
299+
helping to avoid conflicts and ensuring that each subvolume is associated
300+
with the intended service or use case.
292301

293302
Removing a subvolume
294303
~~~~~~~~~~~~~~~~~~~~
@@ -418,6 +427,7 @@ The output format is JSON and contains the following fields.
418427
* ``pool_namespace``: RADOS namespace of the subvolume
419428
* ``features``: features supported by the subvolume
420429
* ``state``: current state of the subvolume
430+
* ``earmark``: earmark of the subvolume
421431

422432
If a subvolume has been removed but its snapshots have been retained, the
423433
output contains only the following fields.
@@ -522,6 +532,33 @@ subvolume using the metadata key:
522532
Using the ``--force`` flag allows the command to succeed when it would
523533
otherwise fail (if the metadata key did not exist).
524534

535+
Getting earmark of a subvolume
536+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
537+
538+
Use a command of the following form to get the earmark of a subvolume:
539+
540+
.. prompt:: bash #
541+
542+
ceph fs subvolume earmark get <vol_name> <subvol_name> [--group_name <subvol_group_name>]
543+
544+
Setting earmark of a subvolume
545+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
546+
547+
Use a command of the following form to set the earmark of a subvolume:
548+
549+
.. prompt:: bash #
550+
551+
ceph fs subvolume earmark set <vol_name> <subvol_name> [--group_name <subvol_group_name>] <earmark>
552+
553+
Removing earmark of a subvolume
554+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
555+
556+
Use a command of the following form to remove the earmark of a subvolume:
557+
558+
.. prompt:: bash #
559+
560+
ceph fs subvolume earmark rm <vol_name> <subvol_name> [--group_name <subvol_group_name>]
561+
525562
Creating a Snapshot of a Subvolume
526563
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
527564

qa/tasks/cephfs/test_volumes.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,124 @@ def test_subvolume_create_and_ls_providing_group_as_nogroup(self):
23672367

23682368
# verify trash dir is clean.
23692369
self._wait_for_trash_empty()
2370+
2371+
def test_subvolume_create_with_earmark(self):
2372+
# create subvolume with earmark
2373+
subvolume = self._gen_subvol_name()
2374+
earmark = "nfs.test"
2375+
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--earmark", earmark)
2376+
2377+
# make sure it exists
2378+
subvolpath = self._get_subvolume_path(self.volname, subvolume)
2379+
self.assertNotEqual(subvolpath, None)
2380+
2381+
# verify the earmark
2382+
get_earmark = self._fs_cmd("subvolume", "earmark", "get", self.volname, subvolume)
2383+
self.assertEqual(get_earmark.rstrip('\n'), earmark)
2384+
2385+
def test_subvolume_set_and_get_earmark(self):
2386+
# create subvolume
2387+
subvolume = self._gen_subvol_name()
2388+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2389+
2390+
# set earmark
2391+
earmark = "smb.test"
2392+
self._fs_cmd("subvolume", "earmark", "set", self.volname, subvolume, "--earmark", earmark)
2393+
2394+
# get earmark
2395+
get_earmark = self._fs_cmd("subvolume", "earmark", "get", self.volname, subvolume)
2396+
self.assertEqual(get_earmark.rstrip('\n'), earmark)
2397+
2398+
def test_subvolume_clear_earmark(self):
2399+
# create subvolume
2400+
subvolume = self._gen_subvol_name()
2401+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2402+
2403+
# set earmark
2404+
earmark = "smb.test"
2405+
self._fs_cmd("subvolume", "earmark", "set", self.volname, subvolume, "--earmark", earmark)
2406+
2407+
# remove earmark
2408+
self._fs_cmd("subvolume", "earmark", "rm", self.volname, subvolume)
2409+
2410+
# get earmark
2411+
get_earmark = self._fs_cmd("subvolume", "earmark", "get", self.volname, subvolume)
2412+
self.assertEqual(get_earmark, "")
2413+
2414+
def test_earmark_on_non_existing_subvolume(self):
2415+
subvolume = "non_existing_subvol"
2416+
earmark = "nfs.test"
2417+
commands = [
2418+
("set", earmark),
2419+
("get", None),
2420+
("rm", None),
2421+
]
2422+
2423+
for action, arg in commands:
2424+
try:
2425+
# Build the command arguments
2426+
cmd_args = ["subvolume", "earmark", action, self.volname, subvolume]
2427+
if arg is not None:
2428+
cmd_args.extend(["--earmark", arg])
2429+
2430+
# Execute the command with built arguments
2431+
self._fs_cmd(*cmd_args)
2432+
except CommandFailedError as ce:
2433+
self.assertEqual(ce.exitstatus, errno.ENOENT)
2434+
2435+
def test_get_remove_earmark_when_not_set(self):
2436+
# Create a subvolume without setting an earmark
2437+
subvolume = self._gen_subvol_name()
2438+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2439+
2440+
# Attempt to get an earmark when it's not set
2441+
get_earmark = self._fs_cmd("subvolume", "earmark", "get", self.volname, subvolume)
2442+
self.assertEqual(get_earmark, "")
2443+
2444+
# Attempt to remove an earmark when it's not set
2445+
self._fs_cmd("subvolume", "earmark", "rm", self.volname, subvolume)
2446+
2447+
def test_set_invalid_earmark(self):
2448+
# Create a subvolume
2449+
subvolume = self._gen_subvol_name()
2450+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2451+
2452+
# Attempt to set an invalid earmark
2453+
invalid_earmark = "invalid_format"
2454+
expected_message = (
2455+
f"Invalid earmark specified: '{invalid_earmark}'. A valid earmark should "
2456+
"either be empty or start with 'nfs' or 'smb', followed by dot-separated "
2457+
"non-empty components."
2458+
)
2459+
try:
2460+
self._fs_cmd("subvolume", "earmark", "set", self.volname, subvolume, "--earmark", invalid_earmark)
2461+
except CommandFailedError as ce:
2462+
self.assertEqual(ce.exitstatus, errno.EINVAL, expected_message)
2463+
2464+
def test_earmark_on_deleted_subvolume_with_retained_snapshot(self):
2465+
subvolume = self._gen_subvol_name()
2466+
snapshot = self._gen_subvol_snap_name()
2467+
2468+
# Create subvolume and snapshot
2469+
self._fs_cmd("subvolume", "create", self.volname, subvolume)
2470+
self._fs_cmd("subvolume", "snapshot", "create", self.volname, subvolume, snapshot)
2471+
2472+
# Delete subvolume while retaining the snapshot
2473+
self._fs_cmd("subvolume", "rm", self.volname, subvolume, "--retain-snapshots")
2474+
2475+
# Define the expected error message
2476+
error_message = f'subvolume "{subvolume}" is removed and has only snapshots retained'
2477+
2478+
# Test cases for setting, getting, and removing earmarks
2479+
for operation in ["get", "rm", "set"]:
2480+
try:
2481+
extra_arg = "smb" if operation == "set" else None
2482+
if operation == "set":
2483+
self._fs_cmd("subvolume", "earmark", operation, self.volname, subvolume, "--earmark", extra_arg)
2484+
else:
2485+
self._fs_cmd("subvolume", "earmark", operation, self.volname, subvolume)
2486+
except CommandFailedError as ce:
2487+
self.assertEqual(ce.exitstatus, errno.ENOENT, error_message)
23702488

23712489
def test_subvolume_expand(self):
23722490
"""
@@ -2440,6 +2558,14 @@ def test_subvolume_info(self):
24402558
for feature in ['snapshot-clone', 'snapshot-autoprotect', 'snapshot-retention']:
24412559
self.assertIn(feature, subvol_info["features"], msg="expected feature '{0}' in subvolume".format(feature))
24422560

2561+
# set earmark
2562+
earmark = "smb.test"
2563+
self._fs_cmd("subvolume", "earmark", "set", self.volname, subvolume, "--earmark", earmark)
2564+
2565+
subvol_info = json.loads(self._get_subvolume_info(self.volname, subvolume))
2566+
2567+
self.assertEqual(subvol_info["earmark"], earmark)
2568+
24432569
# remove subvolumes
24442570
self._fs_cmd("subvolume", "rm", self.volname, subvolume)
24452571

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

Lines changed: 3 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):
8+
def create_subvol(mgr, fs, vol_spec, group, subvolname, size, isolate_nspace, pool, mode, uid, gid, earmark):
99
"""
1010
create a subvolume (create a subvolume with the max known version).
1111
@@ -18,10 +18,11 @@ def create_subvol(mgr, fs, vol_spec, group, subvolname, size, isolate_nspace, po
1818
:param mode: the user permissions
1919
:param uid: the user identifier
2020
:param gid: the group identifier
21+
:param earmark: metadata string to identify if subvolume is associated with nfs/smb
2122
:return: None
2223
"""
2324
subvolume = loaded_subvolumes.get_subvolume_object_max(mgr, fs, vol_spec, group, subvolname)
24-
subvolume.create(size, isolate_nspace, pool, mode, uid, gid)
25+
subvolume.create(size, isolate_nspace, pool, mode, uid, gid, earmark)
2526

2627

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class SubvolumeOpType(Enum):
6868
SNAP_METADATA_GET = 'snap-metadata-get'
6969
SNAP_METADATA_LIST = 'snap-metadata-ls'
7070
SNAP_METADATA_REMOVE = 'snap-metadata-rm'
71+
EARMARK_GET = 'earmark-get'
72+
EARMARK_SET = 'earmark-set'
73+
EARMARK_CLEAR = 'earmark-clear'
7174

7275
class SubvolumeTemplate(object):
7376
VERSION = None # type: int

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from .auth_metadata import AuthMetadataManager
1919
from .subvolume_attrs import SubvolumeStates
2020

21+
from ceph.fs.earmarking import CephFSVolumeEarmarking, EarmarkException # type: ignore
22+
2123
log = logging.getLogger(__name__)
2224

2325

@@ -192,6 +194,14 @@ def get_attrs(self, pathname):
192194
except cephfs.NoData:
193195
attrs["quota"] = None
194196

197+
try:
198+
fs_earmark = CephFSVolumeEarmarking(self.fs, pathname)
199+
attrs["earmark"] = fs_earmark.get_earmark()
200+
except cephfs.NoData:
201+
attrs["earmark"] = ''
202+
except EarmarkException:
203+
attrs["earmark"] = ''
204+
195205
return attrs
196206

197207
def set_attrs(self, path, attrs):
@@ -277,6 +287,12 @@ def set_attrs(self, path, attrs):
277287
if mode is not None:
278288
self.fs.lchmod(path, mode)
279289

290+
# set earmark
291+
earmark = attrs.get("earmark")
292+
if earmark is not None:
293+
fs_earmark = CephFSVolumeEarmarking(self.fs, path)
294+
fs_earmark.set_earmark(earmark)
295+
280296
def _resize(self, path, newsize, noshrink):
281297
try:
282298
newsize = int(newsize)
@@ -418,6 +434,14 @@ def info(self):
418434
except cephfs.Error as e:
419435
raise VolumeException(-e.args[0], e.args[1])
420436

437+
try:
438+
fs_earmark = CephFSVolumeEarmarking(self.fs, subvolpath)
439+
earmark = fs_earmark.get_earmark()
440+
except cephfs.NoData:
441+
earmark = ''
442+
except EarmarkException:
443+
earmark = ''
444+
421445
return {'path': subvolpath,
422446
'type': etype.value,
423447
'uid': int(st["uid"]),
@@ -434,7 +458,9 @@ def info(self):
434458
if nsize == 0
435459
else '{0:.2f}'.format((float(usedbytes) / nsize) * 100.0),
436460
'pool_namespace': pool_namespace,
437-
'features': self.features, 'state': self.state.value}
461+
'features': self.features,
462+
'state': self.state.value,
463+
'earmark': earmark}
438464

439465
def set_user_metadata(self, keyname, value):
440466
try:

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

Lines changed: 3 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):
88+
def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark):
8989
subvolume_type = SubvolumeTypes.TYPE_NORMAL
9090
try:
9191
initial_state = SubvolumeOpSm.get_init_state(subvolume_type)
@@ -103,7 +103,8 @@ def create(self, size, isolate_nspace, pool, mode, uid, gid):
103103
'gid': gid,
104104
'data_pool': pool,
105105
'pool_namespace': self.namespace if isolate_nspace else None,
106-
'quota': size
106+
'quota': size,
107+
'earmark': earmark
107108
}
108109
self.set_attrs(subvol_path, attrs)
109110

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

Lines changed: 3 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):
157+
def create(self, size, isolate_nspace, pool, mode, uid, gid, earmark):
158158
subvolume_type = SubvolumeTypes.TYPE_NORMAL
159159
try:
160160
initial_state = SubvolumeOpSm.get_init_state(subvolume_type)
@@ -175,7 +175,8 @@ def create(self, size, isolate_nspace, pool, mode, uid, gid):
175175
'gid': gid,
176176
'data_pool': pool,
177177
'pool_namespace': self.namespace if isolate_nspace else None,
178-
'quota': size
178+
'quota': size,
179+
'earmark': earmark
179180
}
180181
self.set_attrs(subvol_path, attrs)
181182

0 commit comments

Comments
 (0)