test_restore.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066
  1. import pytest
  2. from flexmock import flexmock
  3. import borgmatic.actions.restore as module
  4. @pytest.mark.parametrize(
  5. 'first_dump,second_dump,expected_result',
  6. (
  7. (
  8. module.Dump('postgresql_databases', 'foo'),
  9. module.Dump('postgresql_databases', 'foo'),
  10. True,
  11. ),
  12. (
  13. module.Dump('postgresql_databases', 'foo'),
  14. module.Dump('postgresql_databases', 'bar'),
  15. False,
  16. ),
  17. (
  18. module.Dump('postgresql_databases', 'foo'),
  19. module.Dump('mariadb_databases', 'foo'),
  20. False,
  21. ),
  22. (module.Dump('postgresql_databases', 'foo'), module.Dump(module.UNSPECIFIED, 'foo'), True),
  23. (module.Dump('postgresql_databases', 'foo'), module.Dump(module.UNSPECIFIED, 'bar'), False),
  24. (
  25. module.Dump('postgresql_databases', module.UNSPECIFIED),
  26. module.Dump('postgresql_databases', 'foo'),
  27. True,
  28. ),
  29. (
  30. module.Dump('postgresql_databases', module.UNSPECIFIED),
  31. module.Dump('mariadb_databases', 'foo'),
  32. False,
  33. ),
  34. (
  35. module.Dump('postgresql_databases', 'foo', 'myhost'),
  36. module.Dump('postgresql_databases', 'foo', 'myhost'),
  37. True,
  38. ),
  39. (
  40. module.Dump('postgresql_databases', 'foo', 'myhost'),
  41. module.Dump('postgresql_databases', 'foo', 'otherhost'),
  42. False,
  43. ),
  44. (
  45. module.Dump('postgresql_databases', 'foo', 'myhost'),
  46. module.Dump('postgresql_databases', 'foo', module.UNSPECIFIED),
  47. True,
  48. ),
  49. (
  50. module.Dump('postgresql_databases', 'foo', 'myhost'),
  51. module.Dump('postgresql_databases', 'bar', module.UNSPECIFIED),
  52. False,
  53. ),
  54. (
  55. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  56. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  57. True,
  58. ),
  59. (
  60. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  61. module.Dump('postgresql_databases', 'foo', 'myhost', 4321),
  62. False,
  63. ),
  64. (
  65. module.Dump('postgresql_databases', 'foo', 'myhost', module.UNSPECIFIED),
  66. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  67. True,
  68. ),
  69. (
  70. module.Dump('postgresql_databases', 'foo', 'myhost', module.UNSPECIFIED),
  71. module.Dump('postgresql_databases', 'foo', 'otherhost', 1234),
  72. False,
  73. ),
  74. (
  75. module.Dump(
  76. module.UNSPECIFIED, module.UNSPECIFIED, module.UNSPECIFIED, module.UNSPECIFIED
  77. ),
  78. module.Dump('postgresql_databases', 'foo', 'myhost', 1234),
  79. True,
  80. ),
  81. ),
  82. )
  83. def test_dumps_match_compares_two_dumps_while_respecting_unspecified_values(
  84. first_dump, second_dump, expected_result
  85. ):
  86. assert module.dumps_match(first_dump, second_dump) == expected_result
  87. @pytest.mark.parametrize(
  88. 'dump,expected_result',
  89. (
  90. (
  91. module.Dump('postgresql_databases', 'foo'),
  92. 'foo@localhost (postgresql_databases)',
  93. ),
  94. (
  95. module.Dump(module.UNSPECIFIED, 'foo'),
  96. 'foo@localhost',
  97. ),
  98. (
  99. module.Dump('postgresql_databases', module.UNSPECIFIED),
  100. 'unspecified@localhost (postgresql_databases)',
  101. ),
  102. (
  103. module.Dump('postgresql_databases', 'foo', 'host'),
  104. 'foo@host (postgresql_databases)',
  105. ),
  106. (
  107. module.Dump('postgresql_databases', 'foo', module.UNSPECIFIED),
  108. 'foo (postgresql_databases)',
  109. ),
  110. (
  111. module.Dump('postgresql_databases', 'foo', 'host', 1234),
  112. 'foo@host:1234 (postgresql_databases)',
  113. ),
  114. (
  115. module.Dump('postgresql_databases', 'foo', module.UNSPECIFIED, 1234),
  116. 'foo@:1234 (postgresql_databases)',
  117. ),
  118. (
  119. module.Dump('postgresql_databases', 'foo', 'host', module.UNSPECIFIED),
  120. 'foo@host (postgresql_databases)',
  121. ),
  122. (
  123. module.Dump(
  124. module.UNSPECIFIED, module.UNSPECIFIED, module.UNSPECIFIED, module.UNSPECIFIED
  125. ),
  126. 'unspecified',
  127. ),
  128. ),
  129. )
  130. def test_render_dump_metadata_renders_dump_values_into_string(dump, expected_result):
  131. assert module.render_dump_metadata(dump) == expected_result
  132. def test_get_configured_data_source_matches_data_source_with_restore_dump():
  133. flexmock(module).should_receive('dumps_match').and_return(False)
  134. flexmock(module).should_receive('dumps_match').with_args(
  135. module.Dump('postgresql_databases', 'bar'),
  136. module.Dump('postgresql_databases', 'bar'),
  137. ).and_return(True)
  138. assert module.get_configured_data_source(
  139. config={
  140. 'other_databases': [{'name': 'other'}],
  141. 'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}],
  142. },
  143. restore_dump=module.Dump('postgresql_databases', 'bar'),
  144. ) == {'name': 'bar'}
  145. def test_get_configured_data_source_matches_nothing_when_nothing_configured():
  146. flexmock(module).should_receive('dumps_match').and_return(False)
  147. assert (
  148. module.get_configured_data_source(
  149. config={},
  150. restore_dump=module.Dump('postgresql_databases', 'quux'),
  151. )
  152. is None
  153. )
  154. def test_get_configured_data_source_matches_nothing_when_restore_dump_does_not_match_configuration():
  155. flexmock(module).should_receive('dumps_match').and_return(False)
  156. assert (
  157. module.get_configured_data_source(
  158. config={
  159. 'postgresql_databases': [{'name': 'foo'}],
  160. },
  161. restore_dump=module.Dump('postgresql_databases', 'quux'),
  162. )
  163. is None
  164. )
  165. def test_get_configured_data_source_with_multiple_matching_data_sources_errors():
  166. flexmock(module).should_receive('dumps_match').and_return(False)
  167. flexmock(module).should_receive('dumps_match').with_args(
  168. module.Dump('postgresql_databases', 'bar'),
  169. module.Dump('postgresql_databases', 'bar'),
  170. ).and_return(True)
  171. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  172. with pytest.raises(ValueError):
  173. module.get_configured_data_source(
  174. config={
  175. 'other_databases': [{'name': 'other'}],
  176. 'postgresql_databases': [
  177. {'name': 'foo'},
  178. {'name': 'bar'},
  179. {'name': 'bar', 'format': 'directory'},
  180. ],
  181. },
  182. restore_dump=module.Dump('postgresql_databases', 'bar'),
  183. )
  184. def test_strip_path_prefix_from_extracted_dump_destination_renames_first_matching_databases_subdirectory():
  185. flexmock(module.os).should_receive('walk').and_return(
  186. [
  187. ('/foo', flexmock(), flexmock()),
  188. ('/foo/bar', flexmock(), flexmock()),
  189. ('/foo/bar/postgresql_databases', flexmock(), flexmock()),
  190. ('/foo/bar/mariadb_databases', flexmock(), flexmock()),
  191. ]
  192. )
  193. flexmock(module.shutil).should_receive('move').with_args(
  194. '/foo/bar/postgresql_databases', '/run/user/0/borgmatic/postgresql_databases'
  195. ).once()
  196. flexmock(module.shutil).should_receive('move').with_args(
  197. '/foo/bar/mariadb_databases', '/run/user/0/borgmatic/mariadb_databases'
  198. ).never()
  199. module.strip_path_prefix_from_extracted_dump_destination('/foo', '/run/user/0/borgmatic')
  200. def test_restore_single_dump_extracts_and_restores_single_file_dump():
  201. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  202. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  203. 'make_data_source_dump_patterns', object, object, object, object, object
  204. ).and_return({'postgresql': flexmock()})
  205. flexmock(module.tempfile).should_receive('mkdtemp').never()
  206. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  207. 'convert_glob_patterns_to_borg_pattern'
  208. ).and_return(flexmock())
  209. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  210. flexmock()
  211. ).once()
  212. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  213. flexmock(module.shutil).should_receive('rmtree').never()
  214. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  215. function_name='restore_data_source_dump',
  216. config=object,
  217. log_prefix=object,
  218. hook_name=object,
  219. data_source=object,
  220. dry_run=object,
  221. extract_process=object,
  222. connection_params=object,
  223. borgmatic_runtime_directory=object,
  224. ).once()
  225. module.restore_single_dump(
  226. repository={'path': 'test.borg'},
  227. config=flexmock(),
  228. local_borg_version=flexmock(),
  229. global_arguments=flexmock(dry_run=False),
  230. local_path=None,
  231. remote_path=None,
  232. archive_name=flexmock(),
  233. hook_name='postgresql',
  234. data_source={'name': 'test', 'format': 'plain'},
  235. connection_params=flexmock(),
  236. borgmatic_runtime_directory='/run/borgmatic',
  237. )
  238. def test_restore_single_dump_extracts_and_restores_directory_dump():
  239. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  240. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  241. 'make_data_source_dump_patterns', object, object, object, object, object
  242. ).and_return({'postgresql': flexmock()})
  243. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
  244. '/run/user/0/borgmatic/tmp1234'
  245. )
  246. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  247. 'convert_glob_patterns_to_borg_pattern'
  248. ).and_return(flexmock())
  249. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  250. flexmock()
  251. ).once()
  252. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').once()
  253. flexmock(module.shutil).should_receive('rmtree').once()
  254. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  255. function_name='restore_data_source_dump',
  256. config=object,
  257. log_prefix=object,
  258. hook_name=object,
  259. data_source=object,
  260. dry_run=object,
  261. extract_process=object,
  262. connection_params=object,
  263. borgmatic_runtime_directory='/run/borgmatic',
  264. ).once()
  265. module.restore_single_dump(
  266. repository={'path': 'test.borg'},
  267. config=flexmock(),
  268. local_borg_version=flexmock(),
  269. global_arguments=flexmock(dry_run=False),
  270. local_path=None,
  271. remote_path=None,
  272. archive_name=flexmock(),
  273. hook_name='postgresql',
  274. data_source={'name': 'test', 'format': 'directory'},
  275. connection_params=flexmock(),
  276. borgmatic_runtime_directory='/run/borgmatic',
  277. )
  278. def test_restore_single_dump_with_directory_dump_error_cleans_up_temporary_directory():
  279. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  280. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  281. 'make_data_source_dump_patterns', object, object, object, object, object
  282. ).and_return({'postgresql': flexmock()})
  283. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
  284. '/run/user/0/borgmatic/tmp1234'
  285. )
  286. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  287. 'convert_glob_patterns_to_borg_pattern'
  288. ).and_return(flexmock())
  289. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_raise(
  290. ValueError
  291. ).once()
  292. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  293. flexmock(module.shutil).should_receive('rmtree').once()
  294. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  295. function_name='restore_data_source_dump',
  296. config=object,
  297. log_prefix=object,
  298. hook_name=object,
  299. data_source=object,
  300. dry_run=object,
  301. extract_process=object,
  302. connection_params=object,
  303. borgmatic_runtime_directory='/run/user/0/borgmatic/tmp1234',
  304. ).never()
  305. with pytest.raises(ValueError):
  306. module.restore_single_dump(
  307. repository={'path': 'test.borg'},
  308. config=flexmock(),
  309. local_borg_version=flexmock(),
  310. global_arguments=flexmock(dry_run=False),
  311. local_path=None,
  312. remote_path=None,
  313. archive_name=flexmock(),
  314. hook_name='postgresql',
  315. data_source={'name': 'test', 'format': 'directory'},
  316. connection_params=flexmock(),
  317. borgmatic_runtime_directory='/run/borgmatic',
  318. )
  319. def test_restore_single_dump_with_directory_dump_and_dry_run_skips_directory_move_and_cleanup():
  320. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  321. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  322. 'make_data_source_dump_patterns', object, object, object, object, object
  323. ).and_return({'postgresql': flexmock()})
  324. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return('/run/borgmatic/tmp1234')
  325. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  326. 'convert_glob_patterns_to_borg_pattern'
  327. ).and_return(flexmock())
  328. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  329. flexmock()
  330. ).once()
  331. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  332. flexmock(module.shutil).should_receive('rmtree').never()
  333. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  334. function_name='restore_data_source_dump',
  335. config=object,
  336. log_prefix=object,
  337. hook_name=object,
  338. data_source=object,
  339. dry_run=object,
  340. extract_process=object,
  341. connection_params=object,
  342. borgmatic_runtime_directory='/run/borgmatic',
  343. ).once()
  344. module.restore_single_dump(
  345. repository={'path': 'test.borg'},
  346. config=flexmock(),
  347. local_borg_version=flexmock(),
  348. global_arguments=flexmock(dry_run=True),
  349. local_path=None,
  350. remote_path=None,
  351. archive_name=flexmock(),
  352. hook_name='postgresql',
  353. data_source={'name': 'test', 'format': 'directory'},
  354. connection_params=flexmock(),
  355. borgmatic_runtime_directory='/run/borgmatic',
  356. )
  357. def test_collect_dumps_from_archive_parses_archive_paths():
  358. flexmock(module.borgmatic.config.paths).should_receive(
  359. 'get_borgmatic_source_directory'
  360. ).and_return('/root/.borgmatic')
  361. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  362. 'make_data_source_dump_path'
  363. ).and_return('')
  364. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  365. [
  366. 'borgmatic/postgresql_databases/localhost/foo',
  367. 'borgmatic/postgresql_databases/localhost/bar',
  368. 'borgmatic/mysql_databases/localhost/quux',
  369. ]
  370. )
  371. archive_dumps = module.collect_dumps_from_archive(
  372. repository={'path': 'repo'},
  373. archive='archive',
  374. config={},
  375. local_borg_version=flexmock(),
  376. global_arguments=flexmock(log_json=False),
  377. local_path=flexmock(),
  378. remote_path=flexmock(),
  379. borgmatic_runtime_directory='/run/borgmatic',
  380. )
  381. assert archive_dumps == {
  382. module.Dump('postgresql_databases', 'foo'),
  383. module.Dump('postgresql_databases', 'bar'),
  384. module.Dump('mysql_databases', 'quux'),
  385. }
  386. def test_collect_dumps_from_archive_parses_archive_paths_with_different_base_directories():
  387. flexmock(module.borgmatic.config.paths).should_receive(
  388. 'get_borgmatic_source_directory'
  389. ).and_return('/root/.borgmatic')
  390. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  391. 'make_data_source_dump_path'
  392. ).and_return('')
  393. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  394. [
  395. 'borgmatic/postgresql_databases/localhost/foo',
  396. '.borgmatic/postgresql_databases/localhost/bar',
  397. '/root/.borgmatic/postgresql_databases/localhost/baz',
  398. '/var/run/0/borgmatic/mysql_databases/localhost/quux',
  399. ]
  400. )
  401. archive_dumps = module.collect_dumps_from_archive(
  402. repository={'path': 'repo'},
  403. archive='archive',
  404. config={},
  405. local_borg_version=flexmock(),
  406. global_arguments=flexmock(log_json=False),
  407. local_path=flexmock(),
  408. remote_path=flexmock(),
  409. borgmatic_runtime_directory='/run/borgmatic',
  410. )
  411. assert archive_dumps == {
  412. module.Dump('postgresql_databases', 'foo'),
  413. module.Dump('postgresql_databases', 'bar'),
  414. module.Dump('postgresql_databases', 'baz'),
  415. module.Dump('mysql_databases', 'quux'),
  416. }
  417. def test_collect_dumps_from_archive_parses_directory_format_archive_paths():
  418. flexmock(module.borgmatic.config.paths).should_receive(
  419. 'get_borgmatic_source_directory'
  420. ).and_return('/root/.borgmatic')
  421. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  422. 'make_data_source_dump_path'
  423. ).and_return('')
  424. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  425. [
  426. 'borgmatic/postgresql_databases/localhost/foo/table1',
  427. 'borgmatic/postgresql_databases/localhost/foo/table2',
  428. ]
  429. )
  430. archive_dumps = module.collect_dumps_from_archive(
  431. repository={'path': 'repo'},
  432. archive='archive',
  433. config={},
  434. local_borg_version=flexmock(),
  435. global_arguments=flexmock(log_json=False),
  436. local_path=flexmock(),
  437. remote_path=flexmock(),
  438. borgmatic_runtime_directory='/run/borgmatic',
  439. )
  440. assert archive_dumps == {
  441. module.Dump('postgresql_databases', 'foo'),
  442. }
  443. def test_collect_dumps_from_archive_skips_bad_archive_paths():
  444. flexmock(module.borgmatic.config.paths).should_receive(
  445. 'get_borgmatic_source_directory'
  446. ).and_return('/root/.borgmatic')
  447. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  448. 'make_data_source_dump_path'
  449. ).and_return('')
  450. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  451. [
  452. 'borgmatic/postgresql_databases/localhost/foo',
  453. 'borgmatic/invalid',
  454. 'invalid/as/well',
  455. '',
  456. ]
  457. )
  458. archive_dumps = module.collect_dumps_from_archive(
  459. repository={'path': 'repo'},
  460. archive='archive',
  461. config={},
  462. local_borg_version=flexmock(),
  463. global_arguments=flexmock(log_json=False),
  464. local_path=flexmock(),
  465. remote_path=flexmock(),
  466. borgmatic_runtime_directory='/run/borgmatic',
  467. )
  468. assert archive_dumps == {
  469. module.Dump('postgresql_databases', 'foo'),
  470. }
  471. def test_get_dumps_to_restore_gets_requested_dumps_found_in_archive():
  472. dumps_from_archive = {
  473. module.Dump('postgresql_databases', 'foo'),
  474. module.Dump('postgresql_databases', 'bar'),
  475. module.Dump('postgresql_databases', 'baz'),
  476. }
  477. flexmock(module).should_receive('dumps_match').and_return(False)
  478. flexmock(module).should_receive('dumps_match').with_args(
  479. module.Dump(module.UNSPECIFIED, 'foo'),
  480. module.Dump('postgresql_databases', 'foo'),
  481. ).and_return(True)
  482. flexmock(module).should_receive('dumps_match').with_args(
  483. module.Dump(module.UNSPECIFIED, 'bar'),
  484. module.Dump('postgresql_databases', 'bar'),
  485. ).and_return(True)
  486. assert module.get_dumps_to_restore(
  487. restore_arguments=flexmock(
  488. hook=None,
  489. data_sources=['foo', 'bar'],
  490. original_hostname=None,
  491. original_port=None,
  492. ),
  493. dumps_from_archive=dumps_from_archive,
  494. ) == {
  495. module.Dump('postgresql_databases', 'foo'),
  496. module.Dump('postgresql_databases', 'bar'),
  497. }
  498. def test_get_dumps_to_restore_raises_for_requested_dumps_missing_from_archive():
  499. dumps_from_archive = {
  500. module.Dump('postgresql_databases', 'foo'),
  501. }
  502. flexmock(module).should_receive('dumps_match').and_return(False)
  503. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  504. with pytest.raises(ValueError):
  505. module.get_dumps_to_restore(
  506. restore_arguments=flexmock(
  507. hook=None,
  508. data_sources=['foo', 'bar'],
  509. original_hostname=None,
  510. original_port=None,
  511. ),
  512. dumps_from_archive=dumps_from_archive,
  513. )
  514. def test_get_dumps_to_restore_without_requested_dumps_finds_all_archive_dumps():
  515. dumps_from_archive = {
  516. module.Dump('postgresql_databases', 'foo'),
  517. module.Dump('postgresql_databases', 'bar'),
  518. }
  519. flexmock(module).should_receive('dumps_match').and_return(False)
  520. assert (
  521. module.get_dumps_to_restore(
  522. restore_arguments=flexmock(
  523. hook=None,
  524. data_sources=[],
  525. original_hostname=None,
  526. original_port=None,
  527. ),
  528. dumps_from_archive=dumps_from_archive,
  529. )
  530. == dumps_from_archive
  531. )
  532. def test_get_dumps_to_restore_with_all_in_requested_dumps_finds_all_archive_dumps():
  533. dumps_from_archive = {
  534. module.Dump('postgresql_databases', 'foo'),
  535. module.Dump('postgresql_databases', 'bar'),
  536. }
  537. flexmock(module).should_receive('dumps_match').and_return(False)
  538. flexmock(module).should_receive('dumps_match').with_args(
  539. module.Dump(module.UNSPECIFIED, 'foo'),
  540. module.Dump('postgresql_databases', 'foo'),
  541. ).and_return(True)
  542. flexmock(module).should_receive('dumps_match').with_args(
  543. module.Dump(module.UNSPECIFIED, 'bar'),
  544. module.Dump('postgresql_databases', 'bar'),
  545. ).and_return(True)
  546. assert (
  547. module.get_dumps_to_restore(
  548. restore_arguments=flexmock(
  549. hook=None,
  550. data_sources=['all'],
  551. original_hostname=None,
  552. original_port=None,
  553. ),
  554. dumps_from_archive=dumps_from_archive,
  555. )
  556. == dumps_from_archive
  557. )
  558. def test_get_dumps_to_restore_with_all_in_requested_dumps_plus_additional_requested_dumps_omits_duplicates():
  559. dumps_from_archive = {
  560. module.Dump('postgresql_databases', 'foo'),
  561. module.Dump('postgresql_databases', 'bar'),
  562. }
  563. flexmock(module).should_receive('dumps_match').and_return(False)
  564. flexmock(module).should_receive('dumps_match').with_args(
  565. module.Dump(module.UNSPECIFIED, 'foo'),
  566. module.Dump('postgresql_databases', 'foo'),
  567. ).and_return(True)
  568. flexmock(module).should_receive('dumps_match').with_args(
  569. module.Dump(module.UNSPECIFIED, 'bar'),
  570. module.Dump('postgresql_databases', 'bar'),
  571. ).and_return(True)
  572. assert (
  573. module.get_dumps_to_restore(
  574. restore_arguments=flexmock(
  575. hook=None,
  576. data_sources=['all', 'foo', 'bar'],
  577. original_hostname=None,
  578. original_port=None,
  579. ),
  580. dumps_from_archive=dumps_from_archive,
  581. )
  582. == dumps_from_archive
  583. )
  584. def test_get_dumps_to_restore_raises_for_multiple_matching_dumps_in_archive():
  585. flexmock(module).should_receive('dumps_match').and_return(False)
  586. flexmock(module).should_receive('dumps_match').with_args(
  587. module.Dump(module.UNSPECIFIED, 'foo'),
  588. module.Dump('postgresql_databases', 'foo'),
  589. ).and_return(True)
  590. flexmock(module).should_receive('dumps_match').with_args(
  591. module.Dump(module.UNSPECIFIED, 'foo'),
  592. module.Dump('mariadb_databases', 'foo'),
  593. ).and_return(True)
  594. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  595. with pytest.raises(ValueError):
  596. module.get_dumps_to_restore(
  597. restore_arguments=flexmock(
  598. hook=None,
  599. data_sources=['foo'],
  600. original_hostname=None,
  601. original_port=None,
  602. ),
  603. dumps_from_archive={
  604. module.Dump('postgresql_databases', 'foo'),
  605. module.Dump('mariadb_databases', 'foo'),
  606. },
  607. )
  608. def test_get_dumps_to_restore_raises_for_all_in_requested_dumps_and_requested_dumps_missing_from_archive():
  609. flexmock(module).should_receive('dumps_match').and_return(False)
  610. flexmock(module).should_receive('dumps_match').with_args(
  611. module.Dump(module.UNSPECIFIED, 'foo'),
  612. module.Dump('postgresql_databases', 'foo'),
  613. ).and_return(True)
  614. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  615. with pytest.raises(ValueError):
  616. module.get_dumps_to_restore(
  617. restore_arguments=flexmock(
  618. hook=None,
  619. data_sources=['all', 'foo', 'bar'],
  620. original_hostname=None,
  621. original_port=None,
  622. ),
  623. dumps_from_archive={module.Dump('postresql_databases', 'foo')},
  624. )
  625. def test_ensure_requested_dumps_restored_with_all_dumps_restored_does_not_raise():
  626. module.ensure_requested_dumps_restored(
  627. dumps_to_restore={
  628. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  629. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  630. },
  631. dumps_actually_restored={
  632. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  633. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  634. },
  635. )
  636. def test_ensure_requested_dumps_restored_with_no_dumps_raises():
  637. with pytest.raises(ValueError):
  638. module.ensure_requested_dumps_restored(
  639. dumps_to_restore={},
  640. dumps_actually_restored={},
  641. )
  642. def test_ensure_requested_dumps_restored_with_missing_dumps_raises():
  643. flexmock(module).should_receive('render_dump_metadata').and_return('test')
  644. with pytest.raises(ValueError):
  645. module.ensure_requested_dumps_restored(
  646. dumps_to_restore={
  647. module.Dump(hook_name='postgresql_databases', data_source_name='foo')
  648. },
  649. dumps_actually_restored={
  650. module.Dump(hook_name='postgresql_databases', data_source_name='bar')
  651. },
  652. )
  653. def test_run_restore_restores_each_data_source():
  654. dumps_to_restore = {
  655. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  656. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  657. }
  658. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  659. borgmatic_runtime_directory = flexmock()
  660. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  661. borgmatic_runtime_directory
  662. )
  663. flexmock(module.borgmatic.config.paths).should_receive(
  664. 'make_runtime_directory_glob'
  665. ).replace_with(lambda path: path)
  666. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  667. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  668. flexmock()
  669. )
  670. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  671. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  672. flexmock(module).should_receive('get_configured_data_source').and_return(
  673. {'name': 'foo'}
  674. ).and_return({'name': 'bar'})
  675. flexmock(module).should_receive('restore_single_dump').with_args(
  676. repository=object,
  677. config=object,
  678. local_borg_version=object,
  679. global_arguments=object,
  680. local_path=object,
  681. remote_path=object,
  682. archive_name=object,
  683. hook_name='postgresql_databases',
  684. data_source={'name': 'foo', 'schemas': None},
  685. connection_params=object,
  686. borgmatic_runtime_directory=borgmatic_runtime_directory,
  687. ).once()
  688. flexmock(module).should_receive('restore_single_dump').with_args(
  689. repository=object,
  690. config=object,
  691. local_borg_version=object,
  692. global_arguments=object,
  693. local_path=object,
  694. remote_path=object,
  695. archive_name=object,
  696. hook_name='postgresql_databases',
  697. data_source={'name': 'bar', 'schemas': None},
  698. connection_params=object,
  699. borgmatic_runtime_directory=borgmatic_runtime_directory,
  700. ).once()
  701. flexmock(module).should_receive('ensure_requested_dumps_restored')
  702. module.run_restore(
  703. repository={'path': 'repo'},
  704. config=flexmock(),
  705. local_borg_version=flexmock(),
  706. restore_arguments=flexmock(
  707. repository='repo',
  708. archive='archive',
  709. data_sources=flexmock(),
  710. schemas=None,
  711. hostname=None,
  712. port=None,
  713. username=None,
  714. password=None,
  715. restore_path=None,
  716. ),
  717. global_arguments=flexmock(dry_run=False),
  718. local_path=flexmock(),
  719. remote_path=flexmock(),
  720. )
  721. def test_run_restore_bails_for_non_matching_repository():
  722. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
  723. False
  724. )
  725. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  726. flexmock()
  727. )
  728. flexmock(module.borgmatic.config.paths).should_receive(
  729. 'make_runtime_directory_glob'
  730. ).replace_with(lambda path: path)
  731. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  732. 'call_hooks_even_if_unconfigured'
  733. ).never()
  734. flexmock(module).should_receive('restore_single_dump').never()
  735. module.run_restore(
  736. repository={'path': 'repo'},
  737. config=flexmock(),
  738. local_borg_version=flexmock(),
  739. restore_arguments=flexmock(repository='repo', archive='archive', data_sources=flexmock()),
  740. global_arguments=flexmock(dry_run=False),
  741. local_path=flexmock(),
  742. remote_path=flexmock(),
  743. )
  744. def test_run_restore_restores_data_source_configured_with_all_name():
  745. dumps_to_restore = {
  746. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  747. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  748. }
  749. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  750. borgmatic_runtime_directory = flexmock()
  751. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  752. borgmatic_runtime_directory
  753. )
  754. flexmock(module.borgmatic.config.paths).should_receive(
  755. 'make_runtime_directory_glob'
  756. ).replace_with(lambda path: path)
  757. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  758. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  759. flexmock()
  760. )
  761. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  762. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  763. flexmock(module).should_receive('get_configured_data_source').with_args(
  764. config=object,
  765. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  766. ).and_return({'name': 'foo'})
  767. flexmock(module).should_receive('get_configured_data_source').with_args(
  768. config=object,
  769. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  770. ).and_return(None)
  771. flexmock(module).should_receive('get_configured_data_source').with_args(
  772. config=object,
  773. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='all'),
  774. ).and_return({'name': 'bar'})
  775. flexmock(module).should_receive('restore_single_dump').with_args(
  776. repository=object,
  777. config=object,
  778. local_borg_version=object,
  779. global_arguments=object,
  780. local_path=object,
  781. remote_path=object,
  782. archive_name=object,
  783. hook_name='postgresql_databases',
  784. data_source={'name': 'foo', 'schemas': None},
  785. connection_params=object,
  786. borgmatic_runtime_directory=borgmatic_runtime_directory,
  787. ).once()
  788. flexmock(module).should_receive('restore_single_dump').with_args(
  789. repository=object,
  790. config=object,
  791. local_borg_version=object,
  792. global_arguments=object,
  793. local_path=object,
  794. remote_path=object,
  795. archive_name=object,
  796. hook_name='postgresql_databases',
  797. data_source={'name': 'bar', 'schemas': None},
  798. connection_params=object,
  799. borgmatic_runtime_directory=borgmatic_runtime_directory,
  800. ).once()
  801. flexmock(module).should_receive('ensure_requested_dumps_restored')
  802. module.run_restore(
  803. repository={'path': 'repo'},
  804. config=flexmock(),
  805. local_borg_version=flexmock(),
  806. restore_arguments=flexmock(
  807. repository='repo',
  808. archive='archive',
  809. data_sources=flexmock(),
  810. schemas=None,
  811. hostname=None,
  812. port=None,
  813. username=None,
  814. password=None,
  815. restore_path=None,
  816. ),
  817. global_arguments=flexmock(dry_run=False),
  818. local_path=flexmock(),
  819. remote_path=flexmock(),
  820. )
  821. def test_run_restore_skips_missing_data_source():
  822. dumps_to_restore = {
  823. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  824. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  825. }
  826. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  827. borgmatic_runtime_directory = flexmock()
  828. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  829. borgmatic_runtime_directory
  830. )
  831. flexmock(module.borgmatic.config.paths).should_receive(
  832. 'make_runtime_directory_glob'
  833. ).replace_with(lambda path: path)
  834. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  835. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  836. flexmock()
  837. )
  838. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  839. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  840. flexmock(module).should_receive('get_configured_data_source').with_args(
  841. config=object,
  842. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  843. ).and_return({'name': 'foo'})
  844. flexmock(module).should_receive('get_configured_data_source').with_args(
  845. config=object,
  846. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  847. ).and_return(None)
  848. flexmock(module).should_receive('get_configured_data_source').with_args(
  849. config=object,
  850. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='all'),
  851. ).and_return(None)
  852. flexmock(module).should_receive('restore_single_dump').with_args(
  853. repository=object,
  854. config=object,
  855. local_borg_version=object,
  856. global_arguments=object,
  857. local_path=object,
  858. remote_path=object,
  859. archive_name=object,
  860. hook_name='postgresql_databases',
  861. data_source={'name': 'foo', 'schemas': None},
  862. connection_params=object,
  863. borgmatic_runtime_directory=borgmatic_runtime_directory,
  864. ).once()
  865. flexmock(module).should_receive('restore_single_dump').with_args(
  866. repository=object,
  867. config=object,
  868. local_borg_version=object,
  869. global_arguments=object,
  870. local_path=object,
  871. remote_path=object,
  872. archive_name=object,
  873. hook_name='postgresql_databases',
  874. data_source={'name': 'bar', 'schemas': None},
  875. connection_params=object,
  876. borgmatic_runtime_directory=borgmatic_runtime_directory,
  877. ).never()
  878. flexmock(module).should_receive('ensure_requested_dumps_restored')
  879. module.run_restore(
  880. repository={'path': 'repo'},
  881. config=flexmock(),
  882. local_borg_version=flexmock(),
  883. restore_arguments=flexmock(
  884. repository='repo',
  885. archive='archive',
  886. data_sources=flexmock(),
  887. schemas=None,
  888. hostname=None,
  889. port=None,
  890. username=None,
  891. password=None,
  892. restore_path=None,
  893. ),
  894. global_arguments=flexmock(dry_run=False),
  895. local_path=flexmock(),
  896. remote_path=flexmock(),
  897. )
  898. def test_run_restore_restores_data_sources_from_different_hooks():
  899. dumps_to_restore = {
  900. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  901. module.Dump(hook_name='mysql_databases', data_source_name='foo'),
  902. }
  903. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  904. borgmatic_runtime_directory = flexmock()
  905. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  906. borgmatic_runtime_directory
  907. )
  908. flexmock(module.borgmatic.config.paths).should_receive(
  909. 'make_runtime_directory_glob'
  910. ).replace_with(lambda path: path)
  911. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  912. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  913. flexmock()
  914. )
  915. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  916. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  917. flexmock(module).should_receive('get_configured_data_source').with_args(
  918. config=object,
  919. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  920. ).and_return({'name': 'foo'})
  921. flexmock(module).should_receive('get_configured_data_source').with_args(
  922. config=object,
  923. restore_dump=module.Dump(hook_name='mysql_databases', data_source_name='foo'),
  924. ).and_return({'name': 'bar'})
  925. flexmock(module).should_receive('restore_single_dump').with_args(
  926. repository=object,
  927. config=object,
  928. local_borg_version=object,
  929. global_arguments=object,
  930. local_path=object,
  931. remote_path=object,
  932. archive_name=object,
  933. hook_name='postgresql_databases',
  934. data_source={'name': 'foo', 'schemas': None},
  935. connection_params=object,
  936. borgmatic_runtime_directory=borgmatic_runtime_directory,
  937. ).once()
  938. flexmock(module).should_receive('restore_single_dump').with_args(
  939. repository=object,
  940. config=object,
  941. local_borg_version=object,
  942. global_arguments=object,
  943. local_path=object,
  944. remote_path=object,
  945. archive_name=object,
  946. hook_name='mysql_databases',
  947. data_source={'name': 'bar', 'schemas': None},
  948. connection_params=object,
  949. borgmatic_runtime_directory=borgmatic_runtime_directory,
  950. ).once()
  951. flexmock(module).should_receive('ensure_requested_dumps_restored')
  952. module.run_restore(
  953. repository={'path': 'repo'},
  954. config=flexmock(),
  955. local_borg_version=flexmock(),
  956. restore_arguments=flexmock(
  957. repository='repo',
  958. archive='archive',
  959. data_sources=flexmock(),
  960. schemas=None,
  961. hostname=None,
  962. port=None,
  963. username=None,
  964. password=None,
  965. restore_path=None,
  966. ),
  967. global_arguments=flexmock(dry_run=False),
  968. local_path=flexmock(),
  969. remote_path=flexmock(),
  970. )