]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/cephfs/test_snap_schedules.py
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / qa / tasks / cephfs / test_snap_schedules.py
CommitLineData
f67539c2
TL
1import os
2import json
3import time
4import errno
5import logging
6
7from tasks.cephfs.cephfs_test_case import CephFSTestCase
8from teuthology.exceptions import CommandFailedError
9from datetime import datetime, timedelta
10
11log = logging.getLogger(__name__)
12
13def 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)
17
18def seconds_upto_next_schedule(time_from, timo):
19 ts = int(time_from)
20 return ((int(ts / 60) * 60) + timo) - ts
21
22class TestSnapSchedules(CephFSTestCase):
23 CLIENTS_REQUIRED = 1
24
25 TEST_VOLUME_NAME = 'snap_vol'
26 TEST_DIRECTORY = 'snap_test_dir1'
27
28 # this should be in sync with snap_schedule format
29 SNAPSHOT_TS_FORMAT = '%Y-%m-%d-%H_%M_%S'
30
31 def check_scheduled_snapshot(self, exec_time, timo):
32 now = time.time()
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))
37
38 def _fs_cmd(self, *args):
39 return self.mgr_cluster.mon_manager.raw_cluster_cmd("fs", *args)
40
41 def fs_snap_schedule_cmd(self, *args):
42 args = list(args)
43 args.append(f'fs={self.volname}')
44 res = self._fs_cmd('snap-schedule', *args)
45 log.debug(f'res={res}')
46 return res
47
48 def _create_or_reuse_test_volume(self):
49 result = json.loads(self._fs_cmd("volume", "ls"))
50 if len(result) == 0:
51 self.vol_created = True
52 self.volname = TestSnapSchedules.TEST_VOLUME_NAME
53 self._fs_cmd("volume", "create", self.volname)
54 else:
55 self.volname = result[0]['name']
56
57 def _enable_snap_schedule(self):
58 return self.mgr_cluster.mon_manager.raw_cluster_cmd("mgr", "module", "enable", "snap_schedule")
59
60 def _disable_snap_schedule(self):
61 return self.mgr_cluster.mon_manager.raw_cluster_cmd("mgr", "module", "disable", "snap_schedule")
62
63 def _allow_minute_granularity_snapshots(self):
64 self.config_set('mgr', 'mgr/snap_schedule/allow_m_granularity', True)
65
66 def setUp(self):
67 super(TestSnapSchedules, self).setUp()
68 self.volname = None
69 self.vol_created = False
70 self._create_or_reuse_test_volume()
71 self.create_cbks = []
72 self.remove_cbks = []
73 # used to figure out which snapshots are created/deleted
74 self.snapshots = set()
75 self._enable_snap_schedule()
76 self._allow_minute_granularity_snapshots()
77
78 def tearDown(self):
79 if self.vol_created:
80 self._delete_test_volume()
81 self._disable_snap_schedule()
82 super(TestSnapSchedules, self).tearDown()
83
84 def _schedule_to_timeout(self, schedule):
85 mult = schedule[-1]
86 period = int(schedule[0:-1])
87 if mult == 'M':
88 return period * 60
89 elif mult == 'h':
90 return period * 60 * 60
91 elif mult == 'd':
92 return period * 60 * 60 * 24
93 elif mult == 'w':
94 return period * 60 * 60 * 24 * 7
95 else:
96 raise RuntimeError('schedule multiplier not recognized')
97
98 def add_snap_create_cbk(self, cbk):
99 self.create_cbks.append(cbk)
100 def remove_snap_create_cbk(self, cbk):
101 self.create_cbks.remove(cbk)
102
103 def add_snap_remove_cbk(self, cbk):
104 self.remove_cbks.append(cbk)
105 def remove_snap_remove_cbk(self, cbk):
106 self.remove_cbks.remove(cbk)
107
108 def assert_if_not_verified(self):
109 self.assertTrue(len(self.create_cbks) == 0 and len(self.remove_cbks) == 0)
110
111 def verify(self, dir_path, max_trials):
112 trials = 0
113 snap_path = "{0}/.snap".format(dir_path)
114 while (len(self.create_cbks) or len(self.remove_cbks)) and trials < max_trials:
115 snapshots = set(self.mount_a.ls(path=snap_path))
116 added = snapshots - self.snapshots
117 removed = self.snapshots - snapshots
118 if added:
119 for cbk in list(self.create_cbks):
120 res = cbk(list(added))
121 if res:
122 self.remove_snap_create_cbk(cbk)
123 break
124 if removed:
125 for cbk in list(self.remove_cbks):
126 res = cbk(list(removed))
127 if res:
128 self.remove_snap_remove_cbk(cbk)
129 break
130 self.snapshots = snapshots
131 trials += 1
132 time.sleep(1)
133
134 def calc_wait_time_and_snap_name(self, snap_sched_exec_epoch, schedule):
135 timo = self._schedule_to_timeout(schedule)
136 # calculate wait time upto the next minute
137 wait_timo = seconds_upto_next_schedule(snap_sched_exec_epoch, timo)
138
139 # expected "scheduled" snapshot name
140 ts_name = (datetime.utcfromtimestamp(snap_sched_exec_epoch)
141 + timedelta(seconds=wait_timo)).strftime(TestSnapSchedules.SNAPSHOT_TS_FORMAT)
142 return (wait_timo, ts_name)
143
144 def verify_schedule(self, dir_path, schedules, retentions=[]):
145 log.debug(f'expected_schedule: {schedules}, expected_retention: {retentions}')
146
147 result = self.fs_snap_schedule_cmd('list', f'path={dir_path}', 'format=json')
148 json_res = json.loads(result)
149 log.debug(f'json_res: {json_res}')
150
151 for schedule in schedules:
152 self.assertTrue(schedule in json_res['schedule'])
153 for retention in retentions:
154 self.assertTrue(retention in json_res['retention'])
155
156 def remove_snapshots(self, dir_path):
157 snap_path = f'{dir_path}/.snap'
158
159 snapshots = self.mount_a.ls(path=snap_path)
160 for snapshot in snapshots:
161 snapshot_path = os.path.join(snap_path, snapshot)
162 log.debug(f'removing snapshot: {snapshot_path}')
163 self.mount_a.run_shell(['rmdir', snapshot_path])
164
165 def test_non_existent_snap_schedule_list(self):
166 """Test listing snap schedules on a non-existing filesystem path failure"""
167 try:
168 self.fs_snap_schedule_cmd('list', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'format=json')
169 except CommandFailedError as ce:
170 if ce.exitstatus != errno.ENOENT:
171 raise RuntimeError('incorrect errno when listing a non-existing snap schedule')
172 else:
173 raise RuntimeError('expected "fs snap-schedule list" to fail')
174
175 def test_non_existent_schedule(self):
176 """Test listing non-existing snap schedules failure"""
177 self.mount_a.run_shell(['mkdir', '-p', TestSnapSchedules.TEST_DIRECTORY])
178
179 try:
180 self.fs_snap_schedule_cmd('list', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'format=json')
181 except CommandFailedError as ce:
182 if ce.exitstatus != errno.ENOENT:
183 raise RuntimeError('incorrect errno when listing a non-existing snap schedule')
184 else:
185 raise RuntimeError('expected "fs snap-schedule list" returned fail')
186
187 self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
188
189 def test_snap_schedule_list_post_schedule_remove(self):
190 """Test listing snap schedules post removal of a schedule"""
191 self.mount_a.run_shell(['mkdir', '-p', TestSnapSchedules.TEST_DIRECTORY])
192
193 self.fs_snap_schedule_cmd('add', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'snap-schedule=1h')
194
195 self.fs_snap_schedule_cmd('remove', f'path={TestSnapSchedules.TEST_DIRECTORY}')
196
197 try:
198 self.fs_snap_schedule_cmd('list', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'format=json')
199 except CommandFailedError as ce:
200 if ce.exitstatus != errno.ENOENT:
201 raise RuntimeError('incorrect errno when listing a non-existing snap schedule')
202 else:
203 raise RuntimeError('"fs snap-schedule list" returned error')
204
205 self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
206
207 def test_snap_schedule(self):
208 """Test existence of a scheduled snapshot"""
209 self.mount_a.run_shell(['mkdir', '-p', TestSnapSchedules.TEST_DIRECTORY])
210
211 # set a schedule on the dir
212 self.fs_snap_schedule_cmd('add', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'snap-schedule=1M')
213 exec_time = time.time()
214
215 timo, snap_sfx = self.calc_wait_time_and_snap_name(exec_time, '1M')
216 log.debug(f'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx} in ~{timo}s...')
217 to_wait = timo + 2 # some leeway to avoid false failures...
218
219 # verify snapshot schedule
220 self.verify_schedule(TestSnapSchedules.TEST_DIRECTORY, ['1M'])
221
222 def verify_added(snaps_added):
223 log.debug(f'snapshots added={snaps_added}')
224 self.assertEquals(len(snaps_added), 1)
225 snapname = snaps_added[0]
226 if snapname.startswith('scheduled-') and snapname[10:] == snap_sfx:
227 self.check_scheduled_snapshot(exec_time, timo)
228 return True
229 return False
230 self.add_snap_create_cbk(verify_added)
231 self.verify(TestSnapSchedules.TEST_DIRECTORY, to_wait)
232 self.assert_if_not_verified()
233
234 # remove snapshot schedule
235 self.fs_snap_schedule_cmd('remove', f'path={TestSnapSchedules.TEST_DIRECTORY}')
236
237 # remove all scheduled snapshots
238 self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY)
239
240 self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
241
242 def test_multi_snap_schedule(self):
243 """Test exisitence of multiple scheduled snapshots"""
244 self.mount_a.run_shell(['mkdir', '-p', TestSnapSchedules.TEST_DIRECTORY])
245
246 # set schedules on the dir
247 self.fs_snap_schedule_cmd('add', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'snap-schedule=1M')
248 self.fs_snap_schedule_cmd('add', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'snap-schedule=2M')
249 exec_time = time.time()
250
251 timo_1, snap_sfx_1 = self.calc_wait_time_and_snap_name(exec_time, '1M')
252 log.debug(f'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx_1} in ~{timo_1}s...')
253 timo_2, snap_sfx_2 = self.calc_wait_time_and_snap_name(exec_time, '2M')
254 log.debug(f'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx_2} in ~{timo_2}s...')
255 to_wait = timo_2 + 2 # use max timeout
256
257 # verify snapshot schedule
258 self.verify_schedule(TestSnapSchedules.TEST_DIRECTORY, ['1M', '2M'])
259
260 def verify_added_1(snaps_added):
261 log.debug(f'snapshots added={snaps_added}')
262 self.assertEquals(len(snaps_added), 1)
263 snapname = snaps_added[0]
264 if snapname.startswith('scheduled-') and snapname[10:] == snap_sfx_1:
265 self.check_scheduled_snapshot(exec_time, timo_1)
266 return True
267 return False
268 def verify_added_2(snaps_added):
269 log.debug(f'snapshots added={snaps_added}')
270 self.assertEquals(len(snaps_added), 1)
271 snapname = snaps_added[0]
272 if snapname.startswith('scheduled-') and snapname[10:] == snap_sfx_2:
273 self.check_scheduled_snapshot(exec_time, timo_2)
274 return True
275 return False
276 self.add_snap_create_cbk(verify_added_1)
277 self.add_snap_create_cbk(verify_added_2)
278 self.verify(TestSnapSchedules.TEST_DIRECTORY, to_wait)
279 self.assert_if_not_verified()
280
281 # remove snapshot schedule
282 self.fs_snap_schedule_cmd('remove', f'path={TestSnapSchedules.TEST_DIRECTORY}')
283
284 # remove all scheduled snapshots
285 self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY)
286
287 self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])
288
289 def test_snap_schedule_with_retention(self):
290 """Test scheduled snapshots along with rentention policy"""
291 self.mount_a.run_shell(['mkdir', '-p', TestSnapSchedules.TEST_DIRECTORY])
292
293 # set a schedule on the dir
294 self.fs_snap_schedule_cmd('add', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'snap-schedule=1M')
295 self.fs_snap_schedule_cmd('retention', 'add', f'path={TestSnapSchedules.TEST_DIRECTORY}', 'retention-spec-or-period=1M')
296 exec_time = time.time()
297
298 timo_1, snap_sfx = self.calc_wait_time_and_snap_name(exec_time, '1M')
299 log.debug(f'expecting snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx} in ~{timo_1}s...')
300 to_wait = timo_1 + 2 # some leeway to avoid false failures...
301
302 # verify snapshot schedule
303 self.verify_schedule(TestSnapSchedules.TEST_DIRECTORY, ['1M'], retentions=[{'M':1}])
304
305 def verify_added(snaps_added):
306 log.debug(f'snapshots added={snaps_added}')
307 self.assertEquals(len(snaps_added), 1)
308 snapname = snaps_added[0]
309 if snapname.startswith('scheduled-') and snapname[10:] == snap_sfx:
310 self.check_scheduled_snapshot(exec_time, timo_1)
311 return True
312 return False
313 self.add_snap_create_cbk(verify_added)
314 self.verify(TestSnapSchedules.TEST_DIRECTORY, to_wait)
315 self.assert_if_not_verified()
316
317 timo_2 = timo_1 + 60 # expected snapshot removal timeout
318 def verify_removed(snaps_removed):
319 log.debug(f'snapshots removed={snaps_removed}')
320 self.assertEquals(len(snaps_removed), 1)
321 snapname = snaps_removed[0]
322 if snapname.startswith('scheduled-') and snapname[10:] == snap_sfx:
323 self.check_scheduled_snapshot(exec_time, timo_2)
324 return True
325 return False
326 log.debug(f'expecting removal of snap {TestSnapSchedules.TEST_DIRECTORY}/.snap/scheduled-{snap_sfx} in ~{timo_2}s...')
327 to_wait = timo_2
328 self.add_snap_remove_cbk(verify_removed)
329 self.verify(TestSnapSchedules.TEST_DIRECTORY, to_wait+2)
330 self.assert_if_not_verified()
331
332 # remove snapshot schedule
333 self.fs_snap_schedule_cmd('remove', f'path={TestSnapSchedules.TEST_DIRECTORY}')
334
335 # remove all scheduled snapshots
336 self.remove_snapshots(TestSnapSchedules.TEST_DIRECTORY)
337
338 self.mount_a.run_shell(['rmdir', TestSnapSchedules.TEST_DIRECTORY])