test_mysql.py 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.hooks.data_source import mysql as module
  5. def test_database_names_to_dump_passes_through_name():
  6. environment = flexmock()
  7. names = module.database_names_to_dump(
  8. {'name': 'foo'},
  9. {},
  10. 'root',
  11. 'trustsome1',
  12. environment,
  13. dry_run=False,
  14. )
  15. assert names == ('foo',)
  16. def test_database_names_to_dump_with_non_all_name_and_skip_names_warns():
  17. environment = flexmock()
  18. flexmock(module.logger).should_receive('warning').once()
  19. names = module.database_names_to_dump(
  20. {'name': 'foo', 'skip_names': ('foo', 'bar')},
  21. {},
  22. 'root',
  23. 'trustsome1',
  24. environment,
  25. dry_run=False,
  26. )
  27. assert names == ('foo',)
  28. def test_database_names_to_dump_bails_for_dry_run():
  29. environment = flexmock()
  30. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  31. 'resolve_credential',
  32. ).replace_with(lambda value, config: value)
  33. flexmock(module).should_receive('execute_command_and_capture_output').never()
  34. names = module.database_names_to_dump(
  35. {'name': 'all'},
  36. {},
  37. 'root',
  38. 'trustsome1',
  39. environment,
  40. dry_run=True,
  41. )
  42. assert names == ()
  43. def test_database_names_to_dump_queries_mysql_for_database_names():
  44. environment = flexmock()
  45. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  46. 'resolve_credential',
  47. ).replace_with(lambda value, config: value)
  48. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  49. 'parse_extra_options',
  50. ).and_return((), None)
  51. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  52. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  53. 'make_defaults_file_options',
  54. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  55. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  56. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  57. (
  58. 'mysql',
  59. '--defaults-extra-file=/dev/fd/99',
  60. '--skip-column-names',
  61. '--batch',
  62. '--execute',
  63. 'show schemas',
  64. ),
  65. environment=environment,
  66. working_directory=None,
  67. ).and_return('foo\nbar\nmysql\n').once()
  68. names = module.database_names_to_dump(
  69. {'name': 'all'},
  70. {},
  71. 'root',
  72. 'trustsome1',
  73. environment,
  74. dry_run=False,
  75. )
  76. assert names == ('foo', 'bar')
  77. def test_database_names_to_dump_with_database_name_all_and_skip_names_filters_out_unwanted_databases():
  78. environment = flexmock()
  79. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  80. 'resolve_credential',
  81. ).replace_with(lambda value, config: value)
  82. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  83. 'parse_extra_options'
  84. ).and_return((), None)
  85. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  86. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  87. 'make_defaults_file_options'
  88. ).with_args(
  89. 'root',
  90. 'trustsome1',
  91. None,
  92. ).and_return(('--defaults-extra-file=/dev/fd/99',))
  93. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  94. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  95. (
  96. 'mysql',
  97. '--defaults-extra-file=/dev/fd/99',
  98. '--skip-column-names',
  99. '--batch',
  100. '--execute',
  101. 'show schemas',
  102. ),
  103. environment=environment,
  104. working_directory=None,
  105. ).and_return('foo\nbar\nbaz\nmysql\n').once()
  106. names = module.database_names_to_dump(
  107. {'name': 'all', 'skip_names': ('foo', 'bar')},
  108. {},
  109. 'root',
  110. 'trustsome1',
  111. environment,
  112. dry_run=False,
  113. )
  114. assert names == ('baz',)
  115. def test_database_names_to_dump_runs_mysql_with_socket_path():
  116. environment = flexmock()
  117. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  118. 'resolve_credential',
  119. ).replace_with(lambda value, config: value)
  120. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  121. 'parse_extra_options',
  122. ).and_return((), None)
  123. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  124. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  125. 'make_defaults_file_options',
  126. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  127. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  128. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  129. (
  130. 'mysql',
  131. '--defaults-extra-file=/dev/fd/99',
  132. '--socket',
  133. '/socket',
  134. '--skip-column-names',
  135. '--batch',
  136. '--execute',
  137. 'show schemas',
  138. ),
  139. environment=environment,
  140. working_directory=None,
  141. ).and_return('foo\nbar\nmysql\n').once()
  142. names = module.database_names_to_dump(
  143. {'name': 'all', 'socket_path': '/socket'},
  144. {},
  145. 'root',
  146. 'trustsome1',
  147. environment,
  148. dry_run=False,
  149. )
  150. assert names == ('foo', 'bar')
  151. def test_database_names_to_dump_with_environment_password_transport_skips_defaults_file_and_passes_user_flag():
  152. environment = flexmock()
  153. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  154. 'resolve_credential',
  155. ).replace_with(lambda value, config: value)
  156. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  157. 'parse_extra_options',
  158. ).and_return((), None)
  159. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  160. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  161. 'make_defaults_file_options',
  162. ).never()
  163. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  164. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  165. (
  166. 'mysql',
  167. '--user',
  168. 'root',
  169. '--skip-column-names',
  170. '--batch',
  171. '--execute',
  172. 'show schemas',
  173. ),
  174. environment=environment,
  175. working_directory=None,
  176. ).and_return('foo\nbar\nmysql\n').once()
  177. names = module.database_names_to_dump(
  178. {'name': 'all', 'password_transport': 'environment'},
  179. {},
  180. 'root',
  181. 'trustsome1',
  182. environment,
  183. dry_run=False,
  184. )
  185. assert names == ('foo', 'bar')
  186. def test_database_names_to_dump_runs_mysql_with_tls():
  187. environment = flexmock()
  188. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  189. 'resolve_credential',
  190. ).replace_with(lambda value, config: value)
  191. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  192. 'parse_extra_options',
  193. ).and_return((), None)
  194. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  195. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  196. 'make_defaults_file_options',
  197. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  198. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  199. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  200. (
  201. 'mysql',
  202. '--defaults-extra-file=/dev/fd/99',
  203. '--ssl',
  204. '--skip-column-names',
  205. '--batch',
  206. '--execute',
  207. 'show schemas',
  208. ),
  209. environment=environment,
  210. working_directory=None,
  211. ).and_return('foo\nbar\nmysql\n').once()
  212. names = module.database_names_to_dump(
  213. {'name': 'all', 'tls': True},
  214. {},
  215. 'root',
  216. 'trustsome1',
  217. environment,
  218. dry_run=False,
  219. )
  220. assert names == ('foo', 'bar')
  221. def test_database_names_to_dump_runs_mysql_without_tls():
  222. environment = flexmock()
  223. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  224. 'resolve_credential',
  225. ).replace_with(lambda value, config: value)
  226. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  227. 'parse_extra_options',
  228. ).and_return((), None)
  229. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  230. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  231. 'make_defaults_file_options',
  232. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  233. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  234. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  235. (
  236. 'mysql',
  237. '--defaults-extra-file=/dev/fd/99',
  238. '--skip-ssl',
  239. '--skip-column-names',
  240. '--batch',
  241. '--execute',
  242. 'show schemas',
  243. ),
  244. environment=environment,
  245. working_directory=None,
  246. ).and_return('foo\nbar\nmysql\n').once()
  247. names = module.database_names_to_dump(
  248. {'name': 'all', 'tls': False},
  249. {},
  250. 'root',
  251. 'trustsome1',
  252. environment,
  253. dry_run=False,
  254. )
  255. assert names == ('foo', 'bar')
  256. def test_use_streaming_true_for_any_databases():
  257. assert module.use_streaming(
  258. databases=[flexmock(), flexmock()],
  259. config=flexmock(),
  260. )
  261. def test_use_streaming_false_for_no_databases():
  262. assert not module.use_streaming(databases=[], config=flexmock())
  263. def test_dump_data_sources_dumps_each_database():
  264. databases = [{'name': 'foo'}, {'name': 'bar'}]
  265. processes = [flexmock(), flexmock()]
  266. flexmock(module).should_receive('make_dump_path').and_return('')
  267. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  268. 'resolve_credential',
  269. ).and_return(None)
  270. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  271. flexmock(module).should_receive('database_names_to_dump').with_args(
  272. database=databases[0],
  273. config={},
  274. username=None,
  275. password=None,
  276. environment={'USER': 'root'},
  277. dry_run=False,
  278. ).and_return(('foo',))
  279. flexmock(module).should_receive('database_names_to_dump').with_args(
  280. database=databases[1],
  281. config={},
  282. username=None,
  283. password=None,
  284. environment={'USER': 'root'},
  285. dry_run=False,
  286. ).and_return(('bar',))
  287. for name, process in zip(('foo', 'bar'), processes):
  288. flexmock(module).should_receive('execute_dump_command').with_args(
  289. database={'name': name},
  290. config={},
  291. username=None,
  292. password=None,
  293. dump_path=object,
  294. database_names=(name,),
  295. environment={'USER': 'root'},
  296. dry_run=object,
  297. dry_run_label=object,
  298. ).and_return(process).once()
  299. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  300. '/run/borgmatic',
  301. 'mysql_databases',
  302. [
  303. module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'),
  304. module.borgmatic.actions.restore.Dump('mysql_databases', 'bar'),
  305. ],
  306. ).once()
  307. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  308. object,
  309. module.borgmatic.borg.pattern.Pattern(
  310. '/run/borgmatic/mysql_databases',
  311. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  312. ),
  313. ).once()
  314. assert (
  315. module.dump_data_sources(
  316. databases,
  317. {},
  318. config_paths=('test.yaml',),
  319. borgmatic_runtime_directory='/run/borgmatic',
  320. patterns=[],
  321. dry_run=False,
  322. )
  323. == processes
  324. )
  325. def test_dump_data_sources_dumps_with_password():
  326. database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
  327. process = flexmock()
  328. flexmock(module).should_receive('make_dump_path').and_return('')
  329. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  330. 'resolve_credential',
  331. ).replace_with(lambda value, config: value)
  332. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  333. flexmock(module).should_receive('database_names_to_dump').with_args(
  334. database=database,
  335. config={},
  336. username='root',
  337. password='trustsome1',
  338. environment={'USER': 'root'},
  339. dry_run=False,
  340. ).and_return(('foo',))
  341. flexmock(module).should_receive('execute_dump_command').with_args(
  342. database=database,
  343. config={},
  344. username='root',
  345. password='trustsome1',
  346. dump_path=object,
  347. database_names=('foo',),
  348. environment={'USER': 'root'},
  349. dry_run=object,
  350. dry_run_label=object,
  351. ).and_return(process).once()
  352. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  353. '/run/borgmatic',
  354. 'mysql_databases',
  355. [
  356. module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'),
  357. ],
  358. ).once()
  359. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  360. object,
  361. module.borgmatic.borg.pattern.Pattern(
  362. '/run/borgmatic/mysql_databases',
  363. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  364. ),
  365. ).once()
  366. assert module.dump_data_sources(
  367. [database],
  368. {},
  369. config_paths=('test.yaml',),
  370. borgmatic_runtime_directory='/run/borgmatic',
  371. patterns=[],
  372. dry_run=False,
  373. ) == [process]
  374. def test_dump_data_sources_dumps_with_environment_password_transport_passes_password_environment_variable():
  375. database = {
  376. 'name': 'foo',
  377. 'username': 'root',
  378. 'password': 'trustsome1',
  379. 'password_transport': 'environment',
  380. }
  381. process = flexmock()
  382. flexmock(module).should_receive('make_dump_path').and_return('')
  383. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  384. 'resolve_credential',
  385. ).replace_with(lambda value, config: value)
  386. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  387. flexmock(module).should_receive('database_names_to_dump').with_args(
  388. database=database,
  389. config={},
  390. username='root',
  391. password='trustsome1',
  392. environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
  393. dry_run=False,
  394. ).and_return(('foo',))
  395. flexmock(module).should_receive('execute_dump_command').with_args(
  396. database=database,
  397. config={},
  398. username='root',
  399. password='trustsome1',
  400. dump_path=object,
  401. database_names=('foo',),
  402. environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
  403. dry_run=object,
  404. dry_run_label=object,
  405. ).and_return(process).once()
  406. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  407. '/run/borgmatic',
  408. 'mysql_databases',
  409. [
  410. module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'),
  411. ],
  412. ).once()
  413. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  414. object,
  415. module.borgmatic.borg.pattern.Pattern(
  416. '/run/borgmatic/mysql_databases',
  417. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  418. ),
  419. ).once()
  420. assert module.dump_data_sources(
  421. [database],
  422. {},
  423. config_paths=('test.yaml',),
  424. borgmatic_runtime_directory='/run/borgmatic',
  425. patterns=[],
  426. dry_run=False,
  427. ) == [process]
  428. def test_dump_data_sources_dumps_all_databases_at_once():
  429. databases = [{'name': 'all'}]
  430. process = flexmock()
  431. flexmock(module).should_receive('make_dump_path').and_return('')
  432. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  433. 'resolve_credential',
  434. ).and_return(None)
  435. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  436. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  437. flexmock(module).should_receive('execute_dump_command').with_args(
  438. database={'name': 'all'},
  439. config={},
  440. username=None,
  441. password=None,
  442. dump_path=object,
  443. database_names=('foo', 'bar'),
  444. environment={'USER': 'root'},
  445. dry_run=object,
  446. dry_run_label=object,
  447. ).and_return(process).once()
  448. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  449. '/run/borgmatic',
  450. 'mysql_databases',
  451. [
  452. module.borgmatic.actions.restore.Dump('mysql_databases', 'all'),
  453. ],
  454. ).once()
  455. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  456. object,
  457. module.borgmatic.borg.pattern.Pattern(
  458. '/run/borgmatic/mysql_databases',
  459. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  460. ),
  461. ).once()
  462. assert module.dump_data_sources(
  463. databases,
  464. {},
  465. config_paths=('test.yaml',),
  466. borgmatic_runtime_directory='/run/borgmatic',
  467. patterns=[],
  468. dry_run=False,
  469. ) == [process]
  470. def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
  471. databases = [{'name': 'all', 'format': 'sql'}]
  472. processes = [flexmock(), flexmock()]
  473. flexmock(module).should_receive('make_dump_path').and_return('')
  474. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  475. 'resolve_credential',
  476. ).and_return(None)
  477. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  478. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  479. for name, process in zip(('foo', 'bar'), processes):
  480. flexmock(module).should_receive('execute_dump_command').with_args(
  481. database={'name': name, 'format': 'sql'},
  482. config={},
  483. username=None,
  484. password=None,
  485. dump_path=object,
  486. database_names=(name,),
  487. environment={'USER': 'root'},
  488. dry_run=object,
  489. dry_run_label=object,
  490. ).and_return(process).once()
  491. flexmock(module.dump).should_receive('write_data_source_dumps_metadata').with_args(
  492. '/run/borgmatic',
  493. 'mysql_databases',
  494. [
  495. module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'),
  496. module.borgmatic.actions.restore.Dump('mysql_databases', 'bar'),
  497. ],
  498. ).once()
  499. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
  500. object,
  501. module.borgmatic.borg.pattern.Pattern(
  502. '/run/borgmatic/mysql_databases',
  503. source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
  504. ),
  505. ).once()
  506. assert (
  507. module.dump_data_sources(
  508. databases,
  509. {},
  510. config_paths=('test.yaml',),
  511. borgmatic_runtime_directory='/run/borgmatic',
  512. patterns=[],
  513. dry_run=False,
  514. )
  515. == processes
  516. )
  517. def test_dump_data_sources_errors_for_missing_all_databases():
  518. databases = [{'name': 'all'}]
  519. flexmock(module).should_receive('make_dump_path').and_return('')
  520. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  521. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  522. 'resolve_credential',
  523. ).replace_with(lambda value, config: value)
  524. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  525. 'databases/localhost/all',
  526. )
  527. flexmock(module).should_receive('database_names_to_dump').and_return(())
  528. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
  529. with pytest.raises(ValueError):
  530. assert module.dump_data_sources(
  531. databases,
  532. {},
  533. config_paths=('test.yaml',),
  534. borgmatic_runtime_directory='/run/borgmatic',
  535. patterns=[],
  536. dry_run=False,
  537. )
  538. def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
  539. databases = [{'name': 'all'}]
  540. flexmock(module).should_receive('make_dump_path').and_return('')
  541. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  542. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  543. 'resolve_credential',
  544. ).replace_with(lambda value, config: value)
  545. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  546. 'databases/localhost/all',
  547. )
  548. flexmock(module).should_receive('database_names_to_dump').and_return(())
  549. flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
  550. assert (
  551. module.dump_data_sources(
  552. databases,
  553. {},
  554. config_paths=('test.yaml',),
  555. borgmatic_runtime_directory='/run/borgmatic',
  556. patterns=[],
  557. dry_run=True,
  558. )
  559. == []
  560. )
  561. def test_database_names_to_dump_runs_mysql_with_list_options():
  562. database = {'name': 'all', 'list_options': '--defaults-extra-file=my.cnf --skip-ssl'}
  563. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  564. 'parse_extra_options',
  565. ).and_return(('--skip-ssl',), 'my.cnf')
  566. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  567. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  568. 'make_defaults_file_options',
  569. ).with_args('root', 'trustsome1', 'my.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
  570. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  571. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  572. (
  573. 'mysql',
  574. '--defaults-extra-file=/dev/fd/99',
  575. '--skip-ssl',
  576. '--skip-column-names',
  577. '--batch',
  578. '--execute',
  579. 'show schemas',
  580. ),
  581. environment=None,
  582. working_directory=None,
  583. ).and_return('foo\nbar').once()
  584. assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
  585. 'foo',
  586. 'bar',
  587. )
  588. def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
  589. database = {
  590. 'name': 'all',
  591. 'list_options': '--defaults-extra-file=my.cnf --skip-ssl',
  592. 'mysql_command': 'custom_mysql',
  593. }
  594. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  595. 'parse_extra_options',
  596. ).and_return(('--skip-ssl',), 'my.cnf')
  597. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  598. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  599. 'make_defaults_file_options',
  600. ).with_args('root', 'trustsome1', 'my.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
  601. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  602. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  603. environment=None,
  604. full_command=(
  605. 'custom_mysql', # Custom MySQL command
  606. '--defaults-extra-file=/dev/fd/99',
  607. '--skip-ssl',
  608. '--skip-column-names',
  609. '--batch',
  610. '--execute',
  611. 'show schemas',
  612. ),
  613. working_directory=None,
  614. ).and_return('foo\nbar').once()
  615. assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
  616. 'foo',
  617. 'bar',
  618. )
  619. def test_execute_dump_command_runs_mysqldump():
  620. process = flexmock()
  621. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  622. flexmock(module.os.path).should_receive('exists').and_return(False)
  623. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  624. 'resolve_credential',
  625. ).replace_with(lambda value, config: value)
  626. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  627. 'parse_extra_options',
  628. ).and_return((), None)
  629. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  630. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  631. 'make_defaults_file_options',
  632. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  633. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  634. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  635. flexmock(module).should_receive('execute_command').with_args(
  636. (
  637. 'mysqldump',
  638. '--defaults-extra-file=/dev/fd/99',
  639. '--add-drop-database',
  640. '--single-transaction',
  641. '--databases',
  642. 'foo',
  643. '--result-file',
  644. 'dump',
  645. ),
  646. environment=None,
  647. run_to_completion=False,
  648. working_directory=None,
  649. ).and_return(process).once()
  650. assert (
  651. module.execute_dump_command(
  652. database={'name': 'foo'},
  653. config={},
  654. username='root',
  655. password='trustsome1',
  656. dump_path=flexmock(),
  657. database_names=('foo',),
  658. environment=None,
  659. dry_run=False,
  660. dry_run_label='',
  661. )
  662. == process
  663. )
  664. def test_execute_dump_command_with_environment_password_transport_skips_defaults_file_and_passes_user_flag():
  665. process = flexmock()
  666. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  667. flexmock(module.os.path).should_receive('exists').and_return(False)
  668. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  669. 'resolve_credential',
  670. ).replace_with(lambda value, config: value)
  671. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  672. 'parse_extra_options',
  673. ).and_return((), None)
  674. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  675. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  676. 'make_defaults_file_options',
  677. ).never()
  678. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  679. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  680. flexmock(module).should_receive('execute_command').with_args(
  681. (
  682. 'mysqldump',
  683. '--add-drop-database',
  684. '--single-transaction',
  685. '--user',
  686. 'root',
  687. '--databases',
  688. 'foo',
  689. '--result-file',
  690. 'dump',
  691. ),
  692. environment=None,
  693. run_to_completion=False,
  694. working_directory=None,
  695. ).and_return(process).once()
  696. assert (
  697. module.execute_dump_command(
  698. database={'name': 'foo', 'password_transport': 'environment'},
  699. config={},
  700. username='root',
  701. password='trustsome1',
  702. dump_path=flexmock(),
  703. database_names=('foo',),
  704. environment=None,
  705. dry_run=False,
  706. dry_run_label='',
  707. )
  708. == process
  709. )
  710. def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
  711. process = flexmock()
  712. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  713. flexmock(module.os.path).should_receive('exists').and_return(False)
  714. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  715. 'resolve_credential',
  716. ).replace_with(lambda value, config: value)
  717. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  718. 'parse_extra_options',
  719. ).and_return((), None)
  720. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  721. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  722. 'make_defaults_file_options',
  723. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  724. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  725. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  726. flexmock(module).should_receive('execute_command').with_args(
  727. (
  728. 'mysqldump',
  729. '--defaults-extra-file=/dev/fd/99',
  730. '--single-transaction',
  731. '--databases',
  732. 'foo',
  733. '--result-file',
  734. 'dump',
  735. ),
  736. environment=None,
  737. run_to_completion=False,
  738. working_directory=None,
  739. ).and_return(process).once()
  740. assert (
  741. module.execute_dump_command(
  742. database={'name': 'foo', 'add_drop_database': False},
  743. config={},
  744. username='root',
  745. password='trustsome1',
  746. dump_path=flexmock(),
  747. database_names=('foo',),
  748. environment=None,
  749. dry_run=False,
  750. dry_run_label='',
  751. )
  752. == process
  753. )
  754. def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
  755. process = flexmock()
  756. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  757. flexmock(module.os.path).should_receive('exists').and_return(False)
  758. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  759. 'resolve_credential',
  760. ).replace_with(lambda value, config: value)
  761. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  762. 'parse_extra_options',
  763. ).and_return((), None)
  764. flexmock(module.database_config).should_receive('resolve_database_option').and_return(
  765. 'database.example.org'
  766. )
  767. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  768. 'make_defaults_file_options',
  769. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  770. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  771. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  772. flexmock(module).should_receive('execute_command').with_args(
  773. (
  774. 'mysqldump',
  775. '--defaults-extra-file=/dev/fd/99',
  776. '--add-drop-database',
  777. '--single-transaction',
  778. '--host',
  779. 'database.example.org',
  780. '--port',
  781. '5433',
  782. '--protocol',
  783. 'tcp',
  784. '--databases',
  785. 'foo',
  786. '--result-file',
  787. 'dump',
  788. ),
  789. environment=None,
  790. run_to_completion=False,
  791. working_directory=None,
  792. ).and_return(process).once()
  793. assert (
  794. module.execute_dump_command(
  795. database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
  796. config={},
  797. username='root',
  798. password='trustsome1',
  799. dump_path=flexmock(),
  800. database_names=('foo',),
  801. environment=None,
  802. dry_run=False,
  803. dry_run_label='',
  804. )
  805. == process
  806. )
  807. def test_execute_dump_command_runs_mysqldump_with_tls():
  808. process = flexmock()
  809. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  810. flexmock(module.os.path).should_receive('exists').and_return(False)
  811. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  812. 'resolve_credential',
  813. ).replace_with(lambda value, config: value)
  814. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  815. 'parse_extra_options',
  816. ).and_return((), None)
  817. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  818. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  819. 'make_defaults_file_options',
  820. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  821. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  822. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  823. flexmock(module).should_receive('execute_command').with_args(
  824. (
  825. 'mysqldump',
  826. '--defaults-extra-file=/dev/fd/99',
  827. '--add-drop-database',
  828. '--single-transaction',
  829. '--ssl',
  830. '--databases',
  831. 'foo',
  832. '--result-file',
  833. 'dump',
  834. ),
  835. environment=None,
  836. run_to_completion=False,
  837. working_directory=None,
  838. ).and_return(process).once()
  839. assert (
  840. module.execute_dump_command(
  841. database={'name': 'foo', 'tls': True},
  842. config={},
  843. username='root',
  844. password='trustsome1',
  845. dump_path=flexmock(),
  846. database_names=('foo',),
  847. environment=None,
  848. dry_run=False,
  849. dry_run_label='',
  850. )
  851. == process
  852. )
  853. def test_execute_dump_command_runs_mysqldump_without_tls():
  854. process = flexmock()
  855. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  856. flexmock(module.os.path).should_receive('exists').and_return(False)
  857. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  858. 'resolve_credential',
  859. ).replace_with(lambda value, config: value)
  860. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  861. 'parse_extra_options',
  862. ).and_return((), None)
  863. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  864. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  865. 'make_defaults_file_options',
  866. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  867. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  868. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  869. flexmock(module).should_receive('execute_command').with_args(
  870. (
  871. 'mysqldump',
  872. '--defaults-extra-file=/dev/fd/99',
  873. '--add-drop-database',
  874. '--single-transaction',
  875. '--skip-ssl',
  876. '--databases',
  877. 'foo',
  878. '--result-file',
  879. 'dump',
  880. ),
  881. environment=None,
  882. run_to_completion=False,
  883. working_directory=None,
  884. ).and_return(process).once()
  885. assert (
  886. module.execute_dump_command(
  887. database={'name': 'foo', 'tls': False},
  888. config={},
  889. username='root',
  890. password='trustsome1',
  891. dump_path=flexmock(),
  892. database_names=('foo',),
  893. environment=None,
  894. dry_run=False,
  895. dry_run_label='',
  896. )
  897. == process
  898. )
  899. def test_execute_dump_command_runs_mysqldump_with_username_and_password():
  900. process = flexmock()
  901. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  902. flexmock(module.os.path).should_receive('exists').and_return(False)
  903. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  904. 'resolve_credential',
  905. ).replace_with(lambda value, config: value)
  906. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  907. 'parse_extra_options',
  908. ).and_return((), None)
  909. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  910. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  911. 'make_defaults_file_options',
  912. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  913. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  914. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  915. flexmock(module).should_receive('execute_command').with_args(
  916. (
  917. 'mysqldump',
  918. '--defaults-extra-file=/dev/fd/99',
  919. '--add-drop-database',
  920. '--single-transaction',
  921. '--databases',
  922. 'foo',
  923. '--result-file',
  924. 'dump',
  925. ),
  926. environment={},
  927. run_to_completion=False,
  928. working_directory=None,
  929. ).and_return(process).once()
  930. assert (
  931. module.execute_dump_command(
  932. database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
  933. config={},
  934. username='root',
  935. password='trustsome1',
  936. dump_path=flexmock(),
  937. database_names=('foo',),
  938. environment={},
  939. dry_run=False,
  940. dry_run_label='',
  941. )
  942. == process
  943. )
  944. def test_execute_dump_command_runs_mysqldump_with_options():
  945. process = flexmock()
  946. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  947. flexmock(module.os.path).should_receive('exists').and_return(False)
  948. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  949. 'resolve_credential',
  950. ).replace_with(lambda value, config: value)
  951. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  952. 'parse_extra_options',
  953. ).and_return(('--stuff=such',), None)
  954. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  955. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  956. 'make_defaults_file_options',
  957. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  958. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  959. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  960. flexmock(module).should_receive('execute_command').with_args(
  961. (
  962. 'mysqldump',
  963. '--defaults-extra-file=/dev/fd/99',
  964. '--stuff=such',
  965. '--add-drop-database',
  966. '--single-transaction',
  967. '--databases',
  968. 'foo',
  969. '--result-file',
  970. 'dump',
  971. ),
  972. environment=None,
  973. run_to_completion=False,
  974. working_directory=None,
  975. ).and_return(process).once()
  976. assert (
  977. module.execute_dump_command(
  978. database={'name': 'foo', 'options': '--stuff=such'},
  979. config={},
  980. username='root',
  981. password='trustsome1',
  982. dump_path=flexmock(),
  983. database_names=('foo',),
  984. environment=None,
  985. dry_run=False,
  986. dry_run_label='',
  987. )
  988. == process
  989. )
  990. def test_execute_dump_command_runs_non_default_mysqldump():
  991. process = flexmock()
  992. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  993. flexmock(module.os.path).should_receive('exists').and_return(False)
  994. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  995. 'resolve_credential',
  996. ).replace_with(lambda value, config: value)
  997. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  998. 'parse_extra_options',
  999. ).and_return((), None)
  1000. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1001. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1002. 'make_defaults_file_options',
  1003. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  1004. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  1005. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1006. flexmock(module).should_receive('execute_command').with_args(
  1007. (
  1008. 'custom_mysqldump', # Custom MySQL dump command
  1009. '--defaults-extra-file=/dev/fd/99',
  1010. '--add-drop-database',
  1011. '--single-transaction',
  1012. '--databases',
  1013. 'foo',
  1014. '--result-file',
  1015. 'dump',
  1016. ),
  1017. environment=None,
  1018. run_to_completion=False,
  1019. working_directory=None,
  1020. ).and_return(process).once()
  1021. assert (
  1022. module.execute_dump_command(
  1023. database={
  1024. 'name': 'foo',
  1025. 'mysql_dump_command': 'custom_mysqldump',
  1026. }, # Custom MySQL dump command specified
  1027. config={},
  1028. username='root',
  1029. password='trustsome1',
  1030. dump_path=flexmock(),
  1031. database_names=('foo',),
  1032. environment=None,
  1033. dry_run=False,
  1034. dry_run_label='',
  1035. )
  1036. == process
  1037. )
  1038. def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
  1039. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  1040. flexmock(module.os.path).should_receive('exists').and_return(True)
  1041. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1042. 'parse_extra_options',
  1043. ).and_return((), None)
  1044. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1045. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1046. 'make_defaults_file_options',
  1047. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  1048. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  1049. flexmock(module).should_receive('execute_command').never()
  1050. assert (
  1051. module.execute_dump_command(
  1052. database={'name': 'foo'},
  1053. config={},
  1054. username='root',
  1055. password='trustsome1',
  1056. dump_path=flexmock(),
  1057. database_names=('foo',),
  1058. environment=None,
  1059. dry_run=True,
  1060. dry_run_label='SO DRY',
  1061. )
  1062. is None
  1063. )
  1064. def test_execute_dump_command_with_dry_run_skips_mysqldump():
  1065. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
  1066. flexmock(module.os.path).should_receive('exists').and_return(False)
  1067. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1068. 'resolve_credential',
  1069. ).replace_with(lambda value, config: value)
  1070. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1071. 'parse_extra_options',
  1072. ).and_return((), None)
  1073. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1074. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1075. 'make_defaults_file_options',
  1076. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  1077. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  1078. flexmock(module).should_receive('execute_command').never()
  1079. assert (
  1080. module.execute_dump_command(
  1081. database={'name': 'foo'},
  1082. config={},
  1083. username='root',
  1084. password='trustsome1',
  1085. dump_path=flexmock(),
  1086. database_names=('foo',),
  1087. environment=None,
  1088. dry_run=True,
  1089. dry_run_label='SO DRY',
  1090. )
  1091. is None
  1092. )
  1093. def test_restore_data_source_dump_runs_mysql_to_restore():
  1094. hook_config = [{'name': 'foo'}, {'name': 'bar'}]
  1095. extract_process = flexmock(stdout=flexmock())
  1096. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1097. 'resolve_credential',
  1098. ).replace_with(lambda value, config: value)
  1099. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1100. 'parse_extra_options',
  1101. ).and_return((), None)
  1102. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1103. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1104. 'make_defaults_file_options',
  1105. ).with_args(None, None, None).and_return(())
  1106. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1107. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1108. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1109. ('mysql', '--batch'),
  1110. processes=[extract_process],
  1111. output_log_level=logging.DEBUG,
  1112. input_file=extract_process.stdout,
  1113. environment={'USER': 'root'},
  1114. working_directory=None,
  1115. ).once()
  1116. module.restore_data_source_dump(
  1117. hook_config,
  1118. {},
  1119. data_source={'name': 'foo'},
  1120. dry_run=False,
  1121. extract_process=extract_process,
  1122. connection_params={
  1123. 'hostname': None,
  1124. 'port': None,
  1125. 'username': None,
  1126. 'password': None,
  1127. },
  1128. borgmatic_runtime_directory='/run/borgmatic',
  1129. )
  1130. def test_restore_data_source_dump_runs_mysql_with_options():
  1131. hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
  1132. extract_process = flexmock(stdout=flexmock())
  1133. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1134. 'resolve_credential',
  1135. ).replace_with(lambda value, config: value)
  1136. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1137. 'parse_extra_options',
  1138. ).and_return(('--harder',), None)
  1139. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1140. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1141. 'make_defaults_file_options',
  1142. ).with_args(None, None, None).and_return(())
  1143. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1144. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1145. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1146. ('mysql', '--harder', '--batch'),
  1147. processes=[extract_process],
  1148. output_log_level=logging.DEBUG,
  1149. input_file=extract_process.stdout,
  1150. environment={'USER': 'root'},
  1151. working_directory=None,
  1152. ).once()
  1153. module.restore_data_source_dump(
  1154. hook_config,
  1155. {},
  1156. data_source=hook_config[0],
  1157. dry_run=False,
  1158. extract_process=extract_process,
  1159. connection_params={
  1160. 'hostname': None,
  1161. 'port': None,
  1162. 'username': None,
  1163. 'password': None,
  1164. },
  1165. borgmatic_runtime_directory='/run/borgmatic',
  1166. )
  1167. def test_restore_data_source_dump_runs_non_default_mysql_with_options():
  1168. hook_config = [{'name': 'foo', 'mysql_command': 'custom_mysql', 'restore_options': '--harder'}]
  1169. extract_process = flexmock(stdout=flexmock())
  1170. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1171. 'resolve_credential',
  1172. ).replace_with(lambda value, config: value)
  1173. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1174. 'parse_extra_options',
  1175. ).and_return(('--harder',), None)
  1176. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1177. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1178. 'make_defaults_file_options',
  1179. ).with_args(None, None, None).and_return(())
  1180. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1181. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1182. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1183. ('custom_mysql', '--harder', '--batch'),
  1184. processes=[extract_process],
  1185. output_log_level=logging.DEBUG,
  1186. input_file=extract_process.stdout,
  1187. environment={'USER': 'root'},
  1188. working_directory=None,
  1189. ).once()
  1190. module.restore_data_source_dump(
  1191. hook_config,
  1192. {},
  1193. data_source=hook_config[0],
  1194. dry_run=False,
  1195. extract_process=extract_process,
  1196. connection_params={
  1197. 'hostname': None,
  1198. 'port': None,
  1199. 'username': None,
  1200. 'password': None,
  1201. },
  1202. borgmatic_runtime_directory='/run/borgmatic',
  1203. )
  1204. def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
  1205. hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  1206. extract_process = flexmock(stdout=flexmock())
  1207. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1208. 'resolve_credential',
  1209. ).replace_with(lambda value, config: value)
  1210. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1211. 'parse_extra_options',
  1212. ).and_return((), None)
  1213. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1214. lambda option, data_source, connection_params=None, restore=False: data_source.get(option)
  1215. )
  1216. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1217. 'make_defaults_file_options',
  1218. ).with_args(None, None, None).and_return(())
  1219. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1220. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1221. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1222. (
  1223. 'mysql',
  1224. '--batch',
  1225. '--host',
  1226. 'database.example.org',
  1227. '--port',
  1228. '5433',
  1229. '--protocol',
  1230. 'tcp',
  1231. ),
  1232. processes=[extract_process],
  1233. output_log_level=logging.DEBUG,
  1234. input_file=extract_process.stdout,
  1235. environment={'USER': 'root'},
  1236. working_directory=None,
  1237. ).once()
  1238. module.restore_data_source_dump(
  1239. hook_config,
  1240. {},
  1241. data_source=hook_config[0],
  1242. dry_run=False,
  1243. extract_process=extract_process,
  1244. connection_params={
  1245. 'hostname': None,
  1246. 'port': None,
  1247. 'username': None,
  1248. 'password': None,
  1249. },
  1250. borgmatic_runtime_directory='/run/borgmatic',
  1251. )
  1252. def test_restore_data_source_dump_runs_mysql_with_socket_path():
  1253. hook_config = [{'name': 'foo', 'socket_path': '/socket'}]
  1254. extract_process = flexmock(stdout=flexmock())
  1255. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1256. 'resolve_credential',
  1257. ).replace_with(lambda value, config: value)
  1258. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1259. 'parse_extra_options',
  1260. ).and_return((), None)
  1261. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1262. lambda option, data_source, connection_params=None, restore=False: data_source.get(option)
  1263. )
  1264. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1265. 'make_defaults_file_options'
  1266. ).with_args(
  1267. None,
  1268. None,
  1269. None,
  1270. ).and_return(())
  1271. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1272. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1273. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1274. (
  1275. 'mysql',
  1276. '--batch',
  1277. '--socket',
  1278. '/socket',
  1279. ),
  1280. processes=[extract_process],
  1281. output_log_level=logging.DEBUG,
  1282. input_file=extract_process.stdout,
  1283. environment={'USER': 'root'},
  1284. working_directory=None,
  1285. ).once()
  1286. module.restore_data_source_dump(
  1287. hook_config,
  1288. {},
  1289. data_source=hook_config[0],
  1290. dry_run=False,
  1291. extract_process=extract_process,
  1292. connection_params={
  1293. 'hostname': None,
  1294. 'port': None,
  1295. 'username': None,
  1296. 'password': None,
  1297. },
  1298. borgmatic_runtime_directory='/run/borgmatic',
  1299. )
  1300. def test_restore_data_source_dump_runs_mysql_with_tls():
  1301. hook_config = [{'name': 'foo', 'tls': True}]
  1302. extract_process = flexmock(stdout=flexmock())
  1303. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1304. 'resolve_credential',
  1305. ).replace_with(lambda value, config: value)
  1306. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1307. 'parse_extra_options',
  1308. ).and_return((), None)
  1309. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1310. lambda option, data_source, connection_params=None, restore=False: data_source.get(option)
  1311. )
  1312. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1313. 'make_defaults_file_options',
  1314. ).with_args(None, None, None).and_return(())
  1315. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1316. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1317. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1318. (
  1319. 'mysql',
  1320. '--batch',
  1321. '--ssl',
  1322. ),
  1323. processes=[extract_process],
  1324. output_log_level=logging.DEBUG,
  1325. input_file=extract_process.stdout,
  1326. environment={'USER': 'root'},
  1327. working_directory=None,
  1328. ).once()
  1329. module.restore_data_source_dump(
  1330. hook_config,
  1331. {},
  1332. data_source=hook_config[0],
  1333. dry_run=False,
  1334. extract_process=extract_process,
  1335. connection_params={
  1336. 'hostname': None,
  1337. 'port': None,
  1338. 'username': None,
  1339. 'password': None,
  1340. },
  1341. borgmatic_runtime_directory='/run/borgmatic',
  1342. )
  1343. def test_restore_data_source_dump_runs_mysql_without_tls():
  1344. hook_config = [{'name': 'foo', 'tls': False}]
  1345. extract_process = flexmock(stdout=flexmock())
  1346. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1347. 'resolve_credential',
  1348. ).replace_with(lambda value, config: value)
  1349. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1350. lambda option, data_source, connection_params=None, restore=False: data_source.get(option)
  1351. )
  1352. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1353. 'parse_extra_options',
  1354. ).and_return((), None)
  1355. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1356. 'make_defaults_file_options',
  1357. ).with_args(None, None, None).and_return(())
  1358. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1359. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1360. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1361. (
  1362. 'mysql',
  1363. '--batch',
  1364. '--skip-ssl',
  1365. ),
  1366. processes=[extract_process],
  1367. output_log_level=logging.DEBUG,
  1368. input_file=extract_process.stdout,
  1369. environment={'USER': 'root'},
  1370. working_directory=None,
  1371. ).once()
  1372. module.restore_data_source_dump(
  1373. hook_config,
  1374. {},
  1375. data_source=hook_config[0],
  1376. dry_run=False,
  1377. extract_process=extract_process,
  1378. connection_params={
  1379. 'hostname': None,
  1380. 'port': None,
  1381. 'username': None,
  1382. 'password': None,
  1383. },
  1384. borgmatic_runtime_directory='/run/borgmatic',
  1385. )
  1386. def test_restore_data_source_dump_runs_mysql_with_username_and_password():
  1387. hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
  1388. extract_process = flexmock(stdout=flexmock())
  1389. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1390. 'resolve_credential',
  1391. ).replace_with(lambda value, config: value)
  1392. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1393. 'parse_extra_options',
  1394. ).and_return((), None)
  1395. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1396. lambda option, data_source, connection_params=None, restore=False: data_source.get(option)
  1397. )
  1398. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1399. 'make_defaults_file_options',
  1400. ).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
  1401. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1402. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1403. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1404. ('mysql', '--defaults-extra-file=/dev/fd/99', '--batch'),
  1405. processes=[extract_process],
  1406. output_log_level=logging.DEBUG,
  1407. input_file=extract_process.stdout,
  1408. environment={'USER': 'root'},
  1409. working_directory=None,
  1410. ).once()
  1411. module.restore_data_source_dump(
  1412. hook_config,
  1413. {},
  1414. data_source=hook_config[0],
  1415. dry_run=False,
  1416. extract_process=extract_process,
  1417. connection_params={
  1418. 'hostname': None,
  1419. 'port': None,
  1420. 'username': None,
  1421. 'password': None,
  1422. },
  1423. borgmatic_runtime_directory='/run/borgmatic',
  1424. )
  1425. def test_restore_data_source_with_environment_password_transport_skips_defaults_file_and_passes_user_flag():
  1426. hook_config = [
  1427. {
  1428. 'name': 'foo',
  1429. 'username': 'root',
  1430. 'password': 'trustsome1',
  1431. 'password_transport': 'environment',
  1432. },
  1433. ]
  1434. extract_process = flexmock(stdout=flexmock())
  1435. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1436. 'resolve_credential',
  1437. ).replace_with(lambda value, config: value)
  1438. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1439. 'parse_extra_options',
  1440. ).and_return((), None)
  1441. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1442. lambda option, data_source, connection_params=None, restore=False: data_source.get(option)
  1443. )
  1444. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1445. 'make_defaults_file_options',
  1446. ).never()
  1447. flexmock(module.os).should_receive('environ').and_return(
  1448. {'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
  1449. )
  1450. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1451. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1452. ('mysql', '--batch', '--user', 'root'),
  1453. processes=[extract_process],
  1454. output_log_level=logging.DEBUG,
  1455. input_file=extract_process.stdout,
  1456. environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
  1457. working_directory=None,
  1458. ).once()
  1459. module.restore_data_source_dump(
  1460. hook_config,
  1461. {},
  1462. data_source=hook_config[0],
  1463. dry_run=False,
  1464. extract_process=extract_process,
  1465. connection_params={
  1466. 'hostname': None,
  1467. 'port': None,
  1468. 'username': None,
  1469. 'password': None,
  1470. },
  1471. borgmatic_runtime_directory='/run/borgmatic',
  1472. )
  1473. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  1474. hook_config = [
  1475. {
  1476. 'name': 'foo',
  1477. 'username': 'root',
  1478. 'password': 'trustsome1',
  1479. 'restore_hostname': 'restorehost',
  1480. 'restore_port': 'restoreport',
  1481. 'restore_username': 'restoreusername',
  1482. 'restore_password': 'restorepassword',
  1483. },
  1484. ]
  1485. extract_process = flexmock(stdout=flexmock())
  1486. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1487. 'resolve_credential',
  1488. ).replace_with(lambda value, config: value)
  1489. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1490. 'parse_extra_options',
  1491. ).and_return((), None)
  1492. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1493. lambda option, data_source, connection_params=None, restore=False: (
  1494. connection_params or {}
  1495. ).get(option)
  1496. )
  1497. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1498. 'make_defaults_file_options',
  1499. ).with_args('cliusername', 'clipassword', None).and_return(
  1500. ('--defaults-extra-file=/dev/fd/99',),
  1501. )
  1502. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1503. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1504. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1505. (
  1506. 'mysql',
  1507. '--defaults-extra-file=/dev/fd/99',
  1508. '--batch',
  1509. '--host',
  1510. 'clihost',
  1511. '--port',
  1512. 'cliport',
  1513. '--protocol',
  1514. 'tcp',
  1515. ),
  1516. processes=[extract_process],
  1517. output_log_level=logging.DEBUG,
  1518. input_file=extract_process.stdout,
  1519. environment={'USER': 'root'},
  1520. working_directory=None,
  1521. ).once()
  1522. module.restore_data_source_dump(
  1523. hook_config,
  1524. {},
  1525. data_source={'name': 'foo'},
  1526. dry_run=False,
  1527. extract_process=extract_process,
  1528. connection_params={
  1529. 'hostname': 'clihost',
  1530. 'port': 'cliport',
  1531. 'username': 'cliusername',
  1532. 'password': 'clipassword',
  1533. },
  1534. borgmatic_runtime_directory='/run/borgmatic',
  1535. )
  1536. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  1537. hook_config = [
  1538. {
  1539. 'name': 'foo',
  1540. 'username': 'root',
  1541. 'password': 'trustsome1',
  1542. 'hostname': 'dbhost',
  1543. 'port': 'dbport',
  1544. 'tls': True,
  1545. 'restore_username': 'restoreuser',
  1546. 'restore_password': 'restorepass',
  1547. 'restore_hostname': 'restorehost',
  1548. 'restore_port': 'restoreport',
  1549. 'restore_tls': False,
  1550. },
  1551. ]
  1552. extract_process = flexmock(stdout=flexmock())
  1553. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1554. 'resolve_credential',
  1555. ).replace_with(lambda value, config: value)
  1556. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1557. 'parse_extra_options',
  1558. ).and_return((), None)
  1559. flexmock(module.database_config).should_receive('resolve_database_option').replace_with(
  1560. lambda option, data_source, connection_params=None, restore=False: data_source.get(
  1561. f'restore_{option}'
  1562. )
  1563. )
  1564. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1565. 'make_defaults_file_options',
  1566. ).with_args('restoreuser', 'restorepass', None).and_return(
  1567. ('--defaults-extra-file=/dev/fd/99',),
  1568. )
  1569. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1570. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  1571. flexmock(module).should_receive('execute_command_with_processes').with_args(
  1572. (
  1573. 'mysql',
  1574. '--defaults-extra-file=/dev/fd/99',
  1575. '--batch',
  1576. '--host',
  1577. 'restorehost',
  1578. '--port',
  1579. 'restoreport',
  1580. '--protocol',
  1581. 'tcp',
  1582. '--skip-ssl',
  1583. ),
  1584. processes=[extract_process],
  1585. output_log_level=logging.DEBUG,
  1586. input_file=extract_process.stdout,
  1587. environment={'USER': 'root'},
  1588. working_directory=None,
  1589. ).once()
  1590. module.restore_data_source_dump(
  1591. hook_config,
  1592. {},
  1593. data_source=hook_config[0],
  1594. dry_run=False,
  1595. extract_process=extract_process,
  1596. connection_params={
  1597. 'hostname': None,
  1598. 'port': None,
  1599. 'username': None,
  1600. 'password': None,
  1601. },
  1602. borgmatic_runtime_directory='/run/borgmatic',
  1603. )
  1604. def test_restore_data_source_dump_with_dry_run_skips_restore():
  1605. hook_config = [{'name': 'foo'}]
  1606. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  1607. 'resolve_credential',
  1608. ).replace_with(lambda value, config: value)
  1609. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1610. 'parse_extra_options',
  1611. ).and_return((), None)
  1612. flexmock(module.database_config).should_receive('resolve_database_option').and_return(None)
  1613. flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
  1614. 'make_defaults_file_options',
  1615. ).with_args(None, None, None).and_return(())
  1616. flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
  1617. flexmock(module).should_receive('execute_command_with_processes').never()
  1618. module.restore_data_source_dump(
  1619. hook_config,
  1620. {},
  1621. data_source={'name': 'foo'},
  1622. dry_run=True,
  1623. extract_process=flexmock(),
  1624. connection_params={
  1625. 'hostname': None,
  1626. 'port': None,
  1627. 'username': None,
  1628. 'password': None,
  1629. },
  1630. borgmatic_runtime_directory='/run/borgmatic',
  1631. )