Skip to content

Commit

Permalink
repozo: recover ZODB in a temporary file then rename it
Browse files Browse the repository at this point in the history
So if recovering a ZODB fails for any reason, it doesn't leave behind
a partial file which may be confused with the recovered file (as it
bears same name).
  • Loading branch information
Sebatyne committed Mar 6, 2019
1 parent 949a420 commit b94910c
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
5.5.2 (unreleased)
==================

- Make repozo's recover mode atomic by recovering the backup in a
temporary file which is then moved to the expected output file.
- Add a new option to repozo in recover mode which allows to verify
backups integrity on the fly.

Expand Down
22 changes: 18 additions & 4 deletions src/ZODB/scripts/repozo.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
--with-verification
Verify on the fly the backup files on recovering. This option runs
the same checks as when repozo is run in -V/--verify mode, and
allows to verify and recover a backup in one single step.
allows to verify and recover a backup in one single step. If a sanity
check fails, the partially recovered ZODB will be left in place.
Options for -V/--verify:
-Q / --quick
Expand Down Expand Up @@ -665,8 +666,14 @@ def do_recover(options):
log('Recovering file to stdout')
outfp = sys.stdout
else:
# Delete old ZODB before recovering backup as size of
# old ZODB + full partial file may be superior to free disk space
if os.path.exists(options.output):
log('Deleting old %s', options.output)
os.unlink(options.output)
log('Recovering file to %s', options.output)
outfp = open(options.output, 'wb')
temporary_output_file = options.output + '.part'
outfp = open(temporary_output_file, 'wb')
if options.withverify:
datfile = os.path.splitext(repofiles[0])[0] + '.dat'
with open(datfile) as fp:
Expand Down Expand Up @@ -699,8 +706,6 @@ def do_recover(options):
else:
reposz, reposum = concat(repofiles, outfp)
log('Recovered %s bytes, md5: %s', reposz, reposum)
if outfp != sys.stdout:
outfp.close()

if options.output is not None:
last_base = os.path.splitext(repofiles[-1])[0]
Expand All @@ -712,6 +717,15 @@ def do_recover(options):
else:
log('No index file to restore: %s', source_index)

if outfp != sys.stdout:
outfp.close()
try:
os.rename(temporary_output_file, options.output)
except OSError:
log("ZODB has been fully recovered as %s, but it cannot be renamed into : %s",
temporary_output_file, options.output)
raise


def do_verify(options):
# Verify the sizes and checksums of all files mentioned in the .dat file
Expand Down
3 changes: 3 additions & 0 deletions src/ZODB/scripts/tests/test_repozo.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,7 @@ def test_w_incr_backup_with_verify_all_is_fine(self):
'/backup/2010-05-14-02-03-04.fs 0 3 e1faffb3e614e6c2fba74296962386b7\n'
'/backup/2010-05-14-04-05-06.deltafs 3 7 f50881ced34c7d9e6bce100bf33dec60\n')
self._callFUT(options)
self.assertFalse(os.path.exists(output + '.part'))
self.assertEqual(_read_file(output), b'AAABBBB')

def test_w_incr_backup_with_verify_sum_inconsistent(self):
Expand All @@ -953,6 +954,7 @@ def test_w_incr_backup_with_verify_sum_inconsistent(self):
'/backup/2010-05-14-02-03-04.fs 0 3 e1faffb3e614e6c2fba74296962386b7\n'
'/backup/2010-05-14-04-05-06.deltafs 3 7 f50881ced34c7d9e6bce100bf33dec61\n')
self.assertRaises(VerificationFail, self._callFUT, options)
self.assertTrue(os.path.exists(output + '.part'))

def test_w_incr_backup_with_verify_size_inconsistent(self):
import tempfile
Expand All @@ -969,6 +971,7 @@ def test_w_incr_backup_with_verify_size_inconsistent(self):
'/backup/2010-05-14-02-03-04.fs 0 3 e1faffb3e614e6c2fba74296962386b7\n'
'/backup/2010-05-14-04-05-06.deltafs 3 8 f50881ced34c7d9e6bce100bf33dec60\n')
self.assertRaises(VerificationFail, self._callFUT, options)
self.assertTrue(os.path.exists(output + '.part'))


class Test_do_verify(OptionsTestBase, unittest.TestCase):
Expand Down

0 comments on commit b94910c

Please sign in to comment.