Testing¶
Use just check for workspace checks and just fmt for formatting. Operator
keyring inspection, rewrap and bundle verification live in rs3-server;
repository initialization uses rs3-server init. The xtask CLI contains
integration lanes, performance tools and the isolated repository gc-rehearsal command.
Testing is part of the architecture because privacy and rollback behavior are product requirements.
Default Check¶
Inside nix develop:
just check
This runs formatting, clippy with warnings denied, and workspace tests.
v03 Codec Fixtures¶
cargo test -p rs3-repository --test v03_vectors checks the signed commit
fixtures. Workspace tests also pin index tables, roots, payload segments,
repository envelopes and recovery bundles against test-vectors/v03/.
That directory documents fixture keys, fixed test-only nonces and the named
rejection tests. Fixed fixtures do not change production nonce generation.
Repository multipart completion tests cover empty and detached values, receipt
lookup after overwrite, checkpoint, compaction and restart, publication-time
create-only checks, failed anchor advancement, lost anchor replies and local
installation failure. Adapter tests also cover the five multipart routes, replacement serialization,
parallel parts, completion freeze, duplicate completion, monotonic expiry,
admission limits, cancellation, readonly mode and invalid selected/body facts.
Checksum tests cover all five algorithms, encrypted metadata and completion
receipt persistence, and failed writes that leave the accepted value intact.
MD5/ETag tests cover empty, buffered, known-length, unknown-length and
multipart paths, while Content-MD5 tests cover canonical parsing, exact EOF
validation, and failures that preserve the accepted object or part.
cargo test -p rs3-server --test checksum_trailers sends authenticated SigV4
chunks and trailers through the real listener, including missing and invalid
trailer cases.
Local flexible-checksum qualification covered 100 MiB AWS CLI 2.34.24 ordinary and multipart uploads with CRC64NVME and SHA256 on memory and RustFS. Velero 1.18.0 with AWS plugin 1.14.0 passed default CRC32 backup and restore after namespace deletion. Those runs preceded the MD5 ETag format update.
MD5 ETag qualification uses rclone 1.75.0 and AWS CLI 2.34.24 on memory and
RustFS. Direct rclone single PUT validates Content-MD5 and returns the expected
MD5 ETag; md5sum and hash-based check agree, while a same-size corrupted
local file fails hash comparison. A rejected bad-MD5 overwrite preserves the
accepted ETag and original bytes.
Direct AWS two-part uploads validate each part's Content-MD5 and return the
independently calculated MD5-of-part-MD5s ETag. Bad-MD5 part replacements leave
the prior part usable, and direct rclone check --download verifies the
completed bytes. These runs use 100 MiB objects and verify matching ETags from
HEAD, GET and listing responses.
The earlier MD5 qualification used AWS-created multipart objects because
multipart creation then rejected rclone's Mtime metadata. Ordinary PUT and
multipart creation now both accept user metadata without preserving it.
These results qualify byte and ETag behavior, not metadata preservation or all
rclone upload options. At revision 453c2ff, the
corrected nightly run
passed both the gateway-restart dynamic-PVC and PostgreSQL backup/restore Velero
smokes. This is lane-scoped evidence and does not establish an overall nightly
pass.
just integration-s3-gateway --tooling-smoke requires AWS CLI v2, rclone, mc
and restic. The repository's nix develop shell supplies these clients. It
uses deterministic incompressible data above 8 MiB, verifies
uploaded and restored bytes, and checks exact length and multipart ETags for
AWS and rclone uploads. AWS uses an isolated configuration with an explicit
8 MiB multipart threshold; rclone uses explicit settings that disable system
metadata and its custom MD5 metadata. Restic initializes, backs up and restores
a repository. A missing tool or failed required upload fails the lane. Generic
user-metadata preservation is outside this preview compatibility contract.
When a gateway exits during startup, the qualification harness attaches the final 80 captured log lines to that startup failure. When a Helm gateway installation fails, it attaches a capped failure excerpt containing selected-pod readiness, namespace events and selected-container logs before deleting the disposable kind cluster. The Helm excerpt redacts fixture credentials and configured backend paths; these diagnostics aid failure triage and do not change readiness timeouts or qualify a provider.
The four-client lane passes against a disposable local RustFS backend with AWS CLI 2.34.24, rclone 1.75.0, mc RELEASE.2025-08-13 and restic 0.19.1. Both AWS and rclone return independently verified two-part ETags for the 9 MiB-plus- one-byte fixture. mc uses ordinary PUT at that size; restic backup and restore pass without a claim about its internal multipart layout. This lane uses an in-memory rollback anchor and does not qualify process restart or retention.
Copy qualification uses the final s3,k8s release binary with rclone 1.75.0
and AWS CLI 2.34.24. Direct rclone moveto passes for a 4 KiB packed object
and an AWS-created 100 MiB multipart object on memory and RustFS. Each move
uses CopyObject followed by DeleteObject, without payload download/upload
fallback. ETag, SHA256 checksum/type, length and restored bytes remain exact
after source deletion. RustFS provider traces record zero source-payload reads
or writes during both moves; normal encrypted metadata publication still occurs.
A fresh gateway process restores both copied destinations using the unchanged
Kubernetes Lease anchor, with repository initialization disabled. This is a
graceful process-restart check, not crash takeover or retained-provider
qualification. Source-condition mismatch, cross-bucket copy and metadata
REPLACE reject without creating destinations. COPY accepts the client's inert
content/user metadata and default STANDARD storage class; these results do
not qualify arbitrary metadata preservation or other copy extensions.
just fuzz-smoke exercises commit headers/objects, canonical CBOR, both
repository-envelope purposes, recovery bundles, index runs/roots, format roots,
payload packs and detached single-part payloads. It copies raw seeds and frozen
vectors to temporary corpora so fuzzing does not modify the fixtures. Defaults
are 60 seconds per target and a 512 MiB per-target RSS ceiling; use
RS3_FUZZ_SMOKE_SECONDS and RS3_FUZZ_RSS_LIMIT_MB for an explicit local override.
Runtime replay, recovery-history traversal and adoption require strictly increasing parent-to-child publication timestamps. New recovery-history acceptance also checks freshness against sampled time and declared uncertainty. Signed timestamps are not a current-time oracle; codec and runtime checks do not replace retained-provider and complete recovery qualification.
September 9 history-efficiency qualification¶
At 3e879cb, the source-bound recovery fixture passes against disposable RustFS
and Kind with a real Kubernetes Lease. It covers a 4 KiB packed value, a 100 MiB
multipart value with part replacement, checkpoint/compaction after 256 churn
writes, selected historical recovery, exact-version COMPLIANCE protection for
the selected commit and observed restore sources, legal-hold enforcement on an
unretained control, maintenance with reclamation disabled and no provider DELETE,
AWS/rclone copy-out and range reads, and graceful readonly/writer restarts without
changing the accepted anchor.
The earlier standard just preview-gate-v3-retained-local attempt stopped at RustFS
readiness because this host's Docker forwarded port timed out while the same
health endpoint returned HTTP 200 inside the container. The recovery fixture
therefore used host-network RustFS and a TLS relay into the temporary Kind API.
Its first attempt failed when the relay's inherited 10-second socket timeout
closed idle connections; the gateway stopped after Lease renewal exceeded its
30-second duration. A copied fixture removed the relay's idle timeout while
keeping connection and client request deadlines. The successful retry uses the
same frozen server binary, records the helper hash and cleans all owned resources.
The failed attempts remain in local evidence.
A separate host-network run passes the direct retained-version storage contract and isolated GC rehearsal. It deletes one unprotected version, preserves one protected candidate and renews three exact dependencies under the explicitly unenforced guard of a disposable prefix. The rehearsal uses one-day GOVERNANCE retention; it does not test governance-bypass IAM.
The qualification receipt records source/binary/result hashes, passed checks, failed attempts and scope boundaries.
After host Docker connectivity was repaired, the complete standard
just preview-gate-v3-retained-local passes at 25e0747. It uses the normal
Docker/Kind network path and covers six retained-storage contract tests, the
isolated GC rehearsal, a fresh Helm gateway installation with 30-day COMPLIANCE
retention, readiness, S3 smoke, AWS CLI/rclone/mc/restic round trips, and v03
Lease-anchor verification. The temporary cluster is deleted after success. Source
files remained unchanged during the run, and the receipt binds the gateway image
and logs to that revision. The earlier Docker blocker is resolved.
These are bounded local regression results. They do not qualify an external provider, governance-bypass IAM, crash takeover, lifecycle upgrades or an elapsed 30-day retention interval. The separate 100,000-write comparison uses a controlled in-memory provider and simulated time; its scale must not be attributed to this real-provider fixture.
Important Lanes¶
| Lane | Command | Scope |
|---|---|---|
| Default | just check |
Format, clippy, workspace tests. |
| Preview local gate | just preview-gate-local |
Default checks, S3-feature checks, and dependency policy checks. |
| Storage S3 | just integration-s3-container |
Storage contract against a disposable S3-compatible provider; just integration-s3-local --mode provided runs it against an existing endpoint. |
| Gateway S3 | just integration-s3-gateway |
Gateway S3 operations through the repository path. |
| Local v3 nightly gate | just preview-gate-v3-nightly |
Scheduled or release-candidate gate: S3 feature checks plus v3 S3 tooling, Kopia, Kubernetes Lease, Velero dynamic-PVC gateway-restart, and Velero/Postgres lanes against disposable local backends. |
| Live v3 preview gate | just preview-gate-v3-live <bucket> <endpoint> <region> |
Consolidated retained-backend gate. Generates fresh sub-prefixes and runs v3 provider conformance, Gateway S3, Kopia, Kubernetes Lease, Velero dynamic-PVC gateway-restart, and Velero/Postgres lanes. |
| Live v3 provider conformance | just check-provider-live <bucket> <endpoint> <region> <fresh-prefix> |
Runs rs3 check-provider for the retained-version/Object Lock profile and emits JSON evidence for admin posture or release artifacts. |
| Live v3 Gateway S3 | just integration-s3-gateway-v3-live --backend-bucket <bucket> --endpoint-url <endpoint> --region <region> --backend-prefix <fresh-prefix> |
v3-preview gateway smoke against an existing retained S3-compatible backend, including mc, default rclone lsf, and backend key privacy checks. |
| Kopia | just integration-kopia-gateway |
Real Kopia create, snapshot, and restore through the gateway. |
| Live v3 Kopia | just integration-kopia-gateway-v3-live --backend-bucket <bucket> --endpoint-url <endpoint> --region <region> --backend-prefix <fresh-prefix> |
Real Kopia create, snapshot, and restore through a v3-preview gateway against an existing retained backend. |
| Kubernetes Lease | just integration-k8s-gateway |
Image build, kind cluster, Helm install, readiness and S3 smoke, including verification that v03 anchor annotations and repository format generation 3 are written. |
| Kubernetes lifecycle | just integration-k8s-gateway-lifecycle |
Generated-salt lifecycle test: restarts the gateway, repeats the same Helm command against the live writer without requalification, and upgrades to a rebuilt fixture that requalifies once, reading the earlier object back after every step. The upgrade uses a synthetic revision to invalidate evidence; it does not qualify a second real source revision or assert fail-fast retry ordering. |
| Velero/Kopia | just integration-velero-kopia-smoke |
Velero node-agent/Kopia backup and restore smoke. |
| Live v3 Velero dynamic PVC | just integration-velero-kopia-dynamic-pvc-gateway-restart-v3-live --backend-bucket <bucket> --backend-endpoint-url <endpoint> --backend-region <region> --backend-prefix <fresh-prefix> |
Velero/Kopia dynamic-PVC backup and restore through a v3-preview gateway after a gateway restart, against an existing retained backend. |
| Live v3 Velero Postgres | just integration-velero-kopia-postgres-v3-live --backend-bucket <bucket> --backend-endpoint-url <endpoint> --backend-region <region> --backend-prefix <fresh-prefix> |
Velero/Kopia Postgres backup and restore through a v3-preview gateway against an existing retained backend. |
| Preview release gate | just preview-gate-release |
v3 Kopia gateway, Velero dynamic PVC gateway-restart in normal write mode, and Velero Postgres smoke. The restart lane rejects any gateway container restart during the forced rollout. |
| Velero strict restore-readonly | just integration-velero-kopia-dynamic-pvc-restore-readonly-smoke |
Incident-restore behavior: restored bytes verify, Velero artifact writes are denied, and backend writes stay at zero during restore. |
| Lightweight perf smoke | just perf-s3-gateway --format jsonl |
Small gateway scenario metrics and amplification. |
| Gateway perf smoke | just perf-s3-gateway --objects 32 --object-size 262144 --reads 64 --range-len 4096 --commit-batch-items 8 --concurrency 8 --format jsonl |
Release-profile local gateway run for current v03 request cost, throughput, and amplification. |
| Docker-free HTTP gateway perf | cargo run -p xtask --features containers -- perf --backend gateway-memory --scenario full-read --object-size 268435456 --reads 3 --gateway-build-profile release --format jsonl |
Spawns a release gateway, drives its real S3 adapter, reports the child process rather than driver RSS, and retains exact generic-storage request/byte counters. gateway-filesystem is also available for non-multipart local lanes. Memory-backend RSS includes stored ciphertext. |
| 10k object scale gate | just perf-scale-10k |
Three release-binary committed-write runs. Every run publishes a final signed checkpoint, discards writer state, reloads through a new repository instance, checks exact list cardinality, reads the first, middle, and last payload, and enforces the 1.50x lifetime write gate, 30-second reload ceiling, 1.04x cold-read byte amplification, one backend request per sentinel read, and at most 255 recovered active index runs. Runs on every CI change. |
| 10k automatic compaction gate | just perf-scale-10k-compaction |
The same scale gates with 16-item batches, concurrency 16, and 1 KiB objects to amortize the extra per-commit overhead while retaining the 1.50x write limit. Every run must observe at least one accepted active-run count decrease before its final checkpoint. JSONL reports observed_compactions, a lower bound for arbitrary concurrency and a per-pass count for this one-run-per-wave lane. Runs next to the ordinary 10k PR gate. |
| 100k object scale gate | just perf-scale-100k |
Release-binary 4,096-item low-amplification bulk tier with the same final-checkpoint, recovery, cardinality, amplification, direct cold-read, and active-run-count checks. |
| 270k bounded-compaction evidence | just perf-scale-tier 270000 |
Crosses the 256-run watermark with the 1,024-item bulk tier and applies the lifetime amplification, 180-second elapsed, 4 GiB peak-RSS, recovery, cold-read, and active-run gates. The final post-remediation sample completed in 11.655 s at 758,521,856 B peak RSS, 1.505740509x amplification, and 140 recovered runs. |
| 1M object scale gate | just perf-scale-1m |
Manual in-memory high-capacity tier with the same checks. Historical revision 8f99a8a passed three wire-v6 4,096-record runs at 44.639-45.553 s, 1,680,826,368-1,681,162,240 B process peak RSS including the in-memory backend, 1.268292436x amplification, 245 recovered runs, and exact sentinel reads. |
| Fresh-process filesystem scale gates | just perf-scale-fs-10k <root>, just perf-scale-fs-100k <root>, just perf-scale-fs-1m <root> |
Runs release writer and reader processes over an explicitly selected filesystem. Clean revision f4d05b0 passed three 1M runs at 955,781,120-956,370,944 B writer RSS, 1,009,922,048-1,010,692,096 B reader RSS, 5.324-5.482 s recovery, 1.268284240x writes, exact cardinality, 245 active runs, and exact sentinel reads. This host was not the pinned release runner. |
| Complete candidate scale sample | just preview-gate-scale-candidate <root> |
Runs the three-sample path-length matrix plus fresh-process 10k, 100k, and 1M filesystem tiers. Use the documented pinned runner and retain <root> with the release record. Reports bind the source revision and mark dirty builds. |
| Kopia measured matrix | cargo run -p xtask --bin xtask --features containers -- integration kopia-measured-matrix --runs 3 --profile-set larger-restores --gateway-build-profile release --enforce-regression-budgets |
Release-grade Kopia restore comparison against the straight RustFS proxy baseline with current gateway defaults. |
| Revision-bound Kopia candidate profile | just perf-kopia-profile-candidate <profile> |
Three alternating direct/gateway pairs for one bounded real-client profile, with the exact clean or -dirty source revision embedded in summary.json. Run all five release-process profiles. |
Expensive lanes emit artifacts under .local/integration/ by default.
The scale recipes use three runs unless RS3_SCALE_GATE_RUNS is set to a
different positive integer. A scale run is successful only after new-instance
reload, cardinality verification, and sentinel payload reads; write throughput
alone is not a recovery or release result. The underlying harness options
--max-cold-read-amp and --max-cold-read-requests-per-read require
--verify-reload. The --max-active-index-runs option also requires reload
verification and checks the authenticated recovered catalog, not writer memory.
The in-memory scale recipes also enforce --max-elapsed-seconds 180,
--max-reload-elapsed-seconds 30, and --max-peak-rss-bytes 4294967296.
The filesystem recipes use the same write ceiling, a conservative 180-second
complete reader-verification ceiling, and separate 4 GiB writer and reader RSS
ceilings. Release timing claims still require the documented pinned runner.
Peak RSS comes from the harness process high-water mark, so an over-budget
attempt fails even when all correctness checks pass. The harness prints the
measurement before returning an aggregated gate error, so one expensive run
retains every available failure and its JSON evidence.
After recovery, the harness resets its observation window,
reads the first, middle, and last object, and fails unless those reads use only
the permitted exact range GET requests and bytes. JSONL and TSV output report
the cold-read counters separately from recovery.
These lightweight lanes qualify write amplification, bounded recovery, direct cold sentinel reads, sentinel correctness, and the recovered active-run budget. Automatic compaction performs bounded passes beginning at 256 active runs, each selecting at most 256 active runs across levels 0 and 1. Each window is also capped at 131,072 mutations and 16 MiB of stored run sections; catalog-only selection favors more runs and lower rewrite cost. Accepted blinded-key generations identify obsolete upserts after source validation; winning tombstones and references outside the window remain. A missing guard or fully validated nonreducing bounded plan may defer and retry at later 64-run boundaries before pausing at 896. Configured-guard, corruption, storage, anchor, and other compaction errors poison immediately. The current 4,096-record low-amplification 1M lane finishes at 245 runs without crossing the trigger. The earlier 1,024-record adversarial lane crossed six compaction windows and recovered 233 runs. Keep both lanes; one does not prove the other. Release timing still requires the documented pinned runner.
just test-churn-scale runs 1,024 overwrite/delete/recreate cycles against an
independent fixed-key model. Every cycle asserts one active run after
compaction and fresh recovery, one replay commit, at most 32 compaction GETs,
12 HEADs, 64 KiB read and 16 KiB written. Signed section spans prove zero
payload reads or writes during compaction. Guarded exact-version GC reclaims
obsolete objects while fresh current and protected historical roots still
restore. A 64-cycle variant runs in just check. Planner regressions also cover
full older shards that would otherwise block later churn, tombstone masking
and invalid obsolete source facts. These deterministic memory-store checks do
not qualify retained providers or production timing.
The filesystem lane proves a fresh application process with empty rs3 caches;
it does not claim a cold kernel page cache. Use a pinned local-disk mount rather
than /tmp, preserve every generated run directory, and record any runner-level
cache-control procedure separately.
The current gateway no longer has a v1 or v2 repository runtime. Commands with v3
in their names keep their existing harness names, but they exercise the only
supported repository format.
S3 Provider Qualification¶
Do not infer production support from S3 compatibility alone. A live provider trial must choose and verify one storage safety profile:
atomic-create: a secondPutObjectwithIf-None-Match: *for an existing current object must fail instead of creating a new version.retained-version: Object Lock/versioning must return provider version IDs for retained writes, block version deletion before expiry, and support exact-version reads after a newer latest version exists. Retained multipart completion must also return a protected exact version. This profile requires read-after-write consistency for the completed retained version, because the gateway verifies multipart postconditions immediately and fails closed when it cannot read the completed version.
The shared memory, filesystem and live S3 contract checks string prefixes, complete inventory under raw-member page limits, and consistent range errors. The versioned tests also delete an unprotected current value above a protected older version, verify that the older exact version remains readable and cannot be deleted, and count delete markers when paging the inventory. Multipart tests check that duplicate or out-of-range internal parts fail without changing the accepted upload. These internal parts are distinct from client-facing S3 multipart sessions. SDK request fixtures verify that empty or missing-part completion attempts abort the internal upload without publishing it, including when cleanup is denied. Cleanup remains best effort; these tests do not prove cleanup after process cancellation.
SDK response fixtures cover missing or non-advancing pagination cursors, missing completion flags, delete-marker-only pages and exact full-read lengths. A successful container run is local provider evidence; repeat these tests on the configured provider before qualifying a deployment.
HEAD before PUT is not the production fallback for create-only writes. It is
non-atomic and only useful for a deliberately degraded compatibility mode, which
is not part of the production-preview contract.
Use --qualification-profile retained-version --object-lock for providers
where retained version IDs are the safety primitive. The command
rejects retained-version without --object-lock because the profile is only
meaningful when the retention and exact-version checks run:
just integration-s3-local --qualification-profile retained-version --object-lock
For retained gateway qualification, use the guarded Kubernetes fixture:
just preview-gate-v3-retained-local
This runs direct Object Lock/exact-version checks, then a 30-day COMPLIANCE repository with journaled initialization, a Kubernetes Lease, and AWS CLI, rclone, mc and restic round trips. It uses disposable local providers. It does not qualify an external provider or demonstrate an elapsed retention window.
The legacy preview-gate-v3-live, integration-s3-gateway-v3-live and
integration-kopia-gateway-v3-live recipes still include retained local
memory-anchor launchers. Those launchers cannot meet the current recovery
maintenance-guard requirement and are not current end-to-end qualification
commands. For an external backend, qualify the provider and exercise backup
and restore through a journaled Kubernetes deployment as described in
Production Preview. External-provider qualification
remains a separate requirement.
For governance mode, review IAM or bucket policy so normal gateway credentials
cannot bypass governance retention. RS3_GOVERNANCE_BYPASS_REVIEWED=true records
that operator assertion; the live Object Lock probe does not establish it.
integration k8s-gateway --keep-cluster retains the kind cluster for log and
resource inspection. Its disposable S3 backend is still removed, so the kept
gateway is not usable for further requests.
Privacy Tests¶
Features that handle logical names should verify:
- plaintext paths do not appear in backend object IDs
- plaintext paths do not appear in unauthenticated repository metadata
- logs, traces, metrics labels, and errors remain redacted
- generated Kubernetes-like names do not leak into backend storage
Rollback Tests¶
Rollback-sensitive changes should cover:
- latest anchored commit accepted
- stale commit rejected after anchor advance
- anchor digest mismatch rejected
- missing configured anchor fails closed
- commit write succeeded but anchor advance failed
- recovery accepts an already-written identical commit object
Retention Tests¶
Retention and Object Lock work should cover:
- native conditional create rejects duplicate create-only writes for the
atomic-createprofile - retention extension never shortens existing retention
- retained writes fail when the provider does not return version IDs
- retry after an anchor failure remains safe when a retained-version provider appends a same-key version instead of rejecting duplicate create-only writes
- exact-version reads return the anchor-bound object after a newer latest version exists
- legal hold blocks destructive cleanup
- provider cannot extend retention
- governance bypass is not present in normal gateway credentials
- garbage collection does not remove reachable or retained objects
The ignored live S3 Object Lock tests exercise provider version IDs against a real retained bucket. They are the lane for S3-compatible providers where local RustFS/container tests cannot verify retained version-addressed reads.
Performance Tests¶
Performance changes should update the measured matrix when they affect:
- payload segment size
- range
GET - namespace listing
- commit batching
- S3 request body handling
- backend storage adapters
- metrics collection overhead
- adaptive payload segment sizing
- decrypted segment cache behavior
Use release-profile gateway runs for performance claims. Debug builds are useful for development but distort medium and larger payload timings.