mirror of
https://github.com/rustfs/rustfs.git
synced 2026-11-01 08:40:23 +00:00
896 lines
54 KiB
Markdown
896 lines
54 KiB
Markdown
# Dedicated Artifact Buckets
|
|
|
|
**Use this when:** changing the creation, persisted identity, or versioning of a
|
|
bucket reserved for plugin outputs.
|
|
|
|
## Identity and creation
|
|
|
|
`MakeBucketOptions::artifact_base_prefix` in
|
|
`crates/storage-api/src/bucket.rs` selects a dedicated artifact bucket at creation.
|
|
The bucket incarnation and prefix identify its output namespace. An existing
|
|
ordinary bucket cannot be converted into an artifact bucket, and an existing
|
|
artifact bucket's prefix cannot be removed or changed. Deleting and recreating a
|
|
bucket creates a different incarnation; a bucket name alone is not an identity.
|
|
|
|
The prefix follows the Runner's artifact-store encoding: at most 1024 bytes,
|
|
ASCII letters, digits, `/`, `.`, `_`, and `-`, with a trailing slash and no `.` or
|
|
`..` path segment. It is a key prefix, not a filesystem path. Runner's final
|
|
attempt prefix must also fit its own length limit.
|
|
|
|
`ECStore::handle_make_bucket` in `crates/ecstore/src/store/bucket.rs` uses the
|
|
existing bucket lifecycle, metadata transaction, and namespace locks. Before
|
|
creating the physical bucket it persists the incarnation and creation intent,
|
|
including the artifact prefix and enabled versioning. The creation is then
|
|
committed through the existing bucket creation protocol. A retry before physical
|
|
creation preserves the pending identity instead of falling back to ordinary
|
|
bucket creation. If only some disks contain a bucket volume, the existing
|
|
incomplete-generation guard requires administrator recovery; this change does
|
|
not bypass that guard to finish creation automatically.
|
|
|
|
On each local disk, volume creation and deletion take the write side of the
|
|
existing volume publication lock. Physical filesystem workers retain this
|
|
lock after their caller is cancelled, including deferred snapshot cleanup.
|
|
Their last owner invalidates volume and file-descriptor caches before releasing
|
|
the lock. Linux workers also retain the original mount descriptor; Windows
|
|
directory deletion closes its handle before releasing the owner. This local
|
|
ordering does not establish the distributed legacy-write admission boundary.
|
|
|
|
The legacy `WriteAll` and `CompareAndUpdateFile` receivers take a disk-bound
|
|
volume read owner before reading authoritative bucket metadata. Ordinary buckets
|
|
must still have a live directory on that disk; retained metadata cannot recreate
|
|
a missing volume. Their filesystem workers keep the owner through completion,
|
|
even after cancellation. Internal volumes retain on-demand directory creation.
|
|
The ordinary `WriteMetadata` receiver also prepares its object metadata mutex,
|
|
final `xl.meta` namespace mutex, and volume read lock before classification. It
|
|
reuses those exact locks for temporary-file writes and final publication; queued
|
|
bucket deletion cannot force a second read acquisition. Preparation and execution
|
|
retain finite deadlines, while cancelled physical workers keep their ownership.
|
|
Ordinary `UpdateMetadata` reuses that prepared publication for its read-modify-write
|
|
operation, preserving metadata merge/replacement and durability options. Transition
|
|
reconciliation retains its separate authorization path and is not covered by this
|
|
admission.
|
|
The HTTP file-stream receivers serialize requests for the same target before
|
|
admitting the captured disk and live volume. Classification follows admission;
|
|
the stream then fixes its target and optional staging paths under namespace
|
|
ownership. Authenticated writes verify the staged body before publication, while
|
|
legacy writes retain their direct append/truncate behavior. Physical stream
|
|
workers, publication and failure cleanup retain the same ownership, including
|
|
the HTTP target guard, after the request waiter is cancelled.
|
|
Ordinary `RenameData` similarly prepares a single-use disk-bound transaction
|
|
before classifying either volume. It acquires the destination metadata and
|
|
namespace mutexes, then distinct source and destination volume read locks in
|
|
path order. Both ordinary volume directories must still exist. Commit consumes
|
|
the sanitized metadata and the same leases, including the quota claim and
|
|
captured instance owner; staging, publication and source restoration keep that
|
|
ownership through physical completion even after cancellation. Internal staging
|
|
retains on-demand directory creation. Preparation and execution have finite
|
|
deadlines, and scanner/incarnation-bound requests retain their existing paths.
|
|
Ordinary `UndoWrite` prepares metadata admission on that same captured instance
|
|
before classification. Backup restoration, remaining-version rewrites and fresh
|
|
version rollback retain the admission through physical completion. A single-object
|
|
`UndoDelete` with both undo flags and a rollback directory also uses this admission.
|
|
Before its first mutation, it freezes the restoration paths and tries all required
|
|
extra namespace locks; contention fails before any marker or object changes.
|
|
Physical restoration workers retain the complete lock ownership after cancellation.
|
|
Optional rollback cleanup skips lock contention. Incarnation-bound deletion keeps
|
|
its separate authorization path.
|
|
Ordinary `DeleteVersion` without rollback staging uses the same admission. It
|
|
covers remaining-version rewrites, final-version removal, and forced delete
|
|
markers when metadata is missing. These operations reuse the prepared publication
|
|
instead of reacquiring namespace or volume locks. Missing metadata without a
|
|
forced marker preserves the original error and leaves optional residue cleanup
|
|
to the existing unprepared cleanup path.
|
|
Single-object staged deletion also prepares admission before classification. It
|
|
freezes the backup, marker, staging and restoration paths, then tries every required
|
|
namespace before its first write. Backup publication, reservation, marker commit
|
|
and internal failure restoration retain the same complete ownership. Internal
|
|
restoration reuses the prepared paths and locks instead of acquiring them again;
|
|
optional cleanup remains best effort.
|
|
Bulk `DeleteVersions` captures one instance-bound volume admission before
|
|
classification. Each object group tries its metadata and namespace locks, then
|
|
uses the existing group read-modify-write operation and one original backup.
|
|
Contention is reported for that group without blocking later groups. Duplicate
|
|
objects retain input order; empty groups keep their existing mutation semantics.
|
|
One execution deadline preserves completed group results and marks only unfinished
|
|
groups as timed out. Physical workers retain admission after cancellation. Empty
|
|
batches preserve their original classification and response behavior.
|
|
Ordinary and durable `RenameFile` requests bind both source and destination
|
|
volumes to the captured instance before classification. Destination cleanup,
|
|
rename, optional ancestor cleanup and required sync workers retain the same
|
|
admission until physical completion. Durable acknowledgement follows successful
|
|
completion of the destination volume's existing sync policy; a post-rename sync
|
|
failure still reports failure even if the destination has already been published.
|
|
Part preparation, publication and settlement share the destination part's
|
|
namespace admission before acquiring distinct volume read owners. Every required
|
|
extra path, including integrity indexes, is frozen before the first write.
|
|
Settlement waits for the same destination admission before inspecting the
|
|
transaction, so rollback cannot race a timed-out publisher's physical worker.
|
|
The existing transaction files and recovery decisions remain authoritative;
|
|
a timed-out settlement leaves recoverable state instead of claiming completion.
|
|
Admission failures and consumed operations retain their original operation
|
|
metrics without counting a successful write twice.
|
|
`Delete` and `DeletePaths` bind one volume admission to the captured instance
|
|
before classification. Each item tries all required namespaces before its first
|
|
mutation, including the encoded-directory cleanup target. Batches retain input
|
|
order and stop at the first error without undoing completed items. Scanner
|
|
ownership comes from the same instance and remains with the physical workers.
|
|
The two residual `DeleteVersion` undo combinations reuse prepared metadata
|
|
deletion rather than bypassing admission.
|
|
|
|
Snapshot-deferred cleanup releases its initial admission while readers remain.
|
|
The final snapshot release reacquires volume admission and checks the pending
|
|
claim under that ownership before deleting. Claim completion retains the same
|
|
owner, so an old cleanup cannot remove a recreated volume's registry entry.
|
|
Bucket deletion settles pending cleanup under its existing write owner and tries
|
|
required namespaces without waiting for a competing volume reader. Successful
|
|
physical volume deletion retires old snapshot state before releasing that owner,
|
|
including when the caller has already been cancelled.
|
|
The incarnation-bound heal path retains its separate admission. Internal bucket
|
|
metadata continues to use its existing owned publication transaction. These
|
|
boundaries complement the receiver-side artifact PUT leases; they do not enable
|
|
distributed repair or garbage collection.
|
|
|
|
Inline `RenameData` retains the backup namespace mutex during rollback-backup
|
|
publication. Because it already owns a volume read lock, it acquires this second
|
|
namespace only without waiting: contention returns `Io(WouldBlock)` through the
|
|
existing failure cleanup, leaving the old object metadata intact. This prevents
|
|
a competing rename and queued bucket deletion from forming a lock cycle while
|
|
preserving exclusion against a delayed backup unlink. The caller may retry after
|
|
contention clears; this path does not add automatic retries.
|
|
|
|
## Persistence and versioning
|
|
|
|
`BucketMetadata` in `crates/ecstore/src/bucket/metadata.rs` stores the prefix in
|
|
the bucket metadata record. Missing fields in older records mean ordinary
|
|
buckets; malformed or duplicate artifact identity fields are errors.
|
|
|
|
Artifact buckets require enabled versioning. Configuration updates cannot
|
|
suspend or disable it. Full metadata replacement must enforce the same namespace
|
|
and versioning invariants; checking only the versioning configuration handler
|
|
would leave another mutation route open. Ordinary configuration changes retain
|
|
the artifact identity.
|
|
|
|
Upgrade all nodes to a version that understands these invariants before creating
|
|
artifact buckets. Older binaries may ignore or overwrite the new metadata field;
|
|
mixed-version operation and downgrade after activation are not supported by this
|
|
contract. Existing ordinary buckets retain their creation semantics.
|
|
|
|
## Boundary with write sealing and garbage collection
|
|
|
|
This storage-library creation capability does not expose an S3 creation header
|
|
or establish a permanently sealed attempt. An artifact prefix alone does not
|
|
prevent later PUT, multipart completion, copy, replication, or restore writes.
|
|
|
|
`ECStore::seal_artifact_attempt_puts` in `crates/ecstore/src/bucket/artifact.rs`
|
|
provides a narrower storage-library operation for ordinary owned ECStore PUTs
|
|
on local stores and Unix distributed stores with URL endpoints.
|
|
The record binds the bucket incarnation and full attempt prefix. Only a missing
|
|
record permits a PUT; a matching record rejects it, and malformed records or
|
|
failed reads also reject it. Repeating the seal verifies the same identity.
|
|
|
|
Artifact bucket creation, inspection, ordinary PUT and PUT sealing use the
|
|
configured storage topology and require namespace locking. Distributed operation
|
|
requires URL endpoints and Unix physical disk leases on every receiver; merely
|
|
changing the setup mode of local file endpoints does not enable it. Repair and
|
|
repair sealing use the same supported topology; version retirement remains
|
|
local-only. This does not enable distributed garbage collection. The legacy RPC
|
|
boundary retains its admission
|
|
owner through physical completion, including cancellation and bucket recreation.
|
|
A remote timeout, quorum response or expired lock lease cannot prove that a
|
|
physical writer stopped.
|
|
|
|
Each managed PUT now carries its metadata-derived identity into the physical
|
|
local disk writer. On Unix, a descriptor-pinned directory under
|
|
`.rustfs-artifact-put-seals/<identity digest>/` contains two permanent lock inodes
|
|
and a closing marker. The stable digest binds bucket name, incarnation and full
|
|
attempt prefix. The admission lock serializes checking the marker with obtaining
|
|
a shared active-write lease. Both the mutation and its blocking filesystem
|
|
workers retain that lease. Sealing durably writes the marker and syncs all parent
|
|
entries before waiting for an exclusive active-write lock. Cancellation during
|
|
this wait loses only the confirmation; subsequent admission remains denied.
|
|
These files must not be deleted, replaced or omitted during disk restoration.
|
|
Non-Unix platforms reject this disk-fence operation.
|
|
|
|
The dedicated `ArtifactRenameData` and `SealArtifactAttemptPuts` RPCs require
|
|
body-bound authentication and a ready listener-owned storage instance. Their
|
|
receivers validate the bucket incarnation; seal requests also bind the expected
|
|
physical disk UUID. Seal response proofs cover the complete request, actual disk
|
|
identity and outcome. Old peers cannot be used through a legacy-method fallback.
|
|
The coordinator requires every configured disk, including offline slots, and
|
|
rejects missing or mismatched identities, including on retries after the seal
|
|
record already exists. Ordinary PUT retains its write quorum; successful sealing
|
|
requires confirmation from every configured receiver.
|
|
|
|
The PUT commit path takes locks in this order: bucket lifecycle READ, attempt
|
|
record READ, then target object WRITE. Different objects in the same attempt can
|
|
commit concurrently; uploading a body does not hold the attempt lock. The actual
|
|
commit and rename-tail owner retains the attempt guard after a quorum response
|
|
or cancellation of the request waiter. The seal takes the attempt lock in WRITE
|
|
mode and persists its record with all metadata writes drained before returning.
|
|
Thus it waits for admitted ordinary PUTs and prevents later ordinary PUT
|
|
publication, including after a restart.
|
|
|
|
The four-node scenarios in
|
|
`crates/e2e_test/src/distributed/artifact_seal_test.rs` exercise cross-node
|
|
versioned PUT/GET, full restart, and exact seal retries with an offline receiver,
|
|
including retries after a successful seal. Other attempts in the managed bucket
|
|
and ordinary buckets remain writable. After a receiver restarts, HTTP readiness
|
|
does not imply that peers have re-established its disk connection; retry the
|
|
same seal identity until a successful receipt confirms every receiver.
|
|
|
|
`crates/e2e_test/src/distributed/artifact_put_tail_test.rs` sends a direct signed
|
|
ordinary PUT receiver request without a coordinator attempt lock. It pauses the
|
|
actual filesystem worker, disconnects the RPC, observes server-side waiter
|
|
cancellation, and requires seal refusal until physical completion. Re-signed
|
|
requests remain rejected both while draining and after confirmation, without
|
|
changing staged or published bytes. Exact-version shard census and GET verify
|
|
the completed publication on all four nodes. This test requires the
|
|
`e2e-test-hooks` server feature. RPC cancellation does not cancel the admitted
|
|
PUT transaction: it may finish metadata publication while retaining its owner.
|
|
The first successful seal confirmation is the boundary after which the test
|
|
requires published files to remain unchanged.
|
|
|
|
These scenarios do not inject power loss, disk replacement, or backup
|
|
restoration. Successful ordinary PUT sealing is not a repair seal, version
|
|
retirement, or GC authorization.
|
|
|
|
A failed or cancelled seal request may still leave a record for its original
|
|
incarnation. Retry idempotently to confirm success; an error is not proof that
|
|
the attempt remains writable or that all metadata writes have been cleaned up.
|
|
|
|
The managed ECStore entry points reject unsupported writes into an artifact
|
|
bucket: borrowed-lock and special-purpose PUTs, copy destinations, multipart
|
|
creation/parts/completion, data movement, restore, transition, metadata rewrites,
|
|
and tag changes. Ordinary single, batch, and exact-version deletion are also
|
|
rejected, including delete-marker creation and removal, before any local disk
|
|
can stage metadata or rollback state. This applies both at ECStore and public
|
|
SetDisks entry points; it does not authorize retirement or GC. Copying from an
|
|
artifact bucket to an ordinary bucket remains allowed. Listing and aborting
|
|
existing multipart uploads remain available for
|
|
cleanup. Classification is bound to the authoritative bucket incarnation, so an
|
|
old ordinary-bucket request cannot publish into a recreated artifact bucket.
|
|
The guarded entry points only borrow lifecycle authority from a matching snapshot
|
|
that retains the actual lock owner. A standalone lock-loss signal or a snapshot
|
|
without that owner is rejected; internal callers must supply the owned snapshot
|
|
or let the entry point acquire its own lifecycle lock.
|
|
|
|
Lower-level SetDisks PUTs also resolve authoritative bucket metadata and require
|
|
a matching managed target for an artifact object. Missing metadata authority is
|
|
an error, not evidence that a bucket is ordinary. Internal callers borrowing a
|
|
lifecycle fence must retain its actual owner through physical publication.
|
|
|
|
Deletion preserves that owner in the existing Object Lock snapshot. Detached
|
|
prefix deletion transfers its owned lifecycle lock, rather than borrowing only
|
|
a lock-loss signal. The internal free-version cleaner passes its existing lock
|
|
owner explicitly, avoiding a second read acquisition behind a queued bucket
|
|
writer. It still rejects managed buckets. Transaction-private rollback cleanup
|
|
keeps its existing admission contract.
|
|
|
|
`crates/e2e_test/src/distributed/artifact_delete_test.rs` exercises single and
|
|
mixed batch deletion through all four nodes before sealing, after sealing, and
|
|
after restart. It includes persisted delete-marker state and ordinary-bucket
|
|
success controls. Rejected requests must preserve file bytes and filesystem
|
|
identity/change times, so a local mutation followed by rollback is not treated
|
|
as a successful rejection.
|
|
|
|
Object repair takes the same attempt lock in WRITE mode before capacity and object locks.
|
|
Local rename and metadata-replacement workers retain the lifecycle and attempt
|
|
owners until the physical mutation completes, even if the async waiter is
|
|
cancelled or times out. On distributed Unix URL topologies, dedicated
|
|
`ArtifactRepairRenameData` and `ArtifactRepairWriteMetadata` RPCs bind the
|
|
physical disk UUID, bucket incarnation, target and both FileInfo payloads.
|
|
The receiver captures its own ready storage instance and retains an independent
|
|
repair lease and exact-version retirement guard through physical mutation and
|
|
rollback. It does not reacquire the
|
|
coordinator's attempt lock or fall back to legacy RPCs on an older peer.
|
|
|
|
Repair leases use `.rustfs-artifact-repair-seals`, independent of the existing
|
|
PUT lease directory. Preserve both directories during disk restoration.
|
|
Distributed managed repair rejects destructive cleanup that lacks equivalent
|
|
receiver ownership; optional orphan cleanup is skipped. Ordinary-bucket repair
|
|
keeps its existing behavior. Repair of an existing version remains allowed after
|
|
PUT sealing; it does not reopen logical publication. Ordinary-bucket repair also
|
|
retains lifecycle ownership to prevent publication into a recreated bucket.
|
|
|
|
`ECStore::seal_artifact_attempt_repairs` separately closes repair admission for
|
|
the entire attempt, including existing versions and minority metadata repair.
|
|
It requires an ordinary PUT seal first. Under lifecycle, attempt, then repair
|
|
record locks, it persists an immutable record bound to the incarnation and full
|
|
attempt prefix, then closes and drains each configured disk's repair domain.
|
|
`SealArtifactAttemptRepairs` has its own request and response proof domains.
|
|
Confirmation requires every configured receiver with its expected physical
|
|
UUID; quorum is insufficient. Retries repeat this check even when the global
|
|
record already exists. An offline member prevents successful confirmation. New repair
|
|
requests reject a matching record; corrupt, mismatched, or unreadable records
|
|
also fail closed. Retrying confirms the same identity, including after restart.
|
|
Existing bytes remain readable while sufficient healthy shards remain. Other
|
|
attempts retain their repair behavior.
|
|
|
|
Repair sealing is irreversible through this API and reduces recoverability:
|
|
do not apply it automatically to successful outputs that still need repair.
|
|
It is a repair fact, not an all-writers or deletion authorization. Do not
|
|
downgrade to a binary that ignores repair seals after using this operation.
|
|
Restoring pre-seal storage can rewind the decision and requires reconciliation
|
|
before relying on the seal again; records must not expire by age.
|
|
|
|
`ECStore::retire_artifact_object_version` disables repair of one exact version
|
|
inside an already sealed attempt. Retirement still requires local topology.
|
|
It requires the current bucket incarnation,
|
|
an object strictly inside the attempt prefix, and a non-null version. Retirement
|
|
does not delete data: existing bytes remain readable, and other versions remain
|
|
repairable. Directory objects use the same canonical identity as storage.
|
|
|
|
Retirement takes lifecycle, attempt, then record locks. It waits for earlier
|
|
physical repair publishers, including workers whose callers were cancelled.
|
|
The record transaction retains its owners after request cancellation; retrying
|
|
the same identity confirms the durable outcome. An immutable record binds the
|
|
bucket incarnation, attempt, object, and version. Unreadable, corrupt, or
|
|
mismatched records fail closed. Normal repair and minority metadata regeneration
|
|
check the selected version before publication, including when the caller asks
|
|
for the latest version. Refusal is not a physical absence proof.
|
|
|
|
PUT sealing, repair sealing, and version retirement remain separate facts.
|
|
Runner must not combine independently obtained narrow receipts into an
|
|
all-writes claim. Distributed exact-version retirement still requires
|
|
implementation and verification. Retirement records must not expire by age;
|
|
restoring storage from before a retirement can rewind that decision and requires
|
|
a separate recovery contract.
|
|
|
|
Before Runner can rely on a seal, storage must fence every supported write path
|
|
for the exact bucket incarnation and attempt namespace, retain the fence through
|
|
commit and any remaining physical writes, and durably record the seal. Unsupported
|
|
write paths must reject writes into the managed namespace. HTTP cancellation or
|
|
a cached metadata value cannot substitute for that storage boundary.
|
|
|
|
Automatic garbage collection remains disabled until storage sealing and the
|
|
backup/restore retention contract are both implemented and verified. Unknown
|
|
backup/PITR coverage must retain artifacts; a completed plugin execution does not
|
|
prove that historical results are safe to delete.
|
|
|
|
## Combined write sealing
|
|
|
|
`ECStore::seal_artifact_attempt_writes` closes both supported publication domains
|
|
for one managed bucket incarnation and attempt. It persists the existing PUT
|
|
and repair records under lifecycle, attempt, then repair-record ownership. It
|
|
does not introduce a third record or change either narrow operation's meaning.
|
|
|
|
Both domains use one captured set of configured endpoints, disk instances, and
|
|
non-nil disk UUIDs. Every configured receiver must participate; quorum is not
|
|
enough. Local confirmation reads the actual format through the captured disk
|
|
root, bypasses the ordinary ID cache, and rejects a changed configured root.
|
|
Remote confirmation uses the existing signed, disk-bound seal RPCs. Membership
|
|
and physical identity are rechecked before success. A partial failure leaves
|
|
durable closure in place; an exact retry must confirm both domains again.
|
|
|
|
Upgrade every configured node to an implementation of the fresh identity and
|
|
shared-root checks before using combined sealing. Mixed-version operation is
|
|
unsupported. These existing RPCs do not negotiate that capability: a peer with
|
|
the older seal implementation can still return a valid signed response. Peers
|
|
without the RPC fail without fallback, but that is not a version gate.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant C as Controller
|
|
participant S as Storage coordinator
|
|
participant R as Every configured receiver
|
|
C->>S: seal-writes(version, target)
|
|
S->>S: Own lifecycle and attempt; capture disk IDs; persist PUT record
|
|
S->>R: Close and drain PUT on the captured receivers
|
|
S->>S: Own and persist the repair record
|
|
S->>R: Close and drain repair on the same receivers
|
|
R-->>S: Durable closure and physical drain confirmed
|
|
S->>R: Recheck membership and physical identity
|
|
S-->>C: 200 only after all confirmations
|
|
```
|
|
|
|
The receipt covers the managed profile: ordinary PUT and repair use their
|
|
respective domains; unsupported mutation paths reject the bucket. It is not a
|
|
cross-disk atomic snapshot, a backup/restore guarantee, or permission to delete
|
|
versions. A failed or cancelled caller may leave one or both domains closed;
|
|
only a successful exact retry confirms completion. As with repair sealing,
|
|
closing an attempt's writes also removes its future repair capability.
|
|
|
|
## Local sealed-attempt inventory
|
|
|
|
`LocalDisk::inventory_artifact_attempt` is a crate-private, single-disk physical
|
|
candidate census. Its caller supplies the validated managed bucket, incarnation,
|
|
attempt prefix and existing `ArtifactPutIdentity`. It opens both existing PUT
|
|
and repair closure domains read-only, validates their closed markers, then holds
|
|
the actual active-file drain locks in PUT-to-repair order. A durable closed
|
|
marker alone does not prove that earlier physical owners have finished. The
|
|
scanner never creates a seal or removes residual data.
|
|
|
|
The scanner retains the initialized root descriptor and both drain owners in its
|
|
blocking worker, including after its async waiter is cancelled. It checks fresh
|
|
physical disk identity before scanning and before the terminal frame. It walks
|
|
all versions directly from strict `xl.meta` decoding, preserves UUID-shaped
|
|
namespace children beside referenced data directories, and accounts for known
|
|
part files. Unknown residue, corrupt or unsupported metadata, missing referenced
|
|
parts, symlinks, identity changes, deadlines and resource limits fail without a
|
|
`Complete` frame. This does not verify every shard's content checksum.
|
|
|
|
Output is one invocation with bounded entry frames and sequence numbers. The
|
|
4096-entry traversal quantum only yields work; it is not a total entry limit.
|
|
Depth, active handles, metadata size, pending references and frame size are
|
|
bounded. Backpressure and cancellation cannot turn a partial stream into
|
|
success. Consumers require both the explicit terminal frame and successful
|
|
operation completion; ordinary EOF is insufficient. An absent attempt prefix
|
|
inside the verified bucket yields an empty census; a missing bucket or disk root
|
|
fails. The logical key limit applies to actual metadata objects after decoding
|
|
one directory suffix, not to their longer physical payload-directory paths.
|
|
|
|
The local primitive alone does not expose an HTTP or RPC inventory interface, persist a
|
|
Controller inventory, or establish all-member completeness, quorum readability,
|
|
backup coverage or permission to collect garbage. Automatic GC remains disabled.
|
|
|
|
## Verification
|
|
|
|
`crates/ecstore/src/store/init/tests/artifact_bucket_tests.rs` uses real four-disk
|
|
ECStore instances for creation, restart, pending creation retry, configuration
|
|
updates, metadata replacement, and versioning rejection. Direct libtest runs
|
|
use the same 4 MiB thread stack as the repository's ecstore nextest setup:
|
|
|
|
```sh
|
|
export RUST_MIN_STACK=4194304
|
|
cargo test --locked -p rustfs-storage-api --lib artifact
|
|
cargo test --locked -p rustfs-ecstore --lib --features test-util artifact_bucket -- --test-threads=1
|
|
cargo test --locked -p rustfs-ecstore --lib --features test-util artifact_put_seal -- --test-threads=1
|
|
cargo test --locked -p rustfs-ecstore --lib --features test-util artifact_write_entrypoint -- --test-threads=1
|
|
cargo test --locked -p rustfs-ecstore --lib --features test-util artifact_repair_tests -- --test-threads=1
|
|
cargo test --locked -p rustfs-ecstore --lib --features test-util artifact_retirement -- --test-threads=1
|
|
cargo test --locked -p rustfs-ecstore --lib --features test-util artifact_writes_seal -- --test-threads=1
|
|
```
|
|
|
|
`crates/ecstore/src/store/init/tests/artifact_put_seal_tests.rs` pauses real
|
|
rename work to check the PUT barrier, including a cancelled request waiter, and
|
|
compares on-disk object metadata after rejected writes. It also checks restart,
|
|
record corruption, generation mismatch, and ordinary bucket behavior.
|
|
|
|
`crates/ecstore/src/store/init/tests/artifact_write_entrypoint_tests.rs` checks
|
|
unsupported entry points against real four-disk state, ordinary-bucket success
|
|
controls, cleanup of preexisting multipart uploads, and deletion/recreation
|
|
between request classification and publication.
|
|
|
|
`crates/ecstore/src/store/init/tests/artifact_repair_tests.rs` exercises direct
|
|
PUT admission, sealed-version repair and stale-incarnation rejection on four
|
|
real disks. Physical rename and metadata-replacement pauses test that cancellation
|
|
cannot release the attempt barrier before publication finishes. The rename case
|
|
also drops the repair future at a caller deadline; its guarded disk wrapper
|
|
deliberately has no timeout. The metadata case uses public SetDisks repair to
|
|
trigger real disk-wrapper timeouts, checks per-disk timeout counters, and verifies
|
|
that the physical publishers still retain the attempt barrier.
|
|
|
|
`crates/ecstore/src/store/init/tests/artifact_retirement_tests.rs` uses real
|
|
four-disk storage to check exact-version and latest repair refusal, minority
|
|
metadata regeneration, restart, record corruption, directory normalization, and
|
|
unretired-version repair. Physical syscall pauses cover both cancelled repair
|
|
workers and cancelled retirement publication. Retirement leaves GET available
|
|
and never supplies evidence that the version is physically absent.
|
|
|
|
The narrow-operation checks establish bucket identity, versioning, ordinary PUT sealing,
|
|
attempt repair sealing, and exact-version repair retirement. Repair-seal tests
|
|
also cover restart, corrupt records, other-attempt isolation, and cancelled
|
|
rename/metadata workers whose physical writes must drain before confirmation.
|
|
They do not certify all-writes sealing, backup retention, or safe artifact
|
|
deletion.
|
|
|
|
`crates/ecstore/src/store/init/tests/artifact_writes_seal_tests.rs` checks the
|
|
combined operation with real storage, including partial closure, exact retries,
|
|
record corruption, missing members, and changed disk identity without waiting
|
|
for the ordinary format cache to expire. The four-node scenario in
|
|
`crates/e2e_test/src/distributed/artifact_writes_test.rs` checks combined
|
|
confirmation, offline refusal, restart, and continued PUT/repair rejection.
|
|
Its Object Lock requests first enable and read back bucket configuration, so
|
|
retention and legal-hold refusal reaches the object mutation path. The admin
|
|
handler tests similarly use real historical multipart staging when exercising
|
|
the S3 UploadPartCopy entry point.
|
|
|
|
The distributed repair scenarios in
|
|
`crates/e2e_test/src/distributed/artifact_repair_test.rs` verify exact-version
|
|
shard and minority metadata recovery after PUT sealing, repair refusal across
|
|
restart, offline-member seal retries, and preservation of dangling metadata.
|
|
`artifact_repair_tail_test.rs` pauses a real receiver filesystem worker after a
|
|
direct authenticated repair RPC. It waits for receiver-side cancellation before
|
|
sealing, requires refusal while the worker remains paused, then verifies physical
|
|
recovery and successful seal retry. This test requires the `e2e-test-hooks`
|
|
server feature; the barrier is absent from production builds.
|
|
|
|
```sh
|
|
python3 scripts/e2e_binary.py build --features e2e-test-hooks
|
|
python3 scripts/e2e_binary.py run --features e2e-test-hooks -- cargo test -p e2e_test distributed::artifact_repair -- --test-threads=1
|
|
```
|
|
|
|
## Controller API
|
|
|
|
The admin router exposes the following SigV4-authenticated POST operations:
|
|
|
|
- `/rustfs/admin/v3/plugin-artifacts/buckets`, requiring
|
|
`admin:CreatePluginArtifactBucket` on the bucket ARN.
|
|
- `/rustfs/admin/v3/plugin-artifacts/buckets/inspect`, requiring
|
|
`admin:GetPluginArtifactBucket` on the bucket ARN.
|
|
- `/rustfs/admin/v3/plugin-artifacts/seal-attempt`, requiring
|
|
`admin:SealPluginArtifactAttempt` on the attempt namespace.
|
|
- `/rustfs/admin/v3/plugin-artifacts/seal-repairs`, requiring
|
|
`admin:SealPluginArtifactRepairs` on the attempt namespace.
|
|
- `/rustfs/admin/v3/plugin-artifacts/seal-writes`, requiring
|
|
`admin:SealPluginArtifactWrites` on the attempt namespace.
|
|
- `/rustfs/admin/v3/plugin-artifacts/retire-version`, requiring
|
|
`admin:RetirePluginArtifactVersion` on the exact logical object key.
|
|
- `/rustfs/admin/v3/plugin-artifacts/inventory`, requiring
|
|
`admin:InventoryPluginArtifactAttempt` on the complete attempt namespace.
|
|
|
|
All require a permanent, non-root original IAM user and an installed request
|
|
server context. Temporary credentials and service accounts are rejected. The
|
|
initial profile requires built-in IAM; external authorization plugins cannot
|
|
establish its explicit local Resource contract. No separate signer map is used.
|
|
The controller grant is independent of an execution delegation's expiration.
|
|
|
|
### Sealed-attempt inventory stream
|
|
|
|
The inventory POST accepts `version: 1`, a non-nil UUID `invocation_id`, and
|
|
`target` with the same complete `bucket`, `bucket_incarnation`, and canonical
|
|
`attempt_prefix` identity as the seal requests below. Unknown fields and
|
|
ambiguous paths are rejected. Its independent action must have an explicit
|
|
matching Resource; seal actions, ordinary S3 listing and another attempt's
|
|
grant do not authorize inventory. The permanent original-user, built-in IAM
|
|
and request-context requirements above apply unchanged.
|
|
|
|
A successful request starts an `application/x-ndjson` body with `Cache-Control:
|
|
no-store`. Each newline-terminated JSON frame repeats `version: 1`, the supplied
|
|
`invocation_id`, full `target`, and a continuous zero-based `sequence`.
|
|
Its tagged `payload` is one of:
|
|
|
|
- `{"type":"observation","disk_id":"<non-nil UUID>","key":"<complete logical key>","version_id":"<non-nil UUID>","delete_marker":false}`.
|
|
- `{"type":"complete","members":4,"observations":12}`.
|
|
|
|
The counts above are examples. Observations retain their physical disk identity;
|
|
replicas are not additional business versions or a quorum-readability proof.
|
|
An empty attempt has no observation frames, but still requires every configured
|
|
member and the complete terminal. Internal endpoints and physical paths are
|
|
never projected to this wire.
|
|
|
|
The adapter uses bounded channels and caps each actual encoded line, including
|
|
its newline, at 1 MiB. One absolute 30-second deadline covers authorization,
|
|
request decoding, all-member inventory, backpressure and output. The existing
|
|
16 KiB request bound and 10-second body-read bound also apply. A stalled body
|
|
consumer cannot extend that deadline. Disconnecting the HTTP body cancels the
|
|
coordinator future; existing physical workers retain their own owners until
|
|
they actually exit.
|
|
|
|
Complete is staged until I2 has both returned success and ended its input
|
|
normally. Late storage failure, timeout, corruption or changed membership can
|
|
only abort/close the body without Complete; internal errors are replaced with
|
|
a generic stream failure. Authentication and validation errors before streaming
|
|
use the existing AccessDenied, InvalidRequest or ServiceUnavailable responses.
|
|
HTTP 200 and EOF alone do not establish completeness. Consumers must stage all
|
|
observations and validate identity, sequence, counts, one final Complete and
|
|
clean EOF before accepting the result; a transport error after Complete also
|
|
invalidates that result. Never automatically replay a partially emitted stream.
|
|
|
|
This endpoint uses the existing read-only I2 authority and closure checks. It
|
|
creates no seal/identity record, repairs no metadata and changes no object,
|
|
bucket or IAM configuration. It neither persists a Controller inventory nor
|
|
grants GC, backup retention or deletion permission. Old servers without this
|
|
route fail instead of falling back to ordinary listing.
|
|
|
|
### Sealing requests
|
|
|
|
All three seal operations use this request:
|
|
|
|
```json
|
|
{
|
|
"version": 1,
|
|
"target": {
|
|
"bucket": "artifacts",
|
|
"bucket_incarnation": "bdbf4d55-7ae3-42f1-a8ea-88cb2836d35d",
|
|
"attempt_prefix": "out/61/8694133a-8a38-4911-b2cc-614556a852a9/301c4039-9739-4a70-a88f-6d83cf721509/"
|
|
}
|
|
}
|
|
```
|
|
|
|
Retirement uses the same fields plus `object` (the complete logical key below
|
|
that attempt) and `version_id` (a non-nil UUID). Unknown fields are rejected at
|
|
both levels. Bodies are limited to 16 KiB and a 10-second read deadline.
|
|
The storage call has a 30-second caller deadline.
|
|
|
|
IAM uses the existing S3 resource grammar, including its path normalization:
|
|
the namespace's trailing slash is removed during matching. An exact attempt
|
|
controller policy can therefore use these statements (grant repair sealing
|
|
separately only to the component making that irreversible decision; the combined
|
|
operation likewise needs its own `admin:SealPluginArtifactWrites` grant):
|
|
|
|
```json
|
|
{
|
|
"Version": "2012-10-17",
|
|
"Statement": [
|
|
{
|
|
"Effect": "Allow",
|
|
"Action": "admin:SealPluginArtifactAttempt",
|
|
"Resource": "arn:aws:s3:::artifacts/out/61/8694133a-8a38-4911-b2cc-614556a852a9/301c4039-9739-4a70-a88f-6d83cf721509"
|
|
},
|
|
{
|
|
"Effect": "Allow",
|
|
"Action": "admin:SealPluginArtifactRepairs",
|
|
"Resource": "arn:aws:s3:::artifacts/out/61/8694133a-8a38-4911-b2cc-614556a852a9/301c4039-9739-4a70-a88f-6d83cf721509"
|
|
},
|
|
{
|
|
"Effect": "Allow",
|
|
"Action": "admin:RetirePluginArtifactVersion",
|
|
"Resource": "arn:aws:s3:::artifacts/out/61/8694133a-8a38-4911-b2cc-614556a852a9/301c4039-9739-4a70-a88f-6d83cf721509/*"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
An Allow with neither Resource nor NotResource does not grant these artifact actions.
|
|
Explicit unscoped Deny, including `admin:*`, remains effective. Existing admin
|
|
and table/KMS action semantics are unchanged. A single-object grant cannot seal
|
|
its containing attempt. IAM scopes the logical key, not individual versions;
|
|
exact bucket generation and version identity are validated by storage.
|
|
|
|
Only a completed storage operation returns HTTP 200 with `Cache-Control: no-store`.
|
|
PUT seal returns `version`, `target`, and `ordinary_put_sealed: true`. Repair seal
|
|
returns `version`, `target`, and `attempt_repairs_sealed: true`. Combined sealing
|
|
returns `version`, `target`, `ordinary_put_sealed: true`,
|
|
`attempt_repairs_sealed: true`, and `all_writes_sealed: true`. The two narrow
|
|
permissions do not grant access to the combined operation. Retirement
|
|
returns `version`, `target`, `object`, `version_id`, and
|
|
`exact_version_repair_retired: true`. These are idempotent confirmations of
|
|
specific storage facts. Only the combined operation attests to both write
|
|
domains on a common receiver set. No receipt attests that data was deleted or
|
|
that GC is permitted.
|
|
|
|
Malformed requests return InvalidRequest; authentication/resource denial returns
|
|
AccessDenied. All storage failures and caller deadlines return a sanitized
|
|
ServiceUnavailable response: `artifact operation unconfirmed; retry the same identity`.
|
|
This deliberately does not reveal internal record paths or distinguish durable
|
|
record corruption from unavailable storage. It also does not mean the operation
|
|
was rolled back: the cancellation-safe storage transaction may still complete.
|
|
Retry the identical target to obtain confirmation; operators must investigate
|
|
persistent failures. Version retirement still rejects distributed topologies.
|
|
Ordinary PUT sealing and repair sealing use their separate receiver confirmations
|
|
described above.
|
|
|
|
Bucket creation does not change STS vending. Existing v1
|
|
vending requires input and output to share the delegation's bucket;
|
|
cross-bucket output credentials need a separate change. Bucket creation alone
|
|
does not authorize cleanup or establish backup retention; those require the
|
|
explicit protocols below.
|
|
|
|
### Dedicated bucket creation
|
|
|
|
POST `/rustfs/admin/v3/plugin-artifacts/buckets` with:
|
|
|
|
```json
|
|
{"version":1,"bucket":"plugin-artifacts","base_prefix":"outputs/"}
|
|
```
|
|
|
|
The controller needs an explicit `admin:CreatePluginArtifactBucket` Allow on
|
|
`arn:aws:s3:::plugin-artifacts`. An object-only ARN such as
|
|
`arn:aws:s3:::plugin-artifacts/*` does not authorize bucket creation. The same
|
|
original-user, built-in IAM, request-instance and body/deadline checks apply.
|
|
Unknown fields and invalid bucket names or base prefixes are rejected.
|
|
|
|
The base prefix is relative, uses only ASCII letters, digits, `/`, `.`, `_`
|
|
and `-`, and ends with exactly one `/`. Components must be nonempty, cannot be
|
|
`.` or `..`, and are limited to 255 bytes each. The prefix is at most 946 bytes,
|
|
leaving 78 bytes for the shortest encoded tenant, two UUIDs, separators and a
|
|
nonempty object name. Each complete object key is still validated; longer tenant
|
|
IDs and object names need more space. On Windows, the existing reserved-name and
|
|
trailing-dot restrictions also apply to each component.
|
|
|
|
The storage helper requires a supported configured topology (local storage or
|
|
Unix distributed URL endpoints) and enabled namespace locking. It invokes the
|
|
existing creation transaction with an
|
|
immutable artifact namespace and versioning enabled. It does not change the
|
|
type of an existing bucket or turn an existing-bucket failure into success.
|
|
`BucketExists` maps to `BucketAlreadyExists`; other unconfirmed storage outcomes
|
|
retain the sanitized ServiceUnavailable response described above.
|
|
|
|
After creation returns, the helper acquires the lifecycle read lock and reads
|
|
one authoritative metadata snapshot. The namespace, non-nil incarnation and
|
|
full versioning configuration must match; excluded folders or prefixes are not
|
|
allowed. Success returns HTTP 200 with `version`, `bucket`, `base_prefix`,
|
|
`bucket_incarnation`, and `versioning_enabled: true`.
|
|
|
|
This receipt identifies the current managed generation observed under the
|
|
lifecycle lock. It does not assert that this request created that generation:
|
|
a legitimate deletion/recreation between creation and observation may produce
|
|
a newer matching generation. Every later seal or retirement operation must
|
|
still supply and validate the receipt's incarnation. A cancelled or timed-out
|
|
creation may have changed durable state; retrying it can return an existing-bucket
|
|
error and is not a substitute for confirming identity. No GC authority follows.
|
|
|
|
The `artifact_bucket_create_*` handler tests reuse the four-disk S4 fixture and
|
|
real IAM. They cover creation and persisted identity, existing-bucket failures
|
|
without changing type, bucket-resource authorization, request-instance isolation,
|
|
rejection of file endpoints mislabeled as distributed before creation, and actual delete/recreate with
|
|
old-receipt rejection. The handler recreation test performs two completed creations with the same
|
|
prefix, and checks both rejection of the old incarnation and acceptance of the
|
|
current one. The storage test `artifact_bucket_create_observes_current_generation`
|
|
performs real replacement between creation and the same internal observation
|
|
function consumed by the public helper, then reopens storage and verifies the
|
|
persisted identity. This is directed coverage of the production observation
|
|
phase, not an injected HTTP handler race.
|
|
|
|
`artifact_bucket_create_observation_rejects_corrupt_authority` checks the
|
|
observation's actual authoritative-cache path with damaged name, namespace and
|
|
versioning data, plus physical metadata and nil-sidecar corruption through fresh
|
|
store instances. The physical cases assert the specific disk parsing error, then
|
|
restore the record and reopen again to verify the original identity. The
|
|
authoritative snapshot may already be cached from a committed write; observation always holds the lifecycle read lock but does not acquire
|
|
metadata and bucket locks on every cache hit. Removing its identity/versioning
|
|
validation must fail `ARTIFACT_CREATE_CURRENT_GENERATION` at runtime.
|
|
`artifact_bucket_create_requires_namespace_locking_before_creation` uses a
|
|
real lock-disabled instance and verifies no physical bucket is created. Local
|
|
fast-lock guards cannot lose distributed quorum; this local-only profile does
|
|
not claim to inject distributed guard loss. Policy coverage is
|
|
`artifact_bucket_create_resource_requires_exact_bucket_authority`.
|
|
|
|
|
|
### Existing bucket identity inspection
|
|
|
|
POST `/rustfs/admin/v3/plugin-artifacts/buckets/inspect` accepts the same strict
|
|
`version: 1`, `bucket`, and `base_prefix` body as creation. It requires its own
|
|
`admin:GetPluginArtifactBucket` Allow on `arn:aws:s3:::plugin-artifacts`;
|
|
creation permission does not authorize inspection. Original-user and
|
|
request-instance checks are unchanged. An object ARN cannot authorize this
|
|
bucket operation, and explicit Deny still takes precedence.
|
|
|
|
Inspection requires local storage and namespace locking. Under the lifecycle
|
|
read lock, the existing metadata loader returns one snapshot without performing
|
|
legacy incarnation migration or completing a pending creation. The snapshot
|
|
must have a committed creation, a non-nil sidecar-backed incarnation, the exact
|
|
artifact prefix, and fully enabled versioning without folder or prefix
|
|
exclusions. Ordinary buckets, missing buckets, pending intents and mismatching
|
|
prefixes fail; the query does not create buckets or write metadata. Unsupported
|
|
or unconfirmed storage outcomes return sanitized HTTP 503. They do not mean the
|
|
bucket is absent, safe to recreate, or safe to delete.
|
|
|
|
The HTTP 200 receipt has the same fields as creation. A restarted controller
|
|
can observe the current managed incarnation without creating another bucket.
|
|
It must compare that identity with its accepted configuration; a matching name
|
|
and prefix do not establish continuity across deletion/recreation. Subsequent
|
|
seal and retirement calls must carry this exact incarnation and revalidate it.
|
|
The response is a current observation, not a permanent seal or GC permission.
|
|
Runner persistence and automatic reconciliation are not implemented by this API.
|
|
|
|
Three `artifact_bucket_inspect_*` handler tests reuse the real IAM/four-disk
|
|
fixture for identity and recreation, resource and request-instance isolation,
|
|
and rejection without retyping or creation. They dispatch the internal handler,
|
|
not a signed network request. The storage tests reopen the store before reading,
|
|
assert that a real pending intent's bytes stay unchanged, and verify that legacy
|
|
volumes and missing buckets gain no metadata or incarnation records. Namespace
|
|
locking and rejection of file endpoints mislabeled as distributed are covered
|
|
independently.
|
|
|
|
|
|
## Authenticated all-member inventory stream
|
|
|
|
`ECStore::inventory_artifact_attempt` observes one attempt under lifecycle READ,
|
|
then the existing PUT-attempt and repair-record owners. Both immutable seal
|
|
records must already match; inventory creates neither records nor disk closure.
|
|
The coordinator captures every configured endpoint, exact Disk Arc and expected
|
|
UUID once. Every member, including an empty member, must successfully contribute
|
|
a terminal scan result. Observations retain endpoint and disk UUID; replica
|
|
duplicates remain observations, with no unbounded global version set or quorum
|
|
readability claim. Earlier frames are incomplete staging until global Complete
|
|
and the operation's successful return.
|
|
|
|
`InventoryArtifactAttempt` protocol version 1 uses a fresh invocation UUID and
|
|
binds endpoint, expected physical UUID, bucket, incarnation, attempt prefix,
|
|
time budget and explicit Scan/Verify phase into the canonical v2 request and
|
|
every response proof. Entries, exact version UUIDs, delete-marker flags, sequence,
|
|
terminal count and actual disk UUID are authenticated. Wire frames are limited to
|
|
256 entries and 64 KiB of actual protobuf encoding, including response proof.
|
|
The client enforces contiguous sequence, scope, counts and distinct phase
|
|
terminals, and withholds scan Complete until clean EOF. Truncation, post-terminal
|
|
frames and terminal-followed-by-transport-error are incomplete. Old peers that
|
|
lack this RPC fail; there is no fallback to List or Walk EOF.
|
|
|
|
Verify is a separate, signed `Verified` terminal; it is never a zero-entry scan
|
|
Complete. After scans, the coordinator uses this read-only phase to freshly
|
|
recheck each remote physical receiver and both existing drained domains while
|
|
other members may have taken longer. Local verification retains the same pinned
|
|
root and two-domain owners. The captured member set/Arc and expected IDs, and
|
|
actual persisted bucket metadata/incarnation, are checked again before global
|
|
completion. These are conservative rechecks, not an atomic snapshot across all
|
|
disks or a backup/restore guarantee.
|
|
|
|
Receiver authority uses bounded `read_config_observation` of existing metadata
|
|
and incarnation sidecar: it bypasses cache, suppresses repair, and never migrates
|
|
legacy identity. These reads take internal metadata-object locks, without
|
|
reentering the coordinator's business-bucket lifecycle READ behind a queued
|
|
writer. Each physical scan retains I1's initialized root and real drain owners
|
|
through its last read; cancellation or slow consumers cannot release a detached
|
|
worker's owner early. Deadline/resource/identity/closure failures omit Complete;
|
|
invalid authentication is PermissionDenied, invalid wire input InvalidArgument,
|
|
physical scan/closure failure (including a local scanner timeout) FailedPrecondition,
|
|
and expiration of the outer handler deadline DeadlineExceeded.
|
|
Client protocol, signature or membership failures are errors, regardless of
|
|
previous partial output. No public admin route, Controller persistence, deletion,
|
|
GC permission or saved scan session is introduced by this layer.
|
|
|
|
## Exact-version cleanup
|
|
|
|
`POST /rustfs/admin/v3/plugin-artifacts/delete-version` accepts the same strict
|
|
version-1 exact target as `retire-version`: `target` contains `bucket`,
|
|
`bucket_incarnation` and the canonical `attempt_prefix`; `object` and non-nil
|
|
`version_id` select one exact version. Its independent
|
|
`admin:DeletePluginArtifactVersion` permission is scoped to the exact object
|
|
resource. Grant it only to the cleanup controller, separately from upload,
|
|
inventory, sealing and verification identities.
|
|
|
|
The storage operation checks the incarnation under its lifecycle owner and
|
|
requires both ordinary PUT and repair seals. It preserves the normal Object
|
|
Lock checks. A successful response echoes the target, object and version and
|
|
sets `exact_version_deleted: true` under the normal RustFS quorum deletion
|
|
semantics. This is not proof of physical erasure from every offline disk; retain
|
|
the permanent seals and deletion ledger. Retrying the same identity after an unknown
|
|
response is safe. A missing bucket, changed incarnation, missing seal, permission
|
|
failure or unavailable storage is not a successful deletion receipt. Ordinary
|
|
S3 DELETE remains prohibited for managed buckets.
|
|
|
|
This endpoint executes an already authorized cleanup decision; it does not
|
|
establish absence of publication references or backups. The runner must commit
|
|
its eligibility decision and exact deletion intent before invoking it. Initial
|
|
support is restricted to configured local disks; remote/distributed layouts
|
|
reject rather than bypass their managed-write receiver protections.
|
|
|
|
Before enabling cleanup, request bucket inspection with `version: 2`. It returns
|
|
the existing identity fields and `exact_delete_supported`; the Controller must
|
|
require `true`. The ordinary `version: 1` inspection response is unchanged, and
|
|
bucket creation still accepts only version 1. The capability check is read-only
|
|
and uses `admin:GetPluginArtifactBucket`. Every later deletion rechecks its
|
|
identity and fences, regardless of this startup observation.
|
|
|
|
## Permanent backup retention
|
|
|
|
Backup scheduling is owned by plugin-runner and is disabled by default. Before
|
|
creating a restorable snapshot, the Controller must reserve a permanent
|
|
`BACKUP_GC_DISABLED` database policy and then enable storage protection. This
|
|
storage API does not create a database backup or certify that PostgreSQL WAL is
|
|
complete. A database restored to an older time cannot remove this storage fact.
|
|
|
|
`POST /rustfs/admin/v3/plugin-artifacts/buckets/retain-backups` accepts exactly:
|
|
|
|
```json
|
|
{"version":1,"bucket":"plugin-artifacts","base_prefix":"outputs/","bucket_incarnation":"<UUID>"}
|
|
```
|
|
|
|
It requires the separate bucket-scoped `admin:RetainPluginArtifactBackups`
|
|
action. The response echoes those four fields and adds `backup_retained: true`.
|
|
Unknown fields, a nil/stale incarnation, a wrong prefix, unavailable locks or
|
|
unconfirmed storage fail the request. Retry the same identity after a lost
|
|
response; a timeout does not mean the protection was rolled back. There is no
|
|
unretain endpoint. Turning off backup scheduling leaves existing protection in
|
|
place, and this first mode has no automatic backup expiration or artifact GC.
|
|
|
|
The storage owner acquires lifecycle WRITE, draining earlier exact DELETE owners,
|
|
then the metadata transaction lock. It validates authoritative metadata and the
|
|
incarnation sidecar, writes the marker with `TailDrained`, and publishes the
|
|
cache only after persistence. The tracked detached owner survives cancellation
|
|
of the HTTP waiter. Exact deletion checks protection under lifecycle READ;
|
|
bucket deletion, including force deletion of an empty bucket, also rejects a
|
|
protected generation. An unreadable metadata record cannot authorize deletion.
|
|
Ordinary metadata updates cannot clear the marker.
|
|
|
|
Only protected buckets use metadata header version 2. Ordinary buckets retain
|
|
version 1. Old binaries reject version 2 instead of silently ignoring the new
|
|
MessagePack field. The new reader requires agreement between the header and
|
|
marker, a committed managed identity and enabled versioning. Upgrade all serving
|
|
nodes before enabling this irreversible protection; rolling back to an older
|
|
binary does not remove it and may make the protected bucket unavailable.
|
|
|
|
Inspect with `version: 3` using `admin:GetPluginArtifactBucket`. Its response adds
|
|
`backup_retained` to the version-2 shape. A restore must verify this field is
|
|
true and match the fixed bucket, prefix and incarnation; configuration alone is
|
|
not proof. Version-1 and version-2 response shapes remain unchanged.
|
|
|
|
This protects versions on the running store against supported cleanup paths. It
|
|
does not replace storage replication or protect against destruction of the
|
|
underlying disks, manual metadata replacement, or an administrator bypassing the
|
|
storage service. Backup/PITR deployment procedures must preserve the same
|
|
artifact store identity and isolate old execution authority before reopening a
|
|
restored Controller.
|