Skip to content

Commit 435306d

Browse files
committed
Merge PR ceph#52670 into main
* refs/pull/52670/head: doc: add the reject the clone when threads are not available feature in the document qa: add test cases for the support to reject clones feature mgr/volumes: support to reject CephFS clones if cloner threads are not available Reviewed-by: Kotresh Hiremath Ravishankar <[email protected]> Reviewed-by: Venky Shankar <[email protected]>
2 parents c66cb5e + 6a44322 commit 435306d

File tree

8 files changed

+245
-12
lines changed

8 files changed

+245
-12
lines changed

PendingReleaseNotes

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
140140
isn't scalable. So we have removed the 'network_ping_times' section from
141141
the output. Details in the tracker: https://tracker.ceph.com/issues/57460
142142

143+
* CephFS: The `subvolume snapshot clone` command now depends on the config option
144+
`snapshot_clone_no_wait` which is used to reject the clone operation when
145+
all the cloner threads are busy. This config option is enabled by default which means
146+
that if no cloner threads are free, the clone request errors out with EAGAIN.
147+
The value of the config option can be fetched by using:
148+
`ceph config get mgr mgr/volumes/snapshot_clone_no_wait`
149+
and it can be disabled by using:
150+
`ceph config set mgr mgr/volumes/snapshot_clone_no_wait false`
151+
143152
>=18.0.0
144153

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

doc/cephfs/fs-volumes.rst

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,8 @@ To initiate a clone operation use:
579579

580580
ceph fs subvolume snapshot clone <vol_name> <subvol_name> <snap_name> <target_subvol_name>
581581

582+
.. note:: ``subvolume snapshot clone`` command depends upon the above mentioned config option ``snapshot_clone_no_wait``
583+
582584
If a snapshot (source subvolume) is a part of non-default group, the group name needs to be specified:
583585

584586
.. prompt:: bash #
@@ -597,12 +599,6 @@ Similar to specifying a pool layout when creating a subvolume, pool layout can b
597599

598600
ceph fs subvolume snapshot clone <vol_name> <subvol_name> <snap_name> <target_subvol_name> --pool_layout <pool_layout>
599601

600-
Configure the maximum number of concurrent clones. The default is 4:
601-
602-
.. prompt:: bash #
603-
604-
ceph config set mgr mgr/volumes/max_concurrent_clones <value>
605-
606602
To check the status of a clone operation use:
607603

608604
.. prompt:: bash #
@@ -728,6 +724,29 @@ On successful cancellation, the cloned subvolume is moved to the ``canceled`` st
728724

729725
.. note:: The canceled cloned may be deleted by supplying the ``--force`` option to the `fs subvolume rm` command.
730726

727+
Configurables
728+
~~~~~~~~~~~~~
729+
730+
Configure the maximum number of concurrent clone operations. The default is 4:
731+
732+
.. prompt:: bash #
733+
734+
ceph config set mgr mgr/volumes/max_concurrent_clones <value>
735+
736+
Configure the snapshot_clone_no_wait option :
737+
738+
.. prompt:: bash #
739+
740+
``snapshot_clone_no_wait`` config option is used to reject the clone creation request when the cloner threads
741+
( which can be configured using above option i.e. ``max_concurrent_clones``) are not available.
742+
It is enabled by default i.e. the value set is True, whereas it can be configured by using below command.
743+
744+
ceph config set mgr mgr/volumes/snapshot_clone_no_wait <bool>
745+
746+
The current value of ``snapshot_clone_no_wait`` can be fetched by using below command.
747+
748+
ceph config get mgr mgr/volumes/snapshot_clone_no_wait
749+
731750

732751
.. _subvol-pinning:
733752

qa/tasks/cephfs/test_volumes.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7000,6 +7000,11 @@ def test_subvolume_snapshot_clone_cancel_pending(self):
70007000
# snapshot subvolume
70017001
self._fs_cmd("subvolume", "snapshot", "create", self.volname, subvolume, snapshot)
70027002

