test_check.py 21 KB

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