upgrader.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import os
  2. import shutil
  3. import tempfile
  4. import pytest
  5. try:
  6. import attic.repository
  7. import attic.key
  8. import attic.helpers
  9. except ImportError:
  10. attic = None
  11. from ..upgrader import AtticRepositoryUpgrader, AtticKeyfileKey
  12. from ..helpers import get_keys_dir
  13. from ..key import KeyfileKey
  14. from ..repository import Repository, MAGIC
  15. def repo_valid(path):
  16. """
  17. utility function to check if borg can open a repository
  18. :param path: the path to the repository
  19. :returns: if borg can check the repository
  20. """
  21. repository = Repository(str(path), create=False)
  22. # can't check raises() because check() handles the error
  23. state = repository.check()
  24. repository.close()
  25. return state
  26. def key_valid(path):
  27. """
  28. check that the new keyfile is alright
  29. :param path: the path to the key file
  30. :returns: if the file starts with the borg magic string
  31. """
  32. keyfile = os.path.join(get_keys_dir(),
  33. os.path.basename(path))
  34. with open(keyfile, 'r') as f:
  35. return f.read().startswith(KeyfileKey.FILE_ID)
  36. @pytest.fixture()
  37. def attic_repo(tmpdir):
  38. """
  39. create an attic repo with some stuff in it
  40. :param tmpdir: path to the repository to be created
  41. :returns: a attic.repository.Repository object
  42. """
  43. attic_repo = attic.repository.Repository(str(tmpdir), create=True)
  44. # throw some stuff in that repo, copied from `RepositoryTestCase.test1`
  45. for x in range(100):
  46. attic_repo.put(('%-32d' % x).encode('ascii'), b'SOMEDATA')
  47. attic_repo.commit()
  48. attic_repo.close()
  49. return attic_repo
  50. @pytest.fixture(params=[True, False])
  51. def inplace(request):
  52. return request.param
  53. @pytest.mark.skipif(attic is None, reason='cannot find an attic install')
  54. def test_convert_segments(tmpdir, attic_repo, inplace):
  55. """test segment conversion
  56. this will load the given attic repository, list all the segments
  57. then convert them one at a time. we need to close the repo before
  58. conversion otherwise we have errors from borg
  59. :param tmpdir: a temporary directory to run the test in (builtin
  60. fixture)
  61. :param attic_repo: a populated attic repository (fixture)
  62. """
  63. # check should fail because of magic number
  64. assert not repo_valid(tmpdir)
  65. repo = AtticRepositoryUpgrader(str(tmpdir), create=False)
  66. segments = [filename for i, filename in repo.io.segment_iterator()]
  67. repo.close()
  68. repo.convert_segments(segments, dryrun=False, inplace=inplace)
  69. repo.convert_cache(dryrun=False)
  70. assert repo_valid(tmpdir)
  71. class MockArgs:
  72. """
  73. mock attic location
  74. this is used to simulate a key location with a properly loaded
  75. repository object to create a key file
  76. """
  77. def __init__(self, path):
  78. self.repository = attic.helpers.Location(path)
  79. @pytest.fixture()
  80. def attic_key_file(attic_repo, tmpdir):
  81. """
  82. create an attic key file from the given repo, in the keys
  83. subdirectory of the given tmpdir
  84. :param attic_repo: an attic.repository.Repository object (fixture
  85. define above)
  86. :param tmpdir: a temporary directory (a builtin fixture)
  87. :returns: the KeyfileKey object as returned by
  88. attic.key.KeyfileKey.create()
  89. """
  90. keys_dir = str(tmpdir.mkdir('keys'))
  91. # we use the repo dir for the created keyfile, because we do
  92. # not want to clutter existing keyfiles
  93. os.environ['ATTIC_KEYS_DIR'] = keys_dir
  94. # we use the same directory for the converted files, which
  95. # will clutter the previously created one, which we don't care
  96. # about anyways. in real runs, the original key will be retained.
  97. os.environ['BORG_KEYS_DIR'] = keys_dir
  98. os.environ['ATTIC_PASSPHRASE'] = 'test'
  99. return attic.key.KeyfileKey.create(attic_repo,
  100. MockArgs(keys_dir))
  101. @pytest.mark.skipif(attic is None, reason='cannot find an attic install')
  102. def test_keys(tmpdir, attic_repo, attic_key_file):
  103. """test key conversion
  104. test that we can convert the given key to a properly formatted
  105. borg key. assumes that the ATTIC_KEYS_DIR and BORG_KEYS_DIR have
  106. been properly populated by the attic_key_file fixture.
  107. :param tmpdir: a temporary directory (a builtin fixture)
  108. :param attic_repo: an attic.repository.Repository object (fixture
  109. define above)
  110. :param attic_key_file: an attic.key.KeyfileKey (fixture created above)
  111. """
  112. repository = AtticRepositoryUpgrader(str(tmpdir), create=False)
  113. keyfile = AtticKeyfileKey.find_key_file(repository)
  114. AtticRepositoryUpgrader.convert_keyfiles(keyfile, dryrun=False)
  115. assert key_valid(attic_key_file.path)
  116. @pytest.mark.skipif(attic is None, reason='cannot find an attic install')
  117. def test_convert_all(tmpdir, attic_repo, attic_key_file, inplace):
  118. """test all conversion steps
  119. this runs everything. mostly redundant test, since everything is
  120. done above. yet we expect a NotImplementedError because we do not
  121. convert caches yet.
  122. :param tmpdir: a temporary directory (a builtin fixture)
  123. :param attic_repo: an attic.repository.Repository object (fixture
  124. define above)
  125. :param attic_key_file: an attic.key.KeyfileKey (fixture created above)
  126. """
  127. # check should fail because of magic number
  128. assert not repo_valid(tmpdir)
  129. def first_inode(path):
  130. return os.stat(os.path.join(path, 'data', '0', '0')).st_ino
  131. orig_inode = first_inode(attic_repo.path)
  132. repo = AtticRepositoryUpgrader(str(tmpdir), create=False)
  133. backup = repo.upgrade(dryrun=False, inplace=inplace)
  134. if inplace:
  135. assert backup is None
  136. assert first_inode(repo.path) == orig_inode
  137. else:
  138. assert backup
  139. assert first_inode(repo.path) != first_inode(backup)
  140. assert key_valid(attic_key_file.path)
  141. assert repo_valid(tmpdir)
  142. def test_hardlink(tmpdir, inplace):
  143. """test that we handle hard links properly
  144. that is, if we are in "inplace" mode, hardlinks should *not*
  145. change (ie. we write the file directly, so not the whole file, and
  146. not re-create the file).
  147. if we are *not* in inplace mode, then the inode should change, as
  148. we are supposed to leave the original inode alone."""
  149. a = str(tmpdir.join('a'))
  150. with open(a, 'wb') as tmp:
  151. tmp.write(b'aXXX')
  152. b = str(tmpdir.join('b'))
  153. os.link(a, b)
  154. AtticRepositoryUpgrader.header_replace(b, b'a', b'b', inplace=inplace)
  155. if not inplace:
  156. assert os.stat(a).st_ino != os.stat(b).st_ino
  157. else:
  158. assert os.stat(a).st_ino == os.stat(b).st_ino
  159. with open(b, 'rb') as tmp:
  160. assert tmp.read() == b'bXXX'