test_database.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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. mysql_databases:
  41. - name: test
  42. hostname: mysql
  43. username: root
  44. password: test
  45. - name: all
  46. hostname: mysql
  47. username: root
  48. password: test
  49. mongodb_databases:
  50. - name: test
  51. hostname: mongodb
  52. username: root
  53. password: test
  54. authentication_database: admin
  55. - name: all
  56. hostname: mongodb
  57. username: root
  58. password: test
  59. '''
  60. with open(config_path, 'w') as config_file:
  61. config_file.write(config)
  62. def test_database_dump_and_restore():
  63. # Create a Borg repository.
  64. temporary_directory = tempfile.mkdtemp()
  65. repository_path = os.path.join(temporary_directory, 'test.borg')
  66. borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
  67. # Write out a special file to ensure that it gets properly excluded and Borg doesn't hang on it.
  68. os.mkfifo(os.path.join(temporary_directory, 'special_file'))
  69. original_working_directory = os.getcwd()
  70. try:
  71. config_path = os.path.join(temporary_directory, 'test.yaml')
  72. write_configuration(
  73. temporary_directory, config_path, repository_path, borgmatic_source_directory
  74. )
  75. subprocess.check_call(
  76. ['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
  77. )
  78. # Run borgmatic to generate a backup archive including a database dump.
  79. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  80. # Get the created archive name.
  81. output = subprocess.check_output(
  82. ['borgmatic', '--config', config_path, 'list', '--json']
  83. ).decode(sys.stdout.encoding)
  84. parsed_output = json.loads(output)
  85. assert len(parsed_output) == 1
  86. assert len(parsed_output[0]['archives']) == 1
  87. archive_name = parsed_output[0]['archives'][0]['archive']
  88. # Restore the database from the archive.
  89. subprocess.check_call(
  90. ['borgmatic', '--config', config_path, 'restore', '--archive', archive_name]
  91. )
  92. finally:
  93. os.chdir(original_working_directory)
  94. shutil.rmtree(temporary_directory)
  95. def test_database_dump_and_restore_with_directory_format():
  96. # Create a Borg repository.
  97. temporary_directory = tempfile.mkdtemp()
  98. repository_path = os.path.join(temporary_directory, 'test.borg')
  99. borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
  100. original_working_directory = os.getcwd()
  101. try:
  102. config_path = os.path.join(temporary_directory, 'test.yaml')
  103. write_configuration(
  104. temporary_directory,
  105. config_path,
  106. repository_path,
  107. borgmatic_source_directory,
  108. postgresql_dump_format='directory',
  109. )
  110. subprocess.check_call(
  111. ['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
  112. )
  113. # Run borgmatic to generate a backup archive including a database dump.
  114. subprocess.check_call(['borgmatic', 'create', '--config', config_path, '-v', '2'])
  115. # Restore the database from the archive.
  116. subprocess.check_call(
  117. ['borgmatic', '--config', config_path, 'restore', '--archive', 'latest']
  118. )
  119. finally:
  120. os.chdir(original_working_directory)
  121. shutil.rmtree(temporary_directory)
  122. def test_database_dump_with_error_causes_borgmatic_to_exit():
  123. # Create a Borg repository.
  124. temporary_directory = tempfile.mkdtemp()
  125. repository_path = os.path.join(temporary_directory, 'test.borg')
  126. borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
  127. original_working_directory = os.getcwd()
  128. try:
  129. config_path = os.path.join(temporary_directory, 'test.yaml')
  130. write_configuration(
  131. temporary_directory, config_path, repository_path, borgmatic_source_directory
  132. )
  133. subprocess.check_call(
  134. ['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
  135. )
  136. # Run borgmatic with a config override such that the database dump fails.
  137. with pytest.raises(subprocess.CalledProcessError):
  138. subprocess.check_call(
  139. [
  140. 'borgmatic',
  141. 'create',
  142. '--config',
  143. config_path,
  144. '-v',
  145. '2',
  146. '--override',
  147. "hooks.postgresql_databases=[{'name': 'nope'}]",
  148. ]
  149. )
  150. finally:
  151. os.chdir(original_working_directory)
  152. shutil.rmtree(temporary_directory)