test_mongodb.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. import logging
  2. from flexmock import flexmock
  3. from borgmatic.hooks import mongodb as module
  4. def test_use_streaming_true_for_any_non_directory_format_databases():
  5. assert module.use_streaming(
  6. databases=[{'format': 'stuff'}, {'format': 'directory'}, {}],
  7. config=flexmock(),
  8. log_prefix=flexmock(),
  9. )
  10. def test_use_streaming_false_for_all_directory_format_databases():
  11. assert not module.use_streaming(
  12. databases=[{'format': 'directory'}, {'format': 'directory'}],
  13. config=flexmock(),
  14. log_prefix=flexmock(),
  15. )
  16. def test_use_streaming_false_for_no_databases():
  17. assert not module.use_streaming(databases=[], config=flexmock(), log_prefix=flexmock())
  18. def test_dump_data_sources_runs_mongodump_for_each_database():
  19. databases = [{'name': 'foo'}, {'name': 'bar'}]
  20. processes = [flexmock(), flexmock()]
  21. flexmock(module).should_receive('make_dump_path').and_return('')
  22. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  23. 'databases/localhost/foo'
  24. ).and_return('databases/localhost/bar')
  25. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  26. for name, process in zip(('foo', 'bar'), processes):
  27. flexmock(module).should_receive('execute_command').with_args(
  28. ('mongodump', '--db', name, '--archive', '>', f'databases/localhost/{name}'),
  29. shell=True,
  30. run_to_completion=False,
  31. ).and_return(process).once()
  32. assert (
  33. module.dump_data_sources(
  34. databases,
  35. {},
  36. 'test.yaml',
  37. borgmatic_runtime_directory='/run/borgmatic',
  38. source_directories=[],
  39. dry_run=False,
  40. )
  41. == processes
  42. )
  43. def test_dump_data_sources_with_dry_run_skips_mongodump():
  44. databases = [{'name': 'foo'}, {'name': 'bar'}]
  45. flexmock(module).should_receive('make_dump_path').and_return('')
  46. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  47. 'databases/localhost/foo'
  48. ).and_return('databases/localhost/bar')
  49. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  50. flexmock(module).should_receive('execute_command').never()
  51. assert (
  52. module.dump_data_sources(
  53. databases,
  54. {},
  55. 'test.yaml',
  56. borgmatic_runtime_directory='/run/borgmatic',
  57. source_directories=[],
  58. dry_run=True,
  59. )
  60. == []
  61. )
  62. def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
  63. databases = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  64. process = flexmock()
  65. flexmock(module).should_receive('make_dump_path').and_return('')
  66. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  67. 'databases/database.example.org/foo'
  68. )
  69. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  70. flexmock(module).should_receive('execute_command').with_args(
  71. (
  72. 'mongodump',
  73. '--host',
  74. 'database.example.org',
  75. '--port',
  76. '5433',
  77. '--db',
  78. 'foo',
  79. '--archive',
  80. '>',
  81. 'databases/database.example.org/foo',
  82. ),
  83. shell=True,
  84. run_to_completion=False,
  85. ).and_return(process).once()
  86. assert module.dump_data_sources(
  87. databases,
  88. {},
  89. 'test.yaml',
  90. borgmatic_runtime_directory='/run/borgmatic',
  91. source_directories=[],
  92. dry_run=False,
  93. ) == [process]
  94. def test_dump_data_sources_runs_mongodump_with_username_and_password():
  95. databases = [
  96. {
  97. 'name': 'foo',
  98. 'username': 'mongo',
  99. 'password': 'trustsome1',
  100. 'authentication_database': 'admin',
  101. }
  102. ]
  103. process = flexmock()
  104. flexmock(module).should_receive('make_dump_path').and_return('')
  105. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  106. 'databases/localhost/foo'
  107. )
  108. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  109. flexmock(module).should_receive('execute_command').with_args(
  110. (
  111. 'mongodump',
  112. '--username',
  113. 'mongo',
  114. '--password',
  115. 'trustsome1',
  116. '--authenticationDatabase',
  117. 'admin',
  118. '--db',
  119. 'foo',
  120. '--archive',
  121. '>',
  122. 'databases/localhost/foo',
  123. ),
  124. shell=True,
  125. run_to_completion=False,
  126. ).and_return(process).once()
  127. assert module.dump_data_sources(
  128. databases,
  129. {},
  130. 'test.yaml',
  131. borgmatic_runtime_directory='/run/borgmatic',
  132. source_directories=[],
  133. dry_run=False,
  134. ) == [process]
  135. def test_dump_data_sources_runs_mongodump_with_directory_format():
  136. databases = [{'name': 'foo', 'format': 'directory'}]
  137. flexmock(module).should_receive('make_dump_path').and_return('')
  138. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  139. 'databases/localhost/foo'
  140. )
  141. flexmock(module.dump).should_receive('create_parent_directory_for_dump')
  142. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  143. flexmock(module).should_receive('execute_command').with_args(
  144. ('mongodump', '--out', 'databases/localhost/foo', '--db', 'foo'),
  145. shell=True,
  146. ).and_return(flexmock()).once()
  147. assert (
  148. module.dump_data_sources(
  149. databases,
  150. {},
  151. 'test.yaml',
  152. borgmatic_runtime_directory='/run/borgmatic',
  153. source_directories=[],
  154. dry_run=False,
  155. )
  156. == []
  157. )
  158. def test_dump_data_sources_runs_mongodump_with_options():
  159. databases = [{'name': 'foo', 'options': '--stuff=such'}]
  160. process = flexmock()
  161. flexmock(module).should_receive('make_dump_path').and_return('')
  162. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  163. 'databases/localhost/foo'
  164. )
  165. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  166. flexmock(module).should_receive('execute_command').with_args(
  167. (
  168. 'mongodump',
  169. '--db',
  170. 'foo',
  171. '--stuff=such',
  172. '--archive',
  173. '>',
  174. 'databases/localhost/foo',
  175. ),
  176. shell=True,
  177. run_to_completion=False,
  178. ).and_return(process).once()
  179. assert module.dump_data_sources(
  180. databases,
  181. {},
  182. 'test.yaml',
  183. borgmatic_runtime_directory='/run/borgmatic',
  184. source_directories=[],
  185. dry_run=False,
  186. ) == [process]
  187. def test_dump_data_sources_runs_mongodumpall_for_all_databases():
  188. databases = [{'name': 'all'}]
  189. process = flexmock()
  190. flexmock(module).should_receive('make_dump_path').and_return('')
  191. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  192. 'databases/localhost/all'
  193. )
  194. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  195. flexmock(module).should_receive('execute_command').with_args(
  196. ('mongodump', '--archive', '>', 'databases/localhost/all'),
  197. shell=True,
  198. run_to_completion=False,
  199. ).and_return(process).once()
  200. assert module.dump_data_sources(
  201. databases,
  202. {},
  203. 'test.yaml',
  204. borgmatic_runtime_directory='/run/borgmatic',
  205. source_directories=[],
  206. dry_run=False,
  207. ) == [process]
  208. def test_build_dump_command_with_username_injection_attack_gets_escaped():
  209. database = {'name': 'test', 'username': 'bob; naughty-command'}
  210. command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
  211. assert "'bob; naughty-command'" in command
  212. def test_restore_data_source_dump_runs_mongorestore():
  213. hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
  214. extract_process = flexmock(stdout=flexmock())
  215. flexmock(module).should_receive('make_dump_path')
  216. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  217. flexmock(module).should_receive('execute_command_with_processes').with_args(
  218. ['mongorestore', '--archive', '--drop'],
  219. processes=[extract_process],
  220. output_log_level=logging.DEBUG,
  221. input_file=extract_process.stdout,
  222. ).once()
  223. module.restore_data_source_dump(
  224. hook_config,
  225. {},
  226. 'test.yaml',
  227. data_source={'name': 'foo'},
  228. dry_run=False,
  229. extract_process=extract_process,
  230. connection_params={
  231. 'hostname': None,
  232. 'port': None,
  233. 'username': None,
  234. 'password': None,
  235. },
  236. borgmatic_runtime_directory='/run/borgmatic',
  237. )
  238. def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
  239. hook_config = [
  240. {'name': 'foo', 'hostname': 'database.example.org', 'port': 5433, 'schemas': None}
  241. ]
  242. extract_process = flexmock(stdout=flexmock())
  243. flexmock(module).should_receive('make_dump_path')
  244. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  245. flexmock(module).should_receive('execute_command_with_processes').with_args(
  246. [
  247. 'mongorestore',
  248. '--archive',
  249. '--drop',
  250. '--host',
  251. 'database.example.org',
  252. '--port',
  253. '5433',
  254. ],
  255. processes=[extract_process],
  256. output_log_level=logging.DEBUG,
  257. input_file=extract_process.stdout,
  258. ).once()
  259. module.restore_data_source_dump(
  260. hook_config,
  261. {},
  262. 'test.yaml',
  263. data_source=hook_config[0],
  264. dry_run=False,
  265. extract_process=extract_process,
  266. connection_params={
  267. 'hostname': None,
  268. 'port': None,
  269. 'username': None,
  270. 'password': None,
  271. },
  272. borgmatic_runtime_directory='/run/borgmatic',
  273. )
  274. def test_restore_data_source_dump_runs_mongorestore_with_username_and_password():
  275. hook_config = [
  276. {
  277. 'name': 'foo',
  278. 'username': 'mongo',
  279. 'password': 'trustsome1',
  280. 'authentication_database': 'admin',
  281. 'schemas': None,
  282. }
  283. ]
  284. extract_process = flexmock(stdout=flexmock())
  285. flexmock(module).should_receive('make_dump_path')
  286. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  287. flexmock(module).should_receive('execute_command_with_processes').with_args(
  288. [
  289. 'mongorestore',
  290. '--archive',
  291. '--drop',
  292. '--username',
  293. 'mongo',
  294. '--password',
  295. 'trustsome1',
  296. '--authenticationDatabase',
  297. 'admin',
  298. ],
  299. processes=[extract_process],
  300. output_log_level=logging.DEBUG,
  301. input_file=extract_process.stdout,
  302. ).once()
  303. module.restore_data_source_dump(
  304. hook_config,
  305. {},
  306. 'test.yaml',
  307. data_source=hook_config[0],
  308. dry_run=False,
  309. extract_process=extract_process,
  310. connection_params={
  311. 'hostname': None,
  312. 'port': None,
  313. 'username': None,
  314. 'password': None,
  315. },
  316. borgmatic_runtime_directory='/run/borgmatic',
  317. )
  318. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  319. hook_config = [
  320. {
  321. 'name': 'foo',
  322. 'username': 'mongo',
  323. 'password': 'trustsome1',
  324. 'authentication_database': 'admin',
  325. 'restore_hostname': 'restorehost',
  326. 'restore_port': 'restoreport',
  327. 'restore_username': 'restoreusername',
  328. 'restore_password': 'restorepassword',
  329. 'schemas': None,
  330. }
  331. ]
  332. extract_process = flexmock(stdout=flexmock())
  333. flexmock(module).should_receive('make_dump_path')
  334. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  335. flexmock(module).should_receive('execute_command_with_processes').with_args(
  336. [
  337. 'mongorestore',
  338. '--archive',
  339. '--drop',
  340. '--host',
  341. 'clihost',
  342. '--port',
  343. 'cliport',
  344. '--username',
  345. 'cliusername',
  346. '--password',
  347. 'clipassword',
  348. '--authenticationDatabase',
  349. 'admin',
  350. ],
  351. processes=[extract_process],
  352. output_log_level=logging.DEBUG,
  353. input_file=extract_process.stdout,
  354. ).once()
  355. module.restore_data_source_dump(
  356. hook_config,
  357. {},
  358. 'test.yaml',
  359. data_source=hook_config[0],
  360. dry_run=False,
  361. extract_process=extract_process,
  362. connection_params={
  363. 'hostname': 'clihost',
  364. 'port': 'cliport',
  365. 'username': 'cliusername',
  366. 'password': 'clipassword',
  367. },
  368. borgmatic_runtime_directory='/run/borgmatic',
  369. )
  370. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  371. hook_config = [
  372. {
  373. 'name': 'foo',
  374. 'username': 'mongo',
  375. 'password': 'trustsome1',
  376. 'authentication_database': 'admin',
  377. 'schemas': None,
  378. 'restore_hostname': 'restorehost',
  379. 'restore_port': 'restoreport',
  380. 'restore_username': 'restoreuser',
  381. 'restore_password': 'restorepass',
  382. }
  383. ]
  384. extract_process = flexmock(stdout=flexmock())
  385. flexmock(module).should_receive('make_dump_path')
  386. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  387. flexmock(module).should_receive('execute_command_with_processes').with_args(
  388. [
  389. 'mongorestore',
  390. '--archive',
  391. '--drop',
  392. '--host',
  393. 'restorehost',
  394. '--port',
  395. 'restoreport',
  396. '--username',
  397. 'restoreuser',
  398. '--password',
  399. 'restorepass',
  400. '--authenticationDatabase',
  401. 'admin',
  402. ],
  403. processes=[extract_process],
  404. output_log_level=logging.DEBUG,
  405. input_file=extract_process.stdout,
  406. ).once()
  407. module.restore_data_source_dump(
  408. hook_config,
  409. {},
  410. 'test.yaml',
  411. data_source=hook_config[0],
  412. dry_run=False,
  413. extract_process=extract_process,
  414. connection_params={
  415. 'hostname': None,
  416. 'port': None,
  417. 'username': None,
  418. 'password': None,
  419. },
  420. borgmatic_runtime_directory='/run/borgmatic',
  421. )
  422. def test_restore_data_source_dump_runs_mongorestore_with_options():
  423. hook_config = [{'name': 'foo', 'restore_options': '--harder', 'schemas': None}]
  424. extract_process = flexmock(stdout=flexmock())
  425. flexmock(module).should_receive('make_dump_path')
  426. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  427. flexmock(module).should_receive('execute_command_with_processes').with_args(
  428. ['mongorestore', '--archive', '--drop', '--harder'],
  429. processes=[extract_process],
  430. output_log_level=logging.DEBUG,
  431. input_file=extract_process.stdout,
  432. ).once()
  433. module.restore_data_source_dump(
  434. hook_config,
  435. {},
  436. 'test.yaml',
  437. data_source=hook_config[0],
  438. dry_run=False,
  439. extract_process=extract_process,
  440. connection_params={
  441. 'hostname': None,
  442. 'port': None,
  443. 'username': None,
  444. 'password': None,
  445. },
  446. borgmatic_runtime_directory='/run/borgmatic',
  447. )
  448. def test_restore_databases_dump_runs_mongorestore_with_schemas():
  449. hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
  450. extract_process = flexmock(stdout=flexmock())
  451. flexmock(module).should_receive('make_dump_path')
  452. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  453. flexmock(module).should_receive('execute_command_with_processes').with_args(
  454. [
  455. 'mongorestore',
  456. '--archive',
  457. '--drop',
  458. '--nsInclude',
  459. 'bar',
  460. '--nsInclude',
  461. 'baz',
  462. ],
  463. processes=[extract_process],
  464. output_log_level=logging.DEBUG,
  465. input_file=extract_process.stdout,
  466. ).once()
  467. module.restore_data_source_dump(
  468. hook_config,
  469. {},
  470. 'test.yaml',
  471. data_source=hook_config[0],
  472. dry_run=False,
  473. extract_process=extract_process,
  474. connection_params={
  475. 'hostname': None,
  476. 'port': None,
  477. 'username': None,
  478. 'password': None,
  479. },
  480. borgmatic_runtime_directory='/run/borgmatic',
  481. )
  482. def test_restore_data_source_dump_runs_psql_for_all_database_dump():
  483. hook_config = [{'name': 'all', 'schemas': None}]
  484. extract_process = flexmock(stdout=flexmock())
  485. flexmock(module).should_receive('make_dump_path')
  486. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  487. flexmock(module).should_receive('execute_command_with_processes').with_args(
  488. ['mongorestore', '--archive'],
  489. processes=[extract_process],
  490. output_log_level=logging.DEBUG,
  491. input_file=extract_process.stdout,
  492. ).once()
  493. module.restore_data_source_dump(
  494. hook_config,
  495. {},
  496. 'test.yaml',
  497. data_source=hook_config[0],
  498. dry_run=False,
  499. extract_process=extract_process,
  500. connection_params={
  501. 'hostname': None,
  502. 'port': None,
  503. 'username': None,
  504. 'password': None,
  505. },
  506. borgmatic_runtime_directory='/run/borgmatic',
  507. )
  508. def test_restore_data_source_dump_with_dry_run_skips_restore():
  509. hook_config = [{'name': 'foo', 'schemas': None}]
  510. flexmock(module).should_receive('make_dump_path')
  511. flexmock(module.dump).should_receive('make_data_source_dump_filename')
  512. flexmock(module).should_receive('execute_command_with_processes').never()
  513. module.restore_data_source_dump(
  514. hook_config,
  515. {},
  516. 'test.yaml',
  517. data_source={'name': 'foo'},
  518. dry_run=True,
  519. extract_process=flexmock(),
  520. connection_params={
  521. 'hostname': None,
  522. 'port': None,
  523. 'username': None,
  524. 'password': None,
  525. },
  526. borgmatic_runtime_directory='/run/borgmatic',
  527. )
  528. def test_restore_data_source_dump_without_extract_process_restores_from_disk():
  529. hook_config = [{'name': 'foo', 'format': 'directory', 'schemas': None}]
  530. flexmock(module).should_receive('make_dump_path')
  531. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
  532. flexmock(module).should_receive('execute_command_with_processes').with_args(
  533. ['mongorestore', '--dir', '/dump/path', '--drop'],
  534. processes=[],
  535. output_log_level=logging.DEBUG,
  536. input_file=None,
  537. ).once()
  538. module.restore_data_source_dump(
  539. hook_config,
  540. {},
  541. 'test.yaml',
  542. data_source={'name': 'foo'},
  543. dry_run=False,
  544. extract_process=None,
  545. connection_params={
  546. 'hostname': None,
  547. 'port': None,
  548. 'username': None,
  549. 'password': None,
  550. },
  551. borgmatic_runtime_directory='/run/borgmatic',
  552. )