test_database.py 5.9 KB

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