test_mariadb.py 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.hooks.data_source import mariadb as module
  5. def test_make_defaults_file_pipe_without_username_or_password_bails():
  6. flexmock(module.os).should_receive('pipe').never()
  7. assert module.make_defaults_file_options(username=None, password=None) is ()
  8. def test_make_defaults_file_option_with_username_and_password_writes_them_to_file_descriptor():
  9. read_descriptor = 99
  10. write_descriptor = flexmock()
  11. flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
  12. flexmock(module.os).should_receive('write').with_args(
  13. write_descriptor, b'[client]\nuser=root\npassword=trustsome1'
  14. ).once()
  15. flexmock(module.os).should_receive('close')
  16. flexmock(module.os).should_receive('set_inheritable')
  17. assert module.make_defaults_file_options(username='root', password='trustsome1') == ('--defaults-extra-file=/dev/fd/99',)
  18. def test_make_defaults_file_pipe_with_username_only_writes_it_to_file_descriptor():
  19. read_descriptor = 99
  20. write_descriptor = flexmock()
  21. flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
  22. flexmock(module.os).should_receive('write').with_args(
  23. write_descriptor, b'[client]\nuser=root'
  24. ).once()
  25. flexmock(module.os).should_receive('close')
  26. flexmock(module.os).should_receive('set_inheritable')
  27. assert module.make_defaults_file_options(username='root', password=None) == ('--defaults-extra-file=/dev/fd/99',)
  28. def test_make_defaults_file_pipe_with_password_only_writes_it_to_file_descriptor():
  29. read_descriptor = 99
  30. write_descriptor = flexmock()
  31. flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
  32. flexmock(module.os).should_receive('write').with_args(
  33. write_descriptor, b'[client]\npassword=trustsome1'
  34. ).once()
  35. flexmock(module.os).should_receive('close')
  36. flexmock(module.os).should_receive('set_inheritable')
  37. assert module.make_defaults_file_options(username=None, password='trustsome1') == ('--defaults-extra-file=/dev/fd/99',)
  38. def test_make_defaults_file_option_with_defaults_extra_filename_includes_it_in_file_descriptor():
  39. read_descriptor = 99
  40. write_descriptor = flexmock()
  41. flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
  42. flexmock(module.os).should_receive('write').with_args(
  43. write_descriptor, b'!include extra.cnf\n[client]\nuser=root\npassword=trustsome1'
  44. ).once()
  45. flexmock(module.os).should_receive('close')
  46. flexmock(module.os).should_receive('set_inheritable')
  47. assert module.make_defaults_file_options(username='root', password='trustsome1', defaults_extra_filename='extra.cnf') == ('--defaults-extra-file=/dev/fd/99',)
  48. def test_database_names_to_dump_passes_through_name():
  49. environment = flexmock()
  50. names = module.database_names_to_dump(
  51. {'name': 'foo'}, {}, 'root', 'trustsome1', environment, dry_run=False
  52. )
  53. assert names == ('foo',)
  54. def test_database_names_to_dump_bails_for_dry_run():
  55. environment = flexmock()
  56. flexmock(module).should_receive('execute_command_and_capture_output').never()
  57. names = module.database_names_to_dump(
  58. {'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=True
  59. )
  60. assert names == ()
  61. def test_database_names_to_dump_queries_mariadb_for_database_names():
  62. environment = flexmock()
  63. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  64. 'resolve_credential'
  65. ).replace_with(lambda value, config: value)
  66. flexmock(module).should_receive(
  67. 'parse_extra_options'
  68. ).and_return((), None)
  69. flexmock(module).should_receive(
  70. 'make_defaults_file_options'
  71. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  72. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  73. (
  74. 'mariadb',
  75. '--defaults-extra-file=/dev/fd/99',
  76. '--skip-column-names',
  77. '--batch',
  78. '--execute',
  79. 'show schemas',
  80. ),
  81. environment=environment,
  82. ).and_return('foo\nbar\nmysql\n').once()
  83. names = module.database_names_to_dump(
  84. {'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=False
  85. )
  86. assert names == ('foo', 'bar')
  87. def test_use_streaming_true_for_any_databases():
  88. assert module.use_streaming(
  89. databases=[flexmock(), flexmock()],
  90. config=flexmock(),
  91. )
  92. def test_use_streaming_false_for_no_databases():
  93. assert not module.use_streaming(databases=[], config=flexmock())
  94. def test_dump_data_sources_dumps_each_database():
  95. databases = [{'name': 'foo'}, {'name': 'bar'}]
  96. processes = [flexmock(), flexmock()]
  97. flexmock(module).should_receive('make_dump_path').and_return('')
  98. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  99. 'resolve_credential'
  100. ).and_return(None)
  101. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  102. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  103. 'resolve_credential'
  104. ).replace_with(lambda value, config: value)
  105. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  106. ('bar',)
  107. )
  108. for name, process in zip(('foo', 'bar'), processes):
  109. flexmock(module).should_receive('execute_dump_command').with_args(
  110. database={'name': name},
  111. config={},
  112. username=None,
  113. password=None,
  114. dump_path=object,
  115. database_names=(name,),
  116. environment={'USER': 'root'},
  117. dry_run=object,
  118. dry_run_label=object,
  119. ).and_return(process).once()
  120. assert (
  121. module.dump_data_sources(
  122. databases,
  123. {},
  124. config_paths=('test.yaml',),
  125. borgmatic_runtime_directory='/run/borgmatic',
  126. patterns=[],
  127. dry_run=False,
  128. )
  129. == processes
  130. )
  131. def test_dump_data_sources_dumps_with_password():
  132. database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
  133. process = flexmock()
  134. flexmock(module).should_receive('make_dump_path').and_return('')
  135. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  136. 'resolve_credential'
  137. ).replace_with(lambda value, config: value)
  138. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  139. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  140. ('bar',)
  141. )
  142. flexmock(module).should_receive('execute_dump_command').with_args(
  143. database=database,
  144. config={},
  145. username='root',
  146. password='trustsome1',
  147. dump_path=object,
  148. database_names=('foo',),
  149. environment={'USER': 'root'},
  150. dry_run=object,
  151. dry_run_label=object,
  152. ).and_return(process).once()
  153. assert module.dump_data_sources(
  154. [database],
  155. {},
  156. config_paths=('test.yaml',),
  157. borgmatic_runtime_directory='/run/borgmatic',
  158. patterns=[],
  159. dry_run=False,
  160. ) == [process]
  161. def test_dump_data_sources_dumps_all_databases_at_once():
  162. databases = [{'name': 'all'}]
  163. process = flexmock()
  164. flexmock(module).should_receive('make_dump_path').and_return('')
  165. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  166. 'resolve_credential'
  167. ).replace_with(lambda value, config: value)
  168. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  169. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  170. flexmock(module).should_receive('execute_dump_command').with_args(
  171. database={'name': 'all'},
  172. config={},
  173. username=None,
  174. password=None,
  175. dump_path=object,
  176. database_names=('foo', 'bar'),
  177. environment={'USER': 'root'},
  178. dry_run=object,
  179. dry_run_label=object,
  180. ).and_return(process).once()
  181. assert module.dump_data_sources(
  182. databases,
  183. {},
  184. config_paths=('test.yaml',),
  185. borgmatic_runtime_directory='/run/borgmatic',
  186. patterns=[],
  187. dry_run=False,
  188. ) == [process]
  189. def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
  190. databases = [{'name': 'all', 'format': 'sql'}]
  191. processes = [flexmock(), flexmock()]
  192. flexmock(module).should_receive('make_dump_path').and_return('')
  193. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  194. 'resolve_credential'
  195. ).and_return(None)
  196. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  197. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  198. for name, process in zip(('foo', 'bar'), processes):
  199. flexmock(module).should_receive('execute_dump_command').with_args(
  200. database={'name': name, 'format': 'sql'},
  201. config={},
  202. username=None,
  203. password=None,
  204. dump_path=object,
  205. database_names=(name,),
  206. environment={'USER': 'root'},
  207. dry_run=object,
  208. dry_run_label=object,
  209. ).and_return(process).once()
  210. assert (
  211. module.dump_data_sources(
  212. databases,
  213. {},
  214. config_paths=('test.yaml',),
  215. borgmatic_runtime_directory='/run/borgmatic',
  216. patterns=[],
  217. dry_run=False,
  218. )
  219. == processes
  220. )
  221. def test_database_names_to_dump_runs_mariadb_with_list_options():
  222. database = {'name': 'all', 'list_options': '--defaults-extra-file=mariadb.cnf --skip-ssl'}
  223. flexmock(module).should_receive(
  224. 'parse_extra_options'
  225. ).and_return(('--skip-ssl',), 'mariadb.cnf')
  226. flexmock(module).should_receive(
  227. 'make_defaults_file_options'
  228. ).with_args('root', 'trustsome1', 'mariadb.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
  229. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  230. (
  231. 'mariadb',
  232. '--defaults-extra-file=/dev/fd/99',
  233. '--skip-ssl',
  234. '--skip-column-names',
  235. '--batch',
  236. '--execute',
  237. 'show schemas',
  238. ),
  239. environment=None,
  240. ).and_return(('foo\nbar')).once()
  241. assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
  242. 'foo',
  243. 'bar',
  244. )
  245. def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
  246. database = {
  247. 'name': 'all',
  248. 'list_options': '--defaults-extra-file=mariadb.cnf --skip-ssl',
  249. 'mariadb_command': 'custom_mariadb',
  250. }
  251. flexmock(module).should_receive(
  252. 'parse_extra_options'
  253. ).and_return(('--skip-ssl',), 'mariadb.cnf')
  254. flexmock(module).should_receive(
  255. 'make_defaults_file_options'
  256. ).with_args('root', 'trustsome1', 'mariadb.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
  257. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  258. environment=None,
  259. full_command=(
  260. 'custom_mariadb', # Custom MariaDB command
  261. '--defaults-extra-file=/dev/fd/99',
  262. '--skip-ssl',
  263. '--skip-column-names',
  264. '--batch',
  265. '--execute',
  266. 'show schemas',
  267. ),
  268. ).and_return(('foo\nbar')).once()
  269. assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
  270. 'foo',
  271. 'bar',
  272. )
  273. def test_execute_dump_command_runs_mariadb_dump():
  274. process = flexmock()
  275. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  276. flexmock(module.os.path).should_receive('exists').and_return(False)
  277. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  278. 'resolve_credential'
  279. ).replace_with(lambda value, config: value)
  280. flexmock(module).should_receive(
  281. 'parse_extra_options'
  282. ).and_return((), None)
  283. flexmock(module).should_receive(
  284. 'make_defaults_file_options'
  285. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  286. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  287. flexmock(module).should_receive('execute_command').with_args(
  288. (
  289. 'mariadb-dump',
  290. '--defaults-extra-file=/dev/fd/99',
  291. '--add-drop-database',
  292. '--databases',
  293. 'foo',
  294. '--result-file',
  295. 'dump',
  296. ),
  297. environment=None,
  298. run_to_completion=False,
  299. ).and_return(process).once()
  300. assert (
  301. module.execute_dump_command(
  302. database={'name': 'foo'},
  303. config={},
  304. username='root',
  305. password='trustsome1',
  306. dump_path=flexmock(),
  307. database_names=('foo',),
  308. environment=None,
  309. dry_run=False,
  310. dry_run_label='',
  311. )
  312. == process
  313. )
  314. def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
  315. process = flexmock()
  316. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  317. flexmock(module.os.path).should_receive('exists').and_return(False)
  318. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  319. 'resolve_credential'
  320. ).replace_with(lambda value, config: value)
  321. flexmock(module).should_receive(
  322. 'parse_extra_options'
  323. ).and_return((), None)
  324. flexmock(module).should_receive(
  325. 'make_defaults_file_options'
  326. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  327. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  328. flexmock(module).should_receive('execute_command').with_args(
  329. (
  330. 'mariadb-dump',
  331. '--defaults-extra-file=/dev/fd/99',
  332. '--databases',
  333. 'foo',
  334. '--result-file',
  335. 'dump',
  336. ),
  337. environment=None,
  338. run_to_completion=False,
  339. ).and_return(process).once()
  340. assert (
  341. module.execute_dump_command(
  342. database={'name': 'foo', 'add_drop_database': False},
  343. config={},
  344. username='root',
  345. password='trustsome1',
  346. dump_path=flexmock(),
  347. database_names=('foo',),
  348. environment=None,
  349. dry_run=False,
  350. dry_run_label='',
  351. )
  352. == process
  353. )
  354. def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
  355. process = flexmock()
  356. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  357. flexmock(module.os.path).should_receive('exists').and_return(False)
  358. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  359. 'resolve_credential'
  360. ).replace_with(lambda value, config: value)
  361. flexmock(module).should_receive(
  362. 'parse_extra_options'
  363. ).and_return((), None)
  364. flexmock(module).should_receive(
  365. 'make_defaults_file_options'
  366. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  367. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  368. flexmock(module).should_receive('execute_command').with_args(
  369. (
  370. 'mariadb-dump',
  371. '--defaults-extra-file=/dev/fd/99',
  372. '--add-drop-database',
  373. '--host',
  374. 'database.example.org',
  375. '--port',
  376. '5433',
  377. '--protocol',
  378. 'tcp',
  379. '--databases',
  380. 'foo',
  381. '--result-file',
  382. 'dump',
  383. ),
  384. environment=None,
  385. run_to_completion=False,
  386. ).and_return(process).once()
  387. assert (
  388. module.execute_dump_command(
  389. database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
  390. config={},
  391. username='root',
  392. password='trustsome1',
  393. dump_path=flexmock(),
  394. database_names=('foo',),
  395. environment=None,
  396. dry_run=False,
  397. dry_run_label='',
  398. )
  399. == process
  400. )
  401. def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
  402. process = flexmock()
  403. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  404. flexmock(module.os.path).should_receive('exists').and_return(False)
  405. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  406. 'resolve_credential'
  407. ).replace_with(lambda value, config: value)
  408. flexmock(module).should_receive(
  409. 'parse_extra_options'
  410. ).and_return((), None)
  411. flexmock(module).should_receive(
  412. 'make_defaults_file_options'
  413. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  414. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  415. flexmock(module).should_receive('execute_command').with_args(
  416. (
  417. 'mariadb-dump',
  418. '--defaults-extra-file=/dev/fd/99',
  419. '--add-drop-database',
  420. '--databases',
  421. 'foo',
  422. '--result-file',
  423. 'dump',
  424. ),
  425. environment={},
  426. run_to_completion=False,
  427. ).and_return(process).once()
  428. assert (
  429. module.execute_dump_command(
  430. database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
  431. config={},
  432. username='root',
  433. password='trustsome1',
  434. dump_path=flexmock(),
  435. database_names=('foo',),
  436. environment={},
  437. dry_run=False,
  438. dry_run_label='',
  439. )
  440. == process
  441. )
  442. def test_execute_dump_command_runs_mariadb_dump_with_options():
  443. process = flexmock()
  444. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  445. flexmock(module.os.path).should_receive('exists').and_return(False)
  446. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  447. 'resolve_credential'
  448. ).replace_with(lambda value, config: value)
  449. flexmock(module).should_receive(
  450. 'parse_extra_options'
  451. ).and_return(('--stuff=such',), None)
  452. flexmock(module).should_receive(
  453. 'make_defaults_file_options'
  454. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  455. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  456. flexmock(module).should_receive('execute_command').with_args(
  457. (
  458. 'mariadb-dump',
  459. '--defaults-extra-file=/dev/fd/99',
  460. '--stuff=such',
  461. '--add-drop-database',
  462. '--databases',
  463. 'foo',
  464. '--result-file',
  465. 'dump',
  466. ),
  467. environment=None,
  468. run_to_completion=False,
  469. ).and_return(process).once()
  470. assert (
  471. module.execute_dump_command(
  472. database={'name': 'foo', 'options': '--stuff=such'},
  473. config={},
  474. username='root',
  475. password='trustsome1',
  476. dump_path=flexmock(),
  477. database_names=('foo',),
  478. environment=None,
  479. dry_run=False,
  480. dry_run_label='',
  481. )
  482. == process
  483. )
  484. def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
  485. process = flexmock()
  486. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  487. flexmock(module.os.path).should_receive('exists').and_return(False)
  488. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  489. 'resolve_credential'
  490. ).replace_with(lambda value, config: value)
  491. flexmock(module).should_receive(
  492. 'parse_extra_options'
  493. ).and_return(('--stuff=such',), None)
  494. flexmock(module).should_receive(
  495. 'make_defaults_file_options'
  496. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  497. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  498. flexmock(module).should_receive('execute_command').with_args(
  499. (
  500. 'custom_mariadb_dump', # Custom MariaDB dump command
  501. '--defaults-extra-file=/dev/fd/99',
  502. '--stuff=such',
  503. '--add-drop-database',
  504. '--databases',
  505. 'foo',
  506. '--result-file',
  507. 'dump',
  508. ),
  509. environment=None,
  510. run_to_completion=False,
  511. ).and_return(process).once()
  512. assert (
  513. module.execute_dump_command(
  514. database={
  515. 'name': 'foo',
  516. 'mariadb_dump_command': 'custom_mariadb_dump',
  517. 'options': '--stuff=such',
  518. }, # Custom MariaDB dump command specified
  519. config={},
  520. username='root',
  521. password='trustsome1',
  522. dump_path=flexmock(),
  523. database_names=('foo',),
  524. environment=None,
  525. dry_run=False,
  526. dry_run_label='',
  527. )
  528. == process
  529. )
  530. def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
  531. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  532. flexmock(module.os.path).should_receive('exists').and_return(True)
  533. flexmock(module).should_receive(
  534. 'parse_extra_options'
  535. ).and_return((), None)
  536. flexmock(module).should_receive(
  537. 'make_defaults_file_options'
  538. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  539. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  540. flexmock(module).should_receive('execute_command').never()
  541. assert (
  542. module.execute_dump_command(
  543. database={'name': 'foo'},
  544. config={},
  545. username='root',
  546. password='trustsome1',
  547. dump_path=flexmock(),
  548. database_names=('foo',),
  549. environment=None,
  550. dry_run=True,
  551. dry_run_label='SO DRY',
  552. )
  553. is None
  554. )
  555. def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
  556. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  557. flexmock(module.os.path).should_receive('exists').and_return(False)
  558. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  559. 'resolve_credential'
  560. ).replace_with(lambda value, config: value)
  561. flexmock(module).should_receive(
  562. 'parse_extra_options'
  563. ).and_return((), None)
  564. flexmock(module).should_receive(
  565. 'make_defaults_file_options'
  566. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  567. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  568. flexmock(module).should_receive('execute_command').never()
  569. assert (
  570. module.execute_dump_command(
  571. database={'name': 'foo'},
  572. config={},
  573. username='root',
  574. password='trustsome1',
  575. dump_path=flexmock(),
  576. database_names=('foo',),
  577. environment=None,
  578. dry_run=True,
  579. dry_run_label='SO DRY',
  580. )
  581. is None
  582. )
  583. def test_dump_data_sources_errors_for_missing_all_databases():
  584. databases = [{'name': 'all'}]
  585. flexmock(module).should_receive('make_dump_path').and_return('')
  586. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  587. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  588. 'resolve_credential'
  589. ).replace_with(lambda value, config: value)
  590. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  591. 'databases/localhost/all'
  592. )
  593. flexmock(module).should_receive('database_names_to_dump').and_return(())
  594. with pytest.raises(ValueError):
  595. assert module.dump_data_sources(
  596. databases,
  597. {},
  598. config_paths=('test.yaml',),
  599. borgmatic_runtime_directory='/run/borgmatic',
  600. patterns=[],
  601. dry_run=False,
  602. )
  603. def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
  604. databases = [{'name': 'all'}]
  605. flexmock(module).should_receive('make_dump_path').and_return('')
  606. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  607. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  608. 'resolve_credential'
  609. ).replace_with(lambda value, config: value)
  610. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  611. 'databases/localhost/all'
  612. )
  613. flexmock(module).should_receive('database_names_to_dump').and_return(())
  614. assert (
  615. module.dump_data_sources(
  616. databases,
  617. {},
  618. config_paths=('test.yaml',),
  619. borgmatic_runtime_directory='/run/borgmatic',
  620. patterns=[],
  621. dry_run=True,
  622. )
  623. == []
  624. )
  625. def test_restore_data_source_dump_runs_mariadb_to_restore():
  626. hook_config = [{'name': 'foo'}, {'name': 'bar'}]
  627. extract_process = flexmock(stdout=flexmock())
  628. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  629. 'resolve_credential'
  630. ).replace_with(lambda value, config: value)
  631. flexmock(module).should_receive(
  632. 'parse_extra_options'
  633. ).and_return((), None)
  634. flexmock(module).should_receive(
  635. 'make_defaults_file_options'
  636. ).with_args(None, None, None).and_return(())
  637. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  638. flexmock(module).should_receive('execute_command_with_processes').with_args(
  639. ('mariadb', '--batch'),
  640. processes=[extract_process],
  641. output_log_level=logging.DEBUG,
  642. input_file=extract_process.stdout,
  643. environment={'USER': 'root'},
  644. ).once()
  645. module.restore_data_source_dump(
  646. hook_config,
  647. {},
  648. data_source={'name': 'foo'},
  649. dry_run=False,
  650. extract_process=extract_process,
  651. connection_params={
  652. 'hostname': None,
  653. 'port': None,
  654. 'username': None,
  655. 'password': None,
  656. },
  657. borgmatic_runtime_directory='/run/borgmatic',
  658. )
  659. def test_restore_data_source_dump_runs_mariadb_with_options():
  660. hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
  661. extract_process = flexmock(stdout=flexmock())
  662. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  663. 'resolve_credential'
  664. ).replace_with(lambda value, config: value)
  665. flexmock(module).should_receive(
  666. 'parse_extra_options'
  667. ).and_return(('--harder',), None)
  668. flexmock(module).should_receive(
  669. 'make_defaults_file_options'
  670. ).with_args(None, None, None).and_return(())
  671. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  672. flexmock(module).should_receive('execute_command_with_processes').with_args(
  673. ('mariadb', '--harder', '--batch'),
  674. processes=[extract_process],
  675. output_log_level=logging.DEBUG,
  676. input_file=extract_process.stdout,
  677. environment={'USER': 'root'},
  678. ).once()
  679. module.restore_data_source_dump(
  680. hook_config,
  681. {},
  682. data_source=hook_config[0],
  683. dry_run=False,
  684. extract_process=extract_process,
  685. connection_params={
  686. 'hostname': None,
  687. 'port': None,
  688. 'username': None,
  689. 'password': None,
  690. },
  691. borgmatic_runtime_directory='/run/borgmatic',
  692. )
  693. def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
  694. hook_config = [
  695. {'name': 'foo', 'restore_options': '--harder', 'mariadb_command': 'custom_mariadb'}
  696. ]
  697. extract_process = flexmock(stdout=flexmock())
  698. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  699. 'resolve_credential'
  700. ).replace_with(lambda value, config: value)
  701. flexmock(module).should_receive(
  702. 'parse_extra_options'
  703. ).and_return(('--harder',), None)
  704. flexmock(module).should_receive(
  705. 'make_defaults_file_options'
  706. ).with_args(None, None, None).and_return(())
  707. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  708. flexmock(module).should_receive('execute_command_with_processes').with_args(
  709. ('custom_mariadb', '--harder', '--batch'),
  710. processes=[extract_process],
  711. output_log_level=logging.DEBUG,
  712. input_file=extract_process.stdout,
  713. environment={'USER': 'root'},
  714. ).once()
  715. module.restore_data_source_dump(
  716. hook_config,
  717. {},
  718. data_source=hook_config[0],
  719. dry_run=False,
  720. extract_process=extract_process,
  721. connection_params={
  722. 'hostname': None,
  723. 'port': None,
  724. 'username': None,
  725. 'password': None,
  726. },
  727. borgmatic_runtime_directory='/run/borgmatic',
  728. )
  729. def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
  730. hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  731. extract_process = flexmock(stdout=flexmock())
  732. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  733. 'resolve_credential'
  734. ).replace_with(lambda value, config: value)
  735. flexmock(module).should_receive(
  736. 'parse_extra_options'
  737. ).and_return((), None)
  738. flexmock(module).should_receive(
  739. 'make_defaults_file_options'
  740. ).with_args(None, None, None).and_return(())
  741. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  742. flexmock(module).should_receive('execute_command_with_processes').with_args(
  743. (
  744. 'mariadb',
  745. '--batch',
  746. '--host',
  747. 'database.example.org',
  748. '--port',
  749. '5433',
  750. '--protocol',
  751. 'tcp',
  752. ),
  753. processes=[extract_process],
  754. output_log_level=logging.DEBUG,
  755. input_file=extract_process.stdout,
  756. environment={'USER': 'root'},
  757. ).once()
  758. module.restore_data_source_dump(
  759. hook_config,
  760. {},
  761. data_source=hook_config[0],
  762. dry_run=False,
  763. extract_process=extract_process,
  764. connection_params={
  765. 'hostname': None,
  766. 'port': None,
  767. 'username': None,
  768. 'password': None,
  769. },
  770. borgmatic_runtime_directory='/run/borgmatic',
  771. )
  772. def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
  773. hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
  774. extract_process = flexmock(stdout=flexmock())
  775. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  776. 'resolve_credential'
  777. ).replace_with(lambda value, config: value)
  778. flexmock(module).should_receive(
  779. 'parse_extra_options'
  780. ).and_return((), None)
  781. flexmock(module).should_receive(
  782. 'make_defaults_file_options'
  783. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  784. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  785. flexmock(module).should_receive('execute_command_with_processes').with_args(
  786. ('mariadb', '--defaults-extra-file=/dev/fd/99', '--batch'),
  787. processes=[extract_process],
  788. output_log_level=logging.DEBUG,
  789. input_file=extract_process.stdout,
  790. environment={'USER': 'root'},
  791. ).once()
  792. module.restore_data_source_dump(
  793. hook_config,
  794. {},
  795. data_source=hook_config[0],
  796. dry_run=False,
  797. extract_process=extract_process,
  798. connection_params={
  799. 'hostname': None,
  800. 'port': None,
  801. 'username': None,
  802. 'password': None,
  803. },
  804. borgmatic_runtime_directory='/run/borgmatic',
  805. )
  806. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  807. hook_config = [
  808. {
  809. 'name': 'foo',
  810. 'username': 'root',
  811. 'password': 'trustsome1',
  812. 'restore_hostname': 'restorehost',
  813. 'restore_port': 'restoreport',
  814. 'restore_username': 'restoreusername',
  815. 'restore_password': 'restorepassword',
  816. }
  817. ]
  818. extract_process = flexmock(stdout=flexmock())
  819. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  820. 'resolve_credential'
  821. ).replace_with(lambda value, config: value)
  822. flexmock(module).should_receive(
  823. 'parse_extra_options'
  824. ).and_return((), None)
  825. flexmock(module).should_receive(
  826. 'make_defaults_file_options'
  827. ).with_args('cliusername', 'clipassword', None).and_return(
  828. ('--defaults-extra-file=/dev/fd/99',)
  829. )
  830. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  831. flexmock(module).should_receive('execute_command_with_processes').with_args(
  832. (
  833. 'mariadb',
  834. '--defaults-extra-file=/dev/fd/99',
  835. '--batch',
  836. '--host',
  837. 'clihost',
  838. '--port',
  839. 'cliport',
  840. '--protocol',
  841. 'tcp',
  842. ),
  843. processes=[extract_process],
  844. output_log_level=logging.DEBUG,
  845. input_file=extract_process.stdout,
  846. environment={'USER': 'root'},
  847. ).once()
  848. module.restore_data_source_dump(
  849. hook_config,
  850. {},
  851. data_source=hook_config[0],
  852. dry_run=False,
  853. extract_process=extract_process,
  854. connection_params={
  855. 'hostname': 'clihost',
  856. 'port': 'cliport',
  857. 'username': 'cliusername',
  858. 'password': 'clipassword',
  859. },
  860. borgmatic_runtime_directory='/run/borgmatic',
  861. )
  862. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  863. hook_config = [
  864. {
  865. 'name': 'foo',
  866. 'username': 'root',
  867. 'password': 'trustsome1',
  868. 'hostname': 'dbhost',
  869. 'port': 'dbport',
  870. 'restore_username': 'restoreuser',
  871. 'restore_password': 'restorepass',
  872. 'restore_hostname': 'restorehost',
  873. 'restore_port': 'restoreport',
  874. }
  875. ]
  876. extract_process = flexmock(stdout=flexmock())
  877. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  878. 'resolve_credential'
  879. ).replace_with(lambda value, config: value)
  880. flexmock(module).should_receive(
  881. 'parse_extra_options'
  882. ).and_return((), None)
  883. flexmock(module).should_receive(
  884. 'make_defaults_file_options'
  885. ).with_args('restoreuser', 'restorepass', None).and_return(
  886. ('--defaults-extra-file=/dev/fd/99',)
  887. )
  888. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  889. flexmock(module).should_receive('execute_command_with_processes').with_args(
  890. (
  891. 'mariadb',
  892. '--defaults-extra-file=/dev/fd/99',
  893. '--batch',
  894. '--host',
  895. 'restorehost',
  896. '--port',
  897. 'restoreport',
  898. '--protocol',
  899. 'tcp',
  900. ),
  901. processes=[extract_process],
  902. output_log_level=logging.DEBUG,
  903. input_file=extract_process.stdout,
  904. environment={'USER': 'root'},
  905. ).once()
  906. module.restore_data_source_dump(
  907. hook_config,
  908. {},
  909. data_source=hook_config[0],
  910. dry_run=False,
  911. extract_process=extract_process,
  912. connection_params={
  913. 'hostname': None,
  914. 'port': None,
  915. 'username': None,
  916. 'password': None,
  917. },
  918. borgmatic_runtime_directory='/run/borgmatic',
  919. )
  920. def test_restore_data_source_dump_with_dry_run_skips_restore():
  921. hook_config = [{'name': 'foo'}]
  922. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  923. 'resolve_credential'
  924. ).replace_with(lambda value, config: value)
  925. flexmock(module).should_receive(
  926. 'parse_extra_options'
  927. ).and_return((), None)
  928. flexmock(module).should_receive(
  929. 'make_defaults_file_options'
  930. ).with_args(None, None, None).and_return(())
  931. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  932. flexmock(module).should_receive('execute_command_with_processes').never()
  933. module.restore_data_source_dump(
  934. hook_config,
  935. {},
  936. data_source={'name': 'foo'},
  937. dry_run=True,
  938. extract_process=flexmock(),
  939. connection_params={
  940. 'hostname': None,
  941. 'port': None,
  942. 'username': None,
  943. 'password': None,
  944. },
  945. borgmatic_runtime_directory='/run/borgmatic',
  946. )