]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/cephfs/test_snap_schedules.py
7 from tasks
.cephfs
.cephfs_test_case
import CephFSTestCase
8 from teuthology
.exceptions
import CommandFailedError
9 from datetime
import datetime
, timedelta
11 log
= logging
.getLogger(__name__
)
13 def extract_schedule_and_retention_spec(spec
=[]):
14 schedule
= set([s
[0] for s
in spec
])
15 retention
= set([s
[1] for s
in spec
])
16 return (schedule
, retention
)
18 def seconds_upto_next_schedule(time_from
, timo
):
20 return ((int(ts
/ 60) * 60) + timo
) - ts
22 class TestSnapSchedules(CephFSTestCase
):
25 TEST_VOLUME_NAME
= 'snap_vol'
26 TEST_DIRECTORY
= 'snap_test_dir1'
28 # this should be in sync with snap_schedule format
29 SNAPSHOT_TS_FORMAT
= '%Y-%m-%d-%H_%M_%S'
31 def check_scheduled_snapshot(self
, exec_time
, timo
):
33 delta
= now
- exec_time
34 log
.debug(f
'exec={exec_time}, now = {now}, timo = {timo}')
35 # tolerate snapshot existance in the range [-5,+5]
36 self
.assertTrue((delta
<= timo
+ 5) and (delta
>= timo
- 5))
38 def _fs_cmd(self
, *args
):
39 return self
.mgr_cluster
.mon_manager
.raw_cluster_cmd("fs", *args
)
41 def fs_snap_schedule_cmd(self
, *args
, **kwargs
):
42 fs
= kwargs
.pop('fs', self
.volname
)
44 for name
, val
in kwargs
.items():
45 args
+= (f
'--{name}', str(val
))
46 res
= self
._fs
_cmd
('snap-schedule', *args
)
47 log
.debug(f
'res={res}')
50 def _create_or_reuse_test_volume(self
):
51 result
= json
.loads(self
._fs
_cmd
("volume", "ls"))
53 self
.vol_created
= True
54 self
.volname
= TestSnapSchedules
.TEST_VOLUME_NAME
55 self
._fs
_cmd
("volume", "create", self
.volname
)
57 self
.volname
= result
[0]['name']
59 def _enable_snap_schedule(self
):
60 return self
.mgr_cluster
.mon_manager
.raw_cluster_cmd("mgr", "module", "enable", "snap_schedule")
62 def _disable_snap_schedule(self
):
63 return self
.mgr_cluster
.mon_manager
.raw_cluster_cmd("mgr", "module", "disable", "snap_schedule")
65 def _allow_minute_granularity_snapshots(self
):
66 self
.config_set('mgr', 'mgr/snap_schedule/allow_m_granularity', True)
69 super(TestSnapSchedules
, self
).setUp()
71 self
.vol_created
= False
72 self
._create
_or
_reuse
_test
_volume
()
75 # used to figure out which snapshots are created/deleted
76 self
.snapshots
= set()
77 self
._enable
_snap
_schedule
()
78 self
._allow
_minute
_granularity
_snapshots
()
82 self
._delete
_test
_volume
()
83 self
._disable
_snap
_schedule
()
84 super(TestSnapSchedules
, self
).tearDown()
86 def _schedule_to_timeout(self
, schedule
):
88 period
= int(schedule
[0:-1])
92 return period
* 60 * 60
94 return period
* 60 * 60 * 24
96 return period
* 60 * 60 * 24 * 7
98 raise RuntimeError('schedule multiplier not recognized')
100 def add_snap_create_cbk(self
, cbk
):
101 self
.create_cbks
.append(cbk
)
102 def remove_snap_create_cbk(self
, cbk
):
103 self
.create_cbks
.remove(cbk
)
105 def add_snap_remove_cbk(self
, cbk
):
106 self
.remove_cbks
.append(cbk
)
107 def remove_snap_remove_cbk(self
, cbk
):
108 self
.remove_cbks
.remove(cbk
)
110 def assert_if_not_verified(self
):
111 self
.assertTrue(len(self
.create_cbks
) == 0 and len(self
.remove_cbks
) == 0)
113 def verify(self
, dir_path
, max_trials
):
115 snap_path
= "{0}/.snap".format(dir_path
)
116 while (len(self
.create_cbks
) or len(self
.remove_cbks
)) and trials
< max_trials
:
117 snapshots
= set(self
.mount_a
.ls(path
=snap_path
))
118 added
= snapshots
- self
.snapshots
119 removed
= self
.snapshots
- snapshots
121 for cbk
in list(self
.create_cbks
):
122 res
= cbk(list(added
))
124 self
.remove_snap_create_cbk(cbk
)
127 for cbk
in list(self
.remove_cbks
):
128 res
= cbk(list(removed
))
130 self
.remove_snap_remove_cbk(cbk
)
132 self
.snapshots
= snapshots
136 def calc_wait_time_and_snap_name(self
, snap_sched_exec_epoch
, schedule
):
137 timo
= self
._schedule
_to
_timeout
(schedule
)
138 # calculate wait time upto the next minute
139 wait_timo
= seconds_upto_next_schedule(snap_sched_exec_epoch
, timo
)
141 # expected "scheduled" snapshot name
142 ts_name
= (datetime
.utcfromtimestamp(snap_sched_exec_epoch
)
143 + timedelta(seconds
=wait_timo
)).strftime(TestSnapSchedules
.SNAPSHOT_TS_FORMAT
)
144 return (wait_timo
, ts_name
)
146 def verify_schedule(self
, dir_path
, schedules
, retentions
=[]):
147 log
.debug(f
'expected_schedule: {schedules}, expected_retention: {retentions}')
149 result
= self
.fs_snap_schedule_cmd('list', path
=dir_path
, format
='json')
150 json_res
= json
.loads(result
)
151 log
.debug(f
'json_res: {json_res}')
153 for schedule
in schedules
:
154 self
.assertTrue(schedule
in json_res
['schedule'])
155 for retention
in retentions
:
156 self
.assertTrue(retention
in json_res
['retention'])
158 def remove_snapshots(self
, dir_path
):
159 snap_path
= f
'{dir_path}/.snap'
161 snapshots
= self
.mount_a
.ls(path
=snap_path
)
162 for snapshot
in snapshots
:
163 snapshot_path
= os
.path
.join(snap_path
, snapshot
)
164 log
.debug(f
'removing snapshot: {snapshot_path}')
165 self
.mount_a
.run_shell(['rmdir', snapshot_path
])
167 def test_non_existent_snap_schedule_list(self
):
168 """Test listing snap schedules on a non-existing filesystem path failure"""
170 self
.fs_snap_schedule_cmd('list', path
=TestSnapSchedules
.TEST_DIRECTORY
)
171 except CommandFailedError
as ce
:
172 if ce
.exitstatus
!= errno
.ENOENT
:
173 raise RuntimeError('incorrect errno when listing a non-existing snap schedule')
175 raise RuntimeError('expected "fs snap-schedule list" to fail')
177 def test_non_existent_schedule(self
):
178 """Test listing non-existing snap schedules failure"""
179 self
.mount_a
.run_shell(['mkdir', '-p', TestSnapSchedules
.TEST_DIRECTORY
])
182 self
.fs_snap_schedule_cmd('list', path
=TestSnapSchedules
.TEST_DIRECTORY
)
183 except CommandFailedError
as ce
:
184 if ce
.exitstatus
!= errno
.ENOENT
:
185 raise RuntimeError('incorrect errno when listing a non-existing snap schedule')
187 raise RuntimeError('expected "fs snap-schedule list" returned fail')
189 self
.mount_a
.run_shell(['rmdir', TestSnapSchedules
.TEST_DIRECTORY
])
191 def test_snap_schedule_list_post_schedule_remove(self
):
192 """Test listing snap schedules post removal of a schedule"""
193 self
.mount_a
.run_shell(['mkdir', '-p', TestSnapSchedules
.TEST_DIRECTORY
])
195 self
.fs_snap_schedule_cmd('add', path
=TestSnapSchedules
.TEST_DIRECTORY
, snap_schedule
='1h')
197 self
.fs_snap_schedule_cmd('remove', path
=TestSnapSchedules
.TEST_DIRECTORY
)
200 self
.fs_snap_schedule_cmd('list', path
=TestSnapSchedules
.TEST_DIRECTORY
)
201 except CommandFailedError
as ce
:
202 if ce
.exitstatus
!= errno
.ENOENT
:
203 raise RuntimeError('incorrect errno when listing a non-existing snap schedule')
205 raise RuntimeError('"fs snap-schedule list" returned error')
207 self
.mount_a
.run_shell(['rmdir', TestSnapSchedules
.TEST_DIRECTORY
])
209 def test_snap_schedule(self
):
210 """Test existence of a scheduled snapshot"""
211 self
.mount_a
.run_shell(['mkdir', '-p', TestSnapSchedules
.TEST_DIRECTORY
])
213 # set a schedule on the dir
214 self
.fs_snap_schedule_cmd('add', path
=TestSnapSchedules
.TEST_DIRECTORY
, snap_schedule
='1M')
215 exec_time
= time
.time()
217 timo
, snap_sfx
= self
.calc_wait_time_and_snap_name(exec_time
, '1M')
218 log
.debug(f
'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx} in ~{timo}s...')
219 to_wait
= timo
+ 2 # some leeway to avoid false failures...
221 # verify snapshot schedule
222 self
.verify_schedule(TestSnapSchedules
.TEST_DIRECTORY
, ['1M'])
224 def verify_added(snaps_added
):
225 log
.debug(f
'snapshots added={snaps_added}')
226 self
.assertEquals(len(snaps_added
), 1)
227 snapname
= snaps_added
[0]
228 if snapname
.startswith('scheduled-') and snapname
[10:] == snap_sfx
:
229 self
.check_scheduled_snapshot(exec_time
, timo
)
232 self
.add_snap_create_cbk(verify_added
)
233 self
.verify(TestSnapSchedules
.TEST_DIRECTORY
, to_wait
)
234 self
.assert_if_not_verified()
236 # remove snapshot schedule
237 self
.fs_snap_schedule_cmd('remove', path
=TestSnapSchedules
.TEST_DIRECTORY
)
239 # remove all scheduled snapshots
240 self
.remove_snapshots(TestSnapSchedules
.TEST_DIRECTORY
)
242 self
.mount_a
.run_shell(['rmdir', TestSnapSchedules
.TEST_DIRECTORY
])
244 def test_multi_snap_schedule(self
):
245 """Test exisitence of multiple scheduled snapshots"""
246 self
.mount_a
.run_shell(['mkdir', '-p', TestSnapSchedules
.TEST_DIRECTORY
])
248 # set schedules on the dir
249 self
.fs_snap_schedule_cmd('add', path
=TestSnapSchedules
.TEST_DIRECTORY
, snap_schedule
='1M')
250 self
.fs_snap_schedule_cmd('add', path
=TestSnapSchedules
.TEST_DIRECTORY
, snap_schedule
='2M')
251 exec_time
= time
.time()
253 timo_1
, snap_sfx_1
= self
.calc_wait_time_and_snap_name(exec_time
, '1M')
254 log
.debug(f
'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx_1} in ~{timo_1}s...')
255 timo_2
, snap_sfx_2
= self
.calc_wait_time_and_snap_name(exec_time
, '2M')
256 log
.debug(f
'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx_2} in ~{timo_2}s...')
257 to_wait
= timo_2
+ 2 # use max timeout
259 # verify snapshot schedule
260 self
.verify_schedule(TestSnapSchedules
.TEST_DIRECTORY
, ['1M', '2M'])
262 def verify_added_1(snaps_added
):
263 log
.debug(f
'snapshots added={snaps_added}')
264 self
.assertEquals(len(snaps_added
), 1)
265 snapname
= snaps_added
[0]
266 if snapname
.startswith('scheduled-') and snapname
[10:] == snap_sfx_1
:
267 self
.check_scheduled_snapshot(exec_time
, timo_1
)
270 def verify_added_2(snaps_added
):
271 log
.debug(f
'snapshots added={snaps_added}')
272 self
.assertEquals(len(snaps_added
), 1)
273 snapname
= snaps_added
[0]
274 if snapname
.startswith('scheduled-') and snapname
[10:] == snap_sfx_2
:
275 self
.check_scheduled_snapshot(exec_time
, timo_2
)
278 self
.add_snap_create_cbk(verify_added_1
)
279 self
.add_snap_create_cbk(verify_added_2
)
280 self
.verify(TestSnapSchedules
.TEST_DIRECTORY
, to_wait
)
281 self
.assert_if_not_verified()
283 # remove snapshot schedule
284 self
.fs_snap_schedule_cmd('remove', path
=TestSnapSchedules
.TEST_DIRECTORY
)
286 # remove all scheduled snapshots
287 self
.remove_snapshots(TestSnapSchedules
.TEST_DIRECTORY
)
289 self
.mount_a
.run_shell(['rmdir', TestSnapSchedules
.TEST_DIRECTORY
])
291 def test_snap_schedule_with_retention(self
):
292 """Test scheduled snapshots along with rentention policy"""
293 self
.mount_a
.run_shell(['mkdir', '-p', TestSnapSchedules
.TEST_DIRECTORY
])
295 # set a schedule on the dir
296 self
.fs_snap_schedule_cmd('add', path
=TestSnapSchedules
.TEST_DIRECTORY
, snap_schedule
='1M')
297 self
.fs_snap_schedule_cmd('retention', 'add', path
=TestSnapSchedules
.TEST_DIRECTORY
, retention_spec_or_period
='1M')
298 exec_time
= time
.time()
300 timo_1
, snap_sfx
= self
.calc_wait_time_and_snap_name(exec_time
, '1M')
301 log
.debug(f
'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx} in ~{timo_1}s...')
302 to_wait
= timo_1
+ 2 # some leeway to avoid false failures...
304 # verify snapshot schedule
305 self
.verify_schedule(TestSnapSchedules
.TEST_DIRECTORY
, ['1M'], retentions
=[{'M':1}])
307 def verify_added(snaps_added
):
308 log
.debug(f
'snapshots added={snaps_added}')
309 self
.assertEquals(len(snaps_added
), 1)
310 snapname
= snaps_added
[0]
311 if snapname
.startswith('scheduled-') and snapname
[10:] == snap_sfx
:
312 self
.check_scheduled_snapshot(exec_time
, timo_1
)
315 self
.add_snap_create_cbk(verify_added
)
316 self
.verify(TestSnapSchedules
.TEST_DIRECTORY
, to_wait
)
317 self
.assert_if_not_verified()
319 timo_2
= timo_1
+ 60 # expected snapshot removal timeout
320 def verify_removed(snaps_removed
):
321 log
.debug(f
'snapshots removed={snaps_removed}')
322 self
.assertEquals(len(snaps_removed
), 1)
323 snapname
= snaps_removed
[0]
324 if snapname
.startswith('scheduled-') and snapname
[10:] == snap_sfx
:
325 self
.check_scheduled_snapshot(exec_time
, timo_2
)
328 log
.debug(f
'expecting removal of snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx} in ~{timo_2}s...')
330 self
.add_snap_remove_cbk(verify_removed
)
331 self
.verify(TestSnapSchedules
.TEST_DIRECTORY
, to_wait
+2)
332 self
.assert_if_not_verified()
334 # remove snapshot schedule
335 self
.fs_snap_schedule_cmd('remove', path
=TestSnapSchedules
.TEST_DIRECTORY
)
337 # remove all scheduled snapshots
338 self
.remove_snapshots(TestSnapSchedules
.TEST_DIRECTORY
)
340 self
.mount_a
.run_shell(['rmdir', TestSnapSchedules
.TEST_DIRECTORY
])