7003+
# Disable the snapshot_clone_no_wait config option
7004+
self.config_set('mgr', 'mgr/volumes/snapshot_clone_no_wait', False)
7005+
threads_available = self.config_get('mgr', 'mgr/volumes/snapshot_clone_no_wait')
7006+
self.assertEqual(threads_available, 'false')
7007+
70037008
# schedule clones
70047009
for clone in clones:
70057010
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone)
@@ -7485,6 +7490,159 @@ def test_subvolume_under_group_snapshot_clone(self):
74857490
# verify trash dir is clean
74867491
self._wait_for_trash_empty()
74877492

7493+
def test_subvolume_snapshot_clone_with_no_wait_enabled(self):
7494+
subvolume = self._gen_subvol_name()
7495+
snapshot = self._gen_subvol_snap_name()
7496+
clone1, clone2, clone3 = self._gen_subvol_clone_name(3)
7497+
7498+
# create subvolume
7499+
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--mode=777")
7500+
7501+
# do some IO
7502+
self._do_subvolume_io(subvolume, number_of_files=10)
7503+
7504+
# snapshot subvolume
7505+
self._fs_cmd("subvolume", "snapshot", "create", self.volname, subvolume, snapshot)
7506+
7507+
# Decrease number of cloner threads
7508+
self.config_set('mgr', 'mgr/volumes/max_concurrent_clones', 2)
7509+
max_concurrent_clones = int(self.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
7510+
self.assertEqual(max_concurrent_clones, 2)
7511+
7512+
# Enable the snapshot_clone_no_wait config option
7513+
self.config_set('mgr', 'mgr/volumes/snapshot_clone_no_wait', True)
7514+
threads_available = self.config_get('mgr', 'mgr/volumes/snapshot_clone_no_wait')
7515+
self.assertEqual(threads_available, 'true')
7516+
7517+
# Insert delay of 15 seconds at the beginning of the snapshot clone
7518+
self.config_set('mgr', 'mgr/volumes/snapshot_clone_delay', 15)
7519+
7520+
# schedule a clone1
7521+
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone1)
7522+
7523+
# schedule a clone2
7524+
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone2)
7525+
7526+
# schedule a clone3
7527+
cmd_ret = self.run_ceph_cmd(
7528+
args=["fs", "subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone3], check_status=False, stdout=StringIO(),
7529+
stderr=StringIO())
7530+
self.assertEqual(cmd_ret.returncode, errno.EAGAIN, "Expecting EAGAIN error")
7531+
7532+
# check clone1 status
7533+
self._wait_for_clone_to_complete(clone1)
7534+
7535+
# verify clone1
7536+
self._verify_clone(subvolume, snapshot, clone1)
7537+
7538+
# check clone2 status
7539+
self._wait_for_clone_to_complete(clone2)
7540+
7541+
# verify clone2
7542+
self._verify_clone(subvolume, snapshot, clone2)
7543+
7544+
# schedule clone3 , it should be successful this time
7545+
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone3)
7546+
7547+
# check clone3 status
7548+
self._wait_for_clone_to_complete(clone3)
7549+
7550+
# verify clone3
7551+
self._verify_clone(subvolume, snapshot, clone3)
7552+
7553+
# set number of cloner threads to default
7554+
self.config_set('mgr', 'mgr/volumes/max_concurrent_clones', 4)
7555+
max_concurrent_clones = int(self.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
7556+
self.assertEqual(max_concurrent_clones, 4)
7557+
7558+
# set the snapshot_clone_delay to default
7559+
self.config_set('mgr', 'mgr/volumes/snapshot_clone_delay', 0)
7560+
7561+
# remove snapshot
7562+
self._fs_cmd("subvolume", "snapshot", "rm", self.volname, subvolume, snapshot)
7563+
7564+
# remove subvolumes
7565+
self._fs_cmd("subvolume", "rm", self.volname, subvolume)
7566+
self._fs_cmd("subvolume", "rm", self.volname, clone1)
7567+
self._fs_cmd("subvolume", "rm", self.volname, clone2)
7568+
self._fs_cmd("subvolume", "rm", self.volname, clone3)
7569+
7570+
# verify trash dir is clean
7571+
self._wait_for_trash_empty()
7572+
7573+
def test_subvolume_snapshot_clone_with_no_wait_not_enabled(self):
7574+
subvolume = self._gen_subvol_name()
7575+
snapshot = self._gen_subvol_snap_name()
7576+
clone1, clone2, clone3 = self._gen_subvol_clone_name(3)
7577+
7578+
# create subvolume
7579+
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--mode=777")
7580+
7581+
# do some IO
7582+
self._do_subvolume_io(subvolume, number_of_files=10)
7583+
7584+
# snapshot subvolume
7585+
self._fs_cmd("subvolume", "snapshot", "create", self.volname, subvolume, snapshot)
7586+
7587+
# Disable the snapshot_clone_no_wait config option
7588+
self.config_set('mgr', 'mgr/volumes/snapshot_clone_no_wait', False)
7589+
threads_available = self.config_get('mgr', 'mgr/volumes/snapshot_clone_no_wait')
7590+
self.assertEqual(threads_available, 'false')
7591+
7592+
# Decrease number of cloner threads
7593+
self.config_set('mgr', 'mgr/volumes/max_concurrent_clones', 2)
7594+
max_concurrent_clones = int(self.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
7595+
self.assertEqual(max_concurrent_clones, 2)
7596+
7597+
# schedule a clone1
7598+
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone1)
7599+
7600+
# schedule a clone2
7601+
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone2)
7602+
7603+
# schedule a clone3
7604+
self._fs_cmd("subvolume", "snapshot", "clone", self.volname, subvolume, snapshot, clone3)
7605+
7606+
# check clone1 status
7607+
self._wait_for_clone_to_complete(clone1)
7608+
7609+
# verify clone1
7610+
self._verify_clone(subvolume, snapshot, clone1)
7611+
7612+
# check clone2 status
7613+
self._wait_for_clone_to_complete(clone2)
7614+
7615+
# verify clone2
7616+
self._verify_clone(subvolume, snapshot, clone2)
7617+
7618+
# check clone3 status
7619+
self._wait_for_clone_to_complete(clone3)
7620+
7621+
# verify clone3
7622+
self._verify_clone(subvolume, snapshot, clone3)
7623+
7624+
# set the snapshot_clone_no_wait config option to default
7625+
self.config_set('mgr', 'mgr/volumes/snapshot_clone_no_wait', True)
7626+
threads_available = self.config_get('mgr', 'mgr/volumes/snapshot_clone_no_wait')
7627+
self.assertEqual(threads_available, 'true')
7628+
7629+
# set number of cloner threads to default
7630+
self.config_set('mgr', 'mgr/volumes/max_concurrent_clones', 4)
7631+
max_concurrent_clones = int(self.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
7632+
self.assertEqual(max_concurrent_clones, 4)
7633+
7634+
# remove snapshot
7635+
self._fs_cmd("subvolume", "snapshot", "rm", self.volname, subvolume, snapshot)
7636+
7637+
# remove subvolumes
7638+
self._fs_cmd("subvolume", "rm", self.volname, subvolume)
7639+
self._fs_cmd("subvolume", "rm", self.volname, clone1)
7640+
self._fs_cmd("subvolume", "rm", self.volname, clone2)
7641+
self._fs_cmd("subvolume", "rm", self.volname, clone3)
7642+
7643+
# verify trash dir is clean
7644+
self._wait_for_trash_empty()
7645+
74887646

74897647
class TestMisc(TestVolumesHelper):
74907648
"""Miscellaneous tests related to FS volume, subvolume group, and subvolume operations."""

qa/workunits/fs/full/subvolume_clone.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ ceph fs subvolume snapshot create cephfs sub_0 snap_0
5959
# Set clone snapshot delay
6060
ceph config set mgr mgr/volumes/snapshot_clone_delay 15
6161

62+
# Disable the snapshot_clone_no_wait config option
63+
ceph config set mgr mgr/volumes/snapshot_clone_no_wait false
64+
6265
# Schedule few clones, some would fail with no space
6366
for i in $(eval echo {1..$NUM_CLONES});do ceph fs subvolume snapshot clone cephfs sub_0 snap_0 clone_$i;done
6467

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,10 @@ class Cloner(AsyncJobs):
337337
this relies on a simple state machine (which mimics states from SubvolumeOpSm class) as
338338
the driver. file types supported are directories, symbolic links and regular files.
339339
"""
340-
def __init__(self, volume_client, tp_size, snapshot_clone_delay):
340+
def __init__(self, volume_client, tp_size, snapshot_clone_delay, clone_no_wait):
341341
self.vc = volume_client
342342
self.snapshot_clone_delay = snapshot_clone_delay
343+
self.snapshot_clone_no_wait = clone_no_wait
343344
self.state_table = {
344345
SubvolumeStates.STATE_PENDING : handle_clone_pending,
345346
SubvolumeStates.STATE_INPROGRESS : handle_clone_in_progress,
@@ -355,6 +356,9 @@ def reconfigure_max_concurrent_clones(self, tp_size):
355356
def reconfigure_snapshot_clone_delay(self, timeout):
356357
self.snapshot_clone_delay = timeout
357358

359+
def reconfigure_reject_clones(self, clone_no_wait):
360+
self.snapshot_clone_no_wait = clone_no_wait
361+
358362
def is_clone_cancelable(self, clone_state):
359363
return not (SubvolumeOpSm.is_complete_state(clone_state) or SubvolumeOpSm.is_failed_state(clone_state))
360364

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
import orchestrator
1010

1111
from .lock import GlobalLock
12-
from ..exception import VolumeException
12+
from ..exception import VolumeException, IndexException
1313
from ..fs_util import create_pool, remove_pool, rename_pool, create_filesystem, \
1414
remove_filesystem, rename_filesystem, create_mds, volume_exists, listdir
1515
from .trash import Trash
1616
from mgr_util import open_filesystem, CephfsConnectionException
17+
from .clone_index import open_clone_index
1718

1819
log = logging.getLogger(__name__)
1920

@@ -260,6 +261,30 @@ def get_pending_subvol_deletions_count(fs, path):
260261
return {'pending_subvolume_deletions': num_pending_subvol_del}
261262

262263

264+
def get_all_pending_clones_count(self, mgr, vol_spec):
265+
pending_clones_cnt = 0
266+
index_path = ""
267+
fs_map = mgr.get('fs_map')
268+
for fs in fs_map['filesystems']:
269+
volname = fs['mdsmap']['fs_name']
270+
try:
271+
with open_volume(self, volname) as fs_handle:
272+
with open_clone_index(fs_handle, vol_spec) as index:
273+
index_path = index.path.decode('utf-8')
274+
pending_clones_cnt = pending_clones_cnt \
275+
+ len(listdir(fs_handle, index_path,
276+
filter_entries=None, filter_files=False))
277+
except IndexException as e:
278+
if e.errno == -errno.ENOENT:
279+
continue
280+
raise VolumeException(-e.args[0], e.args[1])
281+
except VolumeException as ve:
282+
log.error("error fetching clone entry for volume '{0}' ({1})".format(volname, ve))
283+
raise ve
284+
285+
return pending_clones_cnt
286+
287+
263288
@contextmanager
264289
def open_volume(vc, volname):
265290
"""

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
from .operations.group import open_group, create_group, remove_group, \
1414
open_group_unique, set_group_attrs
1515
from .operations.volume import create_volume, delete_volume, rename_volume, \
16-
list_volumes, open_volume, get_pool_names, get_pool_ids, get_pending_subvol_deletions_count
16+
list_volumes, open_volume, get_pool_names, get_pool_ids, \
17+
get_pending_subvol_deletions_count, get_all_pending_clones_count
1718
from .operations.subvolume import open_subvol, create_subvol, remove_subvol, \
1819
create_clone
1920

2021
from .vol_spec import VolSpec
21-
from .exception import VolumeException, ClusterError, ClusterTimeout, EvictionError
22+
from .exception import VolumeException, ClusterError, ClusterTimeout, \
23+
EvictionError, IndexException
2224
from .async_cloner import Cloner
2325
from .purge_queue import ThreadPoolPurgeQueueMixin
2426
from .operations.template import SubvolumeOpType
@@ -53,7 +55,8 @@ def __init__(self, mgr):
5355
super().__init__(mgr)
5456
# volume specification
5557
self.volspec = VolSpec(mgr.rados.conf_get('client_snapdir'))
56-
self.cloner = Cloner(self, self.mgr.max_concurrent_clones, self.mgr.snapshot_clone_delay)
58+
self.cloner = Cloner(self, self.mgr.max_concurrent_clones, self.mgr.snapshot_clone_delay,
59+
self.mgr.snapshot_clone_no_wait)
5760
self.purge_queue = ThreadPoolPurgeQueueMixin(self, 4)
5861
# on startup, queue purge job for available volumes to kickstart
5962
# purge for leftover subvolume entries in trash. note that, if the
@@ -764,6 +767,10 @@ def clone_subvolume_snapshot(self, **kwargs):
764767
s_groupname = kwargs['group_name']
765768

766769
try:
770+
if self.mgr.snapshot_clone_no_wait and \
771+
get_all_pending_clones_count(self, self.mgr, self.volspec) >= self.mgr.max_concurrent_clones:
772+
raise(VolumeException(-errno.EAGAIN, "all cloner threads are busy, please try again later"))
773+
767774
with open_volume(self, volname) as fs_handle:
768775
with open_group(fs_handle, self.volspec, s_groupname) as s_group:
769776
with open_subvol(self.mgr, fs_handle, self.volspec, s_group, s_subvolname, SubvolumeOpType.CLONE_SOURCE) as s_subvolume:

src/pybind/mgr/volumes/module.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,12 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
489489
'periodic_async_work',
490490
type='bool',
491491
default=False,
492-
desc='Periodically check for async work')
492+
desc='Periodically check for async work'),
493+
Option(
494+
'snapshot_clone_no_wait',
495+
type='bool',
496+
default=True,
497+
desc='Reject subvolume clone request when cloner threads are busy')
493498
]
494499

495500
def __init__(self, *args, **kwargs):
@@ -498,6 +503,7 @@ def __init__(self, *args, **kwargs):
498503
self.max_concurrent_clones = None
499504
self.snapshot_clone_delay = None
500505
self.periodic_async_work = False
506+
self.snapshot_clone_no_wait = None
501507
self.lock = threading.Lock()
502508
super(Module, self).__init__(*args, **kwargs)
503509
# Initialize config option members
@@ -532,6 +538,8 @@ def config_notify(self):
532538
else:
533539
self.vc.cloner.unset_wakeup_timeout()
534540
self.vc.purge_queue.unset_wakeup_timeout()
541+
elif opt['name'] == "snapshot_clone_no_wait":
542+
self.vc.cloner.reconfigure_reject_clones(self.snapshot_clone_no_wait)
535543

536544
def handle_command(self, inbuf, cmd):
537545
handler_name = "_cmd_" + cmd['prefix'].replace(" ", "_")

0 commit comments

Comments
 (0)