Merge PR #67265 into tentacle

* refs/pull/67265/head:
	qa: deactivate snap-schedule to avoid race with snap delete
	qa: test for bad retention period specifiers
	qa: remove possibly created snapshots
	mgr/snap_schedule: restrict retention period multipliers set

Reviewed-by: Rishabh Dave <ridave@redhat.com>
This commit is contained in:
Patrick Donnelly 2026-07-18 19:29:10 -04:00
commit 4ee51693b3
No known key found for this signature in database
GPG Key ID: 053758C0A8A3CE2F
2 changed files with 36 additions and 19 deletions

View File

@ -196,15 +196,6 @@ class TestSnapSchedulesHelper(CephFSTestCase):
self.assertTrue(retention in json_res['retention'])
class TestSnapSchedules(TestSnapSchedulesHelper):
def remove_snapshots(self, dir_path):
snap_path = f'{dir_path}/.snap'
snapshots = self.mount_a.ls(path=snap_path)
for snapshot in snapshots:
snapshot_path = os.path.join(snap_path, snapshot)
log.debug(f'removing snapshot: {snapshot_path}')
self.mount_a.run_shell(['rmdir', snapshot_path])
def test_non_existent_snap_schedule_list(self):
"""Test listing snap schedules on a non-existing filesystem path failure"""
try:
@ -279,7 +270,7 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('remove', path=TestSnapSchedules.TEST_DIRECTORY)
# remove all scheduled snapshots
self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY)
self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
@ -328,7 +319,7 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('remove', path=TestSnapSchedules.TEST_DIRECTORY)
# remove all scheduled snapshots
self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY)
self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
@ -381,7 +372,7 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('remove', path=TestSnapSchedules.TEST_DIRECTORY)
# remove all scheduled snapshots
self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY)
self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
@ -448,7 +439,7 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
for d in testdirs:
self.fs_snap_schedule_cmd('remove', path=d, snap_schedule='1m')
self.remove_snapshots(d[1:])
self.remove_snapshots(d[1:], self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', d[1:]])
def test_snap_schedule_with_mgr_restart(self):
@ -485,7 +476,7 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
# cleanup
self.fs_snap_schedule_cmd('remove', path=testdir, snap_schedule='1m')
self.remove_snapshots(testdir[1:])
self.remove_snapshots(testdir[1:], self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', testdir[1:]])
def test_schedule_auto_deactivation_for_non_existent_path(self):
@ -541,7 +532,7 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('remove', path=test_dir)
# remove all scheduled snapshots
self.remove_snapshots(test_dir[1:])
self.remove_snapshots(test_dir[1:], self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir[1:]])
@ -608,20 +599,35 @@ class TestSnapSchedules(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', path=test_dir, snap_schedule='-1m')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('add', path=test_dir, snap_schedule='')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('add', path=test_dir, snap_schedule='5Y')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('add', path=test_dir, snap_schedule='4W')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('add', path=test_dir, snap_schedule='3D')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('add', path=test_dir, snap_schedule='2H')
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/minutes"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/hourly"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/daily"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/weekly"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/monthly"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/yearly"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
test_dir = TestSnapSchedulesSnapdir.TEST_DIRECTORY + "/bad_period_spec"
self.remove_snapshots(test_dir, self.get_snap_dir_name())
self.mount_a.run_shell(['rmdir', test_dir])
@ -751,6 +757,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self._verify_snap_schedule(self.CREATE_VERSION, 'sv05', 'mygrp05')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv05', 'mygrp05')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv05', '--group', 'mygrp05', path='.', fs='cephfs')
self.remove_snapshots(path, self.get_snap_dir_name())
self._fs_cmd('subvolume', 'rm', 'cephfs', 'sv05', '--group_name', 'mygrp05')
@ -766,6 +773,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', '--subvol', 'sv06', '--group', 'mygrp06', path='.', snap_schedule='1m', fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('list', '--subvol', 'sv06', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv06', '--group', 'mygrp06', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv06', '--group', 'mygrp06', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv06', 'mygrp06')
@ -783,6 +791,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', '--subvol', 'sv07', '--group', 'mygrp07', path='.', snap_schedule='1m', fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('list', '--group', 'mygrp07', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv07', '--group', 'mygrp07', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv07', '--group', 'mygrp07', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv07', 'mygrp07')
@ -799,6 +808,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', '--subvol', 'sv08', '--group', 'mygrp08', path='.', snap_schedule='1m', fs='cephfs')
self.fs_snap_schedule_cmd('list', '--subvol', 'sv08', '--group', 'mygrp08', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv08', '--group', 'mygrp08', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv08', '--group', 'mygrp08', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv08', 'mygrp08')
@ -816,6 +826,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', '--subvol', 'sv09', '--group', 'mygrp09', path='.', snap_schedule='1m', fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('retention', 'add', '--subvol', 'sv09', path='.', retention_spec_or_period='h', retention_count='5')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv09', '--group', 'mygrp09', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv09', '--group', 'mygrp09', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv09', 'mygrp09')
@ -833,6 +844,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', '--subvol', 'sv10', '--group', 'mygrp10', path='.', snap_schedule='1m', fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('retention', 'add', '--group', 'mygrp10', path='.', retention_spec_or_period='h', retention_count='5')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv10', '--group', 'mygrp10', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv10', '--group', 'mygrp10', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv10', 'mygrp10')
@ -849,6 +861,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('add', '--subvol', 'sv11', '--group', 'mygrp11', path='.', snap_schedule='1m', fs='cephfs')
self.fs_snap_schedule_cmd('retention', 'add', '--subvol', 'sv11', '--group', 'mygrp11', path='.', retention_spec_or_period='h', retention_count=5, fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv11', '--group', 'mygrp11', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv11', '--group', 'mygrp11', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv11', 'mygrp11')
@ -867,6 +880,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('retention', 'add', '--subvol', 'sv12', '--group', 'mygrp12', path='.', retention_spec_or_period='h', retention_count=5, fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('activate', '--subvol', 'sv12', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv12', '--group', 'mygrp12', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv12', '--group', 'mygrp12', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv12', 'mygrp12')
@ -885,6 +899,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('retention', 'add', '--subvol', 'sv13', '--group', 'mygrp13', path='.', retention_spec_or_period='h', retention_count=5, fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('activate', '--group', 'mygrp13', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv13', '--group', 'mygrp13', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv13', '--group', 'mygrp13', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv13', 'mygrp13')
@ -904,6 +919,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('activate', '--subvol', 'sv14', '--group', 'mygrp14', path='.', fs='cephfs')
self._verify_snap_schedule(self.CREATE_VERSION, 'sv14', 'mygrp14')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv14', '--group', 'mygrp14', path='.', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv14', 'mygrp14')
self.remove_snapshots(path, self.get_snap_dir_name())
@ -923,6 +939,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('activate', '--subvol', 'sv15', '--group', 'mygrp15', path='.', fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv15', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv15', '--group', 'mygrp15', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv15', '--group', 'mygrp15', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv15', 'mygrp15')
@ -942,6 +959,7 @@ class TestSnapSchedulesSubvolAndGroupArguments(TestSnapSchedulesHelper):
self.fs_snap_schedule_cmd('activate', '--subvol', 'sv16', '--group', 'mygrp16', path='.', fs='cephfs')
with self.assertRaises(CommandFailedError):
self.fs_snap_schedule_cmd('deactivate', '--group', 'mygrp16', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('deactivate', '--subvol', 'sv16', '--group', 'mygrp16', path='.', fs='cephfs')
self.fs_snap_schedule_cmd('remove', '--subvol', 'sv16', '--group', 'mygrp16', path='.', snap_schedule='1m', fs='cephfs')
path = self._get_subvol_snapdir_path(self.CREATE_VERSION, 'sv16', 'mygrp16')

View File

@ -55,10 +55,7 @@ def parse_timestamp(ts: str) -> datetime:
def parse_retention(retention: str) -> Dict[str, int]:
ret = {}
log.debug(f'parse_retention({retention})')
matches = re.findall(r'\d+[a-z]', retention)
for m in matches:
ret[m[-1]] = int(m[0:-1])
matches = re.findall(r'\d+[A-Z]', retention)
matches = re.findall(r'\d+[nmhdwMy]', retention)
for m in matches:
ret[m[-1]] = int(m[0:-1])
log.debug(f'parse_retention({retention}) -> {ret}')
@ -381,6 +378,8 @@ class Schedule(object):
if row is None:
raise ValueError(f'No schedule found for {path}')
retention = parse_retention(retention_spec)
if not retention:
raise ValueError(f'Retention spec {retention_spec} is invalid')
current = row['retention']
current_retention = json.loads(current)
for r, v in retention.items():