test_database.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import json
  2. import os
  3. import shutil
  4. import subprocess
  5. import sys
  6. import tempfile
  7. import pytest
  8. def write_configuration(
  9. source_directory,
  10. config_path,
  11. repository_path,
  12. borgmatic_source_directory,
  13. postgresql_dump_format='custom',
  14. mongodb_dump_format='archive',
  15. ):
  16. '''
  17. Write out borgmatic configuration into a file at the config path. Set the options so as to work
  18. for testing. This includes injecting the given repository path, borgmatic source directory for
  19. storing database dumps, dump format (for PostgreSQL), and encryption passphrase.
  20. '''
  21. config = f'''
  22. location:
  23. source_directories:
  24. - {source_directory}
  25. repositories:
  26. - {repository_path}
  27. borgmatic_source_directory: {borgmatic_source_directory}
  28. storage:
  29. encryption_passphrase: "test"
  30. hooks:
  31. postgresql_databases:
  32. - name: test
  33. hostname: postgresql
  34. username: postgres
  35. password: test
  36. format: {postgresql_dump_format}
  37. - name: all
  38. hostname: postgresql
  39. username: postgres
  40. password: test
  41. - name: all
  42. format: custom
  43. hostname: postgresql
  44. username: postgres
  45. password: test
  46. mysql_databases:
  47. - name: test
  48. hostname: mysql
  49. username: root
  50. password: test
  51. - name: all
  52. hostname: mysql
  53. username: root
  54. password: test
  55. - name: all
  56. format: sql
  57. hostname: mysql
  58. username: root
  59. password: test
  60. mongodb_databases:
  61. - name: test
  62. hostname: mongodb
  63. username: root
  64. password: test
  65. authentication_database: admin
  66. format: {mongodb_dump_format}
  67. - name: all
  68. hostname: mongodb
  69. username: root
  70. password: test
  71. sqlite_databases:
  72. - name: sqlite_test
  73. path: /tmp/sqlite_test.db
  74. '''
  75. with open(config_path, 'w') as config_file:
  76. config_file.write(config)
  77. def test_database_dump_and_restore():
  78. # Create a Borg repository.
  79. temporary_directory = tempfile.mkdtemp()
  80. repository_path = os.path.join(temporary_directory, 'test.borg')
  81. borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
  82. # Write out a special file to ensure that it gets properly excluded and Borg doesn't hang on it.
  83. os.mkfifo(os.path.join(temporary_directory, 'special_file'))
  84. original_working_directory = os.getcwd()
  85. try:
  86. config_path = os.path.join(temporary_directory, 'test.yaml')
  87. write_configuration(
  88. temporary_directory, config_path, repository_path, borgmatic_source_directory
  89. )
  90. subprocess.check_call(
  91. ['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
  92. )
  93. # Run borgmatic to generate a backup archive including a database dump.
  94. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  95. # Get the created archive name.
  96. output = subprocess.check_output(
  97. ['borgmatic', '--config', config_path, 'list', '--json']
  98. ).decode(sys.stdout.encoding)
  99. parsed_output = json.loads(output)
  100. assert len(parsed_output) == 1
  101. assert len(parsed_output[0]['archives']) == 1
  102. archive_name = parsed_output[0]['archives'][0]['archive']
  103. # Restore the database from the archive.
  104. subprocess.check_call(
  105. ['borgmatic', '--config', config_path, 'restore', '--archive', archive_name]
  106. )
  107. finally:
  108. os.chdir(original_working_directory)
  109. shutil.rmtree(temporary_directory)
  110. def test_database_dump_and_restore_with_directory_format():
  111. # Create a Borg repository.
  112. temporary_directory = tempfile.mkdtemp()
  113. repository_path = os.path.join(temporary_directory, 'test.borg')
  114. borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
  115. original_working_directory = os.getcwd()
  116. try:
  117. config_path = os.path.join(temporary_directory, 'test.yaml')
  118. write_configuration(
  119. temporary_directory,
  120. config_path,
  121. repository_path,
  122. borgmatic_source_directory,
  123. postgresql_dump_format='directory',
  124. mongodb_dump_format='directory',
  125. )
  126. subprocess.check_call(
  127. ['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
  128. )
  129. # Run borgmatic to generate a backup archive including a database dump.
  130. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  131. # Restore the database from the archive.
  132. subprocess.check_call(
  133. ['borgmatic', '--config', config_path, 'restore', '--archive', 'latest']
  134. )
  135. finally:
  136. os.chdir(original_working_directory)
  137. shutil.rmtree(temporary_directory)
  138. def test_database_dump_with_error_causes_borgmatic_to_exit():
  139. # Create a Borg repository.
  140. temporary_directory = tempfile.mkdtemp()
  141. repository_path = os.path.join(temporary_directory, 'test.borg')
  142. borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
  143. original_working_directory = os.getcwd()
  144. try:
  145. config_path = os.path.join(temporary_directory, 'test.yaml')
  146. write_configuration(
  147. temporary_directory, config_path, repository_path, borgmatic_source_directory
  148. )
  149. subprocess.check_call(
  150. ['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
  151. )
  152. # Run borgmatic with a config override such that the database dump fails.
  153. with pytest.raises(subprocess.CalledProcessError):
  154. subprocess.check_call(
  155. [
  156. 'borgmatic',
  157. 'create',
  158. '--config',
  159. config_path,
  160. '-v',
  161. '2',
  162. '--override',
  163. "hooks.postgresql_databases=[{'name': 'nope'}]", # noqa: FS003
  164. ]
  165. )
  166. finally:
  167. os.chdir(original_working_directory)
  168. shutil.rmtree(temporary_directory)