test_check.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.borg import check as module
  5. from ..test_verbosity import insert_logging_mock
  6. def insert_execute_command_mock(command):
  7. flexmock(module).should_receive('execute_command').with_args(command).once()
  8. def insert_execute_command_never():
  9. flexmock(module).should_receive('execute_command').never()
  10. def test_parse_checks_returns_them_as_tuple():
  11. checks = module.parse_checks({'checks': [{'name': 'foo'}, {'name': 'bar'}]})
  12. assert checks == ('foo', 'bar')
  13. def test_parse_checks_with_missing_value_returns_defaults():
  14. checks = module.parse_checks({})
  15. assert checks == ('repository', 'archives')
  16. def test_parse_checks_with_empty_list_returns_defaults():
  17. checks = module.parse_checks({'checks': []})
  18. assert checks == ('repository', 'archives')
  19. def test_parse_checks_with_none_value_returns_defaults():
  20. checks = module.parse_checks({'checks': None})
  21. assert checks == ('repository', 'archives')
  22. def test_parse_checks_with_disabled_returns_no_checks():
  23. checks = module.parse_checks({'checks': [{'name': 'foo'}, {'name': 'disabled'}]})
  24. assert checks == ()
  25. def test_parse_checks_with_data_check_also_injects_archives():
  26. checks = module.parse_checks({'checks': [{'name': 'data'}]})
  27. assert checks == ('data', 'archives')
  28. def test_parse_checks_with_data_check_passes_through_archives():
  29. checks = module.parse_checks({'checks': [{'name': 'data'}, {'name': 'archives'}]})
  30. assert checks == ('data', 'archives')
  31. def test_parse_checks_prefers_override_checks_to_configured_checks():
  32. checks = module.parse_checks(
  33. {'checks': [{'name': 'archives'}]}, only_checks=['repository', 'extract']
  34. )
  35. assert checks == ('repository', 'extract')
  36. def test_parse_checks_with_override_data_check_also_injects_archives():
  37. checks = module.parse_checks({'checks': [{'name': 'extract'}]}, only_checks=['data'])
  38. assert checks == ('data', 'archives')
  39. @pytest.mark.parametrize(
  40. 'frequency,expected_result',
  41. (
  42. (None, None),
  43. ('always', None),
  44. ('1 hour', module.datetime.timedelta(hours=1)),
  45. ('2 hours', module.datetime.timedelta(hours=2)),
  46. ('1 day', module.datetime.timedelta(days=1)),
  47. ('2 days', module.datetime.timedelta(days=2)),
  48. ('1 week', module.datetime.timedelta(weeks=1)),
  49. ('2 weeks', module.datetime.timedelta(weeks=2)),
  50. ('1 month', module.datetime.timedelta(weeks=4)),
  51. ('2 months', module.datetime.timedelta(weeks=8)),
  52. ('1 year', module.datetime.timedelta(days=365)),
  53. ('2 years', module.datetime.timedelta(days=365 * 2)),
  54. ),
  55. )
  56. def test_parse_frequency_parses_into_timedeltas(frequency, expected_result):
  57. assert module.parse_frequency(frequency) == expected_result
  58. @pytest.mark.parametrize(
  59. 'frequency', ('sometime', 'x days', '3 decades',),
  60. )
  61. def test_parse_frequency_raises_on_parse_error(frequency):
  62. with pytest.raises(ValueError):
  63. module.parse_frequency(frequency)
  64. def test_filter_checks_on_frequency_without_config_uses_default_checks():
  65. flexmock(module).should_receive('parse_frequency').and_return(
  66. module.datetime.timedelta(weeks=4)
  67. )
  68. flexmock(module).should_receive('make_check_time_path')
  69. flexmock(module).should_receive('read_check_time').and_return(None)
  70. assert module.filter_checks_on_frequency(
  71. location_config={},
  72. consistency_config={},
  73. borg_repository_id='repo',
  74. checks=('repository', 'archives'),
  75. ) == ('repository', 'archives')
  76. def test_filter_checks_on_frequency_retains_unconfigured_check():
  77. assert module.filter_checks_on_frequency(
  78. location_config={}, consistency_config={}, borg_repository_id='repo', checks=('data',),
  79. ) == ('data',)
  80. def test_filter_checks_on_frequency_retains_check_without_frequency():
  81. flexmock(module).should_receive('parse_frequency').and_return(None)
  82. assert module.filter_checks_on_frequency(
  83. location_config={},
  84. consistency_config={'checks': [{'name': 'archives'}]},
  85. borg_repository_id='repo',
  86. checks=('archives',),
  87. ) == ('archives',)
  88. def test_filter_checks_on_frequency_retains_check_with_elapsed_frequency():
  89. flexmock(module).should_receive('parse_frequency').and_return(
  90. module.datetime.timedelta(hours=1)
  91. )
  92. flexmock(module).should_receive('make_check_time_path')
  93. flexmock(module).should_receive('read_check_time').and_return(
  94. module.datetime.datetime(year=module.datetime.MINYEAR, month=1, day=1)
  95. )
  96. assert module.filter_checks_on_frequency(
  97. location_config={},
  98. consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  99. borg_repository_id='repo',
  100. checks=('archives',),
  101. ) == ('archives',)
  102. def test_filter_checks_on_frequency_retains_check_with_missing_check_time_file():
  103. flexmock(module).should_receive('parse_frequency').and_return(
  104. module.datetime.timedelta(hours=1)
  105. )
  106. flexmock(module).should_receive('make_check_time_path')
  107. flexmock(module).should_receive('read_check_time').and_return(None)
  108. assert module.filter_checks_on_frequency(
  109. location_config={},
  110. consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  111. borg_repository_id='repo',
  112. checks=('archives',),
  113. ) == ('archives',)
  114. def test_filter_checks_on_frequency_skips_check_with_unelapsed_frequency():
  115. flexmock(module).should_receive('parse_frequency').and_return(
  116. module.datetime.timedelta(hours=1)
  117. )
  118. flexmock(module).should_receive('make_check_time_path')
  119. flexmock(module).should_receive('read_check_time').and_return(module.datetime.datetime.now())
  120. assert (
  121. module.filter_checks_on_frequency(
  122. location_config={},
  123. consistency_config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
  124. borg_repository_id='repo',
  125. checks=('archives',),
  126. )
  127. == ()
  128. )
  129. def test_make_check_flags_with_repository_check_returns_flag():
  130. flags = module.make_check_flags(('repository',))
  131. assert flags == ('--repository-only',)
  132. def test_make_check_flags_with_archives_check_returns_flag():
  133. flags = module.make_check_flags(('archives',))
  134. assert flags == ('--archives-only',)
  135. def test_make_check_flags_with_data_check_returns_flag():
  136. flags = module.make_check_flags(('data',))
  137. assert flags == ('--verify-data',)
  138. def test_make_check_flags_with_extract_omits_extract_flag():
  139. flags = module.make_check_flags(('extract',))
  140. assert flags == ()
  141. def test_make_check_flags_with_default_checks_and_default_prefix_returns_default_flags():
  142. flags = module.make_check_flags(('repository', 'archives'), prefix=module.DEFAULT_PREFIX)
  143. assert flags == ('--prefix', module.DEFAULT_PREFIX)
  144. def test_make_check_flags_with_all_checks_and_default_prefix_returns_default_flags():
  145. flags = module.make_check_flags(
  146. ('repository', 'archives', 'extract'), prefix=module.DEFAULT_PREFIX
  147. )
  148. assert flags == ('--prefix', module.DEFAULT_PREFIX)
  149. def test_make_check_flags_with_archives_check_and_last_includes_last_flag():
  150. flags = module.make_check_flags(('archives',), check_last=3)
  151. assert flags == ('--archives-only', '--last', '3')
  152. def test_make_check_flags_with_repository_check_and_last_omits_last_flag():
  153. flags = module.make_check_flags(('repository',), check_last=3)
  154. assert flags == ('--repository-only',)
  155. def test_make_check_flags_with_default_checks_and_last_includes_last_flag():
  156. flags = module.make_check_flags(('repository', 'archives'), check_last=3)
  157. assert flags == ('--last', '3')
  158. def test_make_check_flags_with_archives_check_and_prefix_includes_prefix_flag():
  159. flags = module.make_check_flags(('archives',), prefix='foo-')
  160. assert flags == ('--archives-only', '--prefix', 'foo-')
  161. def test_make_check_flags_with_archives_check_and_empty_prefix_omits_prefix_flag():
  162. flags = module.make_check_flags(('archives',), prefix='')
  163. assert flags == ('--archives-only',)
  164. def test_make_check_flags_with_archives_check_and_none_prefix_omits_prefix_flag():
  165. flags = module.make_check_flags(('archives',), prefix=None)
  166. assert flags == ('--archives-only',)
  167. def test_make_check_flags_with_repository_check_and_prefix_omits_prefix_flag():
  168. flags = module.make_check_flags(('repository',), prefix='foo-')
  169. assert flags == ('--repository-only',)
  170. def test_make_check_flags_with_default_checks_and_prefix_includes_prefix_flag():
  171. flags = module.make_check_flags(('repository', 'archives'), prefix='foo-')
  172. assert flags == ('--prefix', 'foo-')
  173. def test_read_check_time_does_not_raise():
  174. flexmock(module.os).should_receive('stat').and_return(flexmock(st_mtime=123))
  175. assert module.read_check_time('/path')
  176. def test_read_check_time_on_missing_file_does_not_raise():
  177. flexmock(module.os).should_receive('stat').and_raise(FileNotFoundError)
  178. assert module.read_check_time('/path') is None
  179. def test_check_archives_with_progress_calls_borg_with_progress_parameter():
  180. checks = ('repository',)
  181. consistency_config = {'check_last': None}
  182. flexmock(module).should_receive('parse_checks')
  183. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  184. flexmock(module.info).should_receive('display_archives_info').and_return(
  185. '{"repository": {"id": "repo"}}'
  186. )
  187. flexmock(module).should_receive('make_check_flags').and_return(())
  188. flexmock(module).should_receive('execute_command').never()
  189. flexmock(module).should_receive('execute_command').with_args(
  190. ('borg', 'check', '--progress', 'repo'), output_file=module.DO_NOT_CAPTURE
  191. ).once()
  192. flexmock(module).should_receive('make_check_time_path')
  193. flexmock(module).should_receive('write_check_time')
  194. module.check_archives(
  195. repository='repo',
  196. location_config={},
  197. storage_config={},
  198. consistency_config=consistency_config,
  199. progress=True,
  200. )
  201. def test_check_archives_with_repair_calls_borg_with_repair_parameter():
  202. checks = ('repository',)
  203. consistency_config = {'check_last': None}
  204. flexmock(module).should_receive('parse_checks')
  205. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  206. flexmock(module.info).should_receive('display_archives_info').and_return(
  207. '{"repository": {"id": "repo"}}'
  208. )
  209. flexmock(module).should_receive('make_check_flags').and_return(())
  210. flexmock(module).should_receive('execute_command').never()
  211. flexmock(module).should_receive('execute_command').with_args(
  212. ('borg', 'check', '--repair', 'repo'), output_file=module.DO_NOT_CAPTURE
  213. ).once()
  214. flexmock(module).should_receive('make_check_time_path')
  215. flexmock(module).should_receive('write_check_time')
  216. module.check_archives(
  217. repository='repo',
  218. location_config={},
  219. storage_config={},
  220. consistency_config=consistency_config,
  221. repair=True,
  222. )
  223. @pytest.mark.parametrize(
  224. 'checks',
  225. (
  226. ('repository',),
  227. ('archives',),
  228. ('repository', 'archives'),
  229. ('repository', 'archives', 'other'),
  230. ),
  231. )
  232. def test_check_archives_calls_borg_with_parameters(checks):
  233. check_last = flexmock()
  234. consistency_config = {'check_last': check_last}
  235. flexmock(module).should_receive('parse_checks')
  236. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  237. flexmock(module.info).should_receive('display_archives_info').and_return(
  238. '{"repository": {"id": "repo"}}'
  239. )
  240. flexmock(module).should_receive('make_check_flags').with_args(
  241. checks, check_last, module.DEFAULT_PREFIX
  242. ).and_return(())
  243. insert_execute_command_mock(('borg', 'check', 'repo'))
  244. flexmock(module).should_receive('make_check_time_path')
  245. flexmock(module).should_receive('write_check_time')
  246. module.check_archives(
  247. repository='repo',
  248. location_config={},
  249. storage_config={},
  250. consistency_config=consistency_config,
  251. )
  252. def test_check_archives_with_json_error_raises():
  253. checks = ('archives',)
  254. check_last = flexmock()
  255. consistency_config = {'check_last': check_last}
  256. flexmock(module).should_receive('parse_checks')
  257. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  258. flexmock(module.info).should_receive('display_archives_info').and_return(
  259. '{"unexpected": {"id": "repo"}}'
  260. )
  261. with pytest.raises(ValueError):
  262. module.check_archives(
  263. repository='repo',
  264. location_config={},
  265. storage_config={},
  266. consistency_config=consistency_config,
  267. )
  268. def test_check_archives_with_missing_json_keys_raises():
  269. checks = ('archives',)
  270. check_last = flexmock()
  271. consistency_config = {'check_last': check_last}
  272. flexmock(module).should_receive('parse_checks')
  273. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  274. flexmock(module.info).should_receive('display_archives_info').and_return('{invalid JSON')
  275. with pytest.raises(ValueError):
  276. module.check_archives(
  277. repository='repo',
  278. location_config={},
  279. storage_config={},
  280. consistency_config=consistency_config,
  281. )
  282. def test_check_archives_with_extract_check_calls_extract_only():
  283. checks = ('extract',)
  284. check_last = flexmock()
  285. consistency_config = {'check_last': check_last}
  286. flexmock(module).should_receive('parse_checks')
  287. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  288. flexmock(module.info).should_receive('display_archives_info').and_return(
  289. '{"repository": {"id": "repo"}}'
  290. )
  291. flexmock(module).should_receive('make_check_flags').never()
  292. flexmock(module.extract).should_receive('extract_last_archive_dry_run').once()
  293. flexmock(module).should_receive('write_check_time')
  294. insert_execute_command_never()
  295. module.check_archives(
  296. repository='repo',
  297. location_config={},
  298. storage_config={},
  299. consistency_config=consistency_config,
  300. )
  301. def test_check_archives_with_log_info_calls_borg_with_info_parameter():
  302. checks = ('repository',)
  303. consistency_config = {'check_last': None}
  304. flexmock(module).should_receive('parse_checks')
  305. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  306. flexmock(module.info).should_receive('display_archives_info').and_return(
  307. '{"repository": {"id": "repo"}}'
  308. )
  309. flexmock(module).should_receive('make_check_flags').and_return(())
  310. insert_logging_mock(logging.INFO)
  311. insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
  312. flexmock(module).should_receive('make_check_time_path')
  313. flexmock(module).should_receive('write_check_time')
  314. module.check_archives(
  315. repository='repo',
  316. location_config={},
  317. storage_config={},
  318. consistency_config=consistency_config,
  319. )
  320. def test_check_archives_with_log_debug_calls_borg_with_debug_parameter():
  321. checks = ('repository',)
  322. consistency_config = {'check_last': None}
  323. flexmock(module).should_receive('parse_checks')
  324. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  325. flexmock(module.info).should_receive('display_archives_info').and_return(
  326. '{"repository": {"id": "repo"}}'
  327. )
  328. flexmock(module).should_receive('make_check_flags').and_return(())
  329. insert_logging_mock(logging.DEBUG)
  330. insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
  331. flexmock(module).should_receive('make_check_time_path')
  332. flexmock(module).should_receive('write_check_time')
  333. module.check_archives(
  334. repository='repo',
  335. location_config={},
  336. storage_config={},
  337. consistency_config=consistency_config,
  338. )
  339. def test_check_archives_without_any_checks_bails():
  340. consistency_config = {'check_last': None}
  341. flexmock(module).should_receive('parse_checks')
  342. flexmock(module).should_receive('filter_checks_on_frequency').and_return(())
  343. flexmock(module.info).should_receive('display_archives_info').and_return(
  344. '{"repository": {"id": "repo"}}'
  345. )
  346. insert_execute_command_never()
  347. module.check_archives(
  348. repository='repo',
  349. location_config={},
  350. storage_config={},
  351. consistency_config=consistency_config,
  352. )
  353. def test_check_archives_with_local_path_calls_borg_via_local_path():
  354. checks = ('repository',)
  355. check_last = flexmock()
  356. consistency_config = {'check_last': check_last}
  357. flexmock(module).should_receive('parse_checks')
  358. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  359. flexmock(module.info).should_receive('display_archives_info').and_return(
  360. '{"repository": {"id": "repo"}}'
  361. )
  362. flexmock(module).should_receive('make_check_flags').with_args(
  363. checks, check_last, module.DEFAULT_PREFIX
  364. ).and_return(())
  365. insert_execute_command_mock(('borg1', 'check', 'repo'))
  366. flexmock(module).should_receive('make_check_time_path')
  367. flexmock(module).should_receive('write_check_time')
  368. module.check_archives(
  369. repository='repo',
  370. location_config={},
  371. storage_config={},
  372. consistency_config=consistency_config,
  373. local_path='borg1',
  374. )
  375. def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters():
  376. checks = ('repository',)
  377. check_last = flexmock()
  378. consistency_config = {'check_last': check_last}
  379. flexmock(module).should_receive('parse_checks')
  380. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  381. flexmock(module.info).should_receive('display_archives_info').and_return(
  382. '{"repository": {"id": "repo"}}'
  383. )
  384. flexmock(module).should_receive('make_check_flags').with_args(
  385. checks, check_last, module.DEFAULT_PREFIX
  386. ).and_return(())
  387. insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
  388. flexmock(module).should_receive('make_check_time_path')
  389. flexmock(module).should_receive('write_check_time')
  390. module.check_archives(
  391. repository='repo',
  392. location_config={},
  393. storage_config={},
  394. consistency_config=consistency_config,
  395. remote_path='borg1',
  396. )
  397. def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
  398. checks = ('repository',)
  399. check_last = flexmock()
  400. consistency_config = {'check_last': check_last}
  401. flexmock(module).should_receive('parse_checks')
  402. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  403. flexmock(module.info).should_receive('display_archives_info').and_return(
  404. '{"repository": {"id": "repo"}}'
  405. )
  406. flexmock(module).should_receive('make_check_flags').with_args(
  407. checks, check_last, module.DEFAULT_PREFIX
  408. ).and_return(())
  409. insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
  410. flexmock(module).should_receive('make_check_time_path')
  411. flexmock(module).should_receive('write_check_time')
  412. module.check_archives(
  413. repository='repo',
  414. location_config={},
  415. storage_config={'lock_wait': 5},
  416. consistency_config=consistency_config,
  417. )
  418. def test_check_archives_with_retention_prefix():
  419. checks = ('repository',)
  420. check_last = flexmock()
  421. prefix = 'foo-'
  422. consistency_config = {'check_last': check_last, 'prefix': prefix}
  423. flexmock(module).should_receive('parse_checks')
  424. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  425. flexmock(module.info).should_receive('display_archives_info').and_return(
  426. '{"repository": {"id": "repo"}}'
  427. )
  428. flexmock(module).should_receive('make_check_flags').with_args(
  429. checks, check_last, prefix
  430. ).and_return(())
  431. insert_execute_command_mock(('borg', 'check', 'repo'))
  432. flexmock(module).should_receive('make_check_time_path')
  433. flexmock(module).should_receive('write_check_time')
  434. module.check_archives(
  435. repository='repo',
  436. location_config={},
  437. storage_config={},
  438. consistency_config=consistency_config,
  439. )
  440. def test_check_archives_with_extra_borg_options_calls_borg_with_extra_options():
  441. checks = ('repository',)
  442. consistency_config = {'check_last': None}
  443. flexmock(module).should_receive('parse_checks')
  444. flexmock(module).should_receive('filter_checks_on_frequency').and_return(checks)
  445. flexmock(module.info).should_receive('display_archives_info').and_return(
  446. '{"repository": {"id": "repo"}}'
  447. )
  448. flexmock(module).should_receive('make_check_flags').and_return(())
  449. insert_execute_command_mock(('borg', 'check', '--extra', '--options', 'repo'))
  450. flexmock(module).should_receive('make_check_time_path')
  451. flexmock(module).should_receive('write_check_time')
  452. module.check_archives(
  453. repository='repo',
  454. location_config={},
  455. storage_config={'extra_borg_options': {'check': '--extra --options'}},
  456. consistency_config=consistency_config,
  457. )