Repository navigation
Conversation
`extenddb backup migrate-legacy <arn>` or `--all` (exactly one) copies in-catalog backups into the configured store, each under its own ARN, backup id, name and creation time, and replaces each by its catalog row, AVAILABLE and unowned as a row `backup sync` inserts. `<arn>` is matched as written and rebuilt in this server's region; `--all` takes every in-catalog backup of every account that is not DELETED, oldest first, and goes on past one it does not migrate. The command needs the configured store healthy, opens the catalog as `serve` does (on SQLite under the serve lock, so the server is stopped for it), takes the maintenance claim on every backend for its whole run and waits as `purge` does, so every backup and restore job of the catalog answers LimitExceededException until it ends. Per backup, in this order: an id with no creation instant is refused before anything is read (that is every backup a 0.1.x release took: a store prefix carries only the 14-digit shape); then a table name the store-prefix rules refuse; a catalog row under the ARN (the configured store's: an earlier run's, finished; another store's: refused); CREATING and DELETING are skipped; a backup the backend cannot read (MongoDB's with no item collection), a name the manifest refuses, what the legacy restore refuses (a vector index, a multi-part key) and a SQLite backup taken before catalog 0.0.4 are refused. Then the copy: objects under the prefix with no row are an earlier run's and are deleted through the deletion fence first; the fence runs again before the first put; the items stream through the backup job's writer, each checked with the item validator under this instance's limits; the manifest is written last with `snapshot.kind` `legacy_in_catalog`, no marker, `unordered`, and `source_backend` and `source_extenddb_version` naming this binary. The prefix is read back through `verify_prefix`, sync's check, and the item count is checked against the in-catalog backup's twice. A refused item, a short read or a copy that does not read back deletes the copy through the fence and keeps the in-catalog backup. A lost claim, a signal, an unhealthy store, a failed listing or catalog call, and a replacement whose answer did not come stop the run at once with exit 1; a signal is acted on between backups and between data files. Exit 0 only when every backup reached is in the store. Each migration is logged under `extenddb::audit`. The manifest's source table is the table the legacy restore creates (`source_table_from_legacy`, pinned by an exhaustive property against `restore_input` and `restore_input_from_manifest`), with the recorded table class, TTL and stream and the SSE description of the recorded specification, so a `KMSMasterKeyId` is not carried over. The vector state is `[]` on PostgreSQL and SQLite and `null` on MongoDB and Cassandra. The replacement is one step per backend, `LegacyBackupReader::replace_with_index_row`, in place of an insert followed by the reader's delete, which let a DeleteBackup between the two answer DELETED for a backup that then came back, and which cannot work on Cassandra, where both rows share one key. PostgreSQL: one transaction (the row `FOR UPDATE`, the CREATING-target check, the insert, the items and definition deleted, DELETED). SQLite: the same under the write lock and `BEGIN IMMEDIATE`. MongoDB: one session transaction that inserts the row and marks the document DELETING with `migrated_to_store`, then the collection's drop and DELETED; a second call finishes one that stopped. Cassandra: the row converted in place by a lightweight transaction `IF backup_status = 'AVAILABLE' AND store_kind = null AND active_restores = null`, then its list and status entries and the item buckets' deletion; an unknown outcome is settled by a serial read, and a second call finishes a conversion that stopped after the row. The answers are `Replaced`, `AlreadyReplaced`, `NotAvailable` and `InUse`. Three frozen deletes change so a DeleteBackup orders wholly before or after a replacement: PostgreSQL's reads the status under its row lock, MongoDB's reads a `migrated_to_store` document as not found, Cassandra's claim carries `store_kind = null`. `LegacyBackupReader` also gains `items(account, arn)`, a frozen copy of each restore's read as a stream (PostgreSQL a REPEATABLE READ catalog transaction held for the copy, SQLite keyset batches each re-checking AVAILABLE, MongoDB a cursor with `batch_size` and a frozen decode, Cassandra each bucket paged 8 rows at a time), and `list_all()`. The core format gains `SnapshotDescriptor::KIND_LEGACY_IN_CATALOG`; no format version or fixture byte changes. The writer runs under a `ClaimWatch`, the job's heartbeat or the maintenance claim, whose loss now cancels a token; the job's tests run unchanged. `backup sync` skips a prefix whose ARN an in-catalog backup holds, under either spelling, naming `migrate-legacy <arn>`, and ListBackups lists an ARN that both a catalog row and the in-catalog reader hold once, with the row's summary. Tests: per-backend cells for the stream (batch and bucket boundaries, a pinned snapshot, a delete ending it), `list_all` across accounts and the replacement's four answers, a racing DeleteBackup and Cassandra's lost answer and stop; 16 engine cells on `Fake` and `MemStore` (each refusal, the fence, the counts, a copy that does not read back, every rerun rule, a stop, the sync skip, the dedupe); wire cells on SQLite, PostgreSQL and MongoDB (two in-catalog backups migrated, described, listed, restored and deleted under their ARNs with the in-catalog rows gone; `--all` past a #618 backup and a 13-digit one, both kept; the claim against a second command, and the serve lock); SIGKILL at the three kill points on SQLite and PostgreSQL, each rerun finishing. 45 mutants of the new tests, all killed. Peak RSS 55 to 56 MiB for 1,000 and for 100,000 items on PostgreSQL, MongoDB and Cassandra debug builds, and 122 MiB on SQLite, its page cache. Docs: the backup manual's legacy section (the command, its cost, what it refuses and the way out, what a migrated backup reports, a stopped run, Cassandra's recurring step, memory); the upgrade manual's store, 0.1.x and rollback paragraphs; the admin guide's paragraph and sample output; the differences doc's two rows. Design revision item 37, with sections 3, 4(c), 4(f), 4(g) and 7, the PR 10 paragraph and transitions, the PR 14 lines (rows that are not DELETED) and section 10's row edited with it. Not here: removing the in-catalog reader (#749); a 13-digit backup carried into the store format; a Cassandra wire leg (direct tests only). Co-authored-by: Lee <leeroyhannigan@yahoo.ie> Signed-off-by: Scott Robinson <robinnsc@amazon.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backup/restore series (design: #396): #384 restore fidelity (merged) · #393 store · #394 format · #395 SnapshotEngine · #666 catalog migration framework (merged) · PR 6 the catalog index · PR 7 configuration, the store identity file and
backup purge· #700 the CreateBackup job · #740 the restore job, the cross-backend matrix andbackup remove-target· #825backup sync,--prune-missing,--adopt-store· #855 the CI filters, the open measurement cells and the backup and restore manual · #880backup sync --move-store,--forget-store,--forget-backup· this PR:backup migrate-legacy· next: #748 export and import through the store, #749 the in-catalog reader's removal.Closes #821 (part of #624).
Stacked on #880 (
feat/backup-store-maintenance, head6793fae2), itself on #855 (feat/backup-docs,88d445da) and #825; opened as a draft againstfeat/backup-store-maintenance. One commit. No migration, no schema change, no new dependency;Cargo.lockis unchanged. This is the third of the three PRs the design's PR 10 ships as (revision item 34). Every binary below is a debug build of this tree (6793fae2plus this diff).What
Format (
crates/core).SnapshotDescriptor::KIND_LEGACY_IN_CATALOG=legacy_in_catalog, whose doc says the marker is empty and thatsource_backendandsource_extenddb_versionname the binary that wrote the copy (the in-catalog creator's version was never recorded). No format version change and no fixture byte change; a unit test parses and round-trips a manifest of that kind.is_valid_backup_nameis exported, so the command refuses a name the manifest would refuse before it writes anything.The definition (
crates/storage/src/backup_definition.rs).source_table_from_legacy(source, vector_state, region, account), besiderestore_input: the table the legacy restore creates, by one rule on every backend. It runsensure_restorable, refusesLegacyVectorState::Unknown(a SQLite backup with no definition), and otherwise takesrestore_inputwith no overrides (a backup with no definition keeps its keys, its key attribute definitions and the 5/5 fallback) plus the definition's table class, TTL and stream andSseDescription::from_specificationof its SSE specification. The vector state is[]on PostgreSQL and SQLite with no names (PostgreSQL with no definition and a NULL or zero snapshot too) andnullon MongoDB and Cassandra. Tests: an exhaustive property over every combination of key schema, billing, recorded throughput, GSI shapes, LSI, four SSE specifications, on-demand throughput, class, TTL and stream and no definition, times two vector states and four override sets, thatrestore_input_from_manifest(source_table_from_legacy(s))restores whats.restore_inputrestores; the one difference, aKMSMasterKeyIdnot carried, has a test of its own.Storage trait (
crates/storage/src/backup_catalog.rs).LegacyBackupReadergainsitems(account, arn) -> LegacyBackupItems(the name, the recorded source, the vector state and a'staticitem stream),list_all() -> Vec<LegacyBackupKey>(the in-catalog backups of every account that are not DELETED, by creation time then ARN) andreplace_with_index_row(row) -> Replaced { Replaced, AlreadyReplaced, NotAvailable(String), InUse(String) }; an index row of the id under another ARN or store isAlreadyExistswith nothing written. The trait doc names the replacement as its second write beside the restore's registration.PostgreSQL, SQLite, MongoDB, Cassandra. Each stream is a frozen copy of the backend's restore read: PostgreSQL a
REPEATABLE READ READ ONLYcatalog transaction whose first statement re-checks AVAILABLE, owned by a spawned task sending through a one-slot channel; SQLite the restore's keyset batches (500 rows or 4 MiB), each in its own read transaction re-checking AVAILABLE; MongoDB a cursor on_backup_<backup_id>withbatch_size(DEFAULT_FETCH_BATCH)andlegacy_item_from_document, a copy ofdocument_to_item, andUnsupportedfor a document with nobackup_id; Cassandra the buckets in order, each read in pages ofLEGACY_ITEMS_PAGE= 8 rows byitem_index. The replacement: PostgreSQL one transaction (thebackupsrowFOR UPDATE, the CREATING-target check, the insert statementinsert_backup_rowused, nowinsert_unowned_row, the items and definition deleted, DELETED); SQLite the same under the write lock andBEGIN IMMEDIATE; MongoDBstart_replacement, one session transaction ($inc lock_seqwhile AVAILABLE, the CREATING-target check, the upsert, DELETING withmigrated_to_store: true), thenfinish_legacy_delete's drop and DELETED, a DELETING document with an index row of the same ARN and store being finished asAlreadyReplaced; Cassandrareplace_legacy_row, the row converted in place by one lightweight transactionIF backup_status = 'AVAILABLE' AND store_kind = null AND active_restores = nullaftercreating_restore_target, thenwrite_index_entries(sent once more on a failure) andcleanup_backup_payload, an unknown outcome settled byread_index_row_serial,NotMatchedread again up to three attempts, and a converted row without its entries finished by the next call. Three frozen deletes change, each to order a DeleteBackup wholly before or after a replacement: PostgreSQL's reads the status under itsFOR UPDATElock and answers not found for anything but AVAILABLE; MongoDB's reads a DELETING document markedmigrated_to_storeas not found, indeleteand in the lock's miss; Cassandra's DELETING claim carriesAND store_kind = null. SQLite's is unchanged: the command holds the serve lock. Module docs name the stream and the replacement.Fakeimplements the three with fault knobs; Cassandra'stest-hooksgainfail_convert_writesandstop_convert_after_row.Engine (
crates/engine/src/backup/migrate_legacy.rs).migrate(storage, &Migration, Selection::{Arn, All}, on_backup) -> MigrateReport, written againstBackupCatalog + LegacyBackupReaderand the configured store. Per backup, in this order: the id (no instant: refused before anything is read, the text naming 0.1.x and the way out); the table name by the store-prefix rules and the partition; a catalog row under the ARN (the configured store's:finish, the replacement with that row; another store's: refused), before the status, so MongoDB's DELETINGmigrated_to_storedocument is finished, not skipped; the status (CREATING and DELETING skipped, named); the open (Unsupportedrefused); the name;ensure_restorable("the legacy restore refuses it too"); the unknown vector state. Then the copy: a listing of the prefix, whose objects with no row are an earlier run's and are deleted through the fence; the fence before the first put;writer::write_data_fileswith the item validator under the instance's limits; the count check; the three documents and the manifest (unordered,legacy_in_catalog, no marker);verify_prefixand the second count check;replace_with_index_rowwith the row built as sync builds it (the manifest's name, size, item total andsource_table, the id's instant ascreated_at, unowned). A refusal or failure after the first put deletes the copy through the fence.Outcome::{Migrated, Finished, InStore, Refused, Skipped, Failed};MigrateReport::stoppedfor a lost claim, a signal, an unhealthy or fenced store, a failed listing, a catalog call and a replacement whose call fails. The writer (job/writer.rs) runs under aClaimWatch(confirms_the_claim, the loss token, the reason):Pipeline<'a, W: ?Sized = HeartbeatHandle>, so the job'sPipeline<'_>is unchanged, andimpl ClaimWatch for HeldClaim, whose timer now cancels alost_signaltoken when it records the loss.is_item_refusalsharesrefusal_reason's words through one constant.put_documentis generic over the watch. Underbackup-test-hooksthe writer'safter_data_fileand the command'sbefore_manifestandafter_manifestkill points pause it as they pause a job.Sync and ListBackups.
sync.rs:SkipReason::InCatalog { backup_arn, status }("an in-catalog backup (STATUS) holds this ARN; run extenddb backup migrate-legacy "), not transient, fromLegacyBackupReader::describeunder both spellings before the row read under them;sync,finish_group,adopt_prefixandstartup_syncnow needLegacyBackupReader, which every caller's storage has.backup.rs: ListBackups drops an in-catalog summary whose ARN a listed catalog row has.App (
crates/app/src/cmd_backup_migrate.rs, a child ofcmd_backup).BackupCommand::MigrateLegacy { arn, all }with a requiredArgGroup(usagemigrate-legacy <ARN|--all>, exit 2 for neither or both),Mode::Write, the registration nameextenddb backup migrate-legacy. The command needsIdentity::Known, takes the claim on every backend, startsHeldClaim, runswait_for_jobsover the configured identity (on SQLite afterresolve_dead_owners, with a zero window), runs the migration, releases the claim, prints one line per backup (with its notes) and the summary line, logsextenddb::auditper migration and per finish, and exits 0 only when every backup reached is in the store.lib.rs's help and the module doc name it.Harness.
tests/test_backup_job.py: two new cells, one extended (below); helpers_catalog_rows,_in_catalog_rows,_copy_in_catalog_backup,_typed_canonical,_migrate.tests/test_backup_store_config.py: the serve-lock cell runsmigrate-legacybeside a SQLite server.Docs and texts. The backup manual's Legacy backups section: "Migrating legacy backups into the store" in place of "This release has no command that converts a legacy backup" (the two forms; what is kept; the check and the one-step replacement per backend; the cost: the claim, every backup and restore job answering
LimitExceededException, the wait, SQLite stopped, PostgreSQL's heldREPEATABLE READtransaction and the vacuum it holds back;<arn>to migrate in pieces; sample output; the outcomes and exit; the refusals, every 0.1.x backup first; the restore-and-back-up way out, kept from the earlier text; what a migrated backup reports; a stopped run and sync's skip; Cassandra's interrupted conversion and its recurring--all; memory); the vector-index sentence corrected; the Strategy section's Cassandra paragraph and the catalog-lost paragraph point to it. The upgrade manual: the store paragraph, the 0.1.x paragraph (refused on their ids) and the rollback paragraph (a migrated backup is in-catalog again in a restored pre-upgrade catalog). The admin guide: the command's paragraph and sample output, the registration names. The differences doc: "Where backups live" and the maintenance-claim row. Design: revision item 37, with sections 3, 4(c), 4(f), 4(g) and 7, the PR 10 paragraph and transitions, the PR 14 lines and section 10's upgrade-manual row edited with it.Why
Design section 3, 4(f), 4(g) and the PR 10 section's
migrate-legacyparagraph, and PR 14's precondition: before the in-catalog reader is removed, operators need a way to move the backups it holds into the store without losing their ARNs. Until now the only way was a restore and a new backup, which gives a new ARN, a new creation time and, for a table that still exists, an interim table. Cassandra keeps writing in-catalog backups until PR 14 settles its backup path, so there the command is a recurring step.Choices to weigh
delete". DeleteBackup does not read the maintenance claim. In the design's order a DeleteBackup that landed between the AVAILABLE check and the insert was answered DELETED for a backup that then came back as the migrated row, and a restore target in CREATING made thedeleterefuse after the insert, leaving both rows and two ListBackups entries. On Cassandra the order cannot work at all: the legacy row and the index row share thebackups_by_arnkey, and the frozendeleteremoves the list rows the migrated row uses and leaves a DELETED row that every later insert answersfalsefor. The cost is one write method on the frozen trait (as the restore's registration already is) and three small changes to frozen deletes, each pinned by a test and a mutant; PR 14 removes all of it with the trait.kind = "disabled", and on Cassandra. The texts say so plainly, and the refusal names the way out. The pre-0.0.4 SQLite refusal is in practice a subset of this one; it stays as a defensive check, reachable through a hand-seeded row, with its own SQLite cell.restore_inputwith no overrides plus the features DescribeBackup reports, so a migrated backup restores the table the in-catalog backup restored. The design refused only the pre-0.0.4 SQLite case; a backup with no definition on PostgreSQL, MongoDB and Cassandra migrates with its keys and the 5/5 fallback, as its legacy restore creates it. What the legacy restore refuses (vector indexes, a multi-part key) is refused here too, so the migration lifts no refusal. The SSE specification'sKMSMasterKeyIddoes not survive the manifest's SSE description; the manual names it.LimitExceededExceptionfor the run, and on SQLite the server is stopped.<arn>migrates in pieces.backup syncskips such a prefix, naming the command, so a sync between the crash and the rerun cannot give the backup two rows. Cassandra already skipped it through the insert'sfalse.table_size_bytesis the sum of the copied items' sizes, as the job records it, soBackupSizeBytesandTableSizeBytescan differ from what the in-catalog backup reported; the name, ARN, id, creation times and item count do not.--allgoes on past a refused, skipped or failed backup and exits 1 at the end with the counts; a global condition (claim lost, signal, store unhealthy or fenced, listing or catalog failure, a replacement whose answer did not come) stops the run at once.list_alldoes not list, so--alldoes not finish it;migrate-legacy <arn>does (its "already in the store" path runs the replacement once more). Until then the backup is served by Describe, List and Restore, andbackup list, which reads status entries, does not show it. The manual says so.Testing done
Measured on this tree, debug builds, a 16-core Xeon Platinum 8124M host (30 GiB, Linux 5.10), the services local.
cargo fmt --all -- --checkclean;cargo +1.97.0 clippy --locked --workspace --all-targets -- -D warningsclean, and with--features extenddb-storage-cassandra/test-hooks,extenddb-storage-mongodb/test-hooksand with--features extenddb-engine/backup-test-hooks;cargo +1.88.0 check --locked --workspace --all-targetsclean.a_manifest_of_the_legacy_in_catalog_kind_parses_and_round_trips;--lib backup_format181 passed, 1 ignored. Storage:--lib237 passed, with the foursource_table_from_legacycells (the exhaustive property; the described features and the legacy identity; the KMS key id; the refusals and the unknown vector state).legacy_migration6 cells (the stream across both batch bounds with 1,201 small and three 3 MiB items; a stream ending at a DeleteBackup after its batch;list_allacross accounts; the replacement's insert and removal in one step, an adopted copy finished, another store refused, in-use and unavailable refused with nothing written); PostgreSQLlegacy_migration7 cells (the stream from its pinned snapshot while another transaction deletes every item row; the same replacement cells; a legacy delete queued behind a replacement answers not found); MongoDB 6 live cells (2,500 items past a cursor batch; a drop ending a stream; the replacement; a replacement stopped before its drop finished by the next call, DeleteBackup leaving it; the in-use and unavailable refusals); Cassandra 5 direct cells on a node of their own (200 items over four buckets in pages of 8;list_all; the conversion in place, the legacy describe, list and delete no longer seeing it; in-use and unavailable; a lost answer either way, a stop after the row finished by the next call, and a DeleteBackup paused between its read and its claim answering not found), run with the 4 existing cells that reach the changed delete,restore_sourceand insert.--lib: 511 passed, 16 of them new (backup::tests::migrate_legacy): the copy under the ARN and its row; each refusal before the copy writing nothing; a refused item deleting the copy and--allgoing on; a short or failing read; the fence on a root another catalog adopted; a copy that does not read back; a copy an earlier run left; a backup whose row is in the store; a replacement that fails; deleted or in use at the replacement; CREATING and DELETING skipped; a signal and a lost claim; the claim's loss token; the ARN's spellings; sync's skip under both spellings; the ListBackups dedupe. The job, writer, sync and purge cells pass unchanged. App--lib: 58 passed.tests/test_backup_store_config.py tests/test_backup_job.pyon the SQLite build 42 passed, 12 skipped (-n 4), on the PostgreSQL build 38 passed, 16 skipped (-n 2); the two new cells passed on the MongoDB build. New:test_migrate_legacy_carries_in_catalog_backups_into_the_store_under_their_arns(303 items with a GSI and NS, B, nested M and L, BOOL and NULL values; two in-catalog backups;<arn>spelt in another region,--all, reruns; the manifests; the in-catalog rows DELETED with no item or definition row; ListBackups once each, DescribeBackup as before, the restore's items and GSI, DeleteBackup; beside the server on PostgreSQL and MongoDB) andtest_migrate_legacy_refuses_a_backup_with_no_instant_or_an_item_618_let_in_and_keeps_it(--allpast a BatchWriteItem, TransactWriteItems and other operations store N values that fail number validation as written #618 backup and, on SQLite and PostgreSQL, a 13-digit copy of a backup's rows; the three refused items logged; both kept and AVAILABLE; the 13-digit one restored and backed up again into the store). Extended:test_a_second_maintenance_command_fails_at_once(PostgreSQL:migrate-legacy --allrefused while--prune-missingholds the claim, then holding it while it waits for a live job,backup purge --confirmrefused beside it, a SIGTERM stopping it before any change) and the serve-lock cell (exit 6 beside a SQLite server).11544ecd):initwithkind = "disabled", two CreateBackups, the store configured, the command run (refused with exit 6 beside a SQLite server, nothing written), boto3 afterwards: 33 and 30 checks, none failed.backup-test-hooks, a 40,000-item table in two data files: after the first data file, before the manifest and after it, the in-catalog backup was whole with no row; after the manifest a plainbackup syncskipped the prefix naming the command and wrote no row; each rerun deleted the copy, migrated 40,000 items and left the in-catalog row DELETED; a restore of the last held every item. On PostgreSQL the killed command's advisory lock ended with its session, so the rerun took the claim at once. Cassandra's stop after the conversion is the direct cell above (no binary kill point and no Cassandra wire leg).ensure_restorableignored,nullwritten as[], the class or the SSE description dropped; a batch or bucket or page boundary skipped, a stream truncated or read outside its snapshot, the per-batch AVAILABLE check dropped; the in-use check skipped, deletion before the insert, Cassandra'sstore_kindleft null or the buckets deleted before the conversion, a lost answer read as applied; each frozen delete's new check dropped; DELETED rows or index rows listed), 10 in the engine (the id refusal, each count check and both, the fence before the first put,--allstopping at a refusal, the leftover kept, the re-read skipped, the sync skip, the dedupe, the loss token), 3 on the wire (a run with refusals exiting 0, the<ARN>/--allgroup optional, the wait skipped): 45, all killed.ru_maxrssof the command:SQLite's excess is the command's connections' page cache (
cache_size, 32 MB each) over a 172 MB database file. With the two GSIs, the Cassandra node's asynchronous GSI queue over the 100,000 writes left enough tombstones ingsi_pendingthat it timed out before the measurement, so that run has no GSI; the read path does not read them.Acceptance lines of #821 (the design's PR 10 acceptance list) and where each is tested:
migrate-legacyon a backup the PR 0 binary created produces a prefix Describe, List and Restore serve under the original ARN, and the legacy rows are gonean_in_catalog_backup_is_copied_under_its_arn_and_replaced_by_its_row; the backends' replacement cells (no non-DELETED row, no items, definition, collection or buckets); wiretest_migrate_legacy_carries_in_catalog_backups_...(SQLite, PostgreSQL, MongoDB; the in-catalog rows read on SQLite and PostgreSQL)migrate-legacyrefuses a backup with a 13-digit id, one holding a value #618 let in and a pre-0.0.4 SQLite backup, keeps their legacy rows and names the reasoneach_refusal_before_the_copy_...anda_refused_item_deletes_the_copy_...; storagea_copy_refuses_what_the_legacy_restore_refuses_and_an_unknown_vector_stateand SQLite's stream cell (the definition row removed:Unknown); wiretest_migrate_legacy_refuses_...(the 13-digit id on SQLite and PostgreSQL, #618 on all three)test_a_second_maintenance_command_fails_at_once(PostgreSQL, both ways)a_read_short_...,a_copy_that_does_not_read_back_...migrate_legacy_on_a_root_another_catalog_adopted_writes_nothingcompleted_atset)sync_skips_a_prefix_...; SIGKILL after the manifest, thenbackup sync(SQLite, PostgreSQL)Cassandra has no wire leg (no pytest fixture runs it); its share is the direct cells above and the memory run.
Not in this PR
Breaking changes
None.
backup syncskips a prefix whose ARN an in-catalog backup holds, where it could adopt it before; no release wrote such a prefix. ListBackups lists an ARN both readers hold once. On PostgreSQL, MongoDB and Cassandra an in-catalog DeleteBackup that read the backup before a replacement committed answersBackupNotFoundException, where it could have answeredDELETEDfor a backup that lives on in the store.DCO
Signed-off-by: Scott Robinson robinnsc@amazon.com