|
|
|
|
@ -306,50 +306,31 @@ def _publish_no_clobber(source, destination):
|
|
|
|
|
|
|
|
|
|
def _publish_pair(root, pairs): |
|
|
|
|
publications = [] |
|
|
|
|
guards = [] |
|
|
|
|
try: |
|
|
|
|
for source, destination in pairs: |
|
|
|
|
_publish_no_clobber(source, destination) |
|
|
|
|
publications.append((destination, source)) |
|
|
|
|
_fsync_directory(root) |
|
|
|
|
|
|
|
|
|
for source, _destination in pairs: |
|
|
|
|
guard = Path(f"{source}.guard") |
|
|
|
|
os.link(source, guard) |
|
|
|
|
guards.append(guard) |
|
|
|
|
for source, _destination in pairs: |
|
|
|
|
source.unlink() |
|
|
|
|
for guard in guards: |
|
|
|
|
guard.unlink() |
|
|
|
|
_fsync_directory(root) |
|
|
|
|
except Exception: |
|
|
|
|
ownership_sources = [*guards, *(source for source, _destination in pairs if source.exists())] |
|
|
|
|
_remove_owned_publications(publications, ownership_sources) |
|
|
|
|
_remove_owned_publications(publications) |
|
|
|
|
for source, _destination in pairs: |
|
|
|
|
_unlink_private(source) |
|
|
|
|
for guard in guards: |
|
|
|
|
_unlink_private(guard) |
|
|
|
|
raise BackupError("publication_failed") from None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _remove_owned_publications(publications, owned_sources): |
|
|
|
|
for destination, original_source in reversed(publications): |
|
|
|
|
sources = [source for source in owned_sources if source.exists()] |
|
|
|
|
if original_source.exists(): |
|
|
|
|
sources.append(original_source) |
|
|
|
|
try: |
|
|
|
|
if destination.exists() and any( |
|
|
|
|
os.path.samefile(destination, source) for source in sources |
|
|
|
|
): |
|
|
|
|
destination.unlink() |
|
|
|
|
except OSError: |
|
|
|
|
pass |
|
|
|
|
cleanup_failed = False |
|
|
|
|
for source, _destination in pairs: |
|
|
|
|
if not _unlink_private(source): |
|
|
|
|
cleanup_failed = True |
|
|
|
|
if cleanup_failed: |
|
|
|
|
raise BackupError("cleanup_failed") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _remove_our_publications(publications): |
|
|
|
|
def _remove_owned_publications(publications): |
|
|
|
|
for destination, source in reversed(publications): |
|
|
|
|
try: |
|
|
|
|
if destination.exists() and os.path.samefile(destination, source): |
|
|
|
|
if source.exists() and destination.exists() and os.path.samefile( |
|
|
|
|
destination, source |
|
|
|
|
): |
|
|
|
|
destination.unlink() |
|
|
|
|
except OSError: |
|
|
|
|
pass |
|
|
|
|
@ -412,7 +393,11 @@ def _load_manifest(path):
|
|
|
|
|
raise BackupError("invalid_manifest") from None |
|
|
|
|
if not isinstance(manifest, dict) or set(manifest) != _MANIFEST_KEYS: |
|
|
|
|
raise BackupError("invalid_manifest") |
|
|
|
|
if manifest["format"] != BACKUP_FORMAT or manifest["version"] != BACKUP_VERSION: |
|
|
|
|
if ( |
|
|
|
|
manifest["format"] != BACKUP_FORMAT |
|
|
|
|
or type(manifest["version"]) is not int |
|
|
|
|
or manifest["version"] != BACKUP_VERSION |
|
|
|
|
): |
|
|
|
|
raise BackupError("invalid_manifest") |
|
|
|
|
if ( |
|
|
|
|
not isinstance(manifest["application_version"], str) |
|
|
|
|
@ -570,11 +555,16 @@ def run_synthetic_restore_verification():
|
|
|
|
|
or _same_database(restore_target, live_target) |
|
|
|
|
): |
|
|
|
|
raise BackupError("live_target") |
|
|
|
|
if _read_snapshot(source_target).row_counts: |
|
|
|
|
raise BackupError("source_not_empty") |
|
|
|
|
restore_before = _read_snapshot(restore_target) |
|
|
|
|
if restore_before.migrations or restore_before.row_counts: |
|
|
|
|
raise BackupError("target_not_empty") |
|
|
|
|
try: |
|
|
|
|
if _read_snapshot(source_target).row_counts: |
|
|
|
|
raise BackupError("source_not_empty") |
|
|
|
|
restore_before = _read_snapshot(restore_target) |
|
|
|
|
if restore_before.migrations or restore_before.row_counts: |
|
|
|
|
raise BackupError("target_not_empty") |
|
|
|
|
except BackupError: |
|
|
|
|
raise |
|
|
|
|
except Exception: |
|
|
|
|
raise BackupError("integration_verification_failed") from None |
|
|
|
|
|
|
|
|
|
default_connection = connections["default"] |
|
|
|
|
original_database = dict(default_connection.settings_dict) |
|
|
|
|
@ -684,6 +674,8 @@ def _clear_disposable_database(target):
|
|
|
|
|
autocommit=True, |
|
|
|
|
) as database: |
|
|
|
|
with database.cursor() as cursor: |
|
|
|
|
cursor.execute("SET lock_timeout = '5s'") |
|
|
|
|
cursor.execute("SET statement_timeout = '15s'") |
|
|
|
|
cursor.execute("DROP SCHEMA public CASCADE") |
|
|
|
|
cursor.execute("CREATE SCHEMA public") |
|
|
|
|
except Exception: |
|
|
|
|
|