test_check.py 22 KB

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