test_restore.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. import pytest
  2. from flexmock import flexmock
  3. import borgmatic.actions.restore as module
  4. def test_get_configured_data_source_matches_data_source_by_name():
  5. assert module.get_configured_data_source(
  6. config={
  7. 'other_databases': [{'name': 'other'}],
  8. 'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}],
  9. },
  10. archive_data_source_names={'postgresql_databases': ['other', 'foo', 'bar']},
  11. hook_name='postgresql_databases',
  12. data_source_name='bar',
  13. ) == ('postgresql_databases', {'name': 'bar'})
  14. def test_get_configured_data_source_matches_nothing_when_nothing_configured():
  15. assert module.get_configured_data_source(
  16. config={},
  17. archive_data_source_names={'postgresql_databases': ['foo']},
  18. hook_name='postgresql_databases',
  19. data_source_name='quux',
  20. ) == (None, None)
  21. def test_get_configured_data_source_matches_nothing_when_data_source_name_not_configured():
  22. assert module.get_configured_data_source(
  23. config={'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}]},
  24. archive_data_source_names={'postgresql_databases': ['foo']},
  25. hook_name='postgresql_databases',
  26. data_source_name='quux',
  27. ) == (None, None)
  28. def test_get_configured_data_source_matches_nothing_when_data_source_name_not_in_archive():
  29. assert module.get_configured_data_source(
  30. config={'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}]},
  31. archive_data_source_names={'postgresql_databases': ['bar']},
  32. hook_name='postgresql_databases',
  33. data_source_name='foo',
  34. ) == (None, None)
  35. def test_get_configured_data_source_matches_data_source_by_configuration_data_source_name():
  36. assert module.get_configured_data_source(
  37. config={'postgresql_databases': [{'name': 'all'}, {'name': 'bar'}]},
  38. archive_data_source_names={'postgresql_databases': ['foo']},
  39. hook_name='postgresql_databases',
  40. data_source_name='foo',
  41. configuration_data_source_name='all',
  42. ) == ('postgresql_databases', {'name': 'all'})
  43. def test_get_configured_data_source_with_unspecified_hook_matches_data_source_by_name():
  44. assert module.get_configured_data_source(
  45. config={
  46. 'other_databases': [{'name': 'other'}],
  47. 'postgresql_databases': [{'name': 'foo'}, {'name': 'bar'}],
  48. },
  49. archive_data_source_names={'postgresql_databases': ['other', 'foo', 'bar']},
  50. hook_name=module.UNSPECIFIED_HOOK,
  51. data_source_name='bar',
  52. ) == ('postgresql_databases', {'name': 'bar'})
  53. def test_collect_archive_data_source_names_parses_archive_paths():
  54. flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
  55. ''
  56. )
  57. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  58. [
  59. '.borgmatic/postgresql_databases/localhost/foo',
  60. '.borgmatic/postgresql_databases/localhost/bar',
  61. '.borgmatic/mysql_databases/localhost/quux',
  62. ]
  63. )
  64. archive_data_source_names = module.collect_archive_data_source_names(
  65. repository={'path': 'repo'},
  66. archive='archive',
  67. config={'borgmatic_source_directory': '.borgmatic'},
  68. local_borg_version=flexmock(),
  69. global_arguments=flexmock(log_json=False),
  70. local_path=flexmock(),
  71. remote_path=flexmock(),
  72. )
  73. assert archive_data_source_names == {
  74. 'postgresql_databases': ['foo', 'bar'],
  75. 'mysql_databases': ['quux'],
  76. }
  77. def test_collect_archive_data_source_names_parses_directory_format_archive_paths():
  78. flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
  79. ''
  80. )
  81. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  82. [
  83. '.borgmatic/postgresql_databases/localhost/foo/table1',
  84. '.borgmatic/postgresql_databases/localhost/foo/table2',
  85. ]
  86. )
  87. archive_data_source_names = module.collect_archive_data_source_names(
  88. repository={'path': 'repo'},
  89. archive='archive',
  90. config={'borgmatic_source_directory': '.borgmatic'},
  91. local_borg_version=flexmock(),
  92. global_arguments=flexmock(log_json=False),
  93. local_path=flexmock(),
  94. remote_path=flexmock(),
  95. )
  96. assert archive_data_source_names == {
  97. 'postgresql_databases': ['foo'],
  98. }
  99. def test_collect_archive_data_source_names_skips_bad_archive_paths():
  100. flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
  101. ''
  102. )
  103. flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
  104. ['.borgmatic/postgresql_databases/localhost/foo', '.borgmatic/invalid', 'invalid/as/well']
  105. )
  106. archive_data_source_names = module.collect_archive_data_source_names(
  107. repository={'path': 'repo'},
  108. archive='archive',
  109. config={'borgmatic_source_directory': '.borgmatic'},
  110. local_borg_version=flexmock(),
  111. global_arguments=flexmock(log_json=False),
  112. local_path=flexmock(),
  113. remote_path=flexmock(),
  114. )
  115. assert archive_data_source_names == {
  116. 'postgresql_databases': ['foo'],
  117. }
  118. def test_find_data_sources_to_restore_passes_through_requested_names_found_in_archive():
  119. restore_names = module.find_data_sources_to_restore(
  120. requested_data_source_names=['foo', 'bar'],
  121. archive_data_source_names={'postresql_databases': ['foo', 'bar', 'baz']},
  122. )
  123. assert restore_names == {module.UNSPECIFIED_HOOK: ['foo', 'bar']}
  124. def test_find_data_sources_to_restore_raises_for_requested_names_missing_from_archive():
  125. with pytest.raises(ValueError):
  126. module.find_data_sources_to_restore(
  127. requested_data_source_names=['foo', 'bar'],
  128. archive_data_source_names={'postresql_databases': ['foo']},
  129. )
  130. def test_find_data_sources_to_restore_without_requested_names_finds_all_archive_data_sources():
  131. archive_data_source_names = {'postresql_databases': ['foo', 'bar']}
  132. restore_names = module.find_data_sources_to_restore(
  133. requested_data_source_names=[],
  134. archive_data_source_names=archive_data_source_names,
  135. )
  136. assert restore_names == archive_data_source_names
  137. def test_find_data_sources_to_restore_with_all_in_requested_names_finds_all_archive_data_sources():
  138. archive_data_source_names = {'postresql_databases': ['foo', 'bar']}
  139. restore_names = module.find_data_sources_to_restore(
  140. requested_data_source_names=['all'],
  141. archive_data_source_names=archive_data_source_names,
  142. )
  143. assert restore_names == archive_data_source_names
  144. def test_find_data_sources_to_restore_with_all_in_requested_names_plus_additional_requested_names_omits_duplicates():
  145. archive_data_source_names = {'postresql_databases': ['foo', 'bar']}
  146. restore_names = module.find_data_sources_to_restore(
  147. requested_data_source_names=['all', 'foo', 'bar'],
  148. archive_data_source_names=archive_data_source_names,
  149. )
  150. assert restore_names == archive_data_source_names
  151. def test_find_data_sources_to_restore_raises_for_all_in_requested_names_and_requested_named_missing_from_archives():
  152. with pytest.raises(ValueError):
  153. module.find_data_sources_to_restore(
  154. requested_data_source_names=['all', 'foo', 'bar'],
  155. archive_data_source_names={'postresql_databases': ['foo']},
  156. )
  157. def test_ensure_data_sources_found_with_all_data_sources_found_does_not_raise():
  158. module.ensure_data_sources_found(
  159. restore_names={'postgresql_databases': ['foo']},
  160. remaining_restore_names={'postgresql_databases': ['bar']},
  161. found_names=['foo', 'bar'],
  162. )
  163. def test_ensure_data_sources_found_with_no_data_sources_raises():
  164. with pytest.raises(ValueError):
  165. module.ensure_data_sources_found(
  166. restore_names={'postgresql_databases': []},
  167. remaining_restore_names={},
  168. found_names=[],
  169. )
  170. def test_ensure_data_sources_found_with_missing_data_sources_raises():
  171. with pytest.raises(ValueError):
  172. module.ensure_data_sources_found(
  173. restore_names={'postgresql_databases': ['foo']},
  174. remaining_restore_names={'postgresql_databases': ['bar']},
  175. found_names=['foo'],
  176. )
  177. def test_run_restore_restores_each_data_source():
  178. restore_names = {
  179. 'postgresql_databases': ['foo', 'bar'],
  180. }
  181. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  182. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  183. flexmock(module.borgmatic.borg.rlist).should_receive('resolve_archive_name').and_return(
  184. flexmock()
  185. )
  186. flexmock(module).should_receive('collect_archive_data_source_names').and_return(flexmock())
  187. flexmock(module).should_receive('find_data_sources_to_restore').and_return(restore_names)
  188. flexmock(module).should_receive('get_configured_data_source').and_return(
  189. ('postgresql_databases', {'name': 'foo'})
  190. ).and_return(('postgresql_databases', {'name': 'bar'}))
  191. flexmock(module).should_receive('restore_single_data_source').with_args(
  192. repository=object,
  193. config=object,
  194. local_borg_version=object,
  195. global_arguments=object,
  196. local_path=object,
  197. remote_path=object,
  198. archive_name=object,
  199. hook_name='postgresql_databases',
  200. data_source={'name': 'foo', 'schemas': None},
  201. connection_params=object,
  202. ).once()
  203. flexmock(module).should_receive('restore_single_data_source').with_args(
  204. repository=object,
  205. config=object,
  206. local_borg_version=object,
  207. global_arguments=object,
  208. local_path=object,
  209. remote_path=object,
  210. archive_name=object,
  211. hook_name='postgresql_databases',
  212. data_source={'name': 'bar', 'schemas': None},
  213. connection_params=object,
  214. ).once()
  215. flexmock(module).should_receive('ensure_data_sources_found')
  216. module.run_restore(
  217. repository={'path': 'repo'},
  218. config=flexmock(),
  219. local_borg_version=flexmock(),
  220. restore_arguments=flexmock(
  221. repository='repo',
  222. archive='archive',
  223. data_sources=flexmock(),
  224. schemas=None,
  225. hostname=None,
  226. port=None,
  227. username=None,
  228. password=None,
  229. restore_path=None,
  230. ),
  231. global_arguments=flexmock(dry_run=False),
  232. local_path=flexmock(),
  233. remote_path=flexmock(),
  234. )
  235. def test_run_restore_bails_for_non_matching_repository():
  236. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
  237. False
  238. )
  239. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  240. 'call_hooks_even_if_unconfigured'
  241. ).never()
  242. flexmock(module).should_receive('restore_single_data_source').never()
  243. module.run_restore(
  244. repository={'path': 'repo'},
  245. config=flexmock(),
  246. local_borg_version=flexmock(),
  247. restore_arguments=flexmock(repository='repo', archive='archive', data_sources=flexmock()),
  248. global_arguments=flexmock(dry_run=False),
  249. local_path=flexmock(),
  250. remote_path=flexmock(),
  251. )
  252. def test_run_restore_restores_data_source_configured_with_all_name():
  253. restore_names = {
  254. 'postgresql_databases': ['foo', 'bar'],
  255. }
  256. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  257. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  258. flexmock(module.borgmatic.borg.rlist).should_receive('resolve_archive_name').and_return(
  259. flexmock()
  260. )
  261. flexmock(module).should_receive('collect_archive_data_source_names').and_return(flexmock())
  262. flexmock(module).should_receive('find_data_sources_to_restore').and_return(restore_names)
  263. flexmock(module).should_receive('get_configured_data_source').with_args(
  264. config=object,
  265. archive_data_source_names=object,
  266. hook_name='postgresql_databases',
  267. data_source_name='foo',
  268. ).and_return(('postgresql_databases', {'name': 'foo'}))
  269. flexmock(module).should_receive('get_configured_data_source').with_args(
  270. config=object,
  271. archive_data_source_names=object,
  272. hook_name='postgresql_databases',
  273. data_source_name='bar',
  274. ).and_return((None, None))
  275. flexmock(module).should_receive('get_configured_data_source').with_args(
  276. config=object,
  277. archive_data_source_names=object,
  278. hook_name='postgresql_databases',
  279. data_source_name='bar',
  280. configuration_data_source_name='all',
  281. ).and_return(('postgresql_databases', {'name': 'bar'}))
  282. flexmock(module).should_receive('restore_single_data_source').with_args(
  283. repository=object,
  284. config=object,
  285. local_borg_version=object,
  286. global_arguments=object,
  287. local_path=object,
  288. remote_path=object,
  289. archive_name=object,
  290. hook_name='postgresql_databases',
  291. data_source={'name': 'foo', 'schemas': None},
  292. connection_params=object,
  293. ).once()
  294. flexmock(module).should_receive('restore_single_data_source').with_args(
  295. repository=object,
  296. config=object,
  297. local_borg_version=object,
  298. global_arguments=object,
  299. local_path=object,
  300. remote_path=object,
  301. archive_name=object,
  302. hook_name='postgresql_databases',
  303. data_source={'name': 'bar', 'schemas': None},
  304. connection_params=object,
  305. ).once()
  306. flexmock(module).should_receive('ensure_data_sources_found')
  307. module.run_restore(
  308. repository={'path': 'repo'},
  309. config=flexmock(),
  310. local_borg_version=flexmock(),
  311. restore_arguments=flexmock(
  312. repository='repo',
  313. archive='archive',
  314. data_sources=flexmock(),
  315. schemas=None,
  316. hostname=None,
  317. port=None,
  318. username=None,
  319. password=None,
  320. restore_path=None,
  321. ),
  322. global_arguments=flexmock(dry_run=False),
  323. local_path=flexmock(),
  324. remote_path=flexmock(),
  325. )
  326. def test_run_restore_skips_missing_data_source():
  327. restore_names = {
  328. 'postgresql_databases': ['foo', 'bar'],
  329. }
  330. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  331. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  332. flexmock(module.borgmatic.borg.rlist).should_receive('resolve_archive_name').and_return(
  333. flexmock()
  334. )
  335. flexmock(module).should_receive('collect_archive_data_source_names').and_return(flexmock())
  336. flexmock(module).should_receive('find_data_sources_to_restore').and_return(restore_names)
  337. flexmock(module).should_receive('get_configured_data_source').with_args(
  338. config=object,
  339. archive_data_source_names=object,
  340. hook_name='postgresql_databases',
  341. data_source_name='foo',
  342. ).and_return(('postgresql_databases', {'name': 'foo'}))
  343. flexmock(module).should_receive('get_configured_data_source').with_args(
  344. config=object,
  345. archive_data_source_names=object,
  346. hook_name='postgresql_databases',
  347. data_source_name='bar',
  348. ).and_return((None, None))
  349. flexmock(module).should_receive('get_configured_data_source').with_args(
  350. config=object,
  351. archive_data_source_names=object,
  352. hook_name='postgresql_databases',
  353. data_source_name='bar',
  354. configuration_data_source_name='all',
  355. ).and_return((None, None))
  356. flexmock(module).should_receive('restore_single_data_source').with_args(
  357. repository=object,
  358. config=object,
  359. local_borg_version=object,
  360. global_arguments=object,
  361. local_path=object,
  362. remote_path=object,
  363. archive_name=object,
  364. hook_name='postgresql_databases',
  365. data_source={'name': 'foo', 'schemas': None},
  366. connection_params=object,
  367. ).once()
  368. flexmock(module).should_receive('restore_single_data_source').with_args(
  369. repository=object,
  370. config=object,
  371. local_borg_version=object,
  372. global_arguments=object,
  373. local_path=object,
  374. remote_path=object,
  375. archive_name=object,
  376. hook_name='postgresql_databases',
  377. data_source={'name': 'bar', 'schemas': None},
  378. connection_params=object,
  379. ).never()
  380. flexmock(module).should_receive('ensure_data_sources_found')
  381. module.run_restore(
  382. repository={'path': 'repo'},
  383. config=flexmock(),
  384. local_borg_version=flexmock(),
  385. restore_arguments=flexmock(
  386. repository='repo',
  387. archive='archive',
  388. data_sources=flexmock(),
  389. schemas=None,
  390. hostname=None,
  391. port=None,
  392. username=None,
  393. password=None,
  394. restore_path=None,
  395. ),
  396. global_arguments=flexmock(dry_run=False),
  397. local_path=flexmock(),
  398. remote_path=flexmock(),
  399. )
  400. def test_run_restore_restores_data_sources_from_different_hooks():
  401. restore_names = {
  402. 'postgresql_databases': ['foo'],
  403. 'mysql_databases': ['bar'],
  404. }
  405. flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
  406. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
  407. flexmock(module.borgmatic.borg.rlist).should_receive('resolve_archive_name').and_return(
  408. flexmock()
  409. )
  410. flexmock(module).should_receive('collect_archive_data_source_names').and_return(flexmock())
  411. flexmock(module).should_receive('find_data_sources_to_restore').and_return(restore_names)
  412. flexmock(module).should_receive('get_configured_data_source').with_args(
  413. config=object,
  414. archive_data_source_names=object,
  415. hook_name='postgresql_databases',
  416. data_source_name='foo',
  417. ).and_return(('postgresql_databases', {'name': 'foo'}))
  418. flexmock(module).should_receive('get_configured_data_source').with_args(
  419. config=object,
  420. archive_data_source_names=object,
  421. hook_name='mysql_databases',
  422. data_source_name='bar',
  423. ).and_return(('mysql_databases', {'name': 'bar'}))
  424. flexmock(module).should_receive('restore_single_data_source').with_args(
  425. repository=object,
  426. config=object,
  427. local_borg_version=object,
  428. global_arguments=object,
  429. local_path=object,
  430. remote_path=object,
  431. archive_name=object,
  432. hook_name='postgresql_databases',
  433. data_source={'name': 'foo', 'schemas': None},
  434. connection_params=object,
  435. ).once()
  436. flexmock(module).should_receive('restore_single_data_source').with_args(
  437. repository=object,
  438. config=object,
  439. local_borg_version=object,
  440. global_arguments=object,
  441. local_path=object,
  442. remote_path=object,
  443. archive_name=object,
  444. hook_name='mysql_databases',
  445. data_source={'name': 'bar', 'schemas': None},
  446. connection_params=object,
  447. ).once()
  448. flexmock(module).should_receive('ensure_data_sources_found')
  449. module.run_restore(
  450. repository={'path': 'repo'},
  451. config=flexmock(),
  452. local_borg_version=flexmock(),
  453. restore_arguments=flexmock(
  454. repository='repo',
  455. archive='archive',
  456. data_sources=flexmock(),
  457. schemas=None,
  458. hostname=None,
  459. port=None,
  460. username=None,
  461. password=None,
  462. restore_path=None,
  463. ),
  464. global_arguments=flexmock(dry_run=False),
  465. local_path=flexmock(),
  466. remote_path=flexmock(),
  467. )