test_mysql.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import logging
  2. import pytest
  3. from flexmock import flexmock
  4. from borgmatic.hooks import mysql as module
  5. def test_database_names_to_dump_passes_through_name():
  6. extra_environment = flexmock()
  7. log_prefix = ''
  8. dry_run_label = ''
  9. names = module.database_names_to_dump(
  10. {'name': 'foo'}, extra_environment, log_prefix, dry_run_label
  11. )
  12. assert names == ('foo',)
  13. def test_database_names_to_dump_queries_mysql_for_database_names():
  14. extra_environment = flexmock()
  15. log_prefix = ''
  16. dry_run_label = ''
  17. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  18. ('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
  19. extra_environment=extra_environment,
  20. ).and_return('foo\nbar\nmysql\n').once()
  21. names = module.database_names_to_dump(
  22. {'name': 'all'}, extra_environment, log_prefix, dry_run_label
  23. )
  24. assert names == ('foo', 'bar')
  25. def test_dump_databases_dumps_each_database():
  26. databases = [{'name': 'foo'}, {'name': 'bar'}]
  27. processes = [flexmock(), flexmock()]
  28. flexmock(module).should_receive('make_dump_path').and_return('')
  29. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  30. ('bar',)
  31. )
  32. for name, process in zip(('foo', 'bar'), processes):
  33. flexmock(module).should_receive('execute_dump_command').with_args(
  34. database={'name': name},
  35. log_prefix=object,
  36. dump_path=object,
  37. database_names=(name,),
  38. extra_environment=object,
  39. dry_run=object,
  40. dry_run_label=object,
  41. ).and_return(process).once()
  42. assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == processes
  43. def test_dump_databases_dumps_with_password():
  44. database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
  45. process = flexmock()
  46. flexmock(module).should_receive('make_dump_path').and_return('')
  47. flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
  48. ('bar',)
  49. )
  50. flexmock(module).should_receive('execute_dump_command').with_args(
  51. database=database,
  52. log_prefix=object,
  53. dump_path=object,
  54. database_names=('foo',),
  55. extra_environment={'MYSQL_PWD': 'trustsome1'},
  56. dry_run=object,
  57. dry_run_label=object,
  58. ).and_return(process).once()
  59. assert module.dump_databases([database], 'test.yaml', {}, dry_run=False) == [process]
  60. def test_dump_databases_dumps_all_databases_at_once():
  61. databases = [{'name': 'all'}]
  62. process = flexmock()
  63. flexmock(module).should_receive('make_dump_path').and_return('')
  64. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  65. flexmock(module).should_receive('execute_dump_command').with_args(
  66. database={'name': 'all'},
  67. log_prefix=object,
  68. dump_path=object,
  69. database_names=('foo', 'bar'),
  70. extra_environment=object,
  71. dry_run=object,
  72. dry_run_label=object,
  73. ).and_return(process).once()
  74. assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == [process]
  75. def test_dump_databases_dumps_all_databases_separately_when_format_configured():
  76. databases = [{'name': 'all', 'format': 'sql'}]
  77. processes = [flexmock(), flexmock()]
  78. flexmock(module).should_receive('make_dump_path').and_return('')
  79. flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
  80. for name, process in zip(('foo', 'bar'), processes):
  81. flexmock(module).should_receive('execute_dump_command').with_args(
  82. database={'name': name, 'format': 'sql'},
  83. log_prefix=object,
  84. dump_path=object,
  85. database_names=(name,),
  86. extra_environment=object,
  87. dry_run=object,
  88. dry_run_label=object,
  89. ).and_return(process).once()
  90. assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == processes
  91. def test_database_names_to_dump_runs_mysql_with_list_options():
  92. database = {'name': 'all', 'list_options': '--defaults-extra-file=my.cnf'}
  93. flexmock(module).should_receive('execute_command_and_capture_output').with_args(
  94. (
  95. 'mysql',
  96. '--defaults-extra-file=my.cnf',
  97. '--skip-column-names',
  98. '--batch',
  99. '--execute',
  100. 'show schemas',
  101. ),
  102. extra_environment=None,
  103. ).and_return(('foo\nbar')).once()
  104. assert module.database_names_to_dump(database, None, 'test.yaml', '') == ('foo', 'bar')
  105. def test_execute_dump_command_runs_mysqldump():
  106. process = flexmock()
  107. flexmock(module.dump).should_receive('make_database_dump_filename').and_return('dump')
  108. flexmock(module.os.path).should_receive('exists').and_return(False)
  109. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  110. flexmock(module).should_receive('execute_command').with_args(
  111. ('mysqldump', '--add-drop-database', '--databases', 'foo', '>', 'dump',),
  112. shell=True,
  113. extra_environment=None,
  114. run_to_completion=False,
  115. ).and_return(process).once()
  116. assert (
  117. module.execute_dump_command(
  118. database={'name': 'foo'},
  119. log_prefix='log',
  120. dump_path=flexmock(),
  121. database_names=('foo',),
  122. extra_environment=None,
  123. dry_run=False,
  124. dry_run_label='',
  125. )
  126. == process
  127. )
  128. def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
  129. process = flexmock()
  130. flexmock(module.dump).should_receive('make_database_dump_filename').and_return('dump')
  131. flexmock(module.os.path).should_receive('exists').and_return(False)
  132. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  133. flexmock(module).should_receive('execute_command').with_args(
  134. (
  135. 'mysqldump',
  136. '--add-drop-database',
  137. '--host',
  138. 'database.example.org',
  139. '--port',
  140. '5433',
  141. '--protocol',
  142. 'tcp',
  143. '--databases',
  144. 'foo',
  145. '>',
  146. 'dump',
  147. ),
  148. shell=True,
  149. extra_environment=None,
  150. run_to_completion=False,
  151. ).and_return(process).once()
  152. assert (
  153. module.execute_dump_command(
  154. database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
  155. log_prefix='log',
  156. dump_path=flexmock(),
  157. database_names=('foo',),
  158. extra_environment=None,
  159. dry_run=False,
  160. dry_run_label='',
  161. )
  162. == process
  163. )
  164. def test_execute_dump_command_runs_mysqldump_with_username_and_password():
  165. process = flexmock()
  166. flexmock(module.dump).should_receive('make_database_dump_filename').and_return('dump')
  167. flexmock(module.os.path).should_receive('exists').and_return(False)
  168. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  169. flexmock(module).should_receive('execute_command').with_args(
  170. ('mysqldump', '--add-drop-database', '--user', 'root', '--databases', 'foo', '>', 'dump',),
  171. shell=True,
  172. extra_environment={'MYSQL_PWD': 'trustsome1'},
  173. run_to_completion=False,
  174. ).and_return(process).once()
  175. assert (
  176. module.execute_dump_command(
  177. database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
  178. log_prefix='log',
  179. dump_path=flexmock(),
  180. database_names=('foo',),
  181. extra_environment={'MYSQL_PWD': 'trustsome1'},
  182. dry_run=False,
  183. dry_run_label='',
  184. )
  185. == process
  186. )
  187. def test_execute_dump_command_runs_mysqldump_with_options():
  188. process = flexmock()
  189. flexmock(module.dump).should_receive('make_database_dump_filename').and_return('dump')
  190. flexmock(module.os.path).should_receive('exists').and_return(False)
  191. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  192. flexmock(module).should_receive('execute_command').with_args(
  193. ('mysqldump', '--stuff=such', '--add-drop-database', '--databases', 'foo', '>', 'dump',),
  194. shell=True,
  195. extra_environment=None,
  196. run_to_completion=False,
  197. ).and_return(process).once()
  198. assert (
  199. module.execute_dump_command(
  200. database={'name': 'foo', 'options': '--stuff=such'},
  201. log_prefix='log',
  202. dump_path=flexmock(),
  203. database_names=('foo',),
  204. extra_environment=None,
  205. dry_run=False,
  206. dry_run_label='',
  207. )
  208. == process
  209. )
  210. def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
  211. flexmock(module.dump).should_receive('make_database_dump_filename').and_return('dump')
  212. flexmock(module.os.path).should_receive('exists').and_return(True)
  213. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  214. flexmock(module).should_receive('execute_command').never()
  215. assert (
  216. module.execute_dump_command(
  217. database={'name': 'foo'},
  218. log_prefix='log',
  219. dump_path=flexmock(),
  220. database_names=('foo',),
  221. extra_environment=None,
  222. dry_run=True,
  223. dry_run_label='SO DRY',
  224. )
  225. is None
  226. )
  227. def test_execute_dump_command_with_dry_run_skips_mysqldump():
  228. flexmock(module.dump).should_receive('make_database_dump_filename').and_return('dump')
  229. flexmock(module.os.path).should_receive('exists').and_return(False)
  230. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  231. flexmock(module).should_receive('execute_command').never()
  232. assert (
  233. module.execute_dump_command(
  234. database={'name': 'foo'},
  235. log_prefix='log',
  236. dump_path=flexmock(),
  237. database_names=('foo',),
  238. extra_environment=None,
  239. dry_run=True,
  240. dry_run_label='SO DRY',
  241. )
  242. is None
  243. )
  244. def test_dump_databases_errors_for_missing_all_databases():
  245. databases = [{'name': 'all'}]
  246. process = flexmock()
  247. flexmock(module).should_receive('make_dump_path').and_return('')
  248. flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
  249. 'databases/localhost/all'
  250. )
  251. flexmock(module).should_receive('database_names_to_dump').and_return(())
  252. with pytest.raises(ValueError):
  253. assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == [process]
  254. def test_restore_database_dump_runs_mysql_to_restore():
  255. database_config = [{'name': 'foo'}]
  256. extract_process = flexmock(stdout=flexmock())
  257. flexmock(module).should_receive('execute_command_with_processes').with_args(
  258. ('mysql', '--batch'),
  259. processes=[extract_process],
  260. output_log_level=logging.DEBUG,
  261. input_file=extract_process.stdout,
  262. extra_environment=None,
  263. ).once()
  264. module.restore_database_dump(
  265. database_config, 'test.yaml', {}, dry_run=False, extract_process=extract_process
  266. )
  267. def test_restore_database_dump_errors_on_multiple_database_config():
  268. database_config = [{'name': 'foo'}, {'name': 'bar'}]
  269. flexmock(module).should_receive('execute_command_with_processes').never()
  270. flexmock(module).should_receive('execute_command').never()
  271. with pytest.raises(ValueError):
  272. module.restore_database_dump(
  273. database_config, 'test.yaml', {}, dry_run=False, extract_process=flexmock()
  274. )
  275. def test_restore_database_dump_runs_mysql_with_options():
  276. database_config = [{'name': 'foo', 'restore_options': '--harder'}]
  277. extract_process = flexmock(stdout=flexmock())
  278. flexmock(module).should_receive('execute_command_with_processes').with_args(
  279. ('mysql', '--batch', '--harder'),
  280. processes=[extract_process],
  281. output_log_level=logging.DEBUG,
  282. input_file=extract_process.stdout,
  283. extra_environment=None,
  284. ).once()
  285. module.restore_database_dump(
  286. database_config, 'test.yaml', {}, dry_run=False, extract_process=extract_process
  287. )
  288. def test_restore_database_dump_runs_mysql_with_hostname_and_port():
  289. database_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
  290. extract_process = flexmock(stdout=flexmock())
  291. flexmock(module).should_receive('execute_command_with_processes').with_args(
  292. (
  293. 'mysql',
  294. '--batch',
  295. '--host',
  296. 'database.example.org',
  297. '--port',
  298. '5433',
  299. '--protocol',
  300. 'tcp',
  301. ),
  302. processes=[extract_process],
  303. output_log_level=logging.DEBUG,
  304. input_file=extract_process.stdout,
  305. extra_environment=None,
  306. ).once()
  307. module.restore_database_dump(
  308. database_config, 'test.yaml', {}, dry_run=False, extract_process=extract_process
  309. )
  310. def test_restore_database_dump_runs_mysql_with_username_and_password():
  311. database_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
  312. extract_process = flexmock(stdout=flexmock())
  313. flexmock(module).should_receive('execute_command_with_processes').with_args(
  314. ('mysql', '--batch', '--user', 'root'),
  315. processes=[extract_process],
  316. output_log_level=logging.DEBUG,
  317. input_file=extract_process.stdout,
  318. extra_environment={'MYSQL_PWD': 'trustsome1'},
  319. ).once()
  320. module.restore_database_dump(
  321. database_config, 'test.yaml', {}, dry_run=False, extract_process=extract_process
  322. )
  323. def test_restore_database_dump_with_dry_run_skips_restore():
  324. database_config = [{'name': 'foo'}]
  325. flexmock(module).should_receive('execute_command_with_processes').never()
  326. module.restore_database_dump(
  327. database_config, 'test.yaml', {}, dry_run=True, extract_process=flexmock()
  328. )