test_restore.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. import collections
  2. import pytest
  3. from flexmock import flexmock
  4. import borgmatic.actions.restore as module
  5. def test_get_configured_data_source_matches_data_source_by_name():
  6. assert module.get_configured_data_source(
  7. config={
  8. 'other_databases': [{'name': 'other'}],
  9. 'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}],
  10. },
  11. archive_data_source_names={'postgresql_databases': ['other', 'foo', 'bar']},
  12. hook_name='postgresql_databases',
  13. data_source_name='bar',
  14. ) == ('postgresql_databases', {'name': 'bar'})
  15. def test_get_configured_data_source_matches_nothing_when_nothing_configured():
  16. assert module.get_configured_data_source(
  17. config={},
  18. archive_data_source_names={'postgresql_databases': ['foo']},
  19. hook_name='postgresql_databases',
  20. data_source_name='quux',
  21. ) == (None, None)
  22. def test_get_configured_data_source_matches_nothing_when_data_source_name_not_configured():
  23. assert module.get_configured_data_source(
  24. config={'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}]},
  25. archive_data_source_names={'postgresql_databases': ['foo']},
  26. hook_name='postgresql_databases',
  27. data_source_name='quux',
  28. ) == (None, None)
  29. def test_get_configured_data_source_matches_nothing_when_data_source_name_not_in_archive():
  30. assert module.get_configured_data_source(
  31. config={'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}]},
  32. archive_data_source_names={'postgresql_databases': ['bar']},
  33. hook_name='postgresql_databases',
  34. data_source_name='foo',
  35. ) == (None, None)
  36. def test_get_configured_data_source_matches_data_source_by_configuration_data_source_name():
  37. assert module.get_configured_data_source(
  38. config={'postgresql_databases': [{'name': 'all'}, {'name': 'bar'}]},
  39. archive_data_source_names={'postgresql_databases': ['foo']},
  40. hook_name='postgresql_databases',
  41. data_source_name='foo',
  42. configuration_data_source_name='all',
  43. ) == ('postgresql_databases', {'name': 'all'})
  44. def test_get_configured_data_source_with_unspecified_hook_matches_data_source_by_name():
  45. assert module.get_configured_data_source(
  46. config={
  47. 'other_databases': [{'name': 'other'}],
  48. 'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}],
  49. },
  50. archive_data_source_names={'postgresql_databases': ['other', 'foo', 'bar']},
  51. hook_name=module.UNSPECIFIED_HOOK,
  52. data_source_name='bar',
  53. ) == ('postgresql_databases', {'name': 'bar'})
  54. def test_strip_path_prefix_from_extracted_dump_destination_renames_first_matching_databases_subdirectory():
  55. flexmock(module.os).should_receive('walk').and_return(
  56. [
  57. ('/foo', flexmock(), flexmock()),
  58. ('/foo/bar', flexmock(), flexmock()),
  59. ('/foo/bar/postgresql_databases', flexmock(), flexmock()),
  60. ('/foo/bar/mariadb_databases', flexmock(), flexmock()),
  61. ]
  62. )
  63. flexmock(module.shutil).should_receive('move').with_args(
  64. '/foo/bar/postgresql_databases', '/run/user/0/borgmatic/postgresql_databases'
  65. ).once()
  66. flexmock(module.shutil).should_receive('move').with_args(
  67. '/foo/bar/mariadb_databases', '/run/user/0/borgmatic/mariadb_databases'
  68. ).never()
  69. module.strip_path_prefix_from_extracted_dump_destination('/foo', '/run/user/0/borgmatic')
  70. def test_restore_single_dump_extracts_and_restores_single_file_dump():
  71. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  72. 'make_data_source_dump_patterns', object, object, object, object, object
  73. ).and_return({'postgresql': flexmock()})
  74. flexmock(module.tempfile).should_receive('mkdtemp').never()
  75. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  76. 'convert_glob_patterns_to_borg_pattern'
  77. ).and_return(flexmock())
  78. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  79. flexmock()
  80. ).once()
  81. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  82. flexmock(module.shutil).should_receive('rmtree').never()
  83. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  84. function_name='restore_data_source_dump',
  85. config=object,
  86. log_prefix=object,
  87. hook_name=object,
  88. data_source=object,
  89. dry_run=object,
  90. extract_process=object,
  91. connection_params=object,
  92. borgmatic_runtime_directory=object,
  93. ).once()
  94. module.restore_single_dump(
  95. repository={'path': 'test.borg'},
  96. config=flexmock(),
  97. local_borg_version=flexmock(),
  98. global_arguments=flexmock(dry_run=False),
  99. local_path=None,
  100. remote_path=None,
  101. archive_name=flexmock(),
  102. hook_name='postgresql',
  103. data_source={'name': 'test', 'format': 'plain'},
  104. connection_params=flexmock(),
  105. borgmatic_runtime_directory='/run/borgmatic',
  106. )
  107. def test_restore_single_dump_extracts_and_restores_directory_dump():
  108. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  109. 'make_data_source_dump_patterns', object, object, object, object, object
  110. ).and_return({'postgresql': flexmock()})
  111. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
  112. '/run/user/0/borgmatic/tmp1234'
  113. )
  114. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  115. 'convert_glob_patterns_to_borg_pattern'
  116. ).and_return(flexmock())
  117. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  118. flexmock()
  119. ).once()
  120. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').once()
  121. flexmock(module.shutil).should_receive('rmtree').once()
  122. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  123. function_name='restore_data_source_dump',
  124. config=object,
  125. log_prefix=object,
  126. hook_name=object,
  127. data_source=object,
  128. dry_run=object,
  129. extract_process=object,
  130. connection_params=object,
  131. borgmatic_runtime_directory='/run/borgmatic',
  132. ).once()
  133. module.restore_single_dump(
  134. repository={'path': 'test.borg'},
  135. config=flexmock(),
  136. local_borg_version=flexmock(),
  137. global_arguments=flexmock(dry_run=False),
  138. local_path=None,
  139. remote_path=None,
  140. archive_name=flexmock(),
  141. hook_name='postgresql',
  142. data_source={'name': 'test', 'format': 'directory'},
  143. connection_params=flexmock(),
  144. borgmatic_runtime_directory='/run/borgmatic',
  145. )
  146. def test_restore_single_dump_with_directory_dump_error_cleans_up_temporary_directory():
  147. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  148. 'make_data_source_dump_patterns', object, object, object, object, object
  149. ).and_return({'postgresql': flexmock()})
  150. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
  151. '/run/user/0/borgmatic/tmp1234'
  152. )
  153. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  154. 'convert_glob_patterns_to_borg_pattern'
  155. ).and_return(flexmock())
  156. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_raise(
  157. ValueError
  158. ).once()
  159. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  160. flexmock(module.shutil).should_receive('rmtree').once()
  161. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  162. function_name='restore_data_source_dump',
  163. config=object,
  164. log_prefix=object,
  165. hook_name=object,
  166. data_source=object,
  167. dry_run=object,
  168. extract_process=object,
  169. connection_params=object,
  170. borgmatic_runtime_directory='/run/user/0/borgmatic/tmp1234',
  171. ).never()
  172. with pytest.raises(ValueError):
  173. module.restore_single_dump(
  174. repository={'path': 'test.borg'},
  175. config=flexmock(),
  176. local_borg_version=flexmock(),
  177. global_arguments=flexmock(dry_run=False),
  178. local_path=None,
  179. remote_path=None,
  180. archive_name=flexmock(),
  181. hook_name='postgresql',
  182. data_source={'name': 'test', 'format': 'directory'},
  183. connection_params=flexmock(),
  184. borgmatic_runtime_directory='/run/borgmatic',
  185. )
  186. def test_restore_single_dump_with_directory_dump_and_dry_run_skips_directory_move_and_cleanup():
  187. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
  188. 'make_data_source_dump_patterns', object, object, object, object, object
  189. ).and_return({'postgresql': flexmock()})
  190. flexmock(module.tempfile).should_receive('mkdtemp').once().and_return('/run/borgmatic/tmp1234')
  191. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  192. 'convert_glob_patterns_to_borg_pattern'
  193. ).and_return(flexmock())
  194. flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
  195. flexmock()
  196. ).once()
  197. flexmock(module).should_receive('strip_path_prefix_from_extracted_dump_destination').never()
  198. flexmock(module.shutil).should_receive('rmtree').never()
  199. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
  200. function_name='restore_data_source_dump',
  201. config=object,
  202. log_prefix=object,
  203. hook_name=object,
  204. data_source=object,
  205. dry_run=object,
  206. extract_process=object,
  207. connection_params=object,
  208. borgmatic_runtime_directory='/run/borgmatic',
  209. ).once()
  210. module.restore_single_dump(
  211. repository={'path': 'test.borg'},
  212. config=flexmock(),
  213. local_borg_version=flexmock(),
  214. global_arguments=flexmock(dry_run=True),
  215. local_path=None,
  216. remote_path=None,
  217. archive_name=flexmock(),
  218. hook_name='postgresql',
  219. data_source={'name': 'test', 'format': 'directory'},
  220. connection_params=flexmock(),
  221. borgmatic_runtime_directory='/run/borgmatic',
  222. )
  223. def test_collect_dumps_from_archive_parses_archive_paths():
  224. flexmock(module.borgmatic.config.paths).should_receive(
  225. 'get_borgmatic_source_directory'
  226. ).and_return('/root/.borgmatic')
  227. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  228. 'make_data_source_dump_path'
  229. ).and_return('')
  230. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  231. [
  232. 'borgmatic/postgresql_databases/localhost/foo',
  233. 'borgmatic/postgresql_databases/localhost/bar',
  234. 'borgmatic/mysql_databases/localhost/quux',
  235. ]
  236. )
  237. archive_data_source_names = module.collect_dumps_from_archive(
  238. repository={'path': 'repo'},
  239. archive='archive',
  240. config={},
  241. local_borg_version=flexmock(),
  242. global_arguments=flexmock(log_json=False),
  243. local_path=flexmock(),
  244. remote_path=flexmock(),
  245. borgmatic_runtime_directory='/run/borgmatic',
  246. )
  247. assert archive_data_source_names == {
  248. 'postgresql_databases': ['foo', 'bar'],
  249. 'mysql_databases': ['quux'],
  250. }
  251. def test_collect_dumps_from_archive_parses_archive_paths_with_different_base_directories():
  252. flexmock(module.borgmatic.config.paths).should_receive(
  253. 'get_borgmatic_source_directory'
  254. ).and_return('/root/.borgmatic')
  255. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  256. 'make_data_source_dump_path'
  257. ).and_return('')
  258. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  259. [
  260. 'borgmatic/postgresql_databases/localhost/foo',
  261. '.borgmatic/postgresql_databases/localhost/bar',
  262. '/root/.borgmatic/postgresql_databases/localhost/baz',
  263. '/var/run/0/borgmatic/mysql_databases/localhost/quux',
  264. ]
  265. )
  266. archive_data_source_names = module.collect_dumps_from_archive(
  267. repository={'path': 'repo'},
  268. archive='archive',
  269. config={},
  270. local_borg_version=flexmock(),
  271. global_arguments=flexmock(log_json=False),
  272. local_path=flexmock(),
  273. remote_path=flexmock(),
  274. borgmatic_runtime_directory='/run/borgmatic',
  275. )
  276. assert archive_data_source_names == {
  277. 'postgresql_databases': ['foo', 'bar', 'baz'],
  278. 'mysql_databases': ['quux'],
  279. }
  280. def test_collect_dumps_from_archive_parses_directory_format_archive_paths():
  281. flexmock(module.borgmatic.config.paths).should_receive(
  282. 'get_borgmatic_source_directory'
  283. ).and_return('/root/.borgmatic')
  284. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  285. 'make_data_source_dump_path'
  286. ).and_return('')
  287. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  288. [
  289. 'borgmatic/postgresql_databases/localhost/foo/table1',
  290. 'borgmatic/postgresql_databases/localhost/foo/table2',
  291. ]
  292. )
  293. archive_data_source_names = module.collect_dumps_from_archive(
  294. repository={'path': 'repo'},
  295. archive='archive',
  296. config={},
  297. local_borg_version=flexmock(),
  298. global_arguments=flexmock(log_json=False),
  299. local_path=flexmock(),
  300. remote_path=flexmock(),
  301. borgmatic_runtime_directory='/run/borgmatic',
  302. )
  303. assert archive_data_source_names == {
  304. 'postgresql_databases': ['foo'],
  305. }
  306. def test_collect_dumps_from_archive_skips_bad_archive_paths():
  307. flexmock(module.borgmatic.config.paths).should_receive(
  308. 'get_borgmatic_source_directory'
  309. ).and_return('/root/.borgmatic')
  310. flexmock(module.borgmatic.hooks.data_source.dump).should_receive(
  311. 'make_data_source_dump_path'
  312. ).and_return('')
  313. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  314. [
  315. 'borgmatic/postgresql_databases/localhost/foo',
  316. 'borgmatic/invalid',
  317. 'invalid/as/well',
  318. '',
  319. ]
  320. )
  321. archive_data_source_names = module.collect_dumps_from_archive(
  322. repository={'path': 'repo'},
  323. archive='archive',
  324. config={},
  325. local_borg_version=flexmock(),
  326. global_arguments=flexmock(log_json=False),
  327. local_path=flexmock(),
  328. remote_path=flexmock(),
  329. borgmatic_runtime_directory='/run/borgmatic',
  330. )
  331. assert archive_data_source_names == {
  332. 'postgresql_databases': ['foo'],
  333. }
  334. def test_get_dumps_to_restore_passes_through_requested_names_found_in_archive():
  335. restore_names = module.get_dumps_to_restore(
  336. requested_data_source_names=['foo', 'bar'],
  337. archive_data_source_names={'postresql_databases': ['foo', 'bar', 'baz']},
  338. )
  339. assert restore_names == {module.UNSPECIFIED_HOOK: ['foo', 'bar']}
  340. def test_get_dumps_to_restore_raises_for_requested_names_missing_from_archive():
  341. with pytest.raises(ValueError):
  342. module.get_dumps_to_restore(
  343. requested_data_source_names=['foo', 'bar'],
  344. archive_data_source_names={'postresql_databases': ['foo']},
  345. )
  346. def test_get_dumps_to_restore_without_requested_names_finds_all_archive_data_sources():
  347. archive_data_source_names = {'postresql_databases': ['foo', 'bar']}
  348. restore_names = module.get_dumps_to_restore(
  349. requested_data_source_names=[],
  350. archive_data_source_names=archive_data_source_names,
  351. )
  352. assert restore_names == archive_data_source_names
  353. def test_get_dumps_to_restore_with_all_in_requested_names_finds_all_archive_data_sources():
  354. archive_data_source_names = {'postresql_databases': ['foo', 'bar']}
  355. restore_names = module.get_dumps_to_restore(
  356. requested_data_source_names=['all'],
  357. archive_data_source_names=archive_data_source_names,
  358. )
  359. assert restore_names == archive_data_source_names
  360. def test_get_dumps_to_restore_with_all_in_requested_names_plus_additional_requested_names_omits_duplicates():
  361. dumps_from_archive = {
  362. module.Dump('postresql_databases', 'foo'),
  363. module.Dump('postresql_databases', 'bar'),
  364. }
  365. assert (
  366. module.get_dumps_to_restore(
  367. restore_arguments=flexmock(
  368. hook=None,
  369. data_sources=['all', 'foo', 'bar'],
  370. original_hostname=None,
  371. original_port=None,
  372. ),
  373. dumps_from_archive=dumps_from_archive,
  374. )
  375. == dumps_from_archive
  376. )
  377. def test_get_dumps_to_restore_raises_for_all_in_requested_names_and_requested_names_missing_from_archives():
  378. with pytest.raises(ValueError):
  379. module.get_dumps_to_restore(
  380. restore_arguments=flexmock(
  381. hook=None,
  382. data_sources=['all', 'foo', 'bar'],
  383. original_hostname=None,
  384. original_port=None,
  385. ),
  386. dumps_from_archive={module.Dump('postresql_databases', 'foo')},
  387. )
  388. def test_ensure_requested_dumps_restored_with_all_dumps_restored_does_not_raise():
  389. module.ensure_requested_dumps_restored(
  390. dumps_to_restore={
  391. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  392. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  393. },
  394. dumps_actually_restored={
  395. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  396. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  397. },
  398. )
  399. def test_ensure_requested_dumps_restored_with_no_dumps_raises():
  400. with pytest.raises(ValueError):
  401. module.ensure_requested_dumps_restored(
  402. dumps_to_restore={},
  403. dumps_actually_restored={},
  404. )
  405. def test_ensure_requested_dumps_restored_with_missing_dumps_raises():
  406. with pytest.raises(ValueError):
  407. module.ensure_requested_dumps_restored(
  408. dumps_to_restore={module.Dump(hook_name='postgresql_databases', data_source_name='foo')},
  409. dumps_actually_restored={
  410. module.Dump(hook_name='postgresql_databases', data_source_name='bar')
  411. },
  412. )
  413. def test_run_restore_restores_each_data_source():
  414. dumps_to_restore = {
  415. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  416. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  417. }
  418. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  419. borgmatic_runtime_directory = flexmock()
  420. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  421. borgmatic_runtime_directory
  422. )
  423. flexmock(module.borgmatic.config.paths).should_receive(
  424. 'make_runtime_directory_glob'
  425. ).replace_with(lambda path: path)
  426. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  427. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  428. flexmock()
  429. )
  430. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  431. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  432. flexmock(module).should_receive('get_configured_data_source').and_return(
  433. ('postgresql_databases', {'name': 'foo'})
  434. ).and_return(('postgresql_databases', {'name': 'bar'}))
  435. flexmock(module).should_receive('restore_single_dump').with_args(
  436. repository=object,
  437. config=object,
  438. local_borg_version=object,
  439. global_arguments=object,
  440. local_path=object,
  441. remote_path=object,
  442. archive_name=object,
  443. hook_name='postgresql_databases',
  444. data_source={'name': 'foo', 'schemas': None},
  445. connection_params=object,
  446. borgmatic_runtime_directory=borgmatic_runtime_directory,
  447. ).once()
  448. flexmock(module).should_receive('restore_single_dump').with_args(
  449. repository=object,
  450. config=object,
  451. local_borg_version=object,
  452. global_arguments=object,
  453. local_path=object,
  454. remote_path=object,
  455. archive_name=object,
  456. hook_name='postgresql_databases',
  457. data_source={'name': 'bar', 'schemas': None},
  458. connection_params=object,
  459. borgmatic_runtime_directory=borgmatic_runtime_directory,
  460. ).once()
  461. flexmock(module).should_receive('ensure_requested_dumps_restored')
  462. module.run_restore(
  463. repository={'path': 'repo'},
  464. config=flexmock(),
  465. local_borg_version=flexmock(),
  466. restore_arguments=flexmock(
  467. repository='repo',
  468. archive='archive',
  469. data_sources=flexmock(),
  470. schemas=None,
  471. hostname=None,
  472. port=None,
  473. username=None,
  474. password=None,
  475. restore_path=None,
  476. ),
  477. global_arguments=flexmock(dry_run=False),
  478. local_path=flexmock(),
  479. remote_path=flexmock(),
  480. )
  481. def test_run_restore_bails_for_non_matching_repository():
  482. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
  483. False
  484. )
  485. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  486. flexmock()
  487. )
  488. flexmock(module.borgmatic.config.paths).should_receive(
  489. 'make_runtime_directory_glob'
  490. ).replace_with(lambda path: path)
  491. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  492. 'call_hooks_even_if_unconfigured'
  493. ).never()
  494. flexmock(module).should_receive('restore_single_dump').never()
  495. module.run_restore(
  496. repository={'path': 'repo'},
  497. config=flexmock(),
  498. local_borg_version=flexmock(),
  499. restore_arguments=flexmock(repository='repo', archive='archive', data_sources=flexmock()),
  500. global_arguments=flexmock(dry_run=False),
  501. local_path=flexmock(),
  502. remote_path=flexmock(),
  503. )
  504. def test_run_restore_restores_data_source_configured_with_all_name():
  505. dumps_to_restore = {
  506. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  507. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  508. }
  509. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  510. borgmatic_runtime_directory = flexmock()
  511. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  512. borgmatic_runtime_directory
  513. )
  514. flexmock(module.borgmatic.config.paths).should_receive(
  515. 'make_runtime_directory_glob'
  516. ).replace_with(lambda path: path)
  517. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  518. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  519. flexmock()
  520. )
  521. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  522. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  523. flexmock(module).should_receive('get_configured_data_source').with_args(
  524. config=object,
  525. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  526. ).and_return(('postgresql_databases', {'name': 'foo'}))
  527. flexmock(module).should_receive('get_configured_data_source').with_args(
  528. config=object,
  529. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  530. ).and_return((None, None))
  531. flexmock(module).should_receive('get_configured_data_source').with_args(
  532. config=object,
  533. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='all'),
  534. ).and_return(('postgresql_databases', {'name': 'bar'}))
  535. flexmock(module).should_receive('restore_single_dump').with_args(
  536. repository=object,
  537. config=object,
  538. local_borg_version=object,
  539. global_arguments=object,
  540. local_path=object,
  541. remote_path=object,
  542. archive_name=object,
  543. hook_name='postgresql_databases',
  544. data_source={'name': 'foo', 'schemas': None},
  545. connection_params=object,
  546. borgmatic_runtime_directory=borgmatic_runtime_directory,
  547. ).once()
  548. flexmock(module).should_receive('restore_single_dump').with_args(
  549. repository=object,
  550. config=object,
  551. local_borg_version=object,
  552. global_arguments=object,
  553. local_path=object,
  554. remote_path=object,
  555. archive_name=object,
  556. hook_name='postgresql_databases',
  557. data_source={'name': 'bar', 'schemas': None},
  558. connection_params=object,
  559. borgmatic_runtime_directory=borgmatic_runtime_directory,
  560. ).once()
  561. flexmock(module).should_receive('ensure_requested_dumps_restored')
  562. module.run_restore(
  563. repository={'path': 'repo'},
  564. config=flexmock(),
  565. local_borg_version=flexmock(),
  566. restore_arguments=flexmock(
  567. repository='repo',
  568. archive='archive',
  569. data_sources=flexmock(),
  570. schemas=None,
  571. hostname=None,
  572. port=None,
  573. username=None,
  574. password=None,
  575. restore_path=None,
  576. ),
  577. global_arguments=flexmock(dry_run=False),
  578. local_path=flexmock(),
  579. remote_path=flexmock(),
  580. )
  581. def test_run_restore_skips_missing_data_source():
  582. dumps_to_restore = {
  583. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  584. module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  585. }
  586. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  587. borgmatic_runtime_directory = flexmock()
  588. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  589. borgmatic_runtime_directory
  590. )
  591. flexmock(module.borgmatic.config.paths).should_receive(
  592. 'make_runtime_directory_glob'
  593. ).replace_with(lambda path: path)
  594. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  595. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  596. flexmock()
  597. )
  598. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  599. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  600. flexmock(module).should_receive('get_configured_data_source').with_args(
  601. config=object,
  602. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  603. ).and_return(('postgresql_databases', {'name': 'foo'}))
  604. flexmock(module).should_receive('get_configured_data_source').with_args(
  605. config=object,
  606. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='bar'),
  607. ).and_return((None, None))
  608. flexmock(module).should_receive('get_configured_data_source').with_args(
  609. config=object,
  610. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='all'),
  611. ).and_return((None, None))
  612. flexmock(module).should_receive('restore_single_dump').with_args(
  613. repository=object,
  614. config=object,
  615. local_borg_version=object,
  616. global_arguments=object,
  617. local_path=object,
  618. remote_path=object,
  619. archive_name=object,
  620. hook_name='postgresql_databases',
  621. data_source={'name': 'foo', 'schemas': None},
  622. connection_params=object,
  623. borgmatic_runtime_directory=borgmatic_runtime_directory,
  624. ).once()
  625. flexmock(module).should_receive('restore_single_dump').with_args(
  626. repository=object,
  627. config=object,
  628. local_borg_version=object,
  629. global_arguments=object,
  630. local_path=object,
  631. remote_path=object,
  632. archive_name=object,
  633. hook_name='postgresql_databases',
  634. data_source={'name': 'bar', 'schemas': None},
  635. connection_params=object,
  636. borgmatic_runtime_directory=borgmatic_runtime_directory,
  637. ).never()
  638. flexmock(module).should_receive('ensure_requested_dumps_restored')
  639. module.run_restore(
  640. repository={'path': 'repo'},
  641. config=flexmock(),
  642. local_borg_version=flexmock(),
  643. restore_arguments=flexmock(
  644. repository='repo',
  645. archive='archive',
  646. data_sources=flexmock(),
  647. schemas=None,
  648. hostname=None,
  649. port=None,
  650. username=None,
  651. password=None,
  652. restore_path=None,
  653. ),
  654. global_arguments=flexmock(dry_run=False),
  655. local_path=flexmock(),
  656. remote_path=flexmock(),
  657. )
  658. def test_run_restore_restores_data_sources_from_different_hooks():
  659. dumps_to_restore = {
  660. module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  661. module.Dump(hook_name='mysql_databases', data_source_name='foo'),
  662. }
  663. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  664. borgmatic_runtime_directory = flexmock()
  665. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  666. borgmatic_runtime_directory
  667. )
  668. flexmock(module.borgmatic.config.paths).should_receive(
  669. 'make_runtime_directory_glob'
  670. ).replace_with(lambda path: path)
  671. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  672. flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
  673. flexmock()
  674. )
  675. flexmock(module).should_receive('collect_dumps_from_archive').and_return(flexmock())
  676. flexmock(module).should_receive('get_dumps_to_restore').and_return(dumps_to_restore)
  677. flexmock(module).should_receive('get_configured_data_source').with_args(
  678. config=object,
  679. restore_dump=module.Dump(hook_name='postgresql_databases', data_source_name='foo'),
  680. ).and_return(('postgresql_databases', {'name': 'foo'}))
  681. flexmock(module).should_receive('get_configured_data_source').with_args(
  682. config=object,
  683. restore_dump=module.Dump(hook_name='mysql_databases', data_source_name='foo'),
  684. ).and_return(('mysql_databases', {'name': 'bar'}))
  685. flexmock(module).should_receive('restore_single_dump').with_args(
  686. repository=object,
  687. config=object,
  688. local_borg_version=object,
  689. global_arguments=object,
  690. local_path=object,
  691. remote_path=object,
  692. archive_name=object,
  693. hook_name='postgresql_databases',
  694. data_source={'name': 'foo', 'schemas': None},
  695. connection_params=object,
  696. borgmatic_runtime_directory=borgmatic_runtime_directory,
  697. ).once()
  698. flexmock(module).should_receive('restore_single_dump').with_args(
  699. repository=object,
  700. config=object,
  701. local_borg_version=object,
  702. global_arguments=object,
  703. local_path=object,
  704. remote_path=object,
  705. archive_name=object,
  706. hook_name='mysql_databases',
  707. data_source={'name': 'bar', 'schemas': None},
  708. connection_params=object,
  709. borgmatic_runtime_directory=borgmatic_runtime_directory,
  710. ).once()
  711. flexmock(module).should_receive('ensure_requested_dumps_restored')
  712. module.run_restore(
  713. repository={'path': 'repo'},
  714. config=flexmock(),
  715. local_borg_version=flexmock(),
  716. restore_arguments=flexmock(
  717. repository='repo',
  718. archive='archive',
  719. data_sources=flexmock(),
  720. schemas=None,
  721. hostname=None,
  722. port=None,
  723. username=None,
  724. password=None,
  725. restore_path=None,
  726. ),
  727. global_arguments=flexmock(dry_run=False),
  728. local_path=flexmock(),
  729. remote_path=flexmock(),
  730. )