Skip to content

feat: migrate an instance's storage between swift and s3 - #4855

Open
Crash-- wants to merge 19 commits into
masterfrom
feat/per-instance-s3-migration
Open

feat: migrate an instance's storage between swift and s3#4855
Crash-- wants to merge 19 commits into
masterfrom
feat/per-instance-s3-migration

Conversation

@Crash--

@Crash-- Crash-- commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add a per-instance fs_scheme override so an instance can use a different storage backend than the stack default; an empty value keeps the global fs.url scheme.
  • Add an optional fs.migration_target config that initializes an S3 connection alongside the default one while the global scheme stays Swift.
  • Add index-free WriteContentAt and StatContentAt on the S3 and Swift v3 backends, and OpenAvatar on the Avatarer interface.
  • Add model/instance/storagemigration: copy an instance's files, file versions and avatar between backends at the object-storage level without touching the CouchDB index, verify the target, then flip the instance's backend inside a read-only window.
  • Support --dry-run, an instant --flag-only rollback to the retained source, and a deferred purge-only reclaim of the previous backend.
  • Add POST /instances/:domain/migrate-storage, the admin client method, and the cozy-stack instances migrate-storage command.
  • Document fs.migration_target and the migration workflow in docs/config.md and docs/s3.md.

@shepilov
shepilov force-pushed the feat/s3-vfs-backend branch from 52655f5 to 4c7b7a9 Compare July 30, 2026 15:13
@shepilov
shepilov force-pushed the feat/s3-vfs-backend branch from 4c7b7a9 to 562bd20 Compare September 3, 2026 12:07
@shepilov
shepilov force-pushed the feat/per-instance-s3-migration branch 3 times, most recently from 099e255 to e0ee637 Compare September 8, 2026 14:07
@shepilov
shepilov force-pushed the feat/s3-vfs-backend branch from 562bd20 to dea07f9 Compare September 8, 2026 15:15
Base automatically changed from feat/s3-vfs-backend to master September 8, 2026 15:33
WriteContentAt puts bytes at the object key for a given (docID,
internalID) pair without creating or touching any CouchDB document.
It is the S3-side primitive used by Swift-to-S3 storage migration,
which moves object bytes while preserving the shared CouchDB index.
The Swift->S3 migration needs to copy a user-uploaded avatar as-is
(it is authoritative, not regenerable). OpenAvatar exposes a generic
read-side (content reader + content-type) across all three vfs
backends, returning os.ErrNotExist when no avatar is stored so the
migration can treat "no avatar" as a skip.
…ollback

Mirrors Task 3's vfss3.WriteContentAt so the Swift v3 backend can also act
as a copy target for storagemigration.CopyContent, enabling full S3->Swift
re-migration (rollback). Also corrects the storagemigration package doc
comment, which previously oversold "Swift<->S3" before Swift-as-target was
actually implemented.
Add Verify, which re-enumerates the same files/versions/avatar that
CopyContent copies and confirms each object exists on the target with a
matching byte size, without touching CouchDB. Backed by a new
StatContentAt primitive on both the S3 and Swift v3 VFS backends, since
either can be the copy target. The orchestrator (a later task) will only
flip an instance's backend flag once Verify passes.
Add Migrate, which ties CopyContent/Verify together into the full
migration flow: guard the target scheme and instance state, block the
instance, copy and verify the content, then flip FsScheme only once
Verify passes (DryRun and a failed Verify always leave FsScheme
unchanged). FlagOnly supports rollback onto an already-populated
backend and requires Force, since writes since cutover would be lost.
PurgeSource best-effort deletes the source objects after a successful
flip (S3-source only for now; swift-source purge reports "not
implemented" rather than silently no-op'ing).

Also add config.HasS3Client/HasSwiftConnection, non-panicking
readiness checks needed to guard Migrate against a target backend that
was never initialized.
FlagOnly previously flipped FsScheme after buildTarget merely ensured
the target bucket/container existed, with no check that it actually
held the source's content (buildTarget's EnsureBucket/ContainerCreate
create an empty target if none exists). Add sourceReport, which
computes the expected file/version counts and avatar presence from
the source's CouchDB docs, and run Verify against it before flipping;
a failed Verify now returns without touching FsScheme.

purgeSource also returned "not implemented" for a swift source after
the flip had already persisted, making a successful migration look
like a failure. Reuse the same per-instance container Delete() that
instance destroy/reset already use to actually purge the swift
source.

Adds coverage for the flag-only flip (populated vs. empty target) and
an end-to-end swift-source purge test using the in-memory swifttest
server.
Documents the fs.migration_target config key that lets an instance be
migrated to a new storage backend (e.g. S3) without changing the global
fs.url, and adds a step-by-step Swift-to-S3 migration guide covering the
migrate-storage command, rollback, and source purge. Also regenerates the
CLI reference page for the new instances migrate-storage command.
…ag-only dry-run

Migrate rejected opts.To == the instance's current scheme even when
PurgeSource was set, making the documented deferred reclaim step
(docs/s3.md step 4, run --purge-source after the flip) and any retry
of a failed inline purge impossible. Migrate now detects this case
and runs a purge-only mode: it deletes the other backend's leftover
data for the instance without copying, verifying, or flipping
anything, and without blocking the instance.

Also fix --flag-only --force --dry-run mutating state: the FlagOnly
branch now checks opts.DryRun after Verify and returns without
flipping FsScheme when set.

Finally, guard model/stack Start against fs.migration_target pointing
at S3 while fs.url is already an s3 scheme, which previously silently
overwrote the global S3 client with the migration endpoint.
@shepilov
shepilov force-pushed the feat/per-instance-s3-migration branch from e0ee637 to 555c2da Compare September 8, 2026 15:35
@shepilov
shepilov force-pushed the feat/per-instance-s3-migration branch from 555c2da to 2c32a43 Compare September 8, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants