fix(release): publish monthly backup marker after successful copy
This commit is contained in:
@@ -153,5 +153,87 @@ class BackupUploadTests(unittest.TestCase):
|
||||
self.assertNotIn('Current recovery set copied', result.stdout)
|
||||
|
||||
|
||||
class BackupRetentionTests(unittest.TestCase):
|
||||
def retain(self, fail_operation='', *, previous_snapshot=False):
|
||||
source = (ROOT / 'deploy/deploy.sh').read_text(encoding='utf-8')
|
||||
start = source.index('apply_backup_retention() {')
|
||||
function = source[start:source.index('\n}\n', start) + 3]
|
||||
gate_start = source.index('if ! apply_backup_retention || ! upload_current_backup_set; then')
|
||||
gate = source[gate_start:source.index('\nfi\n', gate_start) + 4]
|
||||
fixtures = {'jobtracker-db-synthetic.sql.gz': b'database fixture',
|
||||
'jobtracker-files-synthetic.tar.gz': b'files fixture'}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
folder = Path(temporary)
|
||||
for name, contents in fixtures.items():
|
||||
(folder / name).write_bytes(contents)
|
||||
monthly = folder / 'monthly' / '2099-01'
|
||||
if previous_snapshot:
|
||||
monthly.mkdir(parents=True)
|
||||
(monthly / 'jobtracker-db-earlier.sql.gz').write_bytes(b'earlier fixture')
|
||||
(monthly / '.complete').write_bytes(b'earlier\n')
|
||||
script = '\n'.join([
|
||||
'set -euo pipefail',
|
||||
'BACKUP_DIR=' + shlex.quote(folder.as_posix()),
|
||||
'BACKUP_STAMP=synthetic',
|
||||
'FAIL_OPERATION=' + shlex.quote(fail_operation),
|
||||
'date() { echo 2099-01; }',
|
||||
'upload_current_backup_set() { echo "UPLOAD WOULD START"; }',
|
||||
'find() { if [[ "$*" == *-delete* ]]; then echo "PRUNING WOULD START"; fi; }',
|
||||
'rm() { echo "CLEANUP REQUEST $1 $2"; }',
|
||||
'cp() {',
|
||||
' if [ "$FAIL_OPERATION" = cp ]; then',
|
||||
' printf partial > "${@: -1}/partial"; return 23;',
|
||||
' fi;',
|
||||
' command cp "$@";',
|
||||
'}',
|
||||
'mv() { if [ "$FAIL_OPERATION" = mv ]; then return 24; fi; command mv "$@"; }',
|
||||
function,
|
||||
gate,
|
||||
'echo "DEPLOY WOULD CONTINUE"',
|
||||
])
|
||||
result = subprocess.run([BASH, '--noprofile', '--norc'], input=script,
|
||||
text=True, encoding='utf-8', capture_output=True,
|
||||
cwd=ROOT, timeout=10,
|
||||
env={key: value for key, value in os.environ.items()
|
||||
if key.upper() in {'PATH', 'SYSTEMROOT', 'WINDIR', 'TEMP', 'TMP'}})
|
||||
artifacts = {file.name: file.read_bytes() for file in monthly.iterdir()} if monthly.exists() else {}
|
||||
return result, artifacts
|
||||
|
||||
def test_completed_monthly_snapshot_contains_both_artifacts_and_stamp(self):
|
||||
result, artifacts = self.retain()
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
self.assertEqual({'jobtracker-db-synthetic.sql.gz': b'database fixture',
|
||||
'jobtracker-files-synthetic.tar.gz': b'files fixture',
|
||||
'.complete': b'synthetic\n'}, artifacts)
|
||||
self.assertIn('PRUNING WOULD START', result.stdout)
|
||||
self.assertIn('UPLOAD WOULD START', result.stdout)
|
||||
self.assertIn('DEPLOY WOULD CONTINUE', result.stdout)
|
||||
|
||||
def test_partial_monthly_copy_cannot_mark_snapshot_complete_or_proceed(self):
|
||||
result, artifacts = self.retain('cp')
|
||||
self.assertEqual(1, result.returncode, result.stdout + result.stderr)
|
||||
self.assertEqual({'partial': b'partial'}, artifacts)
|
||||
self.assertIn('Could not retain monthly recovery files', result.stdout)
|
||||
self.assertNotIn('PRUNING WOULD START', result.stdout)
|
||||
self.assertNotIn('UPLOAD WOULD START', result.stdout)
|
||||
self.assertNotIn('DEPLOY WOULD CONTINUE', result.stdout)
|
||||
|
||||
def test_marker_publish_failure_does_not_expose_complete_marker_or_proceed(self):
|
||||
result, artifacts = self.retain('mv')
|
||||
self.assertEqual(1, result.returncode, result.stdout + result.stderr)
|
||||
self.assertNotIn('.complete', artifacts)
|
||||
self.assertIn('CLEANUP REQUEST -f --', result.stdout)
|
||||
self.assertIn('Could not publish monthly recovery marker', result.stdout)
|
||||
self.assertNotIn('PRUNING WOULD START', result.stdout)
|
||||
self.assertNotIn('UPLOAD WOULD START', result.stdout)
|
||||
self.assertNotIn('DEPLOY WOULD CONTINUE', result.stdout)
|
||||
|
||||
def test_preserves_the_existing_completed_monthly_snapshot(self):
|
||||
result, artifacts = self.retain('cp', previous_snapshot=True)
|
||||
self.assertEqual(0, result.returncode, result.stdout + result.stderr)
|
||||
self.assertEqual({'jobtracker-db-earlier.sql.gz': b'earlier fixture', '.complete': b'earlier\n'}, artifacts)
|
||||
self.assertIn('DEPLOY WOULD CONTINUE', result.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user