Compare commits

...

1384 Commits

Author SHA1 Message Date
Zhengchao An 4d0af8a330 refactor(ecstore): migrate the bucket bandwidth monitor into InstanceContext (Phase 5 Slice 10) (#4489) 2026-07-08 22:44:52 +08:00
overtrue 5a46819589 refactor(ecstore): migrate lifecycle expiry/transition state into InstanceContext (Phase 5 Slice 9)
Phase 5 Slice 9 (backlog#939): move the lifecycle expiry state and transition
state — two stateful service handles defined in bucket_lifecycle_ops — into the
per-instance InstanceContext, following the lazy-materialization pattern from
Slices 7-8.

- InstanceContext gains `expiry_state: OnceLock<Arc<RwLock<ExpiryState>>>` and
  `transition_state: OnceLock<Arc<TransitionState>>`, each materialized lazily
  on first access (both constructors are cheap — empty worker vecs/channels,
  workers started later — so lazy init reproduces the eager static's
  create-once-then-share).
- The lifecycle owner helpers `get_global_expiry_state` / `get_global_transition_state`
  and the runtime-source handles `expiry_state_handle` / `transition_state_handle`
  keep their signatures and route through the current instance's context; the
  two statics (and the now-unused lazy_static import) are removed. The scanner
  boundary and lifecycle-state arch guards still pass.

Tests: expiry/transition state are stable within an instance (same Arc) and
distinct across instances.

Verification: cargo test -p rustfs-ecstore (20 instance-context tests green),
cargo clippy -p rustfs-ecstore --all-targets (clean), make pre-commit (pass).

Refs: backlog#939 (Phase 5, Slice 9).
2026-07-08 22:16:11 +08:00
houseme 87044b2378 fix(obs): tighten low-risk telemetry correctness (#4483)
Refs rustfs/backlog#1007
Refs rustfs/backlog#986

- respect explicit log-target directives when injecting noisy-crate suppressions
- fix daily rotation day-boundary checks and add a short retry cooldown after failed rotations
- preserve recorder metadata across label variants and serialize gauge updates before export
- correct profiling target_env reporting, trace all=true parsing, cleaner accounting/docs, and legacy system interval rounding

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 14:06:19 +00:00
Zhengchao An dee8e4e639 test(e2e): make security boundary tests assert real outcomes (#4466)
* test(e2e): make security boundary tests assert real outcomes

* fix(e2e): use expect_err to satisfy clippy err_expect lint
2026-07-08 22:01:46 +08:00
Zhengchao An 322b585f71 refactor(ecstore): move deployment id, endpoints, and tier config into InstanceContext (#4465) 2026-07-08 21:50:56 +08:00
houseme 8fc637fb14 perf(ecstore): run the short EC encode inline instead of block_in_place (#4484)
Each erasure block runs its Reed-Solomon encode through
tokio::task::block_in_place on the multi-threaded runtime. That parks the
worker and asks the scheduler to relocate other tasks, but the encode itself
is only ~110µs per 1MiB block (p99 ~542µs) — the profiling in backlog#932
flagged the scheduling disturbance as comparable to the compute it guards.

Call the encode closure inline on the multi-threaded runtime instead. The
CurrentThread (and any other) flavor keeps spawn_blocking so the sole executor
thread is never blocked and block_in_place's multi-thread-only requirement is
respected. Applied to both ingest paths (encode_block / Vec and
encode_block_bytes_mut / BytesMut); no change to encode output, quorum,
shutdown, or error handling.

Adds encode_works_on_multi_thread_runtime to cover the previously-untested
multi-threaded arm for both ingest paths, asserting streaming and batched
encode produce identical shard bytes.

This is the low-risk, correctness-neutral item that backlog#932's adversarial
verification recommended splitting out and doing first; the larger per-writer
pipeline restructure it belongs to stays gated on a Linux multi-disk baseline.

Refs: rustfs/backlog#932 (HP-11), rustfs/backlog#936

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 13:38:00 +00:00
houseme e7cfc510ec fix(obs): correct gpu collector coverage (#4482)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 13:35:49 +00:00
houseme 2157470224 ci(perf): warp A/B relative-budget gate for the hotpath series (#4480)
* ci(perf): warp A/B relative-budget gate for the hotpath series

performance.yml only uploads a samply profile and a cargo-bench artifact with
no pass/fail, so a 26x-class write-path regression like #4221 or the GET perf
churn would sail through CI and only surface in customer load tests. This adds
the missing gate — the acceptance surface every queued HP change (#922/#923/
#925/#927/#930/#932) needs before its default can flip.

- scripts/hotpath_warp_ab_gate.sh: applies the relative budget to the
  baseline_compare.csv that run_object_batch_bench_enhanced.sh already emits
  (it computes deltas but never gates). A metric regressing past --fail-pct
  fails, past --warn-pct warns; reqps/throughput are higher-is-better, latency
  lower-is-better. --allow-regression downgrades a FAIL to an exempted WARN so
  a deliberate correctness cost (e.g. #4221) is recorded, not blocked
  (rustfs/backlog#935 correction 1). Unit-checked across pass/warn/fail/exempt.
- scripts/run_hotpath_warp_ab.sh: single-host baseline-binary vs candidate-
  binary A/B over {put-4mib, get-4mib, mixed-256k} x {drive-sync on, off},
  reusing the enhanced bench as the warp driver and the single-node local-disk
  lifecycle. Has --dry-run; warp is assumed pre-installed as elsewhere in
  scripts/. Drive-sync on/off keeps a sync-semantics change from being masked
  by nosync numbers.
- .github/workflows/performance-ab.yml: nightly on main (post-merge detection)
  plus opt-in pre-merge via the `perf-ab` label; `perf-deliberate-tradeoff`
  runs the gate with --allow-regression; posts the gate table as a PR comment
  and uploads the run. A Linux runner answers "do the macOS conclusions hold".

The gate logic is unit-validated with synthetic compare CSVs; the orchestrator
and workflow are shellcheck- and --dry-run-validated. The first real warp
measurement belongs on the Linux runner (no warp/multi-disk rig locally).

Refs: rustfs/backlog#935 (HP-14 warp A/B gate, item 4), rustfs/backlog#725
(cooled A/B harness precedent), rustfs/backlog#936

Co-Authored-By: heihutu <heihutu@gmail.com>

* ci(perf): pin upload-artifact, add external-cluster A/B mode + runbook

- Pin actions/upload-artifact to the repo-standard full-length SHA (# v6);
  the previous @v4 float tripped the workflow-pin guard.
- Add an external deployment mode to run_hotpath_warp_ab.sh so warp can target
  an already-running cluster instead of a throwaway single-node server:
  --endpoint selects the cluster and --deploy-hook runs between phases to swap
  in the phase's binary and drive-sync config (context passed via
  HOTPATH_AB_PHASE / HOTPATH_AB_BINARY / HOTPATH_AB_DRIVE_SYNC). This maps onto
  the team's ansible harness (cargo zigbuild -> ansible rustfs-manage --tags
  stop,config,binary-copy,start -> warp).
- Restructure the matrix loop to bring a deployment up once per (phase,
  drive-sync) and run all three workloads against it, instead of restarting per
  workload — fewer server starts / cluster redeploys. Baseline medians are read
  from a deterministic path, dropping the bash-4-only associative array so the
  script (and its --dry-run self-check) runs on macOS bash 3.2 too.
- Add docs/operations/hotpath-warp-ab-runbook.md tying local mode, the ansible
  external mode, and the budget/exemption together.

Verification: shellcheck clean; --dry-run in both modes prints the expected
4 deployments x 3 workloads and passes exactly 6 compare CSVs to the gate;
check_workflow_pins.sh, check_doc_paths.sh, and make pre-commit all pass.

Refs: rustfs/backlog#935, rustfs/backlog#936

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 13:22:44 +00:00
houseme f0bf8cfe03 fix(obs): hide unwired request metrics (#4481)
Refs rustfs/backlog#1006

- aggregate request traffic samples per type to avoid future counter collisions
- keep request schema and collector crate-internal until a production stats source exists
- preserve focused regression coverage for the internal request collector logic

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 13:08:57 +00:00
houseme 0cb76e9420 fix(obs): harden metrics scheduler (#4479)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 13:04:42 +00:00
houseme 54872d52dc test(ecstore): deterministic rename_data crash-consistency harness (#4478)
The rename_data commit sequence is the highest-risk durability path in the
store, and the HP-1/HP-4/HP-5 work (fsync coalescing, group commit, relaxed
durability tiers) all need a standing gate that proves a power loss mid-commit
can never leave a mixed or corrupt object. There was one graceful-rollback
failpoint but no crash-consistency coverage.

Add a deterministic harness that models a hard power loss — the commit
sequence stops dead at the armed step with no in-process cleanup — and then
reopens the disk to assert the raw on-disk state is coherent:

- Two pre-commit injection points, RenameDataCrashPoint::{AfterDataRename,
  AfterBackupBeforeMetaCommit}, constructed at the real commit-path call sites
  but gated so the production build compiles them to a const-false no-op
  (mirrors the existing should_fail_before_old_metadata_backup pattern).
- A parameterized scenario over {strict, relaxed} durability x {both crash
  points} x {overwrite, fresh object}: seed, stage a replacement, inject,
  reopen, and assert the object reads back as exactly the old version (or does
  not exist when there was no old version) with its data dir intact, never the
  half-committed new one. The un-injected run asserts the commit makes the new
  version visible. Relaxed is held to the same old-or-new invariant as strict
  (only the durability window widens), which is exactly the property the
  durability-relaxation work must not break (rustfs/backlog#878 hard rule).
- Wire an explicit `ecstore-crash-consistency` gate into the destructive
  profile of run_ecstore_validation_suite.sh so it runs in the standing suite
  (coordinates with the #878 destructive profile rather than building a
  parallel one).

Production behavior is unchanged: the injection guards are no-ops outside
tests, and the existing rename_data rollback tests still pass.

Refs: rustfs/backlog#935 (HP-14 crash-consistency harness), rustfs/backlog#896
(test plan), rustfs/backlog#878 (ECStore validation suite), rustfs/backlog#936

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 20:53:49 +08:00
houseme 4fe0789fb1 fix(obs): honor otlp export contracts (#4477)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 12:41:18 +00:00
houseme 47bee8b314 perf(ecstore): read bitrot hash+data in one pass on the shard path (#4475)
* perf(ecstore): read bitrot hash+data in one pass on the shard path

BitrotReader::read issued two reads per block: one read_exact for the
32-byte hash, then a separate loop for the shard data. On the streaming
disk reader (a raw tokio File whose every read is a spawn_blocking
round-trip) that is two dispatches per block. Since the on-disk layout is
a contiguous [hash][data] run, pull both in a single pass into a reused
scratch buffer and split afterwards, halving the per-block dispatch count
on the streaming path. The no-hash path still reads straight into the
caller buffer with no extra copy, and an in-memory Cursor (inline/mmap)
just does a slice copy.

All existing invariants are preserved: a short read of either the hash or
the data maps to UnexpectedEof before and independent of the hash check
(backlog#799 B2), and the hash-mismatch / InvalidData semantics are
unchanged.

Correctness-only change; the per-dispatch latency win is platform
dependent and its default reliance is left to the warp size-bucket
benchmark gate tracked by backlog#935, per the backlog#933 acceptance
note.

Refs: rustfs/backlog#933 (HP-12 item 2), rustfs/backlog#936

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs: reword bitrot test comment to satisfy typos check

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 11:27:13 +00:00
houseme 1eb393cab1 fix(obs): align metrics schema and collector contracts (#4476)
fix(obs): align schema and collector contracts

Refs rustfs/backlog#1005

- align obs schema descriptors with emitted labels and metric types
- fix bucket traffic help text and TTFB bucket descriptor semantics
- add regression tests for drive, network host, bucket, and node bucket contracts

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 11:23:23 +00:00
houseme 3ddade24f2 fix(obs): validate numeric env settings (#4474)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 10:42:09 +00:00
Henry Guo 757f9b3b7b fix(admin): refresh accountinfo bucket usage (#4472) 2026-07-08 18:34:11 +08:00
Zhengchao An 819e1422fb fix(replication): resolve same-destination rules highest-priority first (#4452) 2026-07-08 18:32:09 +08:00
Zhengchao An 9ffedc0e2b fix(keystone): fail closed on EC2 auth and apply configured timeout (#4464) 2026-07-08 18:31:48 +08:00
Zhengchao An 7a33ba5e21 fix(notify): handle processing_events underflow and literal ? matching (#4468) 2026-07-08 18:30:48 +08:00
houseme 718c051ff3 fix(obs): export cluster usage staleness (#4467)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 09:44:05 +00:00
Zhengchao An 3430a7ead6 fix(object-capacity): don't report dirty-subset bytes as the cluster total (backlog#1011) (#4463)
A dirty-subset refresh scans only the dirty disks, so its raw
`CapacityUpdate` carries just that subset's `total_used`/`file_count`.
`update_capacity` recomputed the correct cluster-wide total into a local
variable and wrote it to the cache, but never wrote it back to the
`CapacityUpdate` that `refresh_or_join`/`spawn_refresh_if_needed` return
and publish to joiners. The admin blocking path (`resolve_admin_used_capacity`
-> `refresh_or_join_admin_disks(allow_dirty_subset=true)`) consumes
`update.total_used`, so a single dirty disk in an N-disk cluster made
admin StorageInfo report only the scanned subset's bytes (a large
transient undercount for that request and same-cycle joiners).

`CachedDiskCapacity` also dropped each disk's `file_count`/`is_estimated`,
so subset refreshes could not recompute a correct cluster file count and
would launder an estimated per-disk value into an exact cluster total.

Fix:
- `update_capacity` now reconciles `total_used`, `file_count`, and
  `is_estimated` from the full per-disk cache and returns the corrected
  `CapacityUpdate`.
- `CachedDiskCapacity` stores `file_count`/`is_estimated` per disk;
  `file_count` sums the cache and `is_estimated` is the OR across disks.
- `refresh_or_join` and `spawn_refresh_if_needed` rebind their result to
  the reconciled update so the leader return value and the value
  published to joiners both carry cluster totals.

Tests: extend the subset-refresh test to assert the returned update's
reconciled `total_used`/`file_count`/`is_estimated`, and add a
`refresh_or_join` dirty-subset test asserting the leader returns the
merged cluster total (not the subset sum) and matches the cache.

Refs: https://github.com/rustfs/backlog/issues/1011
2026-07-08 09:33:35 +00:00
Zhengchao An c138265440 refactor(ecstore): migrate the S3 region into InstanceContext (Phase 5 Slice 4) (#4462)
Phase 5 Slice 4 (backlog#939): move the S3 region — a write-once identity
scalar — out of the GLOBAL_REGION process static into the per-instance
InstanceContext, so two instances can serve different regions.

- InstanceContext gains `region: OnceLock<Region>` with `set_region()` /
  `region()`. `set_region` keeps the write-once fail-fast contract: a second
  write panics, exactly as the process global did (not downgraded to a warn).
- global.rs `set_global_region` / `get_global_region` keep their signatures and
  forward to the current instance's context; the GLOBAL_REGION static is
  removed. Single-instance: startup writes the bootstrap context (which the
  ECStore adopts), so reads are unchanged.

Tests: set/get round-trip, two contexts hold distinct regions, and a second
set_region panics (fail-fast preserved).

Verification: cargo test -p rustfs-ecstore (9 instance-context tests green),
cargo clippy -p rustfs-ecstore --all-targets (clean), make pre-commit (pass).

Refs: backlog#939 (Phase 5, Slice 4). Stacked on Slice 3 (#4417).
2026-07-08 09:31:46 +00:00
Zhengchao An 7aa25387ae fix(heal): gate finalize on transient skips and fix progress failed count (#4460)
The erasure-set finalize block only gated completion on failed_objects, so a pass with transient skips (unmet quorum, DiskNotFound, SlowDown, OperationCanceled) but zero hard failures was marked completed, its resume/checkpoint state cleaned up and the per-disk healing marker cleared. That violated the Transient invariant: the skipped versions were never re-healed on a later pass.

Broaden the finalize gate to failed_objects > 0 || skipped_objects > 0 and reuse the existing bounded-retry path (schedule_retry + checkpoint reset_for_retry, returning Err so the caller preserves state and keeps the healing markers). Transient conditions are deferred to the next heal cycle, never hot-retried in place.

Also fix the object/EC-decode heal success paths, which passed object_size as the failed positional arg to update_progress, corrupting objects_failed and the admin-visible success rate; pass 0 instead.

Add heal tests for the transient-skip finalize behavior and a progress test asserting a successful heal reports zero failures.

Refs rustfs/backlog#1033
2026-07-08 09:26:56 +00:00
Zhengchao An 2b33af6e48 fix(trusted-proxies): validate full Forwarded chain and bare IPv6 (#4461) 2026-07-08 09:25:10 +00:00
Zhengchao An ee6f791100 fix(audit,targets): redact credential request headers from audit/notify entries (backlog#963) (#4459)
extract_params_header copied every request/response header verbatim into
the maps that feed audit entries (requestQuery/responseHeader) and
notification events (req_params). Sensitive headers such as
Authorization and X-Amz-Security-Token were serialized in plaintext and
forwarded to external sinks (webhook/kafka/file), leaking long-lived
credentials.

Redact credential-bearing headers at this single chokepoint: the header
name is kept for correlation, but its value is replaced with the shared
REDACTED_SECRET placeholder. A case-insensitive sensitive-header list
covers authorization, x-amz-security-token, x-amz-content-sha256,
cookie, and set-cookie. Non-sensitive headers keep their existing
behavior.

Refs: https://github.com/rustfs/backlog/issues/963
2026-07-08 09:23:05 +00:00
Zhengchao An afa3935ebb fix(object-data-cache): prune identity index on moka eviction (#4458)
Moka evicts entries by TTL, time-to-idle, and capacity-LRU without going through invalidate_object, so the per-object identity index (by_object) never dropped the evicted keys and grew without bound, bypassing the memory cap. Register an async eviction listener that removes truly evicted keys from the identity index, holding only a Weak reference to avoid an Arc cycle.

Refs rustfs/backlog#1031
2026-07-08 09:14:46 +00:00
houseme dea53616a4 fix(obs): correct versions scanned metrics (#4443)
Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-07-08 09:12:02 +00:00
houseme a30a9c0aba fix(obs): align cluster capacity semantics (#4457)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 09:11:01 +00:00
Henry Guo 506cd156bb fix(scanner): scope long walk timeouts (#4376)
* fix(scanner): scope long walk timeouts

* fix(scanner): bound IAM config walks

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-08 17:06:51 +08:00
Zhengchao An 7fb95d4fc0 fix(multipart): lock upload metadata reads and aborts (#4428) 2026-07-08 17:02:42 +08:00
Zhengchao An 686b39bbef fix(notify): report cursor gap when history is evicted instead of silently dropping events (#4446)
`LiveEventHistory::snapshot_since` resumed from the oldest retained event
whenever a consumer's cursor pointed before it, without signaling that the
in-between events had been evicted from the ring buffer. Cursor-based
consumers therefore assumed the returned batch was contiguous with their
cursor and silently lost events.

Add a `gap` flag to `LiveEventBatch`, set when the consumer's cursor is
older than the oldest retained sequence, so consumers can detect the loss
and trigger a full re-sync or alert. Add a regression test that fills and
evicts past the ring-buffer capacity and asserts a lagging cursor reports
`gap = true` (and that contiguous/caught-up cursors do not).

Refs rustfs/backlog#969
2026-07-08 17:02:36 +08:00
Zhengchao An fefa70b31e fix(ecstore): stop ListMultipartUploads from returning one upload past max-uploads (#4447)
fix(ecstore): stop ListMultipartUploads from returning one upload past max-uploads (backlog#954)

The result-collection loop in `SetDisks::list_multipart_uploads` pushed an
upload into the page and only then checked `ret_uploads.len() > max_uploads`,
so each page returned up to max_uploads + 1 uploads and pointed
`next_upload_id_marker` at that surplus entry, violating the S3
ListMultipartUploads max-uploads contract.

Move the cap check before the push (MinIO-style) so a page never exceeds
max_uploads, and derive `is_truncated` from the post-marker cursor position
(`upload_idx < uploads.len()`) rather than comparing the page length against
the full listing length. The old length comparison mis-reported truncation on
the final page whenever a marker had skipped earlier entries, which prevented
marker-based pagination from terminating.

Add a regression test that starts more in-progress uploads than a page holds
and asserts a single page returns exactly max_uploads with a correct
next-marker, that the exact-boundary case is not marked truncated, and that
paginating one upload at a time enumerates every upload once with no loss,
duplication, or non-termination.

Incidental: add the missing `ctx` field to the `new_multipart_lock_test_store`
cfg(test) helper so the ecstore test build compiles after the #4413/#4437
merge collision.

Refs: https://github.com/rustfs/backlog/issues/954
2026-07-08 17:02:28 +08:00
Zhengchao An d5687e1693 fix(policy): compare NotResource in Statement equality (#4454)
Statement equality drives merge/dedup of policy statements. Omitting NotResource let semantically-distinct statements be treated as duplicates and dropped, which can shrink Deny coverage and escalate privileges. Compare not_resources too and add regression tests.

Refs rustfs/backlog#1028
2026-07-08 17:02:20 +08:00
Zhengchao An 04bf2e7b99 fix(lifecycle): use total order to select eval_inner event (#4455) 2026-07-08 17:02:09 +08:00
Zhengchao An 7e2e553981 fix(signer): use real timestamp and standard base64 for SigV2 (#4456) 2026-07-08 17:02:01 +08:00
houseme 92c8c6db75 perf(ecstore): Linux O_DIRECT shard-write path off the commit critical section (#4411)
`create_file` writes erasure shard / multipart part data through the page
cache, so the commit-point `sync_dir_files` fdatasync in `rename_data` has to
flush the whole shard's dirty pages (profiling: create_file avg 67.52ms vs
1.51ms nosync; BitrotWriter::write p95 40.86ms), and concurrent PUTs on one
device stall each other's writeback inside the rename critical section.

Add an opt-in Linux O_DIRECT streaming writer that reuses the direct-io read
infrastructure (statx DIOALIGN probe, aligned bounce buffer, supported latch,
one-time fallback log). Incoming bytes are staged into an aligned buffer;
full aligned batches and the shutdown tail are flushed on the blocking pool
(same offloading posture as the buffered writer it replaces). The
sub-alignment tail is written buffered after clearing O_DIRECT via fcntl
(MinIO's recipe). Shard bytes reach the device during the write phase, so the
unchanged commit-point `sync_dir_files` fdatasync degrades to a cheap
metadata/device FLUSH instead of flushing ~2MiB of dirty pages.

Gated by `RUSTFS_OBJECT_DIRECT_IO_WRITE_ENABLE` (default false), Linux-only,
and durability-neutral: `sync_dir_files` still fdatasyncs every shard at the
commit point, so the strict/relaxed/none durability tiers keep their exact
guarantees. Filesystems that reject O_DIRECT (tmpfs, overlayfs, 9p) latch the
path off and fall back to buffered writes; an O_DIRECT open/write EINVAL is
never surfaced as `InvalidInput` (which `to_file_error` maps to `FileNotFound`
and would masquerade as a missing shard, triggering a spurious EC rebuild).

Unit tests cover the env gate, the alignment/staging-capacity and tail-split
math, an end-to-end `create_file` round trip across sizes crossing the
alignment and multi-batch boundaries (buffered fallback on macOS / O_DIRECT on
a block device), and the writer state machine over a plain file on Linux. Real
O_DIRECT throughput on Linux NVMe (ext4/xfs) is deferred to the azure 4-node
bench per the issue's GO/NO-GO gate; the development host is macOS and cannot
exercise O_DIRECT.

Refs: rustfs/backlog#927, rustfs/backlog#936

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:39:43 +08:00
houseme 021c955c21 fix(obs): report live replication backlog (#4448)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:39:06 +08:00
houseme 13e48d93aa perf(ecstore): support per-bucket durability tier overrides (#4407)
perf(ecstore): per-bucket durability tier overrides (HP-5 phase 2)

Let a bucket override the process-wide RUSTFS_DURABILITY_MODE with its own
strict/relaxed/none tier, stored as a durability.json extension entry in the
bucket metadata file and resolved at commit points via effective_durability.
System-critical buckets (.rustfs.sys, .minio.sys) can never carry an override
and stay pinned to strict; the legacy full-off switch keeps its historical
semantics and per-bucket overrides do not apply under it. Overrides are
published and cleared through the existing bucket metadata cache-invalidation
path, and an admin GET/PUT handler exposes the configuration. Default behavior
is unchanged: with no override a bucket follows the global mode, which defaults
to strict and stays byte-for-byte identical to before.

Refs: https://github.com/rustfs/backlog/issues/938, https://github.com/rustfs/backlog/issues/936

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:33:49 +08:00
Zhengchao An 09e6983175 fix(object-capacity): use u128 intermediate to stop sampling estimate overflow (#4444)
fix(object-capacity): use u128 intermediate to stop sampling estimate overflow (backlog#1012)

The three sampling extrapolation sites in scan.rs computed
`overflow_sampled_bytes.saturating_mul(overflow_count) / sampled_count`,
multiplying in u64 before dividing. On very large disks the product exceeds
u64::MAX and saturates to a constant, so dividing by a sampled_count that grows
with disk size yields an estimate that monotonically shrinks — bigger disks
report less capacity. It only sets is_estimated, with no alert.

Extract a shared `estimate_overflow_bytes` helper that performs the
multiplication in u128, divides, then clamps to u64::MAX, so the intermediate
product can never overflow. It also guards division by zero with `.max(1)` even
though callers only enter the branch when sampled_count > 0.

Adds unit tests covering the realistic ~105 TB regression scenario (asserting
the inputs actually overflow u64 and the fixed estimate dwarfs the saturated
value), monotonic scaling across disk sizes, extreme-value clamping, small
non-overflowing inputs (unchanged), and the zero-sampled-count guard, all
compared against a u128 reference implementation.

Refs: https://github.com/rustfs/backlog/issues/1012
2026-07-08 16:30:43 +08:00
Zhengchao An a84d7ca0b3 fix(targets): supervise and restart the MQTT event loop after fatal errors (#4445)
The MQTT target spawned its rumqttc event loop exactly once through a
`OnceCell`. When the loop exited on a fatal protocol error, the finished
`JoinHandle` stayed in the cell, so `init()` never respawned it. The target
then went permanently silent: `publish` calls kept enqueuing but nothing was
ever delivered, while the target still looked healthy.

Replace the one-shot spawn with a supervisor task. The `OnceCell` now guards
a single supervisor that runs one event-loop session at a time and, when a
session exits, rebuilds the client and event loop from the latest
`MqttOptions` after an exponential backoff (1s..30s). A session that connected
resets the backoff so a transient drop reconnects promptly; repeated immediate
failures back off to avoid a reconnect storm. Cancellation from `close()` is
handled by the supervisor dropping the in-flight session future, so no
per-session cancel channel is needed.

Rebuilding options per session also keeps TLS hot-reload working across
reconnects. The backoff policy is a pure function and the supervisor loop is
generic over the session runner, so both are unit tested without a broker:
one test asserts the session restarts repeatedly until cancelled, another that
a live session stops promptly on cancel.

Refs: https://github.com/rustfs/backlog/issues/972
2026-07-08 16:30:16 +08:00
houseme f6433ebb8b fix(obs): drop placeholder drive series (#4440)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:29:48 +08:00
yihong bef276611b fix: make precommit all happy (#4451)
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
2026-07-08 16:28:44 +08:00
houseme c1cc0d189f fix(obs): retire stale bucket and target series (#4434)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:23:25 +08:00
houseme acb1b765db fix(obs): export resettable metrics as gauges (#4432)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:23:11 +08:00
houseme d7b55c9762 fix(obs): honor profiling export contracts (#4433)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:22:47 +08:00
houseme 5aa25a3047 fix(obs): reuse network snapshots across ticks (#4431)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:22:35 +08:00
houseme 8df474b41b fix(obs): shut down global telemetry guard (#4430)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:22:23 +08:00
houseme e0a46e940c fix(obs): drain cleaner results inside scope (#4429)
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 16:21:52 +08:00
Zhengchao An 183c1d8cb4 fix(targets): disable HTTP redirects on webhook delivery client (#4420)
fix(targets): disable HTTP redirects on webhook delivery client (backlog#974)

The webhook reqwest client used the default redirect policy (follows up to
10 redirects). Even though the configured endpoint is validated against
outbound-egress rules, a malicious or compromised endpoint could return a
3xx response to bounce the outbound request to an internal address (e.g. the
cloud metadata service at 169.254.169.254), bypassing that validation (SSRF).

- Set `.redirect(reqwest::redirect::Policy::none())` on the delivery client
  so redirects are never followed.
- Treat a 3xx response as a delivery failure (error carries the status code)
  instead of silently succeeding.
- Add a regression test that stands up a loopback server returning a 302 to
  an internal metadata address and asserts the client surfaces the 3xx rather
  than following it.

Refs: https://github.com/rustfs/backlog/issues/974
2026-07-08 16:05:37 +08:00
houseme f968129945 fix(obs): stop exporting fake cpu categories (#4439)
Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-07-08 16:05:11 +08:00
Zhengchao An b06f3df6b6 fix: repair main build (swift clippy assert + ecstore multipart test ctx) (#4441)
* fix(swift): replace assert!(false) with panic! in expiration_worker test

clippy::assertions_on_constants fails the swift clippy CI job under -D warnings.

* fix(ecstore): add missing ctx field to multipart lock test store

The multipart list-parts lock test (#4437) constructs ECStore without the
ctx field added by the Phase 5 InstanceContext work (backlog#939). Both
landed on main independently, leaving a semantic conflict that breaks the
ecstore lib-test build (E0063). Adopt the process bootstrap context,
matching the existing bootstrap_ctx() test in store/mod.rs.
2026-07-08 08:01:12 +00:00
Zhengchao An 93ffbdb9bd fix(ecstore): lock multipart part listings (#4437) 2026-07-08 15:02:26 +08:00
Zhengchao An a48bc89cdc fix(ecstore): lock batch object deletes (#4435)
* fix(ecstore): lock batch object deletes

* fix(ecstore): honor no_lock in batch deletes
2026-07-08 15:02:22 +08:00
Zhengchao An df9cbc4ed1 fix(ecstore): validate erasure distribution values to avoid shuffle index panic (#4427)
fix(ecstore): validate erasure distribution values to avoid shuffle index panic (backlog#949)

The element values of `erasure.distribution` read from `xl.meta` were never
range-checked. `FileInfo::is_valid()` and `MetaObjectV1::valid()` only verified
`distribution.len()` and the `erasure.index` bound, not that each distribution
value is a valid 1-based slot in `[1, N]`. The metadata shuffle helpers then use
these values directly as `distribution[k] - 1` indices, so a corrupt or
adversarial `xl.meta` carrying a `0` (usize underflow) or a value greater than N
(out-of-bounds) triggers a panic in the shuffle path, turning bad-disk metadata
that erasure coding is meant to tolerate into a request/task crash.

Fix, two layers:
- Validate distribution values at metadata acceptance: `is_valid_distribution`
  now requires the distribution to be a permutation of `1..=N` (correct length,
  every value in range, no duplicates). `FileInfo::is_valid()` and
  `MetaObjectV1::valid()` use it, so `find_file_info_in_quorum` rejects corrupt
  metadata and it surfaces as a clean `ErasureReadQuorum` error instead of an
  index path.
- Defensive indexing in the shuffle helpers (`shuffle_disks_and_parts_metadata`,
  `_by_index`, `_by_index_owned`, `shuffle_parts_metadata`, `shuffle_disks`,
  `shuffle_check_parts`): out-of-range distribution values are skipped via
  `checked_sub(1)` + bounds-checked slot access instead of a bare `idx - 1`
  index, matching the existing pattern in
  `collect_inline_data_shard_fileinfos_by_index`.

Regression tests: `is_valid_distribution`/`is_valid`/`valid` reject distributions
containing `0`, values greater than N, duplicates, and wrong length while
accepting valid permutations; the shuffle helpers no longer panic on corrupt
distributions and preserve output length.

Refs: https://github.com/rustfs/backlog/issues/949
2026-07-08 15:02:16 +08:00
Zhengchao An 2490d4ee22 fix(notify): serialize persisted config read-modify-write to prevent lost updates (#4425)
fix(notify): serialize persisted config read-modify-write to prevent lost updates (backlog#968)

The notify config write path performed a read -> modify -> write over the
persisted server config with no serialization: two concurrent updates could
both read the same base config, apply disjoint changes, and race their
full-config writes. The later write silently overwrote the earlier one,
losing updates (e.g. adding two targets concurrently could drop one) with no
error surfaced.

Guard the whole read -> modify -> write with a process-global tokio Mutex
(NOTIFY_CONFIG_RMW_LOCK). The persisted config is a single process-global
resource, so serializing the RMW makes concurrent updates apply in sequence
and every change is preserved. The lock is only acquired inside
update_server_config and is released before the in-memory reload, so it never
nests with the per-manager config RwLock and introduces no lock-ordering risk.

The RMW sequence is extracted into serialized_read_modify_write with injected
read/save operations so the exact production serialization path is exercised
by a regression test that concurrently adds 32 distinct targets and asserts
all of them survive.

Refs: https://github.com/rustfs/backlog/issues/968
2026-07-08 15:02:10 +08:00
Zhengchao An dbc628f169 fix(audit): propagate dispatch delivery failures instead of swallowing them (#4424)
fix(audit): propagate dispatch delivery failures instead of swallowing them (backlog#962)

AuditPipeline::dispatch and dispatch_batch accumulated per-target save()
errors, logged them, and then unconditionally returned Ok(()). When every
configured target failed the audit entry was lost outright (for store-backed
targets a failed save() means the event was neither delivered nor persisted
for replay), yet callers such as dispatch_audit_log saw success and assumed
the audit trail was intact. Audit is a compliance-critical path, so a total
delivery failure that reports success is a silent data-loss bug.

Both methods now distinguish three outcomes: all targets succeeded (Ok),
partial failure where at least one target accepted the event (log a warning
and return Ok, since the entry is not lost), and total failure where no
delivery succeeded while errors were recorded (record the failure metric,
log an error, and return Err(AuditError::Target) carrying the first target
error). This lets the caller react instead of assuming success.

Adds regression tests with a FailingTarget mock asserting that dispatch and
dispatch_batch return Err on total failure and Ok on partial failure.
2026-07-08 15:02:05 +08:00
Zhengchao An 2adf33fcd2 fix(targets): surface truncated store batches instead of returning partial entries (#4423)
fix(targets): surface truncated store batches instead of silently returning partial entries (backlog#967)

get_multiple deserializes a persisted batch by pulling item_count items
from a concatenated JSON stream. When the stream ended early (a truncated
or corrupted batch file), the partial-read branch only logged a warn! and
broke out of the loop, returning Ok(partial). Callers then treated the
batch as fully delivered, deleted the store entry, and permanently lost
the remaining events with no error.

Return StoreError::Deserialization as soon as the stream ends before
item_count items are read, so the caller keeps the entry on disk and can
retry rather than silently dropping events. The empty-file case already
returned an error and remains covered by this path.

Add a regression test that writes a 3-item batch, truncates the file at a
clean boundary after two items, and asserts get_multiple returns Err and
leaves the batch file in place.

Refs: https://github.com/rustfs/backlog/issues/967
2026-07-08 15:02:02 +08:00
Zhengchao An e0972f19ac fix(scanner): cache object lock config (#4422)
* fix(scanner): cache object lock config

* test(scanner): update lifecycle scanner items
2026-07-08 15:01:57 +08:00
Zhengchao An c0d5f938f2 fix(audit): resolve ABBA lock-order deadlock between registry and stream_cancellers (#4421)
* fix(audit): resolve AB-BA lock-order deadlock between registry and stream_cancellers (backlog#961)

`AuditSystem::runtime_status_snapshot` acquired `stream_cancellers.read()`
before `registry.lock()`, while every other path that holds both locks
(`clear_runtime_targets`, `AuditRuntimeFacade::replace_targets`) acquires
`registry` first. Under concurrency (e.g. admin status polling racing a
config reload) this AB-BA ordering could deadlock the whole audit control
plane with no self-recovery.

Reorder `runtime_status_snapshot` to acquire `registry` before
`stream_cancellers`, unifying the global lock order to
`registry -> stream_cancellers` across all double-lock paths, and add a
multi-threaded regression test that hammers both critical sections with a
timeout guard to fail fast if the ordering regresses.

Refs: https://github.com/rustfs/backlog/issues/961

* docs(audit): reword AB-BA to ABBA to satisfy typos check (backlog#961)
2026-07-08 15:01:53 +08:00
Zhengchao An cf89291898 fix(notify): strip rustfs/minio internal metadata from event userMetadata (#4419)
fix(notify): strip rustfs/minio internal metadata from event userMetadata (backlog#964)

Notification event construction only stripped keys prefixed with
`x-amz-meta-internal-`, which is not a prefix RustFS actually uses. Real
internal metadata lives under `x-rustfs-internal-*` / `x-minio-internal-*`
(xl.meta compat, incl. `x-minio-internal-server-side-encryption-*`) and
server-side-encryption details under `x-rustfs-encryption-*` /
`x-minio-encryption-*`. None of these were filtered, so they leaked into
`s3.object.userMetadata` sent to downstream notification targets
(webhook/MQ) — an information disclosure of SSE/replication/healing
internal state.

Filter via the shared classifiers `rustfs_utils::http::is_internal_key`
and `is_encryption_metadata_key` (case-insensitive, covering both key
flavors), while retaining the legacy `x-amz-meta-internal-*` strip for
backward compat. Genuine user metadata (`x-amz-meta-*`, content-type, ...)
is preserved unchanged.

Adds a regression test asserting both internal prefixes, the SSE internal
key, both encryption prefixes (incl. mixed-case) and the legacy prefix are
stripped while user keys survive.

Refs: https://github.com/rustfs/backlog/issues/964
2026-07-08 15:01:49 +08:00
Zhengchao An f64f0ca91a fix(ecstore): report bitrot verifier mismatches as corrupt (#4414) 2026-07-08 15:01:44 +08:00
Zhengchao An 91dec123d9 refactor(ecstore): add per-instance InstanceContext, migrate erasure setup type (#4413)
* refactor(ecstore): add per-instance InstanceContext, migrate erasure setup type

Phase 5 of the global-singleton consolidation (backlog#939): begin moving
runtime identity state out of process globals so multiple ECStore instances
can coexist in one process. Isolation is carried by the object graph
(ECStore -> Sets -> SetDisks holding an Arc<InstanceContext>), not a
task-local, which does not propagate across the many internal tokio::spawn
boundaries in the data/background paths.

This first slice migrates the erasure setup type -- previously three
independent process-global bools -- into a single per-instance
RwLock<SetupType> that derives is_erasure / is_dist_erasure / is_erasure_sd,
removing a triple source of truth that could drift out of sync.

- New runtime::instance module: InstanceContext + process bootstrap context.
- The legacy free-function facade (is_erasure/update_erasure_type/...) keeps
  its signatures and forwards to the current instance's context, falling back
  to the bootstrap context before a store is published.
- ECStore gains a pub(crate) ctx field and setup_is_* accessors; its
  constructors adopt the bootstrap context (never mint a fresh one) so startup
  writes and post-construction reads share one cell -- single-instance
  behavior is byte-for-byte unchanged.

Tests: erasure predicate derivation vs the legacy behavior, object-graph
carrier isolation across two ECStore instances, and bootstrap adoption.

Refs: backlog#939 (Phase 5, Slice 1), backlog#653 (item 8)

* refactor(ecstore): thread InstanceContext down the object graph (Phase 5 Slice 2) (#4415)

* refactor(ecstore): source the namespace lock manager per-instance (#4417)

refactor(ecstore): source the namespace lock manager per-instance (Phase 5 Slice 3)

Phase 5 Slice 3 (backlog#939): give each instance its own lock namespace by
sourcing SetDisks' lock manager from the instance context instead of the
process singleton. This removes the false cross-instance mutual exclusion (and
attendant ABBA risk) that a shared GlobalLockManager would cause once multiple
instances coexist.

- InstanceContext gains a `lock_manager: Arc<GlobalLockManager>`. `new()` mints
  a fresh manager (independent per-instance); `bootstrap_ctx()` aliases the
  process singleton via get_global_lock_manager(), so a single-instance
  deployment keeps exactly one shared namespace.
- SetDisks::new sources `local_lock_manager` from `ctx.lock_manager()` (the ctx
  it already adopts), not `runtime_sources::global_lock_manager()`. Single
  instance: same Arc as before, so behavior is unchanged.
- Remove the now-unused `runtime_sources::global_lock_manager()` wrapper.

Tests: bootstrap lock manager aliases the process singleton; two fresh contexts
own distinct managers; a SetDisks' lock manager is the one from its context and
aliases the global singleton in a single-instance build.

Verification: cargo test -p rustfs-ecstore (10 Phase 5 + set_disk locking
regressions green), cargo clippy -p rustfs-ecstore --all-targets (clean),
make pre-commit (pass).

Refs: backlog#939 (Phase 5, Slice 3). Stacked on #4415 (Slice 2).
2026-07-08 15:01:40 +08:00
Zhengchao An cda7688909 fix(multipart): clean temp part data on failure (#4412)
fix(multipart): clean failed part temp data
2026-07-08 15:01:37 +08:00
Zhengchao An 80cc3b1fcf fix(replication): preserve SSE-C checksum state (#4410)
* fix(replication): preserve ssec checksum state

* fix(replication): route ssec checks through boundary
2026-07-08 15:01:33 +08:00
Zhengchao An 9a7255540b fix(replication): add resync metrics (#4408) 2026-07-08 15:01:28 +08:00
Zhengchao An 1e6207c08e fix(lock): fence write commit on lock loss (#4406)
fix(lock): fence write commit on lock loss (backlog#899 Phase 2)

Phase 0+1 (#4388) made object write locks refreshable and marks the guard
lost when the heartbeat can no longer refresh a quorum, but does not act on
it. Under a partition a long write's lock can expire on an unreachable node
and be reclaimed, letting a third party re-acquire it; the original writer
keeps going and both commit -- a double write.

Expose the loss signal through NamespaceLockGuard::is_lock_lost() and
ObjectLockDiagGuard::is_lock_lost(), and fence the commit in put_object and
complete_multipart_upload: immediately before rename_data (the atomic commit
point), abort with a retryable NamespaceLockQuorumUnavailable (503) if the
lock was lost. In multipart the check precedes cleanup_multipart_path so a
lost lock leaves the upload intact and retryable. A write that already
reached rename_data Ok is durable and never aborted.

The loss criterion is unchanged (reacts to Phase 1's signal). Heal and the
long-GET read side are deferred follow-ups.
2026-07-08 15:01:24 +08:00
Zhengchao An f327707321 fix(ecstore): clear lifecycle metadata cache (#4416) 2026-07-08 07:01:09 +00:00
Zhengchao An 7cd7c84e71 feat(swift): track object expiration (#4409)
fix(swift): wire object expiration tracking
2026-07-08 14:30:46 +08:00
Zhengchao An 976a5d9713 fix(s3-types): give internal event names a mask base case to stop infinite recursion (backlog#965) (#4418)
EventName::mask() recursed forever for the three internal leaf events
(ObjectRemovedAbortMultipartUpload, ObjectCreatedCreateMultipartUpload,
ObjectRemovedDeleteObjects): their discriminants fall past
LAST_SINGLE_TYPE_VALUE so mask() took the compound branch, but expand()
returns vec![*self] for them, so mask() called itself with the same
value and overflowed the stack.

Detect the self-expanding leaf case (expand() yields exactly self) and
give it a dedicated bit derived from its discriminant. Those bits sit
above the single-type bits, so they never collide with each other or
with any compound 'All' mask.

Add exhaustive regression tests: every variant's mask() terminates, the
three internal masks are non-zero and mutually distinct and disjoint
from the single-type bits, and Everything covers all single-type bits.

Refs: https://github.com/rustfs/backlog/issues/965
2026-07-08 05:57:47 +00:00
Zhengchao An ea49d5686b fix(replication): mark failed targets offline (#4405) 2026-07-08 12:08:22 +08:00
Zhengchao An 1acd47f15e fix: SSE crash-loop DoS + credential reserved-char bypass (backlog#806) (#4404) 2026-07-08 10:05:51 +08:00
cxymds d25ddb0e1e fix(logging): reduce listing cancellation error noise (#4372)
* fix(logging): reduce listing cancellation error noise

* fix(ecstore): preserve filemeta io error kind

* fix(ecstore): avoid redundant clone lint in test

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-07-08 09:35:24 +08:00
Zhengchao An 3c3113619e fix(protocols): use constant-time secret comparison in FTPS and WebDAV auth (#4403)
The FTPS and WebDAV authentication handlers compared the client-supplied secret
key against the stored secret with `String::eq`, which short-circuits on the
first differing byte. A network attacker who knows (or enumerates) a valid
access key can recover the secret key byte-by-byte via response-timing analysis;
neither path is rate limited.

Switch both to a constant-time comparison using `subtle::ConstantTimeEq`, the
same primitive the SFTP handler and `rustfs/src/auth.rs::constant_time_eq`
already use. `subtle` is added to the `ftps` and `webdav` feature dependency
sets (it was previously gated on `sftp` only).

Addresses GHSA-3p3x-734c-h5vx.
2026-07-08 09:31:51 +08:00
Zhengchao An 7b20554056 fix(credentials): fail closed when deriving RPC secret from default credentials (#4402)
The internode RPC HMAC secret is derived from the S3 credential pair via
`derive_rpc_secret` when `RUSTFS_RPC_SECRET` is unset. The derivation uses the
secret key as the HMAC key, so when the default secret key (`rustfsadmin`) is in
effect the derived RPC secret is a fixed, publicly computable value. Any network
peer can then forge valid `x-rustfs-signature` headers and invoke internode RPC
routes (e.g. `read_file_stream`), bypassing S3 IAM entirely.

`normalize_rpc_secret` already rejected the literal default when it was supplied
directly, but `resolve_rpc_secret` still derived a secret from the default
credential pair. Make the derivation path fail closed: refuse to derive while
the default secret key is in effect, forcing operators to set `RUSTFS_RPC_SECRET`
(or a non-default `RUSTFS_SECRET_KEY`). A default access key paired with a
non-default secret key remains safe and is still allowed.

Addresses GHSA-68cw-96m3-h2cf (incomplete-fix follow-up to CVE-2026-45039).
2026-07-08 09:31:25 +08:00
Zhengchao An 0fad356450 fix(replication): send versionId on version-purge deletes to generic S3 targets (#4401)
Replication deletes dropped the S3 `versionId` query parameter for every
replication request, conveying the version only via the internal
x-*-source-version-id header. A generic (non-MinIO/RustFS) S3 target ignores
that header, so a version purge degenerated into a delete-marker creation while
the source still stamped VersionPurgeStatus=Complete — a silent divergence.

Only omit the query `versionId` when propagating a delete-marker CREATION
(the target must mint its own marker); version purges, delete-marker purges and
force deletes now address the exact version. Extracted into
resolve_delete_api_version_id with unit coverage.

Fixes rustfs/backlog#857
2026-07-08 09:29:58 +08:00
Zhengchao An f8ee0e7071 fix(crypto): reject plaintext fallback without crypto (#4391)
* fix(crypto): reject plaintext fallback without crypto

* test(crypto): gate no-feature regression under cfg
2026-07-08 09:29:37 +08:00
Zhengchao An 2b063b0c4a fix(heal): repair all object versions during disk-replacement heal (#4400)
Disk-replacement heal previously repaired only the latest version of each
object and never enumerated objects whose latest version is a delete marker,
so old versions were left unrepaired on a replaced drive.

Switch heal enumeration from list_objects_v2 (latest-only) to
list_object_versions (every version incl. delete markers), thread the concrete
version_id into the existing per-version heal_object, and make resume
cursor-based instead of positional: an opaque (marker, version_marker) paging
token persisted in ResumeState, a length-prefixed injective per-version dedup
key, schema_version bumps (v2) migrated independently in each of the two
persisted files, and a retry that resets both managers together (fixing a
latent rescan-skips-everything defect). Adds a real-disk-wipe e2e regression
suite proving old versions and delete-marker-latest objects are physically
restored.

Fixes rustfs/backlog#918
Fixes rustfs/backlog#919
Closes rustfs/backlog#854
2026-07-08 08:58:52 +08:00
houseme 4715dbed01 fix(ecstore): stop rejecting recoverable degraded reads in codec parity verification (#4399) 2026-07-08 08:46:01 +08:00
houseme a413729b16 perf(delete): gate and parallelize DeleteObjects per-object stat fanout (#4398) 2026-07-08 08:45:47 +08:00
houseme eaff17cade perf(ecstore): add strict/relaxed/none durability modes over the drive-sync switch (#4397) 2026-07-08 08:45:33 +08:00
houseme 92bf55ce62 perf(ecstore): right-size BytesMut encode ingest capacity to the EC-expanded block (#4396) 2026-07-08 08:45:21 +08:00
Zhengchao An afc7f1d6f9 fix(ecstore): make post-commit old data dir cleanup best-effort (#4386)
* fix(ecstore): make post-commit old data dir cleanup best-effort (backlog#898)

A write is authoritatively committed once rename_data returns Ok (the new
version is durable on >= write_quorum disks and immediately readable). The
subsequent reclamation of the now-dereferenced old object/<data_dir> is pure
space reclamation, yet commit_rename_data_dir propagated a below-quorum GC
failure via `?` into ErasureWriteQuorum -> 503, producing a false-negative
ACK for an already-persisted write. This is a deliberate divergence from
MinIO (erasure-object.go:1577), which couples the two; the divergence is
justified by durability semantics, not parity.

Changes:
- commit_rename_data_dir now returns a structured OldDataDirCleanup receipt
  and never returns Err. Adds an old==committed-dir anti-misdelete guard and
  a committed_data_dir parameter. Classification is extracted into pure
  functions (classify_old_data_dir_cleanup / map_cleanup_join_result /
  is_cleanup_not_found) so it is unit-testable. Task panic/cancel is mapped to
  a non-ignored DiskError::other (never DiskNotFound), and not-found is
  normalized to reclaimed.
- object.rs / multipart.rs consume the receipt instead of `?`. The result
  reverts to Ok, so the invalidate_get_object_metadata_cache self-heal and the
  capacity/compression accounting that a `?` early-return previously skipped
  now run on the cleanup-failure path too.
- On residue, report_old_data_dir_cleanup emits leak metrics and enqueues an
  object heal over the existing heal channel (disk-health signal replacing the
  503). heal_object -> reclaim_orphan_data_dirs already reclaims unreferenced
  local data dirs, closing the loop end to end.
- Adds rustfs_old_data_dir_* counters (attempted/reclaimed/leaked/below_quorum)
  as the operator-visible backstop for leaked residue.
- Adds a test-only (#[cfg(test)]) delete fault-injection seam; in production it
  inlines to a no-op None and has no behavioral effect.

Tests: pure-function A/C group + join-error mapping + actions decision; A5/A5b
real-disk guard/reclaim integration; end-to-end overwrite returning 200 while
old-data-dir cleanup fails. #864 rollback guard test remains green.

* fix(ecstore): resolve merge conflicts with origin/main in io_primitives.rs

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-07-08 08:24:50 +08:00
Zhengchao An 07324e268b fix(protocols): allow clippy::type_complexity in expiration worker tests (#4395)
fix(protocols): allow clippy type_complexity in test mock struct

The MockExpirationObjectBackend test struct uses a nested generic type
that triggers clippy::type_complexity. Add #[allow(clippy::type_complexity)]
since this is test-only code where the type is inherent to the mock design.

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-08 08:01:13 +08:00
houseme 3f13d098b4 feat(observability): feature-gated hotpath instrumentation for the data path (#4394)
Merge the hotpath-rs wall-time instrumentation from the backlog#936 analysis worktree behind an opt-in 'hotpath' cargo feature, keeping the default build at zero overhead and zero dependency.

- hotpath is an optional dependency everywhere (dep:hotpath feature syntax); the default dependency tree contains no hotpath crate at all
- 40+ measurement points across S3 handlers, ECStore/SetDisks object and multipart ops, erasure encode/decode, bitrot, LocalDisk I/O, FileMeta codec, and HashReader
- attribute sites use #[cfg_attr(feature = "hotpath", hotpath::measure)]; async_trait bodies use per-crate hp_guard! macros (ecstore + rustfs bin); rio gates measure_block! behind hp_measure_block!
- feature chain: rustfs -> rustfs-ecstore -> rustfs-rio / rustfs-filemeta, each crate owning its own gate
- hotpath-alloc is intentionally not wired up (hotpath 0.21.x TLS panic on cross-thread guard drop under tokio, see backlog#935); mimalloc stays the unconditional global allocator
- docs/development/hotpath-profiling.md documents building, HOTPATH_* env vars, SIGTERM report flow, and how to reproduce the backlog#936 timing reports

Refs: https://github.com/rustfs/backlog/issues/935 (HP-14, item 2), https://github.com/rustfs/backlog/issues/936

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 07:39:47 +08:00
houseme bd5d3c5d92 perf(ecstore): data-shards-only lockstep GET reads with stripe-aligned deferred parity engagement (opt-in) (#4392)
* feat(ecstore): add stripe-advance handles for deferred bitrot readers

Give DeferredObjectReader a shared pending state and expose a
DeferredReaderStripeHandle that advances the still-unopened source by whole
bitrot blocks using the same bitrot_encoded_range geometry the reader was
created with (identity mapping when hash_size == 0). This lets the GET decode
path open a parity shard aligned to the stripe where a data shard failed
instead of reading every parity shard on every stripe (backlog#923).

An already-opened (or failed) reader rejects the advance so callers retire it
rather than engage it out of alignment; bitrot verification after an advance
checks the advanced stripe's block against that stripe's stored hash.

Co-Authored-By: heihutu <heihutu@gmail.com>

* perf(ecstore): read only data shards on healthy lockstep GET behind opt-in gate

PR #4289's lockstep fix made every reconstruction-verifying GET read all
data+parity shards per stripe; the parity blocks are read, bitrot-hashed and
then discarded, a deterministic 2x read-bytes/IOPS/hash-CPU amplification on
healthy 2+2 objects (backlog#923). With the new opt-in gate
RUSTFS_GET_LOCKSTEP_DATA_SHARDS_ONLY_ENABLE=true (default: false, behavior
identical to main):

- read_lockstep keeps only the data slots engaged while the object is
  healthy; parity slots stay unopened deferred readers.
- When a data shard is missing or dies at stripe k, parity readers are
  engaged mid-object by advancing their deferred stripe handle to stripe k,
  preserving the lockstep alignment invariant from backlog#832.
- Degraded stripes engage one parity beyond the decode quorum so
  reconstruction verification keeps an extra source to check against
  (erasure.rs only verifies when available > data shards); an engaged parity
  reader that errors is retired for the rest of the object like any other,
  and a parity reader that cannot be realigned is retired instead of being
  read out of position.
- fill_deferred_bitrot_readers records stripe handles for deferred slots and,
  gate-on only, swaps eagerly opened parity readers for unopened deferred
  ones so they remain engageable mid-object; ready/error bookkeeping used by
  quorum decisions is untouched.
- Both GET paths (legacy duplex via Erasure::decode_with_stripe_handles,
  codec streaming via ParallelReader::with_deferred_parity_handles) carry the
  handles from reader setup.

Short-read -> UnexpectedEof -> whole-object retirement and the
inconsistent-source rejection are unchanged in both gate modes; tests lock
the healthy-path data-shards-only call counts, the default read-all-shards
behavior, mid-object parity engagement for streaming and hash_size==0
formats, and mid-stream inconsistent-parity rejection.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 07:15:49 +08:00
Zhengchao An 65953bfdb3 fix(ecstore): reduce rename data version signatures (#4383) 2026-07-08 06:01:50 +08:00
Zhengchao An ddf197ba57 fix(lock): refresh object write locks with a heartbeat (#4388)
fix(lock): renew distributed locks via heartbeat; wire server refresh (backlog#899)

A held distributed write lock had a fixed 30s TTL and was never renewed, so
any operation exceeding it got its per-node lease reclaimed and stolen by a
contender, causing split-brain writes. This implements Phase 0 + Phase 1 of the
#899 design (Phase 2 abort-on-loss is deferred and tracked in code comments).

Phase 0 (server refresh wiring, P0):
- node_service handle_refresh was a no-op stub that parsed args then always
  returned success=true. Extract a testable `refresh_lock` free function that
  actually delegates to the node's lock backend. Not-found maps to
  success=false with no error_info, so RemoteClient::refresh keeps its
  Ok(resp.success) semantics and yields a real not_found signal to the
  coordinator heartbeat. Without this, client heartbeats were silently no-ops.

Phase 1 (client heartbeat + safe interval + observability):
- DistributedLockGuard spawns a heartbeat that refreshes every per-client lease
  on a derived interval; interval is derived without Duration::clamp
  (entries<=1 or interval>=ttl => no spawn), fixing the sub-second-ttl panic.
- Add LockLostSignal: declare the lock lost when not_found exceeds
  entries.len() - refresh_quorum; RPC errors are not counted (absorbed by the
  ttl > interval margin). Expose is_lock_lost()/lock_lost() for observers.
- disarm(), release(), and Drop now abort the heartbeat before releasing so no
  refresh races the unlock (refresh only extends, never creates, a lease).
- Reclaim path stays behaviorally unchanged but now warns with owner/resource/
  lease age and records a metric (#698 scavenger preserved).
- Add DEFAULT_LOCK_REFRESH_INTERVAL, LockRequest.refresh_interval (serde default
  for RPC back-compat) + builder, and lock lifecycle metrics.

Open questions adopt the design's documented defaults (marked TODO in code):
refresh not-found -> Ok(false)/error_info=None; lost-quorum base entries.len();
DEFAULT_LOCK_REFRESH_INTERVAL=10s.

Tests: heartbeat keepalive/quorum-loss/jitter/boundary/disarm (lock crate),
server refresh delegation (rustfs), and end-to-end survives-past-ttl plus
crashed-owner-reclaim regressions (namespace).
2026-07-08 06:01:45 +08:00
Zhengchao An 45435d83ab fix(keystone): accept Swift storage tokens (#4390)
fix(keystone): accept swift storage tokens
2026-07-08 06:01:40 +08:00
Zhengchao An f021e4f321 fix(protocols): simplify swift expiration test types (#4385) 2026-07-08 05:15:30 +08:00
houseme e7cc719c17 perf(ecstore): move speculative PUT-tail tmp cleanup off the hot path (#4389)
* perf(ecstore): move speculative tmp cleanup off the PUT hot path

On a successful PUT, rename_data has already moved the data dir out of the tmp workspace, so the delete_all(RUSTFS_META_TMP_BUCKET) at the end of SetDisks::put_object is a speculative no-op safety net. It was awaited inline on the response path, where profiling (backlog#924 / HP-3) showed the same-disk queueing behind fsync-heavy load turns a ~49us no-op into ~9ms average (p99 77ms, macOS F_FULLFSYNC amplified) added to every PUT.

Run that cleanup on a spawned task instead, keeping it as a real backstop (rename_data's remove_std only removes empty dirs and silently ignores failures). The failure path (quorum loss / rollback) keeps the cleanup inline so a failed PUT never returns with tmp shards still on disk. If the process dies before the spawned task runs, cleanup_stale_tmp_objects (24h expiry, 5-minute loop) reclaims the entry.

Scope note: ops/multipart.rs delete_all on RUSTFS_META_MULTIPART_BUCKET is intentionally untouched; it removes real leftovers and deferring it would widen CompleteMultipartUpload/Abort races.

Regression tests (hermetic SetDisks on formatted local disks, no global state): PUT success drains the tmp workspace (polling the spawned task), and PUT failure (missing bucket volume, rename_data quorum error after tmp shards were written) cleans the workspace inline before returning.

Ref: https://github.com/rustfs/backlog/issues/924

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): do not retry NotFound in reliable_rename_inner

reliable_rename_inner blindly retried the rename once on any error. A NotFound retry cannot succeed: nothing recreates the missing source or parent directory between attempts, so the second rename fails identically and speculative cleanup renames (e.g. move_to_trash on an already-removed tmp path) always paid for two syscalls.

Extract the retry decision into should_retry_rename: NotFound returns immediately, any other error keeps the existing single retry. This helper is shared by the rename_data commit path via rename_all, so behavior there is covered by a new rename_all success regression test alongside the retry-predicate tests.

Ref: https://github.com/rustfs/backlog/issues/924

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 04:01:37 +08:00
houseme 062a68d151 perf(ecstore): skip tmp parent dir fsync for write-then-rename paths (#4387)
Step 1 of 4 for rustfs/backlog#922 (HP-1): write_all_internal previously
coupled file-content durability (fdatasync) with directory-entry
durability (parent dir fsync) behind a single bool. For tmp files that
are immediately renamed away, the tmp parent dir fsync contributes
nothing to crash consistency: the safe-rename recipe only needs file
content fdatasync -> rename -> fsync of the destination parent, because
the rename removes the tmp directory entry and a crash before the
rename means the PUT was never acknowledged.

Replace the bool with a SyncMode enum (None / FileAndDir / FileOnly)
and use FileOnly at exactly the two write-then-rename tmp write points:
the tmp xl.meta write in the non-inline rename_data path and the tmp
write inside write_all_meta. write_all_public (format.json etc.) and
the old-metadata rollback backup keep FileAndDir since those files stay
where they are written. The rest of the commit sequence (shard
sync_dir_files, rename, destination parent fsync) is untouched, and
behavior with drive sync disabled is unchanged.

This saves one directory fsync per disk per non-inline PUT (4 on a
4-drive set). Unit tests assert, via a test-only fsync-dir recorder,
that tmp write points no longer fsync their parent while the public
write point, the rollback backup, and the commit-rename destination
parent still do.

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 04:01:32 +08:00
houseme 9ae4ca5f99 test(ecstore): fit concurrent-resend guard inside lock acquire ceiling (#4384)
Under a full nextest run on loaded machines the legitimately serialized
cross-disk commits in concurrent_resend_same_part_commits_one_generation
exceed the acquire deadline by themselves: observed at the 5s default,
at the 30s production default (#4370), and on CI even at 60s — which is
a hard ceiling, because fast_lock clamps every requested timeout to
MAX_ACQUIRE_TIMEOUT (60s) while the Timeout error still reports the
requested value, so raising the env override higher is a no-op.

Request the full 60s ceiling explicitly and cap the queue depth at three
concurrent resends, bounding the last waiter to two serialized commits
(~12s each on the slowest observed CI runner). Three resends still race
the streaming phase and contend on the commit lock, which is all the
generation-mixing regression (backlog#853) needs; every correctness
assertion is unchanged.

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 04:01:27 +08:00
houseme 86940a9452 fix(protocols): resolve clippy type_complexity in swift expiration worker tests (#4393)
fix(protocols): factor swift expiration mock result type into alias

The swift feature clippy matrix on main fails with clippy::type_complexity on the MockExpirationObjectBackend test struct introduced with the expiration worker tests, blocking CI for every open PR. Introduce a MetadataResult type alias in the test module; no behavior change.

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-08 03:29:46 +08:00
Zhengchao An 7efacbdf95 fix(filemeta): validate part array lengths in into_fileinfo (#4382)
MetaObject::into_fileinfo indexed part_sizes[i]/part_actual_sizes[i] by
part_numbers.len() without checking the arrays are the same length,
unlike the adjacent part_etags/part_indices which are length-guarded.
decode_from pushes the three arrays independently and the xl.meta CRC
only covers bytes, so a CRC-valid but internally inconsistent xl.meta
(foreign writer / MinIO interop) triggers an out-of-bounds panic on the
GET/HEAD/LIST decode path.

Guard the three arrays for equal length and return Err(FileCorrupt) so a
divergent shard is skipped and quorum uses the other disks, instead of
panicking the request task. Cascade into_fileinfo to Result across its
callers, and fix io_primitives early-return to derive the version id from
the merged header and fall into the per-disk loop (single-disk survival +
heal). The 2118 merge-first path is left as a documented follow-up.

Refs backlog#900 (filemeta-01).
2026-07-08 02:01:28 +08:00
Zhengchao An a91d9cefc6 test(interop): add real MinIO read and migration parity tests (#4377)
test(interop): real-MinIO read + migration parity, Phase 1/2 (backlog#580)

Capture authentic on-disk fixtures from MinIO RELEASE.2025-07-23 (a bucket with
versioning, object-lock, lifecycle, tagging, quota, a public policy, SSE-S3
encryption, a webhook notification target, and a replication rule, plus inline /
versioned / multipart objects and a delete marker) and prove RustFS reads and
migrates them losslessly:

- filemeta parses_real_minio_object_xlmeta: small inline, two-object-version +
  delete marker, and multipart object xl.meta parse to the expected FileInfo.
- ecstore parses_real_minio_bucket_metadata_blob_without_loss: the MinIO
  .metadata.bin msgpack decodes via the PascalCase field names and
  parse_all_configs loads all ten config types present (policy, lifecycle incl.
  <ExpiryUpdatedAt>, object-lock, versioning, tagging, quota, notification,
  encryption/SSE-S3, replication incl. DeleteMarkerReplication /
  ExistingObjectReplication) without loss.
- ecstore reads_minio_inline_bucket_metadata_via_bitrot: MinIO inlines an object
  body as [HighwayHash256 32B][body]; RustFS's BitrotReader with HighwayHash256S
  verifies and yields the exact blob (the "inline_data 前缀不同" is that prefix).
- ecstore migrates_real_minio_bucket_metadata_end_to_end: on a throwaway 4-drive
  local ECStore, a real MinIO .metadata.bin seeded under .minio.sys is migrated
  into .rustfs.sys byte-identically for every config, exercising the Phase 2
  source adapter (MIGRATING_META_BUCKET = ".minio.sys") through the object layer.

All four run as ordinary crate tests (nextest CI). Phase 4 (MinIO re-reading a
RustFS drive) is documented as out of scope for one-way migration.

Refs rustfs/backlog#580
2026-07-08 01:12:49 +08:00
Zhengchao An 62a31e4ec4 fix(storage): address pending metadata and health gaps (#4380) 2026-07-08 00:15:07 +08:00
houseme d3660f9ded refactor(config): remove dead Direct I/O constants from zero_copy.rs (#4379)
ENV_OBJECT_DIRECT_IO_ENABLE, DEFAULT_OBJECT_DIRECT_IO_ENABLE,
ENV_OBJECT_DIRECT_IO_THRESHOLD and DEFAULT_OBJECT_DIRECT_IO_THRESHOLD
were never read anywhere in the workspace. The real O_DIRECT read path
landed in PR #4365 uses RUSTFS_OBJECT_DIRECT_IO_READ_ENABLE /
RUSTFS_OBJECT_DIRECT_IO_READ_THRESHOLD (crates/ecstore/src/disk/local.rs).
Keeping the near-identically named dead knobs invites operators to set
the wrong variable and believe O_DIRECT is enabled.

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 23:54:56 +08:00
Zhengchao An 742a59884d test(ecstore): add validation suite coverage gates (#4378) 2026-07-07 23:50:12 +08:00
Zhengchao An 3ed414cdb4 fix(storage): complete pending metadata and quorum fixes (#4375) 2026-07-07 23:22:54 +08:00
houseme 5533e080b0 feat(ecstore): LocalIoBackend trait and O_DIRECT read with fallback (#4365)
* refactor(ecstore): extract LocalIoBackend trait behind LocalDisk

Model the LocalDisk per-file I/O hot path as a LocalIoBackend trait
(pread_bytes / open_read_stream / open_full_read / open_write) and move
the existing method bodies verbatim into a default StdBackend. LocalDisk
holds Arc<dyn LocalIoBackend> and the DiskAPI methods forward to it.

This is a behavior-preserving refactor: no logic, error-mapping, metrics,
or cfg-branch changes. It creates the seam for an alternative
runtime-probed io_uring backend (rustfs/backlog#894) without touching
DiskAPI callers. Commit-point durability (fdatasync -> rename ->
fsync-dir in rename_data) deliberately stays outside the trait.

Add a differential test asserting all four read shapes return identical
bytes across page-boundary and file-tail ranges.

Tracking: rustfs/backlog#891 (parent rustfs/backlog#897)

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(ecstore): true O_DIRECT read path with per-disk graceful fallback (#4366)

* feat(ecstore): true O_DIRECT read path with per-disk graceful fallback

Implement a real O_DIRECT positioned read inside StdBackend::pread_bytes
(Linux only) and wire up the previously dead RUSTFS_OBJECT_DIRECT_IO_READ_*
knobs, which had zero call sites.

Open with rustix OFlags::DIRECT, probe the DIO alignment once per disk via
statx STATX_DIOALIGN (4096 fallback), read the aligned superset into an
alignment-allocated bounce buffer with a short-read loop, and slice out the
exact logical range so padding never reaches BitrotReader.

Any O_DIRECT failure falls back to the buffered read methods; EINVAL or
EOPNOTSUPP (tmpfs, overlayfs, 9p) latches the path off per disk with one
warning. O_DIRECT errors never surface: EINVAL maps to FileNotFound in
to_file_error and would trigger spurious EC rebuilds.

Default behavior is unchanged (knob off). macOS keeps F_NOCACHE.

Tracking: rustfs/backlog#892 (parent rustfs/backlog#897)

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): add P1.5 benchmark gate harness for O_DIRECT reads (#4369)

* test(ecstore): add P1.5 benchmark gate harness for O_DIRECT reads

Add an ignored, Linux-only release-mode test (direct_read_bench_gate)
that measures DiskAPI::read_file_mmap_copy through a real LocalDisk with
cold-cache enforcement (fadvise DONTNEED between rounds), reporting
p50/p95/p99/mean latency, wall time, process CPU time, and throughput as
one JSON line for A/B diffing between the buffered baseline and the
O_DIRECT candidate selected by the production env knobs.

Content is verified byte-for-byte before any timing starts.

Tracking: rustfs/backlog#893 (parent rustfs/backlog#897)

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): add concurrency dimension to P1.5 bench harness

Model the EC GET shape (FuturesUnordered over concurrent shard reads)
via RUSTFS_BENCH_CONCURRENCY (default 1, sequential as before). Needed
to evaluate blocking-pool pressure for the io_uring gate decision.

Tracking: rustfs/backlog#893

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>

* ci: retrigger after cancelled required check

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): use is_multiple_of in O_DIRECT bench cache-drop check

clippy's manual_is_multiple_of (rust 1.96) fails -D warnings on the benchmark helper's `done % file_count == 0`.

Verification:
- cargo clippy -p rustfs-ecstore --all-targets

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 23:01:54 +08:00
houseme 8f4349793e fix(ecstore): log peer offline/online transitions for console reporting (#4367)
A node could show OFFLINE in the console with zero logs explaining why
(rustfs/backlog#888, reported in rustfs#4304): the consecutive-failure
threshold crossing in handle_server_info_failure marked the peer offline
silently, the per-call warnings only existed on the observing node and
never named the transition, and the PeerRestClient offline flag plus its
background recovery monitor ran without any start/success log.

Logging-only change, no behavior change:

- handle_server_info_failure / handle_storage_info_failure: WARN
  event="peer_marked_offline" exactly once at the threshold crossing
  (later failures while already offline stay DEBUG), and DEBUG
  event="peer_probe_failure" while returning cached state below the
  threshold.
- update_server_info_cache / update_storage_info_cache: INFO
  event="peer_recovered_online" when a probe succeeds after the peer had
  been reported offline.
- PeerRestClient::mark_offline_and_spawn_recovery: WARN
  event="peer_connection_marked_offline" when the offline flag is first
  set (guarded by the recovery_running CAS so repeated failures do not
  spam), and INFO event="peer_connection_recovered" with the attempt
  count when connectivity is restored.

An "offline then back" episode now leaves a complete, correlatable
trace: N probe failures -> peer_marked_offline -> recovery monitor ->
peer_recovered_online.

Verification:
- cargo test -p rustfs-ecstore --lib (notification + peer_rest suites)
- make pre-commit

Ref: rustfs/backlog#888, rustfs#4304

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 22:40:24 +08:00
Zhengchao An 9e6d7de0fa docs(agents): forbid hard-wrapping prose in PR/issue bodies (#4374)
GitHub renders single newlines inside a paragraph as line breaks, so hard-wrapped PR/issue/discussion prose shows up with ugly mid-sentence breaks. Add a rule under Git and PR Baseline to keep each paragraph on one line.
2026-07-07 22:16:07 +08:00
houseme 2dfa3d3c36 test(ecstore): restore 30s lock-timeout guard for concurrent resend test (#4370)
concurrent_resend_same_part_commits_one_generation keeps failing on CI
with Lock(Timeout ... 5s) even after the fast-lock lost-wakeup fix
(NOTIFY_WAIT_CAP re-polling) landed — observed on rustfs#4365's Test and
Lint runs. Two independent causes exist and both are real:

1. the lost/stolen wakeup stall — fixed in fast_lock::shard; and
2. the six legitimately serialized cross-disk commits exceeding the
   small 5s default acquire timeout under full-suite CI disk load.

The lost-wakeup fix reverted the earlier 30s test override on the
assumption that cause 1 was the whole story; CI shows cause 2 stands on
its own. Restore the temp_env override (production-default 30s) around
the concurrent section so the regression guard asserts the correctness
property (exactly one intact generation), not CI disk latency. The
NOTIFY_WAIT_CAP fix stays untouched.

Verification:
- cargo test -p rustfs-ecstore --lib concurrent_resend_same_part_commits_one_generation

Ref: rustfs/backlog#882

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 22:07:05 +08:00
houseme 7001373316 fix(iam): load IAM bootstrap snapshot without namespace locks (#4363)
* fix(iam): load IAM bootstrap snapshot without namespace locks

IAM bootstrap (init_iam_sys -> load_all) read every config object with
default ObjectOptions (no_lock=false), so each read acquired a
distributed namespace read lock. Lock quorum is counted over cluster
nodes and unreachable peers are hard failures, so during a sequential
restart the very first read failed with
"Quorum not reached: required 2, achieved 0" and IAM could not come up
until enough peers' lock RPC surfaces converged - even when the storage
read quorum was already satisfiable (rustfs#4304).

Extend the startup contract from rustfs#4056 ("startup metadata I/O must
not require namespace locks") to the IAM bootstrap path:

- Introduce LoadMode {Locked, BootstrapNoLock} and plumb it through the
  load_all chain (groups, users, policies, mapped policies and their
  concurrent variants) down to the storage read options.
- load_all now performs all reads with no_lock=true; on-line
  single-object loads via the Store trait keep locked semantics.
- Fail-closed behavior is unchanged: any loader error still aborts the
  whole snapshot load.

Safety: config objects are atomic whole-object writes, so a lock-free
read only observes an old or a new value; staleness is bounded by the
existing periodic IAM reload. Listing (walk) never took namespace locks,
and maybe_schedule_lazy_rewrite stays a best-effort background task.

Verification:
- cargo test -p rustfs-iam --lib (153 passed, incl. new LoadMode tests)
- cargo clippy -p rustfs-iam --all-targets
- cargo check -p rustfs
- make pre-commit

Ref: rustfs#4304; tracking rustfs/backlog#884, rustfs/backlog#885

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(iam): sequential-restart regression test for lock-free bootstrap

Add an integration test reproducing the rustfs#4304 failure mode against
a real 4-disk temp-dir ECStore:

- Seed IAM group data in single-node mode, then flip the runtime into
  distributed-erasure mode. new_ns_lock now builds a distributed lock
  over the set's (empty) lock-client list, so every namespace-locked
  read fails exactly like a sequential restart with unreachable peers
  (lock quorum unavailable, storage read quorum healthy).
- Assert the locked load_group path fails in that state, while the
  bulk snapshot load_all (no_lock plumbing from the previous commit)
  succeeds, and the data survives intact once single-node mode is
  restored.

Reverse-verified: temporarily switching load_all back to the locked
mode makes the test fail, so it genuinely guards the contract.

Verification:
- cargo test -p rustfs-iam --test iam_bootstrap_no_lock_test
- cargo test -p rustfs-iam --lib

Ref: rustfs#4304; tracking rustfs/backlog#886

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(iam): route test ECStore imports through ecstore_test_compat boundary

The new integration test imported rustfs_ecstore facade paths directly,
tripping three architecture migration rules. Move every ECStore import
behind crates/iam/tests/ecstore_test_compat/mod.rs (the sanctioned
test-compat pattern), and register that module as a reviewed test-only
global-facade boundary in check_architecture_migration_rules.sh: the
sequential-restart regression test needs api::global::update_erasure_type
to flip into distributed-erasure mode for lock-quorum fault injection.

Verification:
- ./scripts/check_architecture_migration_rules.sh
- cargo test -p rustfs-iam --test iam_bootstrap_no_lock_test
- make pre-commit

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(server): expose readiness blocking reason + rolling-restart runbook

Operators hitting the rustfs#4304 sequential cold start could not tell
from the outside why a node stayed unavailable. Three additions:

- The readiness gate's 503 now names the blocking dependency in both the
  body ("Service not ready: waiting for storage_quorum") and a new
  x-rustfs-readiness-pending header (storage_quorum | iam |
  startup_finalization), derived from the current startup stage.
  /health/ready already returned details + degradedReasons; this covers
  the plain S3 requests that hit the gate.
- IAM bootstrap retry logs now carry an actionable `hint` field that
  classifies the failure (storage read quorum vs lock quorum vs
  uninitialized metadata) instead of only echoing the storage error.
- New docs/operations/rolling-restart.md runbook: correct rolling
  restart procedure, sequential cold-start expectations (degraded ->
  auto-recovery), readiness signal reference, and
  RUSTFS_STARTUP_READINESS_MAX_WAIT_SECS guidance.

Verification:
- cargo test -p rustfs --lib -- hint_tests service_not_ready readiness_pending
- make pre-commit

Ref: rustfs#4304; tracking rustfs/backlog#887

Co-Authored-By: heihutu <heihutu@gmail.com>

* upgrade deps version and improve import

* feat(iam): notification-path cache refreshes read without namespace locks (#4368)

P3 step 1 of rustfs/backlog#884 (scoped down from full MinIO readConfig
alignment after review): cross-node notification handlers
(group/policy/policy-mapping/user) refresh the local IAM cache with
single-object reads that previously took distributed namespace read
locks. These refreshes are asynchronous, best-effort, and already
stale-tolerant (the periodic reload converges them), so a node-counted
lock quorum failure or lock RPC hiccup on a peer must not fail them —
the same rationale as the lock-free bootstrap load_all (rustfs#4304).

- Store trait: add load_user_no_lock / load_group_no_lock /
  load_policy_doc_no_lock / load_mapped_policy_no_lock with defaults
  forwarding to the locked variants, so existing implementations and
  test mocks keep their behavior.
- ObjectStore overrides them via the existing LoadMode::BootstrapNoLock
  plumbing. Deletions triggered by the handlers keep locked writes.
- manager.rs: the four *_notification_handler paths (8 call sites)
  switch to the lock-free variants.
- Integration test: while the lock quorum is unavailable (DistErasure
  with empty lockers), load_group_no_lock must succeed exactly where
  the locked load_group fails.

Request-path loads (check_key, verify_temp_user_persistence) and admin
write-then-reload paths intentionally stay locked: load_user_identity
embeds expiry deletions, so those need the side-effect extraction
tracked in rustfs/backlog#884 before going lock-free.

Verification:
- cargo test -p rustfs-iam --lib (156 passed)
- cargo test -p rustfs-iam --test iam_bootstrap_no_lock_test
- make pre-commit

Ref: rustfs/backlog#884, rustfs#4304

Co-authored-by: heihutu <heihutu@gmail.com>

* fix(server): drop unused iam_bootstrap_failure_hint import in tests

The hint tests live in their own hint_tests module with a local import;
the stale re-import in mod tests failed clippy's -D warnings on the
Test and Lint CI variants.

Verification:
- cargo clippy -p rustfs --all-targets

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 20:07:09 +08:00
houseme 2247823200 fix(runtime): remove tokio io-uring feature and add regression guard (#4364)
Drop the [target.'cfg(target_os = "linux")'.dependencies] tokio
"io-uring" feature from 6 crates and the rustfs binary. Because
.cargo/config.toml enables --cfg tokio_unstable globally, this feature
switched every Linux build's file I/O onto tokio's io_uring runtime
backend. Restricted Linux environments (Docker default seccomp, gVisor,
proot, old kernels) reject io_uring_setup with EACCES/ENOSYS, which
tokio surfaced as PermissionDenied and RustFS reported as
DiskAccessDenied at startup.

Add scripts/check_no_tokio_io_uring.sh so the feature cannot silently
return: it fails on any tokio dependency line enabling "io-uring", while
still allowing a future application-level io-uring crate dependency.
Wire it into make pre-commit/pre-pr/dev-check and CI.

Tracking: rustfs/backlog#890 (parent rustfs/backlog#897)

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 17:47:59 +08:00
Henry Guo 0e61ba7c63 test(table-catalog): add scale fault rehearsal (#4359)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-07-07 16:37:58 +08:00
houseme 717cdd2abd fix(migration): decrypt MinIO IAM & server config on drop-in migration (#4358)
* fix(migration): decrypt MinIO IAM & server config on drop-in migration

MinIO encrypts IAM identity/service-account files and the server config at
rest with a key derived from the root credentials. The drop-in migration
paths read those blobs from the legacy `.minio.sys` bucket and parsed them
as plaintext JSON, so any encrypted blob failed to parse and was silently
skipped with "incompatible format". This is why users migrating from MinIO
kept their buckets/objects/policies but lost users and access keys (#2212).

The IAM load path already knows how to decrypt these blobs (RustFS master
keys plus MinIO-compatible legacy keys derived from the root credentials),
but that logic lived behind a private method and was never used by the
migration paths. Expose it as `rustfs_iam::try_decrypt_iam_blob` and inject
it into both migration paths via a `LegacyBlobDecryptFn` callback (ecstore
cannot depend on the IAM crate, so the closure is wired in the binary crate).
When a blob cannot be decrypted the raw bytes are used as-is, preserving the
previous plaintext-only behavior with no regression.

Also improve object-layer migration observability without changing control
flow: `try_migrate_format` now distinguishes "no legacy format" (a normal
fresh install) from "legacy format present but incompatible", and the caller
logs a loud error before initializing a fresh format that would leave the
existing MinIO objects unreadable. Topology/version skip reasons are promoted
from debug to warn.

Fixes a pre-existing test isolation race by marking
`test_recovery_falls_back_to_default_config_when_blob_stays_corrupt` serial,
since it reads a process-wide env var toggled by a sibling test.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(migration): box FormatV3 in LegacyFormatOutcome to satisfy clippy

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): stabilize concurrent multipart resend lock timeout

concurrent_resend_same_part_commits_one_generation spawns 6 same-part
resends whose cross-disk commits serialize on the per-uploadId commit
lock. Under the full nextest suite the parallel disk load pushes those
serialized commits past the small default lock-acquire timeout (5s),
producing a spurious `Lock(Timeout ...)` unrelated to the property under
test (observed on CI at 5.775s vs ~0.5s in isolation).

Raise RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT to the production default (30s)
for the concurrent-commit section via temp_env, so the regression guard
reflects correctness (exactly one intact generation) rather than disk
latency under CI load. The meaningful assertions are unchanged, and
#[serial] keeps the process-wide env override isolated.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(lock): bound fast-lock notification wait to prevent lost-wakeup stall

The real cause of the concurrent_resend_same_part_commits_one_generation
failures was a lost wakeup in the fast-lock slow path, not disk latency:
raising the acquire timeout to 30s only delayed the failure (it then timed
out at 30s), proving a genuine stall rather than overload.

In acquire_lock_slow_path a waiter that reaches the notification phase did a
single `timeout(remaining, wait_for_write())` spanning the whole acquire
budget, and treated that wait's elapse as a hard `Timeout`. But the release
path only notifies when `writer_waiters > 0`, so if the holder releases in
the gap after the waiter's `try_acquire` fails and before it registers as a
waiter, no notification (and no stored permit, since the pooled `Notify` is
gated) is produced. The waiter then blocks until the deadline even though the
lock is free and stays free — a spurious lock-acquire timeout. The shared
process-wide notify pool makes it worse: a wakeup can be consumed by a waiter
of a different lock hashing to the same slot.

Bound each notification wait (NOTIFY_WAIT_CAP = 50ms) and, on elapse, loop
back and re-`try_acquire` instead of returning `Timeout`; the deadline check
at the top of the loop is the single source of truth for timing out. A
lost/stolen wakeup now degrades to bounded re-polling (acquire within ~50ms
of the lock becoming free) instead of stalling for the whole timeout.
Correctness (mutual exclusion) is unchanged — acquisition still only happens
via `try_acquire_*`.

Add a regression test that reproduces the stall (holder + late waiter across
many keys): it times out without the fix and passes in ~1s with it. Revert
the earlier acquire-timeout workaround in the multipart test now that the
underlying stall is fixed, so it runs under the default timeout again.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 16:36:05 +08:00
majinghe 796fbb47da ci: replace docker image build runner with aks dind container (#4361) 2026-07-07 16:01:19 +08:00
houseme 88645c9169 fix(server): stop closing idle proxy keep-alive connections + reverse-proxy hardening (#3076) (#4360)
* fix(server): raise default HTTP/1.1 idle keep-alive timeout for proxies

In hyper's HTTP/1.1 stack `header_read_timeout` is armed as soon as the
connection is ready to read the next request head, so on a keep-alive
connection it also bounds how long an idle upstream connection may sit
before RustFS closes it. The previous 5s default meant RustFS FIN'd pooled
upstream connections while reverse proxies (Caddy ~2min, Nginx 60s) still
believed they were alive. The proxy then reused a dead socket and clients
saw `socket hang up` / connection reset, most visibly on non-idempotent
`PUT` uploads that proxies will not transparently retry (issue #3076).

Raise DEFAULT_HTTP1_HEADER_READ_TIMEOUT to 75s (above common proxy
idle-keepalive windows) and document the coupling. Still overridable via
RUSTFS_HTTP1_HEADER_READ_TIMEOUT for deployments that expose RustFS
directly to untrusted slow clients and want tighter slowloris protection.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(operations): add reverse-proxy deployment guide

Document the request/body semantics RustFS expects from a proxy layer, the
idle keep-alive mismatch that causes `socket hang up` on large PutObject
writes, and known-good Caddy/Nginx configs plus Cloudflare caveats. Closes
the documentation gap called out in issue #3076 and consolidates the
scattered findings from #609/#934/#1492/#1766.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(object): bound stalled PutObject request-body reads with diagnostics

When a reverse proxy or CDN forwards a partial request body and then goes
silent without closing the connection, the inner body stream neither yields
more bytes nor reports EOF, so RustFS waited forever for bytes that never
arrive and the client saw a silent hang/abort (issue #3076). A short body
that ends with a proper EOF was already rejected promptly; the gap was the
no-EOF stall case.

Add a single-point request-body guard on the PutObject path that wraps the
incoming StreamingBlob with an inter-chunk read timeout. The timer resets on
every chunk, so slow-but-progressing uploads are unaffected; it only fires
after RUSTFS_HTTP_REQUEST_BODY_READ_TIMEOUT (default 300s, 0 disables) of
complete silence. On timeout it logs a structured `put_object_body_read_stalled`
event with received/expected byte counts and fails the read with
ErrorKind::TimedOut instead of hanging. remaining_length/size_hint are
forwarded so wrapping is transparent to downstream length handling.

Covered by unit tests for the stall path, length-preserving pass-through, and
the disabled (timeout=0) pass-through.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 15:25:31 +08:00
houseme f73880f354 fix(startup): survive slow multi-node cold starts (#4357)
* fix(server): make startup readiness wait configurable and raise default (#4264)

The startup runtime-readiness wait was a hardcoded 30s constant with no env
override. On slow multi-node cold starts (Docker/K8s/Synology NAS) this window
is shorter than the internal startup budgets it depends on — the endpoint
DNS-retry window (~90s) and the format-load retry loop (~100s worst case) — so
readiness times out and the node exits with
`startup readiness timed out after 30s: storage_ready=false, lock_quorum_ready=false`
before storage/lock quorum can converge, feeding the restart storm reported in
the issue.

- Add `RUSTFS_STARTUP_READINESS_MAX_WAIT_SECS` (default 120s), documented in
  rustfs-config health constants.
- Resolve the wait at runtime via `startup_runtime_readiness_max_wait()`; a
  value of `0` falls back to the default instead of timing out instantly.
- Repoint `STARTUP_RUNTIME_READINESS_MAX_WAIT` at the shared config default so
  there is a single source of truth, and cover the getter with unit tests.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): stop peer/disk background monitors on graceful shutdown (#4264)

Long-lived peer health/recovery and remote-disk monitors are detached
`tokio::spawn` tasks that each hold a `tracing::Span` via `.instrument(..)` for
their whole lifetime. Nothing cancelled them at shutdown, so on the normal
return path the Tokio runtime was dropped while they were still alive and their
`Span`s were dropped during worker-thread thread-local-storage (TLS)
destruction. At that point `tracing-subscriber`'s fmt `on_close` can touch an
already-destroyed TLS slot and panic with
`cannot access a Thread Local Storage value during or after destruction`, which
escalates to a panic-during-panic abort (SIGILL / exit 132) — the crash
reported on Synology in issue #4264, amplified by the restart storm.

- Add `cluster::rpc::background_monitor` with a process-global shutdown token,
  `spawn_background_monitor()` (races the monitor future against that token so
  its span drops while the runtime is alive), and public
  `shutdown_background_monitors()`.
- Route every span-holding peer_s3 / peer_rest / remote_disk monitor spawn
  through `spawn_background_monitor` instead of `tokio::spawn(..).instrument()`.
- Expose `rustfs_ecstore::shutdown_background_monitors()` and call it from the
  graceful shutdown sequence (right after `ctx.cancel()`, before runtime
  teardown) via the `storage_api` compatibility boundary.

Existing recovery-probe span-context tests still pass, confirming log
correlation is preserved.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 14:02:00 +08:00
houseme 9ae233d552 test(e2e): add SHA256 write and HEAD checksum regressions (#4354)
test(e2e): add SHA256 verify-on-write & HEAD-checksum regression for #4341

Issue #4341 reported (on beta.8) that RustFS accepted mismatched S3
SHA256 checksums on PutObject (HTTP 200 instead of 400 BadDigest) and
did not return ChecksumSHA256 on HeadObject with ChecksumMode=ENABLED.

Both behaviors are already correct on current main, but the existing
e2e coverage only asserted the happy path (upload succeeds + GetObject
content matches). It did not encode the issue's two acceptance criteria,
so a future regression would go undetected.

Add two focused e2e tests that assert exactly the issue's contract:
- test_put_object_rejects_mismatched_sha256: a body that does not match
  the declared x-amz-checksum-sha256 must be rejected with BadDigest and
  must not be stored.
- test_head_object_returns_stored_sha256: after a correct upload,
  HeadObject with ChecksumMode=ENABLED must return the stored base64
  SHA256 digest.

Both tests pass against a real rustfs server (mismatch -> HTTP 400
BadDigest verified).

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 14:01:32 +08:00
houseme 58114f49f2 perf(ecstore): k-way heap merge for ListObjects, drop clone-to-parse (#4347)
* perf(ecstore): replace linear merge scan with k-way heap and drop clone-to-parse (backlog#874 backlog#875)

merge_entry_channels advanced the k-way merge with a linear scan over all
channel heads (O(entries x channels)) and allocated two fresh Strings per
pairwise comparison via path::clean. Every step also cloned MetaCacheEntry
values, including entry.clone().xl_meta() clone-to-parse calls.

- Introduce MergeHead with a cached cleaned name (allocated only when the
  raw name is not already clean) and drive the merge with a BinaryHeap of
  boxed heads: O(log channels) per entry, allocation-free comparisons.
- Move entries through the merge instead of cloning; the winner is sent
  without an intermediate copy.
- Remove the dead merge_file_meta_versions block: it only ran for
  prefix-dir groups whose entries have empty metadata, so xl_meta() always
  failed; cross-drive version merging happens in the resolve path.
- Keep legacy same-name semantics (dir groups collapse, objects shadow
  prefix dirs, later object candidate wins) and add regression tests for
  interleaved ordering, dir/object precedence, uncleaned-name grouping,
  and prefix-dir collapse.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): honor ascending versions_sort in ListObjects walk (#4348)

* fix(ecstore): honor ascending versions_sort in walk and document ordering invariant (backlog#876)

The walk loop carried a bare `//TODO: SORT` inside the
`WalkVersionsSortOrder::Ascending` branch, so the requested ascending
order was silently ignored and versions streamed newest-first (the raw
FileMeta order). WalkOptions defaults to Ascending, so every default
walker -- notably replication resync, which replays versions and needs
oldest-first to preserve the version-stack order -- received the exact
opposite of the contract.

FileMeta maintains versions newest-first (sort_by_mod_time is
descending) and into_file_info_versions preserves that order, so
ascending emission is the exact reverse of file_info_versions output.
Reverse in place when ascending is requested and add a regression test
locking the newest-first invariant plus the reversal contract.

Key-ordering audit result (no gap found): per-disk walkers emit sorted
streams, merge_entry_channels performs an ordered k-way merge, and
gather_results only filters by marker/limit, so ListObjects key order is
guaranteed upstream and needs no post-sort.

Co-Authored-By: heihutu <heihutu@gmail.com>

* perf(ecstore): enable GET metadata early-stop by default (#4349)

* perf(ecstore): enable GET metadata early-stop by default with env opt-out (backlog#872)

The metadata early-stop fanout (read_all_fileinfo_early_stop) has been
implemented and instrumented for a while but stayed behind an opt-in
flag, so default GETs always waited for every disk to answer the
metadata read even after quorum agreement was reached.

Flip RUSTFS_GET_METADATA_EARLY_STOP_ENABLE to default-on. The gate stays
conservative: should_allow_metadata_early_stop only admits metadata-only
reads (read_data=false) without version_id, healing, or free-version
requirements, everything else falls back to the full-wait fanout, and
setting the env var to false restores the old behavior entirely. The
version-aware gate (RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE)
remains opt-in because versioned reads carry a higher stale-selection
risk profile.

Also replace the stale "optimize concurrency" TODO in
get_object_fileinfo with a pointer to the early-stop implementation and
add regression tests for the new default plus the explicit opt-out path.

Co-Authored-By: heihutu <heihutu@gmail.com>

* perf(ecstore): lazily construct codec streaming multipart readers (#4350)

* perf(ecstore): lazily construct codec streaming multipart part readers (backlog#871)

get_object_decode_reader_with_fileinfo opened shard readers for every
part of a multipart object before returning the streaming reader, so
TTFB paid for parts x disks file opens up front and an early client
disconnect wasted the setup work for every unread part.

Replace the eager loop with LazyMultipartCodecStreamingReader: the first
part is still built eagerly so the dominant fallback conditions (missing
shards / read quorum) are detected before any byte is streamed and the
whole request can fall back to the legacy duplex path exactly as before.
Each subsequent part is built on demand -- when the previous part hits
EOF -- via a spawned task handle owned by the reader; dropping the
reader aborts an in-flight build so disconnects stop all further IO.

If a later part hits a fallback condition mid-stream (a shard vanished
after the request started), the reader surfaces an explicit read error
with a pipeline-failure metric instead of silently degrading; the
client's retry then detects the condition on the eager first-part setup
and takes the legacy path cleanly.

Adds unit tests for in-order streaming across lazy boundaries, deferred
construction (no build when the client stops within part 1), and the
mid-stream fallback error path.

Co-Authored-By: heihutu <heihutu@gmail.com>

* perf(ecstore): prefetch next multipart part reader setup during decode (#4351)

* perf(ecstore): prefetch next multipart part reader setup during decode (backlog#870)

get_object_with_fileinfo processed multipart parts strictly serially:
the next part's bitrot reader setup (file opens + read-quorum wait
across all disks) only started after the current part finished
decoding, so large multipart reads paid full setup latency between
every part.

Overlap the two stages with a depth-one pipeline: right after the
current part's readers are obtained, the next part's setup is spawned
(shared inputs behind Arc) and joined when the loop reaches that part.
The shared setup_multipart_part_readers helper keeps stage-duration
metrics semantics identical for both paths; a failed or stale prefetch
falls back to the synchronous setup, and the PrefetchedReaderSetup
guard aborts the in-flight task on error returns, early breaks, or
caller drop so disconnects stop background disk IO.

Gate: RUSTFS_GET_MULTIPART_READER_SETUP_PREFETCH (default on, env
opt-out). Adds a three-part end-to-end read test covering the prefetch
hit path and cross-part content ordering.

Co-Authored-By: heihutu <heihutu@gmail.com>

* perf(ecstore): move FileInfo through GET shuffle instead of cloning (#4352)

perf(ecstore): move FileInfo entries through the GET shuffle instead of cloning (backlog#873)

shuffle_disks_and_parts_metadata_by_index deep-cloned every valid
FileInfo (parts, erasure info, metadata map) once per disk on each GET.
Add an ownership-taking variant that runs the same by-index consistency
check as a read-only first pass and then moves entries into their
shuffled slots with mem::take, and switch get_object_with_fileinfo to
it -- that call site already owned the parts metadata vector. Disk
handles are Arc clones and stay cheap.

Scope notes from the backlog#873 audit:
- get_object_fileinfo's disks.clone() stays: DiskStore is Arc<Disk>, so
  the clone is per-slot refcounting and correctly avoids holding the
  RwLock read guard across the metadata fanout awaits.
- get_object_decode_reader_with_fileinfo keeps the borrowing shuffle:
  its caller must retain files/disks for the legacy fallback path, so an
  owned variant would just shift the same clone upstream.
- The metadata-cache hit path still clones parts_metadata; sharing the
  cached entry via Arc changes the read-path return types and is left
  as a follow-up.

Equivalence tests cover both the by-index placement and the mod-time
fallback against the borrowing variant.

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>

* fix(ecstore): gate merge emission on cleaned key and clear clippy redundant_clone

Address review + CI findings on the ListObjects/GET optimization PR:

- merge_entry_channels gated emission on the raw entry name while the heap
  orders by the cleaned key, so entries whose cleaned order and raw byte order
  disagree (e.g. redundant slashes) could be dropped. Gate on the same cleaned
  sort key the heap uses; add a regression test (`a//c` after `a/b`).
- Drop three redundant `.clone()` calls in test code flagged by
  clippy::redundant_clone (owned-shuffle equivalence tests and the walk
  ascending-versions contract test) that failed the CI clippy gate.
- Document the known mid-stream fallback limitation of the opt-in multipart
  codec streaming reader (default off) and mark the in-place per-part legacy
  degradation as a follow-up.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): force full metadata fanout for object tagging writes (backlog#872)

put_object_tags reads the object fileinfo with read_data=false and then
writes the updated tags to the online-disk set that read returned. With
metadata early-stop enabled by default, that read now returns as soon as
read quorum is reached, so the online-disk set is only a read-quorum
subset. Writing tags to that subset fails write quorum -> ErasureWriteQuorum
-> S3 SlowDown, which is exactly the s3-tests tagging failures
(PutObjectTagging/DeleteObjectTagging, reached max retries).

Thread a caller-controlled `allow_early_stop` gate through
read_all_fileinfo_observed/_inner and add get_object_fileinfo_gated;
put_object_tags calls it with allow_early_stop=false so the metadata read
does the full quorum fanout and returns the complete online-disk set as
the write target. Pure-read callers (GET/HEAD/tag read) keep the
early-stop fast path unchanged.

Extract metadata_early_stop_permitted() as the single gate and add a unit
test locking the invariant: caller opt-out (and observe=false, and data
reads) never early-stop even with the env flags on.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 14:01:09 +08:00
houseme c9b976ad46 feat(ecstore): reclaim orphaned data dirs during heal (#4356)
* feat(ecstore): reclaim orphaned data dirs during heal (backlog #3231/#3191)

Pre-#3510, an unversioned overwrite leaked one UUID-named data dir per PUT.
The write path now cleans up going forward, but pre-existing strays stay on
disk forever: heal's dangling logic only removes whole objects whose data is
missing, never surplus data dirs of an otherwise-healthy object. That leaked
space is what eventually made ListObjects scan tens of GB and time out.

Add SetDisks::reclaim_orphan_data_dirs, a fail-closed, quorum-safe sweep that
mirrors purge_orphan_dir_object:

- referenced set is the UNION of get_data_dirs() across every online replica's
  xl.meta, so a dir named by any replica is kept;
- if a disk holds the object dir but its xl.meta is missing or unparseable the
  object is treated as degraded and nothing is removed;
- only UUID-named subdirectories are ever considered.

Wire it into heal_object's healthy tail (under the object write lock) as a
best-effort step so the background heal scanner and admin heal recover the
leaked space automatically; a reclaim failure never fails the heal.

Covered by four unit tests: removal of an unreferenced dir, no-op when all dirs
are referenced, fail-closed abort on missing metadata, and cross-replica union
preservation.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(ecstore): fix typo unparseable -> unparsable

Satisfies the repository typos CI check.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 13:06:22 +08:00
Zhengchao An 0ce0388fc0 fix(ecstore): fsync inline rename_data rollback backup (#4346)
* fix(ecstore): fsync inline rename_data rollback backup

The inline branch of LocalDisk::rename_data writes the previous version's
xl.meta rollback backup (<old_data_dir>/xl.meta.bkp) with a bare
std::fs::write, fsyncing neither the file nor its new directory entry, even
when drive_sync_enabled() is true. The same closure already syncs the new
xl.meta and the commit rename directory, and the non-inline branch persists
its backup durably.

That backup is the sole restore source for delete_version(undo_write=true)
on a set-level write-quorum failure. With sync enabled, an inline overwrite
plus power loss plus a quorum failure that triggers undo_write could restore
a lost or truncated backup and fail to roll back to the prior committed
object.

Write the inline backup via OpenOptions + write_all and, when sync is
enabled, sync_data() it and fsync_dir_std its parent, mirroring the
non-inline branch. Extend the inline backup test to assert the .bkp contents
equal the previous metadata bytes.

Refs backlog#868 (disk-durability-01).

* fix(ecstore): preserve to_file_error mapping on inline backup write

Address review: the rewritten inline rollback-backup write must keep the
same DiskError classification as the previous std::fs::write(...).map_err(
to_file_error)? call. Without it, io errors (PermissionDenied/NotFound/
StorageFull/...) would surface as DiskError::Io(_) instead of the specific
DiskError variants (FileAccessDenied/FileNotFound/DiskFull/...), since
From<io::Error> for DiskError only recovers variants that were wrapped via
to_file_error. Map open/write_all/sync_data/fsync_dir_std through
to_file_error, matching write_all_internal.

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-07 11:47:43 +08:00
yihong e4ecb1bce5 fix: ci failed (#4353) 2026-07-07 10:38:41 +08:00
RustFS 85ba51ce72 fix(ci): use direct hash comparison for sha256 verification on Windows (#4345) 2026-07-07 10:17:40 +08:00
Zhengchao An 073bc96756 fix(filemeta): compute header signature instead of hardcoding zero (#4343)
The xl.meta version header `signature` was hardcoded to `[0,0,0,0]` on the
write path (`From<FileMetaVersion> for FileMetaVersionHeader`), and the
existing `get_signature` implementations only hashed `version_id` + `mod_time`
(+ `size`), so two versions that share a version_id and mod_time but differ in
body (e.g. a PutObjectTagging that reached only some disks) produced identical
headers. Such divergence was undetectable and unhealable in the strict merge
path (backlog#861, B12).

Compute a real signature over the full version body, mirroring MinIO's
`xlMetaV2Version.getSignature` semantics:

- Fill the signature on the write path via the `From` impl (covers
  add_version / set_idx / update_object_version).
- `MetaObject::get_signature` / `MetaDeleteMarker::get_signature` now clone the
  body, zero the per-disk `erasure_index`, fold `meta_user` / `meta_sys` in
  with order-independent hashes (msgpack map order is not stable across disks),
  marshal the rest and xxh64 it, then fold 64->32 bits. Empty vs all-empty
  PartETags are normalized alike.
- Invalid/bodyless versions return an `err` sentinel rather than all-zero, so
  they never collide with a real all-zero legacy signature.

Backward compatibility: the decode path preserves stored signature bytes, and
the normal (non-strict) merge path already zeroes signatures before grouping,
so existing all-zero xl.meta stays consistent across disks. Only strict
merge/heal compares signatures, where legacy data is uniformly zero (no split)
and genuine partial-write divergence is now correctly detected.

Adds unit tests covering: non-zero signature on write, erasure_index
independence, tag/metadata/size divergence detection, map-order independence,
PartETags normalization, delete-marker divergence, and the err sentinel.
2026-07-07 09:40:28 +08:00
Zhengchao An 5594b18912 fix(ecstore): make delete_volume non-recursive by default to prevent bucket-heal wipe (backlog#799 B1) (#4339)
* fix(ecstore): make delete_volume non-recursive by default to prevent bucket-heal wipe (backlog#799 B1)

`delete_volume` unconditionally `remove_dir_all`'d the whole bucket tree, and the
bucket-heal "remove" branch called it fire-and-forget on every local disk. A
mis-classified "dangling" bucket (or a non-force S3 DeleteBucket on a populated
bucket) was therefore recursively wiped — a potential whole-bucket data loss.
The `VolumeNotEmpty` -> recreate/`BucketNotEmpty` handling already present in
both delete_bucket paths was dead code because the primitive never refused.

Add an explicit `force_delete` flag to `DiskAPI::delete_volume` and default the
non-force path to a non-recursive `remove_dir` (rmdir), which fails atomically
with `VolumeNotEmpty` if the bucket still holds any object data. Only an explicit
force delete (S3 force bucket delete) removes recursively. Mirrors MinIO's
`xlStorage.DeleteVol` (`Remove` vs `RemoveAll`).

- Trait + all impls (local behavior, dispatch, disk_store, remote RPC) take the
  flag; the gRPC `DeleteVolumeRequest` gains a `force` field (proto3 default
  false → old peers get the safe non-recursive behavior on rolling upgrade).
- Heal remove branch passes `false` and no longer discards the result: a
  `VolumeNotEmpty` refusal is logged (the bucket is not dangling) instead of
  wiping data.
- Both `delete_bucket` paths pass `opts.force`, activating the previously-dead
  `VolumeNotEmpty` -> `BucketNotEmpty`/recreate handling (correct S3 semantics).

Adds a regression test: non-force delete of a non-empty bucket returns
VolumeNotEmpty and preserves the data; force delete removes it.

Design converged by two independent expert reviews (MinIO-fidelity +
defense-in-depth) referencing MinIO xl-storage.go. Refs backlog#799 (B1),
issue rustfs/backlog#850. The safety expert's deeper hardening (typed
capability instead of a bool, trash-instead-of-in-place for force, quorum
re-verification of dangling) is noted on #850 as follow-up.

* fix(ecstore): reword 'mis-classified' -> 'misclassified' to satisfy typos (backlog#799 B1)

* fix(rustfs): thread force_delete through StorageDiskRpcExt::delete_volume + test literal (backlog#799 B1)
2026-07-07 08:25:17 +08:00
Zhengchao An 28c0543f3c fix(filemeta): resolve core-storage audit P2 items B15/B16/B18 (backlog#863) (#4344)
fix(filemeta): resolve P2 core-storage audit items B15/B16/B18 (backlog#863)

Fixes the three remaining actionable items from the core-storage reliability
audit (rustfs/backlog#863). All three are metadata-layer correctness defects in
the filemeta crate.

B15 — version sort tie-break consistency
  `FileMeta::sort_by_mod_time` and the delete path used a hand-rolled comparator
  whose version_type tie-break for equal mod_time was the OPPOSITE of the
  canonical `FileMetaVersionHeader::sorts_before` (used by the metacache merge
  and latest-version selection). At equal mod_time it sorted a delete marker
  before its object, so the per-disk read path could treat an object as deleted
  while the quorum-merge path treated it as present. Both sort sites now derive
  their ordering from `sorts_before`, matching MinIO (object-first).

B16 — replication reset state persistence
  The three sites that flush `reset_statuses_map` into `meta_sys` inserted each
  key verbatim. A bare-ARN key (produced by `ObjectInfo::replication_state`) has
  no internal prefix, so read-back — which only recognizes prefixed keys —
  silently dropped the reset state; a rustfs-only key was invisible to
  MinIO-compatible readers. New `persist_reset_statuses` normalizes every entry
  to the canonical `replication-reset-<arn>` suffix and writes both the
  `x-rustfs-internal-*` and `x-minio-internal-*` prefixes.

B18 — Legacy (V1Obj) body round trip
  `FileMetaVersion::encode_to` never emitted the `V1Obj` field, so re-encoding a
  Legacy version silently dropped its entire body. Adds symmetric `encode_to`
  for `MetaObjectV1`/Stat/Erasure/ChecksumInfo/Part and a `write_msgp_time`
  helper (ext8/type-5/12-byte, matching the decoder). `Mode` uses the strict
  `write_u32` marker the decoder requires, and `Stat.ModTime` is written only
  when present so a `None` never round-trips to `Some(UNIX_EPOCH)`.

Adds regression tests for each fix (138 filemeta tests pass). Verified with an
adversarial multi-expert review that could not break any of the three.

Refs rustfs/backlog#863 (B15, B16, B18).
2026-07-07 08:25:12 +08:00
Zhengchao An b813fc7739 fix(ecstore): reject zero erasure block_size in codec streaming read (#4340)
The codec streaming GET reader divides by erasure.block_size in
build_codec_streaming_part_reader without validating the erasure
dimensions, unlike the legacy multipart path which already rejects
block_size==0 / data_shards==0. FileInfo::is_valid() does not check
block_size, so corrupted on-disk metadata (block_size==0, data_blocks>0)
passes validation and panics the read task with a divide-by-zero.

Add Erasure::has_valid_dimensions() and reject invalid dimensions at the
codec streaming entry before any disk access, mirroring the legacy guard
(which now reuses the same predicate).

Refs backlog#868 (868-1).
2026-07-07 07:17:58 +08:00
Zhengchao An 8b2c67a100 ci: cancel superseded main release builds (#4342) 2026-07-07 05:47:06 +08:00
houseme 6f613317f6 feat(internode): optimize gRPC transport (#4337)
* feat(internode): P0 gRPC transport tuning, message limits, payload metrics

Land the P0 subtask from docs/grpc-optimization: close the client-vs-server
transport gaps and add instrumentation to size which unary RPCs need channel
isolation in P1.

Transport tuning (G3): the client `Endpoint` now disables Nagle and raises the
HTTP/2 stream/connection flow-control windows to mirror the server socket, so
small lock/health RPCs are not batched and larger metadata responses are not
throttled by the 64KiB default window. All env-overridable, 0 opts out.

Message-size limits (G1): both `NodeServiceClient` and `NodeServiceServer` set
max decode/encode size (default 100MiB) instead of tonic's silent 4MiB cap, so a
large multi-version xl.meta or aggregated ReadMultiple no longer fails
out_of_range. The server limit is set on `NodeServiceServer` before wrapping in
the auth `InterceptedService` (the interceptor type does not expose it).

Payload instrumentation (P1 prep): ReadAll/ReadMultiple record a payload-size
histogram plus a large-payload counter when a response crosses the configured
threshold (default 8MiB), feeding alerting on paths that contend with
latency-sensitive control-plane traffic on the shared channel. Threshold-only
counter, no per-call hot-path log.

Verification: cargo check/test on config, io-metrics, ecstore, rustfs; clippy
clean on touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P1 control/bulk gRPC channel isolation (opt-in)

Land the P1 subtask from docs/grpc-optimization: physically separate large
bytes-carrying unary RPCs from latency-sensitive control-plane RPCs so a big
transfer can no longer head-of-line block a lock/health RPC on the shared
HTTP/2 connection (G2/G5).

Introduce ChannelClass { Control, Bulk } and get_channel_for_class in protos.
Control RPCs keep the per-peer connection keyed by the bare address; Bulk RPCs
(ReadAll/WriteAll/ReadMultiple/BatchReadVersion, via a new get_bulk_client) are
round-robined across a small per-peer bulk pool.

Rather than restructuring the global GLOBAL_CONN_MAP (and every consumer), bulk
channels are cached under a composite key (addr\0bulk\0idx). The NUL separator
cannot appear in a URL, so bulk keys never collide with the control key. This
keeps the blast radius small on a consistency-sensitive path. create_new_channel
is refactored into build_channel(dial_addr, cache_key) so several physically
distinct channels to one peer cache independently while dialing/TLS still use
the real address.

Gated by RUSTFS_INTERNODE_CHANNEL_ISOLATION (default OFF) so the default build
is byte-for-byte the pre-P1 behavior: bulk resolves to the control channel and
the switch is a single-env rollback. RUSTFS_INTERNODE_BULK_CHANNELS (default 2,
clamped >=1) sizes the pool. On failure, evict_failed_connection drops the whole
bulk pool for the peer (round-robin hides which index was used), avoiding
half-dead cached channels.

Lock RPCs (remote_locker) already use the default Control path, so lock
semantics and retry behavior are unchanged.

Verification: cargo check/test on config, protos, ecstore, rustfs; new protos
tests for bulk key routing and isolation-off passthrough; clippy clean on
touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P2 msgpack/JSON codec observability + encode buffer presizing

Land the safe, wire-compatible slice of P2 from docs/grpc-optimization: the
observability prerequisite for retiring the redundant JSON fields, plus a codec
micro-optimization. No proto/wire-format change; JSON is still dual-written.

Internode RPCs today dual-encode each metadata value as both msgpack (`*_bin`)
and a JSON compatibility string, and decoders prefer `_bin` with a JSON
fallback. Before the JSON fields can ever be dropped (a cross-version change),
that fallback must be proven unused in production.

Add rustfs_system_network_internode_msgpack_json_fallback_total{direction,
message}: incremented whenever a decode falls back to the JSON field because the
msgpack payload was absent. Wired into both directions — the client decoding
peer responses (remote_disk.rs, incl. the list-level read_multiple/batch
fallbacks) and the server decoding peer requests (node_service/disk.rs). This
counter must read zero across a release window before send paths stop writing
JSON and the proto text fields are reserved/removed (the deferred P2-1 steps).

Also pre-size the msgpack encode buffers (Vec::with_capacity(512)) on both
sides, eliminating the repeated growth reallocations for typical FileInfo
payloads with zero added copy. Full thread_local buffer pooling is deferred: it
needs either an extra copy (unclear net win) or a send-path buffer-return
lifecycle, to be justified by a codec microbenchmark first.

Verification: cargo check/test on io-metrics, ecstore, rustfs; new fallback
counter smoke test; existing codec decode tests green; clippy clean on touched
files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(internode): add msgpack/JSON convergence observation runbook

Runbook driving the observation-gated retirement of the redundant JSON
compatibility fields on internode gRPC metadata RPCs (grpc-optimization P2-1).

Documents the shipped fallback counter
(rustfs_system_network_internode_msgpack_json_fallback_total{direction,message}),
the PromQL to confirm it reads zero across a release window, a standing alert,
and the staged flip/rollback procedure (env-gated msgpack-only send, then proto
field removal in N+1).

Includes the verified field -> peer-decoder audit: only fields whose peer
decodes _bin first may be converged. Notes DeleteVersion.opts (DeleteOptions) is
NOT convergence-ready — its server handler is not _bin-first and must gain a
decode_msgpack_or_json path first. This gates the send-side change so it cannot
empty a JSON field an old peer still needs.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): env-gated msgpack-only send + DeleteVersion _bin support (P2-1)

Implements the send-side lever for retiring the redundant JSON compatibility
fields on internode gRPC metadata RPCs, plus the missing `_bin` support on the
delete path that it depends on (grpc-optimization P2-1). Default-off: the base
build is byte-for-byte the prior dual-write behavior.

Gated msgpack-only send (RUSTFS_INTERNODE_RPC_MSGPACK_ONLY, default false):
- New rustfs_protos::internode_rpc_msgpack_only() reads the flag.
- Client (remote_disk.rs) compat_json() and server (node_service/disk.rs)
  compat_response_json() emit an empty JSON string when the flag is on, so only
  the msgpack _bin payload is sent. The _bin field is always sent; decoders keep
  the JSON read fallback. Applied only to fields with a confirmed _bin-first peer
  decoder (WriteMetadata/UpdateMetadata/RenameData file_info, UpdateMetadata opts,
  ReadOptions, ReadMultipleReq, BatchReadVersionReq; ReadVersion/ReadXL/RenameData
  responses and the ReadMultiple/BatchReadVersion response lists).
- Only enable after the P2 fallback counter has read zero across a release window
  (see docs/operations/internode-msgpack-json-convergence-runbook.md). Single-env
  rollback; no wire-format break.

DeleteVersion(s) _bin support (prerequisite):
- The DeleteVersion/DeleteVersions protos had NO _bin fields. Add additive
  (backward-compatible) bytes file_info_bin/opts_bin (DeleteVersion) and repeated
  bytes versions_bin + bytes opts_bin (DeleteVersions); regenerate the checked-in
  prost struct.
- Client dual-writes them; server decodes them _bin-first with JSON fallback.
- These delete fields are kept OUT of the msgpack-only set (always dual-write)
  until their own fallback counter reads zero across a window with the new
  decoders fully deployed. DeleteVersion.raw_file_info stays JSON-only (no _bin
  field yet).

Verification: cargo check/test on protos, config, ecstore, rustfs (incl. the six
delete request handler tests and a compat_json default-path test); clippy clean
on touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P3 cluster peer online/offline health metric

Land the safe observability core of P3 (grpc-optimization G6/G8): track each
internode peer's reachability and expose the offline count, for parity with
MinIO's minio_cluster_servers_offline_total. Pure instrumentation — peer
selection and quorum are unchanged.

- io-metrics: per-peer PeerHealthState { online, consecutive_failures } registry
  plus record_peer_reachable/record_peer_unreachable. A peer flips offline after
  N consecutive failures (dial failures or RPC-triggered evictions) and back
  online on the next successful dial; the count of offline peers is published to
  the rustfs_cluster_servers_offline_total gauge.
- config: RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD (default 3, clamped >= 1).
- protos: build_channel marks the peer reachable on a successful dial and
  unreachable on a dial failure; evict_failed_connection feeds the failure signal
  too. Keyed by the real peer address, so control and bulk channels to one peer
  share health state.

Deferred (documented in docs/grpc-optimization P3): startup prewarm (no clean
topology-ready hook yet), the offline fast-bypass in peer routing (consistency-
sensitive; must not change quorum), and idempotent-read-only retry. This commit
is observability only.

Verification: cargo check/test on io-metrics, config, protos (new peer-health
state-machine and threshold-clamp tests); clippy clean on touched files; make
pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P3 control-channel prewarm + self-healing offline bypass

Add the remaining P3 connection-lifecycle levers (grpc-optimization G6/G8), both
env-gated and default-off so the base build is unchanged.

Prewarm (RUSTFS_INTERNODE_PREWARM, default off): RemoteDisk::new spawns a
best-effort background dial of the peer's control channel, deduped per peer
address, moving the connect cost off the first RPC. Failures fall through to the
existing lazy connect + recovery monitor.

Offline bypass (RUSTFS_INTERNODE_OFFLINE_BYPASS, default off): remote_disk
get_client/get_bulk_client fast-fail a peer already marked offline instead of
paying the connect timeout, so the erasure layer proceeds on quorum sooner. This
does NOT change quorum. It is self-healing: cluster_peer_should_bypass lets one
request per RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS (default 5s) through to recover
the peer even with no background monitor, and the recovery monitor's own probe
path calls the client directly so it is never bypassed.

io-metrics gains cluster_peer_is_offline / cluster_peer_should_bypass (with a
per-peer re-probe timestamp). Scope: data path only — remote_locker (lock RPCs,
most consistency-sensitive) is left dual-writing/unbypassed as a follow-up.

Verification: cargo check/test on io-metrics, config, ecstore (new self-healing
bypass tests; all 105 rpc tests green); clippy clean on touched files; make
pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* docs(internode): add A/B benchmark runbook for gRPC optimization stages

Reproducible before/after collection procedure for grpc-optimization P0–P3.
Since every stage is env-gated, before/after is the same binary with different
env — no rebuild. Documents, per stage: the exact env toggles (baseline vs
enabled column), which existing bench script to run
(run_internode_transport_baseline.sh / run_four_node_cluster_failover_bench.sh),
the Prometheus metrics to capture, and the acceptance gates from the design docs
(e.g. lock p99 down >= 20% for P1, msgpack fallback counter = 0 before enabling
P2, correct rustfs_cluster_servers_offline_total for P3).

Live runs require a multi-node cluster + load tool + Prometheus scrape and cannot
be produced in a single-process sandbox; artifacts land under target/bench
(gitignored) and attach to the PR.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(internode): P3-2 lock-path offline bypass + P3-3 idempotent read retry

Extend the offline bypass to the lock path and add opt-in retries for idempotent
reads (grpc-optimization P3-2/P3-3). Both env-gated and default-off/zero.

Offline bypass (lock path): factor the bypass decision into a shared pub(crate)
internode_offline_bypass_reason(addr) and call it from remote_locker::get_client
too, so lock RPCs to an offline peer fast-fail (letting dsync reach quorum
sooner) instead of paying the connect timeout. Does not change quorum; the
self-healing re-probe keeps peers recoverable. Gated by
RUSTFS_INTERNODE_OFFLINE_BYPASS (default off).

Idempotent read retry (P3-3): add execute_read_with_retry — a bounded,
exponential-backoff retry for read-only/reentrant RPCs on transient network
errors — and route disk_info through it. RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES
defaults to 0 (disabled). Write/lock RPCs are never retried (quorum/idempotency
safety, per CLAUDE.md); the wrapper requires an Fn closure so only reads that
rebuild their request from borrowed inputs qualify.

Deferred: grpc.health.v1 (optional ecosystem-compat only; needs a new
tonic-health dep and 3-way hybrid-service wiring — internal needs are met by the
existing Ping RPC).

Verification: cargo check/test on config, ecstore (105 rpc tests green incl.
disk_info now via the retry wrapper); clippy clean on touched files; make
pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(scripts): one-click internode gRPC A/B benchmark driver

Wrap the per-stage env matrix from the benchmark runbook into
scripts/run_internode_grpc_ab_bench.sh: given --stage <p0|p1|p2|p3> and --phase
<before|after>, it emits the stage/phase RUSTFS_INTERNODE_* server env to
<out-dir>/server-env.sh and runs the right underlying bench
(run_internode_transport_baseline.sh for p0/p1/p2, run_four_node_cluster_failover_bench.sh
for p3) into a labeled target/bench/internode-transport/<stage>-<phase>/.

Passthrough args after `--` reach the underlying bench; --dry-run previews the
env + command. The script is explicit that RUSTFS_INTERNODE_* are server env, so
for the load-driven stages the operator must restart rustfs with the emitted env
before the run; the docker four-node (p3) path exports them for a forwarding
compose. shellcheck-clean.

Runbook updated with a "One-click driver" section.

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore(compose): forward RUSTFS_INTERNODE_* into the four-node cluster

The four-node local-build compose only forwarded a fixed whitelist of env, so
the internode gRPC knobs (grpc-optimization P0-P3) never reached the containers
and the A/B bench driver's "after" phase was a no-op. Forward the full
RUSTFS_INTERNODE_* set with defaults matching the binary defaults, so leaving
them unset is a no-op and the A/B driver can toggle a stage per phase.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(internode): address Copilot review — retry health action + poison-safe peer health

Two review nits on #4337:

- P3-3 idempotent read retry (remote_disk.rs): execute_read_with_retry ran every
  attempt through execute_with_timeout_for_op, which hardcodes
  FailureHealthAction::MarkFailure. So the first transient error could flip the
  disk faulty and short-circuit the remaining retries, and each attempt
  over-counted the failure. Route all but the final attempt through
  execute_with_timeout_for_op_and_health_action with IgnoreFailure; only the last
  attempt marks faulty/evicts. No default impact (retries default 0).

- Peer-health helpers (io-metrics): record_peer_reachable/record_peer_unreachable,
  cluster_peer_is_offline and cluster_peer_should_bypass early-returned on a
  poisoned mutex, permanently stalling the offline gauge and bypass state after a
  single panic. Recover via PoisonError::into_inner().

Verification: cargo check/test on io-metrics + ecstore (105 rpc tests green);
clippy clean on touched files; make pre-commit green.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 05:18:31 +08:00
Zhengchao An 8f11222a63 feat(admin): add MinIO-compatible IAM and IDP endpoints (#4334)
feat(admin): add MinIO-compatible IAM/IDP admin endpoints

Register and implement MinIO admin API compatibility for IAM/IDP:
- PUT /v3/import-iam-v2 and POST /v3/revoke-tokens/{userProvider}
- generic /v3/idp-config/{type}[/{name}] CRUD mapped onto existing config
- LDAP/OpenID service-account, policy-entities, and list-access-keys flows

Adds IamSys::delete_temp_account primitive to back STS token revocation.
revoke-tokens requires the broader ListUsers admin capability for
cross-user revocation (self-revocation only needs RemoveServiceAccount),
mirroring the cross-user guard used by the service-account handlers.
Registers admin route-policy inventory entries for every new route.
Unsupported LDAP/OpenID backends return honest compatibility errors.

Refs rustfs/backlog#609 #610 #616
2026-07-07 05:12:30 +08:00
Zhengchao An 3420f28c17 feat(admin): add MinIO-compatible diagnostics endpoints (#4333)
feat(admin): add MinIO-compatible diagnostics and service-control endpoints

Register and implement MinIO admin API compatibility routes:
- POST /v3/service: restart/stop trigger the existing graceful-shutdown
  path; freeze/unfreeze record advisory state (request admission is not
  yet gated, surfaced honestly via effective=false)
- profiling/trace and healthinfo/obdinfo/log diagnostics endpoints
- GET /v3/top/locks and POST /v3/force-unlock, backed by new
  FastObjectLockManager::list_locks()/force_unlock() introspection
- speedtest routes where feasible

Refs rustfs/backlog#604 #606 #607 #615
2026-07-07 04:46:42 +08:00
Zhengchao An 68f048b8fe fix(replication): persist delete marker mtime in MRF entries (#4331)
fix(replication): persist original mtime in MRF entries (backlog#867)

MRF delete entries did not persist the original delete-marker mtime, so
after a restart the recovery replay path reconstructed the delete without
a source timestamp. Downstream the replica delete-marker was stamped with
the replay time (now()) instead of the source mtime, causing delete-marker
timestamp divergence across clusters.

Extend the MrfReplicateEntry disk format with an optional deleteMarkerMtime
field (persisted as Unix nanoseconds) in both duplicate struct definitions
(rustfs-replication and rustfs-filemeta). DeletedObjectReplicationInfo now
persists delete_marker_mtime, and start_mrf_processor restores it onto the
reconstructed delete so the replica keeps the source timestamp.

Backward compatibility: the new key uses skip_serializing_if + serde
default, so historical MRF files without it decode to None and replay
falls back to the current time (pre-#867 behaviour). No panic or entry
loss on old files.

Closes rustfs/backlog#867
2026-07-07 04:42:10 +08:00
Zhengchao An b60686eb6f feat(admin): add replication diff/mrf and batch job admin endpoints (#4332)
Add MinIO-compatible admin endpoints under the /v3 prefix, wired to real
RustFS subsystems where they exist and documented as compatibility-only
where they do not.

Replication (backlog#611):
- POST /v3/replication/diff: bounded on-demand scan of object versions,
  returning versions whose replication status is PENDING or FAILED. Wired
  to list_object_versions and the per-version replication status. Requires
  the bucket to exist and have a replication configuration. The scan is
  capped (REPLICATION_DIFF_MAX_SCAN) and reports IsTruncated when partial.
- GET /v3/replication/mrf: reports the failed-replication backlog (MinIO's
  MRF concept) from the replication stats handle: aggregate failed count and
  size per target plus in-queue counters. RustFS records failed replications
  as aggregate counters and persists MRF entries without a runtime query API,
  so per-object MRF rows are not enumerable; PerObjectEntriesAvailable is
  always false to make that explicit.

Batch jobs (backlog#613):
- POST /v3/start-job, GET /v3/list-jobs, GET /v3/status-job,
  GET /v3/describe-job, DELETE /v3/cancel-job.
- RustFS has no batch-job execution engine (no scheduler, queue, or worker),
  so these implement MinIO request/response/error semantics only and never
  fake execution or success: start-job parses and validates the job
  definition then returns NotImplemented for known job types and
  InvalidRequest for unknown ones; list-jobs returns an empty list;
  status/describe/cancel validate jobId and return a no-such-job error.

All routes are registered with admin auth using existing AdminAction
variants (ReplicationDiff, GetReplicationMetricsAction, StartBatchJobAction,
ListBatchJobsAction, DescribeBatchJobAction, CancelBatchJobAction) and are
covered by the admin route-registration matrix and new handler unit tests.

Refs rustfs/backlog#611 #613
2026-07-07 04:36:55 +08:00
Zhengchao An 511ad31ba0 fix(s3): normalize root double-slash ListBuckets requests (#4336)
test(s3): MinIO parity for double-slash ListBuckets, invalid copy-source date, region-aware CreateBucket

- Add DoubleSlashListBucketsCompatLayer: rewrite `GET //` to `GET /` so
  it routes to ListBuckets, matching MinIO browser-client behavior (#601)
- Add e2e regression coverage for invalid copy-source conditional date
  headers (#618) and region-aware MakeBucket(us-east-1) (#629)

Refs rustfs/backlog#601 #618 #629
2026-07-07 04:36:16 +08:00
Zhengchao An eb02486574 chore(swift): remove placeholder SSE module (#4330)
security(swift): remove placeholder SSE module (backlog#646)

The Swift `encryption` module was a non-functional stub: `encrypt_data`
returned the plaintext unchanged while labeling it AES-256-GCM in the
object metadata, and `generate_iv` derived the IV from a timestamp
rather than a CSPRNG. It had no production caller (only a `pub mod`
declaration and one integration test), so wiring it in as-is would have
silently shipped plaintext advertised as ciphertext.

We are not supporting Swift server-side encryption for now, so remove
the module outright rather than keep a dangerous stub around:
- delete crates/protocols/src/swift/encryption.rs
- drop `pub mod encryption;` from swift/mod.rs
- remove the encryption case (and unused import) from the swift
  integration test

The module reached main dubiously: it was introduced together with the
whole Swift API in commit 86e93624 ("fix(heal): canonicalize scanner
object-dir repairs (#3864)"), a 1665-file squash whose PR description
only covered the heal change and never mentioned Swift or SSE.

Verified: cargo fmt; cargo test -p rustfs-protocols --features swift
--test swift_simple_integration (10 passed); arch guardrail scripts pass.
2026-07-07 04:29:24 +08:00
Zhengchao An 3bc8d79fe5 fix(multipart): serialize put_object_part per uploadId (#4329)
fix(multipart): serialize the part commit per uploadId to prevent mixed-generation shards (backlog#853)

Concurrently re-transmitting the same part could land two shard
generations across different disks: each shard is individually
bitrot-valid, so the corruption surfaces only as a silent mix at read
time.

The hazard is confined to rename_part, where two temp parts are moved
cross-disk onto the SAME final part path — interleaving there can leave
shards from two generations. Each concurrent stream already writes to its
own unique temp dir, so the encode/stream phase never conflicts and must
stay lock-free: holding a lock across it would serialize slow re-transmits
of the same part, break the S3 'last finisher wins' semantics, and cause
UploadPart lock-acquire timeouts (ServiceUnavailable).

Scope a write lock to the uploadId namespace around only the rename_part
commit, so each commit is atomic across disks and the last committer wins
consistently. Mirrors MinIO's per-uploadID NS lock; disjoint from the
object lock held by complete_multipart_upload (no lock-ordering cycle).
Honors opts.no_lock.

The pre-delete-before-rename half of backlog#853 was already fixed in
#4316; this completes the issue.

Adds an end-to-end regression test: six concurrent resends of the same
part must all succeed (no lock-acquire timeout), exactly one generation
stays visible, and the reassembled object equals one intact generation.

Closes rustfs/backlog#853
2026-07-07 04:28:39 +08:00
houseme 6d0583872b test(ecstore): serialize list_objects mutation-sequence tests (#4338)
Three tests share the global list-objects mutation-sequence state (the
LIST_OBJECTS_MUTATION_SEQUENCE atomic and the per-bucket sequence map)
and all call reset_list_objects_mutation_sequences_for_test(), but only
observed_mutation_persists_namespace_journal_high_water was marked
#[serial_test::serial]. The serial lock only excludes tests that also
carry the attribute, so the unmarked tests still ran concurrently with
it: the journal test's persist of "bucket" -> 11 could land after the
recovery test's reset, making it observe 11 instead of the expected 9
(flaky under the default multi-threaded test runner, green with
--test-threads=1).

Add #[serial_test::serial] to the two unmarked tests:
- persistent_key_only_index_load_recovers_mutation_sequence_from_journal
- list_objects_mutation_sequence_advances_per_bucket

Unique bucket names alone would not fix this: the per-bucket assertions
in list_objects_mutation_sequence_advances_per_bucket check exact values
produced by the single global atomic counter, which any concurrent
observe call perturbs, and every reset zeroes that global state for
whichever peer is mid-flight.

Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-07 04:18:56 +08:00
Zhengchao An 75fab0a842 fix(filemeta): satisfy clippy::field_reassign_with_default in replication_info_equals test (#4335)
fix(filemeta): use struct-init to satisfy clippy::field_reassign_with_default

The replication_info_equals test constructed FileInfo via
`let mut x = FileInfo::default()` followed by field reassignment, which
clippy::field_reassign_with_default (deny-by-default under -D warnings)
rejects on the lib-test target, breaking `Test and Lint` CI on every PR.

Rewrite the three constructions using struct-init + ..Default::default().
Test-only change; behavior unchanged.
2026-07-07 03:01:43 +08:00
houseme 25193ee6cf feat(listobjects): add guarded metadata-fast listing (#4311)
* feat(listobjects): add phase 0 observability metrics

* perf(listobjects): reduce gather metadata decoding

* test(listobjects): add adversarial listing coverage

* feat(listobjects): add source-aware cursor fields

* feat(listobjects): define index source modes

* feat(listobjects): model index rebuild health

* feat(listobjects): add opt-in index fallback gate

* feat(listobjects): verify index candidate pages

* docs(listobjects): add rollout runbook

* docs(listobjects): untrack baseline fixtures

* feat(listobjects): add opt-in key-only provider

* feat(listobjects): add index verification metrics

* perf(listobjects): gate list metrics on get stage switch

* feat(listobjects): bind provider health generation

* feat(listobjects): add persistent key-only provider

* feat(listobjects): bind persistent provider lifecycle

* feat(listobjects): track persistent index mutations

* feat(listobjects): persist index mutation checkpoints

* fix(listobjects): aggregate benchmark metric snapshots

* feat(listobjects): store journal in system namespace

* chore(listobjects): report fallback reasons in bench

* test(listobjects): verify metadata-fast stale fallbacks

* feat(listobjects): serve metadata-fast snapshots behind guardrails

* test(listobjects): add metadata-fast chaos bench

* fix(listobjects): attribute metadata-fast fallback metrics

* test(listobjects): add metadata-fast fallback chaos probes

* test(listobjects): add quorum journal chaos guardrails

* fix(listobjects): satisfy pre-pr clippy gate

* docs(listobjects): untrack rollout runbook

* fix(ecstore): remove redundant heal error conversion

* test(filemeta): satisfy replication info clippy

----

Co-Authored-By: heihutu <heihutu@gmail.com>
2026-07-07 01:38:52 +08:00
Zhengchao An 0271abc14d docs: add MinIO compatibility router + file-format docs (#4328)
Add two durable architecture references grounded in current code:

- minio-rustfs-router-compatibility.md: MinIO cmd/api-router.go (S3
  object/bucket) and cmd/admin-router.go (admin /v3, /v4) vs RustFS
  implementation status, with per-row landing points and a gaps-only
  checklist of still-missing admin endpoints (profiling, healthinfo,
  LDAP IDP config, replication diff, MRF, batch, locks, speedtest,
  log stream, top, trace).
- minio-file-format-compat.md: xl.meta (meta_ver 3 write, <=3 read,
  XL2 magic, rs-vandermonde, HighwayHash256 bitrot, inline data) and
  .metadata.bin bucket-metadata interop matrix for the backlog#580
  items, plus old-RustFS -> new migration and a phased plan.

Cross-links s3-compatibility-matrix.md and admin-route-action-snapshot.md
and indexes both docs in docs/architecture/README.md.

Refs rustfs/backlog#596 rustfs/backlog#603 rustfs/backlog#580
2026-07-06 23:34:31 +08:00
Zhengchao An a2d679cdb6 fix(bucket): propagate bucket deletion to peer metadata caches (backlog#646) (#4326)
The peer `DeleteBucketMetadata` RPC handler was a stub that returned
success without doing anything, and the delete-bucket flow never sent
the notification in the first place. As a result, after a bucket was
deleted other nodes kept serving its stale cached metadata.

Wire the whole path end to end:
- ecstore: add an in-memory `remove_bucket_metadata` (free fn) and
  `BucketMetadataSys::remove`, the counterpart to `set_bucket_metadata`,
  and export it through the `api::bucket::metadata_sys` facade.
- node_service: `handle_delete_bucket_metadata` now validates the bucket
  name and actually drops the cached metadata for it.
- bucket_usecase: after a successful delete_bucket, notify peers via
  `notification_sys.delete_bucket_metadata` in the background, symmetric
  to the existing `notify_bucket_metadata_reload` path.

Also update the delete-bucket-metadata unit test to assert the
empty-bucket rejection instead of the old always-success stub, and drop
an unused `tracing::debug` test import left over from #4322.

Verified: cargo fmt; cargo check -p rustfs-ecstore; cargo test -p rustfs
--lib --features rio-v2 test_delete_bucket_metadata_empty_bucket; arch
guardrail scripts pass.
2026-07-06 23:27:47 +08:00
Zhengchao An 655c5cb403 fix(ecstore): reject short-read shards and advance ParallelReader per stripe (backlog#799 B2) (#4327)
Two compounding defects let a truncated shard corrupt a GET (ranged GET could
return HTTP 200 with wrong bytes):

- `BitrotReader::read` returned `Ok(short_len)` when the shard stream hit EOF
  before filling the caller's buffer. With `skip_verify` /
  `HashAlgorithm::None` / parity=0 there is no hash to catch it, so the short
  shard was accepted and every downstream byte shifted.
- `ParallelReader.offset` was set once and never advanced per stripe, so every
  stripe after the first reused the first stripe's geometry and the last-stripe
  length clamp was wrong.

Fix both (they are mutually required):

- `BitrotReader::read` now errors (`UnexpectedEof`) on a short read, before and
  independent of the bitrot hash check, so it fires under skip-verify/no-hash
  too. The caller sizes the buffer to the expected per-stripe shard length, so
  "buffer not filled" == "shard truncated". A short read routes through the
  existing `errs[i]` path, dropping that reader from the stripe so parity
  reconstruction engages; with parity=0 the stripe fails read quorum and the GET
  errors loudly instead of streaming shifted bytes. Mirrors MinIO's
  `parallelReader.Read` (`n != shardSize` -> reader failed).
- `ParallelReader::read` / `read_lockstep` advance `self.offset += shard_size`
  per stripe so the per-stripe expected length (incl. the shorter final stripe)
  is exact, matching the correct pattern already used by heal.

Updates three bitrot tests that used an obsolete oversized-buffer pattern to
size per-stripe (as the real decode/heal paths do), and adds a regression test
that a truncated shard errors under None/HighwayHash + skip_verify.

Refs backlog#799 (B2), issue rustfs/backlog#851. Design converged by two
independent expert reviews referencing MinIO cmd/erasure-decode.go.
2026-07-06 23:25:54 +08:00
Zhengchao An 6297d112c3 fix: auto-repair breaking changes from 1b3727e2 (#4324)
fix(heal): remove useless .into() conversion in heal error path

Clippy flagged a useless_conversion lint at heal.rs:491 where
 is redundant because  is already of type .
2026-07-06 23:14:14 +08:00
Zhengchao An 92596da7fa fix(filemeta): key round-tripped replication reset state by canonical header (backlog#799 B16) (#4325)
fix(filemeta): key round-tripped replication reset state by canonical header (backlog#799 B19->B16)

`get_internal_replication_state` stored the reset status under the bare ARN after
stripping the internal prefix, while the write and lookup sides
(`get_replication_state` / `ReplicationState::target_state`) use the full
`target_reset_header(arn)` key. A `target_state` fallback masked the lookup miss,
but the map stayed keyed inconsistently (bare on read, full on write), which can
drop reset state across merge/reflatten cycles.

Store the canonical `target_reset_header(arn)` key on read so the map is
consistent everywhere; the lookup fallback becomes belt-and-suspenders rather
than load-bearing. Adds a round-trip regression test.

Refs backlog#799 (B16), tracked in rustfs/backlog#863.
2026-07-06 22:58:52 +08:00
Zhengchao An 31c6859965 chore: converge stale TODOs and apply safe fills (backlog#646) (#4322)
Second TODO-convergence round over the current tree (backlog#646). All
line numbers in the old inventory had gone stale after the set_disk /
diagnostics / cluster refactors, so this re-scans and reduces the marker
count from 144 to 99.

STALE removals (comment describes already-implemented behavior, or dead
commented-out blocks) across ecstore (set_disk ops/core, store,
cluster/rpc, bucket/metadata_sys, services), iam, filemeta, s3select and
rustfs auth/object_usecase. No behavior change.

Safe fills, each verified:
- filemeta: replication_info_equals now also compares
  replication_state_internal (function currently has no callers; adds a
  regression test).
- bitrot: drop the confirmed-unused `_want` parameter from bitrot_verify
  and the now-unused `sum` on LocalDisk::bitrot_verify, removing a
  Bytes::copy_from_slice allocation. Streaming verify uses the file's
  embedded per-shard hash, never the passed sum.
- signer: rename v4_ignored_headers -> V4_IGNORED_HEADERS and drop the
  non_upper_case_globals allow.
- admin/heal: test_decode was #[ignore]d and used serde_urlencoded on a
  JSON body (would panic); rewire to serde_json::from_slice to match the
  production decode path, add assertions, un-ignore.

Verified: cargo fmt; cargo check on touched crates; tests pass
(filemeta, signer, bitrot, heal::test_decode); arch guardrail scripts
pass.
2026-07-06 22:43:32 +08:00
Zhengchao An 5f1759eb3c ci: cancel PR workflows on close (#4323) 2026-07-06 22:38:00 +08:00
Zhengchao An 83edafb11f fix(ecstore): reject renewing a mis-mounted drive into the wrong set (backlog#799 B19) (#4321)
fix(ecstore): reject renewing a misplaced drive into the wrong set (backlog#799 B19)

`renew_disk` located the drive's (set, disk) position from its own format via
`find_disk_index`, but never checked that the resolved `set_idx` matched this
`SetDisks`' own `set_index` before inserting the drive into `self.disks`. A
drive whose format places it in another set would be claimed by this set too,
so two sets could manage the same drive and degrade together.

Skip (with a warning) when `set_idx != self.set_index`.

Refs backlog#799 (B19), tracked in rustfs/backlog#863.
2026-07-06 22:29:05 +08:00
Zhengchao An 5c5f8063d8 fix(replication): log (not swallow) resync status persistence failure (backlog#799 B23) (#4320)
fix(replication): don't silently swallow resync status persistence failure (backlog#799 B23)

After a resync computes a new replication status, it persists it via
`put_object_metadata` but discarded the `Err` case (`if let Ok(u) = ...`). A
failure left the object's on-disk replication status disagreeing with the resync
result with no signal at all. Log the failure at warn level instead.

Refs backlog#799 (B23), tracked in rustfs/backlog#863.
2026-07-06 22:25:00 +08:00
Zhengchao An 1b3727e2c4 fix(heal): clean up .rustfs/tmp healed shards on all failure paths (backlog#799 B20) (#4319)
heal_object wrote healed shards to .rustfs/tmp/<uuid>/ and only removed that tmp
dir on the success path (the final delete_all). Three early exits after the tmp
shards were written leaked the dir:

- `erasure.heal(...)?` failing midway,
- the `disks_to_heal_count == 0` early return, and
- the `?` on the post-rename remote data-dir delete.

Add the tmp cleanup before the first two, and downgrade the remote data-dir
cleanup failure to a warning (the healed shard is already renamed into place, so
that cleanup failing must not abort the heal or leak the tmp shards).

Refs backlog#799 (B20), tracked in rustfs/backlog#863.
2026-07-06 22:16:58 +08:00
唐小鸭 849ea9a122 fix(site-replication): align IAM and bucket metadata replication (#4318) 2026-07-06 22:15:02 +08:00
Zhengchao An 5c67c508cb fix(filemeta): skip unknown fields when decoding MetaDeleteMarker (backlog#799 B17) (#4317)
`MetaDeleteMarker::decode_from` returned an error on any field key it didn't
recognize, unlike `MetaObject::decode_from` / `MetaObjectV1::decode_from`, which
skip unknown fields. That breaks forward compatibility: a delete marker written
by a newer version with an extra key fails to decode on an older binary.

Skip the unknown field's value (`skip_msgp_value`) and continue, matching the
object decoders. Adds a regression test.

Refs backlog#799 (B17), tracked in rustfs/backlog#863.
2026-07-06 22:09:05 +08:00
Zhengchao An 8ffe230a89 fix(multipart): don't pre-delete the committed part before rename_part (backlog#853) (#4316)
`rename_part` unconditionally ran `cleanup_multipart_path([part.N, part.N.meta])`
on every disk *before* the per-disk rename fan-out. The per-disk `rename_part`
already replaces `part.N` atomically (std::fs::rename) and rewrites `part.N.meta`,
so the pre-delete is redundant — and destructive: it removed an already-committed
(ACKed) part on all disks before the new rename landed, so a re-upload of the same
part that then failed write quorum destroyed the committed part outright.

Remove the pre-rename cleanup and rely on the atomic rename to overwrite in place;
the quorum-failure rollback below is unchanged. No behaviour change on the success
paths (first upload / retry both end with part.N replaced), verified by the
existing multipart suite.

Refs backlog#799 (B4). The remaining half of B4 — serializing concurrent
same-part uploads with an uploadId-scoped lock so two generations can't be mixed
across disks — is a larger concurrency change tracked as a follow-up on the issue.
2026-07-06 22:08:50 +08:00
Zhengchao An 5e73d59eb6 fix(replication): re-derive replication decision when replaying MRF delete entries (backlog#858) (#4315)
MRF delete replay reconstructed the delete with `..Default::default()`, leaving
`replication_state = None`. `replicate_delete` derives its target set purely
from `replication_state.replicate_decision_str`, so an empty state produced an
empty decision -> zero targets: the replayed delete contacted no remote at all,
a silent no-op that left replicas permanently diverged after a restart.

The MRF entry doesn't persist the decision and the source object is already
gone, so re-derive it from the live bucket config at replay time via
`check_replicate_delete` — mirroring the object heal path
(`get_heal_replicate_object_info`) — and set it on the reconstructed delete's
`replication_state`. This is a contained fix with no change to the on-disk MRF
format.

Refs backlog#799 (B9).

Note: the source delete-marker mtime is still not persisted in the MRF entry,
so a replayed delete marker is stamped with the replay time on the target. That
is a separate, minor consistency nuance (the delete now propagates correctly)
and can be addressed by extending the MRF entry format in a follow-up.
2026-07-06 22:08:17 +08:00
Zhengchao An f33ae8e9af fix(replication): keep MRF persister backlog cumulative so flushes don't drop entries (backlog#859) (#4314)
The MRF persister accumulated overflow entries in `pending`, flushed them with
`flush_mrf_to_disk`, and cleared `pending` on success. But `flush_mrf_to_disk`
*overwrites* the whole MRF file with exactly the entries passed. After flushing
batch A (file = A) and clearing, the next flush wrote batch B and thereby
overwrote the file to contain only B — and the MRF file is only replayed (and
cleared) at startup, never during the run, so batch A's entries were silently
lost. A crash after the B flush lost all of batch A's pending replications.

Keep `pending` cumulative (the file must hold the full set of overflow entries
for the run) and rewrite the whole set on each flush instead of clearing after
success:

- flush eagerly once 1 000 *new* entries accumulate since the last write
  (measured against the flushed length, so a large backlog isn't rewritten on
  every add), and on the 10s tick when dirty;
- bound the in-memory/on-disk backlog with `MRF_PENDING_CAP` (200 000) and log
  once when the cap is hit rather than growing without limit.

Refs backlog#799 (B10).
2026-07-06 21:34:45 +08:00
Zhengchao An 650a3e5734 fix(replication): classify resync verification HEAD errors instead of miscounting (backlog#862) (#4312)
The resync result verification HEADed the target after replicating and counted
the outcome with inverted error handling:

- for a delete marker, ANY HEAD error (timeout, 5xx, auth, malformed) was
  counted as replicated (success);
- for a versioned object against an AWS-style target, HEAD was sent with the
  RustFS UUID versionId, which AWS rejects with 400, so a well-replicated
  object was counted as failed.

Classify the error before counting:

- delete marker: only a definitive 404/NoSuchKey or 405/MethodNotAllowed
  confirms the marker propagated (`is_retryable_delete_replication_head_error`
  == false); any retryable/ambiguous error now counts as failed;
- versioned object with a version-id-format rejection: re-verify via
  `head_object_fallback` (versionId-less HEAD) before deciding — present ->
  replicated, absent/error -> failed;
- all other errors: failed, as before.

Reuses the existing, unit-tested classifier helpers. Verified against the
existing resyncer suite (24 tests).

Refs backlog#799 (B13).
2026-07-06 21:20:41 +08:00
Zhengchao An 32845e03b0 fix(replication): mark version-id fallback ETag match as Completed, not Failed (backlog#860) (#4310)
During resync against an AWS-style target that rejects RustFS UUID versionIds,
the code retries HEAD without a versionId and compares ETags. On a match it set
`replication_action = None` ("already in sync, nothing to copy") but did not
return, so control fell through to `if replication_action != ReplicationAction::All`
— a branch meant only for the unsupported metadata-only case — and stamped the
object FAILED with "metadata-only replication is not implemented". The target
already held an identical object, yet the source recorded FAILED forever, so
AWS-style targets never converged and the MRF kept re-queuing.

Handle `ReplicationAction::None` explicitly before that branch: record it as
Completed (with the resync timestamp/`replication_resynced` bookkeeping for
ExistingObject + reset_id, mirroring the HEAD-success None path) and return.
Only `ReplicationAction::Metadata` now takes the metadata-unsupported failure
branch; `All` still proceeds to the copy. This path is the only way `None`
reaches that point (the HEAD-success None case already returns earlier).

Refs backlog#799 (B11).
2026-07-06 21:10:50 +08:00
cxymds 6c9efc8064 fix(heal): treat no-heal-required format results as noop (#4301) 2026-07-06 21:09:18 +08:00
Zhengchao An 1cc1fc0f83 fix(ecstore): exclude failed-shard disks from commit so write quorum isn't inflated (backlog#852) (#4309)
`MultiWriter` recorded per-writer failures only in a private `errs` vector and
nulled the failed writer locally, but `put_object` never used that to prune the
disk set: `rename_data` ran over the full `shuffle_disks`, so a disk that took a
short/failed write still had its truncated shard renamed into place and counted
as an online disk. The object then claimed N good shards while one was
short/corrupt, so a single later disk failure could drop it below reconstructable
quorum — silent data loss.

- `write_shard`: null the writer on a generic write error too (not only on
  `ShortWrite`), so a failed writer is uniformly represented as `None` — matching
  `shutdown_writer`, which already does this.
- `put_object`: after encode, drop every disk whose writer failed
  (`drop_failed_writer_disks`) from `shuffle_disks` before `rename_data`, and
  re-check write quorum over the survivors. `rename_data` already re-checks
  quorum and rolls back if too few disks remain, so excluded disks are neither
  committed nor counted (MinIO sets failed writers to nil before `renameData`).

Adds unit tests for the exclusion/quorum accounting.

Refs backlog#799 (B3).
2026-07-06 21:05:05 +08:00
Zhengchao An c3f54f95ed fix(heal): preserve resume state and retry when heal objects fail (backlog#855) (#4308)
fix(heal): don't mark set healed / discard resume state when objects failed (backlog#855)

`execute_heal_with_resume` unconditionally called `mark_completed()` and
returned `Ok(())` after the bucket loop, even when objects failed. The caller
then treats `Ok` as success and cleans up the resume + checkpoint state, so a
heal run with per-object failures was reported as a clean completion and its
state (including the failed set) was destroyed with no retry.

Finalize based on the failure count instead:

- failed_objects == 0 -> mark completed (unchanged);
- failed_objects > 0 with retry budget left -> `schedule_retry()` (bump the
  bounded retry counter + reset per-pass progress for a full re-scan) and
  return Err, so `heal_erasure_set` preserves the resume/checkpoint state and
  the caller keeps the healing markers for the next run;
- failed_objects > 0 with retries exhausted -> drop the resume state (no
  zombie task) but still return Err so the markers survive for a later heal
  cycle / the background scanner. Never silently claim a clean completion.

The failure identities are not persisted across pages (the per-page sets are
pruned in `complete_page`), so a retry is a bounded full re-scan; healed
objects are skipped quickly on the normal-scan pass.

Adds `ResumeState::reset_for_retry`, `ResumeManager::schedule_retry`,
`ResumeCheckpoint::reset_for_retry`, `CheckpointManager::reset_for_retry`, and
regression tests. This activates the failure path the caller already documents
at task.rs ("Keep the markers on failure ... the next run re-marks and
eventually clears them"), which was previously dead because heal never
returned Err on object failures.

Refs backlog#799 (B6).
2026-07-06 20:47:29 +08:00
Zhengchao An 7975f26b90 fix(ecstore): reach orphan-dir purge on nil-version delete miss (#4189) (#4307)
PR #4220 added a purge for orphan empty-directory trees (folder keys with
no xl.meta) on the delete path, but the guard only accepted
object-not-found. Over the real HTTP DELETE path the guard is never
reached: `del_opts` pins `version_id = Uuid::nil()` for directory keys, so
the missing dir object fails the specific-version lookup with
version-not-found (FileVersionNotFound), not object-not-found. The guard
short-circuits, the store returns the error, and the API layer turns it
into a fake 204 — the ghost folder survives, exactly the #4189 symptom.

The existing unit tests passed because they call
`purge_orphan_dir_object` directly, bypassing the nil-version lookup.

Accept both misses in the guard (extracted as
`should_purge_orphan_dir_on_missing`) so the folder delete actually takes
effect. Non-directory keys and non-miss errors (e.g. quorum failures) are
unaffected; the cross-disk data-safety refusal in
`purge_orphan_dir_object` is unchanged.

Verified end-to-end against a running 4-disk erasure server: DELETE of a
planted orphan `ghost/` tree now purges it on all drives and clears the
listing, a real object under the same prefix is untouched, and a prefix
holding real data on any drive is refused (all drives preserved).

Adds predicate regression tests covering version-not-found /
object-not-found on directory keys, and the negative cases.
2026-07-06 20:47:04 +08:00
Henry Guo be52e35a1f test(table-catalog): add vendor compatibility audit (#4299)
* test(table-catalog): add vendor compatibility audit

* fix(table-catalog): include OSS data-plane endpoint

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-06 14:12:19 +08:00
Ramakrishna Chilaka b09526c0f5 feat(helm): allow overriding Kubernetes cluster domain (#4259) 2026-07-06 14:06:44 +08:00
Zhengchao An a9115f729e fix: block force delete on object lock buckets (#4298) 2026-07-06 14:05:39 +08:00
Zhengchao An 3e4c15da5d fix(object-lock): prevent locked version deletes (#4297) 2026-07-06 14:05:19 +08:00
cxymds 2585855d23 fix(ecstore): hide deleted versioned folder prefixes (#4296) 2026-07-06 14:04:53 +08:00
Zhengchao An 005197140e fix(heal): classify heal_object errors by type instead of "not found" substring (backlog#856) (#4295)
fix(heal): classify heal_object errors by type, not "not found" substring (backlog#856)

The heal page loop classified heal_object errors with a substring test
(`message.contains("not found")`). `StorageError::DiskNotFound` renders as
"disk not found", so an offline drive during a deep-scan heal matched the
"object absent" branch: the object was returned as `Ok(false)`, recorded into
the checkpoint's processed set, counted as a success, and permanently skipped
on resume — the object silently never got healed.

Replace the substring test with a typed classifier over the wrapped
`StorageError`:

- genuine object/version absence (FileNotFound / FileVersionNotFound /
  ObjectNotFound / VersionNotFound) -> Absent (treated as handled);
- transient infrastructure conditions (quorum errors, DiskNotFound,
  VolumeNotFound, SlowDown, OperationCanceled) -> TransientSkip, so the object
  is retried on a later pass instead of recorded as processed;
- everything else -> Failed (recorded as failed).

Deliberately does NOT use `StorageError::is_not_found()`, which lumps
DiskNotFound/VolumeNotFound with object absence — the exact conflation that
caused the bug. Applied to both the deep-scan and normal-scan heal_object call
sites. Adds regression tests for the classifier.

Refs backlog#799 (B7).
2026-07-06 10:45:29 +08:00
Zhengchao An bc8e546636 fix(ci): verify release downloads before use (#4294) 2026-07-06 10:29:49 +08:00
Zhengchao An e68a52ff59 fix: replace while-let loop with for-loop to fix clippy lint on main (#4292) 2026-07-05 23:35:06 +08:00
Zhengchao An a8d8e56478 refactor(ecstore): relocate NamespaceLocking + finalize God-Object split (backlog#822) (#4291) 2026-07-05 23:34:46 +08:00
Zhengchao An f737b39cfc refactor(ecstore): move ObjectIO/ObjectOperations into set_disk::ops::object (backlog#821) (#4290)
P6 of the SetDisks God-Object split (tracking backlog#815, issue backlog#821;
depends on P5 #4288). Relocate the core object read/write hot-path contract
impls out of the set_disk/mod.rs God-Object into their own module home:

- impl ObjectIO for SetDisks (~1,010 lines) -> set_disk/ops/object.rs
- impl ObjectOperations for SetDisks (~1,137 lines) -> set_disk/ops/object.rs
- Registered pub(crate) mod object; under set_disk/ops.

Pure move — zero logic change. Both contracts stay implemented for SetDisks, so
their EcstoreObjectIO / EcstoreObjectOperations associated-type bounds are
unchanged and the contract-compat tests still guard them. Method bodies are
moved verbatim: a whitespace-insensitive token-stream diff of the two impl
blocks (with their #[async_trait::async_trait] attributes) against the pre-move
mod.rs source is byte-identical (5,754 tokens each) — NO visibility widening was
required, because the impls reach SetDisks helpers and the P5 io_primitives
through inherent self./Self:: calls that resolve across modules unchanged. The
two inherent impl SetDisks blocks that sat between the trait impls (lock batch
helpers) remain in mod.rs, verified present exactly once and uncorrupted.

The issue's borrow/Arc-clone-avoidance optimization is intentionally deferred:
it is a perf-sensitive change requiring the #738 benchmark and would risk the
'byte-level behavior unchanged' acceptance; this PR delivers the relocation.

Verification:
- cargo check / clippy -D warnings -p rustfs-ecstore --all-targets: clean
- cargo test -p rustfs-ecstore --lib: 1841 passed, 0 failed
- all five arch guard scripts: pass
- token-stream diff of moved impls vs original: identical (mod.rs diff is a pure
  relocation; ObjectIO/ObjectOperations each defined exactly once post-move)
2026-07-05 23:13:29 +08:00
Zhengchao An 3e4a7c1f6a fix(ecstore): lockstep EC stripe read to fix large-object GET EOF (#4289)
fix(ecstore): lockstep stripe read to stop EC GET desync truncation

On the reconstruction-verifying GET path, ParallelReader::read used a
data-first schedule: it read only `data_shards` readers per stripe and
pulled in a parity reader as a substitute on demand. Because the shard
readers are streaming (advanced only by being read, no seek), a parity
reader first used mid-object was still positioned at its stream start
(block 0) and returned an earlier stripe than the surviving data shards.
Every shard passed its own bitrot hash, yet the set was mutually
misaligned, so decode_data_with_reconstruction_verification correctly
rejected it with "inconsistent read source shards" and the large-object
GET truncated mid-stream (client "unexpected EOF").

Add read_lockstep(): on the verify_reconstruction path, read every live
shard reader once per stripe and wait for all of them, so all readers
advance one block per stripe and stay mutually aligned; any reader that
errors is retired for the rest of the object (a stream that failed
mid-block can no longer be trusted to be aligned). The adaptive
data-first path is unchanged for non-verifying callers (e.g. heal).

Adds a regression test reproducing a data shard that dies partway through
a multi-stripe object; it must still reconstruct byte-exact output.

Refs backlog#832.
2026-07-05 22:58:18 +08:00
Zhengchao An 166679a723 refactor(ecstore): sink write + shared-read primitives into set_disk::core::io_primitives (backlog#820) (#4288)
* refactor(ecstore): sink write/rename/delete primitives into set_disk::core::io_primitives (backlog#820)

P5 step 2 of the SetDisks God-Object split (tracking backlog#815, issue
backlog#820; follows step 1 #4285). Relocate the entire set_disk/write.rs
primitive family into the core/io_primitives.rs module home established by
step 1:

- Module items: dangling_delete_grace() + its env consts, and the
  OrphanDirScan scan-result enum.
- impl SetDisks write/rename/delete primitives shared across mod.rs and the
  ops/ operation families: rename_data, commit_rename_data_dir,
  cleanup_multipart_path, rename_part, eval_disks, write_unique_file_info,
  update_object_meta(_with_opts), delete_if_dangling, delete_prefix,
  scan_orphan_dir, purge_orphan_dir_object, check_write_precondition,
  default_read_quorum, default_write_quorum.
- The dangling_delete_grace unit tests move with their subject.

set_disk/write.rs is removed and 'mod write;' dropped from mod.rs.

Pure move + visibility adjustment, zero logic change. Method bodies are moved
verbatim; pub(super) items are widened to pub(in crate::set_disk) so mod.rs /
ops still reach them (write.rs's super was set_disk; the deeper module needs the
explicit path to preserve identical reach). Callers use inherent self./Self::
calls, so no call sites change.

Verification:
- cargo check / clippy -D warnings -p rustfs-ecstore --all-targets: clean
- cargo test -p rustfs-ecstore --lib: 1841 passed, 0 failed (moved
  dangling_delete_grace tests run as set_disk::core::io_primitives::tests::*)
- all five arch guard scripts: pass
- token-stream diff of moved block vs original write.rs: identical modulo
  visibility tokens (2713 tokens each)

* refactor(ecstore): sink shared read primitives into set_disk::core::io_primitives (backlog#820)

P5 step 3 (final) of the SetDisks God-Object split (tracking backlog#815, issue
backlog#820; follows steps 1 #4285 and 2). Relocate the shared, low-level
metadata/erasure READ PRIMITIVE methods out of set_disk/read.rs into the
core/io_primitives.rs module home, leaving the object-read operation itself in
read.rs.

Moved into a new impl SetDisks block in io_primitives.rs (verbatim bodies):
- read_parts, read_all_fileinfo (+ _observed / _inner / _full_wait /
  _early_stop variants), read_all_xl, load_file_info_versions_exact,
  read_all_raw_file_info, pick_latest_quorum_files_info, read_multiple_files.
- The should_allow_metadata_early_stop free helper (called by the moved
  read_all_fileinfo_observed).

Kept in read.rs: the object-read operation and its private helpers
(read_version_optimized, get_object_fileinfo, get_object_info_and_quorum,
try_get_object_direct_data_shards_with_fileinfo, get_object_with_fileinfo,
get_object_decode_reader_with_fileinfo, build_codec_streaming_part_reader) and
the metadata-cache helpers/tests. read.rs reaches the moved primitives through
the SetDisks core (inherent self./Self:: calls; the sole cross-boundary edge,
get_object_fileinfo -> read_all_fileinfo_observed, is handled by widening that
one method to pub(in crate::set_disk)).

Pure move + visibility adjustment, zero logic change. pub(super) primitives are
widened to pub(in crate::set_disk); the three internal-only fanout variants
(_inner/_full_wait/_early_stop) stay private; load_file_info_versions_exact
stays pub(crate). Method bodies are moved verbatim.

Verification:
- cargo check / clippy -D warnings -p rustfs-ecstore --all-targets: clean
- cargo test -p rustfs-ecstore --lib: 1841 passed, 0 failed
- all five arch guard scripts: pass
- token-stream diff of the three moved regions vs original read.rs: identical
  modulo visibility tokens, the new impl-block scaffolding, and rustfmt
  signature re-wrapping (read.rs diff is pure deletion, zero added lines)
2026-07-05 22:35:05 +08:00
Zhengchao An 23075518c2 docs: update security advisory lessons (#4287) 2026-07-05 22:04:59 +08:00
Zhengchao An e3b51e0a94 test(s3): promote non-multipart get-part coverage (#4286) 2026-07-05 20:40:41 +08:00
houseme 76124423a4 fix: stabilize s3-tests delete key-limit coverage (#4283)
* fix: tighten list handling and s3 test support

* chore: tidy imports and metric updates

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Zhengchao An <anzhengchao@gmail.com>

* fix: address s3tests review follow-ups

---------

Signed-off-by: Zhengchao An <anzhengchao@gmail.com>
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-05 20:35:32 +08:00
Zhengchao An 46bb7e52b6 refactor(ecstore): extract read IO primitives to set_disk::core::io_primitives (backlog#820) (#4285)
* refactor(ecstore): extract read IO primitives to set_disk::core::io_primitives (backlog#820)

P5 step 1 of the SetDisks God-Object split (tracking backlog#815, issue
backlog#820). Relocate the module-level low-level read/erasure primitives out of
the 5,898-line set_disk/read.rs into a new core/io_primitives.rs module home:

- Metadata-fanout quorum machinery (MetadataQuorumAccumulator,
  MetadataFanoutDiagnostics/Observation, MetadataEarlyStopDecision,
  MetadataCacheLookup).
- Bitrot reader scheduling/creation (BitrotReaderSetup and the
  create_bitrot_readers_* / schedule / fill helpers).
- Shard-cost classification, read-repair heal dedup, and codec-streaming
  reader helpers.

Pure move + visibility adjustment, zero logic change. The moved block is
byte-identical to the pre-move read.rs source modulo the module header swap
(use super::* -> use super::super::*) and item visibility widening
(module-private / pub(super) -> pub(in crate::set_disk) so read.rs still
reaches them). read.rs's impl SetDisks body and imports are byte-identical to
before; mod.rs only gains 'mod core;' and repoints two
GetCodecStreamingReaderBuildOutcome references to the new path.

Verification:
- cargo check / clippy -D warnings -p rustfs-ecstore --all-targets: clean
- cargo test -p rustfs-ecstore --lib: 1840 passed, 0 failed
- normalized + token-stream diff of moved block vs original: identical modulo
  visibility tokens and rustfmt signature re-wrapping

* fix(arch): allow io_primitives as READ_REPAIR_HEAL_CACHE owner (backlog#820)

The READ_REPAIR_HEAL_CACHE owner-path guard in
check_architecture_migration_rules.sh pinned the cache to
set_disk/read.rs. P5 step 1 relocated the cache (and its accessor
helpers) to set_disk/core/io_primitives.rs, so allow that path too.
Guard intent is unchanged: the cache stays behind the ECStore set-disk
read-owner helpers.

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-05 20:23:49 +08:00
Zhengchao An 9959c828a1 fix(internode): relax keepalive/RPC timeouts to fix large-object GET EOF (#4284)
fix(internode): relax aggressive HTTP/2 keepalive & RPC timeouts to stop large-object GET truncation

The internode gRPC channel used a 3s HTTP/2 keepalive timeout and a 10s
overall RPC timeout. Under high-concurrency large-object reads a saturated
peer's PING ACK is legitimately delayed past 3s, so the whole channel (and
every RPC/stream on it) is torn down as a 'dead peer'. In-flight peer shard
reads then fail mid-object and large GETs truncate after headers+Content-Length
are already sent, surfacing to clients as 'download error: unexpected EOF'.

Reproduced on a 4-node erasure cluster with pure GET-only warp (no concurrent
writes) at 64 concurrency: 10MiB GET ~31 unexpected-EOF / 3min. Raising the
internode keepalive timeout (3s->30s via env) alone cut that to ~7; also raising
the RPC timeout cut it to ~3.

- Raise DEFAULT_INTERNODE_HTTP2_KEEPALIVE_TIMEOUT_SECS 3 -> 20
- Raise DEFAULT_INTERNODE_RPC_TIMEOUT_SECS 10 -> 30
- Wire the data-plane rio HttpReader keepalive timeout (was hardcoded 3s) to the
  same env/default so control- and data-plane stay consistent.

Refs backlog#832.
2026-07-05 19:27:23 +08:00
GatewayJ 9cf211930d fix(iam): expand OIDC auth diagnostics (#4281)
* fix(iam): expand OIDC auth diagnostics

* fix(iam): accept RFC3339 OIDC timestamps

* chore(iam): log OIDC policy mapping diagnostics

* chore(iam): log OIDC claim and policy details

* chore(iam): lower OIDC diagnostic log verbosity

* fix(iam): gate OIDC diagnostics behind debug

* chore: update yanked num-bigint lockfile
2026-07-05 18:05:23 +08:00
Zhengchao An 1f51d21d33 refactor(ecstore): move List/Bucket Operations into set_disk::ops (backlog#819) (#4282) 2026-07-05 15:53:16 +08:00
Henry Guo 5f2359de45 test(table-catalog): expand live conformance guide (#4277)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-05 15:41:08 +08:00
唐小鸭 188ab2131d fix(kms): persist Vault Transit key metadata (#4262) 2026-07-05 13:37:59 +08:00
Zhengchao An a164645ff6 refactor(ecstore): move MultipartOperations into set_disk::ops::multipart (backlog#818) (#4279) 2026-07-05 12:22:09 +08:00
houseme db277b17a4 fix: harden GET object performance paths (#4271)
* fix: harden GET object performance paths

* fix: satisfy GET multipart layer guard

* fix: keep v1 list markers S3 compatible

* perf: tighten GET direct-memory decision

* ci: isolate s3tests from scanner workload

* refactor: simplify get object body lifecycle

* fix: satisfy get object clippy
2026-07-05 12:03:22 +08:00
houseme a134717249 chore(deps): update flake.lock (#4273) 2026-07-05 10:32:41 +08:00
Zhengchao An 6b0fcb1180 fix(docker): require explicit root credentials (#4278) 2026-07-05 10:31:28 +08:00
Zhengchao An 26d6c06e03 fix: auto-repair breaking changes from 0271d7aa (#4276) 2026-07-05 10:28:13 +08:00
Zhengchao An ecc7827780 test(ecstore): cover disabled config recovery fallback (#4272) 2026-07-05 10:28:00 +08:00
Zhengchao An 137008b09a fix(s3): hide internal list marker tags (#4275) 2026-07-05 10:27:34 +08:00
Zhengchao An 456b1ce52f fix(s3): hide internal list markers in v1 responses (#4274) 2026-07-05 09:21:49 +08:00
Zhengchao An 0271d7aa2b refactor(ecstore): narrow api facade exports (#4270)
* refactor(ecstore): narrow bucket api facade

* refactor(ecstore): narrow more api facades

* test(ecstore): stabilize multipart cleanup test
2026-07-05 06:06:39 +08:00
houseme 9b69c6d14c fix(s3): preserve metadata listing extensions (#4261)
* chore(deps): update s3s to 0.14.1

* fix(s3): preserve metadata listing extensions

* fix(swift): make version names monotonic

* fix(s3): preserve v1 list pagination markers
2026-07-05 05:03:21 +08:00
Zhengchao An d0a965f2ee fix(lock): harden transient quorum and diagnostics (#4268)
* fix(lock): retry transient remote quorum gaps

* perf(storage): reduce deadlock detector blocking

* fix(storage): satisfy deadlock detector clippy
2026-07-05 03:01:20 +08:00
Zhengchao An a6b3e4f5d6 refactor: use canonical Rust module paths (#4269) 2026-07-05 03:01:14 +08:00
Zhengchao An 55ad8df1c2 refactor(ecstore): move HealOperations into set_disk::ops::heal (backlog#817) (#4267)
P1 of the SetDisks split (tracking backlog#815, depends on P0 backlog#816).

Give the Heal operation family its own module home: relocate set_disk/heal.rs
to set_disk/ops/heal.rs and move the HealOperations storage-api contract impl
beside its inherent helpers. The contract stays implemented for SetDisks so its
associated-type bounds are unchanged (ecstore_contract_compat_test still
covers it).

Method bodies are moved unchanged. The four inherent helpers widen from
pub(super) to pub(in crate::set_disk) to preserve their exact prior visibility
from the deeper module. get_pool_and_set now reads topology through
SetDisksCtx to keep the Heal family aligned with the P0 borrow pattern; the
read is provably identical (ctx.format()/pool_index() alias the core fields).

Runtime behavior is unchanged.
2026-07-05 00:53:09 +08:00
houseme 6dabbaab4d refactor(deps): replace snafu and heal anyhow (#4266) 2026-07-05 00:31:37 +08:00
Zhengchao An 86eafc799b refactor(ecstore): add SetDisks borrow context for God-Object split (backlog#816) (#4265)
P0 of the SetDisks split (tracking backlog#815). Introduce SetDisksCtx<'a>,
a Copy borrow handle over the shared SetDisks core state (topology/config,
disks, locker trio), and validate the pattern by routing the List family's
delete_all through a ListOperations<'a> service unit.

No trait impl is moved and runtime behavior is byte-for-byte unchanged; the
public SetDisks::delete_all entry point is preserved and delegates to the
borrow-based unit.
2026-07-05 00:13:34 +08:00
GatewayJ f730a5b3c5 test(s3tables): clarify durable draft failure test (#4263) 2026-07-04 23:57:56 +08:00
Henry Guo be45b35472 feat(table-catalog): add distributed maintenance scheduling (#4257)
* feat(table-catalog): add distributed maintenance scheduling

* fix(table-catalog): address scheduler review feedback

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-07-04 23:44:08 +08:00
Ramakrishna Chilaka f3147c5f8c perf(metadata): drop per-object heap allocs in internal-key lookups (#4260) 2026-07-04 22:14:07 +08:00
Zhengchao An d0792b87be refactor(lifecycle): extract core rule contracts (#4258) 2026-07-04 20:05:56 +08:00
Zhengchao An e3a8234bc9 fix: 12 P1 reliability/security defects from the full-repo audit (backlog#806) (#4256)
* fix(rio): reject corrupted short compressed/encrypted blocks instead of panicking

DecompressReader::poll_read and DecryptReader::poll_read sliced the block
body with a fixed `[0..16]` index to read the length varint. The body length
comes from an untrusted 24-bit header field, so a corrupted/truncated block
shorter than 16 bytes made the slice panic and crash the request task — a
read-path DoS on GET of tiered/corrupted data.

Pass the whole (arbitrary-length-safe) slice to uvarint and reject a
non-positive or out-of-range length prefix with InvalidData. Adds a repro
test for each reader; all existing round-trip tests still pass.

Refs rustfs/backlog#812

* fix(utils): close SSRF bypass via IPv4-mapped IPv6 addresses

validate_outbound_ip branched on the IpAddr variant, and the V6 branch's
is_loopback/is_unicast_link_local/is_unique_local checks never inspect the
embedded IPv4 of an IPv4-mapped address (::ffff:a.b.c.d). The metadata guard
also only matched the plain V4 169.254.169.254. So ::ffff:127.0.0.1,
::ffff:10.0.0.5 and ::ffff:169.254.169.254 all passed the outbound guard,
letting an attacker reach loopback/private/metadata endpoints.

Normalize IPv4-mapped IPv6 to its embedded IPv4 (via to_ipv4_mapped, which
matches only the true mapped form) before classification. Adds reject tests
for mapped loopback/private/metadata and an allow test for public IPv6.

Refs rustfs/backlog#813

* fix(ecstore): streaming last-part loss, GCS tier Range/remove, stat_all_dirs alignment

Four confirmed data-reliability defects:

- put_object_multipart_stream: the CompleteMultipartUpload part-collection loop
  used exclusive `1..total_parts_count`, dropping the final part (and collecting
  zero parts for a single-part object) — silently truncating the completed object.
  Extracted collect_complete_parts (1..=total_parts_count) with unit tests.
- GCS warm backend get() ignored the requested byte range, returning the whole
  object for a Range GET; now applies ReadRange::segment like the other backends.
- GCS warm backend remove() was an empty stub, so deleting a tiered object left
  it on GCS forever; now deletes via StorageControl (added a control-plane client),
  and in_use() actually lists (prefix-scoped) instead of always returning false.
- stat_all_dirs skipped None disk slots and dropped JoinErrors, returning a
  compressed, misaligned error vector; heal_object_dir then zipped it against the
  full disks array and could make_volume on the WRONG disk. Now returns one
  index-aligned entry per slot (None -> DiskNotFound), and heal no longer
  pre-fills the drive report (which would double it). Added an alignment test.

Refs rustfs/backlog#807

* fix(kms): stop Vault backend from destroying/reviving keys on failure

Two confirmed key-safety defects in the Vault KV2 backend:

- get_key_material() 'self-healed' a decrypt or wrong-length failure by minting a
  fresh random master key and overwriting the stored value. That destroys the
  original key material, making every DEK ever wrapped by it permanently
  undecryptable. Decryption must never mutate the stored key: both branches now
  return a cryptographic_error instead. (The empty-material bootstrap path, which
  only fills a never-initialized key, is intentionally left intact.)
- cancel_key_deletion() reset key_state to Enabled only in the returned response
  and never persisted it, so the key stayed PendingDeletion in storage and would
  still be reaped. It now writes the state back via update_key_metadata_in_storage
  and fails the request if the write fails.

Adds ignored (Vault-requiring) integration tests documenting both behaviours.

The third item (VaultTransit key state only in memory -> revived as Enabled after
restart) is deferred: a fail-closed guard would break restart availability for all
transit keys; the correct fix needs a persistent metadata store + Vault integration
testing. Tracked in rustfs/backlog#808.

Refs rustfs/backlog#808

* fix(admin): clamp STS AssumeRole duration; persist ImportBucketMetadata to disk

Two confirmed admin-API defects:

- Standard AssumeRole used the raw client-supplied DurationSeconds with no upper
  bound, so a caller could mint near-permanent temporary credentials. Clamp it to
  the AWS/MinIO STS window [900, 43200] (with 0 -> default 3600) via a shared
  clamp_assume_role_duration helper, and build the exp claim with saturating_add.
  This matches the existing AssumeRoleWithWebIdentity path.
- ImportBucketMetadata only mutated an in-memory map and returned 200, silently
  dropping every imported config. It now persists each non-empty config via
  metadata_sys::update (which merges onto existing on-disk metadata) and returns
  InternalError if a write fails. Mapping extracted to imported_configs_to_persist
  with unit tests.

Refs rustfs/backlog#809

* fix(heal): enqueue displacing request in release builds

push_displacing_lower_priority folded the real enqueue call into
debug_assert_eq!(self.push(request), Accepted). In release builds
(debug_assertions off) the whole macro — including its argument — is compiled
out, so after evicting a lower-priority queued item the new high-priority
request was silently dropped and never healed. Hoist self.push(request) out of
the assertion so the side effect runs in all builds. Adds a --release regression
test.

Refs rustfs/backlog#811

* fix(iam): propagate real delete_policy backend errors instead of swallowing them

delete_policy's is_from_notify path had its error handling inverted: a real
backend failure (disk IO / insufficient quorum) evicted the cache and returned
Ok(()), reporting a phantom success while policy.json survived on disk (to be
reloaded on the next full IAM reload); NoSuchPolicy — which should be idempotent
success — returned Err. Propagate real errors and let NoSuchPolicy fall through
to the idempotent cache-evict + Ok, matching delete_user / the notification
handler in the same file. Adds a backend-error-injection regression test.

Refs rustfs/backlog#810

* fix(utils): also normalize IPv4-compatible IPv6 in the SSRF guard

The initial fix only unwrapped IPv4-mapped (::ffff:a.b.c.d) addresses; the
deprecated IPv4-compatible form (::a.b.c.d, e.g. ::127.0.0.1 / ::169.254.169.254)
still bypassed the guard. Reject pure-IPv6 specials (::, ::1, fe80::, fc00::)
first, then normalize BOTH embedded-IPv4 forms before the IPv4 rules. Adds tests
for compatible-form loopback/metadata and confirms ::1 / :: stay rejected.

Found by adversarial review of the initial fix. Refs rustfs/backlog#813

* fix(ecstore): fix the same last-part loss in the parallel streaming path

put_object_multipart_stream_parallel had the identical off-by-one
(1..total_parts_count) that truncated the last part / produced zero parts for a
single-part upload — reachable when concurrent stream parts are enabled. Reuse
collect_complete_parts, which now returns an error instead of panicking on a gap
in the parts map. Adds a missing-part error test.

Found by adversarial review of the initial fix. Refs rustfs/backlog#807

* fix(kms): local backend must preserve key material on status change

LocalKmsClient (the default KMS backend) regenerated the master key material on
enable_key/disable_key/schedule_key_deletion/cancel_key_deletion — a pure status
change. A single disable+enable cycle therefore destroyed the original key,
making every DEK ever wrapped by it permanently undecryptable (silent data loss,
no network needed). Preserve the existing material via get_key_material and
re-save with only the status changed. Adds a hermetic regression test that wraps
a DEK, cycles all four status methods, and asserts the DEK still decrypts.

Found by adversarial review of the Vault fix. Refs rustfs/backlog#808

* test(rio): cover the length-prefix guard; correct its comment

Add a DecompressReader test that feeds an unterminated length varint so uvarint
returns 0 and the new guard (not the downstream codec) produces the InvalidData
error, and reword the guard comment which overclaimed that the > len bound
prevents a reachable panic (it is belt-and-suspenders). No behavior change.

Found by adversarial review. Refs rustfs/backlog#812

* test(rio): build test block headers via vec! to satisfy clippy

The new corrupted-block tests built the header with Vec::new() + repeated push,
tripping clippy::vec_init_then_push (-D warnings in CI). Construct the fixed
header bytes with vec![] instead. No behavior change.

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-04 14:24:02 +08:00
Ramakrishna Chilaka eb85607e37 fix(s3): allow CopyObject to restore a historical object version (#4250) 2026-07-04 10:36:42 +08:00
Zhengchao An 123552d32b refactor(replication): own utility wire contracts (#4255) 2026-07-04 08:00:37 +08:00
Zhengchao An 3d4fb532e5 refactor(replication): own filemeta wire contracts (#4254) 2026-07-04 06:39:54 +08:00
Zhengchao An 09fcacbe2b fix(e2e): align KMS test helpers with server API field names and env requirements (#4252) 2026-07-04 04:18:52 +08:00
Zhengchao An 125c228832 refactor(replication): own delete DTO contracts (#4253) 2026-07-04 04:00:27 +08:00
Zhengchao An 8a0617865b refactor(replication): route app storage through ecstore (#4251) 2026-07-04 02:00:28 +08:00
Henry Guo 2214f67fba feat(table-catalog): deepen row-level maintenance planning (#4249)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-03 23:49:40 +08:00
Zhengchao An 4883d3eb50 refactor(replication): route runtime facades through ecstore (#4248) 2026-07-03 23:49:24 +08:00
Zhengchao An 7cc470cb08 refactor(replication): isolate ecstore boundary imports (#4247) 2026-07-03 23:18:27 +08:00
Zhengchao An 918fd29711 fix(object-capacity,rio): capacity refresh safety + internode HTTP hardening (#4246)
* fix(object-capacity): stop cancelled/remote-disk refreshes from corrupting capacity

Two independent capacity-refresh bugs (backlog rustfs/backlog#805):

- refresh_or_join set the singleflight `running` flag then awaited the
  refresh future with no drop guard. When the admin request that became
  leader was cancelled (client disconnect) mid-await, `running` stayed true
  forever: joiners blocked indefinitely and the 120s scheduled refresh could
  never start again. catch_unwind covered panics but not cancellation. A
  RefreshLeaderGuard now resets the state and publishes an error on drop.

- capacity_disk_refs mapped the cluster-wide storage_info disk list without
  filtering non-local disks, so admin-triggered refreshes ran a local WalkDir
  over remote disks' drive_path. On multi-node clusters this double-counted
  local bytes (shared mount layout) or hit NotFound (per-node layouts), and
  poisoned the per-disk cache the scheduled local-only refresh depends on,
  making the cached total oscillate. Filter to local disks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(rio): harden internode HTTP client build, cache, and PUT body integrity

Follow-ups from the internode HTTP review (backlog rustfs/backlog#805):

- handle_put_file accepted a truncated body as success: a HttpWriter dropped
  mid-stream closes the chunked body cleanly, indistinguishable from EOF, and
  the server never compared bytes copied against the declared size. Reject
  size mismatches on the create path (append/unknown-size writes send size<=0
  and are exempt).

- build_http_client used `.expect()` on ClientBuilder::build(), which runs
  lazily on the first request and on every TLS generation bump (cert
  rotation), so a build failure panicked a serving task. It now returns an
  io::Error; get_http_client falls back to the previous TLS generation when a
  rebuild fails instead of failing the request.

- CLIENT_CACHE was a tokio::Mutex taken on every stream open (data_shards
  times per GET). Replaced with arc_swap::ArcSwapOption for lock-free reads on
  the hot path; the generation-monotonic replacement guard is preserved via rcu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 23:12:49 +08:00
majinghe 4542d4060f ci: replace ubicloud runner with self host runner on k8s (#4245) 2026-07-03 23:12:18 +08:00
houseme 63b6954ae3 chore(deps): update bytesize and s3s (#4242)
* chore(deps): update bytesize and s3s

* fix: keep s3s on rustfs fork for minio extensions

---------

Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-07-03 23:11:52 +08:00
Zhengchao An d19ee6c51c refactor(replication): isolate storage api contracts (#4244) 2026-07-03 22:48:21 +08:00
Zhengchao An 24e88a2cf4 refactor(replication): isolate filemeta facade contracts (#4243) 2026-07-03 22:20:46 +08:00
Zhengchao An c9bf097595 fix(storage): enrich versioning fallback log (#4241) 2026-07-03 22:11:05 +08:00
Zhengchao An 6839e57c96 refactor(replication): isolate storage api contracts (#4240)
* refactor(replication): isolate storage api contracts

* docs(replication): record storage api contract boundary
2026-07-03 22:10:16 +08:00
Zhengchao An 6a81445a96 refactor(replication): isolate ecstore owner contracts (#4239) 2026-07-03 21:42:45 +08:00
Zhengchao An 92402a3bde perf(get,heal): fix GET hot-path overhead and heal checkpoint scaling (#4237)
perf(get,heal): land verified fixes for backlog #800-#804

Five fixes from the GET-path performance audit and scanner/heal
completeness audit (rustfs/backlog#800..#804), each verified locally:

- backlog#800 (heal checkpoint O(N^2)): ResumeCheckpoint object sets are
  now HashSet (Vec::contains was O(n) per healed object; 1.5ms at n=1M
  vs 11ns measured), and per-object checkpoint/resume-state persistence
  is batched (1000 mutations / 5s) instead of rewriting the whole file
  per object. complete_page() prunes the sets at page boundaries so
  memory stays bounded; positions still persist unconditionally and
  legacy Vec-format checkpoints still deserialize.

- backlog#801 (DiskInfo.healing never set): erasure-set heal now writes
  a healing marker (.rustfs.sys/healing.bin) on the disks it rebuilds
  (endpoints plumbed via HealRequest/HealTask.heal_endpoints from the
  auto disk scanner) and clears it on success. LocalDisk::disk_info
  surfaces the marker, so scanner heal coordination, lock selection and
  admin/metrics healing counts see the rebuild.

- backlog#802 (cache probe after data read): new GetObjectBodyCacheHook
  in ecstore lets the app-layer object data cache serve the body inside
  get_object_reader, after metadata quorum resolution (etag known) but
  before the erasure shard read/decode. Previously a hit still paid the
  full disk read. Hook is None/no-op when the cache is disabled.

- backlog#803 (GET hot-path redundant work): ObjectInfo is cloned for
  event notification only when an event will actually be built (GET and
  HEAD paths; events are currently suppressed so the clone was pure
  waste); get_opts/put_opts/del_opts resolve bucket versioning with one
  metadata-sys lookup instead of two; skip_verify_bitrot and
  get_lock_acquire_timeout env reads are cached via OnceLock; the
  io-priority metric is no longer double-counted; GetObject input fields
  are cloned selectively instead of cloning the whole input.

- backlog#804 (disk permit starvation): the disk-read permit wait is now
  bounded (RUSTFS_OBJECT_DISK_PERMIT_WAIT_TIMEOUT, default 5s, 0 =
  previous unbounded behavior); on timeout the GET proceeds without a
  permit and the bypass is counted. DiskReadPermitReader also releases
  the permit at body EOF instead of holding it until the client drops
  the stream.

Verification: make pre-commit; cargo clippy -D warnings on the four
changed crates; full rustfs lib suite (2096 tests) green; rustfs-heal
lib suite green with new unit tests for checkpoint pruning/legacy
format/throttle, permit EOF release, and the cache hook (hit + SSE
skip). The heal_integration_test and one set_disk listing test fail
identically on unmodified main (pre-existing global-state ordering
flakes, verified via git stash A/B).

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-03 21:42:27 +08:00
Zhengchao An 769549dd2c refactor(replication): isolate status contract boundaries (#4236) 2026-07-03 19:40:49 +08:00
Zhengchao An 7001e53546 refactor(replication): isolate stats and app contract boundaries (#4235)
* refactor(replication): isolate stats boundary adapters

* refactor(replication): route app contracts through storage boundary
2026-07-03 18:48:26 +08:00
houseme eebd16d8a4 feat(cache): add object data cache engine and app flow (#4187)
* feat(cache): add object data cache engine

* feat(cache): wire app-layer object cache flow

* refactor(cache): streamline app-layer cache flow

* refactor(cache): tighten cache flow internals

* refactor: address final clippy cleanup

* chore(deps): update quick-xml to 0.41.0

* feat(cache): wire object data cache env config

* fix(cache): gate materialize fill by cache plan

* chore(cache): add object data cache benchmark gate

* fix(cache): guard object cache fill size mismatches

* refactor(cache): streamline object cache body planning

* fix(cache): align object cache rollout config

* test(cache): cover buffered object cache benchmark

* test(cache): isolate object cache benchmark metrics

* test(cache): mark materialize rollout experimental

* test(cache): tighten object cache benchmark gate

* fix(cache): address review findings for object data cache

- singleflight: clean up leader entry on cancellation (Drop impl) so a dropped GET future can no longer wedge all subsequent fills for the same key; switch the fill map to a std Mutex and add a regression test

- adapter: honor RUSTFS_OBJECT_DATA_CACHE_ENABLE=true by defaulting to hit_only when no explicit mode is set (explicit mode still wins)

- planner: treat nil version UUIDs as "no value" per repo convention so unversioned objects key under the canonical "null" instead of fragmenting the key space

- multipart: invalidate the object cache on the quota-exceeded rollback delete after complete-multipart, closing a stale-cache window

- layering: move the disabled-cache fallback into app::context and drop the new infra->app layer-dependency baseline entry

* fix(cache): close invalidation races and drop full-cache scan on writes

- index: make identity-index insert/remove/prune atomic via starshard compute_if_present/compute_if_absent so concurrent fills can no longer drop each other's keys (lost keys made entries unreachable to invalidation until TTL); add a concurrency regression test

- fill: register the key in the identity index before the entry becomes visible in the cache and re-check the index afterwards, undoing the fill when an invalidation raced in between (new skipped_invalidation_race fill result)

- invalidate: with the index now authoritative, remove the full-cache iter() fallback that made every PUT/DELETE of a never-cached object O(total cache entries) (two scans per PUT, 2N per batch delete)

- materialize-fill: fail the GET instead of falling back to the partially consumed stream after a mid-read error (the fallback would send a body missing its prefix under a full-length Content-Length), and log the same size-mismatch warning as the sibling buffering paths

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(storage): fix media-dependent buffer clamp expectation

test_concurrency_manager_multi_factor_strategy_buffer_clamp asserted media_cap.min(MI_B), but the implementation's final safety clamp is [32KiB, media_cap.max(MI_B)] — deliberately so a media cap above 1MiB (NVMe's 2MiB default) stays effective. The test only passed on machines detected as SSD/Unknown (cap == 1MiB) and failed on NVMe-backed CI runners with 2MiB != 1MiB. Assert the media cap itself, which is what the strategy actually guarantees on every environment.

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(storage): format buffer clamp assertion

* chore(logging): update tier guardrail path

---------

Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <cxymds@gmail.com>
Co-authored-by: overtrue <anzhengchao@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
2026-07-03 18:11:14 +08:00
houseme 25d80d7c60 feat(storage): harden internode data-path controls (#4224)
* fix(rio): propagate http writer shutdown errors

* fix(ecstore): unify remote lock rpc deadlines

* fix(storage): reject corrupt read multiple payloads

* feat(rio): add internode http tuning profiles

* feat(metrics): add internode baseline signals

* feat(ecstore): observe shard locality topology

* feat(ecstore): gate shard locality scheduling

* feat(ecstore): gate batch read version rpc

* feat(ecstore): observe batch processor adaptation

* feat(ecstore): gate batch processor observation

* docs: add get benchmark regression analysis

* docs: add issue 797 execution plan status

* fix(ecstore): require explicit batch rpc support

* fix(ecstore): honor documented batch read gate

* fix(ecstore): keep batch read gate stable per call

* chore: update workspace dependencies

* feat(ecstore): log batch read gate decisions

* feat(ecstore): count batch read gate decisions

* test(issue-797): add local internode A/B runner

* test(rio): fix tuning profile spelling fixture

* fix(protocols): adapt sftp channel open callbacks

* fix(metrics): wrap batch processor observation args

* chore(docs): keep issue notes local only

* fix(storage): address internode review feedback

* fix(storage): address internode data-path review findings

- Run the BatchReadVersion auto-mode unary fallback outside the batch
  RPC deadline so each read_version keeps its own per-op timeout and
  health accounting instead of racing the whole batch against one
  drive timeout.
- Cap adaptive batch-processor concurrency growth at a hard multiple
  of the configured baseline so sustained fast batches cannot ratchet
  past the configured limit.
- Parse RUSTFS_INTERNODE_HTTP_* tuning, RUSTFS_BATCH_PROCESSOR_ADAPTIVE,
  and RUSTFS_METADATA_BATCH_READ once per process instead of re-reading
  the environment on hot paths.
- Skip shard read-cost collection in observe mode when stage metrics
  are disabled, and cache the local endpoint host list instead of
  rebuilding it on every read.
- Allow --warp-extra-args values starting with -- and drop the unused
  warp_hosts_csv helper in the issue-797 A/B runner.

* fix(storage): address internode data-path review findings

- Run the BatchReadVersion auto-mode unary fallback outside the batch
  RPC deadline so each read_version keeps its own per-op timeout and
  health accounting instead of racing the whole batch against one
  drive timeout.
- Cap adaptive batch-processor concurrency growth at a hard multiple
  of the configured baseline so sustained fast batches cannot ratchet
  past the configured limit.
- Parse RUSTFS_INTERNODE_HTTP_* tuning, RUSTFS_BATCH_PROCESSOR_ADAPTIVE,
  and RUSTFS_METADATA_BATCH_READ once per process instead of re-reading
  the environment on hot paths.
- Skip shard read-cost collection in observe mode when stage metrics
  are disabled, and cache the local endpoint host list instead of
  rebuilding it on every read.
- Allow --warp-extra-args values starting with -- and drop the unused
  warp_hosts_csv helper in the issue-797 A/B runner.

Co-Authored-By: heihutu<heihutu@gmail.com>

* fix(storage): align buffer clamp test with media cap

* fix(ecstore): release optimized read locks before streaming

---------

Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-07-03 17:08:15 +08:00
GatewayJ a9ac5f578d fix(s3tables): harden durable-strong refresh (#4230) 2026-07-03 14:56:30 +08:00
GatewayJ b1582b3391 fix(iam): improve OIDC token exchange diagnostics (#4232) 2026-07-03 14:15:58 +08:00
majinghe 6fd642253a ci: change runner from ubicloud to k8s (#4231) 2026-07-03 13:27:53 +08:00
Zhengchao An 1c36c6b208 fix(config): recover from corrupt server config instead of crashing (#4229) 2026-07-03 13:08:04 +08:00
Zhengchao An ba1f162f0f fix(server): normalize leading and duplicate slashes in object keys (#4228) 2026-07-03 12:50:57 +08:00
Zhengchao An cec3285d23 fix(bucket): notify peers on bucket versioning and metadata config updates (#4227) 2026-07-03 12:40:53 +08:00
Zhengchao An d2c100fd3e fix(filemeta): guard metacache decoding against corrupt length prefixes (#4226) 2026-07-03 12:27:35 +08:00
Zhengchao An c59d4e6f89 fix(ecstore): reject Windows-unreadable object name segments (#4225) 2026-07-03 12:21:04 +08:00
Zhengchao An 19c21b3522 fix(storage): make acknowledged writes durable across power loss (#4221) 2026-07-03 11:57:05 +08:00
Zhengchao An 7329816ed7 test(e2e): add disk fault injection harness and reliability tests (#4223)
Add crates/e2e_test/src/chaos.rs with in-process fault-injection
primitives for a single-node multi-disk RustFS server: take a disk
offline (rename to <dir>.offline), bring it back online, replace a
disk with a fresh empty directory, corrupt an object's erasure shard
(part.* byte flips, xl.meta untouched), and SIGKILL/restart the server
with the same volumes and port.

Add reliability tests on a 4-disk (EC 2+2) topology, verified via
sha256 manifests recorded at write time:

- degraded read/write with one disk offline (incl. multipart object)
- bitrot read-through with two corrupted shards per object
- fresh-disk replacement healed via admin deep heal after a SIGKILL
  restart

Also reuse the shared signed_admin_post helper from chaos.rs in the
heal regression suite instead of a duplicated local copy.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:37:32 +08:00
Zhengchao An cf056b39e3 fix(core-storage): fix critical correctness defects from core-storage reliability audit (#4222)
* fix(core-storage): fix critical correctness defects from core-storage audit

Fixes verified defects found in a deep audit of the core storage path
(erasure coding, disk persistence, quorum, heal, replication resync):

- ecstore/disk: rewrite live xl.meta atomically (temp+rename) in
  delete_versions_internal and write_metadata instead of in-place
  truncate, which exposed torn metadata to concurrent readers and
  crashes on the DeleteObjects hot path
- ecstore/erasure: allow heal to reconstruct from exactly data_shards
  bitrot-verified sources; requiring data_shards+1 made objects
  permanently unhealable after losing parity_shards disks
- ecstore/set_disk: direct-memory inline GET applied the erasure
  distribution permutation twice (shuffled inputs re-indexed through
  distribution), concatenating wrong shards into the response body in
  degraded reads; collect from canonical disk-ordered inputs
- ecstore/set_disk: heal now preserves the committed inline layout
  instead of recomputing it with a hardcoded unversioned threshold,
  which split quorum identity of healed replicas and caused endless
  re-heal churn
- ecstore/replication: resync results channel switched from
  broadcast(1) to mpsc; a lagged broadcast receiver ended the stats
  collector and every subsequent failure went uncounted, letting
  failed resyncs be marked completed
- ecstore/replication: ignore an empty persisted resync checkpoint;
  resuming with one skipped every object and marked the resync
  completed without replicating anything
- ecstore/replication: fix inverted not-found error classification in
  replicate_object/replicate_delete logging paths
- ecstore/erasure: guard decode paths against zero block_size or
  data_shards from corrupt on-disk metadata (divide-by-zero panic)
- ecstore/disk: os::read_dir no longer consumes the entry limit on
  entries it does not return (is_empty_dir misjudgment); create_file
  opens with O_TRUNC to avoid stale trailing bytes
- filemeta: treat Some(nil) version id as a null version in
  matches_not_strict; disk-loaded headers never store None, so the
  mod_time quorum guard for unversioned overwrites never fired and an
  interrupted overwrite could displace the committed version in merge
- filemeta: fix msgpack skip lengths for fixext (missed the ext type
  byte) and ext16/32 (over-skipped) unknown fields
- filemeta: return FileCorrupt instead of usize underflow when
  xl.meta is truncated inside the CRC trailer
- filemeta: surface delete-marker insertion failure in delete_version
  instead of reporting success when the data dir is shared

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(replication): drop duplicate cfg(test) etag import from boundary module

The test module already imports content_matches_by_etag locally, so the
top-level cfg(test) import is unused under -D warnings and fails clippy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:37:02 +08:00
Zhengchao An aecac5c0ae refactor(replication): isolate object decision contracts (#4219) 2026-07-03 11:36:14 +08:00
Zhengchao An b234f9cf98 fix: auto-repair breaking changes from 48b70f6e (#4218)
fix(ecstore): remove unused cfg(test) import of content_matches_by_etag

The #[cfg(test)] use of content_matches_by_etag at module level is
redundant — the test module already imports it locally at line 369.
This was causing a clippy unused-imports error under -D warnings.
2026-07-03 11:35:51 +08:00
Zhengchao An e9af89b40d fix(ecstore): purge orphan empty-directory trees on folder delete (#4220)
fix(ecstore): purge orphan empty-directory trees on folder delete (#4189)

Empty "phantom" folders — on-disk directory trees with no xl.meta
anywhere — show up in listings as common prefixes but cannot be removed
by any S3 call: DeleteObject on the folder key encodes it to __XLDIR__,
finds no metadata, and returns NotFound, which the API layer masks as a
fake 204 while the tree survives on disk.

Make handle_delete_object attempt a guarded purge when a dir-object key
resolves to NotFound:

- sweep every erasure set of every pool (orphan fragments can sit on
  any set, left behind by whichever sets stored the deleted children)
- per set, refuse if ANY disk holds a regular file under the prefix,
  so degraded/healable objects are never destroyed
- remove empty directories children-first with non-recursive deletes;
  a racing PutObject makes the rmdir fail with DirectoryNotEmpty and
  the entry is skipped

This state is unrepresentable on AWS S3 (CommonPrefixes derive only
from real keys; DeleteObject is an idempotent 204), so the purge moves
visible behavior closer to S3. It deliberately goes beyond MinIO, whose
DeleteObject leaves these trees stranded and whose heal only removes
dangling (minority-presence) dirs — a long-standing complaint
(minio/minio#15257, #19516, #18444).

Also move the test-only content_matches_by_etag import in
replication_target_boundary.rs into the cfg(test) module; it failed
cargo clippy -D warnings and blocked the pre-PR gate.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 11:34:43 +08:00
Zhengchao An fb0e2d6d8f refactor(plugins): harden target-plugin system and admin plugin contract (#4217) 2026-07-03 10:18:21 +08:00
Zhengchao An c260a2f20f refactor(replication): isolate queue boundary adapters (#4216) 2026-07-03 09:43:31 +08:00
Zhengchao An 48b70f6e4f refactor(replication): isolate resync boundary adapters (#4215) 2026-07-03 08:59:42 +08:00
Zhengchao An 38cdbf3939 fix(ecstore): scope replication boundary etag import to test module (#4214)
content_matches_by_etag is only used inside the #[cfg(test)] module, so
the lib-scope import from #4211 fails cargo clippy -D warnings on every
non-test build.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 08:58:56 +08:00
Zhengchao An 65849740f5 fix(test): deflake capacity dirty scope test global state contamination (#4213)
fix(test): use subset check for capacity dirty scope test to handle global state contamination

The data_movement_put_object_marks_dirty_disks_for_capacity_manager test
asserts exact dirty disk count, but the global dirty scope registry is
process-wide. Other concurrent tests can add entries between the drain
and the assertion, causing flaky failures (8 instead of expected 4).

Use subset assertion to verify the expected disks are present without
being sensitive to entries from other tests.
2026-07-03 08:19:32 +08:00
Zhengchao An d1a2dec18e fix(object-capacity): resolve clippy type_complexity lint in test code (#4212)
fix(object-capacity): simplify config getter test type
2026-07-03 08:18:59 +08:00
Zhengchao An 31df11beb7 refactor(replication): isolate multipart and target adapters (#4211)
* refactor(replication): isolate multipart part planning

* refactor(replication): isolate target head adapters
2026-07-03 08:06:17 +08:00
Zhengchao An 8ac6bfcef6 fix: auto-repair clippy type_complexity failure from 83c32a7a (#4210) 2026-07-03 06:35:11 +08:00
Zhengchao An 2d4f5bfb8a fix: suppress clippy type_complexity lint in capacity_manager test helper (#4209) 2026-07-03 06:34:59 +08:00
cxymds 83c32a7a40 fix(lifecycle): avoid blocking expiry enqueue (#4197) 2026-07-03 00:45:34 +08:00
Henry Guo 782e73ca92 feat(table-catalog): add maintenance audit timeline (#4207)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-03 00:36:17 +08:00
Zhengchao An 9dfeffc4c1 test: prune redundant cases and add high-risk coverage across crates (#4208)
Remove or consolidate 57 test cases that cannot catch regressions
(literal-constant asserts, construct-then-assert, derived-serde
round-trips, near-duplicate env/getter matrices) in common, config,
iam, madmin, and object-capacity, keeping all wire-format and
error-path guards. Add 13 tests for previously uncovered high-risk
behavior: filemeta version-sort determinism and merge resilience to
garbage headers, zip extraction path-traversal rejection and exact
limit boundaries, JWT tampered-signature rejection, and the bytes
variant of dual-key (rustfs/minio) metadata fallback and precedence.

Test-only change; no production code touched.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:35:37 +08:00
Zhengchao An dd6b5525e4 fix(ecstore): remove reachable panics in tiering, replication, and heal paths (#4205)
* fix(ecstore): remove reachable panics in tiering, replication, and heal paths

- Parse x-amz-expiration leniently in tier PUT responses; any lifecycle
  rule on the remote tier bucket returns an RFC1123 date that the previous
  ISO8601 unwrap turned into a panic of the ILM transition worker
- Skip invalid user-metadata header values (with a warning) when building
  tier and replication PUT headers instead of panicking on non-ASCII input
- Heal: tolerate absent data_dir for delete markers and remote objects
- transition_object: don't unwrap version_id on unversioned buckets when
  recording partial writes for offline disks
- Admin server info: use port_or_known_default() so default-port (80/443)
  endpoints don't panic is_server_resolvable
- Tier ListObjectsV2 client: decode response body with from_utf8_lossy
- walk_internal: log merge_entry_channels errors instead of dropping them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ecstore): fail heal explicitly when data_dir is missing

Address review feedback: unwrap_or_default() silently substituted a nil
UUID when latest metadata lacked data_dir. Delete markers and remote
objects legitimately have no data_dir and skip the data-heal block, but
for a regular object a missing data_dir means corrupt metadata — return
FileCorrupt with a descriptive log instead of building part paths under
a nil UUID directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:14:11 +08:00
Zhengchao An 3779e674a8 ci(s3-tests): add mint workflow, multi-node sweep, and API coverage tool (#4206)
Follow-ups to the compatibility harness rework:

- Weekly full sweep now runs as a matrix over both topologies: single
  node and the 4-node distributed cluster. Manual dispatch keeps the
  test-mode input.
- New mint workflow (weekly + dispatch) runs MinIO Mint against RustFS:
  functional suites of real client SDKs and tools (awscli, mc,
  aws-sdk-*, minio-*, s3cmd, ...), catching client-specific signing and
  streaming edge cases that boto3-only ceph/s3-tests cannot. Report-only
  for test failures with a per-suite summary table; fails only when mint
  produces no results.
- New scripts/s3-tests/api_coverage.py quantifies API surface coverage
  by diffing the s3s S3 trait (at the Cargo.toml pinned revision)
  against the methods overridden in impl S3 for FS, distinguishing
  NotImplemented defaults from delegating defaults (e.g. post_object).
  Current state: 76/101 operations covered (75 overridden, 1 delegated).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:00:34 +08:00
Zhengchao An 3eeb459ece ci(s3-tests): pin upstream suite, add weekly full sweep and compat report (#4204)
Reworks the S3 compatibility test harness for reproducibility and faster
feedback:

- Pin ceph/s3-tests to a fixed commit (S3TESTS_REV, fetch-by-SHA) so the
  449-test PR gate is reproducible; previously every run cloned upstream
  master, letting test renames or assertion changes break CI silently.
- PR gate (ci.yml s3-implemented-tests): MAXFAIL=0 + XDIST=4 so a single
  CI round reports every failure in parallel instead of stopping at the
  first one serially.
- Add TEST_SCOPE=all to run.sh to run the entire upstream suite, and
  report_compat.py to diff junit results against the classification
  lists (regressions, promotion candidates, unclassified tests).
- Rewrite e2e-s3tests.yml: delegate execution to run.sh (single source
  of truth; also fixes the broken config generation that left S3_PORT
  empty), add a weekly scheduled full sweep that fails only on whitelist
  regressions, and fix the multi-node topology to a real distributed
  cluster (endpoint-style RUSTFS_VOLUMES) instead of four independent
  single-node stores behind a load balancer.
- Docs: rewrite stale .github/s3tests/README.md (marker-era strategy),
  update scripts/s3-tests/README.md, fix dead build_testexpr.sh
  reference in S3_COMPAT_WORKFLOW.md, drop legacy non_standard_tests.txt.

All 747 classified test names verified present at the pinned revision;
13 upstream tests are currently unclassified and will surface in the
first full-sweep report.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:43:19 +08:00
Henry Guo 31c026dd4f feat(table-catalog): add maintenance quarantine operations (#4201)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 23:30:44 +08:00
Zhengchao An d1db9a10cd chore(docs): refresh agent docs, guard doc paths, archive plans (#4203)
Agent-instruction and architecture docs had drifted from the code:

- CLAUDE.md: slim to commands + pointers; fix wrong claim that
  `make pre-commit` is the full pre-PR gate (that is `make pre-pr`);
  drop stale pre-#3929 file paths and merged bug narratives
- AGENTS.md: drop dead `rust-refactor-helper` skill rule and the
  hand-maintained (already stale) scoped-AGENTS index; link
  architecture docs from Sources of Truth
- .github/AGENTS.md: replace the outdated copied CI command matrix
  with a pointer to ci.yml
- crates/AGENTS.md: merge duplicated Testing sections
- ARCHITECTURE.md: resolve the utils->config contradiction (edges are
  removed), mark volatile counts as snapshots, fix a bad path
- docs/architecture: add README router; move one-shot plans/trackers
  (rebalance-decommission phases, migration-progress ledger, PR
  template) to docs/superpowers/plans with archive headers; fix stale
  source paths in kept inventories (core/sets.rs, core/pools.rs,
  store/mod.rs, startup_* split from #3671)
- docs/operations/tier-ilm-debugging.md: extracted tier debugging
  playbook with corrected paths
- scripts/check_doc_paths.sh: new guard failing pre-commit/pre-pr when
  instruction/architecture docs reference nonexistent file paths
- .claude/skills: add tier-debug and arch-checks repo skills;
  .gitignore now keeps .claude/skills and docs/operations committable

Verification: ./scripts/check_doc_paths.sh,
./scripts/check_architecture_migration_rules.sh (both pass)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:30:13 +08:00
Zhengchao An 29899f4731 refactor(replication): isolate queue backpressure decisions (#4202) 2026-07-02 23:28:45 +08:00
Zhengchao An a9894843d9 ci: remove artificial build throttles and fix caching gaps (#4200)
Speeds up the CI pipeline without changing what is tested:

- Drop the global CARGO_BUILD_JOBS=2 and per-job --jobs 2 flags, which
  halved compiler parallelism on the 4-core runners.
- Stop embedding hashFiles(Cargo.lock) in rust-cache shared keys. The
  action already fingerprints lockfiles internally; putting the hash in
  the key prefix meant any PR that touched Cargo.lock started from a
  completely cold cache instead of reusing unchanged dependencies.
- Give the e2e-tests job the full setup action with dependency caching.
  Its migration-proof step compiles the e2e_test crate (which pulls in
  most of the workspace) and previously did so cold on every run with
  no cache, on a 2-core runner.
- Set profile.dev debug = "line-tables-only": keeps usable backtraces
  while cutting compile/link time, target-dir size (faster cache
  save/restore), and debug-binary artifact upload/download.
- Split fmt + repo-script checks into a compile-free quick-checks job
  on ubuntu-latest so contributors get feedback in ~1 minute instead
  of after the full test run.
- Collapse the five per-filter migration-proof cargo test reruns into
  one filtered nextest invocation; the same tests already run in the
  full nextest pass.
- Remove the touch rustfs/build.rs + forced rebuild from the debug
  binary jobs (version stamping is irrelevant for e2e binaries) and
  the unreachable Windows cross-compile branch in build.yml.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:27:28 +08:00
cxymds f9cf4ff2bf fix(ecstore): surface multipart cleanup delete errors (#4198) 2026-07-02 22:36:48 +08:00
cxymds 5404ab155b fix(ecstore): precheck decommission capacity (#4196) 2026-07-02 22:36:29 +08:00
cxymds cf33bcc742 fix(ecstore): hold read locks for streaming readers (#4195) 2026-07-02 22:36:12 +08:00
cxymds 91a02914cc fix(ecstore): retry multipart abort cleanup (#4194) 2026-07-02 22:35:56 +08:00
cxymds d1a6033191 fix(ecstore): recheck source cleanup under lock (#4193) 2026-07-02 22:35:31 +08:00
Henry Guo 962c20bf47 feat(table-catalog): deepen maintenance rewrite support (#4192)
* feat(table-catalog): deepen maintenance rewrite support

* fix(ecstore): satisfy replication boundary clippy lint

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 22:35:07 +08:00
cxymds f8f373a236 fix: preserve compacted bucket counts (#4183)
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-07-02 22:34:47 +08:00
cxymds 4dc75b8760 fix: backpressure replication mrf saves (#4180) 2026-07-02 22:34:30 +08:00
cxymds c7a0178994 fix: honor replication status metadata (#4178)
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-07-02 22:33:41 +08:00
cxymds 6ed5c8fcd6 fix: fail unsupported replication actions (#4182)
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-07-02 22:33:16 +08:00
Zhengchao An 9a142fb123 refactor(replication): move delete schedule decisions (#4199) 2026-07-02 22:11:29 +08:00
houseme 70e1d79dfd feat: replace jemalloc with mimalloc (#4174)
* feat: replace jemalloc with mimalloc

* docs: record allocator rounds5 retest

* fix(replication): satisfy clippy unwrap lints

* docs: keep allocator migration plan local only

* feat(profiling): rely on pyroscope cpu profiling

* refactor(profiling): centralize unsupported pprof responses

* chore(deps): update s3s revision
2026-07-02 19:03:38 +08:00
Rafael Peroco 54496f2796 fix(scanner): advance cycle counter when a cycle ends on budget (#4163) 2026-07-02 17:15:51 +08:00
Zhengchao An 902bfa554b fix: collapse nested if to satisfy clippy::collapsible_if lint (#4190) 2026-07-02 17:15:15 +08:00
Zhengchao An b972e25c51 refactor(replication): move app decisions into crate (#4191) 2026-07-02 17:14:55 +08:00
cxymds 902cf1d3dc fix: handle scanner task failures (#4185) 2026-07-02 16:34:56 +08:00
Henry Guo 14c0fca576 test(table-catalog): extend live engine conformance probes (#4184)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 16:34:37 +08:00
cxymds b21b27dddd fix: preserve scanner usage backups (#4179) 2026-07-02 16:33:59 +08:00
cxymds d6a9826949 fix: preserve scanner usage state (#4177) 2026-07-02 16:32:51 +08:00
cxymds 71e9e81257 fix: finalize replication resync workers (#4175) 2026-07-02 16:32:33 +08:00
Zhengchao An f0adabbe90 refactor(replication): move object config bridge (#4188)
* refactor(replication): move object config bridge

* refactor(replication): centralize log constants
2026-07-02 16:32:02 +08:00
majinghe 3533080d4a ci: add self host runner on k8s (#4181) 2026-07-02 15:55:36 +08:00
Zhengchao An 1a9952c3fe refactor(replication): move target option builders (#4186) 2026-07-02 15:54:48 +08:00
Zhengchao An 008b7fcb6f refactor(replication): move heal queue routing contracts (#4176)
* refactor(replication): move heal queue routing contracts

* fix(replication): satisfy feature clippy
2026-07-02 15:15:08 +08:00
Zhengchao An 98a0cca4a2 refactor(replication): move resync decision contracts (#4173)
* refactor(replication): move resync decision contracts

* fix(replication): avoid resync status clones
2026-07-02 14:13:05 +08:00
Zhengchao An 4615df006d fix: auto-repair clippy failures from d666028cdc (#4172)
fix: simplify match arms to satisfy clippy manual-unwrap-or-default and manual-unwrap-or

Two match expressions in crates/replication/src/runtime.rs triggered
clippy lints (manual-unwrap-or-default, manual-unwrap-or) with the
project's -D warnings policy. Replace them with the idiomatic
.unwrap_or_default() and .unwrap_or() calls.
2026-07-02 14:11:44 +08:00
Henry Guo ba0276dd6f feat(table-catalog): harden maintenance scheduler control (#4164)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-07-02 14:11:07 +08:00
Zhengchao An 48f8443626 refactor(replication): move object compare contracts (#4171) 2026-07-02 13:19:47 +08:00
Zhengchao An d666028cdc refactor(replication): move resync classifiers into crate (#4170) 2026-07-02 12:57:59 +08:00
Zhengchao An 953a080b37 refactor(replication): move runtime sizing contracts (#4169) 2026-07-02 12:33:11 +08:00
Zhengchao An 473132f36b refactor(replication): move stats contracts into crate (#4168) 2026-07-02 12:14:36 +08:00
Zhengchao An 8ce0f824cf refactor(replication): move queue contracts into crate (#4167) 2026-07-02 11:40:49 +08:00
Zhengchao An 2cf856a51d Move replication delete worker contract into crate (#4166)
refactor(replication): move delete worker contract into crate
2026-07-02 11:03:24 +08:00
Zhengchao An df6ce41acd Move replication operation contracts into crate (#4165)
refactor(replication): move operation contracts into crate
2026-07-02 10:48:02 +08:00
Zhengchao An b57820a486 refactor(replication): move config contracts into crate (#4162) 2026-07-02 10:17:48 +08:00
Zhengchao An 18b79ccc2a refactor(replication): route filemeta paths through crate (#4161) 2026-07-02 09:08:47 +08:00
houseme 7a075c91da perf: avoid eager parity reader setup (#4133) 2026-07-02 08:49:08 +08:00
Zhengchao An 18d89d1e31 refactor(replication): route metadata contracts through crate (#4160) 2026-07-02 08:48:42 +08:00
Zhengchao An 0f4f4a6367 fix(replication): avoid target status map allocation (#4159) 2026-07-02 05:08:42 +08:00
Zhengchao An e05c281176 refactor(replication): centralize object replication state parsing (#4158)
* refactor(replication): centralize object replication state parsing

* fix(replication): gate test-only msgpack import
2026-07-02 04:41:54 +08:00
Zhengchao An 2de38d3e5c refactor(replication): move mrf wire format to replication crate (#4157)
* refactor(replication): move mrf wire format to replication crate

* refactor(replication): match re-exported error variants
2026-07-02 04:27:53 +08:00
Zhengchao An 8f01012071 fix(replication): address resync review follow-up (#4155)
* fix(replication): address resync crate review

* fix(replication): skip fixext fields correctly

* fix(replication): satisfy resync clippy
2026-07-02 04:00:28 +08:00
Zhengchao An e80391f1aa refactor(replication): extract resync contracts crate (#4154) 2026-07-02 01:30:01 +08:00
Zhengchao An 00e93f8267 fix(storage): tighten replication handle visibility (#4153) 2026-07-02 00:57:08 +08:00
GatewayJ 7728c9f203 fix(ecstore): require commit quorum for latest metadata (#4117) 2026-07-02 00:41:11 +08:00
Zhengchao An 572a001f93 refactor(storage): hide replication runtime handles (#4152) 2026-07-02 00:40:42 +08:00
Zhengchao An 14016dbe8c refactor(scanner): hide replication queue DTOs (#4150) 2026-07-02 00:17:03 +08:00
Zhengchao An 644a472e52 refactor(obs): hide replication stats handle (#4149) 2026-07-01 23:37:23 +08:00
Zhengchao An 2f5e4a8de4 fix: auto-repair breaking changes from a2ec13ab0 (#4148) 2026-07-01 23:16:52 +08:00
Zhengchao An b43655ccdd refactor(app): hide replication object DTOs (#4147) 2026-07-01 23:02:00 +08:00
Henry Guo a305160087 feat(table-catalog): add maintenance scheduler guardrails (#4123) 2026-07-01 22:31:19 +08:00
GatewayJ f7769884ff fix(lifecycle): honor expired delete marker semantics (#4124) 2026-07-01 22:31:08 +08:00
GatewayJ a60b6310a7 fix(lifecycle): block actions on replication state (#4125) 2026-07-01 22:31:00 +08:00
Zhengchao An b94e7d514d refactor(admin): hide replication resync DTOs (#4146) 2026-07-01 22:18:06 +08:00
majinghe d79f6872d7 ci: add self host runner support for build workflow (#4143) 2026-07-01 22:17:14 +08:00
majinghe a2ec13ab04 fix(otel): fix container unhealty issue caused by healthy check (#4145)
* fix: fix container unhealty issue caused by healthy check

* update readme file

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-07-01 21:56:26 +08:00
Zhengchao An 22cbd41ee9 refactor(ecstore): add replication owner bridges (#4141)
* refactor(ecstore): add replication owner bridges

* fix(ecstore): tighten replication owner guards
2026-07-01 21:51:26 +08:00
loverustfs f7e3fc780f ci: migrate artifact upload step to cloudflare r2 (#4142) 2026-07-01 20:04:31 +08:00
Zhengchao An c0dfc260ca refactor(ecstore): add replication object bridge (#4140) 2026-07-01 19:24:20 +08:00
Zhengchao An e5ab6ee8e4 refactor(ecstore): make replication facade explicit (#4139) 2026-07-01 18:54:05 +08:00
Zhengchao An fc806679fb refactor(ecstore): add replication work bridges (#4137) 2026-07-01 18:37:01 +08:00
Zhengchao An 28e87c1cab refactor(ecstore): name replication boundary contracts (#4134) 2026-07-01 17:55:12 +08:00
Zhengchao An f5adb5abc1 fix: bypass OnceLock in test for metadata early-stop gates (#4135) 2026-07-01 17:51:06 +08:00
Zhengchao An e8326e76a6 fix(ecstore): harden replication runtime guard (#4132) 2026-07-01 17:14:38 +08:00
houseme b16120dbcc feat: optimize small GET read paths (#4022) 2026-07-01 15:40:00 +08:00
Zhengchao An b0f491549a refactor(ecstore): hide replication runtime types (#4131) 2026-07-01 15:39:02 +08:00
Zhengchao An 8fd532ebb7 refactor(ecstore): use local replication paths (#4130) 2026-07-01 15:08:26 +08:00
Zhengchao An 7cb42529fc refactor(ecstore): route replication config access (#4128) 2026-07-01 09:30:25 +08:00
cxymds 6e5c58ca3f fix(replication): harden bucket replication correctness (#4116) 2026-07-01 09:21:30 +08:00
Zhengchao An 722c0805ce refactor(ecstore): route replication errors (#4127) 2026-07-01 08:13:57 +08:00
Zhengchao An 50a3ed8bf3 refactor(ecstore): route replication event host (#4126) 2026-07-01 06:00:50 +08:00
Zhengchao An e917f1f4be refactor(ecstore): route replication filemeta contracts (#4122) 2026-07-01 01:01:01 +08:00
Zhengchao An 942a8b505d refactor(ecstore): route replication metadata paths (#4121) 2026-06-30 22:48:34 +08:00
唐小鸭 4efefd67b5 fix(site-replication): site replication flag issue and resolved the replication storm (#4120)
* fix(site-replication): Add Docker Compose setup for site replication testing

- Fixed the site replication flag issue and resolved the replication storm.
- Introduced a new directory for site replication tests with Docker Compose.
- Created `docker-compose.yml` to define three RustFS sites and a setup container.
- Added `README.md` to document the purpose, usage, and test flow of the replication setup.
- Implemented `run-object-flow-check.sh` script to verify object replication across sites.
- Configured health checks and volume permissions for the RustFS containers.
- Enabled customization of access keys, bucket names, and other parameters via environment variables.

* fix

* fix
2026-06-30 21:52:06 +08:00
Henry Guo 5e68455eed test(table-catalog): add disaster recovery rehearsal probes (#4119)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-30 21:27:06 +08:00
Zhengchao An da7f421f69 refactor(ecstore): route replication storage contracts (#4118) 2026-06-30 20:12:06 +08:00
cxymds f9340da74a fix(lifecycle): harden S3 lifecycle rule handling (#4115)
* fix(lifecycle): honor noncurrent version retention

* fix(lifecycle): validate transition rules

* fix(lifecycle): support immediate multipart abort

* fix(lifecycle): harden select restore metadata

* fix(lifecycle): expose stale multipart cleanup to app

* fix(lifecycle): reduce expiry helper arguments
2026-06-30 19:06:34 +08:00
wood 7484a61fa3 feat(compression): show cluster-level compression stat in grafana (#4112) 2026-06-30 19:05:05 +08:00
Henry Guo cfb24b31ea test(table-catalog): add live client conformance harness (#4110)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-30 16:33:14 +08:00
Zhengchao An 70a1f37508 refactor(ecstore): route replication target types (#4114) 2026-06-30 16:29:05 +08:00
cxymds c67eea7bfa fix(ecstore): preserve rename data rollback metadata (#4107) 2026-06-30 15:45:27 +08:00
Zhengchao An 20919f0b97 refactor(ecstore): route replication bandwidth wrapping (#4113) 2026-06-30 15:42:04 +08:00
Zhengchao An b0dbf35cd4 refactor(ecstore): route lifecycle metadata reads (#4111) 2026-06-30 15:22:12 +08:00
Zhengchao An 4538919efa refactor(ecstore): route runtime getters through facade (#4109) 2026-06-30 14:32:01 +08:00
Zhengchao An 143833ec45 test(architecture): guard rustfs owner statics (#4108) 2026-06-30 14:01:19 +08:00
houseme 22c7cb1923 feat(ecstore): refresh issue-787 phase-3 validation track (#4106)
* feat: isolate list objects quorum config

* perf: add list gather observability for issue-785

* feat: add list-path observability for storage layers

* chore: add issue 785 list observability acceptance runner

* fix: avoid moved fields in list_path logging

* test: add marker replay idempotence checks for list pagination

* feat(ecstore): add list marker v2 cache id

* Revert "feat(ecstore): add list marker v2 cache id"

This reverts commit 980c364e02.

* Reapply "feat(ecstore): add list marker v2 cache id"

This reverts commit 766d2cee65.

* feat(ecstore): propagate list cache cursor in list layers

* test: add versioned continuation fallback tests

* test: add live pagination continuity smoke mode

* chore: run cargo fmt on issue-4003 branch

* build: fix bash3 compatibility in live smoke

* test(ecstore): simplify bool marker test assertion

* refactor(ecstore): unify list pagination state machine

* test: add issue 787 quorum validation runner

* build: support bash32 in issue-787 runner

* docs: add issue-787 benchmark evidence
2026-06-30 11:37:44 +08:00
GatewayJ 4815d608a2 fix(ecstore): preserve recovery quorum identity (#3937)
* fix(ecstore): preserve recovery quorum identity

* fix(get): hold read permits through response streaming

* fix(heal): queue read repair at low priority

* fix(ecstore): harden recovery read validation
2026-06-30 11:19:46 +08:00
Zhengchao An 7041d60cbf refactor(storage): narrow root runtime facade (#4105) 2026-06-30 11:19:21 +08:00
Zhengchao An f6689f5b39 test(app): remove object store fallback (#4104) 2026-06-30 08:08:54 +08:00
Zhengchao An 27e1cb5ffd refactor(storage): narrow object store resolver boundary (#4103) 2026-06-30 07:56:36 +08:00
Zhengchao An 9a8a82e90c test(architecture): guard owner cache globals (#4102) 2026-06-30 07:17:51 +08:00
Zhengchao An 733904deb1 test(architecture): guard owner runtime globals (#4101) 2026-06-30 06:54:07 +08:00
Zhengchao An f0d27982e5 refactor(app): collapse transition stats boundary (#4100) 2026-06-30 06:48:09 +08:00
Zhengchao An 46edc118f0 refactor(app): add lifecycle state context boundary (#4099) 2026-06-30 06:26:06 +08:00
Zhengchao An 055d3ad825 refactor(ecstore): scope dead code allowances (#4098) 2026-06-30 06:07:45 +08:00
Zhengchao An de19342744 refactor(runtime): finish legacy global naming (#4097) 2026-06-30 05:50:58 +08:00
Zhengchao An 74aef760f0 refactor(lifecycle): normalize state global names (#4096) 2026-06-30 05:29:10 +08:00
Zhengchao An cbd527cd6e refactor(runtime): normalize legacy global names (#4095) 2026-06-30 05:13:06 +08:00
Zhengchao An 90f9c24503 refactor(runtime): clean scalar globals (#4094) 2026-06-30 04:52:23 +08:00
Zhengchao An 77e45896ba refactor(runtime): remove unused node names global (#4093) 2026-06-30 04:44:17 +08:00
Zhengchao An c279d34af6 refactor(runtime): batch owner boundary guards (#4092) 2026-06-30 04:34:54 +08:00
Zhengchao An 19dc019038 test(runtime): guard kms service manager global (#4086) 2026-06-30 04:17:24 +08:00
Zhengchao An 90ea66423e test(runtime): guard internode transport global (#4085) 2026-06-30 04:11:30 +08:00
Zhengchao An 47b12972ad refactor(lifecycle): add object lock boundary (#4084) 2026-06-30 04:11:03 +08:00
houseme 3d868cb6e8 perf: add list objects quorum tuning knob (#4072) 2026-06-30 03:41:29 +08:00
Zhengchao An 3b754f649d test(runtime): guard batch processor global (#4083) 2026-06-30 03:40:03 +08:00
Zhengchao An f99429f2f2 refactor(lifecycle): add tagging boundary (#4082) 2026-06-30 03:39:32 +08:00
Zhengchao An fb7bcfb541 refactor(replication): add tagging boundary (#4081) 2026-06-30 03:39:17 +08:00
Zhengchao An 4f335889c1 refactor(lifecycle): add config persistence boundary (#4080) 2026-06-30 02:10:12 +08:00
Zhengchao An fab78f8402 refactor(scanner): use expiry runtime boundary (#4079) 2026-06-30 02:09:52 +08:00
Zhengchao An 35d7d6bf23 refactor(ecstore): wrap background cancel token access (#4078) 2026-06-30 02:09:39 +08:00
Zhengchao An d3a84395dc refactor(replication): add msgp boundary (#4076) 2026-06-30 02:09:14 +08:00
Zhengchao An 74c95920ca fix: dual-write renamed IO metrics (#4077) 2026-06-30 02:08:58 +08:00
Zhengchao An f401a4388e fix(ecstore): tolerate OnceLock re-initialization in tests (#4074) 2026-06-30 00:18:28 +08:00
Zhengchao An c17df6e3d3 refactor(replication): add lock boundary (#4075) 2026-06-30 00:18:08 +08:00
Zhengchao An 50ca1c2cbe refactor(replication): add config store boundary (#4073) 2026-06-30 00:03:45 +08:00
Zhengchao An f8c70e017d refactor(replication): add versioning boundary (#4071) 2026-06-29 23:52:36 +08:00
Zhengchao An 04e8c1bb6b refactor(replication): add metadata boundary (#4070) 2026-06-29 23:24:39 +08:00
Zhengchao An f1ec0bd765 refactor(replication): add target boundary (#4069) 2026-06-29 22:59:10 +08:00
Zhengchao An c94f2981d7 refactor(replication): add event sink boundary (#4068) 2026-06-29 22:46:06 +08:00
Zhengchao An 13474ec1de refactor(replication): add runtime boundary (#4067) 2026-06-29 22:34:10 +08:00
Zhengchao An e9a89e13ae refactor(lifecycle): route events through audit sink (#4066) 2026-06-29 22:21:23 +08:00
Zhengchao An 806dfb233c test(ecstore): guard set disks contract coverage (#4065) 2026-06-29 22:06:21 +08:00
Zhengchao An 7c2789e75a refactor(runtime): guard endpoint port source (#4064) 2026-06-29 21:56:57 +08:00
Zhengchao An a5e7dda6d9 refactor(io): make aligned pread error primary (#4063) 2026-06-29 21:46:20 +08:00
Zhengchao An 158f4a32a1 refactor(io): deprecate legacy method names (#4062) 2026-06-29 21:31:37 +08:00
Zhengchao An a7dd9603e9 refactor(ecstore): add mmap copy disk read name (#4061) 2026-06-29 21:18:41 +08:00
Zhengchao An 9567e7b3be refactor(io): make accurate reader names primary (#4060) 2026-06-29 20:20:22 +08:00
GatewayJ 14e9fa2c8d fix(ecstore): avoid startup pool meta locks (#4056) 2026-06-29 20:19:38 +08:00
Zhengchao An 50c9078142 refactor(runtime): guard scalar lock globals (#4059) 2026-06-29 20:04:44 +08:00
Zhengchao An 27a491a730 refactor(runtime): guard replication stats globals (#4058) 2026-06-29 19:55:38 +08:00
Zhengchao An 9b6c6df81b refactor(runtime): guard local node globals (#4057) 2026-06-29 19:47:21 +08:00
Zhengchao An 35077c033c refactor(runtime): guard endpoint erasure globals (#4055) 2026-06-29 19:26:05 +08:00
Zhengchao An 636476c3e5 refactor(runtime): guard lifecycle event globals (#4053) 2026-06-29 17:56:02 +08:00
Zhengchao An 0c4deb0e2f refactor(runtime): guard setup type globals (#4052) 2026-06-29 17:38:57 +08:00
Zhengchao An 0cd08eae1c refactor(runtime): remove stale lifecycle state comments (#4051) 2026-06-29 17:29:57 +08:00
Zhengchao An 726bf142e0 refactor(runtime): remove stale tier transport comments (#4050) 2026-06-29 17:29:46 +08:00
Zhengchao An aadefcc577 refactor(runtime): guard local disk set global (#4049) 2026-06-29 17:29:24 +08:00
Zhengchao An 2b04bf3a19 refactor(runtime): remove stale lifecycle global comment (#4048) 2026-06-29 16:33:49 +08:00
Zhengchao An ecbc5385d3 refactor(runtime): hide local disk id global (#4047) 2026-06-29 16:33:36 +08:00
Zhengchao An ad6be6a167 refactor(runtime): hide local disk map global (#4046) 2026-06-29 14:57:14 +08:00
Zhengchao An 4111ce1547 refactor(runtime): wrap tier config global (#4045) 2026-06-29 14:38:36 +08:00
Zhengchao An e012d1e799 refactor(runtime): wrap boot time global (#4044) 2026-06-29 14:21:31 +08:00
Henry Guo 809e77d5db fix(runtime): keep lock clients available during startup (#4042) 2026-06-29 14:14:26 +08:00
Zhengchao An 4567f6f521 refactor(runtime): guard rpc secret global (#4043) 2026-06-29 14:02:03 +08:00
Zhengchao An 6093eb0c9c refactor(runtime): wrap local node name global (#4041) 2026-06-29 13:13:54 +08:00
Zhengchao An 9c62a24efc refactor(runtime): wrap outbound tls globals (#4039) 2026-06-29 12:56:34 +08:00
Henry Guo de41303f3e feat(table-catalog): add durable backing migration guardrails (#4038)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-29 12:39:04 +08:00
Zhengchao An 9414d645f4 refactor(runtime): wrap rustfs address globals (#4037) 2026-06-29 11:18:30 +08:00
Henry Guo c200dc10d2 fix(scanner): preserve partial scan progress (#3996) 2026-06-29 10:43:25 +08:00
Zhengchao An 9165de3241 refactor(runtime): wrap internode connection cache (#4036) 2026-06-29 10:43:13 +08:00
dependabot[bot] df82e3e645 build(deps): bump the dependencies group with 3 updates (#4030)
Bumps the dependencies group with 3 updates: [aes-gcm](https://github.com/RustCrypto/AEADs), [chacha20poly1305](https://github.com/RustCrypto/AEADs) and [arc-swap](https://github.com/vorner/arc-swap).


Updates `aes-gcm` from 0.11.0-rc.4 to 0.11.0
- [Commits](https://github.com/RustCrypto/AEADs/compare/aes-gcm-v0.11.0-rc.4...aes-gcm-v0.11.0)

Updates `chacha20poly1305` from 0.11.0-rc.3 to 0.11.0
- [Commits](https://github.com/RustCrypto/AEADs/compare/chacha20poly1305-v0.11.0-rc.3...chacha20poly1305-v0.11.0)

Updates `arc-swap` from 1.9.1 to 1.9.2
- [Changelog](https://github.com/vorner/arc-swap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vorner/arc-swap/commits)

---
updated-dependencies:
- dependency-name: aes-gcm
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: chacha20poly1305
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: arc-swap
  dependency-version: 1.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-29 10:32:52 +08:00
Zhengchao An 801838fdbd test(ecstore): guard replication facade boundary (#4035) 2026-06-29 10:32:09 +08:00
Zhengchao An 319497446b refactor(ecstore): add lifecycle runtime boundary (#4034)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-29 10:31:49 +08:00
Zhengchao An 5fc25eccb3 feat(io-core): add truthful buffered io aliases (#4032) 2026-06-29 10:01:59 +08:00
Zhengchao An 1e6f20912a test(ecstore): cover SetDisks storage contracts (#4033) 2026-06-29 10:01:42 +08:00
Zhengchao An ea88f5b67b refactor(obs): use lifecycle runtime facades (#4031) 2026-06-29 09:29:50 +08:00
Zhengchao An ed56ffb54a docs(io): clarify zero copy metric transition (#4029) 2026-06-29 08:21:07 +08:00
Zhengchao An 2d547de84a docs(architecture): inventory global state migration (#4028) 2026-06-29 08:20:32 +08:00
cxymds 0ff261864a fix(scanner): harden data scanner integrity handling (#4012)
* fix(scanner): heal metadata scan failures

* fix(scanner): preserve dirty buckets after scan failures

* feat(scanner): record leader lock liveness

* fix(scanner): preserve failed deep scan state

* fix(scanner): reject untimestamped stale usage

* fix(scanner): report topology-derived admission limit

* fix(scanner): accumulate tier usage stats

* fix(scanner): guard data usage cache recursion

* feat(scanner): expose startup enabled status

* fix(scanner): continue after heal admission rejection

* fix(scanner): avoid cyclic root usage double count

* fix(scanner): preserve dirty markers on cache save failure

* feat(scanner): expose leader liveness status

* fix(scanner): avoid heal escalation for transient metadata reads

* feat(scanner): persist rejected heal retry candidates

* test(scanner): satisfy freshness status clippy

* test(app): avoid global store reinit in context test

* test(app): gate global store helper to tests
2026-06-29 08:19:58 +08:00
Henry Guo cf7af17b43 feat(table-catalog): wire durable strong backing config (#3971)
* feat(table-catalog): wire durable strong backing config

* fix(table-catalog): guard strong backing warehouse prefixes

* fix(checksums): remove unused base64 decode helper

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-29 07:58:14 +08:00
Zhengchao An 51d44f20c7 docs(ecstore): inventory replication split blockers (#4027) 2026-06-29 07:57:48 +08:00
Zhengchao An 763cbdd5b7 docs(ecstore): inventory lifecycle split blockers (#4026) 2026-06-29 07:57:22 +08:00
Zhengchao An aaef31d4dd docs(ecstore): inventory api facade boundaries (#4025) 2026-06-29 07:57:01 +08:00
Zhengchao An e1c1d8edcb test: expand snapshot coverage (#4024) 2026-06-29 07:56:37 +08:00
Zhengchao An 1104d630d1 docs(obs): inventory ecstore dependency boundary (#4023) 2026-06-29 07:07:47 +08:00
Zhengchao An 8284e6a75c config: add mmap read env alias (#4021) 2026-06-28 23:24:24 +08:00
Zhengchao An b37c8c6435 build: pin rc crypto dependency versions (#4020) 2026-06-28 22:52:20 +08:00
houseme 0485e5adf0 feat(get): Small-file GET performance optimization for 1KiB-1MiB objects (#4016)
* feat(get): SF01 - bucket validation cache

Add 5s TTL cache for bucket validation to avoid repeated stat_volume()
calls on every GET request.

Changes:
- Add BUCKET_VALIDATED_CACHE (OnceLock + RwLock + HashMap)
- Add invalidate_bucket_validation_cache() for cache invalidation
- Add invalidate_all_bucket_validation_cache() for bulk invalidation
- Update get_validated_store() to use cache
- Add cache invalidation in execute_delete_bucket()

Expected impact: 3-5x improvement for small file GET latency.

Closes rustfs/backlog#766

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(get): SF03 - metadata cache TTL increase

Increase metadata cache TTL from 250ms to 2s and capacity from 1024
to 4096 entries.

Changes:
- GET_OBJECT_METADATA_CACHE_TTL: 250ms -> 2s
- GET_OBJECT_METADATA_CACHE_MAX_ENTRIES: 1024 -> 4096

All mutation paths already call invalidate_get_object_metadata_cache,
so the longer TTL is safe.

Expected impact: 10-50x improvement for hot objects.

Closes rustfs/backlog#768

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(get): SF04 - remove unnecessary tokio::spawn in metadata fanout

Replace tokio::spawn with direct async future in read_all_fileinfo_full_wait.
join_all already provides concurrency, so tokio::spawn adds unnecessary
task creation and scheduling overhead.

Changes:
- Remove tokio::spawn from metadata fanout futures
- Update result handling for direct future results

Expected impact: 16-32us reduction per GET request.

Closes rustfs/backlog#769

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(get): SF06 - conditional lifecycle check

Only call resolve_put_object_expiration when the object has an
x-amz-expiration metadata marker. This avoids unnecessary lifecycle
configuration reads on every GET request.

Expected impact: 50-100us reduction per GET request.

Closes rustfs/backlog#771

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(get): SF07 - conditional metrics recording

Gate hot path metrics behind get_stage_metrics_enabled() to reduce
overhead when metrics are not needed.

Changes:
- Conditional record_zero_copy_read
- Conditional manager.record_disk_operation
- Conditional manager.record_access
- Conditional manager.record_transfer

Expected impact: 20-50us reduction per GET request.

Closes rustfs/backlog#772

Co-Authored-By: heihutu <heihutu@gmail.com>

* refactor(get): SF01 - use moka instead of dashmap for bucket cache

Replace OnceLock + RwLock + HashMap with moka::sync::Cache for bucket
validation cache. moka provides built-in TTL support and is already
available in the workspace.

Changes:
- Add moka dependency to rustfs crate
- Replace manual TTL management with moka's time_to_live
- Simplify cache operations

Closes rustfs/backlog#766

Co-Authored-By: heihutu <heihutu@gmail.com>

* feat(get): SF02 - inline data fast path

Add fast path for small inline objects that bypasses duplex pipe,
tokio::spawn, and bitrot reader creation when data is already in memory.

Changes:
- Add inline data detection before codec streaming gate
- Direct in-memory erasure decode for inline objects <= 128KB
- Add GET_OBJECT_PATH_INLINE_DIRECT metric path
- Skip duplex pipe and background task for inline data

Conditions for fast path:
- Single part object
- Inline data available
- Size <= 128KB
- Not encrypted/compressed/remote
- No range request

Expected impact: 2-3x improvement for small file GET latency.

Closes rustfs/backlog#767

Co-Authored-By: heihutu <heihutu@gmail.com>

* refactor: translate Chinese comments to English

Translate all Chinese comments to English in modified files:
- rustfs/src/storage/ecfs_extend.rs
- rustfs/src/app/bucket_usecase.rs

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix

* add

* fmt and improve import

* fmt

* feat(get): SF05 skip IO planning + refactor inline detection + adaptive bucket cache

SF05: Skip disk I/O semaphore for inline data fast path
- Reorder prepare_get_object_read_execution: read first, then decide semaphore
- Inline objects skip acquire_disk_read_permit() entirely (saves 100-200us)
- Add is_inline_fast_path field to GetObjectReadSetup

Refactor: Unify inline detection logic
- Add ObjectInfo::is_inline_fast_path_eligible() as single source of truth
- Version-aware thresholds: non-versioned 128KB, versioned 16KB (matches PUT)
- Eliminates divergent conditions between set_disk/mod.rs and object_usecase.rs

Refactor: Restore fault tolerance in metadata fanout
- Restore tokio::spawn + JoinError handling in read_all_fileinfo_full_wait
- Prevents single disk read panic from unwinding the entire operation

Refactor: Restore lifecycle check correctness
- Remove incorrect SF06 conditional that skipped lifecycle for most objects
- Always call resolve_put_object_expiration (original behavior)

Fix: make_bucket cache invalidation
- Invalidate bucket validation cache on create_bucket

Fix: erasure decode written validation
- Check decode() return value; error if 0 bytes written for non-empty object

Adaptive bucket cache
- Default: RwLock<HashMap> for < 100 buckets (low overhead)
- Opt-in: starshard::ShardedHashMap via RUSTFS_BUCKET_CACHE_STARSHARD=1
- 5s TTL with manual timestamp checking

Benchmark results (warp get, concurrency 32, 10s, 3 rounds):
- 10KiB: 25.10 MiB/s (+28.2% vs SF01-07)
- 100KiB: 221.81 MiB/s
- 1MiB: 1972.78 MiB/s
- vs main: -10% to -12% (inline path not triggered by warp)

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(versioning): use read lock for versioning config query + five-expert analysis

P0 fix: BucketVersioningSys::get() was using write lock on
GLOBAL_BucketMetadataSys for a pure read operation. This serialized
all concurrent GET requests (3 write-lock acquisitions per request).

Changed to read lock — get_versioning_config() handles its own
internal locking via metadata_map RwLock.

Five-expert analysis identified top bottlenecks:
1. Versioning write lock (P0, fixed)
2. Inline fast path not triggered (P0, needs verification)
3. Metadata fanout no early-stop (P1, early-stop has bug, reverted)
4. Request-level versioning cache (P1, pending)
5. Duplex pipe for small objects (P2, pending)

Benchmark (read-lock fix, warp concurrency 32):
- 1KiB: 2.29 MiB/s (vs 2.53 before, within variance)
- 10KiB: 25.00 MiB/s (same as before)
- 100KiB: 246.72 MiB/s (+11% vs 221.81)
- 1MiB: 2039.95 MiB/s (+3% vs 1972.78)

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore: remove benchmark results from git, keep locally only

Remove docs/benchmark/*.md from version control.
Files remain on disk but are no longer tracked by git.
Added docs/benchmark/*.md to .gitignore.

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(get): decode inline fast path through bitrot readers

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-06-28 22:35:42 +08:00
Zhengchao An f5d7fea7a4 fix(ecstore): remove unused mirror fields (#4019) 2026-06-28 22:31:37 +08:00
Zhengchao An 0f3213a530 docs: update security advisory lessons (#4018) 2026-06-28 21:55:40 +08:00
Zhengchao An 99f981a868 docs: plan ecstore split boundaries (#4017)
docs: plan ecstore module split boundaries
2026-06-28 21:55:10 +08:00
Zhengchao An ed0f191e1a docs: document global singleton migration plan (#730) (#4015)
docs: document global singleton migration plan

Add comments documenting the Tier A/B classification and migration
plan for global singletons in ecstore runtime.

Refs #730
2026-06-28 21:25:42 +08:00
Zhengchao An 163e8089db fix: remove all backlog links from code and comments (#4013) 2026-06-28 20:55:25 +08:00
Zhengchao An aa8b8b5706 docs: document crypto RC version dependencies (#732) (#4011)
* docs: document crypto RC version dependencies

Add comment explaining why aes-gcm and chacha20poly1305 use RC
versions and the migration path when stable versions are released.

Refs #732

* fix: remove all backlog links from code and comments
2026-06-28 20:47:51 +08:00
Zhengchao An cd165ab181 docs: document obs reverse dependency on ecstore (#735) (#4010)
docs: document obs reverse dependency on ecstore

Add comment explaining why obs depends on ecstore and the scope
of work required to break this dependency.

Refs #735

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-28 20:28:04 +08:00
Zhengchao An a37e918936 docs: document SHA-1 HMAC migration proposal (#747) (#4009) 2026-06-28 19:42:18 +08:00
Zhengchao An 8d0ba1cd3c docs: document deadlock detector mutex design rationale (#744) (#4008)
docs: document deadlock detector mutex design rationale

Add comment explaining why std::sync::Mutex is used instead of
tokio::sync::Mutex in the deadlock detector.

Refs #744

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-28 19:29:50 +08:00
Zhengchao An 27b8592879 docs: add documentation to storage-api public types (#741) (#4007)
docs: add documentation to storage-api public types

Add doc comments to public structs, enums, and traits in
storage-api crate to improve documentation coverage.

Refs #741
2026-06-28 19:12:02 +08:00
Zhengchao An 1f56ddf913 docs: document table_catalog mutex design rationale (#739) (#4006)
docs: document table_catalog mutex design rationale

Add comment explaining why the single mutex is intentional and
what alternatives to consider if contention becomes a bottleneck.

Refs #739
2026-06-28 18:56:13 +08:00
Zhengchao An 710ae74cde perf: add S3 operations benchmark framework (#738) (#4005) 2026-06-28 18:02:41 +08:00
Zhengchao An 51acf2a99c feat: add rate limiting middleware framework (#737) (#4002)
* feat: add rate limiting middleware framework

Add rate limiting middleware with token bucket algorithm for
per-client request rate limiting. This provides the foundation
for DoS protection.

Refs #737

* fix: address clippy lints in rate_limit.rs

- Collapse nested if statements into single if-let chains
- Use .is_multiple_of() instead of manual modulo check
2026-06-28 17:48:40 +08:00
Zhengchao An ee82d6c026 test: add insta snapshot test for storage error display format (#740) (#4001)
test: add insta snapshot test for storage error display format

Add snapshot test to detect unexpected changes in StorageError
display format. This catches output format regressions that
traditional assert tests might miss.

Refs #740
2026-06-28 16:10:07 +08:00
Zhengchao An 84cdf12083 test(security): add security boundary tests (#748) (#3998)
test(security): add security boundary tests

Add e2e tests for security-sensitive scenarios:
- Large XML body handling (DoS protection)
- Excessive multipart parts (DoS protection)
- Concurrent object operations (race condition handling)
- Internal URL validation (SSRF prevention)

Refs #748
2026-06-28 15:17:30 +08:00
Zhengchao An c768a9c382 docs(storage-api): document filemeta dependency as known limitation (#731) (#3997)
* docs(storage-api): document filemeta dependency as known limitation

Add comment explaining why storage-api depends on filemeta and
the scope of work required to break this dependency (300+ files).

Refs https://github.com/rustfs/backlog/issues/731

* docs(storage-api): remove backlog link from comment
2026-06-28 14:59:24 +08:00
Zhengchao An c475d03b48 fix: replace unwrap() with expect() in more files (#729 batch 14) (#3994) 2026-06-28 11:45:23 +08:00
Zhengchao An 05d201679c fix: replace unwrap() with expect() in more files (#729 batch 13) (#3993) 2026-06-28 11:45:13 +08:00
Zhengchao An f0ab812213 fix: replace unwrap() with expect() in remaining files (#729 batch 12) (#3992) 2026-06-28 11:45:03 +08:00
Zhengchao An 1f8a5bc095 fix(ecstore): replace unwrap() with proper error handling in api_get_object_attributes (#729 batch 11) (#3991)
fix(ecstore): replace unwrap() with proper error handling in api_get_object_attributes

Replace unsafe unwrap() calls with proper error handling in
api_get_object_attributes.rs:
- HTTP header access now uses ok_or_else with descriptive messages
- String parsing now uses map_err with descriptive messages
- HeaderValue creation now uses expect with descriptive messages

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:23:41 +08:00
Zhengchao An 25170943ce fix(ecstore): improve expect() messages in admin_server_info (#729 batch 9) (#3990)
fix(ecstore): improve expect() messages in admin_server_info

Replace unwrap() with expect() for better error diagnostics in
admin_server_info.rs:
- URL host/port access now has descriptive messages
- HashMap get_mut calls now have descriptive messages

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:23:04 +08:00
Zhengchao An 20c7cb1074 fix(server): improve expect() messages in layer.rs (#729 batch 8) (#3989)
fix(server): improve expect() messages in layer.rs

Replace unwrap() with expect(valid response body) for Response
builder calls in layer.rs.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:22:46 +08:00
Zhengchao An 5c94fe7dd7 fix(ecstore): improve expect() messages in replication_resyncer (#729 batch 7) (#3988)
fix(ecstore): improve expect() messages in replication_resyncer

Replace unwrap() with expect() for better error diagnostics in
replication_resyncer.rs:
- HashMap get_mut calls now have descriptive expect messages
- format() calls now use unwrap_or_else for error handling

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:22:26 +08:00
Zhengchao An d668ef837a fix(admin): improve expect() messages in remaining admin handlers (#729 batch 6) (#3987)
fix(admin): improve expect() messages in remaining admin handlers

Replace generic parse().unwrap() with parse().expect(valid header value)
for better error diagnostics in service_account.rs, group.rs, and console.rs.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:22:08 +08:00
Zhengchao An e5a355efe9 fix(admin): improve expect() messages in policies handler (#729 batch 5) (#3986)
fix(admin): improve expect() messages in policies handler

Replace generic parse().unwrap() with parse().expect(valid header value)
for better error diagnostics in policies.rs.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:21:51 +08:00
Zhengchao An dbca96faf6 fix(admin): improve expect() messages in user handler (#729 batch 4) (#3985)
fix(admin): improve expect() messages in user handler

Replace generic parse().unwrap() with parse().expect(valid header value)
for better error diagnostics in user.rs.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:21:24 +08:00
Zhengchao An e8470bbc66 fix(admin): replace unwrap() with safe pattern in bucket_meta handler (#729 batch 3) (#3984)
fix(admin): replace unwrap() with safe pattern in bucket_meta handler

Replace 11 instances of HashMap.get_mut().unwrap() with
match pattern that continues to next iteration if key is missing.

Also improve expect() messages for header value parsing.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:20:51 +08:00
houseme 46d7f9e1f2 feat(get): harden codec streaming rollout (#3981)
* feat(get): consolidate GET performance optimization

Consolidated implementation of all GET performance optimizations into
a single, well-organized commit replacing the previous patch-on-patch
approach.

## Changes

### Configuration (set_disk/mod.rs)
- Consolidated all GET optimization flags into a single organized section
- Enabled by default: codec streaming, metadata early-stop, page cache reclaim
- Added codec streaming multipart flag (default: disabled)
- Added version-aware early-stop flag (default: disabled)
- Added adaptive duplex buffer sizing based on object size
- All flags use OnceLock caching with rollout percentage support

### Metadata Early-Stop (set_disk/read.rs)
- Delete marker early-stop when quorum agrees
- Version-aware early-stop for versioned GET requests
- MetadataQuorumAccumulator enhanced with:
  - delete_marker_votes tracking
  - requested_version_id and matching_version_votes tracking
  - version_early_stop_decision() method
- 6 new tests for version early-stop scenarios

### Codec Streaming (erasure/coding/decode_reader.rs)
- DualInFlight (2-stripe lookahead) enabled by default

### Decode Pipeline (erasure/coding/decode.rs)
- Stripe prefetch count configuration
- Bitrot-decode overlap configuration

### Disk Layer (disk/local.rs)
- O_DIRECT read configuration constants (preparation)

### Metrics (io-metrics/lib.rs)
- BytesPool acquisition/return metrics
- Metadata phase duration with early-stop label
- Total duration with reader_path label

### Diagnostics (diagnostics/)
- Early-stop reason constants
- Pool tier/outcome label constants

### Observability (.docker/observability/)
- 3 Grafana dashboards for GET optimization monitoring
- Prometheus alert rules (6 alerts: 3 critical, 3 warning)
- Updated README.md and README_ZH.md with usage docs

### Config (config/src/constants/runtime.rs)
- Page cache reclaim read enabled by default

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| RUSTFS_GET_CODEC_STREAMING_ENABLE | true | Codec streaming base flag |
| RUSTFS_GET_CODEC_STREAMING_ROLLOUT_PCT | 100 | Codec streaming rollout % |
| RUSTFS_GET_CODEC_STREAMING_MULTIPART_ENABLE | false | Multipart codec streaming |
| RUSTFS_GET_METADATA_EARLY_STOP_ENABLE | true | Early-stop base flag |
| RUSTFS_GET_METADATA_EARLY_STOP_ROLLOUT_PCT | 100 | Early-stop rollout % |
| RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE | false | Version-aware early-stop |
| RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE | true | Page cache reclaim |
| RUSTFS_OBJECT_DIRECT_IO_READ_ENABLE | false | O_DIRECT (preparation) |
| RUSTFS_GET_DECODE_STRIPE_PREFETCH_COUNT | 1 | Stripe prefetch |
| RUSTFS_GET_BITROT_DECODE_OVERLAP_ENABLE | false | Bitrot-decode overlap |
| RUSTFS_GET_CODEC_STREAMING_MAX_INFLIGHT | 2 | DualInFlight stripes |

## Rollback

All optimizations can be disabled via environment variables:
RUSTFS_GET_CODEC_STREAMING_ENABLE=false
RUSTFS_GET_METADATA_EARLY_STOP_ENABLE=false
RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE=false

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(get): add stress test scripts for GET optimization validation

- quick-validate-get-optimization.sh: Quick 5-minute validation
- stress-test-get-optimization.sh: Full 30+ minute stress test
- README-stress-test.md: Usage documentation

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): align file cache reclaim defaults

* chore(deps): update redis and erasure codec

* test(ecstore): align decode fill policy default

* fix(get): wire codec streaming rollout gate

* perf(get): skip metrics-off codec timers

* test(get): capture codec streaming diagnostics

* test(get): add multipart fallback probe

* test(get): add encrypted fallback probe

* test(get): add compressed fallback probe

* test(get): add degraded read fallback probe

* test(get): cover remote fallback probe

* test(get): report warp request p99

* test(get): capture OTLP metric deltas

* perf(get): align codec streaming inflight default

* perf(get): reuse codec reader output buffers

* test(get): count codec reader fill starts

* perf(get): reuse codec reader fill worker

* perf(get): lazy init rustfs codec reconstruct

* test(get): cover rustfs codec source faults

* docs(get): record rustfs codec fallback scope

* feat(get): add multipart codec reader opt-in

* test(get): add multipart codec smoke option

* test(get): cover multipart codec degraded fallback

* perf(get): bound multipart codec eager setup

* test(get): satisfy codec hardening PR gate

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-06-28 11:20:21 +08:00
Zhengchao An d99056902e fix(admin): replace unwrap() with proper error handling in tier handler (#729 batch 2) (#3983)
fix(admin): replace unwrap() with proper error handling in tier handler

Replace 9 instances of args.{type}.clone().unwrap() with
ok_or_else() that returns a descriptive S3Error when the
tier configuration is missing.

Also improve expect() messages for header value parsing.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 11:00:50 +08:00
Zhengchao An 6e72dc3076 fix(ecstore): replace unsafe k.unwrap() in bucket_target_sys (#729) (#3982)
fix(ecstore): replace k.unwrap() with safe pattern in bucket_target_sys

Replace unsafe k.unwrap().as_str() with if let Some(key_str) pattern
in 5 locations where HeaderMap iterator yields (Option<HeaderName>, Value).

This prevents potential panics if header names are invalid.

Refs https://github.com/rustfs/backlog/issues/729
2026-06-28 10:42:56 +08:00
Zhengchao An 259a99a501 fix(ci): pin protocol matrix checkout action (#3977) 2026-06-28 10:04:36 +08:00
houseme c928fd1c7a chore(deps): update flake.lock (#3978) 2026-06-28 09:54:22 +08:00
Zhengchao An 7238a937a9 fix: correct misleading zero-copy/direct-io docs and internal naming (#733 Phase 1) (#3976) 2026-06-28 09:12:49 +08:00
Zhengchao An 3c6dc2a633 ci: remove --no-default-features from protocol test matrix (#3980) 2026-06-28 09:12:23 +08:00
Zhengchao An e1272f2aba revert: restore #![allow(dead_code)] - CI clippy -D warnings conflict (#3979)
revert: restore #![allow(dead_code)] - clippy -D warnings treats warn as error

The #742 PR changed #![allow(dead_code)] to #![warn(dead_code)], but
CI runs clippy with -D warnings which turns warnings into errors.
This caused CI failures across multiple PRs.

Reverting to #![allow(dead_code)] until the dead code is actually
cleaned up. The 189 warnings in ecstore should be fixed incrementally
by deleting dead code and adding item-level allows, not by changing
the crate-level policy.
2026-06-28 08:32:34 +08:00
Henry Guo 1e303e5be0 fix(data-usage): refresh versioned usage state (#3969)
fix(data-usage): refresh versioned usage from authoritative state

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-28 07:58:07 +08:00
Zhengchao An 129ad2296b ci: add protocol feature test matrix (swift, sftp, no-default) (#736) (#3975) 2026-06-28 07:51:15 +08:00
Zhengchao An 113058af54 chore: replace blanket #![allow(dead_code)] with #![warn(dead_code)] (#742) (#3974) 2026-06-28 07:50:51 +08:00
houseme 27468ebfa9 feat(get): consolidate GET performance optimization (#3972)
* feat(get): consolidate GET performance optimization

Consolidated implementation of all GET performance optimizations into
a single, well-organized commit replacing the previous patch-on-patch
approach.

## Changes

### Configuration (set_disk/mod.rs)
- Consolidated all GET optimization flags into a single organized section
- Enabled by default: codec streaming, metadata early-stop, page cache reclaim
- Added codec streaming multipart flag (default: disabled)
- Added version-aware early-stop flag (default: disabled)
- Added adaptive duplex buffer sizing based on object size
- All flags use OnceLock caching with rollout percentage support

### Metadata Early-Stop (set_disk/read.rs)
- Delete marker early-stop when quorum agrees
- Version-aware early-stop for versioned GET requests
- MetadataQuorumAccumulator enhanced with:
  - delete_marker_votes tracking
  - requested_version_id and matching_version_votes tracking
  - version_early_stop_decision() method
- 6 new tests for version early-stop scenarios

### Codec Streaming (erasure/coding/decode_reader.rs)
- DualInFlight (2-stripe lookahead) enabled by default

### Decode Pipeline (erasure/coding/decode.rs)
- Stripe prefetch count configuration
- Bitrot-decode overlap configuration

### Disk Layer (disk/local.rs)
- O_DIRECT read configuration constants (preparation)

### Metrics (io-metrics/lib.rs)
- BytesPool acquisition/return metrics
- Metadata phase duration with early-stop label
- Total duration with reader_path label

### Diagnostics (diagnostics/)
- Early-stop reason constants
- Pool tier/outcome label constants

### Observability (.docker/observability/)
- 3 Grafana dashboards for GET optimization monitoring
- Prometheus alert rules (6 alerts: 3 critical, 3 warning)
- Updated README.md and README_ZH.md with usage docs

### Config (config/src/constants/runtime.rs)
- Page cache reclaim read enabled by default

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| RUSTFS_GET_CODEC_STREAMING_ENABLE | true | Codec streaming base flag |
| RUSTFS_GET_CODEC_STREAMING_ROLLOUT_PCT | 100 | Codec streaming rollout % |
| RUSTFS_GET_CODEC_STREAMING_MULTIPART_ENABLE | false | Multipart codec streaming |
| RUSTFS_GET_METADATA_EARLY_STOP_ENABLE | true | Early-stop base flag |
| RUSTFS_GET_METADATA_EARLY_STOP_ROLLOUT_PCT | 100 | Early-stop rollout % |
| RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE | false | Version-aware early-stop |
| RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE | true | Page cache reclaim |
| RUSTFS_OBJECT_DIRECT_IO_READ_ENABLE | false | O_DIRECT (preparation) |
| RUSTFS_GET_DECODE_STRIPE_PREFETCH_COUNT | 1 | Stripe prefetch |
| RUSTFS_GET_BITROT_DECODE_OVERLAP_ENABLE | false | Bitrot-decode overlap |
| RUSTFS_GET_CODEC_STREAMING_MAX_INFLIGHT | 2 | DualInFlight stripes |

## Rollback

All optimizations can be disabled via environment variables:
RUSTFS_GET_CODEC_STREAMING_ENABLE=false
RUSTFS_GET_METADATA_EARLY_STOP_ENABLE=false
RUSTFS_OBJECT_FILE_CACHE_RECLAIM_READ_ENABLE=false

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(get): add stress test scripts for GET optimization validation

- quick-validate-get-optimization.sh: Quick 5-minute validation
- stress-test-get-optimization.sh: Full 30+ minute stress test
- README-stress-test.md: Usage documentation

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): align file cache reclaim defaults

* chore(deps): update redis and erasure codec

* test(ecstore): align decode fill policy default

* test(ecstore): align metadata early-stop default

* fix(ecstore): keep metadata early stop opt-in

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-06-28 07:14:07 +08:00
Zhengchao An 512418cda9 fix(ecstore): replace unbounded metadata cache with moka (#743) (#3970)
fix(ecstore): replace unbounded metadata cache with moka

Replace the manual Arc<RwLock<HashMap>> metadata cache with
moka::future::Cache, which provides:
- Built-in LRU eviction when max_capacity is reached
- Automatic TTL expiry via time_to_live (250ms)
- Lock-free concurrent reads
- Non-blocking invalidation

Fixes the memory leak risk from unbounded HashMap and the
all-or-nothing eviction logic that cleared all entries at once.

Closes #743

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-28 03:01:32 +08:00
houseme 175566f037 docs(get): record default switch readiness (#3967)
* docs(get): record default switch readiness

* chore(docs): keep pr35 evaluation note local

* refactor(bench): trim pr35 readiness python
2026-06-28 01:12:44 +08:00
Zhengchao An 7ae9697d42 docs: close final architecture audit gaps (#3966) 2026-06-28 00:07:42 +08:00
Zhengchao An 79234c030d docs: close architecture migration validation (#3965) 2026-06-27 23:42:51 +08:00
Zhengchao An 82bbef0b60 test: cover runtime and repair preservation (#3964) 2026-06-27 23:34:59 +08:00
houseme bf03ff2869 feat(get): add limited opt-in rollout gates (#3963)
* feat(bench): harden cooled get ab harness

* feat(get): add limited opt-in rollout gates

* fix(get): tighten rollout gate fallbacks
2026-06-27 23:13:02 +08:00
Zhengchao An 66ad138505 docs: close phase 7 global split plan (#3962) 2026-06-27 22:25:16 +08:00
houseme 47d05a1d6a feat(bench): harden cooled get ab harness (#3960) 2026-06-27 22:23:09 +08:00
Zhengchao An c7dc3d7974 refactor: move runtime fallback defaults to owners (#3961) 2026-06-27 22:21:09 +08:00
houseme 20f56af09c feat(get): tune output response handoff (#3956) 2026-06-27 21:46:38 +08:00
Zhengchao An 68d5d1d41d refactor: isolate specialized runtime fallbacks (#3958) 2026-06-27 21:35:47 +08:00
Zhengchao An 672f6e9ea9 refactor: remove scalar runtime fallbacks (#3957) 2026-06-27 20:45:13 +08:00
Zhengchao An 243a20b14e refactor: remove service resolver fallbacks (#3955) 2026-06-27 19:35:43 +08:00
Zhengchao An 4732f4ee2d refactor: expose runtime fallback boundaries (#3954) 2026-06-27 18:57:51 +08:00
Zhengchao An cdebdeca61 refactor: clean app context fallback signatures (#3953) 2026-06-27 18:30:06 +08:00
Zhengchao An d66391636b refactor: remove optional runtime handle fallbacks (#3952) 2026-06-27 18:07:09 +08:00
Zhengchao An 430aa8e8cb refactor: remove notification config fallbacks (#3951) 2026-06-27 17:43:05 +08:00
Zhengchao An ab9e98a9ca refactor: align core runtime facade helpers (#3948) 2026-06-27 17:14:25 +08:00
houseme de86025f2c feat(get): overlap read verify decode (#3945) 2026-06-27 15:14:44 +08:00
Zhengchao An dff0e467f4 refactor: align app runtime facade helpers (#3947) 2026-06-27 15:14:29 +08:00
Zhengchao An 7979ecb545 refactor: align storage runtime facade helpers (#3946) 2026-06-27 14:55:37 +08:00
Zhengchao An 261e16a446 refactor: route admin runtime sources through current helpers (#3944) 2026-06-27 14:19:18 +08:00
Zhengchao An cc41a50efe refactor: route admin runtime consumers through app context (#3943) 2026-06-27 13:38:21 +08:00
houseme 58b76a3d45 feat(get): add codec engine ab matrix (#3940)
* upgrade version

* feat(get): add codec engine ab matrix

* chore(get): drop unrelated dependency drift

* upgrade version

* upgrade version

* fix cargo deny
2026-06-27 13:27:16 +08:00
Zhengchao An 996e58fd9a refactor: route admin runtime config through app context (#3942) 2026-06-27 13:10:00 +08:00
Zhengchao An 1b3dea012e refactor: route ecstore runtime globals through facade (#3941) 2026-06-27 12:27:03 +08:00
Zhengchao An d3f1ff36af docs: record global state cleanup plan (#3939) 2026-06-27 12:04:59 +08:00
Zhengchao An fe9c0daf0f docs: add architecture readiness matrix (#3938) 2026-06-27 11:23:07 +08:00
Zhengchao An e1a4b9e0b6 refactor: batch cluster lock and health readiness (#3936) 2026-06-27 10:51:06 +08:00
GatewayJ 675597ec16 fix(ecstore): handle stalled recovery reads and listings (#3790)
* fix(ecstore): handle stalled recovery reads and listings

* fix(rio): start HTTP stall timeout on read

* fix(ecstore): handle stalled reads and partial lists

* fix(ecstore): retire stalled shards and list errors

* fix(ecstore): preserve list merge lookahead entries

* fix(ecstore): bound zero-copy shard reads

* fix(ecstore): hedge stalled shard reads

* fix(ecstore): retire abandoned shard reads

* fix(ecstore): include part identity in metadata quorum

* fix(ecstore): validate heal shard sources

* fix(ecstore): verify reconstructed read shards

* chore(ecstore): log slow object read stages

* fix(heal): throttle auto heal during recovery

* fix(scanner): yield to foreground reads

* fix(scanner): track streaming object reads

* fix(ecstore): avoid false read heal fanout

* fix(ecstore): verify codec streaming reconstruction sources

* fix(ecstore): preserve quorum progress on slow shards

* fix(storage): restore read timeout facade

* fix(ecstore): retain fallback readers after quorum

* chore: allow decode helper argument lists

---------

Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-06-27 10:21:09 +08:00
Zhengchao An 3fb4dcd52e refactor: route cluster control plane readiness (#3935) 2026-06-27 09:47:30 +08:00
Zhengchao An 0a5b1b1b3a refactor: consolidate ecstore owner module layout (#3934)
* refactor: shrink ecstore root owner facades

* refactor: remove ecstore core store root shims

* refactor: move ecstore erasure owner modules

* refactor: remove ecstore root rpc facade

* refactor: move ecstore services domain modules
2026-06-27 09:03:20 +08:00
cxymds 080363f10f feat: harden site replication control plane (#3842) 2026-06-27 08:35:49 +08:00
houseme ae4aa4f5d4 feat(get): add rustfs codec decode engine (#3928) 2026-06-27 08:35:38 +08:00
cxymds 688b14b572 fix(ecstore): preserve multipart part quorum errors (#3920)
* fix(ecstore): preserve multipart part quorum errors

* fix(ecstore): honor confirmed missing part quorum

* fix(ecstore): add multipart quorum diagnostics
2026-06-27 08:33:43 +08:00
Zhengchao An 27bb9c75dc refactor: move ecstore rpc metadata modules (#3933) 2026-06-27 07:19:45 +08:00
Zhengchao An c6ecfae39e refactor: move ecstore owner layout modules (#3932) 2026-06-27 05:54:25 +08:00
Zhengchao An 61b1296972 refactor: move ecstore store init support (#3931) 2026-06-27 05:05:01 +08:00
Zhengchao An bdfcde1866 refactor: move ecstore store support modules (#3930) 2026-06-27 03:51:11 +08:00
Zhengchao An 07428c8a34 refactor: move ecstore store owner roots (#3929) 2026-06-27 02:25:04 +08:00
houseme 8994a8ff00 feat(get): add local-first shard reads (#3924)
* feat(get): observe shard locality cost

* feat(get): add local-first shard reads

----

Co-Authored-By: Heihutu <heihutu@gmail.com>
2026-06-27 01:08:36 +08:00
Zhengchao An e5706ead0e ci: close config model ownership guard (#3927) 2026-06-27 00:25:36 +08:00
Zhengchao An 840d21d201 fix(deps): remove vulnerable thrift dependency (#3926) 2026-06-27 00:16:51 +08:00
houseme c0ddc14bb8 feat(get): observe shard locality cost (#3922)
* feat(get): observe shard locality cost

* fix(storage): avoid request counter underflow panic

* fix(storage): import put guard in concurrency tests
2026-06-26 23:50:37 +08:00
Zhengchao An 741b4dab7f refactor: close external storage API boundary guard (#3925) 2026-06-26 23:20:25 +08:00
Zhengchao An a22384708e refactor: decouple server health helpers (#3923) 2026-06-26 22:40:22 +08:00
Zhengchao An 15c39db42f refactor: consolidate storage owner boundary cleanup (#3921)
* refactor: segment app usecase contracts by domain

* refactor: segment root storage contracts by domain

* refactor: segment root runtime facades by domain

* refactor: segment storage owner consumers by domain

* refactor: route storage owner runtime consumers by domain

* refactor: make storage owner rpc imports explicit

* refactor: remove storage owner wildcard imports (#3917)

* refactor: make storage owner root exports explicit (#3918)

* refactor: route storage facades through owner api (#3919)

* refactor: finalize storage owner phase branch
2026-06-26 22:18:00 +08:00
houseme 2c04807f05 feat(get): add guarded metadata early stop (#3916)
* feat(get): add v2 metrics compatibility harness

* feat(get): observe metadata fanout quorum (#3915)

* feat(get): observe metadata fanout quorum

* fix(app): use storage ECStore type in app boundary

* feat(get): add guarded metadata early stop
2026-06-26 21:56:23 +08:00
cxymds 7820a55fdc fix: decouple readiness from cluster health (#3912)
* fix: include foreground write pressure

* fix: split readiness and cluster health probes

* fix: publish ready on node readiness

* fix: bound cluster health collection

* test: pin health probe collector routing

* fix: qualify app storage ECStore type

* test(admin): narrow runtime capabilities topology assertion

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-26 21:55:18 +08:00
Zhengchao An 6469d6ada8 refactor: segment admin storage contracts by domain (#3914) 2026-06-26 20:16:51 +08:00
houseme 5a78a9c416 feat(get): add v2 metrics compatibility harness (#3913)
* feat(get): add v2 metrics compatibility harness

* feat(get): observe metadata fanout quorum (#3915)

* feat(get): observe metadata fanout quorum

* fix(app): use storage ECStore type in app boundary

----
Co-Authored-By: heihutu <heihutu@gmail.com>
2026-06-26 20:09:09 +08:00
Zhengchao An a010dce93c refactor: segment storage owner contracts by domain (#3911) 2026-06-26 18:34:32 +08:00
Henry Guo a8327f8901 fix(scanner): account versioned delete markers in usage (#3904)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-26 18:34:21 +08:00
houseme 0321e4c6ca perf(get): reduce metrics and streaming handoff overhead (#3907) 2026-06-26 18:12:51 +08:00
cxymds 30957e2b51 fix(ecstore): throttle data movement under read pressure (#3906) 2026-06-26 17:48:09 +08:00
Zhengchao An b38976d5ee refactor: segment ECStore storage contracts by domain (#3910) 2026-06-26 17:47:36 +08:00
Zhengchao An 72a589f0c0 refactor: segment RustFS storage API root exports (#3909) 2026-06-26 17:15:33 +08:00
Zhengchao An 72ae43cb90 refactor: segment external storage contract imports (#3908) 2026-06-26 16:44:59 +08:00
Zhengchao An 6efbfff2c5 refactor: segment residual storage api boundaries (#3905) 2026-06-26 15:56:38 +08:00
cxymds ae641a33ac feat(admin): expose global heal progress (#3897) 2026-06-26 15:19:48 +08:00
cxymds 90638bfc19 fix(heal): add backpressure to repair admission (#3900) 2026-06-26 15:19:37 +08:00
Zhengchao An 1f7e159388 refactor: segment external storage api boundaries (#3903) 2026-06-26 15:10:49 +08:00
Zhengchao An 738b805ec0 refactor: segment app storage api boundary (#3902) 2026-06-26 14:45:05 +08:00
Zhengchao An 2a1bddfcca refactor: segment storage api domain boundaries (#3901) 2026-06-26 14:04:28 +08:00
Zhengchao An 92c50156a3 refactor: centralize runtime source boundaries (#3899) 2026-06-26 13:22:00 +08:00
Zhengchao An a038582325 refactor: route app usecase context lookup (#3896) 2026-06-26 12:47:48 +08:00
Zhengchao An 2301787afa refactor: centralize admin runtime globals (#3895) 2026-06-26 12:19:57 +08:00
Zhengchao An 4e87c4427f refactor: route admin usecase construction boundary (#3894) 2026-06-26 11:39:32 +08:00
Zhengchao An 1d5bf2699a refactor: publish oidc through app context (#3893) 2026-06-26 10:17:30 +08:00
Zhengchao An c9614eb7cb refactor: route ecstore storage api boundaries (#3892) 2026-06-26 09:35:18 +08:00
cxymds 0b9c8a7731 fix(heal): align scanner repair safety with quorum errors (#3887)
* fix(heal): avoid task-level delete on repair failure

* fix(scanner): avoid recreate for scanner heal checks

* fix(heal): preserve typed quorum failures

* test(heal): satisfy clippy bool assertion
2026-06-26 08:58:27 +08:00
Zhengchao An 97dce107f0 refactor: route storage owner contract imports (#3891) 2026-06-26 08:58:07 +08:00
Zhengchao An 2aca607119 refactor: move storage owner boundary aggregation (#3890) 2026-06-26 07:52:49 +08:00
Zhengchao An e37e367390 refactor: route remaining external storage boundaries (#3889) 2026-06-26 06:22:51 +08:00
Zhengchao An 4d6ea453a7 refactor: route scanner heal storage boundaries (#3888) 2026-06-26 05:24:41 +08:00
houseme f7a9a7142b chore: update deps and e2e cache action (#3885)
* chore(deps): update workspace crate versions

* ci: update e2e s3tests cache action
2026-06-26 04:19:28 +08:00
Zhengchao An 19c925c480 refactor: expand storage api boundaries (#3886)
* refactor: route root storage contracts

* refactor: expand storage api boundaries
2026-06-26 04:19:02 +08:00
houseme 7bf411f465 obs(get): add GET read pipeline metrics (#3868) 2026-06-26 04:02:02 +08:00
Zhengchao An 33b7f48f37 refactor: route root storage facades (#3867) 2026-06-25 23:23:45 +08:00
Zhengchao An 987ea4919f refactor: route admin storage facades (#3866) 2026-06-25 22:37:54 +08:00
cxymds 86e9362459 fix(heal): canonicalize scanner object-dir repairs (#3864) 2026-06-25 22:07:38 +08:00
Zhengchao An 1c973bc1d9 refactor: route app facade boundaries (#3865)
* refactor: route app storage error helpers

* refactor: route app bucket owner facades

* refactor: route app runtime facades
2026-06-25 21:57:42 +08:00
Zhengchao An 60c00f7622 refactor: route app and admin storage helpers (#3863)
* refactor: route admin storage helper imports

* refactor: route app storage io helper imports
2026-06-25 21:12:51 +08:00
Henry Guo 30f781871d fix(scanner): preserve dirty usage convergence (#3844)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-25 21:09:12 +08:00
cxymds 4cdc6ae63f fix(admin): refresh persisted pool states (#3861) 2026-06-25 21:08:55 +08:00
houseme ef0dcec479 feat(ecstore): add deeper zero-copy ingest experiment (#3847)
* feat(storage): add multipart put stage metrics

* feat(scripts): add multipart put focus runner

* docs(operations): add multipart put server-path guides

* chore(scripts): add local rustfs restart helper

* docs(observability): add local metrics backend guide

* docs(observability): add localized multipart guides

* fix(ecstore): validate multipart batching path

* feat(obs): add erasure encode overlap metrics

* docs(ops): update overlap retest summary

* docs(ops): add batchblocks retest matrix

* docs(ops): extend overlap candidate summary

* docs(ops): capture 8-run overlap summary

* feat(storage): switch rename_data to msgpack map

* test(storage): add rename_data payload checks

* feat(object): add zero_copy_eager put path

* docs(ops): add zero_copy_eager put guide

* docs(ops): add deeper zero-copy next steps

* feat(ecstore): add bytesmut erasure ingest gate

* docs(ops): add bytesmut ingest summary

* docs(ops): extend bytesmut ingest matrix summary

* docs(ops): extend bytesmut larger-object summary

* docs(ops): capture bytesmut variability summary

* chore(scripts): add deeper zero-copy capture flow

* chore(scripts): add deeper zero-copy capture support

* docs(ops): add capture-backed bytesmut retest

* docs(ops): update deeper zero-copy retests

* chore(docs): keep issue-712 notes local only

Co-Authored-By: heihutu <heihutu@gmail.com>
2026-06-25 21:05:00 +08:00
houseme 96b1f5c373 feat(storage): optimize gt1g get read path (#3860)
* feat(storage): tune gt1g get read path

* docs(ops): add gt1g get benchmark guide

* feat(scripts): add gt1g get fallback runner

* test(storage): trim gt1g get helper warnings

* test(storage): reduce gt1g get helper type complexity
2026-06-25 20:12:08 +08:00
houseme a30357d21e feat(storage): extend PUT path tuning and observability (#3829)
* feat(storage): add multipart put stage metrics

* feat(scripts): add multipart put focus runner

* docs(operations): add multipart put server-path guides

* chore(scripts): add local rustfs restart helper

* docs(observability): add local metrics backend guide

* docs(observability): add localized multipart guides

* fix(ecstore): validate multipart batching path

* feat(obs): add erasure encode overlap metrics

* docs(ops): update overlap retest summary

* docs(ops): add batchblocks retest matrix

* docs(ops): extend overlap candidate summary

* docs(ops): capture 8-run overlap summary

* feat(storage): switch rename_data to msgpack map

* test(storage): add rename_data payload checks

* feat(object): add zero_copy_eager put path

* docs(ops): add zero_copy_eager put guide

* docs(ops): add deeper zero-copy next steps
2026-06-25 19:24:35 +08:00
Zhengchao An 3942186f3d refactor: route app storage helper imports (#3856) 2026-06-25 18:10:49 +08:00
Zhengchao An a21f3c1a3f refactor: route app s3 api helpers (#3854) 2026-06-25 16:50:17 +08:00
Zhengchao An 2dd98b1f84 refactor: remove app dto wildcard imports (#3853) 2026-06-25 16:07:49 +08:00
Zhengchao An fe0227e9dc refactor: remove app storage wildcard imports (#3851) 2026-06-25 15:10:51 +08:00
cxymds 55cbfe256b fix(heal): skip scanner object-dir recreate misses (#3839) 2026-06-25 14:52:24 +08:00
GatewayJ 06fdbecf9d fix(s3tables): harden warehouse index races (#3849) 2026-06-25 14:35:24 +08:00
cxymds 4cb28abfc9 fix(scanner): reduce non-actionable scan noise (#3840) 2026-06-25 14:35:10 +08:00
Zhengchao An bfb2a95e3d refactor: route admin context imports (#3848) 2026-06-25 14:27:25 +08:00
Zhengchao An 46fa28f542 refactor: centralize root runtime sources (#3846) 2026-06-25 13:19:52 +08:00
Zhengchao An 1c4bcd4372 refactor: centralize admin runtime sources (#3845) 2026-06-25 13:09:02 +08:00
Henry Guo 1c4a279909 feat(table-catalog): add durable strong backing (#3836) 2026-06-25 12:22:30 +08:00
Zhengchao An 928074a97d refactor: centralize server storage runtime sources (#3843) 2026-06-25 12:20:53 +08:00
Zhengchao An 839cab47ce refactor: centralize startup runtime sources (#3838) 2026-06-25 11:31:38 +08:00
GatewayJ fe5961cc16 fix(s3tables): harden warehouse index consistency (#3835) 2026-06-25 11:31:25 +08:00
Henry Guo 1d6a8259c5 feat(table-catalog): add partition-local compaction (#3832)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-25 11:14:40 +08:00
Zhengchao An 873aea21ca refactor: centralize app context runtime fallbacks (#3837) 2026-06-25 11:00:42 +08:00
Rokton 758677dafe fix(heal): Add podAnnotations to statefulset and deployment templates (#3831) 2026-06-25 09:39:11 +08:00
cxymds e6f04bed47 fix(heal): skip missing object-dir candidates (#3834) 2026-06-25 09:38:08 +08:00
cxymds c6ef37d5a7 fix(server): handle MinIO cluster health probe (#3833) 2026-06-25 09:37:57 +08:00
Zhengchao An ad6184b126 refactor: centralize iam runtime source helpers (#3828) 2026-06-24 22:49:24 +08:00
Zhengchao An a935854b32 refactor: centralize scanner runtime source helpers (#3827)
* refactor: batch lifecycle runtime source handles

* refactor: centralize scanner runtime source helpers
2026-06-24 22:05:03 +08:00
Zhengchao An 5cc8bd7952 refactor: centralize ecstore rpc test runtime sources (#3818) 2026-06-24 21:18:22 +08:00
Henry Guo 90f6371e29 fix(scanner): clear deleted bucket usage stats (#3822)
* fix(scanner): clear deleted bucket usage stats

* fix(scanner): preserve usage timestamp on bucket cleanup

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-24 21:08:32 +08:00
houseme 623fc801f1 feat(replication): support custom TLS for bucket targets (#3825)
* feat(replication): support insecure https bucket targets

* feat(replication): support custom ca bucket targets

* test(replication): satisfy e2e clippy for TLS helpers

* fix(replication): avoid native root panic for custom trust stores

* test(replication): decouple private IP target test from TLS roots

* test(replication): use target TLS client in private IP unit test
2026-06-24 20:23:20 +08:00
Henry Guo fcd0c9ec0f test(table-catalog): add atomic catalog backing fixture (#3821) 2026-06-24 19:05:13 +08:00
houseme efebcb66be feat(perf): add large PUT tuning and encode optimization (#3816) 2026-06-24 19:04:04 +08:00
houseme 3c6e9acfcb fix: route cluster imports through ecstore_cluster module (#3819) 2026-06-24 19:03:31 +08:00
cxymds 0ffd561daf fix(health): decouple liveness and lock RPC deadlines (#3824) 2026-06-24 18:34:51 +08:00
cxymds 57e12e56d2 fix(admin): correct pool lifecycle status mapping (#3823) 2026-06-24 17:05:07 +08:00
cxymds eef8f43251 fix: harden lock timeouts, health, and heal paths (#3815) 2026-06-24 15:48:06 +08:00
Zhengchao An 94034ef406 refactor: centralize ecstore batch sources and aliases (#3817) 2026-06-24 14:12:48 +08:00
houseme cbf526df87 refactor(admin): migrate OIDC consumers to app context (#3800) 2026-06-24 12:51:53 +08:00
GatewayJ 89dfb1357c fix(s3tables): harden catalog guard checks (#3801) 2026-06-24 12:51:40 +08:00
houseme 31b5db9a75 feat(admin): expose cluster snapshot view (#3803) 2026-06-24 12:51:26 +08:00
cxymds 5eda332fee fix(heal): recover shards after node rejoin (#3814) 2026-06-24 12:51:16 +08:00
Zhengchao An aa089f18f2 refactor: centralize ecstore accessor runtime sources (#3813) 2026-06-24 11:05:30 +08:00
Zhengchao An 1a8ff1ea3e refactor: centralize ecstore setup runtime sources (#3812) 2026-06-24 10:41:33 +08:00
Hiroaki KAWAI 55b402ec2e fix(scanner): avoid retrying usage-cache lock failures (#3809)
Avoid retrying scanner cache lock failures
2026-06-24 10:22:43 +08:00
Hiroaki KAWAI fb06897650 fix(lock): respect requested acquire timeout (#3804)
Respect requested lock acquire timeout

Co-authored-by: cxymds <cxymds@gmail.com>
2026-06-24 10:22:25 +08:00
Zhengchao An 6b1b3800d7 refactor: centralize ecstore owner runtime sources (#3811) 2026-06-24 10:00:36 +08:00
Zhengchao An 3f624bf06c refactor: centralize ecstore bucket runtime sources (#3810) 2026-06-24 09:41:11 +08:00
Zhengchao An 17ae335313 chore: split pre-pr quality gate (#3807) 2026-06-24 08:43:05 +08:00
Zhengchao An 5b6c486b13 refactor: centralize ecstore locality runtime sources (#3808) 2026-06-24 08:42:36 +08:00
Zhengchao An 1c04b86494 refactor: centralize ecstore bucket runtime sources (#3806) 2026-06-24 08:00:04 +08:00
cxymds c14a442586 fix(storage): harden rebalance and decommission state (#3730) 2026-06-24 07:59:39 +08:00
Zhengchao An 5046f788be refactor: centralize ecstore lifecycle runtime sources (#3805) 2026-06-24 07:23:13 +08:00
Zhengchao An 7e60432588 refactor: centralize ecstore runtime owner sources (#3798) 2026-06-24 06:34:06 +08:00
Zhengchao An 1735dcde9c refactor: centralize ecstore data-plane runtime sources (#3797) 2026-06-23 23:23:16 +08:00
Zhengchao An e59e1852ec refactor: centralize network client runtime sources (#3796) 2026-06-23 22:35:51 +08:00
Zhengchao An 726c26fa01 refactor: centralize RIO HTTP runtime sources (#3795) 2026-06-23 21:55:05 +08:00
houseme 0a00d8d500 perf(server): lighten internode data-plane stack (#3735)
* refactor(server): split internode dispatch scaffold

* test(server): cover internode dispatch prefix split

* refactor(server): name internode stack boundaries

* perf(server): skip internode request logging layer

* perf(server): skip internode trace layer

* perf(server): use lite internode request context

* feat(metrics): track internode rpc duration

* feat(ecstore): add put object stage summary logs

* test(metrics): update internode descriptor expectations

* fix(server): tighten internode path matching

* fix(pr): address review follow-up comments

* style(ecstore): simplify commit tail duration field

* refactor(ecstore): group put stage summary fields

* refactor(ecstore): inline put stage summary log

* fix(s3): return storage class for object attributes

* merge: sync latest main and resolve object attributes conflict

* fmt

* fix(server): remove duplicate rpc imports

* build(deps): bump memmap2 for RUSTSEC-2026-0186

* fix(s3select): align object_store with datafusion

* chore(deps): prune workspace dependencies

* perf(fuzz): optimize CI runtime with build/run split and matrix parallelization

  Separate fuzz harness compilation from execution to eliminate redundant
  builds across targets. Introduce matrix-based parallel execution for
  PR smoke and nightly fuzz jobs.

  Changes:

  - Split CI workflow into `fuzz-build` (compile once) and matrix run jobs
    (`pr-fuzz-smoke`, `nightly-fuzz-corpus`) that execute targets in parallel
  - Add `BUILD_ONLY` mode to run_ci_targets.sh / run_nightly_targets.sh
  - Add run_single_target.sh for matrix jobs (no build phase)
  - Optimize `local_metadata` fuzz target: reduce prefix iterations from
    8-10 (4 functions each) to 5 critical prefixes (parser-only), cutting
    per-iteration cost by ~3-5x
  - Move archive path validation (`validate_extract_relative_path`,
    `normalize_extract_entry_key`) from `rustfs` to `rustfs-utils::path`,
    eliminating `rustfs` binary crate dependency from fuzz harness
  - Remove `rustfs` from fuzz/Cargo.toml (drops significant transitive deps)
  - Add unit tests for archive path validation in rustfs-utils
  - Update fuzz/README.md with new workflow and script documentation

  Expected CI improvement: PR smoke wall-clock from ~120min (frequent
  timeout) to ~40min; nightly from ~180min to ~60min.

* refactor(fuzz): consolidate scripts and fix prefix test alignment

Replace three duplicated shell scripts (run_ci_targets.sh,
run_nightly_targets.sh, run_single_target.sh) with a single
parameterized run.sh that supports BUILD_ONLY, SKIP_BUILD, and
MAX_TOTAL_TIME environment variables.

Fix local_metadata fuzz target prefix testing: replace always-true
'len > 0' guard with lengths aligned to xl.meta binary layout
(4/5/8/12 bytes for magic+version+header fields). Remove redundant
empty-slice test.

Hoist RUSTFLAGS to workflow top-level env to eliminate per-job
duplication. Update README with unified script documentation.

Net: -118 lines, zero functionality loss.

* perf(fuzz): optimize CI runtime with build/run split and matrix parallelization

Restructure fuzz CI workflow to eliminate redundant compilation and
run targets in parallel via matrix strategy.

Workflow changes:
- Split into fuzz-build (compile once) and matrix run jobs
- PR smoke: 3 targets parallel, 60s each, timeout 30min (was 120min)
- Nightly: 3 targets parallel, 300s each, timeout 60min (was 180min)
- Pass compiled harness via actions/artifact between jobs
- Hoist RUSTFLAGS to workflow top-level env

Script consolidation:
- Replace 3 duplicated scripts with single parameterized run.sh
- Supports BUILD_ONLY, SKIP_BUILD, MAX_TOTAL_TIME env vars

Target optimizations:
- Remove rustfs binary crate from fuzz dependencies (was pulling
  979 transitive deps); move archive path validation to rustfs-utils
- Optimize local_metadata: reduce prefix iterations from 8-10x4
  calls to 5 prefixes with parser-only (no decompress), aligned
  with xl.meta binary layout (4/5/8/12 bytes)
- Add unit tests for archive path validation in rustfs-utils
- Update fuzz/README.md with unified script documentation

Expected: PR smoke wall-clock from ~120min (frequent timeout)
to ~40min; nightly from ~180min to ~60min.

* fix(rpc): resolve internode metrics via app context

* fmt

* ci: speed up fuzz smoke artifact restore

---------

Signed-off-by: houseme <housemecn@gmail.com>
2026-06-23 21:36:39 +08:00
Henry Guo 7bdb25ae9d feat(scanner): define replication boundary contract (#3630)
* feat(scanner): define replication boundary contract

* fix(scanner): propagate replication boundary metrics

* fix(scanner): preserve repair metadata on merge

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-23 21:35:36 +08:00
唐小鸭 eff656e086 fix(storage): restore legacy SSE-S3 read compatibility (#3584)
* Update .gitignore

* Fix. fixed SSE-S3 compatibility issues in large-scale testing

* fix

* fix(ecstore): reject whitespace bucket names

* Update replication_extension_test.rs

* style(ecstore): format bucket whitespace test

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <cxymds@gmail.com>
2026-06-23 21:35:17 +08:00
Zhengchao An 5c60f0cae9 refactor: centralize owner server config reads (#3793) 2026-06-23 21:27:30 +08:00
Zhengchao An 70a2441407 refactor: route notify dispatch through app context (#3789)
* refactor: route notify dispatch through app context

* refactor: route admin IAM globals through app context (#3791)

* refactor: centralize IAM root credential access (#3792)
2026-06-23 20:05:28 +08:00
houseme da565c11bc refactor: migrate readiness and site replication app context (#3788)
* refactor(runtime): route readiness lock clients via app context

* refactor(admin): route site replication iam via app context

* refactor(admin): route site replication oidc via app context
2026-06-23 19:37:34 +08:00
houseme 46775ae019 feat: harden runtime capability snapshots (#3784)
* feat(admin): expose runtime capability snapshots

* feat(runtime): refine workload admission snapshots

* test(ci): align architecture migration checks

* build(deps): bump memmap2 for RUSTSEC-2026-0186

* build(deps): refresh cargo deny lockfile
2026-06-23 18:10:49 +08:00
Zhengchao An fd2ab38581 refactor: route IAM reads through app context (#3786) 2026-06-23 17:57:55 +08:00
Zhengchao An 61cfd4fc13 refactor: route runtime consumers through app context (#3785) 2026-06-23 17:21:34 +08:00
Zhengchao An a62e2b6c94 refactor: route action credentials through app context (#3783) 2026-06-23 16:29:46 +08:00
houseme e42c6df0e8 fix(runtime): remove high-impact unwrap paths (#3755)
* fix(runtime): remove high-impact unwrap paths

* fix(runtime): propagate managed SSE metadata errors

* fix(runtime): add typed OPA config errors

* fix(runtime): harden config and credential helpers

* fix(runtime): remove SSE hmac unwraps

* fix(runtime): complete SSE helper error propagation

* fix(trusted-proxies): avoid legacy global init panics

* test(credentials): allow deprecated rpc token check

* fix(storage): harden object lock retention parsing

* chore(checks): refresh layer dependency baseline

* chore(checks): refresh layer dependency baseline

* Update layer-dependency-baseline.txt

Signed-off-by: houseme <housemecn@gmail.com>

* test(context): avoid clone on copy boot time

---------

Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
Co-authored-by: heihutu <heihutu@gmail.com>
2026-06-23 15:12:47 +08:00
Zhengchao An 825c01060c refactor: route admin config writes through app context (#3782) 2026-06-23 13:46:13 +08:00
Zhengchao An 4ef899dd5c refactor: route admin kms manager through app context (#3781) 2026-06-23 13:18:38 +08:00
Zhengchao An d8a53d090f refactor: route admin status metrics through app context (#3780) 2026-06-23 13:14:00 +08:00
Zhengchao An cb585c7fcf refactor: route admin replication stats through app context (#3779) 2026-06-23 13:09:50 +08:00
Zhengchao An 08d092562c refactor: route site replication tls through app context (#3778) 2026-06-23 12:56:54 +08:00
houseme a6878e8fce fix(runtime): remove startup panic fallbacks (#3754)
* fix(runtime): remove startup panic fallbacks

* test(runtime): cover buffer profile fallback safety

* test(runtime): reduce panic-style assertions

* fix(runtime): expose fallible env config setup

* test(runtime): simplify permit acquisition assertion

* test(runtime): tighten operation helper assertions

* fix(filemeta): stop panicking on invalid free version ids

* fix(init): satisfy buffer profile clippy lints

* fix(lock): harden fast lock config construction

* chore(checks): refresh layer dependency baseline

---------

Signed-off-by: houseme <housemecn@gmail.com>
2026-06-23 12:31:26 +08:00
houseme 583a23bdf2 fix(ecstore): replace panic-driven pool and set stubs (#3753)
* fix(ecstore): replace panic-driven pool and set stubs

* test(runtime): tolerate restricted local bind checks

* fix(ecstore): remove remaining trait stub placeholders

* fix(ecstore): tighten trait stub follow-up semantics

* chore: ignore local worktrees

* chore: update layer dependency baseline for resolve_* context entries

Add 7 accepted infra->app dependency entries introduced by recent
refactoring PRs (#3770, #3771, #3772) that route global state lookups
through app::context::resolve_* functions.

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
2026-06-23 12:31:17 +08:00
Zhengchao An c421e73fef refactor: route site replication iam through app context (#3777) 2026-06-23 12:20:57 +08:00
Zhengchao An 0ebd3911c8 refactor: route admin peer systems through app context (#3776) 2026-06-23 11:45:07 +08:00
Zhengchao An 2fe059811a refactor: route admin topology reads through app context (#3774)
* refactor: route admin topology reads through app context

* chore: accept app context layer baseline entries
2026-06-23 11:22:20 +08:00
Zhengchao An 0acc8f8b43 refactor: route admin runtime reads through app context (#3773) 2026-06-23 10:30:55 +08:00
Zhengchao An 9615086154 refactor: route RPC node globals through app context (#3772) 2026-06-23 10:06:54 +08:00
Zhengchao An 747db971ee refactor: route RPC IAM through app context (#3771) 2026-06-23 09:18:25 +08:00
Zhengchao An 087f794901 refactor: route readiness through app context (#3770) 2026-06-23 08:28:19 +08:00
Zhengchao An 7499dd085d refactor: collapse app notify thin compat boundaries (#3768)
* refactor: collapse app notify thin compat boundaries

* refactor: route app runtime consumers through context (#3769)
2026-06-23 08:13:58 +08:00
Zhengchao An 30559f7e1b refactor: collapse test fuzz ecstore thin bridges (#3765) 2026-06-23 07:04:51 +08:00
Zhengchao An 198fd4f150 refactor(runtime): route RustFS runtime consumers through storage owner (#3756) 2026-06-23 05:17:56 +08:00
Zhengchao An e0b79aa00c refactor: expose test fuzz owner symbols (#3752) 2026-06-23 01:11:46 +08:00
Zhengchao An 6da61b44e5 refactor: expose remaining external owner symbols (#3751) 2026-06-22 23:35:50 +08:00
Zhengchao An ec5f112205 refactor: expose scanner ECStore owner symbols (#3749) 2026-06-22 22:26:25 +08:00
Zhengchao An db7ff8f513 refactor: expose external ECStore owner symbols (#3748) 2026-06-22 21:42:54 +08:00
Zhengchao An 418b5d04f9 refactor: route external ECStore imports through owners (#3747) 2026-06-22 20:57:10 +08:00
Zhengchao An 88a87b3e8f refactor: centralize external ECStore facade aliases (#3746) 2026-06-22 20:37:13 +08:00
Zhengchao An 89805ffb4d refactor: route RustFS ECStore consumers via owner boundary (#3745) 2026-06-22 19:29:01 +08:00
houseme a53ccb2407 fix(ecstore): reject symlink path escapes (#3742)
Co-authored-by: Zhengchao An <anzhengchao@gmail.com>
2026-06-22 19:28:40 +08:00
houseme 2f85ef4455 fix(security): extend outbound egress guard (#3744) 2026-06-22 19:20:31 +08:00
houseme 64aef06c60 fix(replication): harden site-repl versioning sync (#3739) 2026-06-22 19:18:42 +08:00
Zhengchao An e57962d5e8 refactor: remove RustFS owner compat bridges (#3743) 2026-06-22 18:55:59 +08:00
Zhengchao An d3796d6c10 refactor: remove external owner compat bridges (#3741) 2026-06-22 18:28:35 +08:00
Zhengchao An 539c68778d refactor: remove standalone compat bridge modules (#3740) 2026-06-22 17:35:58 +08:00
Zhengchao An b63b076275 refactor: remove remaining compatibility bridges (#3738) 2026-06-22 16:53:14 +08:00
安正超 a66350b645 refactor: remove secondary compatibility bridges (#3737)
* refactor: remove app admin secondary compat bridges

* refactor: remove storage core compat bridge

* refactor: remove nested compat bridges

* refactor: remove admin handlers compat bridge

* refactor: remove runtime local compat bridges
2026-06-22 16:29:33 +08:00
安正超 599e3632d1 refactor: use relative fuzz compat consumers (#3736) 2026-06-22 15:44:59 +08:00
安正超 cca9e83a8b refactor: use relative standalone compat consumers (#3734) 2026-06-22 15:07:58 +08:00
安正超 7d81183cf1 refactor: use relative heal test compat consumers (#3733) 2026-06-22 14:55:12 +08:00
安正超 a3202eb6bb refactor: use relative app compat consumers (#3731) 2026-06-22 14:37:20 +08:00
安正超 1a709e14c0 refactor: use relative admin compat consumers (#3729) 2026-06-22 14:06:49 +08:00
houseme cc6909b08b fix(iam): verify sts temp-user persistence (#3722) 2026-06-22 13:54:57 +08:00
houseme 6353f2093d fix(multipart): include upload owner metadata (#3728) 2026-06-22 13:54:37 +08:00
houseme da5ff226ba fix(object): normalize storage class responses (#3726) 2026-06-22 13:54:12 +08:00
houseme 7bfa80cb0b fix(notify): normalize event time precision (#3725) 2026-06-22 13:53:56 +08:00
houseme 2c500d55b7 fix(server): add MinIO health endpoint aliases (#3724) 2026-06-22 13:53:38 +08:00
houseme ed45d1be2d fix(lock): recover stale distributed object guards (#3720)
fix(lock): reclaim expired stale object guards
2026-06-22 13:53:21 +08:00
安正超 c8de6cac75 refactor: use relative storage compat consumers (#3727) 2026-06-22 13:52:45 +08:00
安正超 da08e411f1 refactor: use relative local compat consumers (#3723) 2026-06-22 13:22:48 +08:00
cxymds 2f25cf606e fix(storage): harden rebalance decommission state (#3515) 2026-06-22 12:03:13 +08:00
安正超 2dcc32db5a refactor: use relative root compat paths (#3721) 2026-06-22 12:02:37 +08:00
安正超 30fa7e2ad4 refactor: use relative local compat paths (#3719) 2026-06-22 11:13:37 +08:00
安正超 abb5f41726 refactor: collapse compat facade self refs (#3718) 2026-06-22 10:36:51 +08:00
houseme baed4d5315 fix(object): bound snowball auto extract (#3715) 2026-06-22 09:54:19 +08:00
dependabot[bot] 5a601869f6 chore(deps): bump sysinfo from 0.39.3 to 0.39.4 in the dependencies group (#3712) 2026-06-22 09:53:57 +08:00
安正超 de158743c3 refactor: split compat facade imports (#3716) 2026-06-22 09:53:22 +08:00
安正超 89552e1df0 refactor: guard root compat facade aliases (#3714) 2026-06-22 09:17:11 +08:00
安正超 6ff2824cbb refactor: narrow remaining local compat exports (#3713) 2026-06-22 09:10:23 +08:00
安正超 6287483053 refactor: narrow local compat re-exports (#3711) 2026-06-22 08:51:08 +08:00
安正超 c7f7ba667f refactor: localize storage core compat consumers (#3710) 2026-06-22 07:50:55 +08:00
安正超 76af78385b refactor: localize owner storage compat consumers (#3709) 2026-06-22 07:19:22 +08:00
安正超 1b718498f4 refactor: retire root storage compat facade (#3708) 2026-06-22 07:06:08 +08:00
安正超 c13f42e9a0 refactor: wrap disk rpc compat trait methods (#3707) 2026-06-22 06:31:24 +08:00
安正超 7eb9a4759e refactor: wrap bucket compat trait methods (#3706) 2026-06-22 04:32:37 +08:00
安正超 b8a0fc4e04 refactor: prune root e2e raw facade paths (#3705) 2026-06-22 03:30:03 +08:00
安正超 d39815470e refactor: prune consumer storage compat paths (#3704) 2026-06-22 02:31:30 +08:00
安正超 e528ee9452 refactor: prune app admin raw facade paths (#3703) 2026-06-22 02:25:58 +08:00
安正超 2a18088ca5 refactor: centralize storage compat facade paths (#3702) 2026-06-22 01:20:22 +08:00
安正超 eb33e9f2ea refactor: prune outer compat signature aliases (#3701) 2026-06-22 00:15:34 +08:00
安正超 ef111a3e40 refactor: prune outer compat facade aliases (#3700) 2026-06-21 23:33:44 +08:00
安正超 6b3d96fde3 refactor: prune trait import compat re-exports (#3699) 2026-06-21 23:17:58 +08:00
安正超 f35821f75d test(s3): reclassify passing atomic conditional write (#3698) 2026-06-21 20:54:52 +08:00
houseme 9e6d0c7292 chore(deps): refresh workflow actions and crate pins (#3696) 2026-06-21 20:29:17 +08:00
安正超 a0e7957e92 refactor: prune admin app compat re-exports (#3697) 2026-06-21 20:28:43 +08:00
安正超 5c01641760 refactor: prune storage owner compat re-exports (#3695) 2026-06-21 19:36:10 +08:00
安正超 48ed331fc8 refactor: prune root runtime compat re-exports (#3694) 2026-06-21 18:42:25 +08:00
安正超 0cf9d07e40 refactor: prune test protocol compat aliases (#3693) 2026-06-21 18:09:39 +08:00
安正超 30b46640b4 refactor: prune edge compat aliases (#3692) 2026-06-21 17:41:07 +08:00
安正超 9d510e9d5b refactor: prune admin app compat aliases (#3690) 2026-06-21 16:40:40 +08:00
安正超 84d662c312 refactor: prune storage compat bucket aliases (#3689) 2026-06-21 15:56:13 +08:00
安正超 6e42afcea5 refactor: prune admin config compat aliases (#3688) 2026-06-21 15:21:25 +08:00
安正超 f4f303a773 refactor: prune root runtime compat aliases (#3687) 2026-06-21 14:39:22 +08:00
安正超 6bfed0bd43 refactor: prune root bucket compat modules (#3686) 2026-06-21 14:08:05 +08:00
安正超 32d7e55564 refactor: compose workload admission providers (#3685) 2026-06-21 13:36:12 +08:00
安正超 7b81f2ed53 refactor: consume storage concurrency policies (#3684) 2026-06-21 12:44:44 +08:00
安正超 60b0fbf075 refactor: bridge storage concurrency policies (#3683) 2026-06-21 11:56:51 +08:00
安正超 81f11df4dd refactor: add cluster status snapshots (#3682) 2026-06-21 11:25:44 +08:00
安正超 15cdeee775 refactor: add ecstore cluster read models (#3681) 2026-06-21 10:45:36 +08:00
安正超 d26d98104a refactor: prune ecstore root global facades (#3680) 2026-06-21 09:52:19 +08:00
安正超 d4150117c3 refactor: narrow startup and ecstore root facades (#3679)
* refactor: narrow startup owner visibility

* refactor: prune remaining ecstore root facades
2026-06-21 09:42:15 +08:00
安正超 0985225448 refactor: consolidate ecstore public facade (#3678)
* refactor: expand ecstore compatibility facade

* test: enforce ecstore api facade imports

* refactor: hide legacy ecstore layout modules

* refactor: hide ecstore facade root modules
2026-06-21 09:09:11 +08:00
安正超 77e005ee98 refactor: narrow startup compatibility shims (#3677) 2026-06-21 08:54:21 +08:00
houseme 5f3722e0a5 chore(deps): update flake.lock (#3676)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/49a4bd0' (2026-06-12)
  → 'github:NixOS/nixpkgs/3e41b24' (2026-06-16)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/5b929d8' (2026-06-13)
  → 'github:oxalica/rust-overlay/e8e2021' (2026-06-20)
2026-06-21 08:39:45 +08:00
安正超 74571cc4ca fix(format): remove whitespace regressions (#3675) 2026-06-21 08:39:26 +08:00
GatewayJ c47dea0009 fix(scanner): preserve complete usage snapshots (#3654) 2026-06-21 08:21:20 +08:00
安正超 796f293b80 test(ecstore): cover DNS resolver raw errors (#3674) 2026-06-21 08:17:45 +08:00
安正超 9b3fda9e30 refactor: route ecstore public surfaces through api (#3673) 2026-06-21 05:23:23 +08:00
安正超 28771f071e refactor: narrow startup orchestration visibility (#3672) 2026-06-21 04:13:55 +08:00
安正超 995af8ebdc refactor: split startup runtime service ownership (#3671)
* refactor: move startup kms context owner

* refactor: move startup iam runtime facade

* refactor: split startup metadata and notification owners

* refactor: split startup runtime service owners

* refactor: retire startup service components

* refactor: narrow startup owner module visibility
2026-06-21 02:53:58 +08:00
安正超 468fd81a8a refactor: move startup app context owner (#3670) 2026-06-21 01:56:27 +08:00
安正超 6912c6d000 refactor: make app context bootstrap explicit (#3669) 2026-06-21 00:28:52 +08:00
安正超 d7c6c07d77 refactor: keep embedded identity with startup config (#3668) 2026-06-21 00:05:50 +08:00
GatewayJ 3e7e39a59c fix(ecstore): retry transient endpoint DNS failures (#3652)
* fix(ecstore): retry transient endpoint DNS failures

* fix(ecstore): satisfy DNS retry clippy check

* fix(ecstore): harden endpoint DNS retry

* test(ecstore): stabilize DNS retry error test
2026-06-20 23:53:11 +08:00
安正超 6056a518b8 refactor: streamline embedded startup result shell (#3667) 2026-06-20 23:52:33 +08:00
Hiroaki KAWAI deee13901d fix(logging): suppress scanner leader eviction noise (#3653) 2026-06-20 22:23:42 +08:00
安正超 1c58ee0d7e refactor: return embedded identity from startup (#3666) 2026-06-20 22:20:26 +08:00
安正超 6d13a0f15a refactor: keep embedded builder as startup shell (#3665) 2026-06-20 22:17:31 +08:00
安正超 150ac967d3 refactor: extract embedded handle boundaries (#3664) 2026-06-20 22:12:50 +08:00
Henry Guo ed1986a5ac feat(table-catalog): harden credential and maintenance ops (#3631)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-20 22:11:18 +08:00
安正超 ebd4882ec1 refactor: extract embedded build orchestration (#3663) 2026-06-20 22:10:40 +08:00
安正超 0cf7e5cf03 refactor: extract embedded startup control helpers (#3661) 2026-06-20 21:57:03 +08:00
安正超 84e292c6da refactor: extract embedded startup server helpers (#3660) 2026-06-20 18:38:02 +08:00
安正超 3edff6a436 refactor: extract embedded runtime orchestration hooks (#3659) 2026-06-20 18:20:56 +08:00
安正超 d933d5e938 refactor: move ecstore rebalance flow modules (#3658) 2026-06-20 17:56:11 +08:00
安正超 1595b2bc3f refactor: move ecstore rebalance migration helpers (#3657) 2026-06-20 17:15:30 +08:00
安正超 6d82b6f855 refactor: move ecstore rebalance worker helpers (#3656) 2026-06-20 16:44:48 +08:00
安正超 f5f727e077 refactor: move ecstore rebalance metadata helpers (#3655) 2026-06-20 16:31:58 +08:00
安正超 f5bd31173d refactor: move ecstore pool-space builder (#3651) 2026-06-20 15:38:49 +08:00
安正超 1d9fd3d7ac refactor: move ecstore rebalance support helpers (#3650) 2026-06-20 13:34:23 +08:00
安正超 aad5379e0c refactor: move ecstore pool-space helpers (#3649) 2026-06-20 12:44:16 +08:00
安正超 3035cad082 refactor: move ecstore set heal helpers (#3648) 2026-06-20 12:03:22 +08:00
安正超 1ea7b36ab4 refactor: move ecstore endpoint layout owners (#3647) 2026-06-20 10:28:25 +08:00
安正超 16d667da0a refactor: move ecstore format layout owners (#3646) 2026-06-20 09:58:50 +08:00
安正超 88dfdda4a0 refactor: establish ecstore layout foundation (#3645) 2026-06-20 09:54:36 +08:00
安正超 4ba8058dbc refactor: expose extension runtime snapshots (#3644) 2026-06-20 09:48:49 +08:00
安正超 5ad47d1354 refactor: publish ops profiler runtime contract (#3643) 2026-06-20 09:42:13 +08:00
安正超 9a3d5bd2a0 refactor: reuse embedded lifecycle publication boundaries (#3642) 2026-06-20 09:15:00 +08:00
安正超 77d842ae34 refactor: reuse embedded runtime service boundaries (#3641) 2026-06-20 08:15:44 +08:00
安正超 131e9dc804 refactor: reuse embedded startup phase boundaries (#3640) 2026-06-20 06:58:48 +08:00
安正超 f8117eb46b refactor: extract startup tls material boundary (#3639) 2026-06-20 05:55:32 +08:00
安正超 46a02b6d03 refactor: extract startup runtime hook boundary (#3638) 2026-06-20 05:32:49 +08:00
安正超 5e96151d9c refactor: extract optional runtime sidecar boundary (#3637) 2026-06-20 04:28:33 +08:00
安正超 aa5de1c908 refactor: extract startup service component boundary (#3636) 2026-06-20 03:26:09 +08:00
安正超 7dfe372bd2 refactor: extract startup ready lifecycle boundary (#3635) 2026-06-20 02:25:20 +08:00
安正超 e9037f9eb0 refactor: extract startup shutdown lifecycle boundary (#3634) 2026-06-20 01:15:39 +08:00
安正超 eec1792cd2 refactor: extract optional runtime startup boundary (#3633) 2026-06-20 00:12:44 +08:00
安正超 74fcfddd7f refactor: extract optional runtime shutdown boundary (#3632) 2026-06-19 22:53:42 +08:00
安正超 e6391598f0 feat: add ops profiler capability snapshots (#3629) 2026-06-19 22:35:36 +08:00
安正超 ff638d1140 feat: add ops profiler extension schema (#3628) 2026-06-19 22:31:18 +08:00
Henry Guo 71809ba0bb test(scanner): add usage freshness validation evidence (#3627) 2026-06-19 21:34:12 +08:00
Henry Guo 2052336a53 fix(heal): track root erasure-set rebuild status (#3625) 2026-06-19 21:33:58 +08:00
Henry Guo 68a061c548 feat(table-catalog): add vendor compatibility profiles (#3624) 2026-06-19 21:33:31 +08:00
安正超 afe46c01e7 refactor: prune consumer ecstore object aliases (#3622) 2026-06-19 21:33:00 +08:00
安正超 08b8f58e64 refactor: prune notify ecstore object alias (#3620) 2026-06-19 20:11:56 +08:00
安正超 a5058e955d test: guard ecstore object api boundaries (#3619) 2026-06-19 19:04:20 +08:00
Henry Guo 8d91e2116f docs(table-catalog): document S3 Tables support matrix (#3616) 2026-06-19 18:37:08 +08:00
Henry Guo a689ff3c39 feat(scanner): wake cycles for dirty usage refresh (#3617) 2026-06-19 18:36:48 +08:00
安正超 54ffbedbc8 refactor: remove ecstore operation compat facades (#3608) 2026-06-19 17:01:12 +08:00
安正超 132c8ae77d feat: expose runtime workload owner snapshots (#3607) 2026-06-19 12:31:43 +08:00
安正超 7cb7aefc3b feat: expose replication workload admission snapshot (#3606) 2026-06-19 11:13:17 +08:00
安正超 00ca3b7c1c feat: expose heal workload admission snapshot (#3605) 2026-06-19 10:39:09 +08:00
houseme 8cf3c0bfbd fix(ecstore): support MinIO DARE fixture compatibility (#3590) 2026-06-19 10:13:39 +08:00
安正超 80b1fca02a feat: wire runtime capability snapshot providers (#3604) 2026-06-19 09:50:42 +08:00
安正超 b106b628c1 feat: expose local scheduler admission snapshots (#3603) 2026-06-19 09:14:50 +08:00
houseme 53ff19dd2d fix(kms): harden local master key storage (#3589) 2026-06-19 08:50:35 +08:00
Henry Guo 2c6881695f test(table-catalog): add Iceberg compatibility and failure coverage (#3581)
* test(table-catalog): add Iceberg client compatibility coverage

* test(table-catalog): add production failure coverage

* fix(table-catalog): generate valid failure probe requests

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-19 08:49:59 +08:00
安正超 56c3cf50ae feat: add scheduler workload admission contracts (#3602) 2026-06-19 08:41:22 +08:00
安正超 ada6f7587e refactor: flatten test harness storage compat aliases (#3596)
* refactor: flatten test harness storage compat aliases

* refactor: flatten rustfs storage compat aliases (#3597)

* refactor: prune runtime storage compat surface (#3598)

* refactor: flatten runtime secondary storage compat (#3599)

* docs: add scheduler placement profiling baselines (#3600)

* feat: add observability topology capability contracts (#3601)
2026-06-19 08:30:47 +08:00
安正超 b1c6578df1 refactor: narrow test harness compatibility surfaces (#3592) 2026-06-19 07:10:52 +08:00
安正超 b14e49e84e refactor: narrow IAM and Swift compatibility surfaces (#3587)
* refactor: narrow IAM and Swift compatibility surfaces

* refactor: narrow heal and scanner compatibility surfaces (#3588)

* refactor: narrow RustFS runtime compatibility surfaces (#3591)
2026-06-19 03:06:57 +08:00
houseme 9b1272529a fix(ecstore): avoid blocking fs in async paths (#3586) 2026-06-19 01:44:17 +08:00
安正超 a860f2b40c refactor: narrow storage compatibility surfaces (#3585)
* refactor: narrow storage compatibility surfaces

* refactor: narrow observability compatibility surfaces
2026-06-19 00:56:14 +08:00
安正超 f11b07bf83 chore: guard ECStore compat passthroughs (#3583) 2026-06-19 00:20:09 +08:00
安正超 205f964dc5 refactor: tighten storage compat store_api aliases (#3582)
* refactor: collapse storage compat store_api modules

* chore: guard storage compat store_api aliases
2026-06-18 22:50:02 +08:00
安正超 c28fee0013 refactor: continue storage api contract cleanup (#3580)
* refactor: move delete object contracts to storage api

* refactor: narrow store api compatibility exports

* refactor: route table catalog test through storage compat
2026-06-18 22:42:02 +08:00
Henry Guo 80144ec886 feat(scanner): expose usage freshness status (#3577)
* feat(scanner): expose usage freshness status

* fix(scanner): reset dirty usage cycle state

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-18 21:14:55 +08:00
Henry Guo 5d9fee5c0c feat(table-catalog): expose backing migration contract (#3574)
* feat(table-catalog): expose backing migration contract

* fix(table-catalog): require register auth for external sync

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-18 21:14:40 +08:00
安正超 7b0cb9e725 refactor: prune storage compatibility re-export allowances (#3579)
* refactor: prune storage compatibility re-export allowances

* fix: reject whitespace-padded dot path segments
2026-06-18 21:14:24 +08:00
安正超 f3fcdd4ba2 refactor: narrow remaining storage compatibility exports (#3578) 2026-06-18 19:18:32 +08:00
安正超 08371a6e09 refactor: narrow storage compatibility exports (#3576) 2026-06-18 18:31:14 +08:00
安正超 c098184c49 refactor: clean runtime and test storage boundaries (#3573)
* refactor: clean runtime observability select boundaries

* refactor: clean test harness fuzz storage boundaries
2026-06-18 18:09:25 +08:00
安正超 8313767f75 refactor: clean app storage admin runtime boundaries (#3572) 2026-06-18 16:42:51 +08:00
安正超 bdb2461b55 refactor: clean scanner heal runtime boundaries (#3571) 2026-06-18 16:12:27 +08:00
Henry Guo 51409c40ef feat(table-catalog): add external catalog bridge sync (#3569)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-18 15:30:41 +08:00
houseme 8cf11af649 fix(security): add shared outbound egress guard (#3567)
* fix(security): block unsafe outbound webhook targets

* chore: keep issue-3557 plan local only
2026-06-18 15:30:09 +08:00
houseme fc93a27974 fix(storage): harden local SSE-S3 fallback (#3564)
* fix: harden local SSE-S3 fallback

* test: update managed SSE-S3 assumptions

* chore: keep issue plan local only

* test(ci): seed local SSE key for s3-tests
2026-06-18 15:29:52 +08:00
安正超 c76e4dc6bc refactor: clean Swift ECStore boundaries (#3570) 2026-06-18 15:18:55 +08:00
安正超 0e7e2660bb refactor: clean remaining storage DTO imports (#3568) 2026-06-18 14:44:46 +08:00
安正超 acdf439371 refactor: clean external DTO consumers (#3566)
* refactor: clean external DTO consumers

* fix: handle empty erasure shard recovery
2026-06-18 14:22:44 +08:00
安正超 99941f7e7c refactor: clean external operation consumers (#3565) 2026-06-18 12:39:41 +08:00
houseme d2a135b397 fix(admin): require admin auth for metrics (#3562)
fix: require admin auth for metrics
2026-06-18 12:39:16 +08:00
GatewayJ a30cafa73f fix(filemeta): detect physical data dirs for overwrite cleanup (#3510) 2026-06-18 11:58:12 +08:00
安正超 24443c829d refactor: clean shared list operation consumers (#3563) 2026-06-18 11:57:46 +08:00
Henry Guo 46e200c22b feat(scanner): prioritize dirty usage refresh work (#3538) 2026-06-18 11:03:23 +08:00
houseme 32aca6d835 feat(fuzz): scaffold cargo-fuzz harness for smoke targets (#3537) 2026-06-18 11:02:56 +08:00
Henry Guo ea70d5697a test(table-catalog): harden smoke catalog probes (#3535) 2026-06-18 11:01:09 +08:00
安正超 57403525ee refactor: move heal and namespace contracts (#3560) 2026-06-18 11:00:21 +08:00
安正超 e5cad7ed20 refactor: move object operation contracts (#3559) 2026-06-18 09:48:13 +08:00
安正超 3fb4cb3d65 refactor: move list operations contract (#3550) 2026-06-18 08:48:18 +08:00
安正超 36f7ad6936 refactor: move walk options contract (#3549) 2026-06-18 08:18:26 +08:00
Henry Guo 604379d62f feat(table-catalog): harden table row-level commit conflicts (#3517)
* feat(table-catalog): harden snapshot conflict validation

* feat: harden table row-level commit conflicts

* fix: accept additional row-level snapshot shapes

* test(table-catalog): expand smoke catalog probes

* fix(table-catalog): satisfy row-level clippy checks

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-18 07:32:20 +08:00
安正超 24fa03e04b refactor: move object list response contracts (#3548) 2026-06-18 07:19:07 +08:00
安正超 80ed63484b refactor: move object precondition contracts (#3547) 2026-06-18 06:34:30 +08:00
安正超 54bbd05b25 refactor: move object list helper contracts (#3546) 2026-06-18 06:09:33 +08:00
houseme 87a3d107d6 test(property): add path validator invariants (#3541)
test(property): add path validator invariants for #3525
2026-06-18 02:14:24 +08:00
houseme bc769948ab test(property): add erasure recoverability invariants (#3540) 2026-06-18 01:34:07 +08:00
安正超 53337a6f71 refactor: move HTTP range helper contracts (#3533) 2026-06-18 00:47:23 +08:00
houseme cc84d9065e test(property): add filemeta invariants (#3536)
* test(property): add filemeta invariants for #3523

* docs: relocate issue-3518 property testing plan

* docs: keep issue-3518 property plan local only
2026-06-18 00:36:55 +08:00
houseme 841f86fe90 test(property): expand parser invariants (#3534)
* test(property): harden parser invariants for #3522

* test(property): satisfy clippy for #3522
2026-06-17 23:52:30 +08:00
安正超 919deeb816 refactor: move object option helper contracts (#3521) 2026-06-17 22:56:10 +08:00
Henry Guo a97122ab80 fix(heal): retry recoverable bucket heal failures (#3509)
* fix(heal): retry recoverable bucket heal failures

* fix(heal): keep merged heal tokens cancellable

* fix(heal): group queue processing context

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-17 22:45:29 +08:00
Henry Guo a9aba323c6 fix(scanner): restore prompt bucket usage scans (#3516)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-17 21:49:56 +08:00
houseme 8d24d9133b perf(put): comprehensive PUT performance optimization (#3514)
* perf(put): add eager path metrics and isolation tooling

* fix(decommission): persist progress adaptively (#3497)

Persist decommission progress after either the existing time interval or a migrated-item threshold, and flush progress baselines after bucket and terminal-state saves.

Also stabilize the OIDC discovery mock used by the pre-commit gate.

* refactor: move bucket operations contract (#3507)

* fix(s3): handle multipart flexible checksums (#3508)

* fix(io-core): avoid blocking on pooled buffer return

* perf(put): add slow inflight diagnostics

* perf(put): fix 16KiB regression with threshold and pool bypass

- Lower SMALL_EAGER_PUT_MAX_SIZE from 256KB to 8KB so objects >8KiB
  use the streaming BufReader path (matches baseline behavior)
- Add POOL_BYPASE_MAX_SIZE (16KiB) to bypass BytesPool for very small
  objects, avoiding Small-tier Mutex contention under high concurrency
- Add read_small_put_body_exact_direct() for direct Vec<u8> allocation
- Fix stale test assertions to match new 8KB threshold

Root cause analysis: the 16KiB regression was primarily caused by
instrumentation overhead in set_disk.rs (4x Instant::now() + metrics
per PUT), not BytesPool contention. Lowering the threshold eliminates
the eager-path overhead for 16KiB+ objects.

* perf(put): gate stage metrics behind observability flag

Add put_stage_metrics_enabled() AtomicBool switch in io-metrics crate.
When disabled (default), record_put_object_path() and
record_put_object_stage_duration() are no-ops, avoiding unnecessary
histogram/counter macro overhead in the PUT hot path.

The flag is set to true during startup when OTEL metric export is
enabled (rustfs_obs::observability_metric_enabled() == true).

This eliminates the per-request metrics overhead that contributed
to the 16KiB PUT regression when metrics collection is not active.

* perf(put): comprehensive optimization - restore eager path, cache env, remove UUID

Change 1: Restore SMALL_EAGER_PUT_MAX_SIZE from 8KB to 1MB
- The try_lock() fix (d13a189e3) eliminates the blocking that caused
  service health timeouts under 512KiB c64 load
- Eager path with BytesPool is now safe for objects up to 1MB
- Recovers the eager path benefit for 32KiB-256KiB objects

Change 2: Adjust POOL_BYPASE_MAX_SIZE from 16KB to 4KB
- With eager path restored to 1MB, objects 4KB-1MB benefit from pool reuse
- Only ≤4KB objects bypass the pool (allocation cost negligible)

Change 3: Cache RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES via OnceLock
- Eliminates per-encode std::env::var() syscall
- Env var still works (read once at first use)

Change 4: Replace Uuid::new_v4() with Uuid::nil() in Erasure construction
- _id field is unused in hot paths (documented in code)
- Eliminates CSPRNG syscall per PUT request

Change 5: Add concurrency-aware buffer sizing to PUT path
- Reuses get_concurrency_aware_buffer_size() from GET path
- Reduces buffer size under high concurrency (0.4x at >8 concurrent)
- Lowers memory pressure for >1MB streaming PUTs

* chore: add pyroscope feature flag and clean up imports

- Add pyroscope feature flag forwarding to rustfs-obs
- Remove unused allow(non_upper_case_globals) in globals.rs
- Sort imports and fix Cargo.toml formatting consistency

* style: fix import ordering and code formatting

- Sort imports alphabetically in globals.rs, encode.rs
- Fix indentation in erasure_coding encode/erasure
- Clean up HashReader formatting in object_usecase.rs

* fix(test): use tokio::test for request_logging_layer tests

The tests call tokio::spawn via RequestContextLayer, which requires a
Tokio runtime. Changed from #[test] + futures::executor::block_on to
#[tokio::test] + .await, and replaced tracing::subscriber::with_default
with tracing::subscriber::set_default to support async.

* fix(bench): normalize no-space throughput/latency parsing in to_bps/to_ms

When a benchmark tool prints throughput without a separator (e.g. 123MiB/s),
awk '{print $2}' returns empty because the whole string is one field,
causing to_bps to return N/A and losing valid measurements in CSV output.

Insert a space between number and unit via sed before awk field splitting.
Same fix applied to to_ms for latency values like '50ms'.

Also add TODO comment on PUT path noting that get_concurrency_aware_buffer_size
reads ACTIVE_GET_REQUESTS instead of PUT concurrency (PR #3514 review).

Refs: PR #3514 review comments by chatgpt-codex-connector

* fix(metrics): correct POOL_BYPASS comments and separate PUT vs generic stage metrics

- Fix 3 comment-code mismatches: POOL_BYPASS_MAX_SIZE is 4KiB, not 16KiB
- Add generic record_stage_duration() with separate histogram
  (rustfs_internal_stage_duration_ms) for non-PUT paths
- Replace record_put_object_stage_duration with record_stage_duration in
  metacache_set, store_list_objects, and bucket_lifecycle_ops to avoid
  polluting PUT-specific dashboards with listing/lifecycle timings
- Fix flaky test: serialize tests mutating PUT_STAGE_METRICS_ENABLED with
  METRICS_FLAG_LOCK mutex and explicitly set desired state at test start

Refs: PR #3514 review comments by chatgpt-codex-connector

* style: apply cargo fmt to metacache_set.rs

---------

Co-authored-by: cxymds <cxymds@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-06-17 21:19:11 +08:00
majinghe a58692f550 fix(heal): bind service account to sts and deployment (#3513) 2026-06-17 18:36:24 +08:00
安正超 38bbba4c5c fix(s3): handle multipart flexible checksums (#3508) 2026-06-17 11:54:58 +08:00
安正超 ed55857bca refactor: move bucket operations contract (#3507) 2026-06-17 09:10:38 +08:00
cxymds 5b36ef5556 fix(decommission): persist progress adaptively (#3497)
Persist decommission progress after either the existing time interval or a migrated-item threshold, and flush progress baselines after bucket and terminal-state saves.

Also stabilize the OIDC discovery mock used by the pre-commit gate.
2026-06-17 08:17:59 +08:00
安正超 1830911973 fix(s3): return content disposition on get object (#3504) 2026-06-17 02:23:13 +08:00
安正超 0c259547d1 refactor: move multipart DTO contracts (#3505) 2026-06-17 00:33:06 +08:00
安正超 2ff69ae21c refactor: remove ecstore bucket DTO aliases (#3503) 2026-06-16 22:10:07 +08:00
cxymds d094d91925 fix(site-replication): harden service account sync (#3500) 2026-06-16 21:20:13 +08:00
Henry Guo cd96491b39 feat(table-catalog): add refs and views semantics (#3502)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-16 21:19:27 +08:00
安正超 966756d788 test: guard storage contract cleanup (#3501) 2026-06-16 21:18:52 +08:00
cxymds c405470f73 test(decommission): cover tier lifecycle recovery flows (#3494)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-16 16:48:24 +08:00
cxymds 0f37c5675e fix(ecstore): bound decommission bucket concurrency (#3498) 2026-06-16 15:11:54 +08:00
cxymds ba0ba0d35e fix(lifecycle): persist remote tier delete journal tasks (#3493)
fix: persist remote tier delete journal tasks
2026-06-16 10:38:41 +08:00
Henry Guo b5d19e6595 test(scanner): close parity validation harness (#3486) 2026-06-16 08:46:22 +08:00
Adnan Hussain Turki 401e876209 fix(admin): reject access keys and names containing any whitespace (#3488) 2026-06-16 08:46:05 +08:00
cxymds 156e21c90e fix(lifecycle): make tier cleanup recoverable (#3491) 2026-06-16 08:45:49 +08:00
Henry Guo d23ed02b3f feat(table-catalog): add parquet compaction commit (#3487)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-16 08:02:18 +08:00
安正超 0baf90ee88 refactor: remove storage api facade (#3490) 2026-06-16 07:46:32 +08:00
安正超 c26593fa7a refactor: clean config storage boundaries (#3489) 2026-06-16 06:35:32 +08:00
安正超 fbab160c2b refactor: narrow replication storage consumers (#3485) 2026-06-15 22:29:38 +08:00
Henry Guo 65c724c786 feat(table-catalog): add manifest reachability cleanup (#3484)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-15 22:29:23 +08:00
Henry Guo b387689f26 feat(heal): expose scanner-aware operations status (#3483)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-15 22:28:53 +08:00
Ramakrishna Chilaka 956bd19417 perf(ecstore): reuse erasure-decode shard buffers across stripes (#3482)
* perf(ecstore): reuse erasure-decode shard buffers across stripes

ParallelReader::read allocated and zero-filled a fresh
`vec![0u8; shard_size]` per shard on every erasure stripe. Erasure::decode
builds one ParallelReader and loops stripes, so those buffers can be
reused: BitrotReader::read overwrites buf[..n] and the caller truncates to
n, so leftover bytes from the prior stripe are never observed. The decode
loop now hands each stripe's shard buffers back to the reader, which
reuses them (resized to shard_size) on the next stripe. The heal path,
which consumes its buffers into Bytes, never hands them back and is
unchanged.

This path runs on every object GET. Streaming-decode benchmark
(Erasure::decode end-to-end, median):

  4+2 16MiB 1MiB-block:  -9.6% verify-on,  -23.8% verify-off
  6+3 24MiB 1MiB-block:  -9.7% verify-on,  -22.5% verify-off
  4+2  4MiB 64KiB-block: -5.5% verify-on,  -16.3% verify-off

Adds a streaming-decode benchmark covering the ParallelReader path and a
regression test that decodes a multi-stripe object with missing data
shards (so reconstructed buffers are recycled) with bitrot verification
both on and off, asserting byte-exact output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* perf(ecstore): tighten decode buffer reuse and bench harness

Address PR review feedback on the erasure-decode buffer-reuse change:

- Only claim a recycled shard buffer inside the `Some(reader)` branch of
  `ParallelReader::read`, so the take is co-located with the read that
  uses it and missing shards no longer touch the recycle slot.
- Move per-shard `BitrotReader` construction into Criterion `iter_batched`
  setup so reader/UUID construction stays out of the timed decode path,
  and assert decode success plus full output length each iteration.

Re-measured with the cleaner harness (median, before -> after, all
p < 0.05): verify-on -5.3%..-11.7%, verify-off -15.8%..-25.3%.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* perf(ecstore): drop dead recycle slots and tidy decode bench

Address the second PR review pass:

- In `Erasure::decode`, clear recycled slots for missing-shard (None-reader)
  indices before handing buffers back. `ParallelReader::read` only reuses
  slots with an active reader, and `decode_data` always re-allocates the
  reconstructed shard, so retaining those buffers only held memory that could
  never be reused in degraded reads.
- Move the output sink into the benchmark's `iter_batched` setup and validate
  decode success/output length once per config instead of inside the timed
  loop, so neither sink construction nor assertions touch the measurement.

Re-measured with the final harness (median, before -> after, all p < 0.05):
verify-on -5.2%..-9.8%, verify-off -19.4%..-25.8%.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-15 22:28:39 +08:00
Hamza 49c0f13120 Update podman instructions in README (#3479)
Added correct volume mounting option in rootless podman command.

Signed-off-by: Hamza <23738574+w4hf@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-15 19:18:17 +08:00
houseme adec195486 chore(scanner): enable pulsar telemetry feature (#3481) 2026-06-15 18:43:14 +08:00
Henry Guo 7ce9838027 feat(table-catalog): add maintenance worker runtime (#3480) 2026-06-15 18:42:29 +08:00
安正超 f782f94f6a refactor: narrow storage namespace consumers (#3477) 2026-06-15 18:32:21 +08:00
Henry Guo 307d788da1 feat(table-catalog): add production iceberg surfaces (#3473) 2026-06-15 16:53:14 +08:00
Henry Guo d28aedfbb3 feat(scanner): expose replication repair kind metrics (#3476) 2026-06-15 16:52:39 +08:00
安正超 5063524b36 ci: guard migration loss-prevention coverage (#3475) 2026-06-15 16:32:41 +08:00
Henry Guo dd6b4c35ad fix(scanner): harden lifecycle and tiering backlog (#3469)
* fix(scanner): expose lifecycle transition backlog

* fix(scanner): expose lifecycle expiry backlog

* fix(scanner): preserve lifecycle backlog states

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-15 15:01:21 +08:00
安正超 57260c8314 refactor: centralize startup command bootstrap (#3471) 2026-06-15 14:55:33 +08:00
Henry Guo 941b8afee8 feat(table-catalog): add snapshot maintenance foundation (#3470)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-15 14:08:28 +08:00
houseme 73a48c06e5 refactor(logging): align API response boundary events (#3466)
* refactor(logging): align api response boundary events

* refactor(logging): align heal admin boundary events

* refactor(logging): restore admin callsite line numbers

* refactor(logging): simplify audit target error logging

* refactor(logging): restore rpc callsite line numbers
2026-06-15 14:07:54 +08:00
安正超 218f473fb5 refactor: centralize startup lifecycle runtime (#3468) 2026-06-15 14:07:24 +08:00
安正超 e26eea7f36 refactor: centralize startup runtime services (#3467) 2026-06-15 13:24:17 +08:00
安正超 3e723a2476 refactor: centralize startup storage runtime (#3465) 2026-06-15 12:12:27 +08:00
Henry Guo 46fb4bdc2f feat(scanner): add source-aware maintenance controls (#3461)
* feat(scanner): add source-aware maintenance controls

* fix(scanner): preserve source control between cycles

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-15 12:12:05 +08:00
Henry Guo f68967de7a feat(table-catalog): add maintenance reachability reports (#3462)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-15 12:11:53 +08:00
dependabot[bot] fbf1f9e5b8 build(deps): bump brotli from 8.0.3 to 8.0.4 in the dependencies group (#3458)
* build(deps): bump brotli from 8.0.3 to 8.0.4 in the dependencies group

Bumps the dependencies group with 1 update: [brotli](https://github.com/dropbox/rust-brotli).


Updates `brotli` from 8.0.3 to 8.0.4
- [Release notes](https://github.com/dropbox/rust-brotli/releases)
- [Commits](https://github.com/dropbox/rust-brotli/compare/8.0.3...8.0.4)

---
updated-dependencies:
- dependency-name: brotli
  dependency-version: 8.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump s3s from cf4c3346 to c59b62f7ba in the dependencies group

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-15 12:06:01 +08:00
abdullahnah92 a19560da41 fix(bucket-repl): persist MRF retry queue to disk and reload on startup (#3456)
* fix(bucket-repl): persist MRF retry queue to disk and reload on startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(bucket-repl): address three blocking MRF issues from review

1. MRF replay loses delete operations — add `MrfOpKind` discriminator to
   `MrfReplicateEntry` (Object | Delete, default=Object for backward
   compat).  `DeletedObjectReplicationInfo::to_mrf_entry` now persists
   `op=Delete`, `version_id`, `delete_marker_version_id`, and
   `delete_marker`.  `start_mrf_processor` branches on `op`: delete
   entries skip `get_object_info` and replay via
   `schedule_replication_delete` with `ReplicationType::Heal`; object
   entries follow the existing heal path.

2. `flush_mrf_to_disk` cleared the in-memory batch even on encode/write
   failure — changed return type to `bool` and callers now only
   `pending.clear()` on `true`, so a transient storage error retries
   on the next tick instead of silently dropping the batch.

3. Add focused tests: encode/decode roundtrips for object, delete-marker,
   versioned-delete, and mixed-batch entries; a routing test confirming
   op-kind propagates correctly and that the default is Object for
   legacy files; a legacy-compat test verifying old entries round-trip
   cleanly through the new format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style: fix clippy redundant-clone in MRF tests

Replace &[entry.clone()] with std::slice::from_ref(&entry) in two
encode_mrf_file call sites flagged by clippy's redundant_clone lint
under --all-targets --features rio-v2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(bucket-repl): strengthen legacy MRF compat test with hand-built msgpack

The previous mrf_legacy_file_without_op_field_decoded_as_object test
round-tripped through encode_mrf_file, so it exercised the new format
and never touched a truly-legacy payload.

Replace it with a hand-built msgpack payload that genuinely omits the
"op", "deleteMarker", and "deleteMarkerVersionID" keys — exactly what
the old binary would have written before MrfOpKind existed.  The test
now fails if #[serde(default)] is removed from the op field, which
proves real backward compatibility rather than round-trip stability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-15 11:56:50 +08:00
安正超 c99d6086cd refactor: centralize startup storage bootstrap (#3464) 2026-06-15 11:23:16 +08:00
安正超 6508f88d3a refactor: centralize startup listen bootstrap (#3460) 2026-06-15 09:41:11 +08:00
abdullahnah92 c2e0792f6f fix(scanner): skip startup delay when replication is active so failed… (#3455)
fix(scanner): skip startup delay when replication is active so failed objects heal promptly after restart

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-15 09:02:21 +08:00
安正超 513f06f268 refactor: centralize startup server preflight (#3459) 2026-06-15 08:32:44 +08:00
安正超 2288b1bb63 refactor: centralize startup runtime bootstrap (#3457) 2026-06-15 07:57:36 +08:00
Henry Guo 73e534682e feat(table-catalog): add catalog recovery diagnostics (#3444)
* feat(table-catalog): add commit recovery observability

* feat(table-catalog): harden catalog recovery diagnostics

* test(table-catalog): cover recovery admin route policy

* test(table-catalog): update recovery route count

* fix(table-catalog): satisfy duration clippy lint

* fix(obs): promote span request id field

* Update local.rs

Signed-off-by: houseme <housemecn@gmail.com>

---------

Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-15 07:06:29 +08:00
Henry Guo 2c5615d2ea feat(scanner): expose distributed metrics (#3452)
* feat(scanner): expose distributed metrics

* docs(scanner): clarify distributed metrics collection

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-15 07:05:43 +08:00
Ramakrishna Chilaka e4c1ee9941 fix(server): actionable 501 for virtual-hosted-style S3 requests (#3442) 2026-06-15 06:49:31 +08:00
安正超 f8e300bf3b refactor: centralize startup protocol bootstrap (#3450) 2026-06-15 06:48:55 +08:00
houseme 036741cb1c refactor(request-id): align contracts and lock field names (#3454)
* refactor(request-id): align header log and trace contracts

* test(contract): lock external request-id field names

* chore(deps): drop unused rustfs-ecstore links

Co-Authored-By: heihutu <heihutu@gmail.com>
2026-06-15 01:59:11 +08:00
houseme efaf07d323 feat: preserve request ids across async recovery logs (#3451)
* feat(obs): promote request ids in structured logs

* refactor(tracing): propagate spans into request tasks

* test(ecstore): baseline recovery monitor log chains

* fix(replication): reduce startup resync log noise

* chore(docs): stop tracking local recovery baseline

* chore(obs): polish request id logging cleanup
2026-06-14 23:16:04 +08:00
安正超 9499391370 refactor: centralize startup service bootstrap (#3448) 2026-06-14 21:59:17 +08:00
安正超 b2e6cc520b docs: update security advisory lessons (#3447) 2026-06-14 21:51:54 +08:00
安正超 b49057c8e3 refactor: centralize startup readiness bootstrap (#3446) 2026-06-14 21:24:07 +08:00
安正超 8d23ce06c6 refactor: consolidate app object store fallback (#3445) 2026-06-14 20:43:42 +08:00
houseme fc894c9b50 fix(obs): harden startup and shutdown logging (#3443) 2026-06-14 20:16:00 +08:00
安正超 323302255c refactor: route ecstore internals through object resolver (#3441) 2026-06-14 19:55:21 +08:00
cxymds bf3a3a6189 fix(rebalance): ignore source cleanup failures (#3440) 2026-06-14 19:50:26 +08:00
cxymds 8f6b1d47b5 chore(obs): standardize runtime logging batch one (#3438) 2026-06-14 18:55:14 +08:00
安正超 bed4c7288b refactor: route server storage lookups through resolver (#3439) 2026-06-14 18:42:21 +08:00
Henry Guo 9372ee7032 feat(table-catalog): bridge table data-plane policy (#3436)
* feat(table-catalog): bridge table data-plane policy

* test(table-catalog): harden vended credential smoke

* test(table-catalog): cover data-plane policy denials

* fix(table-catalog): protect relocated warehouse scope

* fix(table-catalog): skip invalid warehouse entries

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-14 18:13:02 +08:00
GatewayJ e29c136ad5 fix(table-catalog): stabilize standard commit metadata names (#3435) 2026-06-14 18:03:42 +08:00
安正超 84f7027e90 refactor: add object store resolver for standalone crates (#3437) 2026-06-14 18:02:39 +08:00
安正超 59f99a30e2 refactor: prefer app context object store in admin zip (#3434) 2026-06-14 16:37:08 +08:00
GatewayJ 3928117c8f fix(policy): preserve IAM policy readback shape (#3431) 2026-06-14 16:36:28 +08:00
Henry Guo fc17e75fb2 test(table-catalog): verify vended credential data-plane scope (#3429) 2026-06-14 16:35:54 +08:00
安正超 5e68cf8a29 refactor: prefer app context object store in ecfs (#3433) 2026-06-14 15:52:26 +08:00
cxymds 046d5386ba feat: stream object zip downloads (#3380)
* feat: stream object zip downloads

* fix: stream zip downloads page by page

* fix: prepare zip downloads before streaming

---------

Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-14 14:26:44 +08:00
安正超 bdeee173c8 refactor: prefer app context object store in admin (#3432)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-14 13:49:03 +08:00
houseme e8012bd1ba refactor(logging): normalize admin telemetry and error messages (#3430) 2026-06-14 13:27:10 +08:00
Henry Guo dc82efbab4 test(scanner): add validation harness (#3428)
* test(scanner): add validation harness

* fix(scanner): harden validation harness

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-14 12:40:05 +08:00
houseme 61a5500232 chore(deps): update flake.lock (#3423)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/cbb5cf3' (2026-06-06)
  → 'github:NixOS/nixpkgs/49a4bd0' (2026-06-12)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/27b7e78' (2026-06-06)
  → 'github:oxalica/rust-overlay/5b929d8' (2026-06-13)
2026-06-14 11:46:45 +08:00
安正超 6fcd62ba56 refactor: prefer app context object store in usecases (#3425) 2026-06-14 09:38:12 +08:00
安正超 bed875d3e0 fix(logging): remove guardrail script trailing whitespace (#3424) 2026-06-14 08:35:59 +08:00
houseme efa89a98ed refactor(logging): standardize protocol and observability events (#3419)
* refactor(logging): standardize object capacity events

* refactor(logging): standardize protocol server events

* refactor(logging): standardize swift protocol events

* refactor(logging): standardize observability events

* refactor(logging): move masking helper and extend guardrails
2026-06-14 07:14:45 +08:00
abdullahnah92 22460243bf fix(bucket-repl): honor op_type in replicate_object so ExistingObject… (#3420)
fix(bucket-repl): honor op_type in replicate_object so ExistingObject resync respects DISABLED targets

replicate_object was calling filter_target_arns with hard-coded
op_type: Object and existing_object: false regardless of what was
stored in roi.op_type. This meant that a resync worker setting
roi.op_type = ExistingObject (resync_bucket, line 889) had no effect
on target filtering: all configured targets were included, even ones
whose rule had ExistingObjectReplicationStatus::DISABLED.

Fix: pass op_type: roi.op_type and derive existing_object from it
(true only for ExistingObject, not Heal — Heal intentionally bypasses
the existing-object opt-out to repair past failures).

Also add warn! logs at all four MRF channel-overflow sites that were
previously silently returning Missed with no observability.

Verified with a live two-instance test: after resync, objects reached
the ENABLED target and were correctly blocked from the DISABLED target.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-14 07:14:11 +08:00
cxymds 339a192a09 fix(ecstore): preserve bucket visibility during rebalance (#3418)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-14 03:37:54 +08:00
Henry Guo 357287d626 feat(table-catalog): issue scoped table credentials (#3413)
* feat(table-catalog): issue scoped table credentials

* fix(table-catalog): harden credential vending defaults

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-14 02:20:28 +08:00
houseme 7da10db852 refactor(logging): standardize heal and scanner events (#3414)
* refactor(logging): standardize heal and scanner events

* chore(git): untrack local logging governance note

* chore(git): ignore local logging governance note
2026-06-14 01:47:39 +08:00
houseme 9059a9c68d refactor(logging): standardize concurrency and trusted proxy events (#3417)
* refactor(logging): standardize concurrency and proxy events

* chore(logging): extend guardrails for concurrency and proxies

* feat(skill): add rustfs logging governance skill
2026-06-14 01:00:26 +08:00
安正超 88d2c6ad5c test: cover app context resolver compatibility (#3416) 2026-06-14 00:24:08 +08:00
abdullahnah92 cb37a64a81 fix(site-replication): bidirectional sync, pre-existing data back-fill, and related fixes (#3401)
* fix(site-repl): clamp replication_cfg_mismatch to owning deployments and add resync status branch

Fix 3: replication_cfg_mismatch was set on ALL deployments for a bucket when
any replication-config discrepancy existed, even on deployments that simply have
no config. mc computes "in sync" as max_buckets minus per-deployment mismatch
entries; with N deployments all flagged for 1 bucket, the result underflows to
1-N = -1. Now only deployments that own a replication config are flagged.

Fix 4: SiteReplicationResyncOpHandler accepted "start" and "cancel" but returned
an empty body (and later a parse error on the client) for any other operation
string. Add a SITE_REPL_RESYNC_STATUS="status" arm that returns the stored
SRResyncOpStatus or an explicit {"status":"not-found"} object so mc never sees
an unexpected end of JSON input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(site-repl): derive sync_state from reachability and replication rule completeness

PeerInfo.sync_state was set to SyncStatus::Unknown at every construction site
and never updated from real signals. build_status_info now tracks which peers
were reachable during the metainfo fetch phase and, after all bucket stats are
merged, derives sync_state as:
  - Enable  : reachable AND no replication_cfg_mismatch for any bucket
  - Disable : reachable BUT at least one bucket has incomplete/missing rules
  - Unknown : unreachable (fetch failed)

The "Sync" column in mc admin replicate status will now reflect actual state
rather than always showing Unknown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(site-repl): add SRRotateServiceAccountHandler for split-brain svc-acct repair

When site-replicator-0 gets desynced (different secret on different peers), calls
via the old service account return 403, blocking remove and replicate operations
with no in-band recovery path.

SiteReplicationRemoveHandler already purges local state unconditionally before
sending peer notifications, so force-remove works locally even when peers reject
the 403. The new POST /v3/site-replication/rotate-svc-acct endpoint provides the
missing recovery path: it generates a fresh service-account secret, applies it
locally, and pushes a peer/join to every member. Each peer's SRPeerJoinHandler
accepts the join idempotently (update if exists, create otherwise), repairing the
desynced credential without a full teardown + restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(site-repl): back-fill pre-existing buckets and objects on replicate add

SiteReplicationAddHandler and SRPeerJoinHandler both called persist_site_replication_state
and returned success without doing anything for buckets that existed before the
sites were linked. Only buckets created after the link (via site_replication_make_bucket_hook)
ever replicated.

Introduce backfill_existing_buckets_after_add which, after persisting the new
state, iterates every local bucket and:
  1. Ensures versioning is enabled (required by replication).
  2. Reconciles bucket targets so a target entry exists for each remote peer.
  3. Reconciles the replication config so a rule pointing to each peer is present.
  4. Broadcasts a make-bucket-hook to peers (idempotent) so they create the bucket.
  5. Kicks start_site_bucket_resync toward every remote peer so pre-existing
     objects travel across.

Errors per bucket are logged but never abort the overall add — manual resync
remains available as a fallback. Both the initiator and the receiving (join) side
run the backfill so convergence happens regardless of which side held data first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(site-repl): reconcile replication config to restore bidirectional replication

Root cause: ensure_site_replication_bucket_replication_config bailed with Ok(())
the moment any replication config was found on the bucket. When bucket B was
propagated to site-2 via the make-with-versioning bucket-op, site-2's
configure-replication step loaded the freshly-written config and immediately
returned, never adding the reverse-direction rule pointing back to site-1. Result:
objects uploaded to site-2 failed with "replication head_object fallback failed
... service error" because no rule targeted the originating site.

Fix: drop the early-return. Instead load the existing rules, build the full
desired config via build_site_replication_config, and MERGE — adding only the
rules that are absent (identified by their "site-repl-<deployment_id>" id). Re-
number priorities after each merge to avoid conflicts. Existing non-site-repl
rules are preserved. The write is skipped entirely when all desired rules are
already present, so repeat calls remain cheap.

Together with Fix 1 (backfill), any file written to ANY member now replicates to
ALL members. The offline-and-recover case also benefits: when a peer returns,
the per-bucket rules are complete and the resyncer can catch up.

Also register the new rotate-svc-acct route in route_policy and
route_registration_test so the route inventory assertions stay green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(replication): remove broken resync worker-signal gate

The resync_bucket task opened a new broadcast receiver via
worker_rx.resubscribe(), which positions the receiver at the current
write-head of the ring buffer — past all 10 bootstrap signals written
in ReplicationResyncer::new().  Every spawned resync task therefore
blocked on recv() forever, making `mc admin replicate resync start`
report "started" while no objects ever moved.

Remove the dead wait entirely.  Each resync_bucket call is already
spawned on-demand (tokio::spawn in start_bucket_resync / load_resync),
so no additional gate is needed.  The per-object concurrency limit is
already enforced by the inner mpsc worker channels (line ~877).  Also
remove the now-dead worker_tx/worker_rx fields, bootstrap loop, and
signal-send in resync_bucket_mark_status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(site-repl): document rotate-svc-acct idempotency and partial-failure behavior

* style: cargo fmt and remove redundant clones

* fix(site-repl): preserve object-lock state when back-filling existing buckets

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-13 23:34:39 +08:00
Henry Guo 0d16a86d9a docs(scanner): add benchmark runbook (#3412)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-13 22:39:13 +08:00
安正超 6b0d7e06c4 refactor: split app context module (#3411)
* refactor: split app context module

* docs: update app context split verification
2026-06-13 22:36:28 +08:00
安正超 af291afb91 chore(dev): streamline local verification gates (#3410) 2026-06-13 21:51:33 +08:00
安正超 982812614f test: preserve background config reload assumptions (#3409) 2026-06-13 21:39:02 +08:00
Henry Guo 49a893f010 feat(table-catalog): add credential endpoint boundary (#3407)
* feat(table-catalog): add credential scope boundary

* feat(table-catalog): add credential endpoint boundary

* fix(table-catalog): remove redundant scope clone

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-13 21:26:59 +08:00
安正超 6b1e114b39 feat(obs): add metrics runtime controller status (#3408) 2026-06-13 20:48:34 +08:00
安正超 76b375c478 feat(runtime): add allocator reclaim controller status (#3406) 2026-06-13 19:03:49 +08:00
GatewayJ 42d9d5247d fix(policy): normalize IAM policy readback (#3402) 2026-06-13 18:47:08 +08:00
安正超 624de31143 feat(runtime): add memory observability controller pilot (#3405) 2026-06-13 17:42:06 +08:00
Maksim Vykhota e201a881ec fix(cli): remove autogenerated help Clap subcommand (#3289)
* Fix(CLI): remove autogenerated `help` Clap subcommand

* fix(cli): preserve rustfs help behavior

---------

Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-06-13 16:58:20 +08:00
GatewayJ bda0b1f3dd feat(table-catalog): add REST exists endpoints (#3395) 2026-06-13 13:54:39 +08:00
Henry Guo 74604201bb test(table-catalog): add client conformance baseline (#3400)
* test(table-catalog): add client conformance baseline

* docs(table-catalog): remove internal roadmap labels

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-13 13:54:07 +08:00
安正超 8f8064df80 feat(runtime): add memory observability status snapshot (#3397)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-13 10:02:16 +08:00
houseme 0d68851f7a refactor(logging): standardize rustfs runtime events (#3396)
* fix(rpc): adjust log levels and reduce noise in storage RPC layer

Issue #682: pool rebalance normal flow logs were using warn! instead
of info!. Additionally, several high-frequency RPC entry points (ping,
write_stream, read_at, walk_dir) were logging at info! level, causing
unnecessary log noise in production.

Changes:
- load_rebalance_meta: warn! → info! for normal flow (3 occurrences)
- ping body decode: info! → debug! (fires on every health check)
- write_stream/read_at/walk_dir entry: info! → debug!
- metrics.rs: lowercase error messages, add structured error field
- http_service.rs: add structured error field to walk_dir failure
- Add tracing::instrument fields for start_rebalance context

* refactor(logging): standardize rustfs runtime events

* build(deps): bump workspace dependency versions

* build(deps): pin time to 0.3.47

* build(deps): update postgres client versions
2026-06-13 08:39:47 +08:00
Henry Guo b38e71fac3 feat(table-catalog): retain protected ref metadata (#3394) 2026-06-12 23:55:30 +08:00
escapecode b6973636b6 docs(sftp): document server operations (#3391)
Adds an operator guide for the SFTP server: recommended
configuration, the path model, host keys on Unix and Windows, the
environment variable reference, session cleanup behaviour, IAM
policy requirements per SFTP operation, client compatibility
notes, multipart upload sizing and cleanup, and the log lines
worth alerting on.

Corrects documentation the platform change left stale. The module
overview and the UnsupportedPlatform error text still described
Windows as unsupported. A source comment referenced a document
that does not exist in the repository and now points at the new
guide. The changelog adds the two host-key reload variables
missing from its environment list, describes the banner variable
as the SSH identification string, and corrects the upload size
cap and compliance case count.
2026-06-12 22:52:00 +08:00
Henry Guo f1a35bb9e2 fix(heal): require disk RPC before remote recovery (#3392)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-12 22:45:58 +08:00
安正超 f7bf4fd3e1 feat(targets): gate external plugin flow (#3393) 2026-06-12 22:17:54 +08:00
唐小鸭 8afb963d21 feat(ecstore): optimize the triggering conditions of the compression module (#3387)
feat. Optimize the triggering conditions of the compression module

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-12 21:18:44 +08:00
安正超 5f7c991359 feat(targets): add extension hook diagnostics contracts (#3390)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-12 21:17:34 +08:00
escapecode 7a8514bdfa feat(sftp): add macOS and Windows platform support (#3372)
The session watchdog now selects its detection method per
platform. It previously probed kernel TCP state through a
Linux-only procfs path, so on macOS every healthy idle session
was killed within a minute, and on Windows the watchdog never
spawned at all, leaving wedged sessions with no cleanup. Linux
keeps its fast-kill watchdog unchanged. Other platforms get a
silence-only backstop that kills a session only at the
documented 30-minute idle ceiling.

The host-key loader now has a Windows arm. It loads OpenSSH
format host keys from the configured directory and logs a
one-time warning to restrict NTFS ACLs on the key directory,
the same operator-managed approach FTPS, WebDAV, KMS, and IAM
already use on Windows. Startup previously aborted with
UnsupportedPlatform because the Unix mode-bit permission check
has no Windows equivalent. tokio's io-uring feature is now
enabled only in Linux builds. io-uring is a Linux kernel
interface and enabling it unconditionally broke the Windows
build.

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-12 18:08:23 +08:00
安正超 e80b72ae79 feat(targets): gate sidecar runtime policy (#3388)
* feat(targets): gate sidecar runtime policy

* fix(admin): add extension route policy specs
2026-06-12 17:42:12 +08:00
安正超 434b71e569 feat(extension): add admin views (#3386) 2026-06-12 15:54:42 +08:00
安正超 1e7c376586 feat(targets): add extension schema adapter (#3385) 2026-06-12 15:27:55 +08:00
安正超 7d6d56a547 feat(extension): add schema contracts (#3384) 2026-06-12 15:07:17 +08:00
安正超 249a0b56ea feat(targets): require plugin artifact attestations (#3383) 2026-06-12 14:13:41 +08:00
安正超 8df38ea12c ci(docker): add release image scan report (#3382) 2026-06-12 13:11:59 +08:00
安正超 212a0913be ci(release): emit sbom and provenance assets (#3381) 2026-06-12 12:56:02 +08:00
cxymds 4f954397d7 fix: support regular users in access key info (#3285)
* fix: support regular users in access key info

* refactor: extract access key identity helpers

* refactor: add typed access key identity model

* test: cover info access key identity contract

* test: cover derived access key response contracts

* fix: remove needless borrow in access key test

* fix: block fallback on explicit admin deny

* fix: restrict service account access key fallback

* fix: correct info access key identity handling
2026-06-12 12:02:43 +08:00
安正超 99e68f82a2 ci(audit): report unpinned workflow actions (#3379) 2026-06-12 11:33:23 +08:00
安正超 fc32b76c0e fix: avoid mutating signed request headers (#3378) 2026-06-12 11:13:34 +08:00
安正超 559bf9d9d7 ci(audit): wire cargo deny supply-chain gate (#3377) 2026-06-12 11:11:40 +08:00
安正超 08c586d843 security(admin): reject unknown JSON ingress fields (#3376) 2026-06-12 10:21:50 +08:00
wood 9904a4f9eb fix(obs): fix grafana layout and wrong version of tempo (#3368)
* fix(obs): fix grafana dashboard layout errors

Signed-off-by: w0od <dingboning02@163.com>

* fix(docker): align the correct tempo version

Signed-off-by: w0od <dingboning02@163.com>

---------

Signed-off-by: w0od <dingboning02@163.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-12 09:27:32 +08:00
Henry Guo 19ff378e9b fix(scanner): preserve newer usage snapshots (#3370)
* fix(scanner): preserve newer usage snapshots

* fix(table-catalog): require namespace locking backend

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-12 07:06:08 +08:00
Henry Guo 51ef87ed19 fix(heal): recover renewed disk health checks (#3366)
* fix(heal): recover renewed disk health checks

* test(heal): cover replaced remote disk rebuild

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-12 07:05:52 +08:00
安正超 c3055f9335 security(kms): require explicit dev defaults opt-in (#3369)
* security(kms): require explicit dev defaults opt-in

* test(kms): satisfy clippy dev defaults checks
2026-06-12 07:05:20 +08:00
安正超 a85cc0354c refactor(storage-api): remove namespace lock from StorageAPI (#3365)
* refactor(storage-api): remove namespace lock from StorageAPI

* test(scanner): wait for runtime budget cancellation

---------

Co-authored-by: loverustfs <hello@rustfs.com>
2026-06-11 22:42:12 +08:00
houseme 82af181dcf refactor(logging): unify governance runtime events (#3367) 2026-06-11 22:26:02 +08:00
GatewayJ b5676dcc8e fix(table-catalog): support PyIceberg REST commits (#3342)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-11 22:22:13 +08:00
安正超 7146c893cb refactor(storage-api): remove legacy bucket DTO re-export (#3364) 2026-06-11 21:24:56 +08:00
houseme 0a987d870b refactor(logging): reduce runtime noise (#3363) 2026-06-11 19:49:01 +08:00
安正超 a0b6636b61 refactor(config): remove legacy accessor re-export (#3362) 2026-06-11 19:32:57 +08:00
Henry Guo 2e3c777307 fix(heal): skip transient usage cache heal errors (#3359) 2026-06-11 19:16:32 +08:00
安正超 b4524033e3 refactor(config): move global config accessors (#3360) 2026-06-11 19:16:02 +08:00
安正超 ed3851782c refactor(config): remove legacy model re-export (#3357) 2026-06-11 18:09:01 +08:00
houseme f6137f9f77 refactor(auth): tighten startup log redaction (#3358) 2026-06-11 18:08:41 +08:00
安正超 69549634ea refactor(config): migrate scanner config consumer (#3356) 2026-06-11 17:30:26 +08:00
houseme 704c95a22d refactor(server): consolidate request transport logs (#3354) 2026-06-11 17:07:02 +08:00
安正超 ca58d7f0ec refactor(config): migrate server config consumers (#3353) 2026-06-11 17:04:50 +08:00
安正超 2205991180 refactor(config): extract server config model (#3351) 2026-06-11 16:11:17 +08:00
houseme 7d38b0cf90 refactor(obs): unify local and otlp log output (#3352)
wip(obs): start lg-002 output unification
2026-06-11 16:10:54 +08:00
houseme 8d337c4e87 feat(obs): add logging redaction guard rails (#3349)
* feat(obs): add logging redaction guard rails

* chore(docs): keep logging plans out of lg-001 pr

* test(obs): guard against unmasked access key logs

* test(obs): enforce masked access key log patterns
2026-06-11 15:24:32 +08:00
安正超 dd50359161 refactor(storage): narrow table catalog bounds (#3350) 2026-06-11 14:32:28 +08:00
安正超 2ead90d31b fix(security): add authorization check to ListRemoteTargetHandler (GHSA-796f-j7xp-hwf4) (#3346)
fix(security): add authorization check to ListRemoteTargetHandler

ListRemoteTargetHandler only verified that request credentials existed
but did not check whether the caller has replication or administrator
permissions. This allowed any authenticated user to list remote
replication target configuration, potentially leaking remote target
credentials (accessKey, secretKey, sessionToken).

Add validate_replication_admin_request() call with
GetBucketTargetAction, consistent with the other replication admin
handlers that already perform this check.

Fixes: GHSA-796f-j7xp-hwf4

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-11 05:50:14 +00:00
安正超 d60deba9b6 fix(security): add IAM authorization to FTP read/metadata/cwd handlers (GHSA-3g29-xff2-92vp) (#3347)
fix(security): add IAM authorization to FTP read/metadata/cwd handlers

The FTP frontend's get() (RETR), metadata() (SIZE/MDTM), and cwd()
(CWD) handlers dispatched directly to the storage backend without
calling authorize_operation(). This allowed any authenticated FTP user,
including those with explicit Deny policies, to read arbitrary objects
and probe bucket existence regardless of IAM policy.

Add authorize_operation() calls matching the pattern used by the
write-path handlers (put, del, list, rmd) and the WebDAV driver:
- get(): S3Action::GetObject
- metadata() for files: S3Action::HeadObject
- metadata() for directories: S3Action::HeadBucket
- cwd(): S3Action::HeadBucket

Fixes: GHSA-3g29-xff2-92vp

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-11 05:48:53 +00:00
安正超 d4ee44f49f refactor(scanner): narrow cache storage bounds (#3348) 2026-06-11 13:52:35 +08:00
安正超 2f7cc7cb9e refactor(storage): narrow resync metadata bounds (#3345) 2026-06-11 04:41:15 +00:00
houseme ee96e194a3 build(deps): bump s3s for header parsing fix (#3344) 2026-06-11 04:28:36 +00:00
houseme a49c6b4c2e fix(logging): clarify recovery and metacache warn messages (#3341)
* fix(logging): clarify recovery and metacache warn messages

Clarify cached gRPC connection eviction messages so they describe automatic reconnection instead of implying a persistent cluster fault.

Retitle metacache resolution logs to remove the misleading decommission_pool prefix and demote routine reconciliation traces to debug while preserving actionable warning paths.

* fix(logging): avoid overpromising reconnect success
2026-06-11 04:19:22 +00:00
Henry Guo 7191a3abae docs(scanner): document runtime scanner controls (#3339)
* docs(scanner): document runtime scanner controls

* docs(scanner): split English and Chinese README

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: majinghe <42570491+majinghe@users.noreply.github.com>
2026-06-11 12:04:34 +08:00
安正超 686068d8bd refactor(storage): narrow metadata bounds (#3343) 2026-06-11 12:04:02 +08:00
安正超 6b86e3875c refactor(storage): remove old admin surfaces (#3340) 2026-06-11 11:28:09 +08:00
安正超 87968275a7 refactor(storage): route maintenance inventory reads (#3337) 2026-06-11 10:32:48 +08:00
安正超 f325b9f714 refactor(ecstore): route admin read internals (#3336) 2026-06-11 08:43:42 +08:00
安正超 94c53af264 refactor(storage): use admin API for observability reads (#3335) 2026-06-11 08:13:15 +08:00
安正超 8ae0cad667 refactor(admin): use storage admin reads (#3334) 2026-06-11 07:48:38 +08:00
安正超 b48d7b1fa5 refactor(admin): use storage admin info (#3333) 2026-06-11 07:12:35 +08:00
安正超 8a90bda16a refactor(admin): use storage admin drive counts (#3332) 2026-06-11 06:36:18 +08:00
安正超 4b1714438e feat(storage-api): bind ecstore admin contract (#3331) 2026-06-11 05:49:14 +08:00
Henry Guo 474fab5097 fix(table-catalog): validate committed metadata identity (#3327)
* fix(table-catalog): validate committed metadata identity

* fix(table-catalog): preserve legacy metadata identity commits

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-10 16:49:06 +00:00
Henry Guo 91f80a5585 feat(scanner): expose lifecycle transition status (#3326)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-10 16:45:02 +00:00
安正超 d9ddd1bedc feat(storage-api): add disk inventory contract (#3330) 2026-06-10 16:33:31 +00:00
houseme b1339a98ed fix(scanner): delay deep verify for fresh objects (#3329)
Gate scanner-triggered deep heal behind a short new-object cooldown and retry transient bitrot shard size mismatches during local verification.

Add targeted tests for cooldown mode selection and size-mismatch classification, and log when deep scans are downgraded or size-mismatch retries occur.
2026-06-10 15:50:44 +00:00
houseme 3fed21c68a fix(startup): add disk init diagnostics (#3324)
* ci(build): remove macOS x86_64 target

* ci(build): reduce workflow timeout

* fix(startup): add disk init diagnostics
2026-06-10 14:24:12 +00:00
Henry Guo 64c0ede026 feat(table-catalog): add product API surface (#3320)
* feat(table-catalog): add product API surface

* fix(table-catalog): harden product API commits

* fix(table-catalog): close product API review gaps

* fix(table-catalog): validate rollback metadata before commit

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-10 07:58:22 +00:00
Henry Guo 66fd55a8e0 feat(scanner): expose pacing pressure status (#3319)
* feat(scanner): expose pacing pressure status

* fix(scanner): preserve merged pause pressure

* fix(scanner): default missing primary pressure

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-10 07:33:41 +00:00
安正超 bb5d9565a6 feat(storage-api): add bucket DTO contract (#3314)
* feat(storage-api): add bucket DTO contract

* ci(build): increase workflow timeout to 90 minutes

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-10 07:16:14 +00:00
houseme b9c924a6ed build(ci): update macOS runner and bump regex/uuid (#3318)
* ci(build): use macos-26-intel for x86_64 job

* build(deps): bump regex and uuid

* ci(build): disable cross for macos x86_64 job

---------

Co-authored-by: majinghe <42570491+majinghe@users.noreply.github.com>
2026-06-10 04:42:10 +00:00
houseme 68400933b5 chore(release): prepare 1.0.0-beta.8 (#3317)
* chore(release): prepare 1.0.0-beta.8

* chore(release): align release assets for 1.0.0-beta.8
2026-06-10 04:34:39 +00:00
安正超 0dc99a1abb docs: inventory KMS development defaults (#3304) 2026-06-10 12:46:08 +08:00
Henry Guo d7a7d1fc1e feat(table-catalog): add REST catalog alias (#3316)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-10 03:55:58 +00:00
Henry Guo 62e9c5b94f feat(scanner): add adaptive pacing controls (#3315)
* feat(scanner): add adaptive pacing controls

* fix(scanner): bound pacing delay status precision

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-10 03:46:02 +00:00
安正超 f80162b5c9 docs: decide config model boundary (#3307) 2026-06-10 10:02:53 +08:00
Henry Guo 2eafd9c602 feat(table-catalog): add credential response boundary (#3305)
* feat(table-catalog): add credential response boundary

* fix(table-catalog): enforce scoped catalog resources

* fix(table-catalog): reduce admin auth scope arguments

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-10 01:15:51 +00:00
安正超 a73c90c811 security: redact IAM and target debug secrets (#3306) 2026-06-10 09:03:50 +08:00
安正超 3795d44b86 feat(storage-api): add error code contract (#3313) 2026-06-10 09:03:29 +08:00
安正超 5fef105484 docs: define background controller contract (#3312) 2026-06-10 07:30:21 +08:00
安正超 bc95b9fb9e test: guard table catalog ingress unknown fields (#3308) 2026-06-09 20:06:36 +00:00
安正超 45391ac776 feat(storage-api): add contract crate (#3310) 2026-06-09 20:06:15 +00:00
安正超 f9a5e6d7e6 docs: inventory background services (#3311) 2026-06-10 04:05:33 +08:00
安正超 03eb10b07f feat: add KMS config redaction safeguards (#3303) 2026-06-09 14:08:58 +00:00
Henry Guo 7ec16e197c feat(table-catalog): add metadata maintenance control plane (#3302)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-09 22:08:28 +08:00
Julien Pervillé 992de65c58 feat(helm): add priorityClassName attribute (#3301)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-09 22:07:41 +08:00
Maksim Vykhota c4d0d395af Refactor(CLI): avoid unnecessary unreachable! macro in CLI parsing (#3288)
* Refactor(CLI): avoid unnecessary `unreachable!` macro in CLI parsing

* Fix: clippy lint

---------

Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-09 22:01:51 +08:00
Henry Guo e79c793803 feat(heal): add scanner-aware bitrot controls (#3297)
* feat(heal): add scanner-aware bitrot controls

* fix(config): register heal defaults

* test(admin): avoid heal config queue conflict

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-09 13:43:18 +00:00
Henry Guo 0cdcd1eb7b feat(table-catalog): internalize catalog backing paths (#3295)
* feat(table-catalog): internalize catalog backing paths

* fix(table-catalog): clean internal catalog on bucket delete

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-09 12:01:25 +00:00
cxymds 55590e38fb fix(config): accept Kafka SASL keys in legacy admin (#3300) 2026-06-09 12:01:15 +00:00
安正超 f5bb034ec8 feat(kms): migrate KMS handlers to dedicated actions (#3298)
feat: migrate KMS handlers to dedicated actions
2026-06-09 12:01:02 +00:00
安正超 4fec606dc4 feat: add KMS action taxonomy (#3294) 2026-06-09 05:06:15 +00:00
Henry Guo ad9bf41fc8 feat(heal): expose scanner heal admission outcomes (#3292)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-09 03:39:57 +00:00
Henry Guo 51c26278a4 feat(table-catalog): support standard REST create commit (#3287)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-09 02:18:33 +00:00
安正超 b9a3a2245a feat: add admin route policy inventory (#3286)
* feat: add admin route policy inventory

* fix: align table route policy actions

* fix: align load table route policy action
2026-06-09 02:17:50 +00:00
houseme aabda41ae5 build: upgrade Rust baseline to 1.96.0 (#3291)
* build: upgrade Rust baseline to 1.96.0

* ci: pin Rust workflows to 1.96.0

* remove

* ci: pin setup action to Rust 1.96.0

* build: pin toolchain to Rust 1.96.0
2026-06-08 20:32:37 +00:00
houseme 9354b939ca refactor(zip): simplify archive extraction path (#3290)
* refactor(zip): simplify archive extraction path

* build(deps): align datafusion and http updates

* fix(test): restore pre-commit compatibility
2026-06-08 20:03:02 +00:00
Henry Guo 6413df4b7f feat(replication): expose scanner repair outcomes (#3278)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-08 14:24:27 +00:00
cxymds 452c003341 fix(scanner): ignore missing rebalance metadata (#3282)
* fix(ecstore): harden rebalance data movement

* fix(ecstore): preserve failed rebalance status

* fix(ecstore): avoid rebalance walkdir total timeout

* fix(ecstore): retry rebalance listing timeouts

* fix(ecstore): restrict data movement resume target

* refactor(ecstore): simplify multipart movement target

* fix(ecstore): restore resume target checks

* perf(ecstore): speed up rebalance bucket merges

* fix: keep rebalance listing alive on transient failures

* fix(scanner): ignore missing rebalance metadata

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-08 14:24:16 +00:00
Henry Guo 8c3e52efb8 feat(table-catalog): refine table catalog permissions (#3283)
* feat(table-catalog): refine table catalog permissions

* fix(policy): scope table admin resources

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-08 14:23:52 +00:00
唐小鸭 f7724d223b feat(rio): rio_v2 is compatible with minio for storing data. (#3115)
* Set up a compatibility layer for replacing old Rio components with new ones.

* fix(rio). compress range

* feat(rio). Add the experimental feature rio_v2 to support minio data at the binary level.

* feat(rio_v2): add sse-c test

* test compression component

* simple fix

* fix minlz encode

* fix metadata

* fix kms key cache error

* Update launch.json

* ci: set nix crate download user agent

* fix: gate obs pyroscope backend

* ignore minio test

* fix encrypt check

* fix

* fix

* fix

* Update object_usecase.rs

* Update ci.yml

* fix

* ci add rio-v2 test

* fix

* ci fix

* fix

* Reconstructed into a more reasonable compatibility mode

* fix

* fix

---------

Signed-off-by: houseme <housemecn@gmail.com>
Signed-off-by: 唐小鸭 <tangtang1251@qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-06-08 11:59:14 +00:00
cxymds 9504dff595 fix(ecstore): harden rebalance data movement (#3234)
* fix(ecstore): harden rebalance data movement

* fix(ecstore): preserve failed rebalance status

* fix(ecstore): avoid rebalance walkdir total timeout

* fix(ecstore): retry rebalance listing timeouts

* fix(ecstore): restrict data movement resume target

* refactor(ecstore): simplify multipart movement target

* fix(ecstore): restore resume target checks

* perf(ecstore): speed up rebalance bucket merges

* fix: keep rebalance listing alive on transient failures

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-08 10:40:26 +00:00
Michael 2fc1cf90e0 feat(helm): add option to disable log PVCs and mounts (#3189)
Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-08 10:35:12 +00:00
houseme ed73952cb6 perf(ecstore): improve erasure write diagnostics and single-block performance (#3280)
* docs(object-capacity): add localized crate docs

* fix(ecstore): improve quorum and transport diagnostics

* perf(ecstore): add safe single-block write fast path

* refactor(ecstore): collapse layered small write paths

* chore(docs): keep issue 662 design note tracked

* fix(docs): restore issue 662 design note

* chore(docs): keep issue 662 design local only

* feat(obs): add internode reliability metrics and dashboard

* feat(obs): extend internode diagnostics and service logging

* fix(docs): use AGENTS guide filename

* perf(ecstore): reuse owned buffer in small encode

* fix(ecstore): tighten small write diagnostics

---------

Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-08 09:45:56 +00:00
cxymds ddd35badad fix(ecstore): avoid offline disks on admin timeout (#3263)
* fix(ecstore): avoid offline disks on admin timeout

* fix(ecstore): handle poisoned admin cache locks

* fix(ecstore): recover poisoned admin cache on success

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-06-08 08:35:36 +00:00
Henry Guo f40dd2f93c fix(scanner): publish partial usage for compacted scans (#3277)
* fix(scanner): publish partial usage for compacted scans

* fix(scanner): publish first partial usage immediately

* fix(scanner): skip startup delay for cold usage cache

* fix(scanner): tighten cold usage publish gate

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-08 06:18:35 +00:00
Henry Guo 4751a9f4b9 feat(table-catalog): add recovery diagnostics (#3275)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-08 03:59:13 +00:00
安正超 3d0e6ce0da feat: add security governance policy contracts (#3271)
* feat: add security governance policy contracts

* fix: require signatures for release assets

* docs: update policy verification counts

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-08 02:42:03 +00:00
安正超 78df276d25 fix: return 503 on lock contention instead of 500 (#3274)
* fix(ecstore): skip hidden metadata in walk limit

* fix(ecstore): match walk limit to visible versions

* fix(ecstore): avoid decoding all versions for limit

* fix: return 503 on lock contention instead of 500

When concurrent PUTs to the same key contend for the namespace write
lock, lock timeout and conflict errors were wrapped as
StorageError::other(...) → StorageError::Io(...), which fell through
to S3ErrorCode::InternalError (500) in the error mapping.

Only QuorumNotReached was correctly mapped to ServiceUnavailable (503);
all other lock errors (Timeout, AlreadyLocked, etc.) became 500.

Fix: map_namespace_lock_error now returns StorageError::Lock(err) for
non-quorum lock errors, and StorageError::Lock is mapped to
S3ErrorCode::ServiceUnavailable in the HTTP error conversion.

Affected paths:
- crates/ecstore/src/set_disk/lock.rs
- crates/ecstore/src/store/object.rs
- crates/ecstore/src/store/bucket.rs (make_bucket, delete_bucket)
- rustfs/src/app/object_usecase.rs (copy_object)

Regression test: test_concurrent_put_same_key_never_returns_500

* test: fail on unexpected lock contention errors

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-08 02:20:21 +00:00
houseme c479f3d0cb fix(ecstore): gate rustix fs diagnostics on windows (#3267)
* fix(ecstore): gate rustix fs diagnostics on windows

* fix(ecstore): improve windows disk validation diagnostics

* build(deps): bump workspace dependency versions
2026-06-08 00:05:55 +00:00
Henry Guo c452dd8ad7 feat(table-catalog): add metadata maintenance cleanup (#3266)
* feat(table-catalog): add metadata maintenance dry run

* feat(table-catalog): add metadata-only maintenance delete

* fix(table-catalog): protect recent metadata cleanup candidates

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-08 00:05:41 +00:00
安正超 7a9bf707ee feat: add security governance contract types (#3270) 2026-06-07 23:14:11 +00:00
安正超 c03c0ebc36 test: add admin route matrix guard (#3268) 2026-06-08 05:30:18 +08:00
安正超 dee550a831 ci: add architecture migration rule checks (#3264)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-07 16:23:38 +00:00
Henry Guo f49df41db9 fix(lifecycle): harden scanner ILM expiry accounting (#3257)
* fix(lifecycle): harden scanner ILM expiry accounting

* fix(scanner): gate ILM action accounting on enqueue

* fix(metrics): avoid scanner source work argument list

* fix(scanner): gate local ILM accounting on enqueue

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-07 16:18:34 +00:00
houseme cebea9a238 fix(ecstore): add disk validation diagnostics (#3265)
Log local endpoint disk-topology details during physical disk independence checks.
Include canonical paths, st_dev major:minor values, and resolved device ids in validation output to make Docker and bind-mount startup failures easier to diagnose.

Also assert the new diagnostics are present in the shared-disk regression test.
2026-06-07 15:32:38 +00:00
唐小鸭 74296761fa fix(replication): repair site replication setup (#3252)
* fix: repair site replication setup

* Optimized site status collection

---------

Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-07 13:19:50 +00:00
安正超 6f4d0b54a1 fix(ci): install ripgrep for script checks (#3260) 2026-06-07 22:03:25 +08:00
安正超 4254633c9e docs: update security advisory lessons (#3262) 2026-06-07 22:02:56 +08:00
安正超 e5b2bcc088 docs: complete config helper inventory (#3261) 2026-06-07 22:02:21 +08:00
Henry Guo dd0ef1f766 feat(table-catalog): tighten REST load/register compatibility (#3245)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-07 21:54:15 +08:00
安正超 3c71d5ef1c docs: inventory ecstore config consumers (#3259) 2026-06-07 21:44:55 +08:00
安正超 241e45d3b0 docs: add admin route action snapshot (#3258) 2026-06-07 21:20:16 +08:00
安正超 0f9584c8d9 docs: add startup timeline baseline (#3256) 2026-06-07 20:18:30 +08:00
安正超 ae9d25879d ci: stabilize architecture layer guard (#3255) 2026-06-07 19:26:35 +08:00
Marcelo Bartsch f00898d070 fix(tier): recover (#3182)
* fix(tier): stop sending nil/garbage versionId to warm backend S3

Three bugs caused NoSuchVersion errors when reading tiered objects:

1. warm_backend_s3sdk: GET and DELETE ignored rv/range opts entirely —
   fixed to forward version_id and byte-range to the SDK request.

2. version.rs (MetaObject + MetaDeleteMarker): transition_version_id was
   parsed with unwrap_or_default(), turning invalid/wrong-length bytes
   into Uuid::nil(). The nil UUID was then serialized and sent as
   ?versionId=00000000-... to the tier backend -> NoSuchVersion.
   Fixed: .and_then(.ok()).filter(!is_nil()) so only valid non-nil UUIDs
   are forwarded as versionId.

3. bucket_lifecycle_ops: add debug/error logs in
   get_transitioned_object_reader to record tier, tier_object, and
   tier_version_id before and on failure of the tier GET.

Also adds tier transition fields to dump_fileinfo example for offline
xl.meta inspection, and fixes Docker build (cargo path + entrypoint).
Adds CLAUDE.md with tier architecture and debugging notes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* more fixes for versionId

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Marcelo Bartsch <marcelo@bartsch.cl>

* remove branch

* Add tests and fix cargo path, add load to build-docker

* update documentation (CLAUDE.md)

* more fixes for recover

* More fixes to ILM recover

* final fix

* chore: add missing-shard first-scene diagnostics (#3213)

chore(ecstore): add missing-shard first-scene diagnostics

Log rename_data quorum context behind RUSTFS_ISSUE3031_DIAG_ENABLE so partial-disk success can be correlated with later missing shard reads.

Also log put_object commit success and tmp cleanup boundaries to capture when successful quorum writes are followed by tmp_dir cleanup.

* fix test anmd fmt

* fix cargo path
fix test

* fix(tier): format copy_object self-copy guard

---------

Signed-off-by: Marcelo Bartsch <marcelo@bartsch.cl>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: loverustfs <hello@rustfs.com>
2026-06-07 09:38:28 +00:00
安正超 069d1e5a75 test: bypass proxy for embedded readiness probe (#3254) 2026-06-07 09:04:29 +00:00
Henry Guo 4ae070c8cc feat(scanner): expose scan partial source status (#3247)
* feat(scanner): expose scan partial source status

* fix(scanner): expose partial source in aggregated metrics

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-07 06:40:04 +00:00
安正超 f8aa4fa221 docs: add architecture migration guardrails (#3253) 2026-06-07 14:43:55 +08:00
cxymds 61f0dfbc40 fix(ecstore): invalidate wiped disk id cache (#3251) 2026-06-07 04:25:38 +00:00
cxymds 04b5e8f988 fix(replication): normalize local site endpoint port (#3249)
* fix(replication): normalize local site endpoint port

* fix(replication): respect runtime console endpoint

* fix(replication): allow valid peer endpoint ports

---------

Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-07 04:19:04 +00:00
cxymds 316540aaf8 fix(docker): align TLS compose healthchecks (#3246)
* fix(docker): align TLS compose healthchecks

* fix(docker): keep simple compose TLS opt-in
2026-06-07 01:27:03 +00:00
cxymds dd5e2c8ae9 docs(docker): clarify bind mount permission setup (#3248)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-07 09:33:24 +08:00
houseme e73b1c11c0 chore(deps): update flake.lock (#3250)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/e9a7635' (2026-05-29)
  → 'github:NixOS/nixpkgs/cbb5cf3' (2026-06-06)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/85570ef' (2026-05-30)
  → 'github:oxalica/rust-overlay/27b7e78' (2026-06-06)
2026-06-07 01:07:23 +00:00
GatewayJ 4a28e3d671 fix: clean old data dirs on object overwrite (#3244)
Co-authored-by: cxymds <Cxymds@qq.com>
2026-06-07 00:18:56 +00:00
GatewayJ 8c742ede14 fix(admin): format policy JSON and improve error messages in service … (#3242)
* fix(admin): format policy JSON and improve error messages in service account API

- Use serde_json::to_string_pretty for policy serialization to match MinIO behavior
- Replace technical error messages with user-friendly ones for policy validation
- Fixes rustfs/rustfs#3233 (policy JSON not formatted)
- Fixes rustfs/rustfs#3232 (error messages not shown to users)

* Delete scripts/tempfile.zip

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-06-06 16:39:59 +00:00
Henry Guo 7d84e89c36 feat(table-catalog): wire REST commit handler (#3239)
* feat(table-catalog): wire REST commit handler

* fix(table-catalog): hide MVP commit endpoint from config

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-06 13:38:12 +00:00
安正超 e2fe99d8bc refactor(ecstore): add server configuration accessors (#3238)
* refactor(ecstore): add server configuration accessors

Phase 4 of global singleton consolidation. Add ECStore accessor
methods for server-level configuration globals.

New methods:
- port() — delegates to global_rustfs_port()
- host() — delegates to GLOBAL_RUSTFS_HOST
- addr() — delegates to GLOBAL_RUSTFS_ADDR
- region() — delegates to get_global_region()
- endpoints() — delegates to get_global_endpoints()
- server_config() — delegates to get_global_server_config()
- storage_class() — delegates to get_global_storage_class()
- tier_config_mgr() — delegates to get_global_tier_config_mgr()
- notification_system() — delegates to get_global_notification_sys()
- bucket_metadata_sys() — delegates to get_global_bucket_metadata_sys()

1160 tests pass, clippy clean, formatting clean.

* fix(ecstore): avoid duplicate server accessors
2026-06-06 21:38:45 +08:00
Henry Guo 83a4e5712e feat(scanner): track scan cycle source work (#3240)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-06 12:53:48 +00:00
Henry Guo fade13d950 feat(table-catalog): wire REST catalog MVP handlers (#3227)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-06 10:47:49 +00:00
Henry Guo b36a730e48 feat(scanner): expose checkpoint and source work status (#3230)
* feat(scanner): expose checkpoint and source work status

* fix(scanner): count ignored checkpoints once per scan

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-06 10:47:39 +00:00
houseme 6db4c3538d fix(allocator): restore validated jemalloc target gating (#3236)
* fix(allocator): restore validated jemalloc target gating

Restrict the global allocator and jemalloc profiling paths to linux-gnu-x86_64 so Linux ARM64 builds fall back to mimalloc again.

Update the runtime profiling, allocator reclaim, and admin profiling handlers to use the same target gating and avoid exposing jemalloc-only code on unsupported targets.

Verification:
- cargo fmt --all
- cargo fmt --all --check
- cargo check -p rustfs
- make pre-commit

* fix(profiling): restore non-jemalloc platform behavior

Restore CPU profiling support on macOS while keeping jemalloc-backed memory profiling restricted to validated linux-gnu-x86_64 targets.

Also restore Unix log directory permission hardening so the allocator regression fix does not roll back unrelated observability behavior on other supported platforms.

Verification in progress:
- cargo fmt --all
- cargo check -p rustfs
- make pre-commit (running)

* fix(profiling): qualify periodic memory log macros

Use explicit tracing macro paths in periodic jemalloc memory profiling logging so builds do not fail when the local target configuration excludes those branches from the current import set.

Verification:
- cargo fmt --all
- cargo check -p rustfs

* fix(profiling): gate unsupported memory helper

Restrict the unsupported memory profiling helper to non-linux-gnu-x86_64 targets so Linux builds do not emit dead_code warnings for an unreachable fallback.

Verification:
- cargo fmt --all
- cargo check -p rustfs
2026-06-06 08:22:29 +00:00
安正超 60f14cb0f1 fix(bucket-encryption): populate default KMS key for SSE-KMS without key ID (#3225)
* fix(bucket-encryption): populate default KMS key for SSE-KMS without key ID

When PutBucketEncryption receives SSE-KMS configuration without a specific
KMS key ID, query the KMS service for the default key and populate
KMSMasterKeyID before storing. This ensures GetBucketEncryption responses
include the key ID, which S3 clients like mc rely on to distinguish SSE-KMS
from SSE-S3 in their display logic.

Fixes #3039

* test(kms): cover empty bucket encryption key ID

* fix(bucket-encryption): require default KMS key
2026-06-06 02:57:48 +00:00
安正超 06acf49672 refactor(ecstore): add accessor methods for service globals (#3226)
* refactor(ecstore): add accessor methods for service globals

Phase 3 of global singleton consolidation. Add ECStore accessor
methods that delegate to process-global service singletons,
providing a unified API through ECStore.

New methods:
- notification_system() — delegates to notification_sys global
- bucket_metadata_sys() — delegates to bucket metadata global
- endpoints() — delegates to endpoints global
- region() — delegates to region global
- tier_config_mgr() — delegates to tier config global
- server_config() — delegates to server config global
- storage_class() — delegates to storage class global

1161 tests pass, clippy clean, formatting clean.

* fix: use imported functions instead of fully-qualified paths

Address review comments: use imported functions instead of
crate:: paths to avoid unused import warnings.

1160 tests pass, clippy clean.

* fix(ecstore): remove unused service imports
2026-06-05 15:29:38 +00:00
Henry Guo 6d06b574f2 feat(table-catalog): add REST catalog route surface (#3211)
* feat(table-catalog): add REST catalog route surface

* fix(table-catalog): avoid test-only router import

* fix(table-catalog): align unsupported REST responses

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-05 13:52:13 +00:00
安正超 4d50c72e4c fix(ecstore): skip hidden metadata in walk limit (#3224)
* fix(ecstore): skip hidden metadata in walk limit

* fix(ecstore): match walk limit to visible versions

* fix(ecstore): avoid decoding all versions for limit
2026-06-05 13:50:02 +00:00
Henry Guo 0d445afda9 feat(scanner): add versioned scan checkpoints (#3220)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-05 13:49:41 +00:00
GatewayJ f1b8f8312d fix(server): normalize empty request content length (#3215)
* fix(server): normalize empty S3 request content length

* fix(server): normalize empty console content length

* fix(server): keep console constants test scoped

* test(server): align empty body route expectations
2026-06-05 02:03:07 +00:00
安正超 8185376b90 refactor(ecstore): migrate config globals into ECStore struct fields (#3219)
* refactor(ecstore): migrate config globals into ECStore struct fields

Phase 2 of global singleton consolidation. Add server_config and
storage_class fields to ECStore, sharing the same underlying data
as the process-global LazyLock statics.

New ECStore fields:
- server_config: RwLock<Option<Config>>
- storage_class: RwLock<storageclass::Config>

New async accessor methods:
- get_server_config() / set_server_config()
- get_storage_class() / set_storage_class()

Fields initialized with defaults in ECStore::new(), synced from
globals after init() completes. Global functions preserved for
backward compatibility.

1160 tests pass, clippy clean, full workspace compiles.

* fix: address PR #3219 review comments - delegate to globals

Accessors now delegate to process-global statics to avoid state drift:
- get_server_config() delegates to get_global_server_config()
- set_server_config() updates both global and local field
- get_storage_class() delegates to get_global_storage_class()
- set_storage_class() updates both global and local field
- Removed stale sync code from init()
- Changed fields to std::sync::RwLock for sync access

1160 tests pass.

* fix: correct import order for cargo fmt

* fix: simplify config accessors - delegate to globals without local state

Remove local server_config and storage_class fields from ECStore.
Accessors now purely delegate to process-global statics, eliminating
the state drift risk identified in review.

1160 tests pass, formatting clean.
2026-06-05 01:06:08 +00:00
安正超 1dd3839a9f fix(signer): address post-merge review comments (#3216)
* fix: address PR #3150 review comments

- Restore get_host_addr as best-effort wrapper (String return type)
- Replace bare expect("err") with descriptive messages in unsigned trailer
- Simplify aws-chunked header construction
- 20 signer + 96 io-core tests pass

* fix(signer): preserve host fallback

* fix(signer): avoid signer fallback panics

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-04 22:45:50 +00:00
安正超 d6120f5788 refactor(ecstore): migrate mutable globals into ECStore struct fields (#3214)
* refactor(ecstore): migrate mutable globals into ECStore struct fields

Phase 1 of global singleton consolidation. Move mutable globals from
lazy_static into ECStore struct fields as the first step toward
dependency injection and multi-instance support.

New ECStore fields:
- is_erasure, is_dist_erasure, is_erasure_sd (erasure type flags)
- local_disk_map, local_disk_id_map, local_disk_set_drives
- root_disk_threshold
- tier_config_mgr, event_notifier, bucket_monitor

New accessor methods:
- is_erasure(), is_dist_erasure(), is_erasure_sd()
- update_erasure_type()
- tier_config_mgr(), event_notifier(), bucket_monitor()

Global functions in global.rs preserved for backward compatibility.
1151 ecstore tests pass.

* fix: address PR #3214 review comments

- Sync ECStore fields from globals after init()
- Enforce DistErasure => is_erasure invariant in update_erasure_type()
- Change bucket_monitor from Option to OnceLock for deferred initialization
- Restrict TypeLocalDiskSetDrives to pub(crate)
- 1151 tests pass

* fix: address PR #3150 review comments

- Restore get_host_addr as best-effort wrapper (String return type)
- Replace bare expect("err") with descriptive messages in unsigned trailer
- Simplify aws-chunked header construction
- 20 signer + 96 io-core tests pass

* fix(ecstore): format store imports

* fix(ecstore): keep migrated accessors in sync

* fix(signer): preserve host fallback and unsigned trailer errors

* fix(ecstore): defer migrated global accessors

* fix(signer): box unsigned trailer signing errors

* fix(ecstore): avoid lock awaits during sync

* fix(ecstore): narrow phase one globals

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-04 17:27:21 +00:00
houseme 0a74629c48 chore: add delete-objects lock batch diagnostics (#3218)
* chore(ecstore): add missing-shard first-scene diagnostics

Log rename_data quorum context behind RUSTFS_ISSUE3031_DIAG_ENABLE so partial-disk success can be correlated with later missing shard reads.

Also log put_object commit success and tmp cleanup boundaries to capture when successful quorum writes are followed by tmp_dir cleanup.

* chore(ecstore): add delete-objects lock batch diagnostics

Log DeleteObjects batch-lock request size, distributed lock quorum summary, and failed object keys behind RUSTFS_ISSUE3031_DIAG_ENABLE.

Include a Chinese operator checklist under docs/ that explains which logs to capture and how to interpret DeleteObjects lock_batch timeout incidents.

* chore: remove lock batch checklist from repo

Keep docs/issue-658-deleteobjects-lock-batch-checklist-zh.md only in the local checkout and drop it from the PR history.
2026-06-04 17:00:58 +00:00
安正超 3bd89944c2 perf(erasure): remove UUID from clone + increase encode inflight budget (#3212)
* perf(erasure): remove UUID from clone + increase encode inflight budget

Two targeted optimizations for the erasure encoding hot path:

1. Erasure::clone() no longer generates Uuid::new_v4() per clone.
   The _id field is unused in the hot path; reusing the original ID
   eliminates a CSPRNG call per block encode (100 calls for a 100MB
   object with 1MB blocks).

2. Default RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES raised from 8MB
   to 32MB. This increases the encode pipeline depth from ~5 to ~20
   blocks, allowing more read-ahead between the encoder and disk
   writer stages. The per-request memory bound is still controlled
   by the 8-block hard cap and the env var override.

3. Added encode_data_owned() utility method for zero-copy encoding
   when the caller already owns a heap buffer (Vec<u8> → BytesMut
   via Bytes::try_into_mut). Not used in the hot path yet but
   available for future callers.

All 1157 ecstore tests pass. Criterion micro-benchmarks show no
regression (< 2% variance). Single-machine warp E2E tests were
inconclusive due to high variance; a dedicated multi-disk test
environment is needed for reliable E2E comparison.

Ref: https://github.com/rustfs/backlog/issues/659

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: update Cargo.lock

* fix(erasure): align encode inflight cap and tests

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 13:49:36 +00:00
houseme fde519910d chore: add missing-shard first-scene diagnostics (#3213)
chore(ecstore): add missing-shard first-scene diagnostics

Log rename_data quorum context behind RUSTFS_ISSUE3031_DIAG_ENABLE so partial-disk success can be correlated with later missing shard reads.

Also log put_object commit success and tmp cleanup boundaries to capture when successful quorum writes are followed by tmp_dir cleanup.
2026-06-04 10:35:33 +00:00
Henry Guo 542720a1f7 feat(scanner): add partial scan resume hints (#3207)
* feat(scanner): add partial scan resume hints

* test(scanner): cover clearing scan resume hints

* fix(scanner): apply resume hint to combined child order

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-04 09:21:30 +00:00
Henry Guo a7be7c558d feat(table-catalog): add object-backed catalog store (#3206)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-04 07:50:13 +00:00
Henry Guo 6850457707 feat(scanner): add runtime scanner controls and status (#3203)
* feat(scanner): add runtime scanner controls and status

* fix(scanner): validate persisted scanner config

* docs(scanner): clarify start delay behavior

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-04 05:16:26 +00:00
houseme f708c22b0e fix: make ecstore trash cleanup idempotent (#3205)
* fix(http): reduce header timeout log noise

Log peer_addr for HTTP connection errors so idle or probe traffic can be traced back to the source.

Downgrade HeaderTimeout connection logs from warn to info because these timeouts are often caused by incomplete probe traffic rather than a service-side fault.

* fix(ecstore): suppress spurious warn logs for missing trash cleanup sources

Extract `reliable_rename_inner` with a `warn_on_failure` flag so that
`rename_all_ignore_missing_source` can skip the warn log when the source
is absent. This completes the idempotent cleanup fix — the previous
implementation still emitted a `reliable_rename failed` warning before
the NotFound was caught and silenced.

Also restore the `recursive` branching in `move_to_trash` so that
non-recursive deletions use `fs::rename` directly (preserving original
semantics), while recursive deletions use the idempotent helper.
2026-06-04 04:21:31 +00:00
Alexander Kharkevich 528c3278b7 fix(iam): allow colons and dots in STS claim policy names (#3164)
`is_safe_claim_policy_name` rejected any character other than
`[a-zA-Z0-9_-]`, silently dropping policy names containing colons.
This breaks Kubernetes workload identity where `claim_name=sub`
resolves to `system:serviceaccount:<namespace>:<sa-name>` — a valid
policy name that can be created via the admin API but is then
unreachable during STS session authorization.

Add `:` and `.` to the allowed character set. These characters are:
- Used in K8s service account `sub` claims (colons)
- Used in Java/DNS-style group names from OIDC providers (dots)
- Already accepted by the `add-canned-policy` admin API endpoint

Require at least one alphanumeric character to prevent meaningless
names (`.`, `..`, `-`, `_`, `:`, etc.) from resolving.

Still rejected: `/`, `\`, whitespace, `$`, `;`, `{`, `}` and other
chars that could enable path traversal or injection.

Signed-off-by: Alexander Kharkevich <alex@mara.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: GatewayJ <835269233@qq.com>
2026-06-04 03:39:50 +00:00
Michael 45e3c01857 feat(helm): add topology spread constraints configuration to StatefulSet (#3187)
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-04 03:20:51 +00:00
Henry Guo 013b5b7966 feat(table-catalog): add catalog store entry models (#3201)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-04 03:20:46 +00:00
GatewayJ 785d53fce8 fix(s3select): validate scan_range protocol and parquet overlap (#3176)
* fix(s3select): enforce scan range protocol and parquet overlap

* fix(s3select): select row groups by start offset

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 15:57:17 +00:00
安正超 7b57d5b217 perf(signer): reduce String allocations in V4 signing path (#3197)
* perf(signer): reduce String allocations in V4 signing path

- v4_ignored_headers: HashMap<String, bool> -> HashSet<&'static str>
- Header lookups: k.to_string() -> k.as_str() (zero-copy)
- Query params: Vec<(String, String)> -> Vec<(&str, &str)> (zero-copy)
- Canonical request: single String with push_str instead of 6 intermediate Strings + join
- 20 signer tests pass

* fix(signer): align ignored header set type

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 15:40:35 +00:00
安正超 0fb02049ac fix(ecstore): remove dead _buf field from Erasure struct (#3196)
* fix(ecstore): remove dead _buf field from Erasure struct

_buf was allocated in new(), clone(), and Default but never read or written.
Each clone() was allocating block_size bytes (typically 10MB) for nothing.

- Remove _buf from struct, Default, Clone, new_with_options
- 51 erasure tests pass

* fix: remove empty line after doc comment

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 15:40:29 +00:00
安正超 a63aaf933d fix(notify): normalize lock ordering to prevent deadlock (#3199)
* fix(notify): normalize lock ordering to prevent deadlock

replace_targets() and shutdown() acquired target_list before replay_workers,
opposite to runtime_view.rs order. This is an ABBA deadlock pattern.

- Swap to replay_workers -> target_list order (canonical)
- Add lock order comments
- 82 notify tests pass

* docs(notify): update runtime facade lock order

* docs(notify): clarify runtime facade lock order

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 15:37:59 +00:00
安正超 1838922f07 fix(security): add deny_unknown_fields to deserialization structs (#3198)
* fix(security): add deny_unknown_fields to deserialization structs

Prevent silent acceptance of malformed or adversarial payloads.

- policy: Policy, BucketPolicy, Statement, BPStatement, PrincipalObject
- notify: S3KeyFilter custom deserializer rejects unknown child elements
- update: VersionInfo (remote HTTP response)

26 policy + 82 notify tests pass.

* fix(update): keep version response forward compatible

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 15:33:28 +00:00
Henry Guo e0a03ce10b feat(policy): add table catalog admin actions (#3200)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 15:25:26 +00:00
Henry Guo ad1a489f75 feat(scanner): add scanner budget progress controls (#3185)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-03 14:37:58 +00:00
houseme f49827fc58 fix(iam): prevent transient IAM walk timeout from crashing startup (#3188)
* fix(iam): prevent transient IAM walk timeout from crashing startup

  IAM startup performs a blocking full metadata walk on `.rustfs.sys/config/iam/`.
  When that distributed walk times out (e.g. disk pressure after cluster reboot),
  the old code treated the failure as fatal and exited the process, causing a
  systemd restart loop.

  Changes:
  - Add `startup_iam.rs`: attempt IAM init, enter degraded mode on failure,
    spawn background retry task with exponential backoff (5s→10s→20s→30s cap)
  - Log level escalates to ERROR after 12 retries (~5 min) to aid diagnosis
  - `/health/ready` returns 503 until IAM recovers; IAM-dependent ops return
    `IamSysNotInitialized` (existing fail-closed behavior preserved)
  - Fix admin path boundary matching: `/minio/administrator` no longer falsely
    matches as admin prefix
  - Normalize Content-Length: 0 for admin GET requests with empty body

  Fixes #3175

* fix(iam): move constant assertion into const block

Fixes clippy::assertions-on-constants warning on
IAM_RETRY_ESCALATION_THRESHOLD assertion.

* fix(iam): address PR review comments

- Replace OnceLock with AtomicU64 sentinel for test isolation;
  add reset_test_failure_counter() for integration tests
- Use u32::try_from() instead of `as u32` narrowing cast in
  compute_backoff_interval
- Rename misleading test; update to verify finalize retry behavior
- Restructure spawn_iam_recovery_task into init-retry and
  finalize-retry phases so transient readiness failures are retried
  instead of leaving the server permanently degraded

* fix(iam): gate test hooks behind debug_assertions

- reset_test_failure_counter() now stores sentinel (u64::MAX) to
  correctly trigger env var re-read on next call
- RUSTFS_TEST_IAM_FAIL_INIT_ATTEMPTS only honored in debug builds
- RUSTFS_TEST_IAM_RETRY_INTERVAL_MS only honored in debug builds

* test(iam): cover deferred bootstrap recovery

Add a dedicated embedded deferred-IAM integration test in a separate test binary to avoid process-global startup collisions.

Strengthen startup IAM recovery coverage with focused unit tests and keep the existing embedded smoke test isolated while carrying the manual test license header update in the same change set.

* fix(startup): tighten deferred IAM recovery path

Adopt follow-up review feedback by silencing misleading app-context warnings after IAM recovery, reusing boundary-aware path prefix checks in the readiness gate, and tying deferred IAM recovery retries to server shutdown tokens.

Keep the deferred IAM embedded integration coverage and startup recovery unit coverage green after the follow-up hardening.

* refactor(startup): simplify IAM recovery task

Collapse the deferred IAM recovery implementation back to a concrete production flow instead of keeping boxed callback seams in the runtime path.

Keep only stable backoff unit coverage in startup_iam and rely on the embedded deferred bootstrap integration test for end-to-end recovery behavior.

* refactor(startup): trim IAM recovery test scaffolding

Keep the concrete deferred IAM recovery path intact while removing bulky test-only async loop scaffolding from startup_iam.

Retain the stable backoff unit checks and rely on the embedded deferred bootstrap integration test for end-to-end recovery coverage.

* fix: apply code review improvements from PR #3188 review

- Simplify RecoveryFuture type alias by removing unnecessary lifetime
- Fix finalize_iam_recovery to return Err if app context unavailable
- Update bootstrap_or_defer_iam_init doc comment to reflect Err case
- Use boundary-aware has_path_prefix for admin path matching in utils.rs
- Add test for adminx boundary rejection in utils.rs and layer.rs
- Improve embedded deferred IAM test with timeout wrapper

* style: merge has_path_prefix import into existing use block

* fix(iam): address final review follow-ups

- fix main startup readiness publication to pass ServiceStateManager correctly
- centralize IAM test env keys in rustfs_config and reuse them in runtime/tests
- keep deferred IAM bootstrap validation aligned with the final review fixes

* fix: isolate listing timeouts from drive health

Keep walk_dir scanner timeouts request-scoped instead of marking local drives faulty.

Add regression coverage for follow-up bucket info, set-level list_path, and system-prefix listings after prior walk timeouts.

* test(iam): gate deferred bootstrap test to debug

Align the deferred IAM embedded integration test with debug-only IAM fault injection hooks so release-profile runs do not assert deferred bootstrap behavior that cannot be triggered.

* test(ecstore): bound prior walk timeout regressions

- set walk_dir stall timeout explicitly in prior-timeout listing tests
- keep the system-prefix follow-up listing scoped to the same base dir
- assert the expected directory entry so the timeout regression test stays fast and stable

* fmt
2026-06-03 14:37:25 +00:00
Henry Guo 0b69f363d6 fix: prioritize manual heal queue admission (#3192)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 14:21:20 +00:00
houseme 20bb5dc4a2 fix(ecstore): retry transient walk dir stream errors (#3194)
* fix(ecstore): retry transient walk dir stream errors

Retry RemoteDisk walk_dir once when the first failure looks transport-related so restart-time HttpReader stream errors do not immediately count as hard listing failures.

Improve HttpReader request and stream error messages with method and URL context, and add regression coverage for retry recovery and diagnostics.

* fix(ecstore): avoid retry after partial walk dir stream

Stop retrying walk_dir after copy_stream_with_buffer has already written partial bytes into the destination writer.

Keep the retry only for open_walk_dir failures and add a regression test that proves partial stream failures are returned without issuing a second stream.
2026-06-03 13:38:22 +00:00
Alexander Kharkevich ce6fcf39b1 feat(oidc): add HIDE_FROM_UI option to exclude providers from console login (#3162)
Add `RUSTFS_IDENTITY_OPENID_HIDE_FROM_UI[_<SUFFIX>]` setting that
removes a provider from the login page while keeping it fully
functional for STS AssumeRoleWithWebIdentity and site-replication.

Changes:
- Add `hide_from_ui: bool` to `OidcProviderConfig`
- Add `list_visible_providers()` that filters hidden providers
  (used by console login and /v3/oidc/providers endpoint)
- Keep `list_providers()` unfiltered for site-replication/admin config
- Extract `normalize_provider_config(config) -> config` to deduplicate
  field normalization (accepts the struct directly, not 18 parameters)
- Add `parse_enable_state()` helper for consistent EnableState parsing
- Plumb through admin API request structs (`#[serde(default)]`)
- Expose in `OidcConfigView` for admin GET config round-trip
- Persist via `upsert_persisted_provider_config()`

Note: adding `hide_from_ui` to the public `OidcProviderConfig` struct
is a source-level change for code constructing it with struct literals.
This is acceptable for the current pre-1.0 release cycle.

Signed-off-by: Alexander Kharkevich <alex@mara.com>
Co-authored-by: GatewayJ <835269233@qq.com>
2026-06-03 13:37:44 +00:00
Henry Guo be98c1f86a feat(table): add catalog boundary primitives (#3173)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-03 13:16:59 +00:00
houseme 66b8699927 chore(release): prepare 1.0.0-beta.7 (#3184)
* chore(release): prepare 1.0.0-beta.7

* chore(release): align release assets for 1.0.0-beta.7
2026-06-03 04:18:50 +00:00
GatewayJ cc9e4bb207 fix(admin): normalize empty admin GET content length (#3160)
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-06-03 04:12:04 +00:00
Henry Guo cc07946782 feat(scanner): add cycle budget observability (#3166)
* feat(scanner): add cycle budget observability

* fix(scanner): clear cycle state after budget stop

* test(scanner): stabilize timeout-based scanner tests

* fix(scanner): keep cycle ILM counts scanner-only

* test(scanner): avoid test-only pending import

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-06-03 03:32:41 +00:00
Henry Guo 3d9caff3a4 fix(object-lock): allow locked objects to receive new versions (#3179)
* fix(object-lock): allow locked objects to receive new versions

* fix(object-lock): validate copy destination writes

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 02:50:53 +00:00
houseme 29fbdc2dbf feat(ecstore): add object lock diagnostics (#3178)
* feat(ecstore): add object lock diagnostics

Add configurable namespace lock diagnostics for object operations so production contention can be traced by operation, owner, and key.

Wrap object read/write lock acquisition in diagnostic guards across get, head, put, delete, copy, and multipart flows, and log slow acquisition and long hold durations behind new RUSTFS_OBJECT_LOCK_DIAG_* settings.

Verification:

- make pre-commit

* feat(obs): expose object lock diagnostics metrics

Add Prometheus metrics and Grafana panels for object namespace lock diagnostics, covering slow acquire counts, slow hold counts, acquire duration, hold duration, and the diagnostics-enabled state.

Adopt PR review feedback by keeping diagnostic guards alive through the guarded operation so long-hold warnings and metrics are emitted, reusing shared env helpers, and reducing default-path overhead when diagnostics are disabled.

Verification:

- cargo check -p rustfs-ecstore -p rustfs-io-metrics

- cargo test -p rustfs-ecstore store::object -- --nocapture

- make pre-commit

* perf(obs): reduce object lock diag overhead

Avoid repeated environment parsing on hot object-lock paths by caching the diagnostics-enabled flag, and stop allocating label strings for object lock metrics by recording static labels directly.

Strengthen io-metrics tests by using a local recorder and asserting that the expected object lock diagnostic counters, gauges, and histograms are emitted.

Verification:

- cargo check -p rustfs-ecstore -p rustfs-io-metrics

- cargo test -p rustfs-io-metrics -- --nocapture

- make pre-commit
2026-06-03 02:10:27 +00:00
安正超 0dbf0b13a8 fix(lock): align distributed acquisition retries (#3177)
* fix(lock): align distributed acquisition retries

* fix(lock): retry remote lock RPC timeouts

* fix(lock): quiet retryable acquisition logs

* fix(lock): enforce attempt acquisition deadline

* fix(ci): install unzip in setup action

* fix(lock): handle acquisition review followups

* fix(ci): use nextest install-action shorthand

* fix(lock): preserve hard failures on attempt timeout

* fix(lock): retry attempt timeout when quorum remains possible

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 01:58:47 +00:00
安正超 d817bd4449 fix(ci): stabilize Rust setup action (#3163)
* fix(ecstore): restore windows endpoint validation build

* fix(ci): install unzip for protoc setup

* fix(ci): use nextest install-action shorthand

* fix(ci): restore cargo-nextest action ref

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-03 01:51:30 +00:00
Michael 054523695f fix(helm): add apiVersion and kind to PersistentVolumeClaim metadata (#3170) 2026-06-03 09:27:43 +08:00
安正超 ae2d3c4025 ci: restore ubicloud runners for heavy jobs (#3183) 2026-06-03 08:38:17 +08:00
安正超 14aaab9406 ci: use hosted runners for light jobs (#3181) 2026-06-02 23:17:56 +08:00
Henry Guo 15be56f242 docs(table): add S3 table concept model (#3172)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-02 23:05:30 +08:00
安正超 7cc730d9c0 ci: isolate s3 test runner state (#3180) 2026-06-02 22:40:37 +08:00
cxymds a55ead42e7 fix(admin): prefer explicit TLS for site replication endpoint (#3171) 2026-06-02 11:37:59 +00:00
houseme 0d00b886ac fix(rio): map truncated put bodies to incompletebody (#3168)
* fix(rio): surface incomplete put bodies

Propagate incomplete PUT request bodies as IncompleteBody instead of allowing erasure encode to treat truncated input as a normal EOF.\n\n- mark premature EOFs in HardLimitReader with an explicit IncompleteBody error\n- preserve EOF error chains through read_full and map them to S3 IncompleteBody\n- stop erasure encode from swallowing UnexpectedEof on truncated input\n- add regression tests for reader, erasure encode, and API error mapping\n\nRefs: rustfs/backlog#654

* fix(s3): honor decoded length for aws chunked put

Use x-amz-decoded-content-length for aws-chunked PutObject requests so trailer-checksum uploads are sized against the decoded payload instead of the wire-encoded content-length.\n\n- prefer decoded content length for aws-chunked put bodies\n- add a regression test covering the size selection logic\n- keeps the incomplete body fix working for truly truncated uploads while restoring checksum trailer compatibility\n\nRefs: rustfs/backlog#654

* fix(io): follow up review comments on incompletebody handling

Address PR review feedback by restoring read_full's existing EOF contract, adding a dedicated read_full_or_eof helper for erasure encoding, covering nested incomplete-body error chains, and documenting plus hardening aws-chunked size selection.\n\n- keep read_full returning early EOF on empty reads\n- use read_full_or_eof only in erasure encoding paths\n- detect aws-chunked via content-encoding or transfer-encoding\n- add nested error-chain and aws-chunked regression tests\n\nRefs: rustfs/backlog#654

* fix(rio): surface incomplete put bodies

Propagate incomplete PUT request bodies as IncompleteBody instead of allowing erasure encode to treat truncated input as a normal EOF.\n\n- mark premature EOFs in HardLimitReader with an explicit IncompleteBody error\n- preserve EOF error chains through read_full and map them to S3 IncompleteBody\n- stop erasure encode from swallowing UnexpectedEof on truncated input\n- add regression tests for reader, erasure encode, and API error mapping\n\nRefs: rustfs/backlog#654

* fix(s3): honor decoded length for aws chunked put

Use x-amz-decoded-content-length for aws-chunked PutObject requests so trailer-checksum uploads are sized against the decoded payload instead of the wire-encoded content-length.\n\n- prefer decoded content length for aws-chunked put bodies\n- add a regression test covering the size selection logic\n- keeps the incomplete body fix working for truly truncated uploads while restoring checksum trailer compatibility\n\nRefs: rustfs/backlog#654

* fix(io): follow up review comments on incompletebody handling

Address PR review feedback by restoring read_full's existing EOF contract, adding a dedicated read_full_or_eof helper for erasure encoding, covering nested incomplete-body error chains, and documenting plus hardening aws-chunked size selection.\n\n- keep read_full returning early EOF on empty reads\n- use read_full_or_eof only in erasure encoding paths\n- detect aws-chunked via content-encoding or transfer-encoding\n- add nested error-chain and aws-chunked regression tests\n\nRefs: rustfs/backlog#654

* fix(rio): reject bytes beyond hard limit

* fix(ecstore): reject zero-sized erasure blocks
2026-06-02 11:31:51 +00:00
唐小鸭 480babc0af fix: window error (#3167)
fix window error
2026-06-02 05:09:57 +00:00
Henry Guo 1d46047d6f feat(scanner): expand scanner observability metrics (#3159)
* feat(scanner): expand scanner observability metrics

* chore(scanner): align bucket-drive metric wording

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-06-02 01:29:53 +00:00
cxymds 39a283fbce fix(windows): handle zfs volume paths (#3157)
* fix(windows): handle zfs volume paths

* fix(windows): address volume path review

* refactor(windows): share fallback path resolution

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-06-01 13:04:40 +00:00
CptOfEvilMinions e91e513ab3 feat: Helm chart support extra volumes (#2982) 2026-06-01 16:52:35 +08:00
Henry Guo f3bd838925 feat(scanner): expose cycle progress metrics (#3152)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-01 07:54:39 +00:00
dependabot[bot] cbb6f9c76f chore(deps): bump the dependencies group with 6 updates (#3151)
* chore(deps): bump the dependencies group with 6 updates

Bumps the dependencies group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [hyper](https://github.com/hyperium/hyper) | `1.10.0` | `1.10.1` |
| [serial_test](https://github.com/palfrey/serial_test) | `3.4.0` | `3.5.0` |
| [snafu](https://github.com/shepmaster/snafu) | `0.9.0` | `0.9.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.1` | `1.23.2` |
| [dial9-tokio-telemetry](https://github.com/dial9-rs/dial9-tokio-telemetry) | `0.3.12` | `0.3.13` |
| [pyroscope](https://github.com/grafana/pyroscope-rs) | `2.0.5` | `2.0.6` |


Updates `hyper` from 1.10.0 to 1.10.1
- [Release notes](https://github.com/hyperium/hyper/releases)
- [Changelog](https://github.com/hyperium/hyper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper/compare/v1.10.0...v1.10.1)

Updates `serial_test` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/palfrey/serial_test/releases)
- [Commits](https://github.com/palfrey/serial_test/compare/v3.4.0...v3.5.0)

Updates `snafu` from 0.9.0 to 0.9.1
- [Changelog](https://github.com/shepmaster/snafu/blob/main/CHANGELOG.md)
- [Commits](https://github.com/shepmaster/snafu/compare/0.9.0...0.9.1)

Updates `uuid` from 1.23.1 to 1.23.2
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.1...v1.23.2)

Updates `dial9-tokio-telemetry` from 0.3.12 to 0.3.13
- [Release notes](https://github.com/dial9-rs/dial9-tokio-telemetry/releases)
- [Changelog](https://github.com/dial9-rs/dial9/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dial9-rs/dial9-tokio-telemetry/compare/dial9-tokio-telemetry-v0.3.12...dial9-tokio-telemetry-v0.3.13)

Updates `pyroscope` from 2.0.5 to 2.0.6
- [Release notes](https://github.com/grafana/pyroscope-rs/releases)
- [Changelog](https://github.com/grafana/pyroscope-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/grafana/pyroscope-rs/compare/lib-2.0.5...lib-2.0.6)

---
updated-dependencies:
- dependency-name: hyper
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: serial_test
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependencies
- dependency-name: snafu
  dependency-version: 0.9.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: uuid
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: dial9-tokio-telemetry
  dependency-version: 0.3.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: pyroscope
  dependency-version: 2.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(profiling): pin pyroscope to 2.0.5, add jemalloc memory profiling, unify platform gates

Pin pyroscope to =2.0.5 to avoid duplicate `perf_signal_handler` symbol
caused by 2.0.6 vendoring pprof-rs internally. 2.0.5 depends on external
pprof-pyroscope-fork (v0.1500.4), which Cargo unifies with the workspace's
pprof-pyroscope-fork — a single symbol instance, no linker collision.

Add jemalloc_backend for continuous memory profiling export to Pyroscope,
alongside the existing pprof-based CPU profiling. Both CPU and memory
profiling now work on linux and macos.

Unify platform gates from `cfg(all(target_os = "linux", target_env = "gnu",
target_arch = "x86_64"))` to `cfg(any(target_os = "linux",
target_os = "macos"))`, switching macOS global allocator from mimalloc to
jemalloc.

* style: format cfg attributes in allocator_reclaim.rs

* chore(deps): ignore pyroscope 2.x in dependabot, add profile_type tag

Add pyroscope 2.x to dependabot ignore list to prevent auto-upgrade
past 2.0.5 (>=2.0.6 vendors pprof-rs, causing duplicate symbol conflict).

Add profile_type=cpu tag to CPU profiling agent for differentiation
from memory profiling agent in Pyroscope UI.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-06-01 07:21:47 +00:00
majinghe 4d2f13af6f chore(action): add self-host runner support. (#3155) 2026-06-01 15:11:50 +08:00
安正超 97dd825468 fix(ecstore): optimize ObjectInfo clone and fix critical TODOs (#3149)
* fix(ecstore): optimize ObjectInfo clone and fix critical TODOs

## ObjectInfo hot-path clone optimization (issue #653 item 2)
- Wrap user_defined (HashMap), user_tags (String), parts (Vec) in Arc
- Clone cost reduced from ~20 heap allocations to O(1) ref count bump
- Updated 11 downstream access sites with explicit deref where needed

## Critical TODO/FIXME fixes (issue #653 item 7)
- rpc/peer_s3_client.rs: descriptive error message for empty peer response
- set_disk/write.rs: concurrent rollback deletes via tokio::spawn + join_all
- store_list_objects.rs: resolved FIXME with explanation + 4 regression tests
- store/bucket.rs: namespace write locks for make_bucket and delete_bucket

Skipped TODOs (too risky without broader context):
- multipart.rs:30 nslock — causes lock timeout in existing tests
- bucket.rs:88 cached list_bucket — needs cache invalidation strategy
- bucket.rs:149 replication delete — needs replication subsystem integration

1134 tests pass (1 flaky under parallel execution, passes individually).

* fix: address PR #3149 review comments

- Preserve NamespaceLockQuorumUnavailable variant in bucket create/delete
- Fix cancellation test to actually test cancellation (keep senders open)
- Update ObjectInfo consumers in app/ for Arc-backed fields
- Update bucket_usecase.rs user_tags to Arc<String>

* fix: update ObjectInfo Arc consumers

* fix: address ecstore merge review comments

* fix: cancel blocked merge output sends

* fix(ecstore): satisfy ObjectInfo clone clippy
2026-06-01 00:44:58 +00:00
安正超 bd571a575f fix(io-core,signer): replace unwrap() with proper error handling (#3150)
* fix(io-core,signer): replace unwrap() with proper error handling

## io-core (issue #653 item 1)
- pool.rs: replace 8 .lock().unwrap() with poisoned recovery
- pool.rs: replace semaphore acquire unwrap with graceful fallback
- deadlock_detector.rs: replace 5 .lock().unwrap() with match/ok

## signer (issue #653 item 1)
- Add SignV2Error enum, try_pre_sign_v2, try_sign_v2
- Replace 14 unwrap() in v2 signing with ? propagation
- Add try_streaming_sign_v4, replace 5 expect("err") with descriptive errors
- get_host_addr returns Result instead of panicking

All backward-compat wrappers preserved. 95 io-core + 18 signer tests pass.

* fix: address signer and pool review comments

* test: update signer v2 string-to-sign test

* fix: address signer and pool review followups

* fix: tighten signer host and pool fallback
2026-05-31 23:40:20 +00:00
GatewayJ 9ce9ec22d1 fix(ecstore): tighten object copy rename handling (#3131)
* fix(ecstore): tighten object copy rename handling

* fix(ecstore): narrow copy lock lifetime

* test(ecstore): cover reverse copy concurrency

* fix(multipart): ignore preconditions for internal lookup

* fix(ecstore): clean precondition lock bindings

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-31 23:19:21 +00:00
Henry Guo 76da2a48d0 feat(scanner): expose cycle observability controls (#3147)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-31 21:55:46 +00:00
安正超 a14657f517 fix(scanner,data-usage): fix add() logic inversion and usize underflow in reduce_children_of (#3142)
* fix(scanner,data-usage): fix add() logic inversion and usize underflow in reduce_children_of

Bug #1: add() had inverted logic — returned early when children were present,
making the recursive child traversal dead code. Leaf compaction path was
completely non-functional. Fixed by recursing into children when present and
collecting leaves only at leaf nodes.

Bug #2: reduce_children_of used  which underflows in debug
(panic) or release (wraps to usize::MAX, compacts entire cache). Fixed with
saturating_sub.

Both bugs existed identically in crates/scanner and crates/data-usage.

Added 5 tests covering add() subtree traversal, edge cases, and
reduce_children_of compaction behavior.

Closes rustfs/backlog#652

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: collect internal nodes as compaction candidates, not leaves

The previous fix for add() collected leaf nodes (children empty) as
compaction candidates. This is incorrect: total_children_rec returns 0
for leaves, so compacting them never decrements  — the
compaction loop makes no progress.

Collect internal nodes (children non-empty) instead. Compacting an
internal node removes its subtree via delete_recursive, which actually
reduces the total child count. Leaf nodes are skipped since they have
no children to remove.

Updated tests to match the corrected semantics.

* refactor: rename leaves→candidates, add data-usage regression tests

- Rename misleading  variable/parameter to  in
  add() and reduce_children_of() for both crates
- Add 4 regression tests in crates/data-usage covering add() candidate
  selection and reduce_children_of() compaction + saturating_sub

* fix: address data usage compaction review feedback

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-31 21:52:53 +00:00
houseme 315c2e33f0 test(admin): add route registration coverage for config admin APIs (#3148)
Add test assertions for all MinIO-compatible config admin routes
(get/set/del-config-kv, help-config-kv, list/clear/restore-config-history-kv,
GET/PUT /v3/config) and verify their compatibility alias paths.

Relates to rustfs/backlog#608
2026-05-31 18:13:08 +00:00
Henry Guo a99ef64db2 feat(scanner): add scanner budgets and progress metrics (#3145)
* fix(scanner): preserve maintenance scan cadence

* feat(scanner): add scanner concurrency budget

* feat(scanner): expose scanner runtime progress

* fix(scanner): address scanner review feedback

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-31 16:42:38 +00:00
Henry Guo a8557ceb0e fix(rebalance): require target goal before completion (#3141)
* fix(rebalance): require target goal before completion

Stop treating pools within a fixed tolerance of the rebalance goal as completed before any data has moved.

Add a regression test for the two-pool imbalance reported in issue 3137.

* chore(rebalance): clarify goal completion wording

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-31 14:54:12 +00:00
安正超 81b899e9c6 docs: update security advisory lessons (#3143) 2026-05-31 23:01:35 +08:00
安正超 b5e565c0ce chore(agents): add Rust code quality rules and skill (#3144)
* docs: update security advisory lessons

* chore(agents): add Rust code quality rules and skill

Add rules derived from full-project code review (48 findings across
7 dimensions) to prevent recurring issues in agent-generated code.

AGENTS.md changes:
- crates/AGENTS.md: error type design, concurrency, recursion safety,
  type casting, test quality rules
- root AGENTS.md: serde safety, naming conventions
- crates/ecstore/AGENTS.md: allocation discipline, lock ordering,
  recursion safety, dead code policy
- crates/notify/AGENTS.md: lock ordering for runtime_view/facade

Skill changes:
- code-change-verification: add Rust-specific checks (unwrap, as cast,
  clone, lock order, recursion, error types, test assertions)
- security-advisory-lessons: add serde deserialization safety pattern
- NEW rust-code-quality: automated scan + manual review checklist
2026-05-31 22:19:36 +08:00
houseme 8577bd825e feat(admin): restore config admin compatibility (#3133)
* feat(admin): restore config admin compatibility

Co-authored-by: weisd <im@weisd.in>

* fix(admin): align config admin clean rebuild

Co-authored-by: weisd <im@weisd.in>

* fix(admin): align config history and peer signals

* fix(admin): harden config admin mutations

* fix(admin): tighten config review follow-ups

* perf(admin): reuse env snapshot in config render

* fix(ecstore): clean up config admin and listing error handling

Remove redundant is_all_volume_not_found check in list_merged, add
storage class encode/decode roundtrip tests, fresh boot integration
test, and config admin clean rebuild improvements.

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(admin): sync global server config on mutation and reload

Change GLOBAL_SERVER_CONFIG from OnceLock to RwLock so config mutations
(set/del/restore/reload) are visible to readers without restart. Call
set_global_server_config after every store save and on snapshot reload.
Register storage_class as a dynamic config subsystem.

Co-authored-by: hehutu <heihutu@gmail.com>

* style: apply rustfmt to config and admin tests

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(test): update signal_service test for storage_class dynamic subsystem

storage_class is now a valid dynamic config subsystem, so the
"requires object layer" test should expect "storage layer not initialized"
instead of "unsupported dynamic config subsystem".

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(config): publish storage_class runtime config on dynamic reload

Change GLOBAL_STORAGE_CLASS from OnceLock to RwLock so runtime updates
are possible. apply_storage_class_runtime_config now actually publishes
the parsed config via set_global_storage_class instead of dropping it.

Addresses review feedback: storage_class was marked as dynamically
applied but the parsed result was discarded, so mc admin config set
returned config_applied=true while the runtime kept using stale parity
settings until restart.

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(admin): harden config init, history ordering, and env redaction

- Change GLOBAL_SERVER_CONFIG from RwLock<Config> to RwLock<Option<Config>>
  initialized with None, preserving "not initialized" detection via None
- Move save_server_config_history before save_server_config_to_store in
  SetConfigKVHandler, DelConfigKVHandler, and SetConfigHandler so a
  restore point exists before mutations are persisted
- Redact sensitive env override values with *redacted* instead of
  silently omitting the line, improving admin visibility
- Add code comment explaining VolumeNotFound removal rationale in
  list_merged for listing paths

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(config): keep in-memory config in sync after set/restore/reload

GLOBAL_SERVER_CONFIG was a OnceLock set once at startup and never
updated. After mc admin config set writes to the store, any fallback
to get_global_server_config() returned stale init-time data. Similarly,
reload_runtime_config_snapshot read from the store but discarded the
result.

- Replace OnceLock with RwLock for GLOBAL_SERVER_CONFIG and
  GLOBAL_STORAGE_CLASS so they can be updated at runtime
- Add set_global_server_config / set_global_storage_class setters
- Call set_global_server_config after every config save (set-kv,
  del-kv, set-config, restore-history)
- Re-apply dynamic subsystems (storage_class, audit_webhook,
  audit_mqtt) and signal peers in reload_runtime_config_snapshot
  and full-config operations
- Fix render_selected_config scope boundary check: track per-scope
  line count instead of checking global lines.is_empty()
- Include STORAGE_CLASS_SUB_SYS in is_dynamic_config_subsystem so
  apply_storage_class_runtime_config is reachable

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(storageclass): use CLASS_RRS key in lookup_config for RRS parity

lookup_config used kvs.get(RRS) where RRS="REDUCED_REDUNDANCY", but the
admin config path writes the key as CLASS_RRS="rrs". This caused RRS
values to never be read back, always falling back to default parity.

- Changed kvs.get(RRS) to kvs.get(CLASS_RRS) in lookup_config
- Added regression tests verifying RRS read/write consistency

Co-authored-by: hehutu <heihutu@gmail.com>

* fix(config): add peer-side logging and don't swallow apply errors

- Add tracing::warn! in reload_dynamic_config_runtime_state and
  reload_runtime_config_snapshot when config read or subsystem apply
  fails, so on-host diagnostics show which signal failed and why
- Change `let _ = apply_dynamic_config_for_subsystem(...)` to
  `if let Err(err) = ... { warn!(...) }` in reload_runtime_config_snapshot
  so per-subsystem failures are logged instead of silently swallowed
- Remove weak test global_server_config_returns_none_before_init that
  had no meaningful assertion due to shared global state

Co-authored-by: hehutu <heihutu@gmail.com>

* style: apply rustfmt to config and storageclass tests

Co-authored-by: hehutu <heihutu@gmail.com>

---------

Co-authored-by: weisd <im@weisd.in>
Co-authored-by: hehutu <heihutu@gmail.com>
2026-05-31 11:50:13 +00:00
Henry Guo 92104cb354 fix(scanner): reduce single-disk scanner churn (#3135)
* fix(scanner): slow single-disk default scans

* fix(scanner): reduce single-disk scanner churn

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-31 05:05:04 +00:00
Henry Guo d921af6ef7 fix(heal): normalize completed root heal state (#3140)
Clear persisted background heal active state after a deep scanner cycle completes so root heal status does not remain in progress between cycles.

Treat path-based stop for an already missing heal task as stopped while keeping client-token cancellation strict.

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-31 04:47:36 +00:00
安正超 e12d63234a test(targets): cover Kafka check SASL validation (#3136)
test(targets): cover kafka check sasl validation

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-31 04:47:21 +00:00
houseme d951c09cac chore(deps): update flake.lock (#3139)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/3d8f0f3' (2026-05-23)
  → 'github:NixOS/nixpkgs/e9a7635' (2026-05-29)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/d9973e2' (2026-05-23)
  → 'github:oxalica/rust-overlay/85570ef' (2026-05-30)
2026-05-31 01:59:51 +00:00
安正超 cf55743579 perf: reduce spawn_blocking contention in PUT path (#3132)
* perf: reduce spawn_blocking contention in PUT path (~23% throughput gain)

Flame graph profiling identified tokio blocking pool mutex contention
as the #1 bottleneck (17.3% of CPU time). Each spawn_blocking call must
acquire parking_lot::raw_mutex to enqueue work. With 16 concurrent PUTs
× 4 disks × 3+ spawn_blocking per disk, this became a serialization point.

Optimizations applied:
- Merge make_dir_all + file write into single spawn_blocking
- Merge read_file + parse + write + rename into single spawn_blocking
  for inline objects (small files)
- Optimize reliable_rename to try rename first, mkdir only on ENOENT
- Optimize remove/remove_std to try remove_file first, EISDIR fallback
- Add encode_inline_small fast path for small objects
- Parallelize bitrot writer creation with join_all

Benchmark (4KiB PUT, 4-disk EC, 16 concurrent, 8 rounds, randomized A/B):
  Baseline: ~950 obj/s → Optimized: ~1173 obj/s (+23%)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address review comments

- Return old_data_dir for non-inline rename_data path (was incorrectly None)
- Restore delete_all cleanup of PUT temp data on failure paths
- Fix cargo fmt formatting

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* style: apply rustfmt from stable 1.96.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: collapse nested if-let chains for clippy compliance

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: address Copilot review comments

- fs.rs: handle macOS EPERM from remove_file on directories
- os.rs: restore NotFound=Ok(()) semantics on first rename attempt
- local.rs: use try-rename-then-mkdir pattern for inline rename_data

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test: add unit tests for encode_inline_small fast path

* test: fix comment and line length in encode_inline_small tests

* fix: revert reliable_rename and write_all_internal to match original

Restore the original reliable_rename logic (check parent exists, then
rename in loop) and the original write_all_internal (make_dir_all outside
spawn_blocking). The optimization changes caused a CI-only test failure
in capacity_dirty_scope_test that could not be reproduced locally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: update comment and fix formatting for CI

- Fix macOS/BSD comment to accurately say macOS only
- Fix encode_inline_small test formatting to match rustfmt 1.96.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: propagate inline rename errors

* fix: retry rename when parent missing

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-31 01:28:48 +00:00
weisd ca4793f93e fix(s3): preserve listing pagination parity (#3117)
* fix: preserve S3 listing pagination parity

S3 listing responses need stable wire semantics across encoded listings, version markers, and quorum-sensitive metadata reads. This tightens marker handling, response encoding, and version-marker pagination while keeping the changes scoped to listing paths and regression tests.

Constraint: Branch, code, commit, and PR text must avoid restricted upstream project naming.
Constraint: Verification required Rust 1.95 toolchain path because Homebrew cargo-clippy resolved to 1.94.
Rejected: Treat null version-id-marker as no marker | repeats or skips the marker boundary for null-version listings.
Rejected: Compare encoded next-marker values directly | encoded output can diverge from raw listing order.
Confidence: high
Scope-risk: moderate
Directive: Do not change listing marker semantics without covering V1, V2, versions, encoded responses, and null-version markers together.
Tested: cargo fmt --all --check
Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings
Tested: make pre-commit with LC_ALL=en_US.UTF-8 and Rust 1.95 PATH
Tested: git diff --check
Not-tested: live distributed object-store compatibility test against a remote cluster

* fix: keep listing echo fields raw

S3 compatibility tests expect list response echo fields such as Prefix, Delimiter, StartAfter, and Marker to preserve the request value even when URL encoding is requested. Keep URL encoding scoped to object keys and common prefixes while preserving the next-marker raw comparison fix.

Constraint: PR branch was updated with latest main before this fix.

Constraint: Do not use restricted upstream project naming in commit text.

Rejected: Encode all response string fields | breaks compatibility tests for unreadable prefix values.

Confidence: high

Scope-risk: narrow

Tested: cargo test -p rustfs list_objects --lib

Tested: cargo fmt --all --check

Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings

Tested: make pre-commit

Tested: git diff --check

Not-tested: live CI s3 compatibility rerun before push

* fix: accept empty listing continuation token

S3 compatibility tests treat an empty ListObjectsV2 continuation token as an explicit empty echo value, not as an invalid base64 token. Preserve the request echo while skipping decoded-token pagination for the empty string.

Constraint: Keep invalid non-empty continuation tokens rejected before store lookup.

Rejected: Drop empty continuation tokens from the response | compatibility tests assert the empty echo field is present.

Confidence: high

Scope-risk: narrow

Tested: cargo test -p rustfs list_objects --lib

Tested: cargo fmt --all --check

Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings

Tested: make pre-commit

Tested: git diff --check

Not-tested: live CI s3 compatibility rerun after this push

* fix: delete explicit null object versions

S3 compatibility cleanup lists unversioned objects with VersionId=null and then sends that value back through DeleteObjects. The API layer keeps null as an internal sentinel, but the storage delete path must map it back to the stored null version instead of treating it as a real UUID.

Constraint: Keep the wire response able to echo null version IDs.

Rejected: Treat VersionId=null the same as an absent version id everywhere | versioned and suspended buckets need explicit null version semantics.

Confidence: high

Scope-risk: moderate

Tested: cargo test -p rustfs-ecstore delete_file_info_version_id_maps_explicit_null_version_to_stored_null

Tested: cargo test -p rustfs normalize_delete_objects_version_id_preserves_explicit_null_marker --lib

Tested: cargo fmt --all --check

Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings

Tested: make pre-commit

Tested: git diff --check

Not-tested: live CI s3 compatibility rerun after this push

* fix: keep version marker scoped to marker key

Version listing cleanup can delete the key returned as the previous page marker before requesting the next page. When the marker key is no longer present, the version marker must not be applied to the first later key, otherwise each cleanup page can skip one null-version object and leave the bucket non-empty.

Constraint: Preserve version-marker pagination for marker keys that still exist.

Rejected: Drop version markers whenever the marker key was supplied | multi-version marker keys still need intra-key pagination.

Confidence: high

Scope-risk: narrow

Tested: cargo test -p rustfs-ecstore version_marker_is_applied_only_when_key_marker_entry_is_present

Tested: cargo test -p rustfs-ecstore delete_file_info_version_id_maps_explicit_null_version_to_stored_null

Tested: cargo test -p rustfs normalize_delete_objects_version_id_preserves_explicit_null_marker --lib

Tested: cargo fmt --all --check

Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings

Tested: make pre-commit

Tested: git diff --check

Not-tested: live CI s3 compatibility rerun after this push

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-30 11:19:59 +00:00
Henry Guo 8e809f005d fix(scanner): support PBS subfolder alert threshold (#3129)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-30 11:03:13 +00:00
Henry Guo 5bdbd66d09 feat(targets): support Kafka SASL auth (#3128)
* feat(targets): support Kafka SASL auth

* fix(targets): infer Kafka SASL when enable is blank

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-30 11:01:17 +00:00
安正超 97cd19becc fix(sse): handle case-insensitive encryption metadata (#3127)
* fix(sse): handle case-insensitive encryption metadata

* test(ecstore): cover case-insensitive managed material resolution
2026-05-30 02:38:53 +00:00
houseme d5f9467368 ci(build): enable cross-compilation for macOS x86_64 target (#3125)
Set cross=true for the macos-x86_64 build matrix entry so that the
  x86_64-apple-darwin target is cross-compiled on macOS ARM runners
  instead of relying on a native x86_64 macOS runner.
2026-05-29 23:34:47 +08:00
wood 58ac19f7a4 fix(sse): optimize is_encrypted for old metadata compatibility (#3113)
Signed-off-by: w0od <dingboning02@163.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-29 11:59:08 +00:00
安正超 ac97ceb744 fix(config): restore default credential startup (#3114)
* fix(config): restore default credential startup

* fix: align e2e credentials with server env

* fix(config): restore default credential consistency

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-29 11:52:46 +00:00
houseme 5d74637968 fix(utils): tolerate bavail greater than bfree on Linux (#3119)
* fix(utils): tolerate bavail greater than bfree on Linux

Treat f_bavail > f_bfree as a compatibility edge case instead of fatal corruption during startup disk info probing.

- keep corruption checks for other invalid counter relationships
- log a warning and clamp reserved blocks to 0 when bavail exceeds bfree
- add linux unit coverage for the compatibility and overflow paths

Refs #3025

* fix(utils): cap bavail to bfree for Linux statfs edge

* fix(utils): rate-limit Linux statfs compatibility warning

* test(utils): cover Linux statfs capacity math

Expand Linux statfs capacity math tests around normal, equal, zero, and fully-free block counter combinations.

Refs #3025

* fix(utils): avoid statfs warning guard reallocations

* test(utils): align statfs reserved block expectations

* fmt

* fix(utils): avoid warning guard path allocation

Check the warn-once set before allocating a PathBuf so repeated bavail/bfree compatibility warnings stay low-overhead.

Refs #3025
2026-05-29 11:20:47 +00:00
houseme 9f78cd3896 chore(deps): bump workspace dependencies (#3118)
* chore(deps): bump workspace dependencies

- bump direct dependency versions in Cargo.toml\n- refresh Cargo.lock to resolve updated crates

* fix(deps): avoid pyroscope and pprof linker collision

- downgrade pyroscope from 2.0.6 to 2.0.5\n- keep a single pprof implementation path via pprof-pyroscope-fork\n- fix duplicate perf_signal_handler during test linking
2026-05-29 09:36:19 +00:00
houseme 2b82432f9e fix(ecstore): send valid ping body in remote locker (#3112)
* fix(ecstore): send valid ping body in remote locker

Build ping requests with a flatbuffer payload so health checks remain compatible with the ping response parser after restart.

* fix(bench): use multi-host warp target during failover

Normalize comma-separated warp host lists in run_object_batch_bench and let four-node failover bench pass BENCH_WARP_HOSTS so rolling restart does not pin load to a single restarting node.

* feat(health): add compat health probes with busy/KMS checks

  - Add /health/live liveness probe endpoint
  - Add busy protection (429) for readiness probes, gated by RUSTFS_HEALTH_COMPAT_BUSY_CHECK_ENABLE
  - Add KMS readiness check for /health/ready, gated by RUSTFS_HEALTH_COMPAT_KMS_READY_CHECK_ENABLE
  - Add lock quorum status caching with TTL to reduce RPC pressure
  - Consolidate health response building into build_health_response_parts
  - Register /health/live in console router and readiness gate
  - Remove MinIO references from newly added health code

* fix(health): decouple kms readiness from lock quorum
2026-05-29 08:02:50 +00:00
GatewayJ c257043b63 fix(iam): serialize IAM cache writes (#3105)
* fix(iam): serialize IAM cache writes

* fix(iam): timestamp rebuilt group memberships

* fix(iam): publish cache updates atomically

* fix(iam): reuse policy cache snapshots

* fix(iam): commit missing user notification cache updates atomically

* fix(iam): remove unused cache membership rebuild wrapper

---------

Co-authored-by: 季宏伟 <jihongwei@jihongweis-MacBook-Pro.local>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-29 08:02:42 +00:00
houseme ede813070f fix(site-replication): refresh TLS peer client and tls inspect alias (#3109)
* fix(site-replication): refresh peer TLS client cache

- rebuild site-replication peer reqwest client when outbound TLS generation changes\n- keep cached client/failed state per generation for low-overhead requests\n- accept --tls-path as alias of tls inspect --path for operator compatibility\n- add targeted parser/cache tests\n\nRefs #2723

* docs(issue-2723): add private-ca cert rotation retest

- add executable retest checklist for private CA site-replication flows\n- cover tls inspect compatibility (--path and --tls-path)\n- include post-rotation and post-restart pass criteria\n\nRefs #2723

* test(site-replication): isolate global TLS cache test

- serialize generation-sensitive peer TLS cache test\n- snapshot and restore global outbound TLS generation and static cache\n- avoid order-dependent interactions with parallel test execution\n\nRefs #2723

* chore: remove docs file from tracking, keep locally

The docs directory is already in .gitignore but this file was
previously committed. Remove it from git index to prevent docs
content from being included in the PR.

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(site-replication): cover ready client cache hit path

Add a focused unit test for unchanged-generation Ready cache entries in site_replication_peer_client_cache_hit to validate steady-state client reuse behavior highlighted in PR #3109 review feedback.

---------

Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: heihutu <heihutu@gmail.com>
2026-05-29 07:17:55 +00:00
安正超 b436272642 fix: tolerate stalled listing readers after quorum (#3110)
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-29 08:57:57 +08:00
houseme 1de0ba916d fix(ecstore): reduce restart-time startup race noise (#3108)
Treat empty ping bodies as liveness probes, add a startup cleanup barrier for early walk_dir calls, and delay immediate background cleanup/capacity timers to reduce transient restart-time VolumeNotFound noise.

Also downgrade expected missing-path producer results during startup from generic errors to warnings while preserving existing storage semantics.
2026-05-28 15:14:30 +00:00
houseme 088c4bda43 fix(ecstore): harden issue3031 multipart validation path (#3106)
* fix(ecstore): harden issue3031 multipart validation path

- clear stale multipart part destinations before rename fan-out
- add repeated part overwrite regression coverage
- reduce remote disk startup false-fault escalation to suspect-first
- refine remote locker diagnostics and lower scanner leader-lock log noise
- add a dedicated 4-node issue3031 docker validation script

* refactor(admin): inline console version json macro

- drop the unused serde_json::json import in admin console
- call serde_json::json! inline in version_handler
- keep the console version response behavior unchanged

* fix(remote-disk): recover suspect health on probe success

- record probe success during remote disk health checks so suspect drives recover
- use async_with_vars for the remote disk health probe test
- make the missing-listener test assert the state transition more robustly
2026-05-28 14:26:31 +00:00
GatewayJ 8d20e89bf8 fix(iam): avoid stale cache replacement on walk errors (#3094)
* fix(iam): avoid stale cache replacement on walk errors

* fix(iam): guard full reload cache commits

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 季宏伟 <jihongwei@jihongweis-MacBook-Pro.local>
2026-05-28 09:54:06 +00:00
houseme 28bac7fbd6 chore(release): prepare 1.0.0-beta.6 (#3104)
* chore(release): prepare 1.0.0-beta.6

* ci(nix): harden flaky crate fetch handling

* ci(nix): drop magic cache and force fallback

* ci(nix): set explicit user-agent for crate fetch

* ci(nix): adopt determinate nix workflow stack

* ci(nix): add nix user-agent suffix for fetches

* ci(nix): add flakehub cache and align determinate actions

* ci(nix): pin determinate actions to release tags

* ci(nix): disable flakehub auth path in CI cache

* ci(nix): restore stable magic cache baseline

* ci(nix): trust local magic cache substituter

* ci(nix): stop forcing Node24 for JS actions

* ci(nix): drop manual localhost cache config

* ci(nix): adopt latest determinate flakehub stack

* ci(nix): record latest determinate workflow state
2026-05-28 09:21:16 +00:00
cxymds 527faad575 docs(readme): add Discord community links (#3103) 2026-05-28 12:22:17 +08:00
houseme dd1ffbaee7 fix(lock): isolate retry attempt lock ids (#3102)
* fix(lock): isolate retry attempt lock ids

Generate a fresh lock id for each distributed lock retry attempt so late pending cleanup from an earlier attempt cannot release a lock acquired by a later retry.

Also add a regression test covering late-success cleanup versus retried acquisition.

* style(lock): normalize retry race regression test

Normalize the distributed lock retry race regression test formatting without changing behavior.

* test(lock): remove quorum-order assumption

Make the retry race regression test assert the safety property without assuming which delayed client enters the retry quorum first.
2026-05-28 03:56:12 +00:00
GatewayJ 247973f34c fix(lifecycle): make transition worker resize nonblocking (#3090)
Co-authored-by: 季宏伟 <jihongwei@jihongweis-MacBook-Pro.local>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-28 01:36:08 +00:00
安正超 d9c683decc fix: retry namespace lock quorum contention (#3098)
* fix: retry namespace lock quorum contention

* fix(lock): treat remote lock rpc failures as hard

* fix(lock): classify lock contention timeout and avoid hard rollback blocks

* fix(lock): handle namespace lock quorum timeouts

* fix(lock): refine quorum contention handling

* fix(lock): refine unrecoverable quorum handling logs

* fix: address namespace lock quorum review feedback

* test: stabilize batch quorum timing tests

* fix(lock): classify remote quorum failures

* fix: drop remote lock timeout grace period

* fix: satisfy clippy on lock quorum error

* fix: classify remote lock rpc timeouts as hard failures

* fix: fast fail impossible lock quorum attempts

* fix: retry on transient timeout with pending cleanup

* fix: evict remote lock timeout connections

* refactor: avoid duplicate timeout eviction warning

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-28 01:35:00 +00:00
overtrue f77e979e4a fix: satisfy clippy for Snowball traversal test 2026-05-28 09:16:00 +08:00
overtrue 95836a0a4d fix: reject Snowball extract path traversal 2026-05-28 08:32:13 +08:00
Henry Guo 0c52334480 fix(lock): retry transient distributed lock timeouts (#3101)
* fix(lock): retry transient distributed lock timeouts

* fix(lock): avoid retry during pending lock cleanup

* fix(lock): preserve acquire timeout budget

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-27 17:55:46 +00:00
Henry Guo f8e6fc1f10 fix(ecstore): offload erasure encoding from async workers (#3099)
* fix(ecstore): offload erasure encoding from async workers

* fix(ecstore): reuse encode buffers in blocking tasks

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-27 17:54:05 +00:00
houseme 5dfc1b5f07 fix(readiness): gate on lock quorum health (#3100)
* fix(readiness): gate on lock quorum health

Include distributed lock quorum in runtime readiness decisions and expose the signal in health responses.

Map namespace lock quorum failures to ServiceUnavailable instead of InternalError so clients can safely retry while keeping quorum enforcement unchanged.

* fix(ecstore): restore namespace lock error decoding

Add the missing from_u32 arm for NamespaceLockQuorumUnavailable and cover the numeric roundtrip in error tests.
2026-05-27 17:12:52 +00:00
houseme 4648de9e62 chore(deploy): refine systemd and nixos service docs (#3096)
* fix(deploy): simplify systemd service startup

* docs(deploy): add nixos systemd service example

* docs(deploy): add nixos service guide

* chore(deps): update pulsar and pyroscope

* docs(deploy): refine nixos service guidance

* fix(deploy): use explicit rustfs server entrypoint
2026-05-27 14:47:47 +00:00
Derek Ditch 11e97951fd fix(helm): add LoadBalancer service type support (#3049)
The service template only handled ClusterIP and NodePort via if/else-if
branches. When service.type=LoadBalancer was set, no branch matched and
the type field was omitted from the rendered Service, causing Kubernetes
to silently default to ClusterIP.

Add the missing LoadBalancer branch with support for:
- loadBalancerIP: request a specific IP (e.g. Cilium LB-IPAM, MetalLB)
- loadBalancerClass: select a specific LB implementation
- loadBalancerSourceRanges: restrict client source IPs

This is particularly useful for bare-metal / on-prem clusters using
software load balancers (Cilium LB-IPAM, MetalLB, kube-vip) where
a stable routable IP is preferable to an Ingress for S3 clients.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: majinghe <42570491+majinghe@users.noreply.github.com>
2026-05-27 14:35:09 +00:00
安正超 f53eb4ad44 fix: reject invalid multipart part numbers (#3091)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-27 08:09:59 +00:00
houseme ceffe21f75 feat(tls): add inspect command for TLS layouts (#3092) 2026-05-27 07:23:58 +00:00
houseme 909f0d57fb feat: improve degraded readiness reporting and shutdown handling (#3089)
* fix(server): unify runtime readiness and shutdown flow

* refactor(server): decouple readiness shared types

* refactor(server): keep readiness types crate-private

* refactor(server): await protocol shutdown handles

* fix(server): bypass cached startup readiness

* feat(health): expose degraded readiness reasons

* fix(health): preserve raw IAM readiness in degraded reports
2026-05-27 03:25:28 +00:00
houseme 658b8dea66 fix: unify runtime readiness publication and graceful shutdown flow (#3087)
* fix(server): unify runtime readiness and shutdown flow

* refactor(server): decouple readiness shared types

* refactor(server): keep readiness types crate-private

* refactor(server): await protocol shutdown handles

* fix(server): bypass cached startup readiness
2026-05-26 18:49:42 +00:00
Henry Guo 0478505839 fix(heal): restore single disk data during deep heal (#3085)
* fix(heal): restore single disk data during deep heal

* fix(heal): clarify erasure heal step logging

* fix(heal): avoid duplicate deep object heals

* fix(ecstore): preserve volume-not-found walk errors

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-26 17:36:17 +00:00
安正超 0875f09a39 fix: rebuild wiped disks during admin heal (#3084)
* fix: rebuild wiped disks during admin heal

* Preserve forceStart heal admission semantics

* Address heal regression test review comments

* fix(heal): address admin heal review comments

* fix(heal): fix force-start dedup and test polling

* fix(heal): address unresolved review comments

* fix(heal): simplify dedup key for clippy

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-26 21:54:29 +08:00
weisd ea74fa7a95 fix(heal): rebuild parity shards during repair (#3086)
Object repair rebuilt missing data shards but left missing parity shards unrecreated. A parity-only repair could therefore complete without restoring the shard contents.

Add a repair-specific reconstruction path that regenerates parity after data shards are available, while keeping normal read decoding data-only. Also shut down repair writers after all blocks are written.

Constraint: Preserve read-path decode behavior and limit parity regeneration to repair.

Confidence: high

Scope-risk: moderate

Directive: Do not route read decoding through parity regeneration without measuring the cost.

Tested: cargo test -p rustfs-ecstore decode_data_and_parity -- --nocapture

Tested: cargo test -p rustfs-ecstore heal_reconstructs_missing_parity_shard -- --nocapture

Tested: cargo test -p rustfs-ecstore erasure_coding -- --nocapture

Tested: cargo test -p rustfs heal_object_marks_missing_shard_disk_dirty_for_capacity_manager -- --nocapture

Tested: cargo test -p rustfs-heal -- --nocapture

Tested: cargo clippy -p rustfs-ecstore --all-targets -- -D warnings

Tested: cargo fmt --all --check

Tested: make pre-commit

Not-tested: Live multi-node disk replacement outside local test harness
2026-05-26 09:49:25 +00:00
Henry Guo 62d1f80dbf fix(data-usage): refresh admin usage after object changes (#3081)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-25 14:24:00 +00:00
安正超 0d0a17bb36 fix: include deployment ID in admin info (#3083)
* fix: include deployment ID in admin info

* test(ecstore): avoid mutating global deployment id
2026-05-25 14:23:35 +00:00
houseme 3d2449872a refactor(credentials): derive RPC secret fallback and remove IAM keygen duplication (#3079)
* refactor(credentials): derive rpc secret and remove iam keygen

* fix(credentials): reject default access key RPC secret

* test(credentials): align RPC fallback and add keygen coverage
2026-05-25 11:05:58 +00:00
Demo Macro 5f5b1207e0 fix(ecstore): correct is_truncated logic in ListObjectsV2 pagination (#2997) 2026-05-25 14:59:19 +08:00
Sergei Z. 64feca3550 fix(user): service account expiration handling with RFC3339 (#3078)
fix(user): enhance service account expiration handling with RFC3339 support

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-25 04:27:25 +00:00
houseme f1207a9e28 docs(skills): quote release bump description (#3077)
* docs(skills): quote release bump description

* chore(deps): bump lapin and rumqttc-next
2026-05-24 19:59:29 +00:00
weisd f9475e10dc fix(replication): preserve multipart pending state (#3058)
* Preserve multipart replication recovery state

Multipart uploads previously only scheduled replication after completion, leaving no persisted pending state for scanner recovery if the initial async work was lost. Persist the same pending replication metadata during multipart initialization and let completion evaluate the object metadata that was actually stored.

The scanner heal path also treated ordinary pending objects as delete-replication candidates. Restrict that path to delete markers and version purge state so pending objects remain eligible for object replication heal.

Constraint: Bucket replication recovery depends on persisted object metadata after the async queue is unavailable.
Rejected: Rely only on immediate completion-time scheduling | it cannot recover after process restart or worker loss.
Confidence: high
Scope-risk: moderate
Directive: Keep multipart upload initialization aligned with single PUT replication metadata semantics.
Tested: cargo fmt --all --check
Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings
Tested: LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 make pre-commit
Tested: Runtime replication outage check confirmed multipart xl.meta stores PENDING status and timestamp.

* fix(replication): preserve version purge scanner state

Role-derived replication configs need target-scoped status strings before scanner heal can build per-target purge status. The duplicated replication-status assignment left version purge status unset, so scanner recovery could lose the target-level purge state.

Constraint: Scanner heal derives per-target purge decisions from version_purge_status_internal.
Rejected: Leave the duplicate as a harmless cleanup | it changes recovery behavior for role-only configs with version purge state.
Confidence: high
Scope-risk: narrow
Directive: Keep role-derived replication and version purge internal status mapping symmetric.
Tested: cargo fmt --all --check
Tested: cargo test -p rustfs-ecstore heal -- --nocapture

---------

Co-authored-by: wly <wlywly0735@126.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-24 14:24:00 +00:00
GatewayJ b0646be756 feat(s3select): improve SelectObjectContent streaming (#3072)
* feat(s3select): improve SelectObjectContent streaming

* fix(s3select): reject empty select expressions

* fix(s3select): address streaming review feedback

---------

Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-24 14:23:47 +00:00
houseme d74e6eb042 refactor(tls): centralize runtime foundation (#3065)
* refactor(targets): move notify net helpers from utils

* refactor(tls): centralize runtime foundation

* refactor(targets): move notify net helpers from utils

* refactor(tls): centralize runtime foundation

* feat(tls-runtime): add TLS debug state and admin handler

* refactor(tls-runtime): unify TLS debug consumer status view

* fix(tls): address PR3065 review feedback

* refactor(tls): align debug status payload types

* refactor(targets): harden TLS hot reload paths

* fix(targets): resolve review-4348251652 findings

* fix(targets): finalize tls runtime review follow-ups

* fix(targets): harden tls reload and review follow-ups

* fix(targets): align tls reload handling across targets

* fix(targets): finalize tls reload state and metrics updates

* chore(deps): trim unused TLS deps

* style(targets): normalize TLS reload formatting

* refactor(targets): introduce tls runtime adapter path

* chore: update workspace manifests for tls refactor

* fix(tls): stabilize material reload and audit workflow

* fix(targets): refresh tls fingerprint flow across sinks

* fix(tls): align runtime coordinator and http reader updates

* fix(sftp): simplify protocol error mapping

* fix(tls): harmonize material loading behavior

* fix(server): finalize tls material wiring in startup flow

* fix(protos): tighten tls generation cache and deps
2026-05-24 06:41:15 +00:00
安正超 8be787387c test(utils): cover bracketed IPv6 zone host parsing (#3073) 2026-05-24 03:18:56 +00:00
houseme c9377161e4 chore(deps): update flake.lock (#3074)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/d233902' (2026-05-15)
  → 'github:NixOS/nixpkgs/3d8f0f3' (2026-05-23)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/61ec6a4' (2026-05-16)
  → 'github:oxalica/rust-overlay/d9973e2' (2026-05-23)
2026-05-24 03:18:41 +00:00
Henry Guo 522605a055 test(internode): cover adapter metrics validation (#3071)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-23 15:23:05 +00:00
安正超 22bcfc474e perf(ecstore): use direct std writes for local disk (#3069)
* perf(ecstore): use direct std writes for local disk

* fix(ecstore): avoid blocking disk writes in local disk path

* fix(ecstore): use tokio async write in local disk path

* chore(ecstore): remove unused AsyncWriteExt import

* fix(ecstore): preserve write semantics and avoid bytes copy

* fix(ecstore): optimize write_all_internal buffer paths

* fix(ecstore): sync write path in local disk

* fix(ecstore): align sync flag docs and avoid ref copy

* chore(rust): format local write path

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-23 15:22:49 +00:00
Henry Guo a28cb34381 test(internode): cover RemoteDisk adapter routing (#3070)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-23 09:41:25 +00:00
LeonWang0735 cbcfe625ef fix(replication): avoid skipping existing-object backfill for new targets (#2992)
* fix(replication): avoid skipping existing-object backfill for new targets

* optimize(replication): delete redundant line

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-23 04:53:08 +00:00
安正超 02a7d3c228 fix: derive run script CORS console port (#3068) 2026-05-23 04:52:46 +00:00
Henry Guo 53b608c089 docs(internode): keep transport adapter OSS scoped (#3067)
docs(internode): keep transport adapter oss scoped

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-23 13:00:36 +08:00
Henry Guo 2786a4734a docs(internode): align transport adapter scope (#3064)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-22 16:27:27 +00:00
GatewayJ c9f0f25f55 fix: bind run script to localhost (#3063) 2026-05-22 14:28:17 +00:00
houseme 20a4db7c9a fix(tls): resolve RUSTFS_TLS_PATH startup regression (#3059)
* fix(tls): resolve RUSTFS_TLS_PATH startup regression

* fix(tls): adopt PR review follow-ups
2026-05-22 09:09:31 +00:00
houseme 6264be437c fix(storage): add scoped timeout policy and startup fs guardrail (#3056)
* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

* style: fix cargo fmt formatting in disk_store.rs

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

Extend the TimeoutHealthAction introduced in #2996 to read_metadata,
list_dir, and disk_info operations when RUSTFS_NETWORK_MOUNT_MODE=true.
Also raises all drive operation timeouts to 60s (explicit per-operation
overrides still take precedence).

Closes #2790

* feat(startup): add unsupported filesystem policy guardrail

* chore(deps): refresh lockfile and dependency pins

* feat(ecstore): add scoped timeout health-action policy

* docs(config): document drive timeout health-action policy

* refactor(ecstore): cache timeout health policy per disk wrapper

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends (#2838)

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

* style: fix cargo fmt formatting in disk_store.rs

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

Extend the TimeoutHealthAction introduced in #2996 to read_metadata,
list_dir, and disk_info operations when RUSTFS_NETWORK_MOUNT_MODE=true.
Also raises all drive operation timeouts to 60s (explicit per-operation
overrides still take precedence).

Closes #2790

* fix(utils): map verified Linux filesystem magic values (#3051)

* fix(utils): cover sha256 checksum validation (#3052)

* fix(utils): cover sha256 checksum validation

* docs: clarify sha256 checksum validation

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>

* refactor(config): replace network mount mode with timeout profile preset

* fix(review): align fallback defaults and extend fs-type detection

* fix(review): cache timeout profile and restore probe timeout semantics

* refactor(ecstore): cache timeout health policy lookup

* perf(ecstore): cache active probe timeout per monitor task

---------

Co-authored-by: mistik <mistiklord4@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-22 06:37:30 +00:00
weisd 69345fe059 fix(scanner): preserve background heal compatibility (#3041) 2026-05-22 14:38:35 +08:00
Henry Guo b42766f1d3 feat(internode): define transport capabilities (#3047)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-22 02:56:37 +00:00
安正超 3e3fcbf44d fix(utils): cover sha256 checksum validation (#3052)
* fix(utils): cover sha256 checksum validation

* docs: clarify sha256 checksum validation
2026-05-22 02:45:53 +00:00
安正超 0d94437788 fix(utils): map verified Linux filesystem magic values (#3051) 2026-05-22 02:37:50 +00:00
houseme ed2078e025 fix(ecstore): allow expired delete markers on locked buckets (#3048)
* fix: allow expired delete markers on locked buckets

* fix: reject zero-day del marker expiration
2026-05-22 01:39:53 +00:00
Henry Guo 2404bc1657 docs(internode): inventory transport data paths (#3040)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-21 15:40:39 +00:00
Henry Guo 0985f0b37b feat(internode): label transport operation metrics (#3045)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-21 15:20:39 +00:00
houseme e69e32285b fix(ecstore): harden multipart part metadata visibility (#3042)
* fix(ecstore): harden multipart part metadata visibility

* fix(ecstore): address PR3042 review follow-ups
2026-05-21 14:47:52 +00:00
Henry Guo 97858baf8e docs(internode): analyze buffer lifecycle (#3046) 2026-05-21 22:52:39 +08:00
Henry Guo 69dcf9e6cb fix(tooling): harden internode transport benchmark setup (#3037)
* refactor(config): centralize internode transport constants

* fix(bench): guard all ripgrep calls behind dry-run check

Move require_cmd rg and metrics collection inside the non-dry-run
path so that --dry-run works on hosts without rg installed.

* feat(tooling): cross-platform protoc setup for Linux and macOS

Make install-protoc.sh support Linux (x86_64, aarch64) alongside
macOS, and bump CI protoc from 29.3 to 33.1 to match the version
required by the gproto build script.

* fix(bench): record internode baseline error counts

* fix(skill): correct YAML frontmatter formatting for release-version-bump

* chore(ci): bump protoc version to 34.1

* fix(tooling): bump protoc 33.1 to 34.1 in install script, restore SKILL.md description

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-21 05:47:32 +00:00
houseme 503f89bf0e chore(deps): bump aws sdk and config (#3035) 2026-05-21 01:47:34 +00:00
houseme dcbffb084f chore(deps): refresh workspace deps and linux fs_type gating (#3030)
* chore(deps): refresh workspace deps and linux fs_type gating

- refresh workspace dependency pins and lockfile updates

- remove now-unused crate dependency entries in multiple Cargo.toml files

- enable profiling export defaults in config and scripts/run.sh

- gate os::fs_type module/function/tests to Linux to avoid non-Linux dead_code warnings

* fix(utils): simplify fs_type linux gating

- keep fs_type module-level linux cfg in os::mod

- remove redundant linux cfg on get_fs_type and test module

* chore(deps): bump s3s git revision

- update workspace s3s dependency to rev 507e1312b211c3ddc214b03875d6fabd15d22ed5

- refresh Cargo.lock source entry for s3s

* chore(dev): allow mysql_async git source and env overrides

- allow mysql_async git source in deny.toml allow-git list

- make scripts/run.sh core env vars overrideable via existing shell env

* fix(utils): import get_fs_type in fs_type tests

- add explicit super::get_fs_type import in fs_type test module

- fix Linux E0425 unresolved function errors in unit tests

* chore(dev): tune run script observability defaults

- make profiling export env overrideable in scripts/run.sh

- set RUSTFS_OBS_SAMPLE_RATIO default from 2.0 to 1.0

- update allow-git review window comments in deny.toml

* test(obs): stabilize profiling env alias tests
2026-05-20 15:03:07 +00:00
houseme b2dfdf85de chore(release): prepare 1.0.0-beta.4 (#3032)
* chore(release): prepare 1.0.0-beta.4

* docs(skill): refine rustfs spec changelog rule

* docs(skill): optimize rustfs release bump workflow
2026-05-20 13:56:43 +00:00
houseme 375482a4b6 refactor: converge storage io hot paths (#3029)
* refactor(issue-633): clarify layered io control policies

* refactor(issue-633): consolidate timeout and deadlock layers

* refactor(issue-633): align storage backpressure metadata

* refactor(issue-633): unify storage backpressure transitions

* refactor(issue-633): simplify watermark transition API

* test(issue-633): add storage backpressure transition test

* refactor(issue-633): align storage pipe meta shape

* refactor(issue-633): enrich storage monitor metadata

* refactor(issue-633): finalize storage backpressure convergence

* refactor(issue-633): complete scheduler layer convergence

* refactor(issue-633): reduce concurrency facade config duplication

* refactor(issue-633): migrate storage callsites to final policy names

* chore(issue-633): apply final pre-commit normalization

* refactor(issue-633): unify timeout wrapper dynamic size path

* refactor(issue-633): make concurrency policies copyable

* refactor(issue-633): converge storage io hot paths

* fix(issue-633): honor storage timeout min bound

* fix(storage): avoid timeout calc panic on huge sizes

* refactor(storage): consolidate timeout checks and test attrs

* fix(storage): harden io scheduler core config mapping

* refactor(storage): eliminate patch-on-patch patterns and dead code

- Remove trivial accessor methods on ConcurrencyConfig that just return pub fields
- Remove dead BackpressureEvent/BackpressureEventType types from concurrency crate
- Fix io_schedule test using wrong constructor (from_core_config -> from_scheduler_config)
- Update manager.rs to use config fields directly instead of removed accessors

* fix: adopt review feedback for config guards

* test: remove needless struct update defaults

* fix: harden timeout policy and preserve api alias
2026-05-20 12:00:23 +00:00
Henry Guo 45b04316b9 refactor(ecstore): decouple transport construction from RemoteDisk (#3027)
Move build_internode_data_transport_from_env() out of RemoteDisk::new()
into new_disk(), so RemoteDisk depends only on the
Arc<dyn InternodeDataTransport> trait object. TCP/HTTP remains the
default backend; all data paths unchanged.

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-20 11:55:08 +00:00
Henry Guo 19b69abe5c feat(internode): harden p0 transport boundary and baseline tooling (#3017)
* feat(internode): p0 transport baseline and ci hardening

* fix(internode): avoid double wrapping transport errors

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-20 11:06:26 +00:00
安正超 c684438625 fix(obs): add proxied PUT replication metrics (#3020)
fix(obs): add proxied put replication metrics
2026-05-20 05:30:57 +00:00
安正超 11054263c1 fix(utils): map common Linux filesystem magic values (#3023) 2026-05-20 04:00:32 +00:00
安正超 b530a9f0b2 fix(ecstore): remove stale bucket metadata parse TODO (#3021)
* fix(ecstore): remove stale bucket metadata parse TODO

* test: cover stored bucket target config parsing
2026-05-20 03:59:55 +00:00
安正超 dd35538e90 fix(ecstore): remove stale disk TODOs (#3022) 2026-05-20 03:59:34 +00:00
安正超 c727589161 fix(obs): remove stale replication metric TODOs (#3024) 2026-05-20 03:59:16 +00:00
安正超 cde53ca6ad fix(utils): handle IPv6 zones and hex ranges (#3019)
* fix(utils): handle low-risk TODO range parsing

* fix(utils): address scoped IPv6 and range review
2026-05-20 03:56:14 +00:00
Henry Guo e929814edc feat(ecstore): add internode transport boundary and TCP baseline runner (#3010)
* docs: add internode data transport RFC

* feat: add internode operation metrics

* fix feedback

* fix(ci): fallback protoc token to github.token

* feat(ecstore): add internode transport boundary and baseline runner

* feat(internode): harden data transport baseline

* Revert "feat(internode): harden data transport baseline"

This reverts commit 5b8d6b8aa4.

* fix(internode): address baseline review comments

* fix(ci): pin setup-protoc to stable release

* fix(ci): install protoc via apt on linux

* fix(ci): restore protoc install for macos and windows

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-19 22:41:34 +00:00
安正超 a8a5f25af3 perf(ecstore): remove owned write sync regression (#3018) 2026-05-19 22:14:35 +00:00
安正超 54be3cab23 fix(heal): ignore missing response subscribers (#3015)
* fix(heal): ignore missing response subscribers

* fix(heal): ignore missing response subscribers
2026-05-19 15:34:22 +00:00
安正超 1c8fdfddf4 chore(ci): pin RustFS setup-protoc release (#3016)
* chore(ci): use RustFS setup-protoc fork

* chore(ci): pin RustFS setup-protoc release
2026-05-19 15:28:51 +00:00
houseme 73bde843d6 refactor(s3): consolidate semantic boundaries and remove s3-common (#3012)
* refactor(common): introduce rustfs-data-usage core crate

* refactor(concurrency): migrate workers crate into concurrency

* refactor(crypto): migrate appauth token APIs into crypto

* fix docs urls

* remove unused crate

* refactor(data-usage): switch consumers to rustfs-data-usage

* chore(fmt): apply cargo fmt and lockfile sync

* refactor(common): remove data_usage compatibility re-export

* refactor(capacity): move capacity_scope to object-capacity

* refactor(io-metrics): relocate internode metrics from common

* refactor(common): decouple scanner report from madmin

* chore(fmt): normalize import ordering after pre-commit

* refactor(s3): split s3 types and ops crates

* refactor(s3): centralize event version and safe parsing

* refactor(s3): add op-event compatibility guardrails

* refactor(s3): add runtime op-event mismatch observability

* refactor(s3): extract delete event mapping helper

* refactor(s3): extract put event mapping helper

* refactor(s3): consolidate remaining event semantic helpers

* refactor(s3): add op-event coverage checks and observability alerts

* refactor(s3-ops): consolidate op-event semantic mapping

* refactor(scanner): remove last_minute wrapper module

* refactor(scanner): consolidate duplicated data usage models
2026-05-19 12:50:25 +00:00
houseme d6fc70eb12 feat(obs): expose key S3 usecase spans at info (#3013) 2026-05-19 11:00:51 +00:00
houseme 25c6bdf490 perf(filemeta): phase-1~3 rename_data metadata optimization (#3011)
* chore(perf): harden amd64 profiling benchmark flow

* fix(profiling): isolate bench buckets and map protobuf conflict

* perf: avoid blocking owned local writes

* style: format profile admin handler

* docs: clarify observability trace validation

* perf: reduce mkdir overhead on local writes

* perf: add rename_data meta microbenchmark

* perf(filemeta): fast-path data_dir decode in version meta

* perf(filemeta): collapse data-dir lookup into one scan

* perf(filemeta): reduce scan allocs and refresh meta bench

* perf(ecstore): skip mkdir path on read-only open

* perf(filemeta): single-pass unshared data-dir scan

* perf(filemeta): add two-key inline remove fast path

* perf(filemeta): compare remove-two keys by bytes first

* bench(ecstore): add remove_two-only micro benchmark

* bench(ecstore): stabilize rename_data meta benchmark timing

* bench(ecstore): align rename_data path with remove_two

* perf(filemeta): avoid uuid string alloc in remove_two

* perf(filemeta): add fast-path for empty inline data

* perf(filemeta): streamline add_version match branch

* perf(filemeta): fast-return remove_key on miss

* perf(filemeta): speed up add_version insertion lookup

* style(ecstore): normalize formatting in perf-tuning files

* refactor(filemeta): unify inline data removal paths
2026-05-19 10:20:24 +00:00
Henry Guo f695870626 feat(internode): add transport observability (#3007)
* docs: add internode data transport RFC

* feat: add internode operation metrics

* fix feedback

* fix(ci): fallback protoc token to github.token

---------

Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-19 07:16:05 +00:00
安正超 aa3f13c0d3 fix(ecstore): stop listing after reaching result limit (#3001)
* fix(ecstore): stop listing after reaching result limit

* fix(ecstore): ignore intentional list cancellation

* fix(ecstore): satisfy list cancellation clippy lint

* fix(ecstore): include entry before limit early return

* fix(ecstore): cancel on limit in gather_results

* fix(ecstore): sync timeout branch with reviewed patch

* fix(ecstore): satisfy cancellation clippy lint

---------

Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-19 05:20:21 +00:00
houseme c4248078d6 chore: update dependencies (#2890)
* chore: update dependencies

* build(deps): bump the dependencies group with 5 updates

* build(deps): bump the dependencies group with 6 updates (#2908)

Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>

* fix(ecstore): narrow Windows URL drive path rewrite

* chore(deps): bump starshard to 1.2.0

* revert(ecstore): restore endpoint Windows path behavior

* up

* up

* up

* perf(notify): use cached snapshot mode for scans

* fix

* chore(deps): bump workspace dependency versions

* chore(deps): refresh pinned dependency references

* chore(ci): align profiling and decommission tooling updates

- enable tokio_unstable cfg in performance profiling build flags\n- bump Rust base image from 1.93 to 1.95 in source and decommission Dockerfiles\n- remove obsolete compose version key from docker-compose-simple.yml\n- add standard Apache-2.0 license header to docker-compose.decommission.yml

* chore(deps): bump the dependencies group across 1 directory with 7 updates (#2994)

Bumps the dependencies group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [sysinfo](https://github.com/GuillaumeGomez/sysinfo) | `0.39.1` | `0.39.2` |
| [dial9-tokio-telemetry](https://github.com/dial9-rs/dial9-tokio-telemetry) | `0.3.9` | `0.3.10` |
| [opentelemetry](https://github.com/open-telemetry/opentelemetry-rust) | `0.31.0` | `0.32.0` |
| [opentelemetry-otlp](https://github.com/open-telemetry/opentelemetry-rust) | `0.31.1` | `0.32.0` |
| [opentelemetry_sdk](https://github.com/open-telemetry/opentelemetry-rust) | `0.31.0` | `0.32.0` |
| [opentelemetry-semantic-conventions](https://github.com/open-telemetry/opentelemetry-rust) | `0.31.0` | `0.32.0` |
| [opentelemetry-stdout](https://github.com/open-telemetry/opentelemetry-rust) | `0.31.0` | `0.32.0` |



Updates `sysinfo` from 0.39.1 to 0.39.2
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/main/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.39.1...v0.39.2)

Updates `dial9-tokio-telemetry` from 0.3.9 to 0.3.10
- [Release notes](https://github.com/dial9-rs/dial9-tokio-telemetry/releases)
- [Changelog](https://github.com/dial9-rs/dial9/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dial9-rs/dial9-tokio-telemetry/compare/dial9-tokio-telemetry-v0.3.9...dial9-tokio-telemetry-v0.3.10)

Updates `opentelemetry` from 0.31.0 to 0.32.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/release_0.32.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/opentelemetry-prometheus-0.31.0...opentelemetry-0.32.0)

Updates `opentelemetry-otlp` from 0.31.1 to 0.32.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/release_0.32.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/opentelemetry-otlp-0.31.1...opentelemetry-otlp-0.32.0)

Updates `opentelemetry_sdk` from 0.31.0 to 0.32.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/release_0.32.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.31.0...opentelemetry_sdk-0.32.0)

Updates `opentelemetry-semantic-conventions` from 0.31.0 to 0.32.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/release_0.32.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.31.0...opentelemetry-semantic-conventions-0.32.0)

Updates `opentelemetry-stdout` from 0.31.0 to 0.32.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/release_0.32.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/v0.31.0...opentelemetry-stdout-0.32.0)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-version: 0.39.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: dial9-tokio-telemetry
  dependency-version: 0.3.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dependencies
- dependency-name: opentelemetry
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependencies
- dependency-name: opentelemetry-otlp
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependencies
- dependency-name: opentelemetry_sdk
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependencies
- dependency-name: opentelemetry-semantic-conventions
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependencies
- dependency-name: opentelemetry-stdout
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>

* chore(deps): bump workspace dependency versions

* chore(deps): refresh lockfile windows crate graph

* chore(bench): align snapshot mode labels with coverage

* chore(bench): clarify tested snapshot modes

* fix(review): address PR2890 Copilot comments

---------

Signed-off-by: houseme <housemecn@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-19 04:58:42 +00:00
Henry Guo 94da6e34cb fix(notify): parse IPv6 hosts without ports (#3000)
Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com>
2026-05-19 02:40:21 +00:00
安正超 a9e62dc2c2 perf: avoid blocking hop for owned disk writes (#3004)
* perf: avoid blocking hop for owned disk writes

* fix(ecstore): sync owned write path
2026-05-19 02:27:14 +00:00
yihong ecb6704679 fix: make help color not right (#3005)
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
2026-05-19 02:26:23 +00:00
安正超 e65aebba53 fix(ecstore): guard transition worker max (#3003)
* fix(ecstore): guard transition worker max

* test: clarify env mutation safety

* test(ecstore): document env mutation concurrency contract
2026-05-19 02:25:51 +00:00
weisd a66337bd28 fix: keep scanner walk timeouts from offlining drives (#2996)
* fix: keep scanner walk timeouts from offlining drives

Scanner walk operations can time out on large or slow directory listings without proving the backing drive is faulty. Keep the timeout error local to the scan while preserving failure marking for ordinary disk operations.

Constraint: Scanner walk_dir can include listing work that exceeds the drive timeout under slow storage.

Rejected: Disable timeout failure marking globally | real stuck disk operations must still affect drive health

Confidence: high

Scope-risk: narrow

Directive: Do not route scanner/listing timeout back into drive offline state without reproducing issue #2651

Tested: cargo test -p rustfs-ecstore timeout

Tested: cargo fmt --all --check

Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings

Tested: make pre-commit with en_US.UTF-8 locale

Related: https://github.com/rustfs/rustfs/issues/2651

* fix: keep remote scanner walks from offlining drives

Remote walk_dir uses a streaming request that can hit total or stall timeouts during large scans without proving the remote drive is faulty. Route that scanner path through an explicit ignore action while preserving failure marking for ordinary remote operations.

Also treat Duration::ZERO as no operation timeout for remote health tracking, matching the existing local disk wrapper contract while still marking network-like errors on default paths.

Constraint: Scanner walk_dir streams can be slow because of directory size or consumer backpressure.

Rejected: Disable remote timeout failure marking globally | normal remote disk operations still need to evict failed connections and update drive health

Confidence: high

Scope-risk: narrow

Directive: Do not reintroduce remote scanner timeout failure marking without reproducing issue #2651 against remote disks.

Tested: cargo test -p rustfs-ecstore execute_with_timeout

Tested: cargo test -p rustfs-ecstore timeout

Tested: cargo fmt --all --check

Tested: cargo clippy --workspace --all-features --all-targets -- -D warnings

Tested: make pre-commit with en_US.UTF-8 locale

Related: https://github.com/rustfs/rustfs/issues/2651

* test: prove scanner walk backpressure keeps drives online

Add a local scanner walk regression test where the output writer never makes progress. The test confirms the walk timeout returns without marking the drive faulty, covering a stall source that is not caused by object count or network transfer speed.

Constraint: Scanner walk can block on downstream writer backpressure as well as disk or network IO.\nConfidence: high\nScope-risk: narrow\nTested: cargo test -p rustfs-ecstore walk_dir_writer_backpressure_timeout_does_not_mark_drive_failure\nTested: cargo test -p rustfs-ecstore timeout\nTested: cargo fmt --all --check\nTested: cargo clippy --workspace --all-features --all-targets -- -D warnings\nTested: make pre-commit

---------

Co-authored-by: houseme <housemecn@gmail.com>
2026-05-18 15:04:05 +00:00
houseme bd1e57293f fix: harden lifecycle transition compensation and regression coverage (#2995)
* fix(ecstore): honor transition worker configuration

* fix(ecstore): add transition queue backpressure metrics

* fix(ecstore): schedule transition compensation on enqueue pressure

* fix(ecstore): log transition compensation scheduling

* test(rustfs): add transition compensation fault-injection coverage

* test(rustfs): cover delete after transition compensation

* test(scanner): cover cleanup after transition compensation

* test(rustfs): extend compensation transition coverage

* test(scanner): cover backfill idempotency after compensation

* test(scanner): cover noncurrent expiry after compensation

* test(rustfs): cover versioned delete after compensation

* test(rustfs): cover delete marker lifecycle after compensation

* test(scanner): extend versioned lifecycle compensation coverage

* test(scanner): model versioned delete after compensation

* test(scanner): clarify modeled versioned delete helper

* refactor(ecstore): optimize transition enqueue hot path

* refactor(ecstore): centralize transition runtime constants

* style(ecstore): apply rustfmt for transition timeout helper

* fix(ilm): align queue-full metric semantics

* refactor(ecstore): unify immediate enqueue failure handling

* refactor(ecstore): reuse transition worker env constant

* ci(actions): update setup action inputs
2026-05-18 12:50:43 +00:00
wood 0e888cfef2 fix(ecstore): list_object_v2 error when scanning multipart folder (#2946)
* fix(ecstore): list_object_v2 error when scanning multipart directory with prefix

Signed-off-by: w0od <dingboning02@163.com>

* test(ecstore): cover prefix dir scan with multipart folder support

Signed-off-by: w0od <dingboning02@163.com>

* test(ecstore): harden test shape to improve regression detection

Signed-off-by: w0od <dingboning02@163.com>

* refactor(ecstore): move multipart dir filter into recursion to reduce I/O

Signed-off-by: w0od <dingboning02@163.com>

* test(ecstore): replace scan_dir test with walk_dir integration coverage

Signed-off-by: w0od <dingboning02@163.com>

* refactor(ecstore): remove unnecessary .clone() calls

Signed-off-by: w0od <dingboning02@163.com>

---------

Signed-off-by: w0od <dingboning02@163.com>
2026-05-18 11:01:20 +00:00
安正超 4c9fd789ea docs: update security advisory skill lessons (#2991) 2026-05-18 12:34:27 +08:00
Henry Guo 17ae9f34c7 fix(notify): accept case-insensitive filter rule names (#2990) 2026-05-18 03:41:48 +00:00
houseme cdfe83877b chore(deps): update flake.lock (#2986)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/b3da656' (2026-05-08)
  → 'github:NixOS/nixpkgs/d233902' (2026-05-15)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/1d634a9' (2026-05-09)
  → 'github:oxalica/rust-overlay/61ec6a4' (2026-05-16)
2026-05-17 03:09:08 +00:00
安正超 be37fd285e ci(nix): avoid requesting review from PR author (#2987) 2026-05-17 11:13:45 +08:00
安正超 095337100e test(policy): validate default policies (#2985) 2026-05-17 01:41:03 +00:00
GatewayJ e0729f5f4d fix(policy): align action-family validation and defaults (#2984)
* fix(policy): align action-family validation and defaults

* test(e2e): add accountinfo service-account roundtrip

* test(policy): add mixed action family cases
2026-05-16 11:19:04 +00:00
houseme 6e12289339 fix(runtime): finalize issue 2941 profiling cleanup (#2983)
* perf(runtime): narrow profiling support and upgrade starshard

* style(notify): normalize starshard imports

* perf(ecstore): reduce list_path_raw coordination overhead

* docs(scripts): add issue 2941 perf capture workflow

* fix(runtime): finalize issue 2941 profiling cleanup

* build(deps): bump quick-xml to 0.40.0

* chore(scripts): untrack local perf capture guide

* fix(scripts): honor label in perf capture output
2026-05-16 11:09:04 +00:00
weisd 9dcf8cb7ea fix: stabilize rebalance start and listing (#2961)
* fix: unblock empty chunked admin rebalance start

Some admin clients send empty POST requests with chunked framing and no explicit content length. The admin rebalance route carries no request body, so normalize that known route before downstream validation sees a missing length.

Constraint: Keep normalization scoped to known empty-body admin routes

Rejected: Relax transfer-encoding handling for S3 routes | broader protocol risk

Confidence: high

Scope-risk: narrow

Tested: cargo test -p rustfs --lib server::layer::tests::

Tested: cargo fmt --all --check

Tested: git diff --check

* fix: keep rebalance listing from stalling

Large object migration was awaited inside the listing callback, so metacache readers could stop being drained while drive walk operations were still writing listing data. Move entry migration into bounded background tasks and wait for them after each set listing completes.

Constraint: Preserve existing rebalance cancellation and first-error propagation

Rejected: Only increase drive walk timeouts | masks callback backpressure and still fails for slower migrations

Confidence: medium

Scope-risk: moderate

Tested: cargo test -p rustfs-ecstore rebalance_unit_tests

Tested: cargo test -p rustfs-ecstore cache_value::metacache_set::tests

Tested: cargo test -p rustfs --lib server::layer::tests::

Tested: cargo clippy -p rustfs-ecstore --lib --all-features -- -D warnings

Tested: cargo clippy -p rustfs-ecstore --tests --all-features -- -D warnings

Tested: cargo fmt --all --check

Tested: git diff --check

---------

Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-16 05:17:01 +00:00
安正超 6898e720dd fix(security): harden proxy auth and default credentials (#2981)
* fix(security): harden proxy auth and default credentials

* fix(security): address proxy and credential feedback
2026-05-16 04:01:50 +00:00
yihong 824c4f7673 docs: fix some dead links (#2975)
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-16 10:06:53 +08:00
安正超 c0c92cb048 ci(build): honor console asset download fallback (#2980) 2026-05-16 10:06:23 +08:00
Henry Guo bca8b08c2b fix: handle Windows paths in pre-commit tests (#2974)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-15 14:04:51 +00:00
Henry Guo 738fb86611 fix(admin): surface access key policy errors (#2970)
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-15 08:26:38 +00:00
安正超 092c6bc135 ci(build): pin macOS x86 release runner (#2971) 2026-05-15 11:56:13 +08:00
escapecode 5cda460451 fix(sftp): preserve OPEN-time client attrs as object metadata (#2913)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-15 00:32:06 +00:00
GatewayJ fc8322ed64 bucket policy notify & pba (#2968) 2026-05-14 14:31:40 +00:00
houseme cd2cd74314 ci: force Node24 in Nix workflows with pinned actions (#2966) 2026-05-14 11:13:42 +00:00
Henry Guo ced390b02b test(ecstore): cover managed sse range reads (#2962)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-14 06:56:21 +00:00
Henry Guo 11be278c40 test(notify): cover prefix suffix event dispatch (#2960) 2026-05-14 05:38:38 +00:00
Henry Guo 0893c05540 fix(ecstore): use hex sha256 for delete objects (#2958)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-14 05:34:26 +00:00
houseme 81754d80b3 refactor(targets): unify queue/connectivity handling and coverage (#2953)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: marshawcoco <marshawcoco@gmail.com>
2026-05-14 04:31:23 +00:00
houseme bdb98598d2 chore(release): prepare 1.0.0-beta.3 (#2957) 2026-05-14 04:27:26 +00:00
安正超 5fccfceb91 test(ecstore): cover walk listing error success paths (#2954) 2026-05-14 01:03:31 +00:00
Henry Guo c1bcee327c fix(notify): keep live listen events active when disabled (#2952)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-13 17:10:14 +00:00
Henry Guo d4d07095f8 fix(ecstore): propagate walk listing errors (#2949)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-13 16:59:49 +00:00
Sergei Z. 8c49671161 Fix #2775 recursive list handling in LocalDisk::scan_dir() (#2923)
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-13 14:36:23 +00:00
cxymds c65d4071a3 fix(ecstore): map missing metadata to not found (#2944)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-13 13:03:15 +00:00
weisd 4ea805cbc0 fix: preserve pagination when max keys exceed limit (#2943)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-13 12:48:38 +00:00
Henry Guo 26341742e0 fix(ecstore): fail listing on walk_dir producer errors (#2937) 2026-05-13 11:08:05 +00:00
安正超 fd3fb77ed5 fix(sftp): avoid metadata on multipart copy (#2935)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-13 07:44:09 +00:00
weisd c5125b20bd fix(storage): keep storage info RPC map encoded (#2942)
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-13 06:19:43 +00:00
weisd 9b83de53e1 fix(ecstore): surface prefix listing storage errors (#2940) 2026-05-13 04:24:54 +00:00
houseme d72b8cb88e fix(server): fail fast when configured TLS parsing fails (#2939) 2026-05-13 04:16:39 +00:00
Henry Guo 17eff5a97b fix(tls): ignore Kubernetes secret projection dirs (#2938) 2026-05-13 03:45:42 +00:00
houseme 129cb0f920 fix: make HeadObject consistent after write completion (#2936) 2026-05-13 03:27:38 +00:00
安正超 28c9358482 test(notify): cover encoded key target union (#2934)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-12 15:20:06 +00:00
Henry Guo fe5690ca70 fix(sftp): preserve open attrs metadata (#2929)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-12 13:34:52 +00:00
安正超 25cf164461 test(protocols): cover SFTP host key reload failure (#2928)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-12 10:40:23 +00:00
安正超 8a501846f4 test(iam): cover mixed STS claim policy names (#2932) 2026-05-12 10:39:53 +00:00
GatewayJ b2ba2e5bb3 iam: handle sts claim policy names (#2902)
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-12 07:10:42 +00:00
安正超 8169bd63e9 test(protocols): cover TLS reload fingerprint ordering (#2927) 2026-05-12 02:50:49 +00:00
houseme 6185d38911 fix(protocols): add hot reload for WebDAV FTPS and SFTP (#2922)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-11 16:25:20 +00:00
cxymds 8ef2c0a5e1 fix(storage): sync transition tier config across peers (#2918)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-11 15:23:29 +00:00
Henry Guo d6813b53a2 fix(ecstore): preserve list marker set index (#2919)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-11 15:20:43 +00:00
Henry Guo 5ca99a8e32 fix(ecstore): fail listing on stalled reader (#2920)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-11 15:19:46 +00:00
Henry Guo 07a01a1123 fix(notify): match filters against decoded event keys (#2921)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-11 15:01:37 +00:00
Henry Guo 941986b331 test(e2e): gate protocol runner by requested features (#2912) 2026-05-11 13:59:24 +00:00
Henry Guo aba65a448c fix(protocols): encode storage client request URIs (#2911) 2026-05-11 10:47:30 +00:00
Henry Guo 1c94f7a066 fix(sftp): classify backend errors by type (#2909) 2026-05-11 02:56:08 +00:00
安正超 77c21dc5fd test(obs): cover replication bandwidth tombstones (#2906) 2026-05-11 01:55:54 +00:00
安正超 404fc816e9 test(targets): cover MySQL probe validation (#2907) 2026-05-11 01:55:48 +00:00
LeonWang0735 61a7820651 optimize(obs):zero and expire removed replication bandwidth series (#2901)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-10 14:26:18 +00:00
安正超 0375dd39bb fix(targets): handle MySQL DSN scheme case (#2903)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-10 14:25:22 +00:00
安正超 f84d8983ed test(s3): promote passing SSE multipart cases (#2900) 2026-05-10 13:16:23 +00:00
JaySon bafff6d207 feat(targets): add check_mysql_server_available probe function (#2884)
Signed-off-by: JaySon-Huang <tshent@qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-10 11:53:45 +00:00
houseme 53ec1b95d8 keep sftp e2e tests buildable (#2897) 2026-05-10 11:52:15 +00:00
安正超 65c62ab2ea test(sftp): cover init session activity stamp (#2898) 2026-05-10 09:57:04 +00:00
安正超 c419cff348 test(sftp): cover init negotiation and platform gating (#2896) 2026-05-10 05:59:10 +00:00
houseme 5a5ffcfac0 chore(deps): update flake.lock (#2894) 2026-05-10 03:48:57 +00:00
escapecode 96b293bf8a feat(sftp): add SFTPv3 protocol support (#2875)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-10 03:48:42 +00:00
houseme 8892cbbdd7 feat: enhance WebDAV support with features and directory operations (#2856) (#2892)
Signed-off-by: giter <giter@users.noreply.github.com>
Signed-off-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: giter <giter@users.noreply.github.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Lijiajie <lijiajie@ffcode.net>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 唐小鸭 <tangtang1251@qq.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
2026-05-09 16:45:09 +00:00
唐小鸭 09c2d15057 fix(sse): Temporarily refactored the SSE design for ECStore (#2813)
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-09 14:06:35 +00:00
Sergei Z. 6275918d92 fix: empty-body requests without content length (#2888)
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-09 14:05:53 +00:00
Henry Guo 5879d0b59d fix(server): handle public health before s3 host parsing (#2866)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-09 13:15:37 +00:00
JaySon cb54cecf8c docs(targets): sync AGENTS.md and test doc comments with code (#2881)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-09 03:06:11 +00:00
安正超 9288f1ed3a fix(targets): handle postgres dsn redaction scheme case (#2886) 2026-05-09 01:58:23 +00:00
安正超 8182e2dd38 test(ecstore): cover empty runtime listing candidates (#2889) 2026-05-09 01:57:54 +00:00
houseme 81ad48dac2 feat(targets): add AMQP support for notify and audit (#2879)
Co-authored-by: Hyesook Yun <74169420+suk13574@users.noreply.github.com>
2026-05-09 01:56:26 +00:00
安正超 1582f216fe test(ecstore): cover offline capacity snapshots (#2880) 2026-05-08 16:33:34 +00:00
安正超 5081b8396d test(ecstore): cover system path failure classifier (#2874) 2026-05-08 14:51:11 +00:00
houseme c90bfe2b23 fix(ecstore): harden runtime read-path quorum handling (#2872) 2026-05-08 09:56:39 +00:00
Henry Guo 03045ff2e6 fix(iam): keep error state on initial load failure (#2846)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-08 08:26:01 +00:00
安正超 61bd5698bb test(admin): cover pools list response serialization (#2862)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-08 07:32:49 +00:00
安正超 44d2b6a284 [codex] docs: ban rust-refactor-helper skill (#2869) 2026-05-08 15:33:45 +08:00
安正超 fe978488a0 test(admin): cover pool used-size saturation (#2863) 2026-05-08 06:06:54 +00:00
安正超 e209fc6eef test(ecstore): cover store init health reset delegation (#2865) 2026-05-08 05:42:05 +00:00
weisd 97a2775434 fix(ecstore): reset drive health between store init format retries (#2848)
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-08 03:16:58 +00:00
cxymds 9d85b7d23a feat: enrich admin pools list response (#2853)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-07 13:53:25 +00:00
安正超 ba7ee5fbdd docs(security): make advisory skill lesson first (#2860)
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-07 21:53:26 +08:00
安正超 6896b38dc2 test(s3): promote lifecycle expiration header tests (#2858) 2026-05-07 13:08:42 +00:00
安正超 9df57b8cd8 test(targets): cover Redis env config loading (#2857) 2026-05-07 13:08:11 +00:00
安正超 6ec3a4c4d5 docs(security): refresh advisory lesson states (#2859) 2026-05-07 21:21:15 +08:00
houseme fd37a7d01e fix(targets): probe webhook health by host port (#2854) 2026-05-07 11:52:45 +00:00
Henry Guo 9c0141fbdf docs(io-metrics): fix misleading metrics links (#2849) 2026-05-07 20:00:19 +08:00
cxymds 2a0fbb8d77 fix(ecstore): repair decommission pool quorum (#2847)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-07 11:14:11 +00:00
houseme 5431b9273d feat(targets): complete redis mysql postgres target wiring (#2842)
Signed-off-by: jaehanbyun <awbrg789@naver.com>
Signed-off-by: houseme <housemecn@gmail.com>
Signed-off-by: Gunther Xing <jiengup@gmail.com>
Signed-off-by: JaySon-Huang <tshent@qq.com>
Co-authored-by: jaehanbyun <awbrg789@naver.com>
Co-authored-by: Gunther Xing <jiengup@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: JaySon <tshent@qq.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-07 10:00:59 +00:00
安正超 b159d656cc test(admin): cover POST content length compat layer (#2844)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-07 09:51:09 +00:00
cxymds b7815b0694 fix(ecstore): remove startup order sensitivity (#2850) 2026-05-07 09:07:25 +00:00
houseme eaa5ff3053 bump workspace versions and replace cfg-if in crypto (#2851) 2026-05-07 09:04:27 +00:00
houseme f1cd7c1345 feat(rustfs): add ftps/webdav defaults to info output (#2845) 2026-05-07 04:39:56 +00:00
cxymds 06097a3c33 fix(admin): normalize empty admin POST content length (#2843) 2026-05-07 02:52:21 +00:00
安正超 5e0ca006f0 test(replication): cover ETag comparison edge cases (#2840) 2026-05-07 02:48:12 +00:00
安正超 abc07a9dc4 fix(build): quote build script features argument (#2841) 2026-05-07 02:47:53 +00:00
Nikita Bakun 4d6171e996 fix(replication): handle version ID format mismatch with AWS S3 (#2829)
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-06 21:30:07 +00:00
GatewayJ 3130670157 test(object-lock): cover default retention delete marker (#2836) 2026-05-06 21:14:00 +00:00
安正超 96d41b6349 test(helm): cover standalone scale-to-zero rendering (#2831) 2026-05-06 21:13:40 +00:00
安正超 9b9e0db9f6 test(lifecycle): cover ILM process time aliases (#2839) 2026-05-06 21:12:22 +00:00
安正超 68fcbffcb6 test(build): cover build script feature flags (#2837) 2026-05-07 05:18:51 +08:00
giter 04712fb6c6 feat: add features option to build script (#2834)
Signed-off-by: giter <giter@users.noreply.github.com>
Signed-off-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-06 15:09:44 +00:00
安正超 4728abcff1 fix(security): document unsafe and TLS overrides (#2835) 2026-05-06 15:09:02 +00:00
cxymds 70be0804ee fix: 2827 lifecycle days next midnight (#2833)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-06 15:00:53 +00:00
安正超 b10db403b6 test(s3): promote passing copy metadata case (#2832) 2026-05-06 13:58:32 +00:00
GatewayJ 9e93d3f47a fix(object-lock): materialize default retention metadata (#2824)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-06 13:48:12 +00:00
cxymds 9f07029373 fix: reload bucket metadata after lifecycle updates (#2822)
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-06 13:01:44 +00:00
Henry Guo 4b36667ba1 fix(policy): avoid logging generated access keys (#2826)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-06 12:56:27 +00:00
houseme 41ba34a145 fix(rpc): add issue 2815 regression and docker validation (#2828) 2026-05-06 12:22:13 +00:00
Michael Graff 3898d524fe security: same-origin console CORS, fail-closed helm creds, deny.toml, sample-config hardening (#2769)
Signed-off-by: Michael Graff <explorer@flame.org>
Signed-off-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-06 07:34:44 +00:00
Duru Can Celasun 718bec7722 feat(helm-chart): support scale to 0 in standalone mode (#2797)
Signed-off-by: Duru Can Celasun <can@dcc.im>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-06 06:42:56 +00:00
安正超 50ddec3ffc fix: preserve data on self metadata copy (#2819)
Co-authored-by: cxymds <Cxymds@qq.com>
2026-05-06 06:28:36 +00:00
Henry Guo 7692c0c3bd test(credentials): avoid printing default secret (#2820)
Signed-off-by: houseme <housemecn@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-06 05:19:42 +00:00
houseme 3dd0692917 refactor: unify credential env constants and deploy env usage (#2821)
Co-authored-by: Henry Guo <marshawcoco@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-06 04:30:29 +00:00
GatewayJ 090d60e00a fix(auth): authorize DeleteObjects per key (#2814) 2026-05-06 00:47:36 +00:00
安正超 c228fabfdb test(admin): cover orphan resync status cleanup (#2816) 2026-05-06 00:47:11 +00:00
安正超 bde569d055 test(getobject): cover buffer threshold edge cases (#2817) 2026-05-06 00:47:02 +00:00
houseme e19b0dd997 fix(notify): improve webhook target diagnostics for env setup (#2810) 2026-05-06 00:28:46 +00:00
houseme 26b3aad069 build(deps): bump the dependencies group with 3 updates (#2812) 2026-05-05 14:05:13 +00:00
安正超 bf893bcb55 fix(admin): clear removed site resync status (#2803) 2026-05-05 13:42:12 +00:00
安正超 60d4598562 test(lock): cover shared waiter abort cleanup (#2811) 2026-05-05 13:41:15 +00:00
houseme 565cbdffed fix(getobject): prevent large-download memory buffering (#2809) 2026-05-05 12:58:26 +00:00
houseme 49b2782d51 fix(lock): make slow-path waiter accounting cancellation-safe (#2805) 2026-05-05 11:19:23 +00:00
houseme 743b87014b fix(notify): validate bucket notification filter rules (#2806) 2026-05-05 10:35:36 +00:00
安正超 3208f930c5 fix(s3): advertise byte ranges on head object (#2802) 2026-05-05 04:29:25 +00:00
安正超 36b3d21c44 test: cover RPC secret trimming fallback (#2796)
Signed-off-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-05 04:26:36 +00:00
cxymds 935bb8a8e2 fix(admin): allow site replication removal with offline peers (#2739)
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-05 03:54:32 +00:00
安正超 9364ecba67 docs: add security advisory lessons skill (#2801)
Signed-off-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-05 11:59:13 +08:00
Henry Guo 0153710791 fix(server): avoid logging default credential values (#2800)
Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
2026-05-05 03:44:07 +00:00
Alexander Kharkevich 995e26f5ee fix(rpc): use map-encoded msgpack for all internode RPC responses (#2771)
Co-authored-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-05 03:42:29 +00:00
JaySon e6fdcd1ad6 fix: Fix formatting issue in ARCHITECTURE.md (#2787)
Signed-off-by: JaySon-Huang <tshent@qq.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-05-05 11:33:55 +08:00
安正超 f016673416 test: cover license verifier success path (#2798) 2026-05-05 02:55:56 +00:00
安正超 42ff6b4d80 test(admin): cover legacy profile auth guards (#2799) 2026-05-05 02:55:45 +00:00
安正超 3ced40f221 test(license): cover public key validation (#2793) 2026-05-04 15:12:59 +00:00
dependabot[bot] a830ab2a5d build(deps): bump the dependencies group with 4 updates (#2785)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 10:26:51 +00:00
Joey Shi 52f121ab2a Ensure CreationDate is always present in ListBuckets response (#2783) 2026-05-04 08:20:30 +00:00
安正超 eb8868397e fix: address security review follow-ups (#2781) 2026-05-03 13:53:36 +00:00
安正超 66c38b629d Harden admin and RPC security checks (#2773)
Signed-off-by: 安正超 <anzhengchao@gmail.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-03 11:55:09 +00:00
安正超 eb23710d2e fix(security): harden CORS and license handling (#2774) 2026-05-03 11:39:27 +00:00
houseme 4b66155f26 chore(deps): update flake.lock (#2777) 2026-05-03 02:44:53 +00:00
Ramakrishna Chilaka 4978f60254 test(ecstore): cover ranged decode and harden offset bounds (#2758)
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-02 09:02:50 +00:00
安正超 fe59eb4952 test(admin): cover site identity merge gaps (#2767) 2026-05-02 08:43:47 +00:00
安正超 a455b4377c fix: handle empty multipart list-parts (#2765)
Co-authored-by: loverustfs <hello@rustfs.com>
2026-05-02 02:47:52 +00:00
安正超 08fab34804 test(admin): cover peer identity scheme dedupe (#2764) 2026-05-01 04:11:46 +00:00
安正超 f07fed0c49 test(filemeta): cover no-wait refresh coalescing (#2755) 2026-05-01 02:48:30 +00:00
安正超 dcfbc2612a test(admin): cover site replication scheme normalization (#2757) 2026-05-01 02:48:20 +00:00
安正超 87e1c7aeb6 test(filemeta): cover future cache timestamp refresh (#2762) 2026-05-01 02:48:08 +00:00
安正超 0d7e0a814f test(ci): cover Helm Recreate strategy rendering (#2752) 2026-04-30 12:14:38 +00:00
安正超 ef5ccc232a test(filemeta): cover shared cache reuse (#2754) 2026-04-30 12:14:15 +00:00
GatewayJ c29c8a5a1e fix(filemeta): harden and optimize metacache path (#2724)
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-30 07:47:31 +00:00
安正超 403997f2f8 test(ci): cover Helm chart version mapping (#2751) 2026-04-30 06:11:14 +00:00
安正超 e34d75dfdf test(admin): cover site replication absolute URI endpoint (#2745)
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-30 06:11:00 +00:00
安正超 132e72ef39 fix(ci): keep Helm chart versions valid for non-beta releases (#2749) 2026-04-30 01:51:12 +00:00
安正超 4f4c759b67 docs: simplify pull request template (#2744) 2026-04-29 20:29:23 +08:00
majinghe e331a26262 feat: helm chart version update (#2738) 2026-04-29 11:14:35 +00:00
安正超 61d2e9fbc3 test(webdav): cover decoded path parsing (#2729)
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-29 07:30:40 +00:00
majinghe 946755aa89 feat: helm publish manual trigger support (#2732) 2026-04-29 06:01:22 +00:00
houseme c9622611ed fix(admin): harden site-replication identity handling and local verification for issue 2723 (#2730) 2026-04-29 03:27:55 +00:00
majinghe 7041e628b7 fix: docker image build and helm chart publish error caused by versio… (#2731) 2026-04-29 03:23:47 +00:00
Ramakrishna Chilaka 8e1bd560d8 test(get): reject range with part number (#2725) 2026-04-28 23:15:59 +00:00
majinghe d447da75c1 chore: update version from alpha to beta (#2720)
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-28 23:08:10 +00:00
Rafael Peroco 2c9524e2c9 fix(helm): only render rollingUpdate when strategy type is RollingUpdate (#2728) 2026-04-28 23:06:31 +00:00
唐小鸭 e16f1ae639 fix(window): Compatible with Windows Path (#2691)
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-28 14:36:59 +00:00
安正超 d79720da1d test(signer): cover header fallback helpers (#2711) 2026-04-28 14:09:56 +00:00
安正超 b747e9817e fix(policy): preserve gateway ListBucket resources (#2710) 2026-04-28 14:09:42 +00:00
giter 90ce72122b fix(webdav): decode URL-encoded filenames in path parsing (#2722)
Signed-off-by: giter <giter@users.noreply.github.com>
2026-04-28 14:06:05 +00:00
houseme c4d5c5c5ec fix(obs): disable profiling export by default and fix Helm env name (#2719) 2026-04-28 11:57:15 +00:00
cxymds c698a0f2b6 fix(policy): allow AssumeRole in system policies (#2718) 2026-04-28 11:01:46 +00:00
houseme 2953558f41 fix(lifecycle): prevent eager date-expiry deletion on config update (#2708) 2026-04-28 10:26:14 +00:00
weisd e0b8c4fd42 fix(storage): avoid faulting local drives on transient timeouts (#2714) 2026-04-28 06:38:31 +00:00
weisd a995ec0315 fix(iam): preserve portable IAM storage and derived auth (#2713) 2026-04-28 05:57:10 +00:00
houseme 946b502527 build(deps): bump the dependencies group with 2 updates (#2709) 2026-04-27 23:53:29 +00:00
GatewayJ 09be06a4d2 fix(ecstore): log walk failures in IAM listing path (#2705)
Co-authored-by: GatewayJ <8352692332qq.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-27 14:13:40 +00:00
安正超 159ddd5bac fix: honor bucket-scoped ListBucket policies with s3:prefix (#2707)
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-27 14:13:22 +00:00
houseme a68fe1601f fix(ilm): harden signer failures and guard remote tier delete storms (#2706) 2026-04-27 12:39:02 +00:00
weisd 334184b005 fix(replication): prevent target state loss across buckets (#2704) 2026-04-27 09:27:33 +00:00
GatewayJ cfbd094bc4 fix(iam): propagate cache miss load failures (#2692)
Co-authored-by: GatewayJ <8352692332qq.com>
Co-authored-by: loverustfs <hello@rustfs.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-27 09:21:22 +00:00
weisd 468dc3aebd fix(replication): fan out single-bucket rules to all targets (#2701) 2026-04-27 06:51:19 +00:00
dependabot[bot] fd258e877c build(deps): bump the dependencies group with 2 updates (#2694)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: houseme <housemecn@gmail.com>
2026-04-27 02:42:49 +00:00
安正超 4dafb64d58 test(server): cover default module switch source (#2697) 2026-04-27 02:41:38 +00:00
houseme 50d03ef021 perf(memory): add reclaim signals and cache controls (#2689) 2026-04-26 16:42:35 +00:00
houseme 438913b84a feat: add console-managed audit and notify module switches (#2690) 2026-04-26 16:10:41 +00:00
GatewayJ 37a3cbc497 fix(admin): map IAM not found errors to 404 (#2685)
Co-authored-by: GatewayJ <8352692332qq.com>
2026-04-26 14:13:14 +00:00
安正超 a05687b900 test(utils): cover disk-check env alias precedence (#2684) 2026-04-26 07:41:50 +00:00
houseme 561d695237 chore(deps): update flake.lock (#2683) 2026-04-26 00:58:02 +00:00
houseme 59f41eb86a feat(obs): improve metrics coverage and dashboard performance (#2682) 2026-04-25 18:51:29 +00:00
1444 changed files with 436366 additions and 68177 deletions
+51
View File
@@ -0,0 +1,51 @@
---
name: arch-checks
description: Resolve failures from the repository's architecture guard scripts — check_layer_dependencies.sh, check_architecture_migration_rules.sh, check_unsafe_code_allowances.sh, check_logging_guardrails.sh, check_doc_paths.sh. Use when make pre-commit / pre-pr or CI fails on one of these checks.
---
# Architecture Guard Checks
All five run in `make pre-commit` / `make pre-pr` and in CI. Fix the cause;
never weaken a check to get green.
## `check_layer_dependencies.sh` — layer DAG in `rustfs/src`
Enforces `interface (admin, storage/ecfs, storage/s3_api) → app → infra`; no
upward imports. Known legacy violations live in
`scripts/layer-dependency-baseline.txt`.
- **New violation**: restructure your change so the dependency points
downward (move the shared type/function to the lower layer).
- **You legitimately removed a baseline entry**: run
`./scripts/check_layer_dependencies.sh --update-baseline` and commit the
shrunken baseline. Never add new entries to the baseline to make a new
violation pass.
## `check_architecture_migration_rules.sh` — required doc sections
Asserts that the core docs under `docs/architecture/` (overview,
crate-boundaries, runtime-lifecycle, readiness-matrix,
storage-control-data-plane, global-state-crate-split-plan,
ecstore-module-split-plan, …) still contain specific headings and exact
source lines. If it fails after a doc edit, you reworded or removed a
guarded line — restore the wording or update the script deliberately in the
same PR, with rationale.
## `check_unsafe_code_allowances.sh`
Every `#[allow(unsafe_code)]` needs a `SAFETY:` comment within a few lines.
Write the actual safety argument; don't add a placeholder.
## `check_logging_guardrails.sh`
A fixed list of security-sensitive files (auth, IAM, KMS, admin handlers…)
is scanned for logging violations. If you created a new sensitive file,
consider adding it to the script's `checked_files` list.
## `check_doc_paths.sh`
Instruction/architecture docs (`AGENTS.md`, `CLAUDE.md`, `ARCHITECTURE.md`,
`docs/architecture/*.md`) must not reference repo file paths that no longer
exist. If your refactor moved code, update the docs that point at it — the
error message lists `doc -> stale-path` pairs. Historical plans under
`docs/superpowers/plans/` are exempt.
@@ -41,6 +41,19 @@ Use this skill to review code changes consistently before merge, before release,
- hidden coupling to shared helpers/constants/features
- If a point is uncertain, mark it as an open question instead of guessing.
#### Rust-specific checks (apply to all Rust changes)
- **unwrap/expect in production**: Search changed files for `.unwrap()` and `.expect(` outside test modules. Every `unwrap()` in production code must have a justification comment or be replaced with `?`.
- **Silent type truncation**: Search for `as u8/u16/u32/u64/usize/i8/i16/i32/i64/isize` casts. Every `as` cast must be justified; negative-to-unsigned and large-to-small are bugs by default. Use `try_into()` or explicit clamping.
- **Unnecessary cloning**: Check `.clone()` calls in loops, per-request paths, and on structs with >5 heap-allocated fields. Consider `Arc`, references, or `Cow<str>`.
- **Lock ordering**: If the change acquires multiple locks, verify the order matches all other call sites. Document the order in a comment.
- **Locks across .await**: Flag any `tokio::sync::RwLock`/`Mutex` guard held across an `.await` point without bounded hold time.
- **Recursion depth**: If the change adds or modifies a recursive function, verify it has a depth limit or uses iterative traversal with an explicit stack.
- **Error types**: Flag `Result<_, String>`, `Box<dyn Error>`, and missing `Error::source()` implementations in public APIs.
- **Test assertions**: Every test function must have at least one `assert!`. Flag tests that only call code without verifying results.
- **println/eprintln**: Search changed files for `println!`/`eprintln!` outside test modules. Production code must use `tracing` macros.
- **Serde safety**: Structs deserialized from untrusted input (S3 API, user config) should have `#[serde(deny_unknown_fields)]`.
### 4) Findings-first output
- Order findings by severity:
- P0: critical failure, security breach, or data loss risk
@@ -0,0 +1,83 @@
---
name: plugin-contract-guard
description: Invariants and change procedure for the target-plugin / extension system — plugin manifests, admin plugin/extension catalog and instance APIs, secret redaction, external-plugin install policy. Use when editing crates/targets (manifest, plugin, control_plane, catalog, runtime), crates/extension-schema, or rustfs/src/admin plugin_contract.rs / plugins_*.rs / extensions.rs / target_descriptor.rs.
---
# Plugin & Extension Contract Guard
The "plugin system" spans four surfaces that must stay consistent:
| Surface | Location |
|---|---|
| Manifests & registry | `crates/targets/src/{manifest,plugin}.rs` |
| Install/enable planning (control plane) | `crates/targets/src/control_plane.rs` |
| Extension schemas | `crates/extension-schema/src/lib.rs`, `crates/targets/src/catalog/extension.rs` |
| Admin API contract | `rustfs/src/admin/plugin_contract.rs`, `handlers/{plugins_catalog,plugins_instances,extensions,target_descriptor}.rs` |
## Hard invariants (verify before merging)
1. **Secrets have one source of truth.** Secret config keys are declared only
in the plugin manifest (`TargetPluginManifest.secret_fields`,
`crates/targets/src/manifest.rs`) and flow to admin via
`AdminTargetSpec.secret_fields`. Never add a hand-maintained per-service
secret table in a handler; if redaction misses a field, fix the manifest.
2. **Redaction must round-trip.** Instance GET responses replace secret values
with `***redacted***` (`REDACTED_SECRET_VALUE` in `plugins_instances.rs`).
Instance PUT restores the stored secret when it receives that placeholder
back (`restore_redacted_secret_values`). Any new read or write path for
target config must keep both halves: redact on the way out, restore the
placeholder on the way in. The placeholder literal must never be persisted.
3. **Fixtures never reach production responses.**
`example_external_webhook_plugin()` (`crates/targets/src/catalog/mod.rs`)
is a test/demo fixture for control-plane planning tests. Production
catalog/extension handlers must not include it; regression tests
(`plugin_catalog_never_exposes_example_or_external_fixtures`,
`extension_catalog_never_exposes_example_or_external_fixtures`) enforce it.
4. **External plugin flow is planning-only and deny-by-default.**
`plan_external_target_plugin_action` returns decisions, it executes
nothing. `TargetPluginExternalFlowGate::default()` is fully closed and
`TargetPluginInstallPolicy::default().allowed_download_hosts` is empty —
keep it that way; tests opt in via explicit policies. Install validation
requires https, an allowlisted host, a full 64-hex-char sha256 digest,
signature and provenance URIs, and an artifact matching the host
`target_triple`.
5. **Custom target types must not collide.** Unknown target types get an
interned unique `custom:<type>` plugin id (`custom_plugin_id` in
`manifest.rs`). Custom plugins with secrets must register via
`TargetPluginDescriptor::with_manifest` and declare `secret_fields`;
`::new` derives a manifest with no secrets.
## Changing the admin JSON contract
- Shapes are locked twice in `plugin_contract.rs` tests: insta snapshots
(`rustfs/src/admin/snapshots/`) plus literal `json!` assertions. Update
both deliberately; a shape change is a console-facing API change.
- Field naming is `snake_case`, except discovery blocks
(`runtimeCapabilities`, `clusterSnapshot`, `extensionsCatalog`) which are
camelCase **by cross-endpoint convention** (same shape in `system.rs`,
`console.rs`, `pools.rs`). Do not "fix" that inconsistency locally.
- Contract types deliberately duplicate `rustfs_targets` types
(anti-corruption layer). Add a `From` impl; do not serialize internal
types directly.
## Handler conventions
- Every new admin plugin/extension route needs authorization at the top of
`call` and an `include_str!` guard test asserting it (repo-wide pattern —
see `plugin_instance_handlers_require_admin_authorization_contract`).
- Reads use `GetBucketTargetAction` (instances) or `ServerInfoAdminAction`
(catalogs); writes use `SetBucketTargetAction`.
- Refresh persisted module switches once per request
(`refresh_persisted_module_switches`), then evaluate the sync
`module_disabled_block_reason` per domain — do not re-read the store per
domain or per instance.
## Generic bounds
Event-payload generics use the `PluginEvent` blanket trait
(`crates/targets/src/plugin.rs`). Do not respell
`Send + Sync + 'static + Clone + Serialize + DeserializeOwned`.
+5 -2
View File
@@ -30,7 +30,9 @@ Use this skill before `gh pr create`, before `gh pr edit`, or when reviewing whe
- Treat introducing a new hardcoded literal where a project constant already exists as a likely regression risk; require either a refactor to reuse the constant or an explicit exception explanation in the PR body.
3. Verify readiness requirements
- Require `make pre-commit` before marking the PR ready.
- Require `make pre-commit` before marking PRs ready when the diff changes Rust code, product behavior, CI behavior, runtime configuration, security-sensitive logic, migrations, storage, auth, networking, or other high-risk paths.
- For documentation-only, agent-instruction-only, or local developer-tooling-only changes, allow focused verification instead of `make pre-commit` when it directly validates the changed surface.
- For focused verification, explain why the full gate was not run and list the scope-specific commands in the PR body.
- If `make` is unavailable, use the equivalent commands from `.config/make/`.
- Add scope-specific verification commands when the changed area needs more than the baseline.
- If required checks fail, stop and return `BLOCKED`.
@@ -79,7 +81,8 @@ Use this skill before `gh pr create`, before `gh pr edit`, or when reviewing whe
## Blocker rules
- Return `BLOCKED` if `make pre-commit` has not passed.
- Return `BLOCKED` if a code, behavior, CI, runtime configuration, security-sensitive, migration, storage, auth, networking, or other high-risk change has not passed `make pre-commit`.
- Return `BLOCKED` if a documentation-only, agent-instruction-only, or local developer-tooling-only change lacks focused verification for the changed surface.
- Return `BLOCKED` if the diff contains unrelated changes that are not acknowledged.
- Return `BLOCKED` if required template sections are missing.
- Return `BLOCKED` if the title/body is not in English.
@@ -3,7 +3,8 @@
- Confirm the branch is based on current `main`.
- Confirm the diff matches the stated scope.
- Confirm no secrets, logs, temp files, or unrelated refactors are included.
- Confirm `make pre-commit` passed, or document why it could not run.
- Confirm `make pre-commit` passed for code, behavior, CI, runtime configuration, security-sensitive, migration, storage, auth, networking, or other high-risk changes.
- For documentation-only, agent-instruction-only, or local developer-tooling-only changes, confirm focused verification covered the changed surface and the PR body explains why the full gate was not run.
- Confirm extra verification commands are listed for risky changes.
- Confirm the PR title uses Conventional Commits and stays within 72 characters.
- Confirm the PR title does not use tool-specific prefixes such as `[codex]`.
+112
View File
@@ -0,0 +1,112 @@
---
name: rust-code-quality
description: Enforce Rust-specific code quality rules on every code change. Use before merge to catch unwrap abuse, silent truncation, unnecessary cloning, lock ordering violations, recursion risks, and error type anti-patterns.
---
# Rust Code Quality Gate
Use this skill on every Rust code change to enforce quality rules that `cargo clippy` does not catch.
## Quick Start
1. Identify changed `.rs` files.
2. Run automated checks on changed files.
3. Run manual review checklist on the diff.
4. Report findings; block merge if P0/P1 issues exist.
## Automated Checks
Run these on every changed `.rs` file (excluding test modules):
```bash
# 1. unwrap/expect in production code
rg -n '\.unwrap\(\)|\.expect\(' <changed-files> | grep -v '#\[cfg(test)\]' | grep -v 'test' | grep -v 'bench'
# 2. Silent type truncation via `as` cast
rg -n ' as (u8|u16|u32|u64|usize|i8|i16|i32|i64|isize)\b' <changed-files>
# 3. String as error type
rg -n 'Result<.*String>' <changed-files> | grep -v test
# 4. Box<dyn Error> in public APIs
rg -n 'Box<dyn.*Error' <changed-files> | grep -v test
# 5. println/eprintln in production
rg -n 'println!\|eprintln!' <changed-files> | grep -v test
# 6. Ordering::Relaxed usage (verify each is intentional)
rg -n 'Ordering::Relaxed' <changed-files>
```
## Manual Review Checklist
For every Rust code change, verify:
### Error Handling
- [ ] No `unwrap()` or `expect()` in production code without justification comment
- [ ] No `Result<_, String>` in public API signatures
- [ ] No `Box<dyn Error>` in public trait/struct methods
- [ ] `Error::source()` is overridden when inner error is stored
- [ ] Error messages are actionable (what failed, with what input)
### Type Safety
- [ ] No silent `as` truncation (negative→unsigned, large→small)
- [ ] `try_into()` or explicit clamping used for numeric conversions
- [ ] No `f64 as usize` without prior clamping
### Concurrency
- [ ] Lock acquisition order is documented when multiple locks are used
- [ ] No `tokio::sync` write guards held across `.await` without bounded hold time
- [ ] Concurrent counters use `compare_exchange` loops, not load-then-store
- [ ] `std::sync::Mutex` in async context is held only briefly, never across `.await`
### Memory and Performance
- [ ] No `.clone()` on structs with >5 heap-allocated fields in hot paths
- [ ] `HashMap::with_capacity()` / `Vec::with_capacity()` used when size is known
- [ ] Large buffers wrapped in `Arc` rather than cloned
- [ ] Temporary string computations use `&str` or `Cow<str>` instead of `String`
### Recursion Safety
- [ ] Recursive functions have a depth limit or use iterative traversal
- [ ] Tree/cache traversals handle corrupted/cyclic input safely
### Testing
- [ ] Every test function has at least one `assert!`
- [ ] Tests use `.expect("context")` not bare `.unwrap()`
- [ ] No `println!`/`eprintln!` in production code (use `tracing`)
### Serde
- [ ] Structs from untrusted input have `#[serde(deny_unknown_fields)]`
- [ ] `#[serde(default)]` not used on security-critical fields without validation
### Code Hygiene
- [ ] No `#![allow(dead_code)]` at crate root
- [ ] No camelCase statics or Hungarian notation
- [ ] New string literals don't duplicate existing constants
## Severity Classification
- **P0 (Block merge)**: `unwrap()` in request hot path, silent truncation on user input, lock ordering violation, recursion without depth limit
- **P1 (Must fix)**: `Result<_, String>` in public API, unnecessary clone in hot path, `Box<dyn Error>` in trait method
- **P2 (Should fix)**: Missing `assert!` in test, `println!` in production, missing `with_capacity`
- **P3 (Nice to fix)**: Naming convention violation, missing doc comment, `as_ptr()` vs `Arc::ptr_eq`
## Output Template
```
## Rust Code Quality Report
### Automated Scan
- unwrap/expect in production: N found
- as casts: N found
- String errors: N found
- println/eprintln: N found
### Findings
- [P1] `path:line` — description
- Fix: ...
- Validation: ...
### Verdict
PASS / BLOCKED (list blocking findings)
```
@@ -0,0 +1,52 @@
# Rust Code Quality Checklist
Use this as a quick pre-merge checklist for every Rust code change.
## Critical (P0 — block merge)
| Check | Command |
|-------|---------|
| No `unwrap()` in request/storage hot path | `rg '\.unwrap\(\)' <files> \| grep -v test` |
| No `as` truncation on user input | `rg ' as (u32\|usize\|i32)' <files>` |
| Lock order consistent across call sites | Manual: trace all lock acquisitions |
| Recursive functions have depth limit | Manual: check for `max_depth` or iterative pattern |
| No `panic!`/`unwrap_or_else(panic!)` in production | `rg 'panic!\|unwrap_or_else.*panic' <files> \| grep -v test` |
## High (P1 — must fix)
| Check | Command |
|-------|---------|
| No `Result<_, String>` in public API | `rg 'Result<.*String>' <files> \| grep -v test` |
| No `Box<dyn Error>` in public trait | `rg 'Box<dyn.*Error' <files> \| grep -v test` |
| No unnecessary `.clone()` in hot path | Manual: check loops and per-request paths |
| `Error::source()` implemented when inner error stored | Manual: check `impl Error` |
| No `eprintln!`/`println!` in production | `rg 'println!\|eprintln!' <files> \| grep -v test` |
## Medium (P2 — should fix)
| Check | Command |
|-------|---------|
| Tests have assertions | Manual: check for `assert` in test functions |
| `HashMap`/`Vec` use `with_capacity` when size known | Manual: check `::new()` in loops |
| No `#![allow(dead_code)]` at crate root | `rg 'allow.dead_code' <files> \| grep 'lib.rs'` |
| Serde structs from untrusted input have `deny_unknown_fields` | Manual: check `#[derive(Deserialize)]` |
## Low (P3 — nice to fix)
| Check | Command |
|-------|---------|
| No camelCase statics | `rg 'static ref [a-z]' <files>` |
| `Arc::ptr_eq` instead of `as_ptr + ptr::eq` | `rg 'as_ptr\|ptr::eq' <files>` |
| Public functions have doc comments | `rg 'pub fn' <files> \| grep -v '///'` |
## Quick One-Liner
```bash
# Run all automated checks on changed files
CHANGED=$(git diff --name-only HEAD~1 -- '*.rs' | grep -v test | grep -v bench)
echo "=== unwrap/expect ===" && rg -c '\.unwrap\(\)|\.expect\(' $CHANGED 2>/dev/null
echo "=== as casts ===" && rg -c ' as (u8|u16|u32|u64|usize|i8|i16|i32|i64|isize)\b' $CHANGED 2>/dev/null
echo "=== String errors ===" && rg -c 'Result<.*String>' $CHANGED 2>/dev/null
echo "=== println ===" && rg -c 'println!|eprintln!' $CHANGED 2>/dev/null
echo "=== Ordering::Relaxed ===" && rg -c 'Ordering::Relaxed' $CHANGED 2>/dev/null
```
@@ -0,0 +1,107 @@
---
name: rustfs-logging-governance
description: Standardize and review RustFS logging with structured `tracing` events, lower noise on hot paths, preserve security-sensitive diagnostics, and extend guardrails to prevent legacy logging patterns from returning. Use when editing or reviewing RustFS logs, startup/config diagnostics, cloud metadata logs, request validation logs, or `scripts/check_logging_guardrails.sh`.
---
# RustFS Logging Governance
Use this skill when RustFS logging needs to be added, cleaned up, reviewed, or protected against regressions.
## Quick Start
1. Identify the files whose logs are changing.
2. Scan current `tracing` or `log` macros before editing.
3. Convert sentence-style logs to short event-style logs.
4. Demote hot-path success logs unless operators truly need them at `info`.
5. Preserve failure, fallback, and security-relevant diagnostics.
6. Update `scripts/check_logging_guardrails.sh` when a broad cleanup removes a legacy pattern class.
7. Validate with formatting, targeted checks/tests, and the logging guardrail script.
## Core Workflow
### 1. Scope the logging surface
- Read the changed module in full before touching log lines.
- Classify the log site:
- lifecycle/startup
- request or validation path
- background loop or hot path
- fallback/degraded behavior
- cloud metadata or external fetch path
- metrics/config summary
- Do not rewrite business logic to make logging easier.
### 2. Use the RustFS event shape
- Prefer fields first, message second.
- Use short labels, not prose paragraphs.
- Default field shape:
- `event`
- `component`
- `subsystem`
- `state` or `result`
- key context fields
- Reuse stable field names and avoid inventing near-duplicates.
See `references/logging-governance.md` for the event model, level policy, and anti-pattern list.
### 3. Choose the right level
- `error`: operation failure that affects behavior or security guarantees.
- `warn`: degraded path, fallback, suspicious input, or operator-actionable misconfiguration.
- `info`: low-frequency lifecycle or mode selection.
- `debug`: targeted diagnostics and low-volume detail.
- `trace`: hot-path and repetitive success-path events.
When in doubt, lower the verbosity of normal success paths and keep structured detail in fields.
### 4. Preserve security and privacy boundaries
- Do not log secrets, tokens, auth headers, raw credential payloads, or merged config dumps.
- Avoid logging raw forwarded headers or full trusted network inventories above `debug`.
- Keep warning/error logs useful without echoing attacker-controlled payloads unnecessarily.
### 5. Keep summaries aggregated
- Replace multi-line startup banners or checklist logs with one structured event.
- If metrics already express a concept, avoid duplicating it with many `info!` lines.
- Prefer counts, modes, and sources over inventories unless debug detail is truly needed.
### 6. Update guardrails when needed
- Broad logging cleanup should usually extend `scripts/check_logging_guardrails.sh`.
- Add forbidden patterns only for styles the repo has intentionally retired:
- sentence-style lifecycle logs
- noisy hot-path `info!`
- checklist-style summary logs
- legacy fallback wording that has been replaced by structured fields
- Keep guardrails concrete and grep-friendly.
### 7. Validate manually
Use the smallest relevant set:
```bash
cargo fmt --all --check
./scripts/check_logging_guardrails.sh
cargo check -p <affected-crate>
cargo test -p <affected-crate>
```
For broader Rust changes, add:
```bash
./scripts/check_unsafe_code_allowances.sh
./scripts/check_architecture_migration_rules.sh
cargo clippy -p <affected-crates> --all-targets -- -D warnings
```
## RustFS-Specific Notes
- The durable RustFS logging direction is `event + component + subsystem + state/result + key context fields`.
- `crates/concurrency` and `crates/trusted-proxies` are examples of this style for lifecycle, fallback, and cloud metadata logs.
- `scripts/check_logging_guardrails.sh` is the enforcement point for preventing removed log styles from returning.
## References
- Read `references/logging-governance.md` when you need the detailed field set, anti-pattern examples, or guardrail update checklist.
@@ -0,0 +1,4 @@
interface:
display_name: "RustFS Logging Governance"
short_description: "Standardize RustFS logs with structured events and guardrails."
default_prompt: "Use $rustfs-logging-governance to standardize or review RustFS logging, reduce noise, and update guardrails."
@@ -0,0 +1,285 @@
# RustFS Logging Governance Reference
## Workspace Scope Map
Use `Cargo.toml` `[workspace].members` as the source of truth for crate membership. When doing a broad logging sweep, classify crates by operational role so logs stay consistent within each role.
### Core Server And Request Handling
- `rustfs`
- Role: top-level server, startup, auth, admin wiring, S3 request handling.
- Logging focus: startup lifecycle, config summaries, authn/authz failures, protocol entrypoints, degraded subsystems.
- `crates/protocols`
- Role: protocol integrations such as FTP, SFTP, WebDAV, and related server-side protocol layers.
- Logging focus: listener lifecycle, per-protocol enablement/disablement, request bridge failures.
- `crates/madmin`
- Role: admin API contracts and management interfaces.
- Logging focus: admin action boundaries, validation failures, compatibility warnings.
- `crates/trusted-proxies`
- Role: forwarded IP trust, proxy chain validation, cloud metadata sources.
- Logging focus: direct/trusted/fallback decisions, degraded metadata fetches, aggregated config summaries.
- `crates/keystone`
- Role: Keystone auth integration.
- Logging focus: integration enablement, upstream auth failures, config safety without credential leakage.
### Storage, Healing, And Data Plane
- `crates/ecstore`
- Role: erasure-coded storage implementation and peer/store initialization.
- Logging focus: disk/peer lifecycle, storage fallback, object I/O failures, avoid per-object noise.
- `crates/heal`
- Role: healing orchestration and repair workflows.
- Logging focus: scheduler lifecycle, repair decisions, backlog or skipped work summaries, avoid repetitive task spam at `info`.
- `crates/scanner`
- Role: data integrity scanning and health monitoring.
- Logging focus: scan lifecycle, compaction/deep-heal transitions, lag/backlog, noisy folder iteration should stay at `debug/trace`.
- `crates/object-capacity`
- Role: capacity scan and refresh core.
- Logging focus: refresh lifecycle, degraded capacity sources, aggregate stats rather than per-object chatter.
- `crates/filemeta`
- Role: file metadata parsing and helpers.
- Logging focus: parse failures, schema/format mismatch, avoid dumping raw metadata payloads.
- `crates/storage-api`
- Role: storage contracts and shared data plane interfaces.
- Logging focus: contract mismatch and boundary diagnostics, usually low-volume.
- `crates/checksums`
- Role: checksum helpers and validation.
- Logging focus: integrity failures and compatibility mismatches, not per-chunk success logs.
- `crates/zip`
- Role: ZIP handling and compression helpers.
- Logging focus: parse/extract failures, archive path safety issues, avoid verbose file-by-file success logs.
### Security, Identity, And Policy
- `crates/iam`
- Role: identity and access management.
- Logging focus: authz decision boundaries, imported payload safety, do not leak principals, secrets, or claims.
- `crates/policy`
- Role: policy modeling and evaluation.
- Logging focus: deny/allow decision context, parser/validation failures, no raw secret-bearing request dumps.
- `crates/credentials`
- Role: credential handling.
- Logging focus: never log secrets or tokens; only safe identifiers and redacted states.
- `crates/kms`
- Role: key management service integration.
- Logging focus: init/health/fallback, key-source availability, never log key material.
- `crates/crypto`
- Role: cryptographic helpers and security primitives.
- Logging focus: only algorithm or mode state, not plaintext, ciphertext, or secret-derived material.
- `crates/security-governance`
- Role: security governance contracts.
- Logging focus: policy/state transitions and enforcement diagnostics.
- `crates/signer`
- Role: request signing helpers.
- Logging focus: signature validation failures without expected-signature leakage.
### Notifications, Audit, And Targets
- `crates/notify`
- Role: notification dispatch, runtime facade, notifier implementations.
- Logging focus: target lifecycle, dispatch summaries, stream lag/backpressure, avoid per-event success spam.
- `crates/audit`
- Role: audit target fan-out and audit pipeline management.
- Logging focus: pipeline lifecycle, target availability, batch dispatch summaries, avoid noisy "started successfully" prose.
- `crates/targets`
- Role: target-specific configuration and utilities used by fan-out style systems.
- Logging focus: target selection, config validation, per-target degraded state.
- `crates/s3-types`
- Role: S3 event and type definitions.
- Logging focus: usually minimal; keep logging at integration boundaries rather than low-level type crates.
- `crates/s3-ops`
- Role: S3 operation definitions and mapping.
- Logging focus: mapping/contract failures, unsupported combinations, not normal-path request spam.
### Concurrency, Locking, And Runtime Foundations
- `crates/concurrency`
- Role: timeout, locking, backpressure, and I/O scheduling facade.
- Logging focus: lifecycle transitions and degraded states, not high-frequency worker/permit churn at `info`.
- `crates/lock`
- Role: distributed locking implementation.
- Logging focus: lock lifecycle, contention anomalies, lock ordering or timeout diagnostics.
- `crates/tls-runtime`
- Role: shared TLS runtime foundation.
- Logging focus: certificate lifecycle, reload/fallback, validation failures without sensitive dumps.
- `crates/obs`
- Role: observability helpers.
- Logging focus: this crate shapes other crates' telemetry conventions; avoid recursive or redundant summaries.
- `crates/io-core`
- Role: zero-copy I/O core primitives.
- Logging focus: keep very sparse; prefer metrics unless failures are actionable.
- `crates/io-metrics`
- Role: I/O metrics collection.
- Logging focus: typically minimal; metrics should carry the hot-path signal.
- `crates/rio`
- Role: Rust I/O utility layer.
- Logging focus: compatibility or runtime boundary failures, not fast-path internals.
- `crates/rio-v2`
- Role: next-generation I/O compatibility layer.
- Logging focus: migration/feature-mode differences and degraded fallback between I/O paths.
- `crates/utils`
- Role: shared helpers.
- Logging focus: usually avoid direct logging in generic helpers unless the helper is itself an operational boundary.
- `crates/common`
- Role: shared data structures and helpers.
- Logging focus: same principle as `utils`; prefer callers to log context-rich events.
- `crates/config`
- Role: configuration management.
- Logging focus: config source, fallback, validation, and summary aggregation; avoid dumping merged configs.
- `crates/data-usage`
- Role: shared data usage models and algorithms.
- Logging focus: refresh lifecycle, summary stats, and degraded reads.
### Schema, Contracts, And API Support
- `crates/protos`
- Role: protobuf definitions.
- Logging focus: usually none inside the crate; emit logs at decode/use boundaries.
- `crates/extension-schema`
- Role: extension schema contracts.
- Logging focus: schema validation and compatibility mismatches.
- `crates/s3select-api`
- Role: S3 Select API interfaces.
- Logging focus: request validation and unsupported feature boundaries.
- `crates/s3select-query`
- Role: S3 Select query engine.
- Logging focus: query parse/planning/execution failures, avoid row-level spam.
- `crates/protocols`
- Role: non-S3 protocol support.
- Logging focus: see core server section; keep per-request verbosity below `info`.
### Testing And Non-Production Crates
- `crates/e2e_test`
- Role: end-to-end tests.
- Logging focus: test clarity matters more than production governance, but avoid copying test-only logging style into production crates.
## Current Guardrail Coverage Map
`scripts/check_logging_guardrails.sh` currently enforces retired patterns in these high-signal areas:
- `rustfs/src/main.rs`
- `rustfs/src/startup_iam.rs`
- `rustfs/src/auth.rs`
- `rustfs/src/protocols/client.rs`
- `crates/audit/src/pipeline.rs`
- `crates/audit/src/system.rs`
- `crates/audit/src/global.rs`
- `crates/notify/src/config_manager.rs`
- `crates/notify/src/runtime_facade.rs`
- `crates/notify/src/notifier.rs`
- `crates/ecstore/src/store/peer.rs`
- `crates/ecstore/src/store/init.rs`
- `crates/ecstore/src/tier/tier.rs`
- `crates/concurrency/src/workers.rs`
- `crates/concurrency/src/manager.rs`
- `crates/concurrency/src/lock.rs`
- `crates/concurrency/src/deadlock.rs`
- `crates/trusted-proxies/src/global.rs`
- `crates/trusted-proxies/src/config/loader.rs`
- `crates/trusted-proxies/src/proxy/metrics.rs`
- `crates/trusted-proxies/src/proxy/validator.rs`
- `crates/trusted-proxies/src/proxy/chain.rs`
- `crates/trusted-proxies/src/middleware/service.rs`
- `crates/trusted-proxies/src/cloud/detector.rs`
- `crates/trusted-proxies/src/cloud/ranges.rs`
- `crates/trusted-proxies/src/cloud/metadata/aws.rs`
- `crates/trusted-proxies/src/cloud/metadata/azure.rs`
- `crates/trusted-proxies/src/cloud/metadata/gcp.rs`
When expanding coverage, prefer crates with:
- repeated sentence-style lifecycle logs
- high-frequency success-path `info!`
- startup/config checklist banners
- security-sensitive fallback wording
- external fetch/retry/fallback flows
That typically means the next broad candidates are `rustfs`, `crates/notify`, `crates/audit`, `crates/targets`, `crates/heal`, and `crates/scanner`.
## Event Model
Prefer this structure when the fields are available:
- `event`
- `component`
- `subsystem`
- `state` or `result`
- stable context fields such as:
- `enabled`
- `implementation`
- `validation_mode`
- `peer_ip`
- `client_ip`
- `proxy_hops`
- `duration_ms`
- `fallback`
- `reason`
- `range_count`
- `hold_time_ms`
- `available_slots`
- `total_slots`
- `permits_in_use`
## Level Policy
- `error`: the operation fails and callers or security guarantees are affected.
- `warn`: a degraded path, fallback, suspicious request, or operator-actionable config issue occurs.
- `info`: a low-frequency lifecycle or mode transition occurs.
- `debug`: useful diagnostics exist but normal operators do not need them all the time.
- `trace`: hot-path and repetitive success-path details occur.
## Preferred Patterns
- Use a short message label:
- `"trusted proxy validation failed"`
- `"concurrency manager state changed"`
- `"trusted proxy cloud metadata loaded"`
- Put key meaning into fields, not only the message text.
- Aggregate config or metrics summaries into one log event.
## Retired Patterns
These should usually be removed or replaced:
- Sentence-style lifecycle logs:
- `info!("Concurrency manager stopped")`
- `info!("Trusted Proxies module initialized")`
- Checklist or banner logs:
- `info!("=== Application Configuration ===")`
- `info!("Available metrics:")`
- Hot-path noise:
- `info!("worker take, {}", *available)`
- `debug!("Proxy validation successful in {:?}", duration)`
- Legacy fallback prose:
- `"Request from private network but not trusted: ..."`
- `"Cloud metadata fetching is disabled"`
## Guardrail Update Checklist
When extending `scripts/check_logging_guardrails.sh`:
1. Add the touched files to `checked_files`.
2. Add only legacy patterns that have been intentionally retired.
3. Keep patterns literal and grep-friendly.
4. Run the guardrail script after changes.
5. Avoid adding patterns for logs that are still valid elsewhere in the repo.
## Validation Checklist
For logging-only changes:
```bash
cargo fmt --all --check
./scripts/check_logging_guardrails.sh
cargo check -p <affected-crate>
cargo test -p <affected-crate>
```
For broader Rust changes:
```bash
./scripts/check_unsafe_code_allowances.sh
./scripts/check_architecture_migration_rules.sh
cargo clippy -p <affected-crates> --all-targets -- -D warnings
```
@@ -0,0 +1,124 @@
---
name: rustfs-release-version-bump
description: "Publish a RustFS alpha/beta/stable release with an auditable flow: confirm target version and scope, update workspace and release assets (including strict rustfs.spec changelog identity/date/version format), run required verification, and finish with commit, push, and GitHub PR creation."
---
# RustFS Release Version Bump
Use this skill to publish a RustFS release (alpha, beta, or stable) with a minimal, auditable diff and a complete ship flow (`edit -> verify -> commit -> push -> PR`).
Validated baseline: release pattern used in PR `#2957`.
## Required inputs
- Exact target version, for example `1.0.0-beta.4`.
- Delivery scope:
- Local only (`edit/verify`).
- Local + git (`commit/push`).
- Full GitHub flow (`commit/push/PR`).
If target version is missing or ambiguous, stop and ask before editing.
## Read before editing
- `AGENTS.md` (root and nearest path-specific files).
- `.github/pull_request_template.md`.
- Current branch status and diff against `origin/main`.
## Default release file scope
Treat the following file list as the default checklist for each release bump:
- `Cargo.toml`
- `Cargo.lock`
- `README.md`
- `README_ZH.md`
- `flake.nix`
- `helm/rustfs/Chart.yaml`
- `rustfs.spec`
Only drop a file when the current repository release process clearly no longer requires it.
## Hard release policy
- Docker doc tags use `<version>` (for example `rustfs/rustfs:1.0.0-beta.4`), not `v<version>`.
- Helm chart version mapping follows `beta.N -> 0.N.0`.
- `rustfs.spec` `Release` uses prerelease suffix only (for example `beta.4`).
- Do not change these rules without explicit confirmation.
## Step-by-step workflow
1. Confirm intent and isolate scope
- Confirm target version string exactly.
- Confirm whether user requested local-only or full GitHub flow.
- Inspect current branch and ensure only release-related files are touched for this task.
2. Update workspace versions
- Bump `[workspace.package].version` in `Cargo.toml`.
- Bump internal workspace crate dependency versions in `Cargo.toml`.
- Update `Cargo.lock` so workspace package versions match target version.
- Re-scan for partial leftovers.
3. Update release assets
- `README.md` and `README_ZH.md`: update versioned Docker examples to target version.
- `flake.nix`: update package version to target version.
- `helm/rustfs/Chart.yaml`:
- `appVersion` = target version.
- `version` follows chart mapping rule, for example:
- `1.0.0-beta.3` -> `0.3.0`
- `1.0.0-beta.4` -> `0.4.0`
- `rustfs.spec`:
- Set `Release` to prerelease suffix (example `beta.4`).
- Add/update top changelog entry with exact format:
- `* Thu May 20 2026 houseme <housemecn@gmail.com>`
- `- Update RPM package to RustFS 1.0.0-beta.4`
- Changelog identity and time must come from current environment:
- `git config --get user.name`
- `git config --get user.email`
- `date '+%a %b %d %Y'`
- Changelog version text must match target release version exactly.
4. Verify before shipping
- Run:
- `cargo fmt --all`
- `cargo fmt --all --check`
- `make pre-commit`
- If verification passes, run `cargo clean`.
- If `make pre-commit` fails, return `BLOCKED` with root cause and do not silently widen scope to fix unrelated issues unless user asks.
5. Commit strategy
- Preferred split when both parts changed:
- `chore(release): prepare <version>` for `Cargo.toml` and `Cargo.lock`.
- `chore(release): align release assets for <version>` for docs and packaging files.
- If user asks for one commit, use one commit.
- Stage only intended release files; do not include unrelated working tree changes.
6. Push and PR
- Push branch:
- `git push -u origin <branch>` (first push), or `git push` (tracking already exists).
- Create PR with template headings unchanged:
- `gh pr create --base main --head <branch> --title ... --body-file ...`
- PR title/body must be English.
- Use `N/A` for non-applicable template sections.
- Include verification commands and any `BLOCKED` reason clearly.
## Recommended check commands
- `git status --short --branch`
- `git diff --name-only origin/main...HEAD`
- `git diff --stat origin/main...HEAD`
- `rg -n "<old_version>|<new_version>" Cargo.toml Cargo.lock README.md README_ZH.md flake.nix helm/rustfs/Chart.yaml rustfs.spec`
- `cargo fmt --all`
- `cargo fmt --all --check`
- `make pre-commit`
- `cargo clean`
## Output contract
When using this skill, always report:
- Target version.
- Files changed.
- Any assumptions or uncertainties requiring confirmation.
- Verification result (`PASSED` or `BLOCKED`) with key evidence.
- Commit message(s) used.
- Push status and PR URL when GitHub flow is requested.
@@ -0,0 +1,4 @@
interface:
display_name: "RustFS Release Bump"
short_description: "Prepare RustFS release branches like PR #2957."
default_prompt: "Use $rustfs-release-version-bump to prepare a RustFS release version, ask about any unclear version policy, and finish the commit/push/PR flow."
@@ -0,0 +1,136 @@
---
name: security-advisory-lessons
description: Apply RustFS security lessons distilled from repository GitHub Security Advisories. Use when making or reviewing RustFS code changes, doing security checks, handling PR review for auth/authz, IAM, storage, RPC, logging, CORS, console/browser, encryption, policy, or endpoint changes, and when deciding which security regression tests are required.
---
# RustFS Security Advisory Lessons
Use this skill as a RustFS-specific security lens before changing or approving code. For the distilled advisory lessons and review patterns, read [advisory-patterns.md](references/advisory-patterns.md).
When currentness matters, fetch the live advisory inventory instead of relying on this skill as a status mirror:
```bash
gh api repos/rustfs/rustfs/security-advisories --paginate \
--jq '.[] | {ghsa_id,state,severity,summary,updated_at}'
```
Fetch full advisory details only when the live summary suggests a new or changed lesson:
```bash
gh api repos/rustfs/rustfs/security-advisories/<GHSA_ID>
```
For the full pattern map, read [advisory-patterns.md](references/advisory-patterns.md).
## Workflow
### 1. Scope the change
- Identify touched routes, protocol frontends, handlers, storage paths, credentials, logs, browser surfaces, CI/release code, and policy checks.
- Treat these paths as security-sensitive by default: `rustfs/src/admin/`, `rustfs/src/storage/`, `rustfs/src/auth.rs`, `rustfs/src/server/layer.rs`, `crates/iam/`, `crates/policy/`, `crates/credentials/`, `crates/ecstore/src/rpc/`, `crates/protocols/`, `crates/rio/`, and console preview/auth code.
### 2. Map to advisory classes
- Read [advisory-patterns.md](references/advisory-patterns.md) for matching GHSA lessons.
- Do not rely on advisory titles alone. Confirm whether the issue is authentication, authorization, input validation, storage invariant, browser isolation, logging, or operational hardening.
### 3. Verify fail-closed behavior
- Check that unauthenticated, wrong-permission, cross-user, cross-bucket, malformed-input, and default-config cases fail explicitly.
- Prefer exact action/permission checks over broad helper calls or inferred ownership.
- Confirm lower storage/RPC layers do not bypass checks done in upper layers.
### 4. Require regression evidence
- For behavior changes, add focused negative tests that reproduce the advisory class.
- For sensitive fixes, include tests for the bypass form, not only the happy path.
- If a test is impractical, explain the residual risk and provide a manual verification command.
### 5. Report clearly
- Lead with concrete findings and file/line evidence.
- Separate proven vulnerabilities from hardening risks.
- Avoid exaggerating unauthenticated impact when the code actually rejects unauthenticated requests but allows a low-privileged authenticated bypass.
## Advisory-Derived Guardrails
### Auth and admin authorization
- Every admin or diagnostic route needs an explicit authn and authz story. Route registration, router whitelist, and handler-level authorization must agree.
- Match the admin action to the operation exactly. Copy-paste action constants are a known RustFS vulnerability class.
- Avoid authentication-only helpers for state-changing admin APIs; use `validate_admin_request` or the established equivalent with the right `AdminAction`.
- Read-only admin APIs such as metrics, server info, and diagnostics still require admin authorization; checking only that credentials exist is not enough.
- Replication admin reads can expose remote target credentials; list/get target endpoints require replication/admin authorization and must not return secrets to low-privilege callers.
- Do not assume admin-action `Resource` scoping constrains blast radius unless the policy engine actually enforces resources for that action.
### IAM and service accounts
- Treat imported IAM payload fields as attacker-controlled: `parent`, `claims`, `accessKey`, `secretKey`, status, policy names, and groups.
- For service account create/update/import, prove parent ownership or root/admin authority before writing credentials or claims.
- Do not let `deny_only` or "no explicit deny" become an allow decision that skips required allow checks.
- Test cross-user list/update/import flows with wrong, correct, self, parent, and root identities.
### S3 copy, multipart, and presigned POST
- Multipart copy must enforce source `GetObject` and destination `PutObject` semantics equivalent to `CopyObject`, including copy-source and policy conditions.
- Do not let `CreateMultipartUpload`, `UploadPartCopy`, `CompleteMultipartUpload`, or `AbortMultipartUpload` return success without authorization.
- Presigned POST policies are server-side contracts. Enforce `content-length-range`, key prefix, exact metadata/content-type, and all signed policy conditions.
### Protocol frontends and IAM parity
- FTP/FTPS, SFTP, gateway, and other protocol drivers must enforce IAM per operation before calling storage backends; authentication to a protocol listener is not authorization.
- Match protocol commands to the same S3 actions as HTTP, such as `RETR` to `GetObject`, `SIZE`/`MDTM` to `HeadObject`, `MKD` to `CreateBucket`, and bucket probes to `ListBucket` or `HeadBucket`.
- Review every handler in a protocol driver, not only the changed handler, because RustFS advisories show mixed guarded and unguarded siblings in the same driver.
- Regression tests for protocol frontends should deny the shared authorization hook and prove the backend is not reached for the denied command.
- Compare protocol secrets in constant time, normalize invalid-user and invalid-secret failures where practical, and add rate limiting before exposing password-style protocol endpoints.
### Paths, object keys, and filesystem access
- Never join untrusted bucket/object/RPC path strings onto filesystem roots without normalization and boundary checks.
- Reject or safely handle `..`, absolute paths, URL-encoded traversal, platform separators, empty components, and paths that canonicalize outside the intended root.
- Validate both S3 object-key paths and internode/RPC disk paths; storage helpers can bypass S3 authorization if they trust already-parsed paths.
- Archive auto-extract paths are object keys too. Validate tar/zip entry names before IAM checks and before storage writes, and prove cleaned paths cannot cross bucket or prefix boundaries.
### Secrets, default credentials, and crypto
- Do not ship hard-coded shared tokens, HMAC secrets, private keys, or production test keys.
- Defaults for root credentials and internode/RPC auth must fail closed for network-reachable deployments or generate per-install random secrets; warnings alone are not a security boundary.
- Keep cryptographic roles separated: root S3 credentials, RPC HMAC keys, and STS/JWT signing keys must not be reused or deterministically derived from each other.
- License or token validation must use signatures with embedded public/verifying keys only; do not use private-key decryption as authenticity.
- Plan key rotation and key IDs when removing exposed keys.
### Logging and debug output
- Logs must never include access keys beyond safe identifiers, secret keys, session tokens, JWT claims, HMAC secrets, expected signatures, license secrets, or raw response bodies containing credentials.
- Treat `Debug` implementations, `?value` tracing, merged config dumps, and dependency-level HTTP body logging as leak surfaces.
- Add log-capture tests or targeted unit tests for redaction wrappers when changing credential structs or response bodies.
### RPC, parsing, and panic safety
- Treat all RPC payload bytes as attacker-controlled. Replace `unwrap`, `expect`, and panic-prone deserialization with typed errors.
- Malformed request tests should cover empty bytes, truncated MessagePack/protobuf, invalid enum values, stale timestamps, and invalid signatures.
- RPC authentication must be independently strong; do not depend on S3 admin credentials unless the fallback is explicit and safe.
- RPC signatures must bind the exact generated gRPC method path, timestamp, and request method. Service-prefix signatures must not authorize a different concrete NodeService call.
### Browser, CORS, and console surfaces
- Do not reflect arbitrary `Origin` while also allowing credentials. Default CORS should be no CORS unless explicitly configured.
- Do not render user-controlled object content in a same-origin iframe with console credentials available to JavaScript.
- Prefer origin separation for object preview/download, `nosniff`, CSP, strict content-type handling, and avoiding durable credentials in `localStorage`.
- Preview safety must be based on trusted content type and sandboxing, not object names or extensions such as `.pdf`.
- Console license/version-like metadata endpoints should expose only coarse public data unless authenticated, especially subject names and expiration timestamps.
### Profiling, debug, and health endpoints
- Profiling and debug endpoints are not health checks. They require admin auth, opt-in enablement, rate limiting, and safe responses.
- Do not return absolute filesystem paths or other deployment layout in unauthenticated or low-privilege responses.
- Ensure health endpoint allowlists cannot accidentally include expensive diagnostics.
### Trusted proxy and network identity
- Only honor `X-Forwarded-For` or `X-Real-IP` when the request came from a configured trusted proxy.
- Direct clients must use the socket peer address for `aws:SourceIp` and policy condition evaluation.
- Add tests for direct spoofed headers and trusted-proxy headers.
### SSE and storage invariants
- Encryption metadata is not proof that bytes were encrypted on disk.
- When touching reader/writer wrappers such as hashing, encryption, compression, or warp readers, verify wrapper order and inspect stored bytes in regression tests.
- Avoid helper shortcuts that unwrap nested readers and accidentally bypass encryption or integrity layers.
## Review Prompts
Use these prompts while reviewing a diff:
- Could a low-privileged authenticated user reach this path with the wrong action, parent, bucket, or source object?
- Does a non-HTTP protocol path call the same authorization boundary as the S3 API before touching storage?
- Does a public/default/empty config change security behavior from fail-closed to fail-open?
- Is any attacker-controlled value later used as a path, policy condition, credential identity, log field, URL, Origin, or response body?
- Does this response contain stored replication, remote target, or service credentials that need redaction or stricter authorization?
- Is an archive entry, object key, or policy resource normalized differently between authorization and storage?
- Is the same operation implemented in multiple paths, such as `CopyObject` vs `UploadPartCopy`, and do all paths enforce the same security contract?
- Does a preview or browser-surface fix preserve the original security invariant when adding alternate viewers or file-type detection?
- Does the test prove the exploit form is denied, or only that the intended form still works?
@@ -0,0 +1,4 @@
interface:
display_name: "Security Advisory Lessons"
short_description: "Apply advisory lessons in reviews."
default_prompt: "Review code changes against past RustFS security advisory lessons and report concrete risks, missing tests, and recommended fixes."
@@ -0,0 +1,119 @@
# RustFS Advisory Pattern Map
This file is a lesson map, not an advisory inventory mirror. It keeps durable security patterns distilled from RustFS GitHub Security Advisories.
When current advisory state, severity, URLs, or full text matters, fetch it live:
```bash
gh api repos/rustfs/rustfs/security-advisories --paginate \
--jq '.[] | {ghsa_id,state,severity,summary,updated_at}'
gh api repos/rustfs/rustfs/security-advisories/<GHSA_ID>
```
Update this file only when an advisory adds or changes a reusable lesson, affected surface, validation pattern, or regression-test expectation. Do not update it for state-only, URL-only, count-only, or timestamp-only changes.
## Pattern Index
### Admin authorization and route exposure
- `GHSA-pfcq-4gjr-6gjm`: notification target endpoints accepted authenticated users but skipped admin authorization. Lesson: distinguish authn from authz; admin target CRUD must call the operation-specific admin authorization path.
- `GHSA-mm2q-qcmx-gw4w`: `ListServiceAccount` used `UpdateServiceAccountAdminAction`, while update lacked target ownership checks. Lesson: exact action constants and ownership checks are both required; information disclosure can chain into secret rotation and takeover.
- `GHSA-vcwh-pff9-64cc`: `ImportIam` checked `ExportIAMAction` for an import/write operation. Lesson: every admin handler must authorize the action it actually performs.
- `GHSA-jqmc-mg33-v45g` and `GHSA-8784-9m7f-c6p6`: `/profile/cpu` and `/profile/memory` were whitelisted from auth and allowed expensive diagnostics plus path disclosure. Lesson: profiling/debug endpoints need admin auth, opt-in, rate limits, and non-sensitive responses.
- `GHSA-f5cv-v44x-2xgf`: `/rustfs/admin/v3/metrics` accepted any authenticated IAM user and skipped admin authorization. Lesson: read-only metrics and diagnostic admin endpoints still require an operation-specific admin action check.
- `GHSA-796f-j7xp-hwf4`: `/rustfs/admin/v3/list-remote-targets` checked only that credentials existed and leaked replication target credentials. Lesson: replication target reads are privileged admin operations, and stored remote credentials require strict authz plus response redaction review.
- `GHSA-xp32-gxq2-3v52`: console license metadata endpoint was public and exposed subject and expiration fields. Lesson: management metadata endpoints should require admin auth or return only coarse public status.
### IAM import, service accounts, and privilege boundaries
- `GHSA-566f-q62r-wcr8`: `ImportIam` accepted attacker-controlled service account `parent`, `claims`, `accessKey`, and `secretKey`, enabling persistent backdoor accounts under root. Lesson: imported IAM payloads are untrusted data and must be validated against privilege boundaries.
- `GHSA-xgr5-qc6w-vcg9`: `deny_only=true` skipped allow checks and let restricted service accounts mint unrestricted children. Lesson: deny-only logic must never become implicit allow for privilege creation.
- `GHSA-mm2q-qcmx-gw4w`: leaked service account access keys plus update-without-ownership formed an escalation chain. Lesson: service-account identifiers are security-sensitive because update APIs consume them.
### S3 copy, multipart, and upload policy validation
- `GHSA-mx42-j6wv-px98`: `UploadPartCopy` missed source authorization and allowed cross-bucket object exfiltration. Lesson: multipart copy must enforce the same source and destination contract as `CopyObject`.
- `GHSA-wfxj-ph3v-7mjf`: `UploadPartCopy` checked source and destination independently but missed destination copy-source policy constraints. Lesson: source read and destination write checks are not sufficient when policy constrains allowed copy sources.
- `GHSA-w5fh-f8xh-5x3p`: presigned POST accepted uploads without enforcing signed policy conditions. Lesson: parse and enforce all POST policy constraints server-side, including size, key prefix, and content type.
### Protocol frontends and IAM parity
- `GHSA-3g29-xff2-92vp`: FTP `RETR` and `SIZE`/`MDTM` read paths authenticated the user but skipped IAM before calling storage. Lesson: non-HTTP protocol frontends must enforce the same per-operation authorization as the S3 API before backend access.
- `GHSA-g3vq-vv42-f647`: FTPS `MKD` called `create_bucket` without checking `s3:CreateBucket`. Lesson: protocol command handlers need action-specific checks even when sibling handlers already authorize correctly.
- `GHSA-3p3x-734c-h5vx`: FTPS and WebDAV compared secret keys with early-return string equality, while FTPS also returned distinguishable invalid-user and invalid-password failures. Lesson: password-style protocol auth needs constant-time secret comparison, indistinguishable failures where practical, and rate limiting.
### Filesystem paths and object key traversal
- `GHSA-pq29-69jg-9mxc`: RPC `read_file_stream` joined untrusted paths under a volume directory without canonical boundary checks. Lesson: `PathBuf::join` plus length checks are not path security.
- `GHSA-8r6f-hmq2-28rg`: object keys containing traversal sequences bypassed bucket/object authorization when mapped to filesystem paths. Lesson: reject traversal at object-key parsing and verify final storage paths remain under the expected bucket/key root.
- `GHSA-f4vq-9ffr-m8m3`: Snowball auto-extract accepted archive entries such as `../victim-bucket/object`, authorized the raw attacker-bucket path, then storage path cleaning crossed bucket boundaries. Lesson: archive entries become object keys and need traversal rejection plus consistent authz/storage normalization before writes.
### Secrets, defaults, and cryptographic misuse
- `GHSA-j59h-h7q5-q348`: RustFS shipped known default root credentials that could authenticate to S3, admin APIs, IAM, KMS, and console surfaces. Lesson: root credentials must be operator-provided or generated per install; known defaults and warnings are not acceptable for network-reachable deployments.
- `GHSA-h956-rh7x-ppgj`: gRPC used the hard-coded token `rustfs rpc` on both client and server. Lesson: source-visible shared tokens are authentication bypasses.
- `GHSA-r5qv-rc46-hv8q`: internode RPC HMAC secret fell back to the public default `rustfsadmin`. Lesson: RPC/internode auth must fail closed instead of silently using public defaults.
- `GHSA-75fx-qg6f-8rm7` and `GHSA-68cw-96m3-h2cf`: internode RPC secrets were derivable from known root credentials, making raw storage RPC signatures forgeable when explicit RPC secrets were unset. Lesson: RPC auth keys must be independent random secrets, never derived from S3 root credentials, and raw storage RPC should not share the public S3 listener without an internode-only boundary.
- `GHSA-m77q-r63m-pj89`: STS JWTs used the root secret key as the shared token signing key, allowing token forgery when the root secret was known. Lesson: STS signing keys need key separation, rotation, and key IDs; do not reuse root credentials for JWT/HMAC signing.
- `GHSA-923g-jp7v-f97f`: license verification embedded a production RSA private key and used private-key decryption as authenticity. Lesson: ship verifying/public keys only and use real signature verification.
### Sensitive logging and debug output
- `GHSA-r54g-49rx-98cr`: STS credentials were logged at info level. Lesson: generated credentials must never be logged in plaintext.
- `GHSA-8cm2-h255-v749`: debug logs leaked session tokens, secret keys, JWT claims, and raw STS response bodies. Lesson: redaction must cover custom `Debug` implementations and dependency response-body logging.
- `GHSA-333v-68xh-8mmq`: invalid RPC signature logging included the shared HMAC secret and expected signature. Lesson: error paths often leak secrets; never log raw secrets or derived authenticators.
### RPC input validation and panic safety
- `GHSA-gw2x-q739-qhcr`: malformed gRPC `GetMetrics` payloads reached `unwrap()` on deserialization and caused remote DoS. Lesson: every network/RPC deserialization failure returns an error, not a panic.
- `GHSA-h956-rh7x-ppgj` and `GHSA-r5qv-rc46-hv8q`: weak RPC auth increased reachability of otherwise internal handlers. Lesson: panic bugs become more severe when internode auth is weak or defaulted.
- `GHSA-c667-rgrv-99vj`: NodeService authentication signed the service prefix instead of the concrete generated method path, so valid metadata for one RPC could be replayed to another method during the timestamp window. Lesson: RPC HMAC payloads must bind exact gRPC method path, HTTP method surrogate, timestamp, and secret.
### Browser, CORS, and console isolation
- `GHSA-v9fg-3cr2-277j`: object preview rendered attacker-controlled HTML in a same-origin iframe, exposing console credentials stored in `localStorage`. Lesson: user content must be origin-isolated from the console and protected with `nosniff`, CSP, and strict content-type handling.
- `GHSA-7gcx-wg4x-q9x6`: an incomplete preview fix reintroduced extension-based PDF detection and bypassed the sandboxed fallback for attacker-controlled content. Lesson: browser-surface fixes need regression tests for alternate viewers and file-type branches, and preview trust must come from validated content type plus sandboxing rather than object names.
- `GHSA-x5xv-223c-8vm7`: default CORS reflected arbitrary origins with credentials. Lesson: never combine reflected origins with `Access-Control-Allow-Credentials: true`; default should be fail-closed.
### Trusted proxy and source IP conditions
- `GHSA-fc6g-2gcp-2qrq`: `aws:SourceIp` trusted client-supplied `X-Forwarded-For` or `X-Real-IP`. Lesson: forwarded IP headers are valid only behind configured trusted proxies; direct clients use socket peer IP.
### SSE and on-disk storage invariants
- `GHSA-xrrf-67jm-3c2r`: SSE metadata reported encryption while reader composition bypassed `EncryptReader` and stored plaintext. Lesson: test actual bytes on disk and wrapper order, not only API metadata.
### Serde deserialization and input validation
- No `#[serde(deny_unknown_fields)]` found across the entire codebase. Lesson: all structs deserialized from untrusted input (S3 API XML/JSON, lifecycle rules, bucket policies, replication configs) should have `#[serde(deny_unknown_fields)]` to reject malformed or adversarial payloads.
- `#[serde(default)]` on security-critical fields silently accepts missing values as zero/empty. Lesson: when a field has security implications (retention days, permissions, limits), validate the deserialized value explicitly rather than relying on defaults.
- Integer fields deserialized from user input and cast with `as` (e.g., `i32 as u32`) can wrap negative values to large positives. Lesson: validate ranges before casting; use `try_into()` or clamp.
- XML config typos (e.g., `"NoncurentDays"` instead of `"NoncurrentDays"`) are silently accepted when `deny_unknown_fields` is absent. Lesson: strict deserialization prevents silent misconfiguration that could cause data loss or unexpected retention behavior.
## Useful Search Seeds
Use these targeted searches when a diff touches security-sensitive code:
```bash
rg -n "validate_admin_request|check_permissions|AdminAction::|deny_only|is_allowed" rustfs crates
rg -n "authorize_operation|FtpsDriver|SftpDriver|RETR|MKD|SIZE|MDTM|CreateBucket|GetObject|HeadObject" crates/protocols rustfs
rg -n "UploadPartCopy|upload_part_copy|CompleteMultipart|PostObject|content-length-range|starts-with" rustfs crates
rg -n "normalize_extract_entry_key|Snowball|auto-extract|PathBuf::join|canonicalize|\\.\\.|x-forwarded-for|x-real-ip|SourceIp" rustfs crates
rg -n "DEFAULT_SECRET|DEFAULT_ACCESS|TEST_PRIVATE_KEY|rustfs rpc|RUSTFS_RPC_SECRET" rustfs crates
rg -n "TONIC_RPC_PREFIX|verify_rpc_signature|check_auth|NodeServiceServer|x-rustfs-signature" rustfs crates
rg -n "debug!|trace!|info!|error!|\\?resp|\\?merged_config|session_token|secret_key" rustfs crates
rg -n "HashReader|EncryptReader|SSE|server-side encryption|Access-Control-Allow-Credentials|Origin" rustfs crates
rg -n "deny_unknown_fields|serde.default|as u32|as usize|as i32" rustfs crates
```
## Minimum Regression Test Expectations
- Authz fixes: include unauthenticated, valid low-privilege, wrong-action, correct-action, owner, non-owner, and root/admin cases as applicable.
- Protocol frontend authz fixes: include denied `RETR`, `SIZE`/`MDTM`, `MKD`, bucket probe, and sibling allowed-operation cases, and assert denied paths do not reach the storage backend.
- IAM fixes: include import/update/list service-account cases with attacker-controlled parent, claims, access key, secret key, and policy.
- Copy/upload fixes: include cross-bucket, cross-user, source-denied, destination-denied, copy-source-condition, and multipart completion cases.
- Path fixes: include encoded traversal, absolute path, nested traversal, archive entries with `..`, valid object keys that resemble traversal text but should be rejected, and canonical bucket/prefix boundary checks.
- Logging fixes: assert redacted output for structs and response bodies that may contain credentials.
- RPC auth fixes: include captured metadata replay across two concrete methods, stale timestamps, wrong path, wrong method surrogate, wrong secret, and valid same-method calls.
- Browser/CORS fixes: assert no credentials on reflected/default origins, correct behavior for explicit allowlists, and no same-origin script execution for previewed object content.
- SSE fixes: inspect stored bytes and verify API metadata, read-back behavior, and on-disk ciphertext together.
+34
View File
@@ -0,0 +1,34 @@
---
name: tier-debug
description: Debug ILM tiering / lifecycle transition issues — NoSuchVersion on tier GET, restore failures, xl.meta inspection, remote-tier versionId tracing. Use when investigating tiered/transitioned objects, warm backends, or transition metadata.
---
# Tier / ILM Debugging
Full playbook: [docs/operations/tier-ilm-debugging.md](../../../docs/operations/tier-ilm-debugging.md)
— read it before changing tier code.
Quick moves:
```bash
# Inspect transition metadata on disk (one xl.meta per erasure shard disk)
cargo run -p rustfs-filemeta --example dump_fileinfo -- "/path/to/{bucket}/{object}/xl.meta"
# Trace what versionId is sent to the remote tier
RUST_LOG=rustfs_ecstore::bucket::lifecycle=debug ./target/debug/rustfs …
```
Interpretation:
- `transition_ver_id: <none>` → correct for an unversioned tier bucket; no
`versionId` must be sent on tier GET/DELETE.
- `transition_ver_id: 00000000-…` (nil) → corrupt legacy write-back; readers
must filter it out, never send it.
- Empty-string `transitioned-versionID` metadata under both
`x-rustfs-internal-*` and `x-minio-internal-*` keys → object went to an
unversioned tier bucket.
Code entry points: `crates/ecstore/src/bucket/lifecycle/bucket_lifecycle_ops.rs`
(ILM actions), `crates/ecstore/src/services/tier/` (warm backends),
`crates/filemeta/src/filemeta/version.rs` (metadata read/write + regression
tests).
+1
View File
@@ -0,0 +1 @@
../.agents/skills
+2 -2
View File
@@ -9,7 +9,7 @@ build-docker: SOURCE_BUILD_CONTAINER_NAME = rustfs-$(BUILD_OS)-build
build-docker: BUILD_CMD = /root/.cargo/bin/cargo build --release --bin rustfs --target-dir /root/s3-rustfs/target/$(BUILD_OS)
build-docker: ## Build using Docker container # e.g (make build-docker BUILD_OS=ubuntu22.04)
@echo "🐳 Building RustFS using Docker ($(BUILD_OS))..."
$(DOCKER_CLI) buildx build -t $(SOURCE_BUILD_IMAGE_NAME) -f $(DOCKERFILE_SOURCE) .
$(DOCKER_CLI) buildx build -t $(SOURCE_BUILD_IMAGE_NAME) -f $(DOCKERFILE_SOURCE) --load .
$(DOCKER_CLI) run --rm --name $(SOURCE_BUILD_CONTAINER_NAME) -v $(shell pwd):/root/s3-rustfs -it $(SOURCE_BUILD_IMAGE_NAME) $(BUILD_CMD)
.PHONY: docker-inspect-multiarch
@@ -19,4 +19,4 @@ docker-inspect-multiarch: ## Check image architecture support
exit 1; \
fi
@echo "🔍 Inspecting multi-architecture image: $(IMAGE)"
docker buildx imagetools inspect $(IMAGE)
docker buildx imagetools inspect $(IMAGE)
+1 -1
View File
@@ -4,7 +4,7 @@
.PHONY: help
help: ## Shows This Help Menu
echo -e "$$HEADER"
grep -E '(^[a-zA-Z0-9_-]+:.*?## .*$$)|(^## )' $(MAKEFILE_LIST) | sed 's/^[^:]*://g' | awk 'BEGIN {FS = ":.*?## | #"} ; {printf "${cyan}%-30s${reset} ${white}%s${reset} ${green}%s${reset}\n", $$1, $$2, $$3}' | sed -e 's/\[36m##/\n[32m##/'
grep -E '(^[a-zA-Z0-9_-]+:.*?## .*$$)|(^## )' $(MAKEFILE_LIST) | sed 's/^[^:]*://g' | awk 'BEGIN {FS = ":.*?## | #"} /^## / {printf "\n${green}%s${reset}\n", $$0; next} {printf "${cyan}%-30s${reset} ${white}%s${reset} ${green}%s${reset}\n", $$1, $$2, $$3}'
.PHONY: help-build
help-build: ## Shows RustFS build help
+35 -1
View File
@@ -1,5 +1,7 @@
## —— Code quality and Formatting ------------------------------------------------------------------
.NOTPARALLEL: fix
.PHONY: fmt
fmt: core-deps fmt-deps ## Format code
@echo "🔧 Formatting code..."
@@ -13,9 +15,41 @@ fmt-check: core-deps fmt-deps ## Check code formatting
.PHONY: clippy-check
clippy-check: core-deps ## Run clippy checks
@echo "🔍 Running clippy checks..."
cargo clippy --fix --allow-dirty
cargo clippy --all-targets --all-features -- -D warnings
.PHONY: clippy-fix
clippy-fix: core-deps ## Apply clippy fixes
@echo "🔧 Applying clippy fixes..."
cargo clippy --fix --allow-dirty
.PHONY: fix
fix: fmt clippy-fix ## Format code and apply clippy fixes
.PHONY: quick-check
quick-check: core-deps ## Run fast workspace compilation check
@echo "🔨 Running fast compilation check..."
cargo check --workspace --exclude e2e_test
.PHONY: unsafe-code-check
unsafe-code-check: ## Check unsafe_code allowances have SAFETY comments
@echo "🔒 Checking unsafe_code allowances..."
./scripts/check_unsafe_code_allowances.sh
.PHONY: architecture-migration-check
architecture-migration-check: ## Check architecture migration guardrails
@echo "🏗️ Checking architecture migration guardrails..."
./scripts/check_architecture_migration_rules.sh
.PHONY: logging-guardrails-check
logging-guardrails-check: ## Check logging guardrails for redaction and noise regressions
@echo "🪵 Checking logging guardrails..."
./scripts/check_logging_guardrails.sh
.PHONY: tokio-io-uring-check
tokio-io-uring-check: ## Check tokio io-uring runtime feature stays removed
@echo "🚫 Checking tokio io-uring feature guard..."
./scripts/check_no_tokio_io_uring.sh
.PHONY: compilation-check
compilation-check: core-deps ## Run compilation check
@echo "🔨 Running compilation check..."
+17 -2
View File
@@ -1,11 +1,26 @@
## —— Pre Commit Checks ----------------------------------------------------------------------------
.NOTPARALLEL: pre-commit pre-pr dev-check
.PHONY: setup-hooks
setup-hooks: ## Set up git hooks
@echo "🔧 Setting up git hooks..."
chmod +x .git/hooks/pre-commit
@echo "✅ Git hooks setup complete!"
.PHONY: doc-paths-check
doc-paths-check: ## Check that instruction/architecture docs reference existing file paths
@echo "📄 Checking doc path references..."
./scripts/check_doc_paths.sh
.PHONY: pre-commit
pre-commit: fmt clippy-check compilation-check test ## Run pre-commit checks
@echo "✅ All pre-commit checks passed!"
pre-commit: fmt-check unsafe-code-check architecture-migration-check logging-guardrails-check tokio-io-uring-check doc-paths-check quick-check ## Run fast pre-commit checks without clippy/full tests
@echo "✅ All pre-commit checks passed!"
.PHONY: pre-pr
pre-pr: fmt-check unsafe-code-check architecture-migration-check logging-guardrails-check tokio-io-uring-check doc-paths-check clippy-check test ## Run full pre-PR checks with clippy and tests
@echo "✅ All pre-PR checks passed!"
.PHONY: dev-check
dev-check: fmt-check unsafe-code-check architecture-migration-check logging-guardrails-check tokio-io-uring-check doc-paths-check quick-check ## Run fast local development checks
@echo "✅ Fast development checks passed!"
+7 -1
View File
@@ -2,8 +2,14 @@
TEST_THREADS ?= 1
.PHONY: script-tests
script-tests: ## Run shell script tests
@echo "Running script tests..."
./scripts/test_build_rustfs_options.sh
./scripts/test_entrypoint_credentials.sh
.PHONY: test
test: core-deps test-deps ## Run all tests
test: core-deps test-deps script-tests ## Run all tests
@echo "🧪 Running tests..."
@if command -v cargo-nextest >/dev/null 2>&1; then \
cargo nextest run --all --exclude e2e_test; \
+58
View File
@@ -36,6 +36,64 @@ To start a 4-node cluster for distributed testing:
docker compose -f .docker/compose/docker-compose.cluster.yaml up -d
```
### Script-Based 4-Node Validation (Recommended)
Use the local validation script when you need local-source image build, failover checks,
and benchmark workflow in one command:
```bash
# Default mode: WAIT_PROBE_MODE=service
# This avoids false negatives where /health/ready remains 503 locally
# while the service path is already available.
./scripts/run_four_node_cluster_failover_bench.sh
```
Strict mode is available when you explicitly want `/health/ready == 200` as the gate:
```bash
WAIT_PROBE_MODE=ready ./scripts/run_four_node_cluster_failover_bench.sh
```
### Profiling + Trace Validation
The profiling-focused 4-node compose keeps profiling enabled and points RustFS
to an OTLP/HTTP collector endpoint:
```bash
docker compose -f .docker/compose/docker-compose.cluster.local-build.profiling-amd64.yml up -d
```
Important behavior notes:
- `RUSTFS_OBS_ENDPOINT` is the OTLP/HTTP base URL. RustFS automatically sends
traces to `/v1/traces`, metrics to `/v1/metrics`, and logs to `/v1/logs`.
- Startup usually produces logs and metrics first. That does not guarantee
visible traces yet.
- Trace data becomes obvious only after real HTTP/S3/gRPC requests hit RustFS.
- `RUSTFS_OBS_LOGGER_LEVEL=info` keeps the top-level request span but filters
many nested `debug` spans. If Tempo/Jaeger looks sparse, retry with
`RUSTFS_OBS_LOGGER_LEVEL=debug` before suspecting the collector.
Minimal trace verification flow:
```bash
# 1. Start the profiling compose with richer span visibility.
RUSTFS_OBS_LOGGER_LEVEL=debug \
docker compose -f .docker/compose/docker-compose.cluster.local-build.profiling-amd64.yml up -d
# 2. Generate real request traffic after startup.
curl -I http://127.0.0.1:9000/health
curl -I http://127.0.0.1:9000/health/ready
# 3. Then inspect Tempo or Jaeger.
# Grafana: http://localhost:3000
# Jaeger: http://localhost:16686
```
If logs and metrics are present but traces are sparse, the most common cause is
"no real request traffic yet" or "`info` level filtered nested spans", not an
OTLP routing failure.
### (Deprecated) Minimal Observability
```bash
@@ -0,0 +1,236 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Profiling-first 4-node local-build compose.
#
# Goals:
# - force linux/amd64 runtime/build on Apple Silicon hosts;
# - enable RustFS built-in CPU profiling;
# - keep all tuning knobs host-overridable via env.
#
# Observability notes:
# - `RUSTFS_OBS_ENDPOINT` is the OTLP/HTTP base URL. RustFS appends
# `/v1/traces`, `/v1/metrics`, and `/v1/logs` automatically.
# - Logs and metrics usually appear during startup. Traces mainly appear after
# real HTTP/S3/gRPC requests create spans.
# - `RUSTFS_OBS_LOGGER_LEVEL=info` keeps the top-level request trace span but
# filters many `debug`-level nested spans. Use `debug` when validating trace
# richness rather than collector reachability.
services:
node1:
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/amd64}
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node1
environment:
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
# `info` is enough for startup logs/metrics. Use `debug` if Tempo/Jaeger
# should show richer nested spans during request-path verification.
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_PROFILING_ENDPOINT=${RUSTFS_OBS_PROFILING_ENDPOINT:-http://host.docker.internal:4040}
- RUSTFS_OBS_PROFILING_EXPORT_ENABLED=${RUSTFS_OBS_PROFILING_EXPORT_ENABLED:-true}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
- RUSTFS_ENABLE_PROFILING=${RUSTFS_ENABLE_PROFILING:-true}
- RUSTFS_PROF_CPU_MODE=${RUSTFS_PROF_CPU_MODE:-continuous}
- RUSTFS_PROF_CPU_FREQ=${RUSTFS_PROF_CPU_FREQ:-99}
- RUSTFS_PROF_OUTPUT_DIR=${RUSTFS_PROF_OUTPUT_DIR:-/tmp/rustfs-profiles}
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=${RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS:-48}
- RUSTFS_OBJECT_IO_BUFFER_SIZE=${RUSTFS_OBJECT_IO_BUFFER_SIZE:-262144}
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD:-6}
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD:-12}
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=${RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY:-8}
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=${RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE:-8388608}
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=${RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES:-25165824}
- RUSTFS_RUNTIME_WORKER_THREADS=${RUSTFS_RUNTIME_WORKER_THREADS:-12}
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=${RUSTFS_RUNTIME_MAX_BLOCKING_THREADS:-512}
- RUSTFS_ALLOCATOR_RECLAIM_ENABLED=${RUSTFS_ALLOCATOR_RECLAIM_ENABLED:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node1_data_0:/data/rustfs0
- node1_data_1:/data/rustfs1
- node1_data_2:/data/rustfs2
- node1_data_3:/data/rustfs3
ports:
- "9000:9000"
networks:
- rustfs-cluster-net
node2:
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/amd64}
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node2
environment:
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
# `info` is enough for startup logs/metrics. Use `debug` if Tempo/Jaeger
# should show richer nested spans during request-path verification.
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_PROFILING_ENDPOINT=${RUSTFS_OBS_PROFILING_ENDPOINT:-http://host.docker.internal:4040}
- RUSTFS_OBS_PROFILING_EXPORT_ENABLED=${RUSTFS_OBS_PROFILING_EXPORT_ENABLED:-true}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
- RUSTFS_ENABLE_PROFILING=${RUSTFS_ENABLE_PROFILING:-true}
- RUSTFS_PROF_CPU_MODE=${RUSTFS_PROF_CPU_MODE:-continuous}
- RUSTFS_PROF_CPU_FREQ=${RUSTFS_PROF_CPU_FREQ:-99}
- RUSTFS_PROF_OUTPUT_DIR=${RUSTFS_PROF_OUTPUT_DIR:-/tmp/rustfs-profiles}
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=${RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS:-48}
- RUSTFS_OBJECT_IO_BUFFER_SIZE=${RUSTFS_OBJECT_IO_BUFFER_SIZE:-262144}
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD:-6}
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD:-12}
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=${RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY:-8}
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=${RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE:-8388608}
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=${RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES:-25165824}
- RUSTFS_RUNTIME_WORKER_THREADS=${RUSTFS_RUNTIME_WORKER_THREADS:-12}
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=${RUSTFS_RUNTIME_MAX_BLOCKING_THREADS:-512}
- RUSTFS_ALLOCATOR_RECLAIM_ENABLED=${RUSTFS_ALLOCATOR_RECLAIM_ENABLED:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node2_data_0:/data/rustfs0
- node2_data_1:/data/rustfs1
- node2_data_2:/data/rustfs2
- node2_data_3:/data/rustfs3
ports:
- "9001:9000"
networks:
- rustfs-cluster-net
node3:
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/amd64}
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node3
environment:
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
# `info` is enough for startup logs/metrics. Use `debug` if Tempo/Jaeger
# should show richer nested spans during request-path verification.
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_PROFILING_ENDPOINT=${RUSTFS_OBS_PROFILING_ENDPOINT:-http://host.docker.internal:4040}
- RUSTFS_OBS_PROFILING_EXPORT_ENABLED=${RUSTFS_OBS_PROFILING_EXPORT_ENABLED:-true}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
- RUSTFS_ENABLE_PROFILING=${RUSTFS_ENABLE_PROFILING:-true}
- RUSTFS_PROF_CPU_MODE=${RUSTFS_PROF_CPU_MODE:-continuous}
- RUSTFS_PROF_CPU_FREQ=${RUSTFS_PROF_CPU_FREQ:-99}
- RUSTFS_PROF_OUTPUT_DIR=${RUSTFS_PROF_OUTPUT_DIR:-/tmp/rustfs-profiles}
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=${RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS:-48}
- RUSTFS_OBJECT_IO_BUFFER_SIZE=${RUSTFS_OBJECT_IO_BUFFER_SIZE:-262144}
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD:-6}
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD:-12}
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=${RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY:-8}
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=${RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE:-8388608}
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=${RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES:-25165824}
- RUSTFS_RUNTIME_WORKER_THREADS=${RUSTFS_RUNTIME_WORKER_THREADS:-12}
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=${RUSTFS_RUNTIME_MAX_BLOCKING_THREADS:-512}
- RUSTFS_ALLOCATOR_RECLAIM_ENABLED=${RUSTFS_ALLOCATOR_RECLAIM_ENABLED:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node3_data_0:/data/rustfs0
- node3_data_1:/data/rustfs1
- node3_data_2:/data/rustfs2
- node3_data_3:/data/rustfs3
ports:
- "9002:9000"
networks:
- rustfs-cluster-net
node4:
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/amd64}
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node4
environment:
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
# `info` is enough for startup logs/metrics. Use `debug` if Tempo/Jaeger
# should show richer nested spans during request-path verification.
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_PROFILING_ENDPOINT=${RUSTFS_OBS_PROFILING_ENDPOINT:-http://host.docker.internal:4040}
- RUSTFS_OBS_PROFILING_EXPORT_ENABLED=${RUSTFS_OBS_PROFILING_EXPORT_ENABLED:-true}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
- RUSTFS_ENABLE_PROFILING=${RUSTFS_ENABLE_PROFILING:-true}
- RUSTFS_PROF_CPU_MODE=${RUSTFS_PROF_CPU_MODE:-continuous}
- RUSTFS_PROF_CPU_FREQ=${RUSTFS_PROF_CPU_FREQ:-99}
- RUSTFS_PROF_OUTPUT_DIR=${RUSTFS_PROF_OUTPUT_DIR:-/tmp/rustfs-profiles}
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=${RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS:-48}
- RUSTFS_OBJECT_IO_BUFFER_SIZE=${RUSTFS_OBJECT_IO_BUFFER_SIZE:-262144}
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD:-6}
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=${RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD:-12}
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=${RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY:-8}
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=${RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE:-8388608}
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=${RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES:-25165824}
- RUSTFS_RUNTIME_WORKER_THREADS=${RUSTFS_RUNTIME_WORKER_THREADS:-12}
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=${RUSTFS_RUNTIME_MAX_BLOCKING_THREADS:-512}
- RUSTFS_ALLOCATOR_RECLAIM_ENABLED=${RUSTFS_ALLOCATOR_RECLAIM_ENABLED:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node4_data_0:/data/rustfs0
- node4_data_1:/data/rustfs1
- node4_data_2:/data/rustfs2
- node4_data_3:/data/rustfs3
ports:
- "9003:9000"
networks:
- rustfs-cluster-net
volumes:
node1_data_0:
node1_data_1:
node1_data_2:
node1_data_3:
node2_data_0:
node2_data_1:
node2_data_2:
node2_data_3:
node3_data_0:
node3_data_1:
node3_data_2:
node3_data_3:
node4_data_0:
node4_data_1:
node4_data_2:
node4_data_3:
networks:
rustfs-cluster-net:
driver: bridge
@@ -0,0 +1,220 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
services:
node1:
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node1
environment:
- RUSTFS_VOLUMES=${RUSTFS_VOLUMES:-http://node{1...4}:9000/data/rustfs{0...3}}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_USE_STDOUT=${RUSTFS_OBS_USE_STDOUT:-false}
- RUSTFS_OBS_LOG_STDOUT_ENABLED=${RUSTFS_OBS_LOG_STDOUT_ENABLED:-false}
- RUSTFS_ISSUE3031_DIAG_ENABLE=${RUSTFS_ISSUE3031_DIAG_ENABLE:-false}
- RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
# Internode gRPC optimization knobs (grpc-optimization P0-P3). Defaults match the binary
# defaults, so leaving these unset is a no-op; the A/B driver exports them to toggle a stage.
- RUSTFS_INTERNODE_RPC_TCP_NODELAY=${RUSTFS_INTERNODE_RPC_TCP_NODELAY:-true}
- RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE:-1048576}
- RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE:-2097152}
- RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE=${RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE:-104857600}
- RUSTFS_INTERNODE_CHANNEL_ISOLATION=${RUSTFS_INTERNODE_CHANNEL_ISOLATION:-false}
- RUSTFS_INTERNODE_BULK_CHANNELS=${RUSTFS_INTERNODE_BULK_CHANNELS:-2}
- RUSTFS_INTERNODE_RPC_MSGPACK_ONLY=${RUSTFS_INTERNODE_RPC_MSGPACK_ONLY:-false}
- RUSTFS_INTERNODE_PREWARM=${RUSTFS_INTERNODE_PREWARM:-false}
- RUSTFS_INTERNODE_OFFLINE_BYPASS=${RUSTFS_INTERNODE_OFFLINE_BYPASS:-false}
- RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS=${RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS:-5}
- RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD=${RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD:-3}
- RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES=${RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES:-0}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node1_data_0:/data/rustfs0
- node1_data_1:/data/rustfs1
- node1_data_2:/data/rustfs2
- node1_data_3:/data/rustfs3
ports:
- "9000:9000"
networks:
- rustfs-cluster-net
node2:
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node2
environment:
- RUSTFS_VOLUMES=${RUSTFS_VOLUMES:-http://node{1...4}:9000/data/rustfs{0...3}}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_USE_STDOUT=${RUSTFS_OBS_USE_STDOUT:-false}
- RUSTFS_OBS_LOG_STDOUT_ENABLED=${RUSTFS_OBS_LOG_STDOUT_ENABLED:-false}
- RUSTFS_ISSUE3031_DIAG_ENABLE=${RUSTFS_ISSUE3031_DIAG_ENABLE:-false}
- RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
# Internode gRPC optimization knobs (grpc-optimization P0-P3). Defaults match the binary
# defaults, so leaving these unset is a no-op; the A/B driver exports them to toggle a stage.
- RUSTFS_INTERNODE_RPC_TCP_NODELAY=${RUSTFS_INTERNODE_RPC_TCP_NODELAY:-true}
- RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE:-1048576}
- RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE:-2097152}
- RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE=${RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE:-104857600}
- RUSTFS_INTERNODE_CHANNEL_ISOLATION=${RUSTFS_INTERNODE_CHANNEL_ISOLATION:-false}
- RUSTFS_INTERNODE_BULK_CHANNELS=${RUSTFS_INTERNODE_BULK_CHANNELS:-2}
- RUSTFS_INTERNODE_RPC_MSGPACK_ONLY=${RUSTFS_INTERNODE_RPC_MSGPACK_ONLY:-false}
- RUSTFS_INTERNODE_PREWARM=${RUSTFS_INTERNODE_PREWARM:-false}
- RUSTFS_INTERNODE_OFFLINE_BYPASS=${RUSTFS_INTERNODE_OFFLINE_BYPASS:-false}
- RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS=${RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS:-5}
- RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD=${RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD:-3}
- RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES=${RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES:-0}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node2_data_0:/data/rustfs0
- node2_data_1:/data/rustfs1
- node2_data_2:/data/rustfs2
- node2_data_3:/data/rustfs3
ports:
- "9001:9000"
networks:
- rustfs-cluster-net
node3:
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node3
environment:
- RUSTFS_VOLUMES=${RUSTFS_VOLUMES:-http://node{1...4}:9000/data/rustfs{0...3}}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_USE_STDOUT=${RUSTFS_OBS_USE_STDOUT:-false}
- RUSTFS_OBS_LOG_STDOUT_ENABLED=${RUSTFS_OBS_LOG_STDOUT_ENABLED:-false}
- RUSTFS_ISSUE3031_DIAG_ENABLE=${RUSTFS_ISSUE3031_DIAG_ENABLE:-false}
- RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
# Internode gRPC optimization knobs (grpc-optimization P0-P3). Defaults match the binary
# defaults, so leaving these unset is a no-op; the A/B driver exports them to toggle a stage.
- RUSTFS_INTERNODE_RPC_TCP_NODELAY=${RUSTFS_INTERNODE_RPC_TCP_NODELAY:-true}
- RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE:-1048576}
- RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE:-2097152}
- RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE=${RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE:-104857600}
- RUSTFS_INTERNODE_CHANNEL_ISOLATION=${RUSTFS_INTERNODE_CHANNEL_ISOLATION:-false}
- RUSTFS_INTERNODE_BULK_CHANNELS=${RUSTFS_INTERNODE_BULK_CHANNELS:-2}
- RUSTFS_INTERNODE_RPC_MSGPACK_ONLY=${RUSTFS_INTERNODE_RPC_MSGPACK_ONLY:-false}
- RUSTFS_INTERNODE_PREWARM=${RUSTFS_INTERNODE_PREWARM:-false}
- RUSTFS_INTERNODE_OFFLINE_BYPASS=${RUSTFS_INTERNODE_OFFLINE_BYPASS:-false}
- RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS=${RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS:-5}
- RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD=${RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD:-3}
- RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES=${RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES:-0}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node3_data_0:/data/rustfs0
- node3_data_1:/data/rustfs1
- node3_data_2:/data/rustfs2
- node3_data_3:/data/rustfs3
ports:
- "9002:9000"
networks:
- rustfs-cluster-net
node4:
image: ${RUSTFS_IMAGE:-rustfs/rustfs:local-4node}
build:
context: ../..
dockerfile: Dockerfile.source
hostname: node4
environment:
- RUSTFS_VOLUMES=${RUSTFS_VOLUMES:-http://node{1...4}:9000/data/rustfs{0...3}}
- RUSTFS_ADDRESS=:9000
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
- RUSTFS_OBS_USE_STDOUT=${RUSTFS_OBS_USE_STDOUT:-false}
- RUSTFS_OBS_LOG_STDOUT_ENABLED=${RUSTFS_OBS_LOG_STDOUT_ENABLED:-false}
- RUSTFS_ISSUE3031_DIAG_ENABLE=${RUSTFS_ISSUE3031_DIAG_ENABLE:-false}
- RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_OBJECT_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_LOCK_ACQUIRE_TIMEOUT=${RUSTFS_LOCK_ACQUIRE_TIMEOUT:-5}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
# Internode gRPC optimization knobs (grpc-optimization P0-P3). Defaults match the binary
# defaults, so leaving these unset is a no-op; the A/B driver exports them to toggle a stage.
- RUSTFS_INTERNODE_RPC_TCP_NODELAY=${RUSTFS_INTERNODE_RPC_TCP_NODELAY:-true}
- RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_STREAM_WINDOW_SIZE:-1048576}
- RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE=${RUSTFS_INTERNODE_RPC_HTTP2_CONN_WINDOW_SIZE:-2097152}
- RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE=${RUSTFS_INTERNODE_RPC_MAX_MESSAGE_SIZE:-104857600}
- RUSTFS_INTERNODE_CHANNEL_ISOLATION=${RUSTFS_INTERNODE_CHANNEL_ISOLATION:-false}
- RUSTFS_INTERNODE_BULK_CHANNELS=${RUSTFS_INTERNODE_BULK_CHANNELS:-2}
- RUSTFS_INTERNODE_RPC_MSGPACK_ONLY=${RUSTFS_INTERNODE_RPC_MSGPACK_ONLY:-false}
- RUSTFS_INTERNODE_PREWARM=${RUSTFS_INTERNODE_PREWARM:-false}
- RUSTFS_INTERNODE_OFFLINE_BYPASS=${RUSTFS_INTERNODE_OFFLINE_BYPASS:-false}
- RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS=${RUSTFS_INTERNODE_OFFLINE_REPROBE_SECS:-5}
- RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD=${RUSTFS_INTERNODE_OFFLINE_FAILURE_THRESHOLD:-3}
- RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES=${RUSTFS_INTERNODE_IDEMPOTENT_READ_RETRIES:-0}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- node4_data_0:/data/rustfs0
- node4_data_1:/data/rustfs1
- node4_data_2:/data/rustfs2
- node4_data_3:/data/rustfs3
ports:
- "9003:9000"
networks:
- rustfs-cluster-net
volumes:
node1_data_0:
node1_data_1:
node1_data_2:
node1_data_3:
node2_data_0:
node2_data_1:
node2_data_2:
node2_data_3:
node3_data_0:
node3_data_1:
node3_data_2:
node3_data_3:
node4_data_0:
node4_data_1:
node4_data_2:
node4_data_3:
networks:
rustfs-cluster-net:
driver: bridge
@@ -0,0 +1,56 @@
services:
node1:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=56
- RUSTFS_OBJECT_IO_BUFFER_SIZE=131072
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=10
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=20
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=10
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=16
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=768
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=420
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=50
node2:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=56
- RUSTFS_OBJECT_IO_BUFFER_SIZE=131072
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=10
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=20
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=10
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=16
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=768
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=420
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=50
node3:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=56
- RUSTFS_OBJECT_IO_BUFFER_SIZE=131072
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=10
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=20
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=10
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=16
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=768
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=420
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=50
node4:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=56
- RUSTFS_OBJECT_IO_BUFFER_SIZE=131072
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=10
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=20
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=10
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=16
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=768
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=420
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=50
@@ -0,0 +1,56 @@
services:
node1:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=96
- RUSTFS_OBJECT_IO_BUFFER_SIZE=524288
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=32
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=24
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=33554432
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=50331648
- RUSTFS_RUNTIME_WORKER_THREADS=24
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1536
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=600
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=80
node2:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=96
- RUSTFS_OBJECT_IO_BUFFER_SIZE=524288
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=32
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=24
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=33554432
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=50331648
- RUSTFS_RUNTIME_WORKER_THREADS=24
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1536
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=600
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=80
node3:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=96
- RUSTFS_OBJECT_IO_BUFFER_SIZE=524288
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=32
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=24
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=33554432
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=50331648
- RUSTFS_RUNTIME_WORKER_THREADS=24
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1536
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=600
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=80
node4:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=96
- RUSTFS_OBJECT_IO_BUFFER_SIZE=524288
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=32
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=24
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=33554432
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=50331648
- RUSTFS_RUNTIME_WORKER_THREADS=24
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1536
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=600
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=80
@@ -0,0 +1,56 @@
services:
node1:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=64
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=24
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=16
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=16777216
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=33554432
- RUSTFS_RUNTIME_WORKER_THREADS=20
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1024
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=60
node2:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=64
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=24
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=16
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=16777216
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=33554432
- RUSTFS_RUNTIME_WORKER_THREADS=20
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1024
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=60
node3:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=64
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=24
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=16
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=16777216
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=33554432
- RUSTFS_RUNTIME_WORKER_THREADS=20
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1024
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=60
node4:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=64
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=24
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=16
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=16777216
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=33554432
- RUSTFS_RUNTIME_WORKER_THREADS=20
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=1024
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=60
@@ -0,0 +1,56 @@
services:
node1:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=48
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=8
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=12
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=12
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=512
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=40
node2:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=48
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=8
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=12
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=12
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=512
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=40
node3:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=48
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=8
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=12
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=12
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=512
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=40
node4:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=48
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=8
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=16
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=12
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=25165824
- RUSTFS_RUNTIME_WORKER_THREADS=12
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=512
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=40
@@ -0,0 +1,56 @@
services:
node1:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=32
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=6
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=8
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=16777216
- RUSTFS_RUNTIME_WORKER_THREADS=6
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=256
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=30
node2:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=32
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=6
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=8
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=16777216
- RUSTFS_RUNTIME_WORKER_THREADS=6
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=256
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=30
node3:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=32
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=6
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=8
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=16777216
- RUSTFS_RUNTIME_WORKER_THREADS=6
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=256
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=30
node4:
environment:
- RUSTFS_OBJECT_MAX_CONCURRENT_DISK_READS=32
- RUSTFS_OBJECT_IO_BUFFER_SIZE=262144
- RUSTFS_OBJECT_MEDIUM_CONCURRENCY_THRESHOLD=6
- RUSTFS_OBJECT_HIGH_CONCURRENCY_THRESHOLD=12
- RUSTFS_OBJECT_IO_RANDOM_READAHEAD_DISABLE_CONCURRENCY=8
- RUSTFS_OBJECT_DUPLEX_BUFFER_SIZE=8388608
- RUSTFS_ERASURE_ENCODE_MAX_INFLIGHT_BYTES=16777216
- RUSTFS_RUNTIME_WORKER_THREADS=6
- RUSTFS_RUNTIME_MAX_BLOCKING_THREADS=256
- RUSTFS_CAPACITY_SCHEDULED_INTERVAL=300
- RUSTFS_CAPACITY_WRITE_FREQUENCY_THRESHOLD=30
@@ -26,7 +26,7 @@ services:
restart: "no"
tempo:
image: grafana/tempo:latest
image: grafana/tempo:2.10.5
user: "10001"
command: [ "-config.file=/etc/tempo.yaml" ]
volumes:
@@ -36,9 +36,19 @@ services:
- "3200:3200" # tempo
- "4317" # otlp grpc
- "4318" # otlp http
- "7946" # memberlist
restart: unless-stopped
networks:
- rustfs-network
depends_on:
tempo-init:
condition: service_completed_successfully
healthcheck:
test: [ "CMD", "/tempo", "-version" ]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
@@ -61,6 +71,12 @@ services:
- jaeger
- prometheus
- loki
restart: unless-stopped
healthcheck:
test: [ "CMD", "/otelcol-contrib", "--version" ]
interval: 10s
timeout: 5s
retries: 3
jaeger:
image: jaegertracing/jaeger:latest
@@ -72,12 +88,23 @@ services:
- BADGER_DIRECTORY_KEY=/badger/key
- COLLECTOR_OTLP_ENABLED=true
volumes:
- ../../.docker/observability/jaeger.yaml:/etc/jaeger/config.yml:ro
- jaeger-data:/badger
ports:
- "16686:16686" # Web UI
- "14269:14269" # Admin/Metrics
- "4317" # otlp grpc
- "4318" # otlp http
command: [ "--config", "/etc/jaeger/config.yml" ]
networks:
- rustfs-network
restart: unless-stopped
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:14269" ]
interval: 10s
timeout: 5s
retries: 3
start_period: 15s
prometheus:
image: prom/prometheus:latest
@@ -85,6 +112,7 @@ services:
- TZ=Asia/Shanghai
volumes:
- ../../.docker/observability/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ../../.docker/observability/prometheus-rules:/etc/prometheus/rules:ro
- prometheus-data:/prometheus
ports:
- "9090:9090"
@@ -94,23 +122,45 @@ services:
- '--web.enable-remote-write-receiver'
- '--enable-feature=promql-experimental-functions'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
- '--storage.tsdb.retention.time=30d'
networks:
- rustfs-network
restart: unless-stopped
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy" ]
interval: 10s
timeout: 5s
retries: 3
loki:
image: grafana/loki:latest
environment:
- TZ=Asia/Shanghai
volumes:
- ../../.docker/observability/loki.yaml:/etc/loki/local-config.yaml:ro
- ../../.docker/observability/loki.yaml:/etc/loki/loki.yaml:ro
- loki-data:/loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
command: -config.file=/etc/loki/loki.yaml
networks:
- rustfs-network
restart: unless-stopped
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ]
interval: 15s
timeout: 10s
retries: 5
start_period: 60s
pyroscope:
image: grafana/pyroscope:latest
ports:
- "4040:4040"
command:
- -self-profiling.disable-push=true
networks:
- rustfs-network
restart: unless-stopped
grafana:
image: grafana/grafana:latest
@@ -127,10 +177,17 @@ services:
volumes:
- ../../.docker/observability/grafana/provisioning:/etc/grafana/provisioning:ro
- ../../.docker/observability/grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana-data:/var/lib/grafana
depends_on:
- prometheus
- tempo
- loki
restart: unless-stopped
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health" ]
interval: 10s
timeout: 5s
retries: 3
# --- RustFS Cluster ---
@@ -215,6 +272,7 @@ volumes:
tempo-data:
loki-data:
jaeger-data:
grafana-data:
networks:
rustfs-network:
+11
View File
@@ -74,6 +74,17 @@ http {
# ssl_certificate /etc/nginx/ssl/server.crt;
# ssl_certificate_key /etc/nginx/ssl/server.key;
#
# # Restrict to modern TLS versions and ciphers. Operators copying this
# # example must keep at least these directives — without them, nginx
# # may negotiate older protocol versions that have known weaknesses.
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_prefer_server_ciphers on;
# ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
# ssl_session_timeout 1d;
# ssl_session_cache shared:SSL:10m;
# ssl_session_tickets off;
# # add_header Strict-Transport-Security "max-age=63072000" always;
#
# location / {
# proxy_pass http://rustfs:9000;
# ...
+98
View File
@@ -34,6 +34,56 @@ If you want the Kafka-backed HA Tempo path, use `docker-compose-example-for-rust
- **High Performance**: Optimized configurations for batching, compression, and memory management.
- **Standardized Protocols**: Built entirely on OpenTelemetry standards.
## GET Performance Optimization Dashboards
Three pre-built Grafana dashboards are included for monitoring RustFS GET performance optimization rollout:
### Available Dashboards
| Dashboard | File | Description |
|-----------|------|-------------|
| **GET Rollout Health** | `grafana-get-rollout-health.json` | Monitors optimization rollout: latency by reader path, early-stop hit rate, codec streaming usage, pipeline failures |
| **GET Data Integrity** | `grafana-get-data-integrity.json` | Monitors data safety: bitrot verify failures, decode errors, short reads, shard read outcomes |
| **GET Resource Impact** | `grafana-get-resource-impact.json` | Monitors resource usage: concurrent requests, IO queue utilization, disk permit wait, RSS trend |
### Prometheus Alert Rules
The file `prometheus-rules/rustfs-get-optimization-alerts.yaml` contains pre-configured alerting rules:
| Alert | Severity | Condition |
|-------|----------|-----------|
| `GetP99Regression` | Critical | GET p99 latency > 2x baseline for 10m |
| `PipelineFailureSpike` | Critical | Pipeline failure rate > 5x baseline for 5m |
| `BitrotMismatchSpike` | Critical | Bitrot mismatch rate > 3x baseline for 5m |
| `EarlyStopInsufficientQuorum` | Warning | Early-stop insufficient quorum rate > 0.1/s for 5m |
| `CodecStreamingFallbackSpike` | Warning | Codec streaming fallback > 10x baseline for 10m |
| `IoQueueSaturation` | Warning | IO queue utilization > 90% for 5m |
### Enabling Alert Rules
Add the alert rules file to your Prometheus configuration:
```yaml
# prometheus.yml
rule_files:
- "/etc/prometheus/rules/*.yml"
# Or mount the file in docker-compose.yml:
# volumes:
# - ./prometheus-rules:/etc/prometheus/rules
```
### Dashboard Usage
The dashboards are automatically provisioned when Grafana starts. They use the `${DS_PROMETHEUS}` datasource variable, so you need a Prometheus datasource configured in Grafana.
Key panels to monitor during optimization rollout:
1. **GET Latency by Reader Path** - Compare `codec_streaming` vs `legacy_duplex` latency
2. **Early-Stop Hit Rate** - Verify early-stop is triggering effectively
3. **Pipeline Failure Rate** - Detect any new failure modes introduced by optimizations
4. **Bitrot Verify Failures** - Ensure data integrity is maintained
## Quick Start
### Prerequisites
@@ -90,6 +140,54 @@ docker compose down -v
- **Grafana**: Dashboards and datasources are provisioned from the `grafana/` directory.
- **Collector**: Edit `otel-collector-config.yaml` to modify pipelines, processors, or exporters.
### Verifying RustFS Traces
When RustFS points `RUSTFS_OBS_ENDPOINT` at this stack, treat the value as the
OTLP/HTTP base URL, for example:
```bash
export RUSTFS_OBS_ENDPOINT=http://host.docker.internal:4318
```
RustFS automatically expands that base URL to:
- `/v1/traces`
- `/v1/metrics`
- `/v1/logs`
Important behavior notes:
- Logs and metrics usually appear during startup, so seeing those two signals
first is expected.
- Visible trace data usually requires real HTTP/S3/gRPC request traffic after
startup, because request-path spans are created on demand.
- `RUSTFS_OBS_LOGGER_LEVEL=info` keeps the top-level request span but filters
many nested `debug` spans. If Tempo or Jaeger looks sparse, retry with
`RUSTFS_OBS_LOGGER_LEVEL=debug` before suspecting collector or Tempo issues.
Minimal validation flow:
```bash
# 1. Start this observability stack.
docker compose up -d
# 2. Start RustFS with OTLP/HTTP export and richer span visibility.
export RUSTFS_OBS_ENDPOINT=http://host.docker.internal:4318
export RUSTFS_OBS_LOGGER_LEVEL=debug
# 3. Generate real request traffic.
curl -I http://127.0.0.1:9000/health
curl -I http://127.0.0.1:9000/health/ready
# 4. Inspect Grafana or Jaeger.
# Grafana: http://localhost:3000
# Jaeger: http://localhost:16686
```
If logs and metrics are present but traces are sparse, the most common cause is
"no real request traffic yet" or "`info` level filtered nested spans", not an
OTLP routing failure.
## Troubleshooting
- **Service Health**: Check the health of services using `docker compose ps`.
+94
View File
@@ -34,6 +34,56 @@
- **高性能**: 针对批处理、压缩和内存管理进行了优化配置。
- **标准化协议**: 完全基于 OpenTelemetry 标准构建。
## GET 性能优化仪表盘
包含三个预构建的 Grafana 仪表盘,用于监控 RustFS GET 性能优化发布:
### 可用仪表盘
| 仪表盘 | 文件 | 描述 |
|--------|------|------|
| **GET 发布健康度** | `grafana-get-rollout-health.json` | 监控优化发布:按 reader path 的延迟、early-stop 命中率、codec streaming 使用率、pipeline 失败率 |
| **GET 数据完整性** | `grafana-get-data-integrity.json` | 监控数据安全:bitrot 校验失败、decode 错误、short read、shard 读取结果 |
| **GET 资源影响** | `grafana-get-resource-impact.json` | 监控资源使用:并发请求数、IO 队列利用率、disk permit 等待、RSS 趋势 |
### Prometheus 告警规则
文件 `prometheus-rules/rustfs-get-optimization-alerts.yaml` 包含预配置的告警规则:
| 告警 | 级别 | 条件 |
|------|------|------|
| `GetP99Regression` | 严重 | GET p99 延迟 > 2x 基线,持续 10 分钟 |
| `PipelineFailureSpike` | 严重 | Pipeline 失败率 > 5x 基线,持续 5 分钟 |
| `BitrotMismatchSpike` | 严重 | Bitrot 不匹配率 > 3x 基线,持续 5 分钟 |
| `EarlyStopInsufficientQuorum` | 警告 | Early-stop quorum 不足率 > 0.1/s,持续 5 分钟 |
| `CodecStreamingFallbackSpike` | 警告 | Codec streaming 回退 > 10x 基线,持续 10 分钟 |
| `IoQueueSaturation` | 警告 | IO 队列利用率 > 90%,持续 5 分钟 |
### 启用告警规则
在 Prometheus 配置中添加告警规则文件:
```yaml
# prometheus.yml
rule_files:
- "/etc/prometheus/rules/*.yml"
# 或在 docker-compose.yml 中挂载文件:
# volumes:
# - ./prometheus-rules:/etc/prometheus/rules
```
### 仪表盘使用
仪表盘在 Grafana 启动时自动预置。它们使用 `${DS_PROMETHEUS}` 数据源变量,因此需要在 Grafana 中配置 Prometheus 数据源。
优化发布期间需要关注的关键面板:
1. **GET 延迟按 Reader Path** - 对比 `codec_streaming` vs `legacy_duplex` 延迟
2. **Early-Stop 命中率** - 验证 early-stop 是否有效触发
3. **Pipeline 失败率** - 检测优化引入的新故障模式
4. **Bitrot 校验失败** - 确保数据完整性
## 快速开始
### 前置条件
@@ -90,6 +140,50 @@ docker compose down -v
- **Grafana**: 仪表盘和数据源从 `grafana/` 目录预置。
- **Collector**: 编辑 `otel-collector-config.yaml` 以修改管道、处理器或导出器。
### 验证 RustFS Trace
当 RustFS 将 `RUSTFS_OBS_ENDPOINT` 指向这套技术栈时,应将该值视为
OTLP/HTTP 的基础 URL,例如:
```bash
export RUSTFS_OBS_ENDPOINT=http://host.docker.internal:4318
```
RustFS 会自动在该基础 URL 后补全:
- `/v1/traces`
- `/v1/metrics`
- `/v1/logs`
需要注意:
- 启动阶段通常会先看到日志和指标,因此“先有日志/指标、后有 trace”是正常现象。
- 可见的 trace 数据通常依赖启动后的真实 HTTP/S3/gRPC 请求流量,因为请求路径上的 span 是按需创建的。
- `RUSTFS_OBS_LOGGER_LEVEL=info` 会保留顶层请求 span,但会过滤掉很多 `debug` 级别的嵌套 span。
如果 Tempo 或 Jaeger 中的 trace 看起来很稀疏,建议先改成 `RUSTFS_OBS_LOGGER_LEVEL=debug`,再判断是否是 collector 或 Tempo 问题。
最小验证流程:
```bash
# 1. 启动本目录下的可观测性技术栈。
docker compose up -d
# 2. 以 OTLP/HTTP 导出方式启动 RustFS,并提高 span 可见性。
export RUSTFS_OBS_ENDPOINT=http://host.docker.internal:4318
export RUSTFS_OBS_LOGGER_LEVEL=debug
# 3. 产生真实请求流量。
curl -I http://127.0.0.1:9000/health
curl -I http://127.0.0.1:9000/health/ready
# 4. 到 Grafana 或 Jaeger 中检查。
# Grafana: http://localhost:3000
# Jaeger: http://localhost:16686
```
如果日志和指标已经正常,但 trace 仍然稀疏,最常见的原因通常是
“还没有真实请求流量”或“`info` 级别过滤了嵌套 span”,而不是 OTLP 路由失败。
## 故障排除
- **服务健康**: 使用 `docker compose ps` 检查服务健康状况。
+4 -3
View File
@@ -63,7 +63,7 @@ services:
- jaeger-data:/badger
ports:
- "16686:16686" # Web UI
- "14269:14269" # Admin/Metrics
- "18888:8888" # Metrics
- "4317" # otlp grpc
- "4318" # otlp http
command: [ "--config", "/etc/jaeger/config.yml" ]
@@ -71,7 +71,7 @@ services:
- otel-network
restart: unless-stopped
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:14269" ]
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:8888/metrics" ]
interval: 10s
timeout: 5s
retries: 3
@@ -84,6 +84,7 @@ services:
container_name: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus-rules:/etc/prometheus/rules:ro
- prometheus-data:/prometheus
ports:
- "9090:9090"
@@ -117,7 +118,7 @@ services:
- otel-network
restart: unless-stopped
healthcheck:
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ]
test: [ "CMD", "/usr/bin/loki", "--version" ]
interval: 15s
timeout: 10s
retries: 5
@@ -0,0 +1,500 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 20,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "line+area"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max", "sum"]
}
},
"title": "Bitrot Verify Failures",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_pipeline_failures_total{stage=\"bitrot_verify\"}[$__rate_interval])) by (path, reason)",
"legendFormat": "bitrot_fail {{path}} / {{reason}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_pipeline_failures_total{reason=\"bitrot_verify_failed\"}[$__rate_interval])) by (path, stage)",
"legendFormat": "verify_failed {{path}} / {{stage}}",
"refId": "B"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Bitrot Verify Duration (p50 / p95 / p99)",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.50, sum(rate(rustfs_io_get_object_shard_bitrot_verify_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p50",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum(rate(rustfs_io_get_object_shard_bitrot_verify_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p95",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum(rate(rustfs_io_get_object_shard_bitrot_verify_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p99",
"refId": "C"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 20,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "line+area"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max", "sum"]
}
},
"title": "Decode Errors",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_pipeline_failures_total{reason=\"decode_error\"}[$__rate_interval])) by (path, stage)",
"legendFormat": "decode_error {{path}} / {{stage}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 20,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "line+area"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 4,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max", "sum"]
}
},
"title": "Short Reads",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_pipeline_failures_total{reason=\"short_read\"}[$__rate_interval])) by (path, stage)",
"legendFormat": "short_read {{path}} / {{stage}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 5,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Shard Read Outcomes by Role",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_shard_read_total[$__rate_interval])) by (path, role, outcome)",
"legendFormat": "{{path}} {{role}}/{{outcome}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"id": 6,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Request Result Status Rate",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_request_results_total[$__rate_interval])) by (status)",
"legendFormat": "{{status}}",
"refId": "A"
}
]
}
],
"refresh": "30s",
"schemaVersion": 38,
"style": "dark",
"tags": ["rustfs", "get-optimization", "data-integrity"],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "RustFS GET Data Integrity",
"uid": "rustfs-get-data-integrity",
"version": 1
}
@@ -0,0 +1,716 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_strategy_total[$__rate_interval])) or vector(0)",
"legendFormat": "{{strategy}} / {{mode}}",
"refId": "A"
}
],
"title": "GET Reader Setup Strategy Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path, strategy, size_bucket) (rate(rustfs_io_get_object_reader_setup_strategy_by_size_total[$__rate_interval])) or vector(0)",
"legendFormat": "{{path}} / {{strategy}} / {{size_bucket}}",
"refId": "A"
}
],
"title": "GET Reader Setup Strategy by Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_scheduled_sum[$__rate_interval])) / clamp_min(sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_scheduled_count[$__rate_interval])), 1) or vector(0)",
"legendFormat": "scheduled {{strategy}} / {{mode}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_attempted_sum[$__rate_interval])) / clamp_min(sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_attempted_count[$__rate_interval])), 1) or vector(0)",
"legendFormat": "attempted {{strategy}} / {{mode}}",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_ready_sum[$__rate_interval])) / clamp_min(sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_ready_count[$__rate_interval])), 1) or vector(0)",
"legendFormat": "ready {{strategy}} / {{mode}}",
"refId": "C"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_deferred_sum[$__rate_interval])) / clamp_min(sum by (strategy, mode) (rate(rustfs_io_get_object_reader_setup_deferred_count[$__rate_interval])), 1) or vector(0)",
"legendFormat": "deferred {{strategy}} / {{mode}}",
"refId": "D"
}
],
"title": "GET Reader Setup Fanout Average",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 4,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path, strategy, size_bucket) (rate(rustfs_io_get_object_reader_setup_scheduled_by_size_sum[$__rate_interval])) / clamp_min(sum by (path, strategy, size_bucket) (rate(rustfs_io_get_object_reader_setup_scheduled_by_size_count[$__rate_interval])), 1) or vector(0)",
"legendFormat": "scheduled {{path}} / {{strategy}} / {{size_bucket}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path, strategy, size_bucket) (rate(rustfs_io_get_object_reader_setup_ready_by_size_sum[$__rate_interval])) / clamp_min(sum by (path, strategy, size_bucket) (rate(rustfs_io_get_object_reader_setup_ready_by_size_count[$__rate_interval])), 1) or vector(0)",
"legendFormat": "ready {{path}} / {{strategy}} / {{size_bucket}}",
"refId": "B"
}
],
"title": "GET Reader Setup Fanout by Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 5,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum by (path, stage, size_bucket, le) (rate(rustfs_io_get_object_stage_duration_seconds_by_size_bucket{stage=~\"reader_setup|reader_setup_schedule|reader_setup_wait_quorum|reader_setup_drop_pending|reader_task_file_open|reader_task_reader_construction|reader_task_bitrot_reader_init\"}[$__rate_interval]))) or vector(0)",
"legendFormat": "p95 {{path}} / {{stage}} / {{size_bucket}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum by (path, stage, size_bucket, le) (rate(rustfs_io_get_object_stage_duration_seconds_by_size_bucket{stage=~\"reader_setup|reader_setup_schedule|reader_setup_wait_quorum|reader_setup_drop_pending|reader_task_file_open|reader_task_reader_construction|reader_task_bitrot_reader_init\"}[$__rate_interval]))) or vector(0)",
"legendFormat": "p99 {{path}} / {{stage}} / {{size_bucket}}",
"refId": "B"
}
],
"title": "GET Hot Stage Latency by Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"id": 6,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum by (path, stage, le) (rate(rustfs_io_get_object_stage_duration_seconds_bucket{stage=~\"request_context|first_byte|response_handoff|full_body|body_build|output_poll|output_lock_wait|reader_open_mmap_copy_success|reader_open_mmap_copy_fallback|reader_open_stream|reader_stream_first_read|reader_mmap_blocking_wait|reader_mmap_blocking_task|reader_mmap_file_open|reader_mmap_map|reader_mmap_copy_buffer|reader_mmap_direct_read_copy\"}[$__rate_interval]))) or vector(0)",
"legendFormat": "p95 {{path}} / {{stage}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum by (path, stage, le) (rate(rustfs_io_get_object_stage_duration_seconds_bucket{stage=~\"request_context|first_byte|response_handoff|full_body|body_build|output_poll|output_lock_wait|reader_open_mmap_copy_success|reader_open_mmap_copy_fallback|reader_open_stream|reader_stream_first_read|reader_mmap_blocking_wait|reader_mmap_blocking_task|reader_mmap_file_open|reader_mmap_map|reader_mmap_copy_buffer|reader_mmap_direct_read_copy\"}[$__rate_interval]))) or vector(0)",
"legendFormat": "p99 {{path}} / {{stage}}",
"refId": "B"
}
],
"title": "GET Reader, Handler and Body Latency",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 24
},
"id": 7,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (subpath) (rate(rustfs_io_get_object_direct_memory_subpath_total[$__rate_interval])) or vector(0)",
"legendFormat": "{{subpath}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (subpath, size_bucket) (rate(rustfs_io_get_object_direct_memory_subpath_by_size_total[$__rate_interval])) or vector(0)",
"legendFormat": "{{subpath}} / {{size_bucket}}",
"refId": "B"
}
],
"title": "GET Direct-Memory Subpath Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 24
},
"id": 8,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path, stage, kind) (rate(rustfs_io_get_object_mmap_page_faults_total[$__rate_interval])) or vector(0)",
"legendFormat": "mmap {{path}} / {{stage}} / {{kind}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path, stage, kind) (rate(rustfs_io_get_object_direct_read_page_faults_total[$__rate_interval])) or vector(0)",
"legendFormat": "direct-read {{path}} / {{stage}} / {{kind}}",
"refId": "B"
}
],
"title": "GET mmap vs Direct-Read Page Fault Rate",
"type": "timeseries"
}
],
"refresh": "30s",
"schemaVersion": 38,
"style": "dark",
"tags": [
"rustfs",
"get",
"performance",
"attribution"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timezone": "",
"title": "RustFS GET Performance Attribution",
"uid": "rustfs-get-performance-attribution",
"version": 1,
"weekStart": ""
}
@@ -0,0 +1,525 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 20,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Concurrent GET Requests",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "rustfs_io_get_object_concurrent_requests",
"legendFormat": "concurrent GETs",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 20,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "line+area"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "transparent",
"value": null
},
{
"color": "yellow",
"value": 70
},
{
"color": "red",
"value": 90
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "IO Queue Utilization",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "rustfs_io_queue_utilization_percent",
"legendFormat": "queue utilization %",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "rustfs_io_queue_permits_in_use",
"legendFormat": "permits in use",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "rustfs_io_queue_permits_available",
"legendFormat": "permits available",
"refId": "C"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Disk Permit Wait Duration (p50 / p95 / p99)",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.50, sum(rate(rustfs_io_disk_permit_wait_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p50",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum(rate(rustfs_io_disk_permit_wait_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p95",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum(rate(rustfs_io_disk_permit_wait_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p99",
"refId": "C"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 15,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 4,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["lastNotNull"]
}
},
"title": "RSS Trend (Process Memory)",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "process_resident_memory_bytes{job=~\"rustfs.*\"}",
"legendFormat": "RSS {{instance}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "rustfs_process_memory_bytes",
"legendFormat": "RustFS memory {{instance}}",
"refId": "B"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 5,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "IO Queue Operations by Priority",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_queue_operations[$__rate_interval])) by (operation, priority)",
"legendFormat": "{{operation}} / {{priority}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 15,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"id": 6,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["sum"]
}
},
"title": "IO Queue Congestion Events",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_queue_congestion_total[$__rate_interval]))",
"legendFormat": "congestion events/s",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_timeout_total[$__rate_interval])) by (stage)",
"legendFormat": "timeout {{stage}}",
"refId": "B"
}
]
}
],
"refresh": "30s",
"schemaVersion": 38,
"style": "dark",
"tags": ["rustfs", "get-optimization", "resource-impact"],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "RustFS GET Resource Impact",
"uid": "rustfs-get-resource-impact",
"version": 1
}
@@ -0,0 +1,530 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "s",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"axisLabel": "",
"axisColorMode": "text",
"scaleDistribution": {
"type": "linear"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "GET Latency by Reader Path (p50 / p95 / p99)",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.50, sum(rate(rustfs_io_get_object_stage_duration_seconds_bucket{stage=\"total\"}[$__rate_interval])) by (le, path))",
"legendFormat": "p50 {{path}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum(rate(rustfs_io_get_object_stage_duration_seconds_bucket{stage=\"total\"}[$__rate_interval])) by (le, path))",
"legendFormat": "p95 {{path}}",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum(rate(rustfs_io_get_object_stage_duration_seconds_bucket{stage=\"total\"}[$__rate_interval])) by (le, path))",
"legendFormat": "p99 {{path}}",
"refId": "C"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"min": 0,
"max": 1,
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 15,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "yellow",
"value": 0.3
},
{
"color": "red",
"value": 0.7
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Early-Stop Hit Rate",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_metadata_early_stop_total{decision=\"hit\"}[$__rate_interval])) by (path) / clamp_min(sum(rate(rustfs_io_get_object_metadata_early_stop_total[$__rate_interval])) by (path), 1)",
"legendFormat": "hit rate {{path}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"min": 0,
"max": 1,
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 15,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "Codec Streaming Usage Rate",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_codec_streaming_decision_total[$__rate_interval])) by (decision) / clamp_min(sum(rate(rustfs_io_get_object_codec_streaming_decision_total[$__rate_interval])), 1)",
"legendFormat": "codec_streaming {{decision}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_stream_strategy_total[$__rate_interval])) by (strategy) / clamp_min(sum(rate(rustfs_io_get_object_stream_strategy_total[$__rate_interval])), 1)",
"legendFormat": "stream_strategy {{strategy}}",
"refId": "B"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "bars",
"fillOpacity": 80,
"lineWidth": 0,
"pointSize": 5,
"showPoints": "never",
"spanNulls": false,
"stacking": {
"mode": "normal",
"group": "A"
},
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 4,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["sum"]
}
},
"title": "Pipeline Failure Rate by Stage",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_pipeline_failures_total[$__rate_interval])) by (stage, reason)",
"legendFormat": "{{stage}} / {{reason}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "reqps",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 5,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "GET Request Rate by Path",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_io_get_object_reader_path_total[$__rate_interval])) by (path)",
"legendFormat": "{{path}}",
"refId": "A"
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"id": 6,
"options": {
"reduceOptions": {
"values": false,
"calcs": ["lastNotNull"],
"fields": ""
},
"tooltip": {
"mode": "multi",
"sort": "desc"
},
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": ["mean", "max"]
}
},
"title": "GET Total Latency (p50 / p95 / p99)",
"type": "timeseries",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.50, sum(rate(rustfs_io_get_object_total_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p50",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum(rate(rustfs_io_get_object_total_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p95",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum(rate(rustfs_io_get_object_total_duration_seconds_bucket[$__rate_interval])) by (le))",
"legendFormat": "p99",
"refId": "C"
}
]
}
],
"refresh": "30s",
"schemaVersion": 38,
"style": "dark",
"tags": ["rustfs", "get-optimization"],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "RustFS GET Rollout Health",
"uid": "rustfs-get-rollout-health",
"version": 1
}
@@ -0,0 +1,534 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path, eager_status, size_bucket, buffer_bucket, large_concurrency_tuning) (rate(rustfs_s3_put_object_diagnostics_total[$__rate_interval])) or vector(0)",
"legendFormat": "{{path}} / {{eager_status}} / {{size_bucket}} / {{buffer_bucket}} / large={{large_concurrency_tuning}}",
"refId": "A"
}
],
"title": "PUT Diagnostics Decision Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 2,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (path) (rate(rustfs_s3_put_object_path_total[$__rate_interval])) or vector(0)",
"legendFormat": "{{path}}",
"refId": "A"
}
],
"title": "PUT Path Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ms",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 3,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum by (stage, le) (rate(rustfs_s3_put_object_stage_duration_ms_bucket{stage=~\"ingress_prepare|set_disk_writer_setup|set_disk_encode|set_disk_rename|set_disk_old_data_cleanup|multipart_ingress_prepare|multipart_set_disk_writer_setup|multipart_set_disk_encode|multipart_complete_tail\"}[$__rate_interval]))) or vector(0)",
"legendFormat": "p95 {{stage}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.99, sum by (stage, le) (rate(rustfs_s3_put_object_stage_duration_ms_bucket{stage=~\"ingress_prepare|set_disk_writer_setup|set_disk_encode|set_disk_rename|set_disk_old_data_cleanup|multipart_ingress_prepare|multipart_set_disk_writer_setup|multipart_set_disk_encode|multipart_complete_tail\"}[$__rate_interval]))) or vector(0)",
"legendFormat": "p99 {{stage}}",
"refId": "B"
}
],
"title": "PUT Hot Stage Latency",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 4,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.50, sum by (path, size_bucket, le) (rate(rustfs_s3_put_object_selected_buffer_size_bytes_bucket[$__rate_interval]))) or vector(0)",
"legendFormat": "p50 {{path}} / {{size_bucket}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "histogram_quantile(0.95, sum by (path, size_bucket, le) (rate(rustfs_s3_put_object_selected_buffer_size_bytes_bucket[$__rate_interval]))) or vector(0)",
"legendFormat": "p95 {{path}} / {{size_bucket}}",
"refId": "B"
}
],
"title": "PUT Selected Buffer Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "percentunit",
"min": 0,
"max": 1,
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 15,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 5,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_s3_put_object_zero_copy_eligible_total[$__rate_interval])) / clamp_min(sum(rate(rustfs_s3_put_object_total[$__rate_interval])), 1) or vector(0)",
"legendFormat": "eligible / total",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_s3_put_object_zero_copy_enabled_total[$__rate_interval])) / clamp_min(sum(rate(rustfs_s3_put_object_total[$__rate_interval])), 1) or vector(0)",
"legendFormat": "enabled / total",
"refId": "B"
}
],
"title": "PUT Zero-Copy Eligibility Ratio",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"unit": "ops",
"custom": {
"drawStyle": "line",
"lineInterpolation": "smooth",
"fillOpacity": 10,
"pointSize": 5,
"lineWidth": 1,
"showPoints": "auto",
"spanNulls": false,
"thresholdsStyle": {
"mode": "off"
}
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 16
},
"id": 6,
"options": {
"legend": {
"calcs": [
"mean",
"max"
],
"displayMode": "table",
"placement": "bottom"
},
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum(rate(rustfs_s3_put_object_total[$__rate_interval])) or vector(0)",
"legendFormat": "PUT total",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"expr": "sum by (stage) (rate(rustfs_system_storage_erasure_write_quorum_failures_total[$__rate_interval])) or vector(0)",
"legendFormat": "write quorum failure {{stage}}",
"refId": "B"
}
],
"title": "PUT Throughput and Quorum Failure Signals",
"type": "timeseries"
}
],
"refresh": "30s",
"schemaVersion": 38,
"style": "dark",
"tags": [
"rustfs",
"put",
"performance",
"attribution"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "Prometheus",
"value": "Prometheus"
},
"hide": 0,
"includeAll": false,
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timezone": "",
"title": "RustFS PUT Performance Attribution",
"uid": "rustfs-put-performance-attribution",
"version": 1,
"weekStart": ""
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
groups:
- name: rustfs-dashboard
interval: 30s
rules:
- record: rustfs:http_server_requests:rate5m
expr: sum by (job) (rate(rustfs_http_server_requests_total[5m]))
- record: rustfs:http_server_request_duration_seconds:p50_5m
expr: histogram_quantile(0.50, sum by (le, job) (rate(rustfs_http_server_request_duration_seconds_bucket[5m])))
- record: rustfs:http_server_request_duration_seconds:p95_5m
expr: histogram_quantile(0.95, sum by (le, job) (rate(rustfs_http_server_request_duration_seconds_bucket[5m])))
- record: rustfs:http_server_request_duration_seconds:p99_5m
expr: histogram_quantile(0.99, sum by (le, job) (rate(rustfs_http_server_request_duration_seconds_bucket[5m])))
- record: rustfs:http_server_response_body_size_bytes:p50_5m
expr: histogram_quantile(0.50, sum by (le, job) (rate(rustfs_http_server_response_body_size_bytes_bucket[5m])))
- record: rustfs:http_server_response_body_size_bytes:p95_5m
expr: histogram_quantile(0.95, sum by (le, job) (rate(rustfs_http_server_response_body_size_bytes_bucket[5m])))
- record: rustfs:http_server_response_body_size_bytes:p99_5m
expr: histogram_quantile(0.99, sum by (le, job) (rate(rustfs_http_server_response_body_size_bytes_bucket[5m])))
- record: rustfs:log_cleaner_runs:rate15m
expr: sum by (job) (rate(rustfs_log_cleaner_runs_total[15m]))
- record: rustfs:log_cleaner_failure_ratio:rate5m
expr: sum by (job) (rate(rustfs_log_cleaner_run_failures_total[5m])) / clamp_min(sum by (job) (rate(rustfs_log_cleaner_runs_total[5m])), 1e-9)
- record: rustfs:log_cleaner_rotation_failure_ratio:rate5m
expr: sum by (job) (rate(rustfs_log_cleaner_rotation_failures_total[5m])) / clamp_min(sum by (job) (rate(rustfs_log_cleaner_rotation_total[5m])), 1e-9)
- record: rustfs:log_cleaner_rotation_duration_seconds:p95_5m
expr: histogram_quantile(0.95, sum by (le, job) (rate(rustfs_log_cleaner_rotation_duration_seconds_bucket[5m])))
- record: rustfs:log_cleaner_compress_duration_seconds:p95_5m
expr: histogram_quantile(0.95, sum by (le, job) (rate(rustfs_log_cleaner_compress_duration_seconds_bucket[5m])))
- record: rustfs:scanner_objects_scanned:rate5m
expr: sum by (job) (rate(rustfs_scanner_objects_scanned_total[5m]))
- record: rustfs:scanner_directories_scanned:rate5m
expr: sum by (job) (rate(rustfs_scanner_directories_scanned_total[5m]))
- record: rustfs:scanner_buckets_scanned:rate5m
expr: sum by (job) (rate(rustfs_scanner_buckets_scanned_total[5m]))
- record: rustfs:scanner_cycles_success:rate5m
expr: sum by (job) (rate(rustfs_scanner_cycles_total{result="success"}[5m]))
- record: rustfs:log_chain_op_event_mismatch:rate5m
expr: sum by (job) (rate(rustfs_log_chain_op_event_mismatch_total[5m]))
- alert: RustFSLogChainOpEventMismatchDetected
expr: rustfs:log_chain_op_event_mismatch:rate5m > 0
for: 10m
labels:
severity: warning
component: s3-log-chain
annotations:
summary: "RustFS log-chain op/event mismatch detected"
description: "job={{ $labels.job }} has non-zero rustfs_log_chain_op_event_mismatch_total rate for more than 10m. Check s3 op/event mapping changes."
@@ -0,0 +1,260 @@
# =============================================================================
# RustFS GET Optimization — Prometheus Alerting Rules
# =============================================================================
#
# Import into Prometheus:
# 1. Copy this file to your Prometheus rules directory
# 2. Add to prometheus.yml:
# rule_files:
# - "prometheus-alert-rules.yaml"
# 3. Validate: promtool check rules prometheus-alert-rules.yaml
# 4. Reload: curl -X POST http://localhost:9090/-/reload
#
# All metric names match those registered in crates/io-metrics/src/lib.rs
# and documented in crates/ecstore/src/diagnostics/get.rs.
#
# Baseline comparison uses "offset 1d" — adjust to "offset 7d" for weekly
# seasonality if your traffic pattern varies by day of week.
# =============================================================================
groups:
# ==========================================================================
# Critical alerts — immediate action required
# ==========================================================================
- name: rustfs-get-optimization-critical
interval: 30s
rules:
# ------------------------------------------------------------------
# 1. GetP99Regression
# GET p99 latency exceeds 2x the baseline (same time yesterday)
# sustained for 10 minutes.
# Action: Roll back the GET optimization immediately.
# ------------------------------------------------------------------
- alert: GetP99Regression
expr: |
histogram_quantile(0.99,
sum(rate(rustfs_io_get_object_total_duration_seconds_bucket[5m])) by (le)
)
>
2
*
histogram_quantile(0.99,
sum(rate(rustfs_io_get_object_total_duration_seconds_bucket[5m] offset 1d)) by (le)
)
for: 10m
labels:
severity: critical
team: rustfs-storage
area: get-optimization
annotations:
summary: "GET p99 latency regression detected (>2x baseline for 10m)"
description: >-
The 99th-percentile GET object latency is {{ $value | humanizeDuration }}
which is more than double the baseline measured 24 hours ago.
This indicates a severe performance regression introduced by
a recent GET optimization change.
runbook_url: "https://internal.wiki/runbooks/rustfs/get-p99-regression"
action: >
1. Verify the regression is not caused by external factors
(disk health, network, load spike).
2. If confirmed optimization-related, roll back:
- Set RUSTFS_GET_CODEC_STREAMING=0
- Set RUSTFS_GET_METADATA_EARLY_STOP=0
- Restart affected nodes.
3. Collect flamegraphs and open a P0 incident.
# ------------------------------------------------------------------
# 2. PipelineFailureSpike
# Pipeline failure rate exceeds 5x the baseline sustained for
# 5 minutes. Covers all failure reasons: bitrot_mismatch,
# decode_error, downstream_closed, io, read_quorum, timeout, etc.
# Action: Investigate pipeline health and roll back if needed.
# ------------------------------------------------------------------
- alert: PipelineFailureSpike
expr: |
sum(rate(rustfs_io_get_object_pipeline_failures_total[5m]))
>
5
*
sum(rate(rustfs_io_get_object_pipeline_failures_total[5m] offset 1d))
for: 5m
labels:
severity: critical
team: rustfs-storage
area: get-optimization
annotations:
summary: "GET pipeline failure rate spike (>5x baseline for 5m)"
description: >-
The GET pipeline failure rate is {{ $value | printf "%.2f" }}/s,
more than 5x the baseline from 24 hours ago.
Failure reasons may include: bitrot_mismatch, decode_error,
downstream_closed, io, range_or_length_invalid, read_quorum,
short_read, timeout, unknown.
runbook_url: "https://internal.wiki/runbooks/rustfs/pipeline-failure-spike"
action: >
1. Check Grafana "GET Data Integrity" dashboard for failure
breakdown by reason label.
2. If decode_error or bitrot_mismatch dominates, stop
optimization and investigate data integrity.
3. If io or timeout dominates, check disk and network health.
4. Roll back optimization if failures persist.
# ------------------------------------------------------------------
# 3. BitrotMismatchSpike
# Bitrot verification mismatch rate exceeds 3x baseline for
# 5 minutes. This is a data-integrity signal — shard checksums
# do not match after read.
#
# The "bitrot_mismatch" reason is recorded on the
# rustfs_io_get_object_pipeline_failures_total counter when a
# StorageError::FileCorrupt or DiskError::FileCorrupt /
# DiskError::PartMissingOrCorrupt is classified during the GET
# pipeline (see classify_storage_error / classify_disk_error in
# crates/ecstore/src/diagnostics/get.rs).
#
# Action: Stop optimization, investigate data integrity urgently.
# ------------------------------------------------------------------
- alert: BitrotMismatchSpike
expr: |
sum(rate(rustfs_io_get_object_pipeline_failures_total{reason="bitrot_mismatch"}[5m]))
>
3
*
sum(rate(rustfs_io_get_object_pipeline_failures_total{reason="bitrot_mismatch"}[5m] offset 1d))
for: 5m
labels:
severity: critical
team: rustfs-storage
area: get-optimization
annotations:
summary: "Bitrot mismatch rate spike (>3x baseline for 5m)"
description: >-
The rate of pipeline failures classified as bitrot_mismatch is
{{ $value | printf "%.2f" }}/s, more than 3x the baseline from
24 hours ago. This indicates shard checksum verification
failures (FileCorrupt / PartMissingOrCorrupt) which may point
to data corruption introduced by the GET optimization pipeline
(e.g., incorrect decode, buffer reuse bug).
runbook_url: "https://internal.wiki/runbooks/rustfs/bitrot-mismatch-spike"
action: >
1. Immediately disable codec streaming:
RUSTFS_GET_CODEC_STREAMING=0
2. Run "mc admin scan" on affected buckets to verify on-disk
integrity independent of the GET path.
3. Compare xl.meta checksums across erasure shards.
4. If corruption confirmed, initiate data recovery from parity.
5. Do NOT re-enable optimization until root cause is identified.
# ==========================================================================
# Warning alerts — investigation needed
# ==========================================================================
- name: rustfs-get-optimization-warning
interval: 30s
rules:
# ------------------------------------------------------------------
# 4. EarlyStopInsufficientQuorum
# The metadata early-stop path is hitting "insufficient_quorum"
# at a rate above 0.1/s for 5 minutes. This means too many
# disks are failing to return valid metadata in time.
# Action: Check disk health and metadata fanout latency.
# ------------------------------------------------------------------
- alert: EarlyStopInsufficientQuorum
expr: |
sum(rate(rustfs_io_get_object_metadata_early_stop_total{reason="insufficient_quorum"}[5m]))
> 0.1
for: 5m
labels:
severity: warning
team: rustfs-storage
area: get-optimization
annotations:
summary: "Early-stop insufficient quorum rate elevated (>0.1/s for 5m)"
description: >-
The metadata early-stop path is returning "insufficient_quorum"
at {{ $value | printf "%.3f" }}/s. This means the bounded
metadata fanout cannot gather enough valid responses before
the quorum deadline, suggesting disk or network issues.
runbook_url: "https://internal.wiki/runbooks/rustfs/early-stop-quorum"
action: >
1. Check disk health: mc admin info --json | jq '.disks'
2. Review rustfs_io_get_object_metadata_response_total by
outcome (error, timeout, disk_not_found) in Grafana.
3. Check rustfs_io_disk_permit_wait_duration_seconds for
I/O scheduler saturation.
4. If disks are healthy, consider increasing the early-stop
timeout or temporarily disabling early-stop.
# ------------------------------------------------------------------
# 5. CodecStreamingFallbackSpike
# The codec streaming fallback rate is >10x baseline for 10
# minutes. This means the optimized codec streaming path is
# being bypassed much more often than expected.
# Action: Check fallback reasons and object eligibility.
# ------------------------------------------------------------------
- alert: CodecStreamingFallbackSpike
expr: |
sum(rate(rustfs_io_get_object_codec_streaming_fallback_total[5m]))
>
10
*
sum(rate(rustfs_io_get_object_codec_streaming_fallback_total[5m] offset 1d))
for: 10m
labels:
severity: warning
team: rustfs-storage
area: get-optimization
annotations:
summary: "Codec streaming fallback rate spike (>10x baseline for 10m)"
description: >-
The codec streaming fallback rate is {{ $value | printf "%.2f" }}/s,
more than 10x the baseline from 24 hours ago. Fallback reasons
are labeled by "reason" — check Grafana for breakdown.
Common reasons: object too small, multipart not supported,
unsupported erasure layout, feature flag disabled.
runbook_url: "https://internal.wiki/runbooks/rustfs/codec-fallback-spike"
action: >
1. Query by reason label:
sum by (reason) (rate(rustfs_io_get_object_codec_streaming_fallback_total[5m]))
2. If dominated by a single reason, investigate why that
condition became more frequent (e.g., workload change,
configuration drift).
3. Cross-reference with rustfs_io_get_object_reader_path_total
to verify the fallback path (legacy_duplex) is healthy.
4. If fallback is expected (e.g., workload shifted to small
objects), update the alert baseline.
# ------------------------------------------------------------------
# 6. IoQueueSaturation
# I/O queue utilization exceeds 90% for 5 minutes. High
# utilization causes disk permit wait latency to increase and
# can cascade into pipeline timeouts.
# Action: Check disk load and consider reducing concurrency.
# ------------------------------------------------------------------
- alert: IoQueueSaturation
expr: |
rustfs_io_queue_utilization_percent > 90
for: 5m
labels:
severity: warning
team: rustfs-storage
area: get-optimization
annotations:
summary: "I/O queue utilization >90% for 5m"
description: >-
The I/O queue utilization is {{ $value | printf "%.1f" }}%,
sustained above 90% for 5 minutes. This indicates the disk
I/O scheduler is near saturation, which will increase
rustfs_io_disk_permit_wait_duration_seconds and may trigger
pipeline timeouts.
runbook_url: "https://internal.wiki/runbooks/rustfs/io-queue-saturation"
action: >
1. Check disk I/O metrics (iostat, node_exporter) for
individual disk saturation.
2. Review rustfs_io_queue_permits_in_use vs
rustfs_io_queue_permits_available for permit exhaustion.
3. Check rustfs_io_starvation_events for priority starvation.
4. If GET optimization increased concurrency, consider:
- Reducing RUSTFS_GET_PIPELINE_PARALLELISM
- Lowering RUSTFS_IO_QUEUE_PERMITS
5. If caused by background operations (ILM, healing), throttle
those before adjusting GET concurrency.
+3
View File
@@ -19,6 +19,9 @@ global:
cluster: 'rustfs-dev' # Label to identify the cluster
replica: '1' # Replica identifier
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
- job_name: 'otel-collector'
static_configs:
+91
View File
@@ -0,0 +1,91 @@
# Rio compatibility compose files
These compose files prepare 4-node, 4-disk clusters for rio/rio-v2 storage format compatibility checks. All disks are bind-mounted under `.docker/compat/data` so the on-disk files remain available on the host.
## Clusters
```bash
docker compose -f .docker/compat/docker-compose.rustfs-beta5.yml up -d --build
docker compose -f .docker/compat/docker-compose.minio.yml up -d
docker compose -f .docker/compat/docker-compose.rustfs-rio-v2.yml up -d --build
```
Default API endpoints:
- RustFS `1.0.0-beta.5`: `http://127.0.0.1:9100`
- MinIO: `http://127.0.0.1:9200`
- current main with `rio-v2`: `http://127.0.0.1:9300`
## Reading old datasets with rio-v2
Stop the writer cluster before mounting its disks into the rio-v2 cluster.
```bash
docker compose -f .docker/compat/docker-compose.rustfs-beta5.yml down
RUSTFS_RIO_V2_DATASET=./data/rustfs-beta5 \
docker compose -f .docker/compat/docker-compose.rustfs-rio-v2.yml up -d --build
docker compose -f .docker/compat/docker-compose.minio.yml down
RUSTFS_RIO_V2_DATASET=./data/minio \
docker compose -f .docker/compat/docker-compose.rustfs-rio-v2.yml up -d --build
```
## 200G object mix
Use the same bucket/object matrix against the beta5 and MinIO endpoints, then read it back through the rio-v2 endpoint. A practical 200G mix is:
- 1 KiB x 1024
- 1 MiB x 1024
- 64 MiB x 512
- 1 GiB x 64
- 8 GiB x 12
- 6 GiB x 1
Compression is enabled by default for RustFS and MinIO. Server-side KMS/SSE settings are intentionally left to environment variables or mounted key directories so real key material is not committed. For SSE-C cases, run the clusters with TLS because MinIO requires HTTPS for SSE-C.
## High-concurrency write/read stress
Use `run_rw_compat_stress.sh` to generate a manifest on an old endpoint, then verify the same objects through the rio-v2 endpoint after mounting the old disks.
```bash
.docker/compat/run_rw_compat_stress.sh \
--mode write \
--endpoint http://127.0.0.1:9100 \
--access-key rustfsadmin \
--secret-key rustfsadmin \
--bucket compat-beta5 \
--concurrency 96
RUSTFS_RIO_V2_DATASET=./data/rustfs-beta5 \
docker compose -f .docker/compat/docker-compose.rustfs-rio-v2.yml up -d --build
.docker/compat/run_rw_compat_stress.sh \
--mode verify \
--endpoint http://127.0.0.1:9300 \
--access-key rustfsadmin \
--secret-key rustfsadmin \
--bucket compat-beta5 \
--concurrency 96 \
--manifest target/compat/rw-stress-YYYYmmdd-HHMMSS/manifest.csv
```
For encrypted datasets, add `--encryption sse-s3`, `--encryption sse-kms --sse-kms-key-id <key-id>`, or `--encryption sse-c --sse-c-key-file <raw-32-byte-key-file>` to both the write and verify commands.
## 5 GiB encrypted compatibility run
The `5g` profile covers 1 KiB, 1 MiB, 16 MiB, 64 MiB, and 1 GiB objects and totals exactly 5 GiB. Generate `compat-key.key` under `.docker/compat/kms/rustfs-compat`, enable local KMS on both RustFS clusters, then use the same encryption arguments while writing with beta5 and verifying with rio-v2. Set non-default local test credentials first because distributed listeners reject the built-in default credentials.
```bash
export COMPAT_ACCESS_KEY='<non-default-access-key>'
export COMPAT_SECRET_KEY='<non-default-secret-key>'
RUSTFS_ACCESS_KEY="$COMPAT_ACCESS_KEY" RUSTFS_SECRET_KEY="$COMPAT_SECRET_KEY" \
RUSTFS_KMS_ENABLE=true RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS=true \
docker compose -f .docker/compat/docker-compose.rustfs-beta5.yml up -d
.docker/compat/run_rw_compat_stress.sh \
--mode write --endpoint http://127.0.0.1:9100 \
--access-key "$COMPAT_ACCESS_KEY" --secret-key "$COMPAT_SECRET_KEY" \
--bucket compat-beta5-sse-s3 --profile 5g --concurrency 16 \
--encryption sse-s3
```
@@ -0,0 +1,88 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0.
x-minio-env: &minio-env
MINIO_ROOT_USER: "${MINIO_ROOT_USER:-minioadmin}"
MINIO_ROOT_PASSWORD: "${MINIO_ROOT_PASSWORD:-minioadmin}"
MINIO_COMPRESSION_ENABLE: "${MINIO_COMPRESSION_ENABLE:-on}"
MINIO_COMPRESSION_ALLOW_ENCRYPTION: "${MINIO_COMPRESSION_ALLOW_ENCRYPTION:-on}"
MINIO_COMPRESSION_EXTENSIONS: "${MINIO_COMPRESSION_EXTENSIONS:-.txt,.log,.csv,.json,.tar,.xml,.bin}"
MINIO_COMPRESSION_MIME_TYPES: "${MINIO_COMPRESSION_MIME_TYPES:-text/*,application/json,application/xml,binary/octet-stream}"
x-minio-node: &minio-node
image: "${MINIO_IMAGE:-quay.io/minio/minio:latest}"
command: server --console-address ":9001" "http://minio{1...4}:9000/data/disk{1...4}"
environment: *minio-env
networks:
- minio-compat-net
restart: unless-stopped
services:
minio-permission-helper:
image: alpine:3.23
command: sh -c "mkdir -p /compat-data && chown -R 1000:1000 /compat-data"
volumes:
- ./data/minio:/compat-data
restart: "no"
minio1:
<<: *minio-node
hostname: minio1
depends_on:
minio-permission-helper:
condition: service_completed_successfully
volumes:
- ./data/minio/node1/disk1:/data/disk1
- ./data/minio/node1/disk2:/data/disk2
- ./data/minio/node1/disk3:/data/disk3
- ./data/minio/node1/disk4:/data/disk4
ports:
- "${MINIO_API_PORT:-9200}:9000"
- "${MINIO_CONSOLE_PORT:-9201}:9001"
minio2:
<<: *minio-node
hostname: minio2
depends_on:
minio-permission-helper:
condition: service_completed_successfully
volumes:
- ./data/minio/node2/disk1:/data/disk1
- ./data/minio/node2/disk2:/data/disk2
- ./data/minio/node2/disk3:/data/disk3
- ./data/minio/node2/disk4:/data/disk4
ports:
- "${MINIO_NODE2_PORT:-9202}:9000"
minio3:
<<: *minio-node
hostname: minio3
depends_on:
minio-permission-helper:
condition: service_completed_successfully
volumes:
- ./data/minio/node3/disk1:/data/disk1
- ./data/minio/node3/disk2:/data/disk2
- ./data/minio/node3/disk3:/data/disk3
- ./data/minio/node3/disk4:/data/disk4
ports:
- "${MINIO_NODE3_PORT:-9203}:9000"
minio4:
<<: *minio-node
hostname: minio4
depends_on:
minio-permission-helper:
condition: service_completed_successfully
volumes:
- ./data/minio/node4/disk1:/data/disk1
- ./data/minio/node4/disk2:/data/disk2
- ./data/minio/node4/disk3:/data/disk3
- ./data/minio/node4/disk4:/data/disk4
ports:
- "${MINIO_NODE4_PORT:-9204}:9000"
networks:
minio-compat-net:
driver: bridge
@@ -0,0 +1,104 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0.
x-rustfs-env: &rustfs-env
RUSTFS_VOLUMES: "http://rustfs-beta5-node{1...4}:9000/data/disk{1...4}"
RUSTFS_ADDRESS: ":9000"
RUSTFS_CONSOLE_ADDRESS: ":9001"
RUSTFS_CONSOLE_ENABLE: "true"
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS: "*"
RUSTFS_ACCESS_KEY: "${RUSTFS_ACCESS_KEY:-rustfsadmin}"
RUSTFS_SECRET_KEY: "${RUSTFS_SECRET_KEY:-rustfsadmin}"
RUSTFS_OBS_LOGGER_LEVEL: "${RUSTFS_OBS_LOGGER_LEVEL:-info}"
RUSTFS_OBS_LOG_DIRECTORY: "/logs"
RUSTFS_COMPRESSION_ENABLED: "${RUSTFS_COMPRESSION_ENABLED:-true}"
RUSTFS_COMPRESSION_EXTENSIONS: "${RUSTFS_COMPRESSION_EXTENSIONS:-.txt,.log,.csv,.json,.tar,.xml,.bin}"
RUSTFS_COMPRESSION_MIME_TYPES: "${RUSTFS_COMPRESSION_MIME_TYPES:-text/*,application/json,application/xml,binary/octet-stream}"
RUSTFS_KMS_ENABLE: "${RUSTFS_KMS_ENABLE:-false}"
RUSTFS_KMS_BACKEND: "${RUSTFS_KMS_BACKEND:-local}"
RUSTFS_KMS_KEY_DIR: "${RUSTFS_KMS_KEY_DIR:-/kms}"
RUSTFS_KMS_DEFAULT_KEY_ID: "${RUSTFS_KMS_DEFAULT_KEY_ID:-compat-key}"
RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS: "${RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS:-false}"
RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}"
x-rustfs-node: &rustfs-node
image: "${RUSTFS_BETA5_IMAGE:-rustfs/rustfs:1.0.0-beta.5}"
build:
context: ../..
dockerfile: Dockerfile
args:
RELEASE: "1.0.0-beta.5"
environment: *rustfs-env
depends_on:
rustfs-beta5-permission-helper:
condition: service_completed_successfully
networks:
- rustfs-beta5-net
restart: unless-stopped
services:
rustfs-beta5-permission-helper:
image: alpine:3.23
command: sh -c "mkdir -p /compat-data /kms && chown -R 10001:10001 /compat-data /kms"
volumes:
- ./data/rustfs-beta5:/compat-data
- ./kms/rustfs-compat:/kms
restart: "no"
rustfs-beta5-node1:
<<: *rustfs-node
hostname: rustfs-beta5-node1
volumes:
- ./data/rustfs-beta5/node1/disk1:/data/disk1
- ./data/rustfs-beta5/node1/disk2:/data/disk2
- ./data/rustfs-beta5/node1/disk3:/data/disk3
- ./data/rustfs-beta5/node1/disk4:/data/disk4
- ./data/rustfs-beta5/logs/node1:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_BETA5_API_PORT:-9100}:9000"
- "${RUSTFS_BETA5_CONSOLE_PORT:-9101}:9001"
rustfs-beta5-node2:
<<: *rustfs-node
hostname: rustfs-beta5-node2
volumes:
- ./data/rustfs-beta5/node2/disk1:/data/disk1
- ./data/rustfs-beta5/node2/disk2:/data/disk2
- ./data/rustfs-beta5/node2/disk3:/data/disk3
- ./data/rustfs-beta5/node2/disk4:/data/disk4
- ./data/rustfs-beta5/logs/node2:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_BETA5_NODE2_PORT:-9102}:9000"
rustfs-beta5-node3:
<<: *rustfs-node
hostname: rustfs-beta5-node3
volumes:
- ./data/rustfs-beta5/node3/disk1:/data/disk1
- ./data/rustfs-beta5/node3/disk2:/data/disk2
- ./data/rustfs-beta5/node3/disk3:/data/disk3
- ./data/rustfs-beta5/node3/disk4:/data/disk4
- ./data/rustfs-beta5/logs/node3:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_BETA5_NODE3_PORT:-9103}:9000"
rustfs-beta5-node4:
<<: *rustfs-node
hostname: rustfs-beta5-node4
volumes:
- ./data/rustfs-beta5/node4/disk1:/data/disk1
- ./data/rustfs-beta5/node4/disk2:/data/disk2
- ./data/rustfs-beta5/node4/disk3:/data/disk3
- ./data/rustfs-beta5/node4/disk4:/data/disk4
- ./data/rustfs-beta5/logs/node4:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_BETA5_NODE4_PORT:-9104}:9000"
networks:
rustfs-beta5-net:
driver: bridge
@@ -0,0 +1,115 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0.
x-rustfs-rio-v2-env: &rustfs-rio-v2-env
RUSTFS_VOLUMES: "http://rustfs-rio-v2-node{1...4}:9000/data/disk{1...4}"
RUSTFS_ADDRESS: ":9000"
RUSTFS_CONSOLE_ADDRESS: ":9001"
RUSTFS_CONSOLE_ENABLE: "true"
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS: "*"
RUSTFS_ACCESS_KEY: "${RUSTFS_ACCESS_KEY:-rustfsadmin}"
RUSTFS_SECRET_KEY: "${RUSTFS_SECRET_KEY:-rustfsadmin}"
RUSTFS_OBS_LOGGER_LEVEL: "${RUSTFS_OBS_LOGGER_LEVEL:-info}"
RUSTFS_OBS_LOG_DIRECTORY: "/logs"
RUSTFS_COMPRESSION_ENABLED: "${RUSTFS_COMPRESSION_ENABLED:-true}"
RUSTFS_COMPRESSION_EXTENSIONS: "${RUSTFS_COMPRESSION_EXTENSIONS:-.txt,.log,.csv,.json,.tar,.xml,.bin}"
RUSTFS_COMPRESSION_MIME_TYPES: "${RUSTFS_COMPRESSION_MIME_TYPES:-text/*,application/json,application/xml,binary/octet-stream}"
RUSTFS_KMS_ENABLE: "${RUSTFS_KMS_ENABLE:-false}"
RUSTFS_KMS_BACKEND: "${RUSTFS_KMS_BACKEND:-local}"
RUSTFS_KMS_KEY_DIR: "${RUSTFS_KMS_KEY_DIR:-/kms}"
RUSTFS_KMS_DEFAULT_KEY_ID: "${RUSTFS_KMS_DEFAULT_KEY_ID:-compat-key}"
RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS: "${RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS:-false}"
RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}"
x-rustfs-rio-v2-node: &rustfs-rio-v2-node
image: "${RUSTFS_RIO_V2_IMAGE:-rustfs/rustfs:compat-rio-v2}"
entrypoint:
- /bin/sh
- -c
- |
until getent hosts rustfs-rio-v2-node1 >/dev/null &&
getent hosts rustfs-rio-v2-node2 >/dev/null &&
getent hosts rustfs-rio-v2-node3 >/dev/null &&
getent hosts rustfs-rio-v2-node4 >/dev/null; do
sleep 1
done
exec /entrypoint.sh /usr/bin/rustfs
build:
context: ../..
dockerfile: Dockerfile.source
args:
RUSTFS_BUILD_FEATURES: "rio-v2"
environment: *rustfs-rio-v2-env
depends_on:
rustfs-rio-v2-permission-helper:
condition: service_completed_successfully
networks:
- rustfs-rio-v2-net
restart: unless-stopped
services:
rustfs-rio-v2-permission-helper:
image: alpine:3.23
command: sh -c "mkdir -p /compat-data /kms && chown -R 10001:10001 /compat-data /kms"
volumes:
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}:/compat-data
- ./kms/rustfs-compat:/kms
restart: "no"
rustfs-rio-v2-node1:
<<: *rustfs-rio-v2-node
hostname: rustfs-rio-v2-node1
volumes:
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node1/disk1:/data/disk1
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node1/disk2:/data/disk2
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node1/disk3:/data/disk3
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node1/disk4:/data/disk4
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/logs/node1:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_RIO_V2_API_PORT:-9300}:9000"
- "${RUSTFS_RIO_V2_CONSOLE_PORT:-9301}:9001"
rustfs-rio-v2-node2:
<<: *rustfs-rio-v2-node
hostname: rustfs-rio-v2-node2
volumes:
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node2/disk1:/data/disk1
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node2/disk2:/data/disk2
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node2/disk3:/data/disk3
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node2/disk4:/data/disk4
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/logs/node2:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_RIO_V2_NODE2_PORT:-9302}:9000"
rustfs-rio-v2-node3:
<<: *rustfs-rio-v2-node
hostname: rustfs-rio-v2-node3
volumes:
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node3/disk1:/data/disk1
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node3/disk2:/data/disk2
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node3/disk3:/data/disk3
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node3/disk4:/data/disk4
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/logs/node3:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_RIO_V2_NODE3_PORT:-9303}:9000"
rustfs-rio-v2-node4:
<<: *rustfs-rio-v2-node
hostname: rustfs-rio-v2-node4
volumes:
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node4/disk1:/data/disk1
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node4/disk2:/data/disk2
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node4/disk3:/data/disk3
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/node4/disk4:/data/disk4
- ${RUSTFS_RIO_V2_DATASET:-./data/rustfs-rio-v2}/logs/node4:/logs
- ./kms/rustfs-compat:/kms
ports:
- "${RUSTFS_RIO_V2_NODE4_PORT:-9304}:9000"
networks:
rustfs-rio-v2-net:
driver: bridge
+639
View File
@@ -0,0 +1,639 @@
#!/usr/bin/env bash
set -euo pipefail
# High-concurrency S3 read/write stress runner for rio/rio-v2 format compatibility.
# Write a manifest on an old endpoint, then verify the same manifest through rio-v2.
MODE="write"
ENDPOINT=""
ACCESS_KEY="${AWS_ACCESS_KEY_ID:-}"
SECRET_KEY="${AWS_SECRET_ACCESS_KEY:-}"
BUCKET="compat-rw-stress"
REGION="us-east-1"
CONCURRENCY=64
OUT_DIR=""
WORK_DIR=""
MANIFEST=""
PROFILE="200g"
OBJECT_SPEC=""
DATA_PATTERN="compressible"
ENCRYPTION="none"
SSE_KMS_KEY_ID=""
SSE_C_KEY_FILE=""
CLIENT="mc"
AWS_BIN="${AWS_BIN:-aws}"
MC_BIN="${MC_BIN:-}"
KEEP_PAYLOADS=false
DRY_RUN=false
RESUME=false
usage() {
cat <<'USAGE'
Usage:
.docker/compat/run_rw_compat_stress.sh --mode <write|verify|mixed> \
--endpoint <url> --access-key <ak> --secret-key <sk> [options]
Modes:
write Create bucket, upload objects concurrently, and write manifest.csv.
verify Read objects concurrently from --endpoint and verify against manifest.csv.
mixed Write and verify against the same endpoint.
Required:
--endpoint S3 endpoint URL, for example http://127.0.0.1:9100
--access-key S3 access key
--secret-key S3 secret key
Core options:
--bucket Bucket name (default: compat-rw-stress)
--region Region (default: us-east-1)
--concurrency Parallel object operations (default: 64)
--out-dir Output directory (default: target/compat/rw-stress-<timestamp>)
--work-dir Payload scratch directory (default: <out-dir>/payloads)
--manifest Manifest path (default: <out-dir>/manifest.csv for write/mixed)
--profile compact | 5g | 200g (default: 200g)
--object-spec Override profile. Format: size:count,size:count
Example: 1KiB:1024,1MiB:1024,64MiB:512,1GiB:64
--data-pattern compressible | random | mixed (default: compressible)
--keep-payloads Do not delete local payload files after upload
--resume Skip write tasks that already have rows in <out-dir>/tasks/write-rows
--dry-run Print planned tasks and commands without executing S3 operations
--client mc | aws (default: mc)
--mc-bin Path to mc binary (default: first tmp/mc.* or mc in PATH)
--aws-bin Path to aws binary (used with --client aws)
Encryption options:
--encryption none | sse-s3 | sse-kms | sse-c (default: none)
--sse-kms-key-id KMS key id for --encryption sse-kms
--sse-c-key-file Raw 32-byte SSE-C key file for --encryption sse-c
Examples:
# Generate the old RustFS beta5 dataset.
.docker/compat/run_rw_compat_stress.sh \
--mode write --endpoint http://127.0.0.1:9100 \
--access-key rustfsadmin --secret-key rustfsadmin \
--bucket compat-beta5 --concurrency 96
# Verify that dataset after mounting beta5 disks into the rio-v2 compose.
.docker/compat/run_rw_compat_stress.sh \
--mode verify --endpoint http://127.0.0.1:9300 \
--access-key rustfsadmin --secret-key rustfsadmin \
--bucket compat-beta5 --concurrency 96 \
--manifest target/compat/rw-stress-YYYYmmdd-HHMMSS/manifest.csv
# Faster smoke run.
.docker/compat/run_rw_compat_stress.sh \
--mode mixed --endpoint http://127.0.0.1:9300 \
--access-key rustfsadmin --secret-key rustfsadmin \
--profile compact --concurrency 16
USAGE
}
die() {
echo "ERROR: $*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "command not found: $1"
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--mode) MODE="$2"; shift 2 ;;
--endpoint) ENDPOINT="$2"; shift 2 ;;
--access-key) ACCESS_KEY="$2"; shift 2 ;;
--secret-key) SECRET_KEY="$2"; shift 2 ;;
--bucket) BUCKET="$2"; shift 2 ;;
--region) REGION="$2"; shift 2 ;;
--concurrency) CONCURRENCY="$2"; shift 2 ;;
--out-dir) OUT_DIR="$2"; shift 2 ;;
--work-dir) WORK_DIR="$2"; shift 2 ;;
--manifest) MANIFEST="$2"; shift 2 ;;
--profile) PROFILE="$2"; shift 2 ;;
--object-spec) OBJECT_SPEC="$2"; shift 2 ;;
--data-pattern) DATA_PATTERN="$2"; shift 2 ;;
--encryption) ENCRYPTION="$2"; shift 2 ;;
--sse-kms-key-id) SSE_KMS_KEY_ID="$2"; shift 2 ;;
--sse-c-key-file) SSE_C_KEY_FILE="$2"; shift 2 ;;
--client) CLIENT="$2"; shift 2 ;;
--mc-bin) MC_BIN="$2"; shift 2 ;;
--aws-bin) AWS_BIN="$2"; shift 2 ;;
--keep-payloads) KEEP_PAYLOADS=true; shift ;;
--resume) RESUME=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown arg: $1" ;;
esac
done
}
validate_args() {
[[ "$MODE" =~ ^(write|verify|mixed)$ ]] || die "--mode must be write, verify, or mixed"
[[ "$CLIENT" =~ ^(mc|aws)$ ]] || die "--client must be mc or aws"
[[ "$PROFILE" =~ ^(compact|5g|200g)$ ]] || die "--profile must be compact, 5g, or 200g"
[[ "$DATA_PATTERN" =~ ^(compressible|random|mixed)$ ]] || die "--data-pattern must be compressible, random, or mixed"
[[ "$ENCRYPTION" =~ ^(none|sse-s3|sse-kms|sse-c)$ ]] || die "--encryption must be none, sse-s3, sse-kms, or sse-c"
[[ -n "$ENDPOINT" && -n "$ACCESS_KEY" && -n "$SECRET_KEY" ]] || die "--endpoint/--access-key/--secret-key are required"
[[ "$CONCURRENCY" =~ ^[0-9]+$ && "$CONCURRENCY" -gt 0 ]] || die "--concurrency must be a positive integer"
if [[ "$ENCRYPTION" == "sse-kms" && -z "$SSE_KMS_KEY_ID" ]]; then
die "--sse-kms-key-id is required for --encryption sse-kms"
fi
if [[ "$ENCRYPTION" == "sse-c" ]]; then
[[ -n "$SSE_C_KEY_FILE" && -f "$SSE_C_KEY_FILE" ]] || die "--sse-c-key-file must point to an existing key file"
fi
if [[ "$MODE" == "verify" && -z "$MANIFEST" ]]; then
die "--manifest is required for --mode verify"
fi
}
setup_paths() {
if [[ -z "$OUT_DIR" ]]; then
OUT_DIR="target/compat/rw-stress-$(date +%Y%m%d-%H%M%S)"
fi
if [[ -z "$WORK_DIR" ]]; then
WORK_DIR="$OUT_DIR/payloads"
fi
if [[ -z "$MANIFEST" ]]; then
MANIFEST="$OUT_DIR/manifest.csv"
fi
mkdir -p "$OUT_DIR" "$WORK_DIR" "$OUT_DIR/tasks" "$OUT_DIR/logs"
TASKS_FILE="$OUT_DIR/tasks/tasks.tsv"
WRITE_ROWS_DIR="$OUT_DIR/tasks/write-rows"
VERIFY_ROWS_DIR="$OUT_DIR/tasks/verify-rows"
MC_CONFIG_DIR_LOCAL="$OUT_DIR/mc-config"
MC_ALIAS="compat"
mkdir -p "$WRITE_ROWS_DIR" "$VERIFY_ROWS_DIR"
}
resolve_mc_bin() {
if [[ -n "$MC_BIN" ]]; then
echo "$MC_BIN"
return
fi
local candidate
candidate="$(find tmp -maxdepth 1 -type f -name 'mc.*' -perm -111 2>/dev/null | sort | tail -n 1 || true)"
if [[ -n "$candidate" ]]; then
echo "$candidate"
return
fi
command -v mc 2>/dev/null || true
}
size_to_bytes() {
local raw="$1"
local num unit
if [[ "$raw" =~ ^([0-9]+)(B|KiB|MiB|GiB|KB|MB|GB)?$ ]]; then
num="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]:-B}"
else
die "invalid size: $raw"
fi
case "$unit" in
B) echo "$num" ;;
KiB) echo $((num * 1024)) ;;
MiB) echo $((num * 1024 * 1024)) ;;
GiB) echo $((num * 1024 * 1024 * 1024)) ;;
KB) echo $((num * 1000)) ;;
MB) echo $((num * 1000 * 1000)) ;;
GB) echo $((num * 1000 * 1000 * 1000)) ;;
*) die "invalid size unit: $unit" ;;
esac
}
profile_spec() {
if [[ -n "$OBJECT_SPEC" ]]; then
echo "$OBJECT_SPEC"
return
fi
case "$PROFILE" in
compact)
echo "1KiB:64,1MiB:64,16MiB:16,128MiB:4"
;;
5g)
echo "1KiB:1024,1MiB:255,16MiB:64,64MiB:28,1GiB:2"
;;
200g)
echo "1KiB:1024,1MiB:1024,64MiB:512,1GiB:64,8GiB:12,6GiB:1"
;;
esac
}
content_for_index() {
local index="$1"
case $((index % 3)) in
0) echo "txt|text/plain" ;;
1) echo "json|application/json" ;;
2) echo "bin|binary/octet-stream" ;;
esac
}
generate_tasks() {
local spec item size count bytes i content ext mime key seed index=0
spec="$(profile_spec)"
: > "$TASKS_FILE"
IFS=',' read -r -a items <<< "$spec"
for item in "${items[@]}"; do
item="${item//[[:space:]]/}"
[[ -n "$item" ]] || continue
[[ "$item" =~ ^([^:]+):([0-9]+)$ ]] || die "invalid object spec item: $item"
size="${BASH_REMATCH[1]}"
count="${BASH_REMATCH[2]}"
bytes="$(size_to_bytes "$size")"
for ((i = 1; i <= count; i++)); do
content="$(content_for_index "$index")"
ext="${content%%|*}"
mime="${content#*|}"
key="rw-stress/${size}/obj-$(printf '%06d' "$i").${ext}"
seed="${BUCKET}:${key}:${bytes}"
printf '%s\t%s\t%s\t%s\t%s\n' "$key" "$size" "$bytes" "$mime" "$seed" >> "$TASKS_FILE"
index=$((index + 1))
done
done
}
write_row_file_for_key() {
local key="$1"
echo "$WRITE_ROWS_DIR/${key//\//_}.csv"
}
prepare_write_input() {
WRITE_INPUT="$TASKS_FILE"
if [[ "$RESUME" != "true" ]]; then
return
fi
WRITE_INPUT="$OUT_DIR/tasks/write-input.tsv"
: > "$WRITE_INPUT"
local line key _size _bytes _mime _seed row_file
while IFS= read -r line || [[ -n "$line" ]]; do
IFS=$'\t' read -r key _size _bytes _mime _seed <<< "$line"
row_file="$(write_row_file_for_key "$key")"
[[ -s "$row_file" ]] && continue
printf '%s\n' "$line" >> "$WRITE_INPUT"
done < "$TASKS_FILE"
}
print_plan() {
local total_objects total_bytes profile_label
if [[ -f "$TASKS_FILE" ]]; then
total_objects="$(wc -l < "$TASKS_FILE" | tr -d ' ')"
total_bytes="$(awk -F '\t' '{sum += $3} END {print sum + 0}' "$TASKS_FILE")"
profile_label="$(profile_spec)"
else
total_objects="$(awk 'END {count = NR - 1; if (count < 0) count = 0; print count}' "$MANIFEST")"
total_bytes="$(awk -F ',' 'NR > 1 {sum += $3} END {print sum + 0}' "$MANIFEST")"
profile_label="from manifest"
fi
cat <<PLAN
Mode: $MODE
Endpoint: $ENDPOINT
Bucket: $BUCKET
Profile: $profile_label
Objects: $total_objects
Bytes: $total_bytes
Concurrency: $CONCURRENCY
Encryption: $ENCRYPTION
Client: $CLIENT
Manifest: $MANIFEST
Out dir: $OUT_DIR
Work dir: $WORK_DIR
PLAN
}
aws_base() {
AWS_ACCESS_KEY_ID="$ACCESS_KEY" AWS_SECRET_ACCESS_KEY="$SECRET_KEY" AWS_DEFAULT_REGION="$REGION" \
"$AWS_BIN" --endpoint-url "$ENDPOINT" "$@"
}
mc_base() {
"$MC_BIN" --config-dir "$MC_CONFIG_DIR_LOCAL" "$@"
}
aws_cp_args() {
case "$ENCRYPTION" in
none) ;;
sse-s3) printf '%s\n' "--sse" "AES256" ;;
sse-kms) printf '%s\n' "--sse" "aws:kms" "--sse-kms-key-id" "$SSE_KMS_KEY_ID" ;;
sse-c) printf '%s\n' "--sse-c" "AES256" "--sse-c-key" "fileb://$SSE_C_KEY_FILE" ;;
esac
}
mc_enc_target() {
printf '%s/%s/rw-stress/' "$MC_ALIAS" "$BUCKET"
}
mc_cp_args() {
local target
target="$(mc_enc_target)"
case "$ENCRYPTION" in
none) ;;
sse-s3) printf '%s\n' "--enc-s3" "$target" ;;
sse-kms) printf '%s\n' "--enc-kms" "${target}=${SSE_KMS_KEY_ID}" ;;
sse-c)
local key_b64
key_b64="$(base64 < "$SSE_C_KEY_FILE" | tr -d '\n')"
printf '%s\n' "--enc-c" "${target}=${key_b64}"
;;
esac
}
aws_get_args() {
if [[ "$ENCRYPTION" == "sse-c" ]]; then
printf '%s\n' "--sse-c" "AES256" "--sse-c-key" "fileb://$SSE_C_KEY_FILE"
fi
}
mc_get_args() {
if [[ "$ENCRYPTION" == "sse-c" ]]; then
local target key_b64
target="$(mc_enc_target)"
key_b64="$(base64 < "$SSE_C_KEY_FILE" | tr -d '\n')"
printf '%s\n' "--enc-c" "${target}=${key_b64}"
fi
}
setup_mc_alias() {
if [[ "$CLIENT" != "mc" || "$DRY_RUN" == "true" ]]; then
return
fi
mkdir -p "$MC_CONFIG_DIR_LOCAL"
mc_base alias set "$MC_ALIAS" "$ENDPOINT" "$ACCESS_KEY" "$SECRET_KEY" --api S3v4 --path auto >/dev/null
}
create_bucket_if_needed() {
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY-RUN] create bucket if missing: $BUCKET"
return
fi
if [[ "$CLIENT" == "mc" ]]; then
mc_base mb --ignore-existing --region "$REGION" "$MC_ALIAS/$BUCKET" >/dev/null
return
fi
if aws_base s3api head-bucket --bucket "$BUCKET" >/dev/null 2>&1; then
return
fi
aws_base s3api create-bucket --bucket "$BUCKET" >/dev/null
}
payload_path_for_key() {
local key="$1"
echo "$WORK_DIR/${key//\//_}"
}
generate_payload() {
local file="$1"
local bytes="$2"
local seed="$3"
local pattern="$DATA_PATTERN"
mkdir -p "$(dirname "$file")"
if [[ "$pattern" == "mixed" ]]; then
if [[ $((bytes % 2)) -eq 0 ]]; then
pattern="compressible"
else
pattern="random"
fi
fi
if [[ "$pattern" == "random" ]]; then
head -c "$bytes" /dev/zero | openssl enc -aes-256-ctr -nosalt -pass "pass:$seed" -out "$file"
else
yes "$seed payload-for-rio-compatibility" | tr '\n' ' ' | head -c "$bytes" > "$file"
fi
}
sha256_file() {
shasum -a 256 "$1" | awk '{print $1}'
}
sha256_object() {
local key="$1"
shift
if [[ "$CLIENT" == "mc" ]]; then
mc_base cat "$@" "$MC_ALIAS/$BUCKET/$key" | shasum -a 256 | awk '{print $1}'
else
aws_base s3 cp "s3://$BUCKET/$key" - --no-progress "$@" | shasum -a 256 | awk '{print $1}'
fi
}
collect_args() {
local generator="$1"
extra_args=()
while IFS= read -r arg; do
extra_args+=("$arg")
done < <("$generator")
}
write_one() {
local line="$1"
local key size bytes mime seed file sha row_file
local -a extra_args
IFS=$'\t' read -r key size bytes mime seed <<< "$line"
file="$(payload_path_for_key "$key")"
row_file="$(write_row_file_for_key "$key")"
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY-RUN] upload $bytes bytes to s3://$BUCKET/$key content-type=$mime"
printf '%s,%s,%s,%s,%s,%s\n' "$key" "$size" "$bytes" "$mime" "DRY_RUN" "$ENCRYPTION" > "$row_file"
return
fi
generate_payload "$file" "$bytes" "$seed"
sha="$(sha256_file "$file")"
if [[ "$CLIENT" == "mc" ]]; then
collect_args mc_cp_args
mc_base cp --quiet --attr "Content-Type=$mime" ${extra_args[@]+"${extra_args[@]}"} "$file" "$MC_ALIAS/$BUCKET/$key" \
> "$OUT_DIR/logs/${key//\//_}.put.log" 2>&1
else
collect_args aws_cp_args
aws_base s3 cp "$file" "s3://$BUCKET/$key" --no-progress --content-type "$mime" ${extra_args[@]+"${extra_args[@]}"} \
> "$OUT_DIR/logs/${key//\//_}.put.log" 2>&1
fi
printf '%s,%s,%s,%s,%s,%s\n' "$key" "$size" "$bytes" "$mime" "$sha" "$ENCRYPTION" > "$row_file"
if [[ "$KEEP_PAYLOADS" != "true" ]]; then
rm -f "$file"
fi
}
verify_one() {
local line="$1"
local key size bytes mime expected encryption actual row_file
local -a extra_args
IFS=',' read -r key size bytes mime expected encryption <<< "$line"
row_file="$VERIFY_ROWS_DIR/${key//\//_}.csv"
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY-RUN] verify s3://$BUCKET/$key expected=$expected"
printf '%s,%s,%s,%s,%s,%s,%s\n' "$key" "$size" "$bytes" "$mime" "$expected" "DRY_RUN" "dry-run" > "$row_file"
return
fi
if [[ "$CLIENT" == "mc" ]]; then
collect_args mc_get_args
else
collect_args aws_get_args
fi
if ! actual="$(sha256_object "$key" ${extra_args[@]+"${extra_args[@]}"})"; then
printf '%s,%s,%s,%s,%s,%s,%s\n' "$key" "$size" "$bytes" "$mime" "$expected" "ERROR" "download failed" > "$row_file"
return 1
fi
if [[ "$actual" == "$expected" ]]; then
printf '%s,%s,%s,%s,%s,%s,%s\n' "$key" "$size" "$bytes" "$mime" "$expected" "$actual" "ok" > "$row_file"
else
printf '%s,%s,%s,%s,%s,%s,%s\n' "$key" "$size" "$bytes" "$mime" "$expected" "$actual" "sha256-mismatch" > "$row_file"
return 1
fi
}
run_parallel_tasks() {
local action="$1"
local input_file="$2"
local failure_file="$OUT_DIR/tasks/${action}.failed"
local line
rm -f "$failure_file"
while IFS= read -r line || [[ -n "$line" ]]; do
while [[ "$(jobs -rp | wc -l | tr -d ' ')" -ge "$CONCURRENCY" ]]; do
sleep 0.2
done
({
if [[ "$action" == "write" ]]; then
write_one "$line"
else
verify_one "$line"
fi
} || touch "$failure_file") &
done < "$input_file"
wait
[[ ! -f "$failure_file" ]]
}
combine_write_manifest() {
echo "key,size,bytes,content_type,sha256,encryption" > "$MANIFEST"
find "$WRITE_ROWS_DIR" -type f -name '*.csv' -print0 | sort -z | xargs -0 cat >> "$MANIFEST"
local expected actual
expected="$(wc -l < "$TASKS_FILE" | tr -d ' ')"
actual="$(( $(wc -l < "$MANIFEST" | tr -d ' ') - 1 ))"
if [[ "$actual" -ne "$expected" ]]; then
die "manifest row count mismatch: expected $expected, got $actual"
fi
}
prepare_verify_input() {
VERIFY_INPUT="$OUT_DIR/tasks/verify-input.csv"
tail -n +2 "$MANIFEST" > "$VERIFY_INPUT"
}
combine_verify_summary() {
VERIFY_SUMMARY="$OUT_DIR/verify-summary.csv"
echo "key,size,bytes,content_type,expected_sha256,actual_sha256,status" > "$VERIFY_SUMMARY"
find "$VERIFY_ROWS_DIR" -type f -name '*.csv' -print0 | sort -z | xargs -0 cat >> "$VERIFY_SUMMARY"
local failed
if [[ "$DRY_RUN" == "true" ]]; then
echo "Verification dry run complete. Summary: $VERIFY_SUMMARY"
return
fi
failed="$(awk -F ',' 'NR > 1 && $7 != "ok" {count++} END {print count + 0}' "$VERIFY_SUMMARY")"
local expected actual
expected="$(wc -l < "$VERIFY_INPUT" | tr -d ' ')"
actual="$(( $(wc -l < "$VERIFY_SUMMARY" | tr -d ' ') - 1 ))"
if [[ "$actual" -ne "$expected" ]]; then
echo "Verification row count mismatch: expected $expected, got $actual" >&2
return 1
fi
if [[ "$failed" -ne 0 ]]; then
echo "Verification failed: $failed object(s). See $VERIFY_SUMMARY" >&2
return 1
fi
echo "Verification passed. Summary: $VERIFY_SUMMARY"
}
export_functions() {
export ENDPOINT ACCESS_KEY SECRET_KEY BUCKET REGION ENCRYPTION SSE_KMS_KEY_ID SSE_C_KEY_FILE AWS_BIN
export CLIENT MC_BIN MC_CONFIG_DIR_LOCAL MC_ALIAS
export WORK_DIR OUT_DIR WRITE_ROWS_DIR VERIFY_ROWS_DIR DATA_PATTERN KEEP_PAYLOADS DRY_RUN
export -f aws_base mc_base aws_cp_args aws_get_args mc_enc_target mc_cp_args mc_get_args payload_path_for_key generate_payload sha256_file sha256_object write_one verify_one
}
main() {
parse_args "$@"
validate_args
setup_paths
if [[ "$DRY_RUN" != "true" ]]; then
if [[ "$CLIENT" == "mc" ]]; then
MC_BIN="$(resolve_mc_bin)"
[[ -n "$MC_BIN" ]] || die "mc binary not found; pass --mc-bin or put mc in PATH"
require_cmd "$MC_BIN"
else
require_cmd "$AWS_BIN"
fi
require_cmd shasum
require_cmd head
require_cmd yes
require_cmd openssl
elif [[ "$CLIENT" == "mc" ]]; then
MC_BIN="$(resolve_mc_bin)"
fi
setup_mc_alias
if [[ "$MODE" == "write" || "$MODE" == "mixed" ]]; then
local write_failed=false
generate_tasks
prepare_write_input
print_plan
create_bucket_if_needed
export_functions
if ! run_parallel_tasks write "$WRITE_INPUT"; then
write_failed=true
fi
combine_write_manifest
echo "Write manifest: $MANIFEST"
if [[ "$write_failed" == "true" ]]; then
die "one or more write tasks failed; see $OUT_DIR/logs"
fi
fi
if [[ "$MODE" == "verify" || "$MODE" == "mixed" ]]; then
local verify_failed=false
[[ -f "$MANIFEST" ]] || die "manifest not found: $MANIFEST"
if [[ "$MODE" == "verify" ]]; then
cp "$MANIFEST" "$OUT_DIR/manifest.csv"
MANIFEST="$OUT_DIR/manifest.csv"
fi
prepare_verify_input
print_plan
export_functions
if ! run_parallel_tasks verify "$VERIFY_INPUT"; then
verify_failed=true
fi
combine_verify_summary
if [[ "$verify_failed" == "true" ]]; then
die "one or more verify tasks failed; see $VERIFY_SUMMARY"
fi
fi
}
main "$@"
@@ -0,0 +1,52 @@
services:
rustfs:
image: rustfs/rustfs:1.0.0-alpha.99-glibc
container_name: rustfs-issue-2715-test
security_opt:
- "no-new-privileges:true"
ports:
- "19000:9000"
- "19001:9001"
environment:
- RUSTFS_VOLUMES=/data/rustfs{0...8}
- RUSTFS_ADDRESS=0.0.0.0:9000
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_CORS_ALLOWED_ORIGINS=*
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
- RUSTFS_ACCESS_KEY=admin
- RUSTFS_SECRET_KEY=admin
- RUSTFS_OBS_LOGGER_LEVEL=info
- RUSTFS_OBS_ENDPOINT=http://otel-collector:4318
- RUSTFS_OBS_PROFILING_ENDPOINT=http://pyroscope:4040
- RUSTFS_STORAGE_CLASS_STANDARD=EC:2
- RUSTFS_STORAGE_CLASS_RRS=EC:1
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true
- RUSTFS_OBS_LOG_DIRECTORY=/opt/rustfs/logs
extra_hosts:
- "otel-collector:host-gateway"
- "pyroscope:host-gateway"
volumes:
- ./deploy/data/issue-2715/rustfs0:/data/rustfs0
- ./deploy/data/issue-2715/rustfs1:/data/rustfs1
- ./deploy/data/issue-2715/rustfs2:/data/rustfs2
- ./deploy/data/issue-2715/rustfs3:/data/rustfs3
- ./deploy/data/issue-2715/rustfs4:/data/rustfs4
- ./deploy/data/issue-2715/rustfs5:/data/rustfs5
- ./deploy/data/issue-2715/rustfs6:/data/rustfs6
- ./deploy/data/issue-2715/rustfs7:/data/rustfs7
- ./deploy/data/issue-2715/rustfs8:/data/rustfs8
- ./deploy/logs/issue-2715:/opt/rustfs/logs
restart: unless-stopped
healthcheck:
test:
[
"CMD",
"sh",
"-c",
"curl -f http://127.0.0.1:9000/health && curl -f http://127.0.0.1:9001/rustfs/console/health"
]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
+1
View File
@@ -0,0 +1 @@
data/
+106
View File
@@ -0,0 +1,106 @@
# Issue 2815 Local Docker Verification
## Purpose
This directory contains the local distributed Docker verification assets used to validate issue `#2815` against the current source build.
The target behavior is:
- 4-node distributed cluster starts successfully
- `/health/ready` becomes reachable on each node
- logs no longer contain `storage_info failed: Io error: wrong msgpack marker FixArray(1)`
- internode RPC authentication succeeds with an explicit non-default RPC secret
## Files
- `docker-compose.yml`: 4-node distributed cluster using a locally built image
## Data Directories
Create the bind-mount directories before `docker compose up`:
```bash
mkdir -p .docker/test/issues-2815/data/rustfs{1..4}-disk{0..3}
```
## Build
Apple Silicon / arm64 host:
```bash
docker build --platform linux/arm64 -f Dockerfile.source -t rustfs-issue-2815-local .
```
If you intentionally want amd64 emulation:
```bash
docker build --platform linux/amd64 -f Dockerfile.source -t rustfs-issue-2815-local .
```
## Run
```bash
docker compose -f .docker/test/issues-2815/docker-compose.yml up -d
```
If the image platform is not `linux/arm64`, align compose explicitly:
```bash
RUSTFS_DOCKER_PLATFORM=linux/amd64 docker compose -f .docker/test/issues-2815/docker-compose.yml up -d
```
## Health Checks
Container-level healthcheck is now included and probes:
```bash
curl -fsS http://127.0.0.1:9000/health
```
Manual checks:
```bash
curl -i http://127.0.0.1:9101/health/ready
curl -i http://127.0.0.1:9102/health/ready
curl -i http://127.0.0.1:9103/health/ready
curl -i http://127.0.0.1:9104/health/ready
```
## RPC Secret Requirement
The current source build no longer reproduces the original `FixArray(1)` decode error from issue `#2815`.
Earlier local Docker attempts failed during erasure bootstrap with:
```text
No valid auth token
store init failed to load formats after 10 retries: erasure read quorum
```
Root cause:
- RPC authentication rejects the default secret `rustfsadmin`
- distributed local Docker validation therefore needs an explicit non-default secret
This compose now sets both:
- `RUSTFS_SECRET_KEY=issue-2815-secret`
- `RUSTFS_RPC_SECRET=issue-2815-rpc-secret`
With those values in place, the current 4-node local Docker cluster reaches healthy state and `/health/ready` returns `200`.
In other words:
- `RUSTFS_ACCESS_KEY` may still be `rustfsadmin` for local service credentials if desired
- `RUSTFS_SECRET_KEY` can still be used for service credentials
- but RPC authentication must not resolve to the default secret value `rustfsadmin`
- if `RUSTFS_RPC_SECRET` is unset, the code falls back to `RUSTFS_SECRET_KEY`
- so at least one of them must provide a non-default shared secret for internode RPC signing
## Suggested Debug Commands
```bash
docker compose -f .docker/test/issues-2815/docker-compose.yml ps
docker compose -f .docker/test/issues-2815/docker-compose.yml logs --no-color --tail=200
docker compose -f .docker/test/issues-2815/docker-compose.yml down -v
```
+120
View File
@@ -0,0 +1,120 @@
services:
rustfs1:
image: rustfs-issue-2815-local
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/arm64}
hostname: rustfs1
container_name: rustfs-issue-2815-rustfs1
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: "rustfsadmin"
RUSTFS_SECRET_KEY: "issue-2815-secret"
RUSTFS_RPC_SECRET: "issue-2815-rpc-secret"
RUSTFS_CONSOLE_ENABLE: "false"
RUST_LOG: "info"
RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "true"
RUSTFS_VOLUMES: "http://rustfs{1...4}:9000/data/rustfs{0...3}"
volumes:
- ./data/rustfs1-disk0:/data/rustfs0
- ./data/rustfs1-disk1:/data/rustfs1
- ./data/rustfs1-disk2:/data/rustfs2
- ./data/rustfs1-disk3:/data/rustfs3
networks: [rustfs-issue-2815-net]
ports:
- "9101:9000"
healthcheck:
test: ["CMD", "sh", "-c", "curl -fsS http://127.0.0.1:9000/health || exit 1"]
interval: 15s
timeout: 5s
retries: 8
start_period: 30s
rustfs2:
image: rustfs-issue-2815-local
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/arm64}
hostname: rustfs2
container_name: rustfs-issue-2815-rustfs2
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: "rustfsadmin"
RUSTFS_SECRET_KEY: "issue-2815-secret"
RUSTFS_RPC_SECRET: "issue-2815-rpc-secret"
RUSTFS_CONSOLE_ENABLE: "false"
RUST_LOG: "info"
RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "true"
RUSTFS_VOLUMES: "http://rustfs{1...4}:9000/data/rustfs{0...3}"
volumes:
- ./data/rustfs2-disk0:/data/rustfs0
- ./data/rustfs2-disk1:/data/rustfs1
- ./data/rustfs2-disk2:/data/rustfs2
- ./data/rustfs2-disk3:/data/rustfs3
networks: [rustfs-issue-2815-net]
ports:
- "9102:9000"
healthcheck:
test: ["CMD", "sh", "-c", "curl -fsS http://127.0.0.1:9000/health || exit 1"]
interval: 15s
timeout: 5s
retries: 8
start_period: 30s
rustfs3:
image: rustfs-issue-2815-local
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/arm64}
hostname: rustfs3
container_name: rustfs-issue-2815-rustfs3
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: "rustfsadmin"
RUSTFS_SECRET_KEY: "issue-2815-secret"
RUSTFS_RPC_SECRET: "issue-2815-rpc-secret"
RUSTFS_CONSOLE_ENABLE: "false"
RUST_LOG: "info"
RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "true"
RUSTFS_VOLUMES: "http://rustfs{1...4}:9000/data/rustfs{0...3}"
volumes:
- ./data/rustfs3-disk0:/data/rustfs0
- ./data/rustfs3-disk1:/data/rustfs1
- ./data/rustfs3-disk2:/data/rustfs2
- ./data/rustfs3-disk3:/data/rustfs3
networks: [rustfs-issue-2815-net]
ports:
- "9103:9000"
healthcheck:
test: ["CMD", "sh", "-c", "curl -fsS http://127.0.0.1:9000/health || exit 1"]
interval: 15s
timeout: 5s
retries: 8
start_period: 30s
rustfs4:
image: rustfs-issue-2815-local
platform: ${RUSTFS_DOCKER_PLATFORM:-linux/arm64}
hostname: rustfs4
container_name: rustfs-issue-2815-rustfs4
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: "rustfsadmin"
RUSTFS_SECRET_KEY: "issue-2815-secret"
RUSTFS_RPC_SECRET: "issue-2815-rpc-secret"
RUSTFS_CONSOLE_ENABLE: "false"
RUST_LOG: "info"
RUSTFS_UNSAFE_BYPASS_DISK_CHECK: "true"
RUSTFS_VOLUMES: "http://rustfs{1...4}:9000/data/rustfs{0...3}"
volumes:
- ./data/rustfs4-disk0:/data/rustfs0
- ./data/rustfs4-disk1:/data/rustfs1
- ./data/rustfs4-disk2:/data/rustfs2
- ./data/rustfs4-disk3:/data/rustfs3
networks: [rustfs-issue-2815-net]
ports:
- "9104:9000"
healthcheck:
test: ["CMD", "sh", "-c", "curl -fsS http://127.0.0.1:9000/health || exit 1"]
interval: 15s
timeout: 5s
retries: 8
start_period: 30s
networks:
rustfs-issue-2815-net:
name: rustfs-issue-2815-net
+183
View File
@@ -0,0 +1,183 @@
# Site Replication Docker Compose Test
## Purpose
This directory contains a local three-site RustFS site replication check. It is intended to verify the admin site-replication flow against real containers:
- three independent RustFS sites start successfully
- the MinIO-compatible `mc admin replicate add` command configures all three sites
- a bucket and object written to site 1 are replicated to site 2 and site 3
- `mc admin replicate status` can read the resulting site-replication state
The compose file uses named volumes so the test does not require preparing host bind-mount directories.
Because Docker named volumes usually share the same physical device in local desktop environments, this test compose defaults `RUSTFS_UNSAFE_BYPASS_DISK_CHECK=true`. Keep that setting limited to local test and CI environments.
## Files
- `docker-compose.yml`: three RustFS sites, a volume permission helper, and a one-shot setup/check container
- `run-object-flow-check.sh`: host-side upload/download verification for replicated 10 MiB to 100 MiB objects
## Ports
Default host endpoints:
- Site 1 API: `http://127.0.0.1:9000`
- Site 1 Console: `http://127.0.0.1:9001`
- Site 2 API: `http://127.0.0.1:9010`
- Site 2 Console: `http://127.0.0.1:9011`
- Site 3 API: `http://127.0.0.1:9020`
- Site 3 Console: `http://127.0.0.1:9021`
Default credentials are `rustfsadmin` / `rustfsadmin`. These are for local testing only.
## Run
From the repository root:
```bash
docker compose -f .docker/test/site-replication/docker-compose.yml up
```
To test a locally built image instead of Docker Hub `rustfs/rustfs:latest`, set `RUSTFS_SITE_REPL_IMAGE`:
```bash
docker build -f Dockerfile.source -t rustfs-site-repl-local:latest .
RUSTFS_SITE_REPL_IMAGE=rustfs-site-repl-local:latest \
docker compose -f .docker/test/site-replication/docker-compose.yml up
```
For a detached run:
```bash
docker compose -f .docker/test/site-replication/docker-compose.yml up -d
docker compose -f .docker/test/site-replication/docker-compose.yml logs -f site-replication-setup
```
## Test Flow
The compose stack performs these steps:
1. `site-replication-volume-permission-helper` fixes ownership on all named volumes for the RustFS runtime user.
2. `rustfs-site1`, `rustfs-site2`, and `rustfs-site3` start as separate RustFS sites.
3. Each site exposes its S3 API and Console on a unique host port.
4. Health checks wait for `/health/ready` on each RustFS container.
5. `site-replication-setup` configures `mc` aliases for all three sites.
6. The setup container waits until `mc admin info` succeeds for all sites.
7. It runs:
```bash
mc admin replicate add site1 site2 site3
```
8. It creates the test bucket on site 1 and uploads `from-site1.txt`.
9. It polls site 2 and site 3 until the replicated object is visible.
10. It prints `mc admin replicate status site1`.
The setup container exits with status `0` only after the object replication check passes.
## Object Flow Check
After the compose setup succeeds, run the larger object flow check from the repository root:
```bash
.docker/test/site-replication/run-object-flow-check.sh
```
The script creates five local files and uploads them from different sites:
- 10 MiB from site 1
- 25 MiB from site 2
- 50 MiB from site 3
- 75 MiB from site 1
- 100 MiB from site 2
For each uploaded object, the script waits for replication to the other two sites, downloads the object from those sites, and verifies both byte size and SHA-256 checksum. It uses a temporary `mc` config directory, so it does not overwrite existing host aliases.
The default bucket is `site-repl-flow-check`. Override it when needed:
```bash
RUSTFS_SITE_REPL_FLOW_BUCKET='site-repl-large-flow' \
.docker/test/site-replication/run-object-flow-check.sh
```
The script keeps uploaded objects under a timestamped prefix. Override the prefix for repeatable runs:
```bash
RUSTFS_SITE_REPL_FLOW_PREFIX='manual-check-001' \
.docker/test/site-replication/run-object-flow-check.sh
```
If replication is slow on the local machine, increase polling:
```bash
RUSTFS_SITE_REPL_WAIT_ATTEMPTS=180 \
RUSTFS_SITE_REPL_WAIT_SLEEP_SECONDS=2 \
.docker/test/site-replication/run-object-flow-check.sh
```
## Optional Settings
Override local test credentials:
```bash
RUSTFS_SITE_REPL_ACCESS_KEY='localadmin' \
RUSTFS_SITE_REPL_SECRET_KEY='localadmin-secret' \
docker compose -f .docker/test/site-replication/docker-compose.yml up
```
Use a different test bucket:
```bash
RUSTFS_SITE_REPL_BUCKET='site-repl-check' \
docker compose -f .docker/test/site-replication/docker-compose.yml up
```
Enable ILM expiry rule replication during site setup:
```bash
RUSTFS_SITE_REPL_ENABLE_ILM_EXPIRY=true \
docker compose -f .docker/test/site-replication/docker-compose.yml up
```
Use this only when the test needs lifecycle expiry metadata included in site replication.
## Manual Checks
After the setup container succeeds, you can inspect the sites with `mc` from the host:
```bash
mc alias set site1 http://127.0.0.1:9000 rustfsadmin rustfsadmin
mc alias set site2 http://127.0.0.1:9010 rustfsadmin rustfsadmin
mc alias set site3 http://127.0.0.1:9020 rustfsadmin rustfsadmin
mc admin replicate info site1
mc admin replicate status site1
mc stat site2/site-repl-demo/from-site1.txt
mc stat site3/site-repl-demo/from-site1.txt
```
After larger object flow checks, replication should converge without a growing queue:
```bash
mc admin replicate status site1
mc admin replicate status site2
mc admin replicate status site3
```
Useful Docker commands:
```bash
docker compose -f .docker/test/site-replication/docker-compose.yml ps
docker compose -f .docker/test/site-replication/docker-compose.yml logs --no-color --tail=200
docker compose -f .docker/test/site-replication/docker-compose.yml logs --no-color site-replication-setup
```
## Cleanup
Remove containers and named volumes:
```bash
docker compose -f .docker/test/site-replication/docker-compose.yml down -v
```
Use `down -v` before rerunning the full setup from scratch. Site replication state is persisted in the named volumes, so rerunning without deleting volumes may attempt to add an already-configured replication topology.
@@ -0,0 +1,243 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
services:
rustfs-site1:
image: ${RUSTFS_SITE_REPL_IMAGE:-rustfs/rustfs:latest}
container_name: rustfs-site-repl-1
security_opt:
- "no-new-privileges:true"
ports:
- "9000:9000"
- "9001:9001"
environment:
- RUSTFS_VOLUMES=/data/rustfs{0...3}
- RUSTFS_ADDRESS=0.0.0.0:9000
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
- RUSTFS_ACCESS_KEY=${RUSTFS_SITE_REPL_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SITE_REPL_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_SITE_REPL_LOG_LEVEL:-info}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
volumes:
- site1_data_0:/data/rustfs0
- site1_data_1:/data/rustfs1
- site1_data_2:/data/rustfs2
- site1_data_3:/data/rustfs3
- site1_logs:/app/logs
networks:
- rustfs-site-replication
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9000/health/ready"]
interval: 10s
timeout: 5s
retries: 30
start_period: 20s
depends_on:
site-replication-volume-permission-helper:
condition: service_completed_successfully
rustfs-site2:
image: ${RUSTFS_SITE_REPL_IMAGE:-rustfs/rustfs:latest}
container_name: rustfs-site-repl-2
security_opt:
- "no-new-privileges:true"
ports:
- "9010:9000"
- "9011:9001"
environment:
- RUSTFS_VOLUMES=/data/rustfs{0...3}
- RUSTFS_ADDRESS=0.0.0.0:9000
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
- RUSTFS_ACCESS_KEY=${RUSTFS_SITE_REPL_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SITE_REPL_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_SITE_REPL_LOG_LEVEL:-info}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
volumes:
- site2_data_0:/data/rustfs0
- site2_data_1:/data/rustfs1
- site2_data_2:/data/rustfs2
- site2_data_3:/data/rustfs3
- site2_logs:/app/logs
networks:
- rustfs-site-replication
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9000/health/ready"]
interval: 10s
timeout: 5s
retries: 30
start_period: 20s
depends_on:
site-replication-volume-permission-helper:
condition: service_completed_successfully
rustfs-site3:
image: ${RUSTFS_SITE_REPL_IMAGE:-rustfs/rustfs:latest}
container_name: rustfs-site-repl-3
security_opt:
- "no-new-privileges:true"
ports:
- "9020:9000"
- "9021:9001"
environment:
- RUSTFS_VOLUMES=/data/rustfs{0...3}
- RUSTFS_ADDRESS=0.0.0.0:9000
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
- RUSTFS_CONSOLE_ENABLE=true
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
- RUSTFS_ACCESS_KEY=${RUSTFS_SITE_REPL_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SECRET_KEY=${RUSTFS_SITE_REPL_SECRET_KEY:-rustfsadmin}
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_SITE_REPL_LOG_LEVEL:-info}
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
volumes:
- site3_data_0:/data/rustfs0
- site3_data_1:/data/rustfs1
- site3_data_2:/data/rustfs2
- site3_data_3:/data/rustfs3
- site3_logs:/app/logs
networks:
- rustfs-site-replication
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9000/health/ready"]
interval: 10s
timeout: 5s
retries: 30
start_period: 20s
depends_on:
site-replication-volume-permission-helper:
condition: service_completed_successfully
site-replication-setup:
image: minio/mc:latest
container_name: rustfs-site-repl-setup
depends_on:
rustfs-site1:
condition: service_healthy
rustfs-site2:
condition: service_healthy
rustfs-site3:
condition: service_healthy
environment:
- RUSTFS_SITE_REPL_ACCESS_KEY=${RUSTFS_SITE_REPL_ACCESS_KEY:-rustfsadmin}
- RUSTFS_SITE_REPL_SECRET_KEY=${RUSTFS_SITE_REPL_SECRET_KEY:-rustfsadmin}
- RUSTFS_SITE_REPL_BUCKET=${RUSTFS_SITE_REPL_BUCKET:-site-repl-demo}
- RUSTFS_SITE_REPL_ENABLE_ILM_EXPIRY=${RUSTFS_SITE_REPL_ENABLE_ILM_EXPIRY:-false}
networks:
- rustfs-site-replication
entrypoint: ["/bin/sh", "-c"]
command:
- |
set -eu
access_key="$${RUSTFS_SITE_REPL_ACCESS_KEY}"
secret_key="$${RUSTFS_SITE_REPL_SECRET_KEY}"
bucket="$${RUSTFS_SITE_REPL_BUCKET}"
mc alias set site1 http://rustfs-site1:9000 "$${access_key}" "$${secret_key}"
mc alias set site2 http://rustfs-site2:9000 "$${access_key}" "$${secret_key}"
mc alias set site3 http://rustfs-site3:9000 "$${access_key}" "$${secret_key}"
for site in site1 site2 site3; do
until mc ls "$${site}" >/dev/null 2>&1; do
echo "waiting for $${site} S3 API"
sleep 2
done
done
ilm_flag=""
if [ "$${RUSTFS_SITE_REPL_ENABLE_ILM_EXPIRY}" = "true" ]; then
ilm_flag="--replicate-ilm-expiry"
fi
echo "configuring 3-site replication"
if ! mc admin replicate add site1 site2 site3 $${ilm_flag}; then
echo "replicate add failed; showing current replication info before exiting"
mc admin replicate info site1 || true
exit 1
fi
mc mb --ignore-existing "site1/$${bucket}"
printf 'rustfs 3-site replication check\n' > /tmp/site-replication-check.txt
mc cp /tmp/site-replication-check.txt "site1/$${bucket}/from-site1.txt"
for site in site2 site3; do
for attempt in $$(seq 1 60); do
if mc stat "$${site}/$${bucket}/from-site1.txt" >/dev/null 2>&1; then
echo "$${site} received replicated object"
break
fi
if [ "$${attempt}" = "60" ]; then
echo "$${site} did not receive replicated object in time"
exit 1
fi
sleep 2
done
done
mc admin replicate status site1
echo "3-site replication example is ready"
restart: "no"
site-replication-volume-permission-helper:
image: alpine:3.23.4
volumes:
- site1_data_0:/site1/data0
- site1_data_1:/site1/data1
- site1_data_2:/site1/data2
- site1_data_3:/site1/data3
- site1_logs:/site1/logs
- site2_data_0:/site2/data0
- site2_data_1:/site2/data1
- site2_data_2:/site2/data2
- site2_data_3:/site2/data3
- site2_logs:/site2/logs
- site3_data_0:/site3/data0
- site3_data_1:/site3/data1
- site3_data_2:/site3/data2
- site3_data_3:/site3/data3
- site3_logs:/site3/logs
command: >
sh -c "
chown -R 10001:10001 /site1 /site2 /site3 &&
echo 'site replication volume permissions fixed'
"
networks:
- rustfs-site-replication
restart: "no"
networks:
rustfs-site-replication:
volumes:
site1_data_0:
site1_data_1:
site1_data_2:
site1_data_3:
site1_logs:
site2_data_0:
site2_data_1:
site2_data_2:
site2_data_3:
site2_logs:
site3_data_0:
site3_data_1:
site3_data_2:
site3_data_3:
site3_logs:
+191
View File
@@ -0,0 +1,191 @@
#!/bin/sh
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eu
ACCESS_KEY="${RUSTFS_SITE_REPL_ACCESS_KEY:-rustfsadmin}"
SECRET_KEY="${RUSTFS_SITE_REPL_SECRET_KEY:-rustfsadmin}"
BUCKET="${RUSTFS_SITE_REPL_FLOW_BUCKET:-site-repl-flow-check}"
PREFIX="${RUSTFS_SITE_REPL_FLOW_PREFIX:-flow-$(date +%Y%m%d-%H%M%S)}"
WAIT_ATTEMPTS="${RUSTFS_SITE_REPL_WAIT_ATTEMPTS:-90}"
WAIT_SLEEP_SECONDS="${RUSTFS_SITE_REPL_WAIT_SLEEP_SECONDS:-2}"
SITE1_ENDPOINT="${RUSTFS_SITE1_ENDPOINT:-http://127.0.0.1:9000}"
SITE2_ENDPOINT="${RUSTFS_SITE2_ENDPOINT:-http://127.0.0.1:9010}"
SITE3_ENDPOINT="${RUSTFS_SITE3_ENDPOINT:-http://127.0.0.1:9020}"
command_exists() {
command -v "$1" >/dev/null 2>&1
}
require_command() {
if ! command_exists "$1"; then
echo "missing required command: $1" >&2
exit 1
fi
}
checksum_file() {
if command_exists sha256sum; then
sha256sum "$1" | awk '{print $1}'
elif command_exists shasum; then
shasum -a 256 "$1" | awk '{print $1}'
else
echo "missing required command: sha256sum or shasum" >&2
exit 1
fi
}
site_endpoint() {
case "$1" in
site1) printf '%s\n' "$SITE1_ENDPOINT" ;;
site2) printf '%s\n' "$SITE2_ENDPOINT" ;;
site3) printf '%s\n' "$SITE3_ENDPOINT" ;;
*) echo "unknown site alias: $1" >&2; exit 1 ;;
esac
}
other_sites() {
case "$1" in
site1) printf '%s\n' "site2 site3" ;;
site2) printf '%s\n' "site1 site3" ;;
site3) printf '%s\n' "site1 site2" ;;
*) echo "unknown source site: $1" >&2; exit 1 ;;
esac
}
wait_for_object() {
site="$1"
object="$2"
attempt=1
while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
if mc stat "$site/$BUCKET/$object" >/dev/null 2>&1; then
return 0
fi
sleep "$WAIT_SLEEP_SECONDS"
attempt=$((attempt + 1))
done
echo "object was not replicated in time: $site/$BUCKET/$object" >&2
return 1
}
wait_for_bucket() {
site="$1"
attempt=1
while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
if mc stat "$site/$BUCKET" >/dev/null 2>&1; then
return 0
fi
sleep "$WAIT_SLEEP_SECONDS"
attempt=$((attempt + 1))
done
echo "bucket was not replicated in time: $site/$BUCKET" >&2
return 1
}
require_command mc
require_command dd
require_command awk
require_command date
require_command mktemp
require_command tr
require_command wc
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/rustfs-site-repl-flow.XXXXXX")"
MC_CONFIG_DIR="$WORK_DIR/mc"
export MC_CONFIG_DIR
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT INT TERM
mkdir -p "$MC_CONFIG_DIR" "$WORK_DIR/src" "$WORK_DIR/downloads"
for site in site1 site2 site3; do
mc alias set "$site" "$(site_endpoint "$site")" "$ACCESS_KEY" "$SECRET_KEY" >/dev/null
done
for site in site1 site2 site3; do
echo "checking S3 API for $site"
mc ls "$site" >/dev/null
done
echo "ensuring bucket exists on site1: $BUCKET"
mc mb --ignore-existing "site1/$BUCKET" >/dev/null
for site in site1 site2 site3; do
wait_for_bucket "$site"
done
cat <<'EOF' | while read -r size_mb source_site object_name; do
10 site1 object-010m.bin
25 site2 object-025m.bin
50 site3 object-050m.bin
75 site1 object-075m.bin
100 site2 object-100m.bin
EOF
src_file="$WORK_DIR/src/$object_name"
object="$PREFIX/$object_name"
echo "creating ${size_mb}MiB file: $object_name"
dd if=/dev/urandom of="$src_file" bs=1048576 count="$size_mb" >/dev/null 2>&1
src_checksum="$(checksum_file "$src_file")"
src_bytes="$(wc -c < "$src_file" | tr -d ' ')"
echo "uploading $object_name to $source_site ($src_bytes bytes)"
mc cp "$src_file" "$source_site/$BUCKET/$object" >/dev/null
for site in $(other_sites "$source_site"); do
wait_for_object "$site" "$object"
dst_file="$WORK_DIR/downloads/$site-$object_name"
verified=false
attempt=1
while [ "$attempt" -le "$WAIT_ATTEMPTS" ]; do
rm -f "$dst_file"
echo "downloading $object_name from $site"
if mc cp "$site/$BUCKET/$object" "$dst_file" >/dev/null 2>&1; then
dst_checksum="$(checksum_file "$dst_file")"
dst_bytes="$(wc -c < "$dst_file" | tr -d ' ')"
if [ "$dst_checksum" = "$src_checksum" ] && [ "$dst_bytes" = "$src_bytes" ]; then
verified=true
break
fi
fi
sleep "$WAIT_SLEEP_SECONDS"
attempt=$((attempt + 1))
done
if [ "$verified" != "true" ]; then
echo "download verification failed for $site/$BUCKET/$object" >&2
echo "expected checksum: $src_checksum" >&2
echo "expected bytes: $src_bytes" >&2
exit 1
fi
done
echo "verified replicated downloads for $object_name"
done
echo "site replication object flow check passed"
echo "bucket: $BUCKET"
echo "prefix: $PREFIX"
+3
View File
@@ -1 +1,4 @@
target
.docker/compat/data
.docker/compat/kms
target/compat
+4 -15
View File
@@ -19,18 +19,7 @@ Applies to `.github/` and repository pull-request operations.
## CI Alignment
When changing CI-sensitive behavior, keep local validation aligned with `.github/workflows/ci.yml`.
Current `test-and-lint` gate includes:
- `cargo nextest run --all --exclude e2e_test`
- `cargo test --all --doc`
- `cargo test -p rustfs get_object_chunk_fast_path`
- `cargo test -p rustfs materialize_chunk_stream_before_commit`
- `touch rustfs/build.rs`
- `cargo build -p rustfs --bins --jobs 2`
- `cargo test -p e2e_test archive_multipart_roundtrip_preserves_bytes`
- `cargo test -p e2e_test presigned_get_and_reverse_proxy_preserve_multipart_bytes_with_fast_path`
- `cargo fmt --all --check`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `./scripts/check_layer_dependencies.sh`
When changing CI-sensitive behavior, keep local validation aligned with
`.github/workflows/ci.yml`. Read the workflow file directly for the current
gate steps — do not rely on (or add) a copied command list here; copies go
stale. `make pre-pr` is the local equivalent of the main gate.
+14 -11
View File
@@ -53,21 +53,24 @@ runs:
musl-tools \
build-essential \
pkg-config \
libssl-dev
libssl-dev \
ripgrep \
unzip \
protobuf-compiler
- name: Install protoc
uses: arduino/setup-protoc@v3
uses: rustfs/setup-protoc@a3705324d8f9bf5b6c3573fb6cf8ae421db55dd6 # v3.0.1
with:
version: "33.1"
repo-token: ${{ inputs.github-token }}
version: "34.1"
repo-token: ${{ github.token }}
- name: Install flatc
uses: Nugine/setup-flatc@v1.2.4
uses: Nugine/setup-flatc@e7855e994773ce90094a3f1626d4afc9080c23ae # v1
with:
version: "25.9.23"
version: "25.12.19"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: ${{ inputs.rust-version }}
targets: ${{ inputs.target }}
@@ -75,17 +78,17 @@ runs:
- name: Install Zig
if: inputs.install-cross-tools == 'true'
uses: mlugg/setup-zig@v2
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
- name: Install cargo-zigbuild
if: inputs.install-cross-tools == 'true'
uses: taiki-e/install-action@cargo-zigbuild
uses: taiki-e/install-action@a21ae4029b089b9ddc45704028756f51ab8abe48 # cargo-zigbuild
- name: Install cargo-nextest
uses: taiki-e/install-action@cargo-nextest
uses: taiki-e/install-action@96c7780c1d8a2b8723e12031def873a434d39d8d # nextest
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
cache-all-crates: true
cache-on-failure: true
+14
View File
@@ -1,3 +1,17 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
enabled: true
document:
+5 -2
View File
@@ -27,9 +27,8 @@ updates:
timezone: "Asia/Shanghai"
time: "08:00"
assignees:
- "heihutu"
reviewers:
- "houseme"
reviewers:
- "overtrue"
- "majinghe"
ignore:
@@ -39,6 +38,10 @@ updates:
versions: [ "0.23.x" ]
- dependency-name: "ratelimit"
versions: [ "1.x" ]
- dependency-name: "ratelimit"
versions: [ "2.x" ]
- dependency-name: "pyroscope"
versions: [ "2.x" ]
groups:
s3s:
update-types:
+20 -21
View File
@@ -2,35 +2,34 @@
Pull Request Template for RustFS
-->
## Type of Change
- [ ] New Feature
- [ ] Bug Fix
- [ ] Documentation
- [ ] Performance Improvement
- [ ] Test/CI
- [ ] Refactor
- [ ] Other:
## Related Issues
<!-- List related Issue numbers, e.g. #123 -->
<!--
List related issues, e.g. Fixes #123.
Use N/A when there is no related issue.
-->
## Summary of Changes
<!-- Briefly describe the main changes and motivation for this PR -->
<!--
Briefly explain what changed and why reviewers should accept it.
Focus on behavior, compatibility, and review-relevant context.
-->
## Checklist
- [ ] I have read and followed the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines
- [ ] Passed `make pre-commit`
- [ ] Added/updated necessary tests
- [ ] Documentation updated (if needed)
- [ ] CI/CD passed (if applicable)
## Verification
<!--
List the commands or checks you ran, for example:
- `make pre-commit`
Use N/A only when verification is not applicable.
-->
## Impact
- [ ] Breaking change (compatibility)
- [ ] Requires doc/config/deployment update
- [ ] Other impact:
<!--
Describe user-facing, compatibility, API, deployment, configuration, or
documentation impact. Use N/A when there is no expected impact.
-->
## Additional Notes
<!-- Any extra information for reviewers -->
<!-- Any extra information for reviewers, or N/A. -->
---
+51 -73
View File
@@ -1,103 +1,81 @@
# S3 Compatibility Tests Configuration
This directory contains the configuration for running [Ceph S3 compatibility tests](https://github.com/ceph/s3-tests) against RustFS.
This directory contains the configuration template for running
[Ceph S3 compatibility tests](https://github.com/ceph/s3-tests) against RustFS.
The test runner itself lives in [`scripts/s3-tests/`](../../scripts/s3-tests/)
— see its README for how tests are selected, executed, and reported. This
directory only holds the shared config template.
## Configuration File
The `s3tests.conf` file is based on the official `s3tests.conf.SAMPLE` from the ceph/s3-tests repository. It uses environment variable substitution via `envsubst` to configure the endpoint and credentials.
`s3tests.conf` is based on the official `s3tests.conf.SAMPLE` from the
ceph/s3-tests repository. It is a template: `scripts/s3-tests/run.sh` renders
it with `envsubst` before every run.
### Key Configuration Points
Required environment variables (all have defaults in `run.sh`):
- **Host**: Set via `${S3_HOST}` environment variable (e.g., `rustfs-single` for single-node, `lb` for multi-node)
- **Port**: 9000 (standard RustFS port)
- **Credentials**: Uses `${S3_ACCESS_KEY}` and `${S3_SECRET_KEY}` from workflow environment
- **TLS**: Disabled (`is_secure = False`)
| Variable | Meaning |
|----------|---------|
| `S3_HOST` | RustFS endpoint host |
| `S3_PORT` | RustFS endpoint port |
| `S3_ACCESS_KEY` / `S3_SECRET_KEY` | main user credentials |
| `S3_ALT_ACCESS_KEY` / `S3_ALT_SECRET_KEY` | alt user credentials (must differ from main) |
## Test Execution Strategy
TLS is disabled (`is_secure = False`).
### Network Connectivity Fix
## Test Selection Strategy
Tests run inside a Docker container on the `rustfs-net` network, which allows them to resolve and connect to the RustFS container hostnames. This fixes the "Temporary failure in name resolution" error that occurred when tests ran on the GitHub runner host.
Tests are selected by exact pytest node ids from classification list files in
`scripts/s3-tests/`**not** by pytest markers:
### Performance Optimizations
- `implemented_tests.txt` — expected to pass; run as the per-PR gate
(`ci.yml`, job `s3-implemented-tests`)
- `unimplemented_tests.txt` — standard S3 features not yet implemented
- `excluded_tests.txt` — intentionally excluded (vendor-specific behavior or
product decisions)
1. **Parallel Execution**: Uses `pytest-xdist` with `-n 4` to run tests in parallel across 4 workers
2. **Load Distribution**: Uses `--dist=loadgroup` to distribute test groups across workers
3. **Fail-Fast**: Uses `--maxfail=50` to stop after 50 failures, saving time on catastrophic failures
The upstream s3-tests repository is pinned to a fixed commit (`S3TESTS_REV` in
`run.sh`) so results are reproducible; bump it deliberately and reclassify the
lists when upstream changes.
### Feature Filtering
## Where Tests Run
Tests are filtered using pytest markers (`-m`) to skip features not yet supported by RustFS:
- `lifecycle` - Bucket lifecycle policies
- `versioning` - Object versioning
- `s3website` - Static website hosting
- `bucket_logging` - Bucket logging
- `encryption` / `sse_s3` - Server-side encryption
- `cloud_transition` / `cloud_restore` - Cloud storage transitions
- `lifecycle_expiration` / `lifecycle_transition` - Lifecycle operations
This filtering:
1. Reduces test execution time significantly (from 1+ hour to ~10-15 minutes)
2. Focuses on features RustFS currently supports
3. Avoids hundreds of expected failures
- **Per PR**: `.github/workflows/ci.yml` job `s3-implemented-tests` runs the
implemented whitelist against a single-node debug binary. Any failure blocks
the PR.
- **Weekly + manual**: `.github/workflows/e2e-s3tests.yml` runs the full
upstream suite (`TEST_SCOPE=all`) against a Docker deployment (single node
or a 4-node distributed cluster behind HAProxy). It fails only on
regressions in the implemented whitelist and publishes a classification
report (`compat-report.md`, also shown in the job summary) listing promotion
candidates and unclassified tests.
## Running Tests Locally
### Single-Node Test
```bash
# Set credentials
export S3_ACCESS_KEY=rustfsadmin
export S3_SECRET_KEY=rustfsadmin
# Whitelist run against a locally built binary (the same thing the PR gate does)
./scripts/s3-tests/run.sh
# Start RustFS container
docker run -d --name rustfs-single \
--network rustfs-net \
-e RUSTFS_ADDRESS=0.0.0.0:9000 \
-e RUSTFS_ACCESS_KEY=$S3_ACCESS_KEY \
-e RUSTFS_SECRET_KEY=$S3_SECRET_KEY \
-e RUSTFS_VOLUMES="/data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3" \
rustfs-ci
# Full sweep, never stop on failures, 4 pytest workers
TEST_SCOPE=all MAXFAIL=0 XDIST=4 ./scripts/s3-tests/run.sh
# Generate config
export S3_HOST=rustfs-single
envsubst < .github/s3tests/s3tests.conf > /tmp/s3tests.conf
# Run tests
docker run --rm \
--network rustfs-net \
-v /tmp/s3tests.conf:/etc/s3tests.conf:ro \
python:3.12-slim \
bash -c '
apt-get update -qq && apt-get install -y -qq git
git clone --depth 1 https://github.com/ceph/s3-tests.git /s3-tests
cd /s3-tests
pip install -q -r requirements.txt pytest-xdist
S3TEST_CONF=/etc/s3tests.conf pytest -v -n 4 \
s3tests/functional/test_s3.py \
-m "not lifecycle and not versioning and not s3website and not bucket_logging and not encryption and not sse_s3"
'
# A specific test against an already-running server
DEPLOY_MODE=existing TESTEXPR="test_bucket_list_empty" ./scripts/s3-tests/run.sh
```
## Test Results Interpretation
- **PASSED**: Test succeeded, feature works correctly
- **FAILED**: Test failed, indicates a potential bug or incompatibility
- **ERROR**: Test setup failed (e.g., network issues, missing dependencies)
- **SKIPPED**: Test skipped due to marker filtering
Results land in `artifacts/s3tests-single/` (`junit.xml`, `pytest.log`,
`compat-report.md`).
## Adding New Feature Support
When adding support for a new S3 feature to RustFS:
1. Remove the corresponding marker from the filter in `.github/workflows/e2e-s3tests.yml`
2. Run the tests to verify compatibility
3. Fix any failing tests
4. Update this README to reflect the newly supported feature
Follow [`scripts/s3-tests/S3_COMPAT_WORKFLOW.md`](../../scripts/s3-tests/S3_COMPAT_WORKFLOW.md).
In short: fix the behavior, verify with `TESTEXPR=...`, then move the test
from `unimplemented_tests.txt` to `implemented_tests.txt` so the PR gate locks
it in.
## References
- [Ceph S3 Tests Repository](https://github.com/ceph/s3-tests)
- [S3 API Compatibility](https://docs.aws.amazon.com/AmazonS3/latest/API/)
- [pytest-xdist Documentation](https://pytest-xdist.readthedocs.io/)
- [RustFS test runner](../../scripts/s3-tests/README.md)
+2 -2
View File
@@ -11,8 +11,8 @@
# host set for RustFS - will be substituted via envsubst
host = ${S3_HOST}
# port for RustFS
port = 9000
# port for RustFS - will be substituted via envsubst
port = ${S3_PORT}
## say "False" to disable TLS
is_secure = False
@@ -0,0 +1,57 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Architecture Migration Rules
on:
pull_request:
types: [ opened, synchronize, reopened, closed ]
branches: [ main ]
paths:
- "ARCHITECTURE.md"
- "docs/architecture/**"
- "scripts/check_architecture_migration_rules.sh"
- ".github/workflows/architecture-migration-rules.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
cancel-closed-pr-runs:
name: Cancel Closed PR Runs
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Explain cancellation run
run: echo "PR closed; this run only cancels older runs in the same concurrency group."
architecture-migration-rules:
name: Architecture Migration Rules
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install ripgrep
run: |
sudo apt-get update
sudo apt-get install -y ripgrep
- name: Check architecture migration rules
run: ./scripts/check_architecture_migration_rules.sh
+66 -11
View File
@@ -20,13 +20,20 @@ on:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
- '.github/workflows/audit.yml'
- 'deny.toml'
- '.github/actions/**'
- '.github/workflows/**'
- 'scripts/security/check_workflow_pins.sh'
pull_request:
types: [ opened, synchronize, reopened, closed ]
branches: [ main ]
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
- '.github/workflows/audit.yml'
- 'deny.toml'
- '.github/actions/**'
- '.github/workflows/**'
- 'scripts/security/check_workflow_pins.sh'
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday at midnight UTC
workflow_dispatch:
@@ -34,20 +41,33 @@ on:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
cancel-closed-pr-runs:
name: Cancel Closed PR Runs
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Explain cancellation run
run: echo "PR closed; this run only cancels older runs in the same concurrency group."
security-audit:
name: Security Audit
runs-on: ubicloud-standard-2
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install cargo-audit
uses: taiki-e/install-action@v2
uses: taiki-e/install-action@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2
with:
tool: cargo-audit
@@ -57,25 +77,60 @@ jobs:
- name: Upload audit results
if: always()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: security-audit-results-${{ github.run_number }}
path: audit-results.json
retention-days: 30
cargo-deny:
name: Cargo Deny
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
cache-shared-key: rustfs-cargo-deny
- name: Install cargo-deny
uses: taiki-e/install-action@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2
with:
tool: cargo-deny
- name: Run cargo-deny
run: cargo deny check --hide-inclusion-graph advisories sources bans licenses
workflow-pin-report:
name: Workflow Pin Report
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Report unpinned GitHub Actions
run: ./scripts/security/check_workflow_pins.sh --enforce
dependency-review:
name: Dependency Review
runs-on: ubicloud-standard-2
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.action != 'closed'
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Dependency Review
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5
with:
fail-on-severity: moderate
comment-summary-in-pr: true
allow-ghsas: GHSA-2f9f-gq7v-9h6m
comment-summary-in-pr: always
+172 -108
View File
@@ -63,6 +63,10 @@ on:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'push' }}
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
@@ -73,7 +77,7 @@ jobs:
# Build strategy check - determine build type based on trigger
build-check:
name: Build Strategy Check
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.check.outputs.should_build }}
build_type: ${{ steps.check.outputs.build_type }}
@@ -82,7 +86,7 @@ jobs:
is_prerelease: ${{ steps.check.outputs.is_prerelease }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
@@ -146,7 +150,7 @@ jobs:
# Build RustFS binaries
prepare-platform-matrix:
name: Prepare Platform Matrix
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
selected: ${{ steps.select.outputs.selected }}
@@ -164,12 +168,12 @@ jobs:
fi
all='{"include":[
{"target_id":"linux-x86_64-musl","os":"ubicloud-standard-2","target":"x86_64-unknown-linux-musl","cross":false,"platform":"linux","rustflags":""},
{"target_id":"linux-aarch64-musl","os":"ubicloud-standard-2","target":"aarch64-unknown-linux-musl","cross":true,"platform":"linux","rustflags":""},
{"target_id":"linux-x86_64-gnu","os":"ubicloud-standard-2","target":"x86_64-unknown-linux-gnu","cross":false,"platform":"linux","rustflags":""},
{"target_id":"linux-aarch64-gnu","os":"ubicloud-standard-2","target":"aarch64-unknown-linux-gnu","cross":true,"platform":"linux","rustflags":""},
{"target_id":"linux-x86_64-musl","os":"sm-standard-2","target":"x86_64-unknown-linux-musl","cross":false,"platform":"linux","rustflags":""},
{"target_id":"linux-aarch64-musl","os":"sm-standard-2","target":"aarch64-unknown-linux-musl","cross":true,"platform":"linux","rustflags":""},
{"target_id":"linux-x86_64-gnu","os":"sm-standard-2","target":"x86_64-unknown-linux-gnu","cross":false,"platform":"linux","rustflags":""},
{"target_id":"linux-aarch64-gnu","os":"sm-standard-2","target":"aarch64-unknown-linux-gnu","cross":true,"platform":"linux","rustflags":""},
{"target_id":"macos-aarch64","os":"macos-latest","target":"aarch64-apple-darwin","cross":false,"platform":"macos","rustflags":""},
{"target_id":"macos-x86_64","os":"macos-latest","target":"x86_64-apple-darwin","cross":false,"platform":"macos","rustflags":""},
{"target_id":"macos-x86_64","os":"macos-26-intel","target":"x86_64-apple-darwin","cross":false,"platform":"macos","rustflags":""},
{"target_id":"windows-x86_64","os":"windows-latest","target":"x86_64-pc-windows-msvc","cross":false,"platform":"windows","rustflags":""}
]}'
@@ -201,8 +205,8 @@ jobs:
name: Build RustFS
needs: [ build-check, prepare-platform-matrix ]
if: needs.build-check.outputs.should_build == 'true' && needs.prepare-platform-matrix.result == 'success'
runs-on: ${{ matrix.os }}
timeout-minutes: 60
runs-on: ${{ matrix.platform == 'linux' && fromJSON('["self-hosted","linux","sm-standard-2"]') || matrix.os }}
timeout-minutes: 90
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
# Always enable Tokio unstable features (required by dial9-tokio-telemetry).
@@ -215,7 +219,7 @@ jobs:
matrix: ${{ fromJson(needs.prepare-platform-matrix.outputs.matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
@@ -224,35 +228,117 @@ jobs:
with:
rust-version: stable
target: ${{ matrix.target }}
cache-shared-key: build-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
cache-shared-key: build-${{ matrix.target }}
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
install-cross-tools: ${{ matrix.cross }}
- name: Download static console assets
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
mkdir -p ./rustfs/static
if [[ "${{ matrix.platform }}" == "windows" ]]; then
curl.exe -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" -o console.zip --retry 3 --retry-delay 5 --max-time 300
if [[ $? -eq 0 ]]; then
unzip -o console.zip -d ./rustfs/static
rm console.zip
verify_sha256() {
local expected="$1"
local file="$2"
local actual=""
local actual_line=""
if command -v sha256sum >/dev/null 2>&1; then
actual_line=$(sha256sum -- "$file") || return 1
elif command -v shasum >/dev/null 2>&1; then
actual_line=$(shasum -a 256 -- "$file") || return 1
else
echo "Warning: Failed to download console assets, continuing without them"
echo "// Static assets not available" > ./rustfs/static/empty.txt
echo "No SHA-256 verification tool found" >&2
return 1
fi
actual="${actual_line%%[[:space:]]*}"
if [ "$actual" = "$expected" ]; then
echo "SHA256 verified OK: $actual"
return 0
else
echo "SHA256 mismatch: expected=$expected actual=$actual" >&2
return 1
fi
}
download_console_assets() {
local curl_bin="curl"
local python_bin="python3"
local console_api="https://api.github.com/repos/rustfs/console/releases/latest"
local console_json="console-release.json"
local console_url
local console_sha256
local curl_auth_args=()
if [[ "${{ matrix.platform }}" == "windows" ]]; then
curl_bin="curl.exe"
else
chmod +w ./rustfs/static/LICENSE || true
fi
if ! command -v "$python_bin" >/dev/null 2>&1; then
python_bin="python"
fi
if ! command -v "$python_bin" >/dev/null 2>&1; then
echo "No Python interpreter found for release metadata parsing" >&2
return 1
fi
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
curl_auth_args=(-H "Authorization: Bearer ${GITHUB_TOKEN}" -H "X-GitHub-Api-Version: 2022-11-28")
fi
"$curl_bin" "${curl_auth_args[@]}" --fail -L "$console_api" \
-o "$console_json" --retry 3 --retry-delay 5 --max-time 300 || return 1
read -r console_url console_sha256 < <("$python_bin" - "$console_json" <<'PY'
import json
import re
import sys
with open(sys.argv[1], encoding="utf-8") as handle:
release = json.load(handle)
for asset in release.get("assets", []):
name = asset.get("name", "")
digest = asset.get("digest", "")
url = asset.get("browser_download_url", "")
if name.startswith("rustfs-console-") and name.endswith(".zip") and digest.startswith("sha256:") and url:
sha256 = digest.split(":", 1)[1]
if not re.fullmatch(r"[0-9a-fA-F]{64}", sha256):
raise SystemExit(f"console zip asset has invalid sha256 digest: {sha256}")
sys.stdout.buffer.write(f"{url} {sha256}\n".encode("utf-8"))
break
else:
raise SystemExit("no console zip asset with sha256 digest found")
PY
) || return 1
if [[ -z "$console_url" || -z "$console_sha256" ]]; then
echo "Console release metadata is missing URL or SHA-256 digest" >&2
return 1
fi
"$curl_bin" --fail -L "$console_url" -o console.zip --retry 3 --retry-delay 5 --max-time 300 || return 1
verify_sha256 "$console_sha256" console.zip || return 2
unzip -o console.zip -d ./rustfs/static || return 2
}
if download_console_assets; then
rm -f console.zip console-release.json
else
chmod +w ./rustfs/static/LICENSE || true
curl -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" \
-o console.zip --retry 3 --retry-delay 5 --max-time 300
if [[ $? -eq 0 ]]; then
unzip -o console.zip -d ./rustfs/static
rm console.zip
else
echo "Warning: Failed to download console assets, continuing without them"
echo "// Static assets not available" > ./rustfs/static/empty.txt
status=$?
rm -f console.zip console-release.json
if [[ "$status" -eq 2 ]]; then
echo "Console asset integrity verification failed" >&2
exit 1
fi
echo "Warning: Failed to download verified console assets, continuing without them"
echo "// Static assets not available" > ./rustfs/static/empty.txt
fi
- name: Build RustFS
@@ -262,14 +348,8 @@ jobs:
touch rustfs/build.rs
if [[ "${{ matrix.cross }}" == "true" ]]; then
if [[ "${{ matrix.platform }}" == "windows" ]]; then
# Use cross for Windows ARM64
cargo install cross --git https://github.com/cross-rs/cross
cross build --release --target ${{ matrix.target }} -p rustfs --bins
else
# Use zigbuild for other cross-compilation
cargo zigbuild --release --target ${{ matrix.target }} -p rustfs --bins
fi
# All cross targets in the matrix are Linux; zigbuild handles them.
cargo zigbuild --release --target ${{ matrix.target }} -p rustfs --bins
else
cargo build --release --target ${{ matrix.target }} -p rustfs --bins
fi
@@ -461,85 +541,53 @@ jobs:
echo "📊 Version: ${VERSION}"
- name: Upload to GitHub artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: ${{ steps.package.outputs.package_name }}
path: "rustfs-*.zip"
retention-days: ${{ startsWith(github.ref, 'refs/tags/') && 30 || 7 }}
- name: Upload to Aliyun OSS
if: env.OSS_ACCESS_KEY_ID != '' && (needs.build-check.outputs.build_type == 'release' || needs.build-check.outputs.build_type == 'prerelease' || needs.build-check.outputs.build_type == 'development')
- name: Upload to Cloudflare R2
if: env.R2_ACCESS_KEY_ID != '' && (needs.build-check.outputs.build_type == 'release' || needs.build-check.outputs.build_type == 'prerelease' || needs.build-check.outputs.build_type == 'development')
env:
OSS_ACCESS_KEY_ID: ${{ secrets.ALICLOUDOSS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.ALICLOUDOSS_KEY_SECRET }}
OSS_REGION: cn-beijing
OSS_ENDPOINT: https://oss-accelerate.aliyuncs.com
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
AWS_EC2_METADATA_DISABLED: true
shell: bash
run: |
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
# Install ossutil (platform-specific)
OSSUTIL_VERSION="2.1.1"
case "${{ matrix.platform }}" in
linux)
if [[ "$(uname -m)" == "arm64" ]]; then
ARCH="arm64"
else
ARCH="amd64"
fi
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-linux-${ARCH}.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-linux-${ARCH}"
if [[ -z "$R2_ACCESS_KEY_ID" || -z "$R2_SECRET_ACCESS_KEY" || -z "$R2_ENDPOINT" || -z "$R2_BUCKET" ]]; then
echo "⚠️ R2 credentials or endpoint missing, skipping artifact upload"
exit 0
fi
curl -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
unzip "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil" /usr/local/bin/
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
chmod +x /usr/local/bin/ossutil
OSSUTIL_BIN=ossutil
;;
macos)
if [[ "$(uname -m)" == "arm64" ]]; then
ARCH="arm64"
else
ARCH="amd64"
fi
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-mac-${ARCH}.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-mac-${ARCH}"
if ! command -v aws >/dev/null 2>&1; then
echo "❌ aws CLI not found on runner; cannot upload to R2"
exit 1
fi
curl -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
unzip "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil" /usr/local/bin/
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
chmod +x /usr/local/bin/ossutil
OSSUTIL_BIN=ossutil
;;
windows)
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-windows-amd64.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-windows-amd64"
curl -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
unzip "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil.exe" ./ossutil.exe
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
OSSUTIL_BIN=./ossutil.exe
;;
esac
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY_ID"
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_ACCESS_KEY"
export AWS_DEFAULT_REGION="auto"
# Determine upload path based on build type
if [[ "$BUILD_TYPE" == "development" ]]; then
OSS_PATH="oss://rustfs-artifacts/artifacts/rustfs/dev/"
echo "📤 Uploading development build to OSS dev directory"
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/dev/"
echo "📤 Uploading development build to R2 dev directory"
else
OSS_PATH="oss://rustfs-artifacts/artifacts/rustfs/release/"
echo "📤 Uploading release build to OSS release directory"
R2_PATH="s3://${R2_BUCKET}/artifacts/rustfs/release/"
echo "📤 Uploading release build to R2 release directory"
fi
# Upload all rustfs zip files to OSS using glob pattern
echo "📤 Uploading all rustfs-*.zip files to $OSS_PATH..."
# Upload all rustfs zip files to R2 using S3-compatible API
echo "📤 Uploading all rustfs-*.zip files to $R2_PATH..."
for zip_file in rustfs-*.zip; do
if [[ -f "$zip_file" ]]; then
echo "Uploading: $zip_file to $OSS_PATH..."
$OSSUTIL_BIN cp "$zip_file" "$OSS_PATH" --force
echo "Uploading: $zip_file to $R2_PATH..."
aws s3 cp "$zip_file" "$R2_PATH" --endpoint-url "$R2_ENDPOINT" --only-show-errors
echo "✅ Uploaded: $zip_file"
fi
done
@@ -551,7 +599,7 @@ jobs:
name: Build Summary
needs: [ build-check, build-rustfs ]
if: always() && needs.build-check.outputs.should_build == 'true'
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
steps:
- name: Build completion summary
shell: bash
@@ -603,7 +651,7 @@ jobs:
name: Create GitHub Release
needs: [ build-check, build-rustfs ]
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type != 'development'
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
@@ -611,7 +659,7 @@ jobs:
release_url: ${{ steps.create.outputs.release_url }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
@@ -689,16 +737,16 @@ jobs:
name: Upload Release Assets
needs: [ build-check, build-rustfs, create-release ]
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type != 'development'
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Download all build artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: ./artifacts
pattern: rustfs-*
@@ -741,6 +789,19 @@ jobs:
echo "# GPG signature will be added in future versions" >> "${file}.asc"
done
cd ..
python3 scripts/security/generate_release_supply_chain_assets.py \
--asset-dir ./release-assets \
--version "$VERSION" \
--tag "$TAG" \
--build-type "${{ needs.build-check.outputs.build_type }}" \
--repository "${{ github.repository }}" \
--ref "${{ github.ref }}" \
--sha "${{ github.sha }}" \
--run-id "${{ github.run_id }}" \
--run-attempt "${{ github.run_attempt }}"
cd ./release-assets
echo "📦 Prepared assets:"
ls -la
@@ -770,7 +831,7 @@ jobs:
name: Update Latest Version
needs: [ build-check, upload-release-assets ]
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
steps:
- name: Update latest.json
env:
@@ -792,12 +853,15 @@ jobs:
OSSUTIL_VERSION="2.1.1"
OSSUTIL_ZIP="ossutil-${OSSUTIL_VERSION}-linux-amd64.zip"
OSSUTIL_DIR="ossutil-${OSSUTIL_VERSION}-linux-amd64"
OSSUTIL_SHA256="60f3a808cbbdfd4b5269b8f967c6a66f564c9dacff52d93a5d21a588976ab5a2"
curl -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
curl --fail -L -o "$OSSUTIL_ZIP" "https://gosspublic.alicdn.com/ossutil/v2/${OSSUTIL_VERSION}/${OSSUTIL_ZIP}"
printf '%s %s\n' "$OSSUTIL_SHA256" "$OSSUTIL_ZIP" | sha256sum -c -
unzip "$OSSUTIL_ZIP"
mv "${OSSUTIL_DIR}/ossutil" /usr/local/bin/
OSSUTIL_BIN="${RUNNER_TEMP}/ossutil"
mv "${OSSUTIL_DIR}/ossutil" "$OSSUTIL_BIN"
rm -rf "$OSSUTIL_DIR" "$OSSUTIL_ZIP"
chmod +x /usr/local/bin/ossutil
chmod +x "$OSSUTIL_BIN"
# Create latest.json
cat > latest.json << EOF
@@ -811,7 +875,7 @@ jobs:
EOF
# Upload to OSS
ossutil cp latest.json oss://rustfs-version/latest.json --force
"$OSSUTIL_BIN" cp latest.json oss://rustfs-version/latest.json --force
echo "✅ Updated latest.json for stable release $VERSION"
@@ -820,12 +884,12 @@ jobs:
name: Publish Release
needs: [ build-check, create-release, upload-release-assets ]
if: startsWith(github.ref, 'refs/tags/') && needs.build-check.outputs.build_type != 'development'
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Update release notes and publish
env:
+253 -47
View File
@@ -35,6 +35,7 @@ on:
- ".github/workflows/audit.yml"
- ".github/workflows/performance.yml"
pull_request:
types: [ opened, synchronize, reopened, closed ]
branches: [ main ]
paths-ignore:
- "**.md"
@@ -54,7 +55,7 @@ on:
- ".github/workflows/audit.yml"
- ".github/workflows/performance.yml"
merge_group:
types: [checks_requested]
types: [ checks_requested ]
schedule:
- cron: "0 0 * * 0" # Weekly on Sunday at midnight UTC
workflow_dispatch:
@@ -63,28 +64,36 @@ permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CARGO_BUILD_JOBS: 2
jobs:
cancel-closed-pr-runs:
name: Cancel Closed PR Runs
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Explain cancellation run
run: echo "PR closed; this run only cancels older runs in the same concurrency group."
skip-check:
name: Skip Duplicate Actions
if: github.event_name != 'pull_request' || github.event.action != 'closed'
permissions:
actions: write
contents: read
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- name: Skip duplicate actions
id: skip_check
uses: fkirc/skip-duplicate-actions@v5
uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5
with:
concurrent_skipping: "same_content_newer"
cancel_others: true
@@ -95,30 +104,65 @@ jobs:
name: Typos
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Typos check with custom config file
uses: crate-ci/typos@master
uses: crate-ci/typos@37bb98842b0d8c4ffebdb75301a13db0267cef89 # master
# Fast, compile-free checks that fail early so contributors get feedback in
# ~1 minute instead of waiting for the full test job.
quick-checks:
name: Quick Checks
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install ripgrep
run: sudo apt-get update && sudo apt-get install -y ripgrep
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: rustfmt
- name: Check code formatting
run: cargo fmt --all --check
- name: Check unsafe code allowances
run: ./scripts/check_unsafe_code_allowances.sh
- name: Check layered dependencies
run: ./scripts/check_layer_dependencies.sh
- name: Check architecture migration rules
run: ./scripts/check_architecture_migration_rules.sh
- name: Check tokio io-uring feature guard
run: ./scripts/check_no_tokio_io_uring.sh
test-and-lint:
name: Test and Lint
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubicloud-standard-4
runs-on: sm-standard-4
timeout-minutes: 60
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-test-${{ hashFiles('**/Cargo.lock') }}
cache-shared-key: ci-test
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -127,65 +171,168 @@ jobs:
cargo nextest run --all --exclude e2e_test
cargo test --all --doc
- name: Check code formatting
run: cargo fmt --all --check
# Explicit gate for migration-critical suites. These tests already ran in
# the full nextest pass above; a single filtered nextest invocation keeps
# the named gate without rebuilding or re-running them one package at a time.
- name: Run rebalance/decommission migration proofs
run: |
cargo nextest run -p rustfs-ecstore --lib \
-E 'test(data_movement) or test(rebalance) or test(decommission) or test(source_cleanup) or test(delete_marker)'
- name: Run clippy lints
run: cargo clippy --all-targets --all-features -- -D warnings
run: cargo clippy --all-targets -- -D warnings
- name: Check layered dependencies
run: ./scripts/check_layer_dependencies.sh
build-rustfs-debug-binary:
name: Build RustFS Debug Binary
test-and-lint-rio-v2:
name: Test and Lint (rio-v2)
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubicloud-standard-4
timeout-minutes: 30
runs-on: sm-standard-4
timeout-minutes: 60
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-rustfs-debug-binary-${{ hashFiles('**/Cargo.lock') }}
cache-shared-key: ci-test-rio-v2
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Run rio-v2 feature tests
run: |
cargo nextest run -p rustfs -p rustfs-ecstore --features rio-v2
cargo test -p rustfs --doc --features rio-v2
- name: Run rio-v2 clippy lints
run: cargo clippy -p rustfs -p rustfs-ecstore --all-targets --features rio-v2 -- -D warnings
test-and-lint-protocols:
name: "Test and Lint (${{ matrix.features.name }})"
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: sm-standard-4
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
features:
- name: swift
flags: "--features swift"
- name: sftp
flags: "--features sftp"
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-test-${{ matrix.features.name }}
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Run tests with ${{ matrix.features.name }}
run: |
cargo nextest run -p rustfs -p rustfs-protocols ${{ matrix.features.flags }}
- name: Run clippy with ${{ matrix.features.name }}
run: |
cargo clippy -p rustfs -p rustfs-protocols --all-targets ${{ matrix.features.flags }} -- -D warnings
build-rustfs-debug-binary:
name: Build RustFS Debug Binary
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: sm-standard-4
timeout-minutes: 30
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-rustfs-debug-binary
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build debug binary
run: |
touch rustfs/build.rs
cargo build -p rustfs --bins --jobs 2
run: cargo build -p rustfs --bins
- name: Upload debug binary
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: rustfs-debug-binary
path: target/debug/rustfs
if-no-files-found: error
retention-days: 1
build-rustfs-debug-binary-rio-v2:
name: Build RustFS Debug Binary (rio-v2)
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: sm-standard-4
timeout-minutes: 30
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-rustfs-debug-binary-rio-v2
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build debug binary with rio-v2
run: cargo build -p rustfs --bins --features rio-v2
- name: Upload debug binary
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: rustfs-debug-binary-rio-v2
path: target/debug/rustfs
if-no-files-found: error
retention-days: 1
e2e-tests:
name: End-to-End Tests
needs: [ skip-check, build-rustfs-debug-binary ]
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubicloud-standard-2
runs-on: sm-standard-2
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Clean up previous test run
run: |
rm -rf /tmp/rustfs
rm -f /tmp/rustfs.log
# Full setup with dependency caching: the migration-proof step below
# compiles the e2e_test crate, which pulls in most of the workspace.
# Without a restored cache this was a cold multi-GB build on every run.
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-e2e
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
# Download after the cache restore so the freshly built binary from the
# build job always wins over anything restored into target/debug.
- name: Download debug binary
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: rustfs-debug-binary
path: target/debug
@@ -193,15 +340,67 @@ jobs:
- name: Make binary executable
run: chmod +x ./target/debug/rustfs
- name: Setup Rust toolchain for s3s-e2e installation
uses: dtolnay/rust-toolchain@stable
- name: Run delete-marker migration proof
run: cargo test -p e2e_test delete_marker_migration_semantics -- --nocapture --test-threads=1
- name: Install s3s-e2e test tool
uses: taiki-e/cache-cargo-install-action@v2
uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # v2
with:
tool: s3s-e2e
git: https://github.com/s3s-project/s3s.git
rev: 4a04a670cf41274d9be9ab65dc36f4aa3f92fbad
rev: 62cb4a71dd759a6ec56b64c4c42fcc183a2c6a52
- name: Run end-to-end tests
run: |
s3s-e2e --version
RUN_ROOT="${RUNNER_TEMP}/rustfs-e2e-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${GITHUB_JOB}"
mkdir -p "${RUN_ROOT}"
RUSTFS_TEST_PORT="$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')"
RUSTFS_TEST_PORT="${RUSTFS_TEST_PORT}" \
RUSTFS_TEST_LOG="${RUN_ROOT}/rustfs.log" \
./scripts/e2e-run.sh ./target/debug/rustfs "${RUN_ROOT}/data"
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: e2e-test-logs-${{ github.run_number }}
path: ${{ runner.temp }}/rustfs-e2e-*/rustfs.log
retention-days: 3
e2e-tests-rio-v2:
name: End-to-End Tests (rio-v2)
needs: [ skip-check, build-rustfs-debug-binary-rio-v2 ]
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: sm-standard-2
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Clean up previous test run
run: |
rm -rf /tmp/rustfs
rm -f /tmp/rustfs.log
- name: Download debug binary
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: rustfs-debug-binary-rio-v2
path: target/debug
- name: Make binary executable
run: chmod +x ./target/debug/rustfs
- name: Setup Rust toolchain for s3s-e2e installation
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Install s3s-e2e test tool
uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # v2
with:
tool: s3s-e2e
git: https://github.com/s3s-project/s3s.git
rev: 62cb4a71dd759a6ec56b64c4c42fcc183a2c6a52
- name: Run end-to-end tests
run: |
@@ -210,24 +409,24 @@ jobs:
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: e2e-test-logs-${{ github.run_number }}
name: e2e-test-logs-rio-v2-${{ github.run_number }}
path: /tmp/rustfs.log
retention-days: 3
s3-implemented-tests:
name: S3 Implemented Tests
needs: [ skip-check, build-rustfs-debug-binary ]
needs: [ skip-check, build-rustfs-debug-binary, e2e-tests ]
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubicloud-standard-4
runs-on: sm-standard-4
timeout-minutes: 60
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Download debug binary
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: rustfs-debug-binary
path: target/debug
@@ -237,15 +436,22 @@ jobs:
- name: Run implemented s3-tests
run: |
RUN_ROOT="${RUNNER_TEMP}/rustfs-s3tests-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${GITHUB_JOB}"
mkdir -p "${RUN_ROOT}"
S3_PORT="$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')"
DEPLOY_MODE=binary \
RUSTFS_BINARY=./target/debug/rustfs \
TEST_MODE=single \
MAXFAIL=1 \
MAXFAIL=0 \
XDIST=4 \
S3_PORT="${S3_PORT}" \
DATA_ROOT="${RUN_ROOT}" \
S3TESTS_CONF=artifacts/s3tests-single/s3tests.conf \
./scripts/s3-tests/run.sh
- name: Upload s3 test artifacts
if: always()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: s3tests-implemented-${{ github.run_number }}
path: artifacts/s3tests-single/**
+17 -5
View File
@@ -16,7 +16,7 @@ name: CLA Check
on:
pull_request_target:
types: [opened, synchronize, reopened]
types: [opened, synchronize, reopened, closed]
merge_group:
types: [checks_requested]
issue_comment:
@@ -28,14 +28,26 @@ permissions:
issues: write
checks: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}
cancel-in-progress: true
jobs:
cancel-closed-pr-runs:
name: Cancel Closed PR Runs
if: github.event_name == 'pull_request_target' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Explain cancellation run
run: echo "PR closed; this run only cancels older runs in the same concurrency group."
cla:
if: ${{ github.event_name != 'issue_comment' || github.event.issue.pull_request }}
if: ${{ (github.event_name != 'issue_comment' || github.event.issue.pull_request) && (github.event_name != 'pull_request_target' || github.event.action != 'closed') }}
runs-on: ubuntu-latest
steps:
- name: Report CLA result for merge queue
if: github.event_name == 'merge_group'
uses: actions/github-script@v8
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
await github.rest.checks.create({
@@ -54,7 +66,7 @@ jobs:
- name: Create token for rustfs/cla
if: github.event_name != 'merge_group'
id: registry-token
uses: actions/create-github-app-token@v3
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ vars.CLA_BOT_APP_ID }}
private-key: ${{ secrets.CLA_BOT_APP_PRIVATE_KEY }}
@@ -64,7 +76,7 @@ jobs:
- name: Run CLA Bot
if: github.event_name != 'merge_group'
uses: overtrue/cla-bot@v0.0.9
uses: overtrue/cla-bot@5e5929846a60cfb42b16d0d38e135ef67d94fc88 # v0.0.9
with:
github-token: ${{ github.token }}
registry-token: ${{ steps.registry-token.outputs.token }}
+76 -38
View File
@@ -73,7 +73,7 @@ jobs:
# Check if we should build Docker images
build-check:
name: Docker Build Check
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
outputs:
should_build: ${{ steps.check.outputs.should_build }}
should_push: ${{ steps.check.outputs.should_push }}
@@ -84,7 +84,7 @@ jobs:
create_latest: ${{ steps.check.outputs.create_latest }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
# For workflow_run events, checkout the specific commit that triggered the workflow
@@ -163,11 +163,10 @@ jobs:
if [[ "$version" == *"alpha"* ]] || [[ "$version" == *"beta"* ]] || [[ "$version" == *"rc"* ]]; then
build_type="prerelease"
is_prerelease=true
# TODO: Temporary change - currently allows alpha versions to also create latest tags
# After the version is stable, you need to remove the following line and restore the original logic (latest is created only for stable versions)
if [[ "$version" == *"alpha"* ]]; then
# Current policy: create latest tags for stable releases and selected prereleases (alpha/beta).
if [[ "$version" == *"alpha"* ]] || [[ "$version" == *"beta"* ]]; then
create_latest=true
echo "🧪 Building Docker image for prerelease: $version (temporarily allowing creation of latest tag)"
echo "🧪 Building Docker image for prerelease: $version (creating latest tag)"
else
echo "🧪 Building Docker image for prerelease: $version"
fi
@@ -216,11 +215,10 @@ jobs:
v*alpha*|v*beta*|v*rc*|*alpha*|*beta*|*rc*)
build_type="prerelease"
is_prerelease=true
# TODO: Temporary change - currently allows alpha versions to also create latest tags
# After the version is stable, you need to remove the if block below and restore the original logic.
if [[ "$input_version" == *"alpha"* ]]; then
# Current policy: create latest tags for stable releases and selected prereleases (alpha/beta).
if [[ "$version" == *"alpha"* ]] || [[ "$version" == *"beta"* ]]; then
create_latest=true
echo "🧪 Building with prerelease version: $input_version (temporarily allowing creation of latest tag)"
echo "🧪 Building with prerelease version: $input_version (creating latest tag)"
else
echo "🧪 Building with prerelease version: $input_version"
fi
@@ -265,48 +263,48 @@ jobs:
name: Build Docker Images
needs: build-check
if: needs.build-check.outputs.should_build == 'true'
runs-on: ubicloud-standard-2
runs-on: dind-sm-standard-2
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- variant: musl
file: Dockerfile
suffix: ""
- variant: glibc
file: Dockerfile.glibc
suffix: "-glibc"
fail-fast: false
matrix:
include:
- variant: musl
file: Dockerfile
suffix: ""
- variant: glibc
file: Dockerfile.glibc
suffix: "-glibc"
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ env.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_PASSWORD }}
- name: Login to Quay.io
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Extract metadata and generate tags
id: meta
@@ -351,9 +349,7 @@ jobs:
# Add channel tags for prereleases and latest for stable
if [[ "$CREATE_LATEST" == "true" ]]; then
# TODO: Temporary change - the current alpha version will also create the latest tag
# After the version is stabilized, the logic here remains unchanged, but the upstream CREATE_LATEST setting needs to be restored.
# Stable release (and temporary alpha versions)
# Create latest tags for stable releases and selected prereleases when CREATE_LATEST=true.
TAGS="$TAGS,${{ env.REGISTRY_DOCKERHUB }}:latest${VARIANT_SUFFIX},${{ env.REGISTRY_GHCR }}:latest${VARIANT_SUFFIX},${{ env.REGISTRY_QUAY }}:latest${VARIANT_SUFFIX}"
elif [[ "$BUILD_TYPE" == "prerelease" ]]; then
# Prerelease channel tags (alpha, beta, rc)
@@ -390,7 +386,7 @@ jobs:
echo "🔖 Version: $VERSION"
- name: Build and push Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: ${{ matrix.file }}
@@ -410,9 +406,8 @@ jobs:
RELEASE=${{ steps.meta.outputs.docker_release }}
CHANNEL=${{ steps.meta.outputs.docker_channel }}
BUILDKIT_INLINE_CACHE=1
# Enable advanced BuildKit features for better performance
provenance: false
sbom: false
provenance: true
sbom: true
# Add retry mechanism by splitting the build process
no-cache: false
pull: true
@@ -420,12 +415,56 @@ jobs:
# Note: Manifest creation is no longer needed as we only build one variant
# Multi-arch manifests are automatically created by docker/build-push-action
# Report-only container image vulnerability scan
scan-docker-image:
name: Scan Docker Images
needs: [ build-check, build-docker ]
if: needs.build-check.outputs.should_build == 'true' && needs.build-check.outputs.should_push == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- variant: musl
suffix: ""
- variant: glibc
suffix: "-glibc"
steps:
- name: Login to GitHub Container Registry
# docker/login-action v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_PASSWORD }}
- name: Scan image with Trivy
# aquasecurity/trivy-action v0.36.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
with:
image-ref: ${{ env.REGISTRY_GHCR }}:${{ needs.build-check.outputs.version }}${{ matrix.suffix }}
format: sarif
output: trivy-${{ matrix.variant }}.sarif
ignore-unfixed: true
vuln-type: os,library
severity: CRITICAL,HIGH
exit-code: "0"
- name: Upload container scan report
# actions/upload-artifact v7.0.1
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: container-image-scan-${{ matrix.variant }}
path: trivy-${{ matrix.variant }}.sarif
if-no-files-found: error
retention-days: 30
# Docker build summary
docker-summary:
name: Docker Build Summary
needs: [ build-check, build-docker ]
if: always() && needs.build-check.outputs.should_build == 'true'
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
steps:
- name: Docker build completion summary
run: |
@@ -450,10 +489,9 @@ jobs:
"prerelease")
echo "🧪 Prerelease Docker image has been built with ${VERSION} tags"
echo "⚠️ This is a prerelease image - use with caution"
# TODO: Temporary change - alpha versions currently create the latest tag
# After the version is stable, you need to restore the following prompt information
if [[ "$VERSION" == *"alpha"* ]] && [[ "$CREATE_LATEST" == "true" ]]; then
echo "🏷️ Latest tag has been created for alpha version (temporary measures)"
# Create latest tags for stable releases and selected prereleases when CREATE_LATEST=true.
if [[ "$CREATE_LATEST" == "true" ]]; then
echo "🏷️ Latest tag has been created for prerelease: $VERSION"
else
echo "🚫 Latest tag NOT created for prerelease"
fi
+144 -288
View File
@@ -12,31 +12,60 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Full ceph/s3-tests sweep against RustFS.
#
# The per-PR compatibility gate lives in ci.yml (job s3-implemented-tests) and
# runs only the implemented_tests.txt whitelist. This workflow complements it:
#
# - Scheduled weekly full sweep (TEST_SCOPE=all): runs the ENTIRE upstream
# suite and reports promotion candidates (tests that newly pass) and
# unclassified tests. The job fails only on regressions in the implemented
# whitelist or on infrastructure errors — expected failures from
# not-yet-implemented features do not turn the run red.
# - Manual runs (workflow_dispatch): same, with configurable mode/scope.
#
# All test execution is delegated to scripts/s3-tests/run.sh (single source of
# truth, also used locally and by the PR gate). This workflow only provisions
# the server topology: a single node, or a real 4-node distributed cluster
# (erasure-coded across nodes) behind an HAProxy round-robin load balancer.
name: e2e-s3tests
on:
workflow_dispatch:
inputs:
test-mode:
description: "Test mode to run"
description: "Server topology"
required: true
type: choice
default: "single"
options:
- single
- multi
scope:
description: "Test scope: full upstream suite or implemented whitelist"
required: true
type: choice
default: "all"
options:
- all
- implemented
xdist:
description: "Enable pytest-xdist (parallel). '0' to disable."
description: "pytest-xdist workers. '0' to disable."
required: false
default: "4"
maxfail:
description: "Stop after N failures. '0' to run everything."
required: false
default: "0"
maxfail:
description: "Stop after N failures (debug friendly)"
required: false
default: "1"
markexpr:
description: "pytest -m expression (feature filters)"
description: "Optional pytest -m expression"
required: false
default: "not lifecycle and not versioning and not s3website and not bucket_logging and not encryption"
default: ""
schedule:
# Weekly full sweep (Sunday 02:00 UTC): full suite, run against BOTH the
# single-node and the 4-node distributed topologies (matrix below).
- cron: "0 2 * * 0"
env:
# main user
@@ -47,11 +76,19 @@ env:
S3_ALT_SECRET_KEY: rustfsalt
S3_REGION: us-east-1
S3_HOST: 127.0.0.1
S3_PORT: "9000"
RUST_LOG: info
PLATFORM: linux/amd64
BUILDX_CACHE_SCOPE: rustfs-e2e-s3tests-source
# Scheduled runs have no inputs; default to a full sweep.
TEST_SCOPE: ${{ github.event.inputs.scope || 'all' }}
XDIST: ${{ github.event.inputs.xdist || '4' }}
MAXFAIL: ${{ github.event.inputs.maxfail || '0' }}
MARKEXPR: ${{ github.event.inputs.markexpr || '' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs['test-mode'] || 'single' }}
cancel-in-progress: true
@@ -61,15 +98,21 @@ defaults:
shell: bash
jobs:
s3tests-single:
if: github.event.inputs['test-mode'] == 'single'
runs-on: ubicloud-standard-2
timeout-minutes: 120
s3tests:
runs-on: sm-standard-4
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
# Scheduled sweeps cover both topologies; manual runs use the input.
test-mode: ${{ github.event_name == 'schedule' && fromJSON('["single", "multi"]') || fromJSON(format('["{0}"]', github.event.inputs.test-mode || 'single')) }}
env:
TEST_MODE: ${{ matrix.test-mode }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Cache pip downloads
uses: actions/cache@v4
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-e2e-s3tests-${{ hashFiles('.github/workflows/e2e-s3tests.yml') }}
@@ -82,7 +125,7 @@ jobs:
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Enable buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build RustFS image (source, cached)
run: |
@@ -93,222 +136,61 @@ jobs:
-t rustfs-ci \
-f Dockerfile.source .
- name: Create network
run: docker network inspect rustfs-net >/dev/null 2>&1 || docker network create rustfs-net
- name: Remove existing rustfs-single (if any)
run: docker rm -f rustfs-single >/dev/null 2>&1 || true
- name: Start single RustFS
if: env.TEST_MODE == 'single'
run: |
docker network inspect rustfs-net >/dev/null 2>&1 || docker network create rustfs-net
docker rm -f rustfs-single >/dev/null 2>&1 || true
docker run -d --name rustfs-single \
--network rustfs-net \
-p 9000:9000 \
-p ${S3_PORT}:9000 \
-e RUSTFS_ADDRESS=0.0.0.0:9000 \
-e RUSTFS_ACCESS_KEY=$S3_ACCESS_KEY \
-e RUSTFS_SECRET_KEY=$S3_SECRET_KEY \
-e RUSTFS_VOLUMES="/data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3" \
-e RUSTFS_ACCESS_KEY=${S3_ACCESS_KEY} \
-e RUSTFS_SECRET_KEY=${S3_SECRET_KEY} \
-e RUSTFS_VOLUMES="/data/rustfs{0...3}" \
-v /tmp/rustfs-single:/data \
rustfs-ci
- name: Wait for RustFS ready
- name: Start 4-node distributed cluster
if: env.TEST_MODE == 'multi'
run: |
for i in {1..60}; do
if curl -sf http://127.0.0.1:9000/health >/dev/null 2>&1; then
echo "RustFS is ready"
exit 0
fi
# A real distributed deployment: every node lists all endpoints in
# RUSTFS_VOLUMES so data is erasure-coded ACROSS nodes. Do not use
# node-local volume paths here — that would create four independent
# single-node stores behind the load balancer.
cat > compose.yml <<EOF
x-rustfs-node: &rustfs-node
image: rustfs-ci
networks: [rustfs-net]
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY}
RUSTFS_VOLUMES: "http://rustfs{1...4}:9000/data/rustfs{0...3}"
if [ "$(docker inspect -f '{{.State.Running}}' rustfs-single 2>/dev/null)" != "true" ]; then
echo "RustFS container not running" >&2
docker logs rustfs-single || true
exit 1
fi
sleep 2
done
echo "Health check timed out" >&2
docker logs rustfs-single || true
exit 1
- name: Generate s3tests config
run: |
export S3_HOST=127.0.0.1
envsubst < .github/s3tests/s3tests.conf > s3tests.conf
- name: Provision s3-tests alt user (required by suite)
run: |
# Admin API requires AWS SigV4 signing. awscurl is used by RustFS codebase as well.
awscurl \
--service s3 \
--region "${S3_REGION}" \
--access_key "${S3_ACCESS_KEY}" \
--secret_key "${S3_SECRET_KEY}" \
-X PUT \
-H 'Content-Type: application/json' \
-d '{"secretKey":"'"${S3_ALT_SECRET_KEY}"'","status":"enabled","policy":"readwrite"}' \
"http://127.0.0.1:9000/rustfs/admin/v3/add-user?accessKey=${S3_ALT_ACCESS_KEY}"
# Explicitly attach built-in policy via policy mapping.
# s3-tests relies on alt client being able to ListBuckets during setup cleanup.
awscurl \
--service s3 \
--region "${S3_REGION}" \
--access_key "${S3_ACCESS_KEY}" \
--secret_key "${S3_SECRET_KEY}" \
-X PUT \
"http://127.0.0.1:9000/rustfs/admin/v3/set-user-or-group-policy?policyName=readwrite&userOrGroup=${S3_ALT_ACCESS_KEY}&isGroup=false"
# Sanity check: alt user can list buckets (should not be AccessDenied).
awscurl \
--service s3 \
--region "${S3_REGION}" \
--access_key "${S3_ALT_ACCESS_KEY}" \
--secret_key "${S3_ALT_SECRET_KEY}" \
-X GET \
"http://127.0.0.1:9000/" >/dev/null
- name: Prepare s3-tests
run: |
git clone --depth 1 https://github.com/ceph/s3-tests.git s3-tests
- name: Run ceph s3-tests (debug friendly)
run: |
export PATH="$HOME/.local/bin:$PATH"
mkdir -p artifacts/s3tests-single
cd s3-tests
set -o pipefail
MAXFAIL="${{ github.event.inputs.maxfail }}"
if [ -z "$MAXFAIL" ]; then MAXFAIL="1"; fi
MARKEXPR="${{ github.event.inputs.markexpr }}"
if [ -z "$MARKEXPR" ]; then MARKEXPR="not lifecycle and not versioning and not s3website and not bucket_logging and not encryption"; fi
XDIST="${{ github.event.inputs.xdist }}"
if [ -z "$XDIST" ]; then XDIST="0"; fi
XDIST_ARGS=""
if [ "$XDIST" != "0" ]; then
# Add pytest-xdist to requirements.txt so tox installs it inside
# its virtualenv. Installing outside tox does NOT work.
echo "pytest-xdist" >> requirements.txt
XDIST_ARGS="-n $XDIST --dist=loadgroup"
fi
# Run tests from s3tests/functional (boto2+boto3 combined directory).
S3TEST_CONF=${GITHUB_WORKSPACE}/s3tests.conf \
tox -- \
-vv -ra --showlocals --tb=long \
--maxfail="$MAXFAIL" \
--junitxml=${GITHUB_WORKSPACE}/artifacts/s3tests-single/junit.xml \
$XDIST_ARGS \
s3tests/functional/test_s3.py \
-m "$MARKEXPR" \
2>&1 | tee ${GITHUB_WORKSPACE}/artifacts/s3tests-single/pytest.log
- name: Collect RustFS logs
if: always()
run: |
mkdir -p artifacts/rustfs-single
docker logs rustfs-single > artifacts/rustfs-single/rustfs.log 2>&1 || true
docker inspect rustfs-single > artifacts/rustfs-single/inspect.json || true
- name: Upload artifacts
if: always() && env.ACT != 'true'
uses: actions/upload-artifact@v6
with:
name: s3tests-single
path: artifacts/**
s3tests-multi:
if: github.event_name == 'workflow_dispatch' && github.event.inputs['test-mode'] == 'multi'
runs-on: ubicloud-standard-2
timeout-minutes: 150
steps:
- uses: actions/checkout@v6
- name: Cache pip downloads
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-e2e-s3tests-${{ hashFiles('.github/workflows/e2e-s3tests.yml') }}
restore-keys: |
${{ runner.os }}-pip-e2e-s3tests-
- name: Install Python tools
run: |
python3 -m pip install --user --upgrade pip awscurl tox
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Enable buildx
uses: docker/setup-buildx-action@v3
- name: Build RustFS image (source, cached)
run: |
DOCKER_BUILDKIT=1 docker buildx build --load \
--platform ${PLATFORM} \
--cache-from type=gha,scope=${BUILDX_CACHE_SCOPE} \
--cache-to type=gha,mode=max,scope=${BUILDX_CACHE_SCOPE} \
-t rustfs-ci \
-f Dockerfile.source .
- name: Prepare cluster compose
run: |
cat > compose.yml <<'EOF'
services:
rustfs1:
image: rustfs-ci
<<: *rustfs-node
hostname: rustfs1
networks: [rustfs-net]
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY}
RUSTFS_VOLUMES: "/data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3"
volumes:
- rustfs1-data:/data
volumes: [rustfs1-data:/data]
rustfs2:
image: rustfs-ci
<<: *rustfs-node
hostname: rustfs2
networks: [rustfs-net]
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY}
RUSTFS_VOLUMES: "/data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3"
volumes:
- rustfs2-data:/data
volumes: [rustfs2-data:/data]
rustfs3:
image: rustfs-ci
<<: *rustfs-node
hostname: rustfs3
networks: [rustfs-net]
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY}
RUSTFS_VOLUMES: "/data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3"
volumes:
- rustfs3-data:/data
volumes: [rustfs3-data:/data]
rustfs4:
image: rustfs-ci
<<: *rustfs-node
hostname: rustfs4
networks: [rustfs-net]
environment:
RUSTFS_ADDRESS: "0.0.0.0:9000"
RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY}
RUSTFS_SECRET_KEY: ${S3_SECRET_KEY}
RUSTFS_VOLUMES: "/data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3"
volumes:
- rustfs4-data:/data
volumes: [rustfs4-data:/data]
lb:
image: haproxy:2.9
hostname: lb
networks: [rustfs-net]
ports:
- "9000:9000"
- "${S3_PORT}:9000"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
networks:
@@ -340,104 +222,78 @@ jobs:
server s4 rustfs4:9000 check
EOF
- name: Launch cluster
run: docker compose -f compose.yml up -d
docker compose -f compose.yml up -d
- name: Wait for LB ready
- name: Wait for RustFS ready
run: |
for i in {1..90}; do
if curl -sf http://127.0.0.1:9000/health >/dev/null 2>&1; then
echo "Load balancer is ready"
for i in {1..120}; do
if curl -sf "http://${S3_HOST}:${S3_PORT}/health" >/dev/null 2>&1; then
echo "RustFS is ready"
exit 0
fi
sleep 2
done
echo "LB or backend not ready" >&2
docker compose -f compose.yml logs --tail=200 || true
echo "Health check timed out" >&2
if [ "${TEST_MODE}" = "single" ]; then
docker logs rustfs-single || true
else
docker compose -f compose.yml logs --tail=200 || true
fi
exit 1
- name: Generate s3tests config
- name: Run ceph s3-tests
run: |
export S3_HOST=127.0.0.1
envsubst < .github/s3tests/s3tests.conf > s3tests.conf
set +e
DEPLOY_MODE=existing \
TEST_MODE="${TEST_MODE}" \
TEST_SCOPE="${TEST_SCOPE}" \
XDIST="${XDIST}" \
MAXFAIL="${MAXFAIL}" \
MARKEXPR="${MARKEXPR}" \
./scripts/s3-tests/run.sh
RC=$?
set -e
- name: Provision s3-tests alt user (required by suite)
run: |
awscurl \
--service s3 \
--region "${S3_REGION}" \
--access_key "${S3_ACCESS_KEY}" \
--secret_key "${S3_SECRET_KEY}" \
-X PUT \
-H 'Content-Type: application/json' \
-d '{"secretKey":"'"${S3_ALT_SECRET_KEY}"'","status":"enabled","policy":"readwrite"}' \
"http://127.0.0.1:9000/rustfs/admin/v3/add-user?accessKey=${S3_ALT_ACCESS_KEY}"
awscurl \
--service s3 \
--region "${S3_REGION}" \
--access_key "${S3_ACCESS_KEY}" \
--secret_key "${S3_SECRET_KEY}" \
-X PUT \
"http://127.0.0.1:9000/rustfs/admin/v3/set-user-or-group-policy?policyName=readwrite&userOrGroup=${S3_ALT_ACCESS_KEY}&isGroup=false"
awscurl \
--service s3 \
--region "${S3_REGION}" \
--access_key "${S3_ALT_ACCESS_KEY}" \
--secret_key "${S3_ALT_SECRET_KEY}" \
-X GET \
"http://127.0.0.1:9000/" >/dev/null
- name: Prepare s3-tests
run: |
git clone --depth 1 https://github.com/ceph/s3-tests.git s3-tests
- name: Run ceph s3-tests (multi, debug friendly)
run: |
export PATH="$HOME/.local/bin:$PATH"
mkdir -p artifacts/s3tests-multi
cd s3-tests
set -o pipefail
MAXFAIL="${{ github.event.inputs.maxfail }}"
if [ -z "$MAXFAIL" ]; then MAXFAIL="1"; fi
MARKEXPR="${{ github.event.inputs.markexpr }}"
if [ -z "$MARKEXPR" ]; then MARKEXPR="not lifecycle and not versioning and not s3website and not bucket_logging and not encryption"; fi
XDIST="${{ github.event.inputs.xdist }}"
if [ -z "$XDIST" ]; then XDIST="0"; fi
XDIST_ARGS=""
if [ "$XDIST" != "0" ]; then
# Add pytest-xdist to requirements.txt so tox installs it inside
# its virtualenv. Installing outside tox does NOT work.
echo "pytest-xdist" >> requirements.txt
XDIST_ARGS="-n $XDIST --dist=loadgroup"
if [ "${TEST_SCOPE}" = "implemented" ]; then
# Whitelist run: every failure is a regression.
exit "${RC}"
fi
# Run tests from s3tests/functional (boto2+boto3 combined directory).
S3TEST_CONF=${GITHUB_WORKSPACE}/s3tests.conf \
tox -- \
-vv -ra --showlocals --tb=long \
--maxfail="$MAXFAIL" \
--junitxml=${GITHUB_WORKSPACE}/artifacts/s3tests-multi/junit.xml \
$XDIST_ARGS \
s3tests/functional/test_s3.py \
-m "$MARKEXPR" \
2>&1 | tee ${GITHUB_WORKSPACE}/artifacts/s3tests-multi/pytest.log
# Full sweep: failures outside the implemented whitelist are
# inventory (promotion candidates / unimplemented features), not a
# gate. Fail only on whitelist regressions or infrastructure errors.
JUNIT="artifacts/s3tests-${TEST_MODE}/junit.xml"
if [ ! -f "${JUNIT}" ]; then
echo "No junit.xml produced — infrastructure failure (exit ${RC})" >&2
exit "${RC}"
fi
python3 scripts/s3-tests/report_compat.py \
--junit "${JUNIT}" \
--lists-dir scripts/s3-tests \
--fail-on-regression
- name: Collect logs
- name: Publish compatibility report
if: always()
run: |
mkdir -p artifacts/cluster
docker compose -f compose.yml logs --no-color > artifacts/cluster/cluster.log 2>&1 || true
REPORT="artifacts/s3tests-${TEST_MODE}/compat-report.md"
if [ -f "${REPORT}" ]; then
cat "${REPORT}" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Collect RustFS logs
if: always()
run: |
mkdir -p "artifacts/rustfs-${TEST_MODE}"
if [ "${TEST_MODE}" = "single" ]; then
docker logs rustfs-single > "artifacts/rustfs-${TEST_MODE}/rustfs.log" 2>&1 || true
docker inspect rustfs-single > "artifacts/rustfs-${TEST_MODE}/inspect.json" || true
else
docker compose -f compose.yml logs --no-color > "artifacts/rustfs-${TEST_MODE}/cluster.log" 2>&1 || true
fi
- name: Upload artifacts
if: always() && env.ACT != 'true'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: s3tests-multi
name: s3tests-${{ env.TEST_MODE }}
path: artifacts/**
+215
View File
@@ -0,0 +1,215 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Fuzz
on:
pull_request:
types: [ opened, synchronize, reopened, closed ]
paths:
- "fuzz/**"
- "scripts/fuzz/**"
- ".github/workflows/fuzz.yml"
- "crates/ecstore/**"
- "crates/filemeta/**"
- "crates/utils/**"
schedule:
- cron: "0 2 * * *"
workflow_dispatch:
inputs:
profile:
description: "Which fuzz profile to run"
required: false
default: "both"
type: choice
options:
- smoke
- nightly
- both
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_BUILD_TARGET: x86_64-unknown-linux-gnu
RUSTFLAGS: "--cfg tokio_unstable -C target-feature=-crt-static"
jobs:
cancel-closed-pr-runs:
name: Cancel Closed PR Runs
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Explain cancellation run
run: echo "PR closed; this run only cancels older runs in the same concurrency group."
# ──────────────────────────────────────────────────────────────
# Phase 1: Build all fuzz harness binaries once.
# ──────────────────────────────────────────────────────────────
fuzz-build:
name: Build Fuzz Harness
if: >
(github.event_name == 'pull_request' && github.event.action != 'closed') ||
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch'
runs-on: sm-standard-4
timeout-minutes: 45
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: nightly
cache-shared-key: fuzz-${{ hashFiles('fuzz/Cargo.lock') }}
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'schedule' }}
- name: Install cargo-fuzz
uses: taiki-e/install-action@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2
with:
tool: cargo-fuzz
- name: Build all fuzz targets
run: BUILD_ONLY=1 ./scripts/fuzz/run.sh
- name: Stage prebuilt fuzz binaries
run: |
binary_root="fuzz/prebuilt/${CARGO_BUILD_TARGET}/release"
mkdir -p "${binary_root}"
for target in path_containment bucket_validation local_metadata; do
install -Dm755 "fuzz/target/${CARGO_BUILD_TARGET}/release/${target}" "${binary_root}/${target}"
done
- name: Upload prebuilt fuzz binaries
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: fuzz-prebuilt-binaries-${{ github.run_number }}
path: |
fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release/path_containment
fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release/bucket_validation
fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release/local_metadata
if-no-files-found: error
retention-days: 1
compression-level: 0
# ──────────────────────────────────────────────────────────────
# Phase 2 (PR): Run smoke targets in parallel via matrix.
# ──────────────────────────────────────────────────────────────
pr-fuzz-smoke:
name: "Smoke / ${{ matrix.target }}"
needs: fuzz-build
if: >
(github.event_name == 'pull_request' && github.event.action != 'closed') ||
(github.event_name == 'workflow_dispatch' &&
(github.event.inputs.profile == 'smoke' || github.event.inputs.profile == 'both'))
runs-on: sm-standard-4
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
target: [path_containment, bucket_validation, local_metadata]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Download prebuilt fuzz binaries
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: fuzz-prebuilt-binaries-${{ github.run_number }}
path: fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release
- name: Restore executable bit
run: chmod +x "fuzz/prebuilt/${CARGO_BUILD_TARGET}/release/${{ matrix.target }}"
- name: Run ${{ matrix.target }}
env:
FUZZ_TARGET: ${{ matrix.target }}
MAX_TOTAL_TIME: 60
SKIP_BUILD: 1
USE_PREBUILT_BINARY: 1
PREBUILT_BINARY_DIR: ${{ github.workspace }}/fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release
run: ./scripts/fuzz/run.sh
- name: Upload fuzz smoke artifacts
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: fuzz-smoke-${{ matrix.target }}-${{ github.run_number }}
path: |
fuzz/artifacts/**
fuzz/corpus/${{ matrix.target }}/**
if-no-files-found: ignore
retention-days: 7
# ──────────────────────────────────────────────────────────────
# Phase 2 (Nightly): Run all targets in parallel via matrix.
# ──────────────────────────────────────────────────────────────
nightly-fuzz-corpus:
name: "Nightly / ${{ matrix.target }}"
needs: fuzz-build
if: >
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' &&
(github.event.inputs.profile == 'nightly' || github.event.inputs.profile == 'both'))
runs-on: sm-standard-4
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
target: [path_containment, bucket_validation, local_metadata]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Download prebuilt fuzz binaries
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: fuzz-prebuilt-binaries-${{ github.run_number }}
path: fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release
- name: Restore executable bit
run: chmod +x "fuzz/prebuilt/${CARGO_BUILD_TARGET}/release/${{ matrix.target }}"
- name: Run ${{ matrix.target }}
env:
FUZZ_TARGET: ${{ matrix.target }}
MAX_TOTAL_TIME: 300
SKIP_BUILD: 1
USE_PREBUILT_BINARY: 1
PREBUILT_BINARY_DIR: ${{ github.workspace }}/fuzz/prebuilt/${{ env.CARGO_BUILD_TARGET }}/release
run: ./scripts/fuzz/run.sh
- name: Upload nightly fuzz artifacts
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: fuzz-nightly-${{ matrix.target }}-${{ github.run_number }}
path: |
fuzz/artifacts/**
fuzz/corpus/${{ matrix.target }}/**
if-no-files-found: ignore
retention-days: 30
+62 -24
View File
@@ -18,77 +18,115 @@ on:
workflow_run:
workflows: [ "Build and Release" ]
types: [ completed ]
workflow_dispatch:
inputs:
version:
description: "Release version to publish, e.g. 1.0.0-beta.1 or v1.0.0-beta.1"
required: true
default: "1.0.0-beta.1"
type: string
permissions:
contents: read
env:
new_version: ${{ github.event.workflow_run.head_branch }}
jobs:
build-helm-package:
runs-on: ubicloud-standard-2
# Only run on successful builds triggered by tag pushes (version format: x.y.z or x.y.z-suffix)
runs-on: ubuntu-latest
if: |
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
contains(github.event.workflow_run.head_branch, '.')
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
contains(github.event.workflow_run.head_branch, '.')
)
outputs:
raw_tag: ${{ steps.version.outputs.raw_tag }}
app_version: ${{ steps.version.outputs.app_version }}
chart_version: ${{ steps.version.outputs.chart_version }}
steps:
- name: Checkout helm chart repo
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Replace chart app version
- name: Normalize release version
id: version
run: |
set -e
set -x
old_version=$(grep "^appVersion:" helm/rustfs/Chart.yaml | awk '{print $2}')
sed -i "s/$old_version/$new_version/g" helm/rustfs/Chart.yaml
set -eux
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
RAW="${{ github.event.inputs.version }}"
else
RAW="${{ github.event.workflow_run.head_branch }}"
fi
case "$RAW" in
refs/tags/*)
RAW_TAG="${RAW#refs/tags/}"
;;
*)
RAW_TAG="$RAW"
;;
esac
./scripts/helm_chart_version.sh "$RAW_TAG"
- name: Replace chart version and app version
run: |
set -eux
sed -i -E 's/^version:.*/version: "${{ steps.version.outputs.chart_version }}"/' helm/rustfs/Chart.yaml
sed -i -E 's/^appVersion:.*/appVersion: "${{ steps.version.outputs.app_version }}"/' helm/rustfs/Chart.yaml
- name: Set up Helm
uses: azure/setup-helm@v4.3.0
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
- name: Test Helm Chart Templates
run: ./scripts/test_helm_templates.sh
- name: Package Helm Chart
run: |
set -eux
cp helm/README.md helm/rustfs/
package_version=$(echo $new_version | awk -F '-' '{print $2}' | awk -F '.' '{print $NF}')
helm package ./helm/rustfs --destination helm/rustfs/ --version "0.0.$package_version"
helm package ./helm/rustfs \
--destination helm/rustfs/ \
--version "${{ steps.version.outputs.chart_version }}"
- name: Upload helm package as artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: helm-package
path: helm/rustfs/*.tgz
retention-days: 1
publish-helm-package:
runs-on: ubicloud-standard-2
runs-on: ubuntu-latest
needs: [ build-helm-package ]
if: needs.build-helm-package.result == 'success'
steps:
- name: Checkout helm package repo
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
repository: rustfs/helm
token: ${{ secrets.RUSTFS_HELM_PACKAGE }}
- name: Download helm package
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: helm-package
path: ./
- name: Set up helm
uses: azure/setup-helm@v4.3.0
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
- name: Generate index
run: helm repo index . --url https://charts.rustfs.com
- name: Push helm package and index file
run: |
set -eux
git config --global user.name "${{ secrets.USERNAME }}"
git config --global user.email "${{ secrets.EMAIL_ADDRESS }}"
git status .
git add .
git commit -m "Update rustfs helm package with $new_version."
git commit -m "Update rustfs helm package with ${{ needs.build-helm-package.outputs.app_version }}." || echo "No changes to commit"
git push origin main
+3 -3
View File
@@ -25,12 +25,12 @@ permissions:
jobs:
build:
runs-on: ubicloud-standard-4
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
- uses: usthe/issues-translate-action@b41f55ddc81d7d54bd542a4f289fe28ec081898e # v2.7
with:
IS_MODIFY_TITLE: false
# not require, default false, . Decide whether to modify the issue title
# not require, default false. Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically.
# not require. Customize the translation robot prefix message.
+209
View File
@@ -0,0 +1,209 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# MinIO Mint against RustFS: multi-SDK client compatibility.
#
# ceph/s3-tests (see e2e-s3tests.yml and the ci.yml PR gate) exercises the S3
# API semantics through boto3 only. Mint complements it by running the
# functional test suites of many REAL client SDKs and tools (awscli, mc,
# aws-sdk-java/go/php/ruby, minio-go/java/js/py, s3cmd, ...), which catches
# client-specific signing, encoding, and streaming edge cases a single client
# cannot.
#
# This job is REPORT-ONLY for test failures: RustFS does not pass every mint
# suite yet, so failures are inventoried in the job summary instead of turning
# the run red. The job fails only when mint produces no results at all
# (infrastructure error). Once a suite passes reliably, tightening this into a
# baseline gate (like implemented_tests.txt for s3-tests) is the natural next
# step.
name: mint
on:
workflow_dispatch:
inputs:
suites:
description: "Space-separated mint suites to run (empty = all, e.g. 'awscli mc minio-go')"
required: false
default: ""
mode:
description: "Mint mode: core (quick) or full"
required: true
type: choice
default: "core"
options:
- core
- full
mint-image:
description: "Mint image reference"
required: false
default: "minio/mint:edge"
schedule:
# Weekly, after the Sunday s3-tests full sweep.
- cron: "0 4 * * 0"
env:
S3_ACCESS_KEY: rustfsadmin
S3_SECRET_KEY: rustfsadmin
S3_REGION: us-east-1
RUST_LOG: info
PLATFORM: linux/amd64
BUILDX_CACHE_SCOPE: rustfs-e2e-s3tests-source
MINT_SUITES: ${{ github.event.inputs.suites || '' }}
MINT_MODE: ${{ github.event.inputs.mode || 'core' }}
# NOTE: minio/mint:edge is a rolling tag. If sweeps get noisy from upstream
# mint changes, pin this to a digest known to work.
MINT_IMAGE: ${{ github.event.inputs.mint-image || 'minio/mint:edge' }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
mint:
runs-on: sm-standard-4
timeout-minutes: 120
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Enable buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build RustFS image (source, cached)
run: |
DOCKER_BUILDKIT=1 docker buildx build --load \
--platform ${PLATFORM} \
--cache-from type=gha,scope=${BUILDX_CACHE_SCOPE} \
--cache-to type=gha,mode=max,scope=${BUILDX_CACHE_SCOPE} \
-t rustfs-ci \
-f Dockerfile.source .
- name: Start RustFS
run: |
docker network inspect rustfs-net >/dev/null 2>&1 || docker network create rustfs-net
docker rm -f rustfs-mint >/dev/null 2>&1 || true
docker run -d --name rustfs-mint \
--network rustfs-net \
-p 9000:9000 \
-e RUSTFS_ADDRESS=0.0.0.0:9000 \
-e RUSTFS_ACCESS_KEY=${S3_ACCESS_KEY} \
-e RUSTFS_SECRET_KEY=${S3_SECRET_KEY} \
-e RUSTFS_VOLUMES="/data/rustfs{0...3}" \
-v /tmp/rustfs-mint:/data \
rustfs-ci
- name: Wait for RustFS ready
run: |
for i in {1..60}; do
if curl -sf http://127.0.0.1:9000/health >/dev/null 2>&1; then
echo "RustFS is ready"
exit 0
fi
if [ "$(docker inspect -f '{{.State.Running}}' rustfs-mint 2>/dev/null)" != "true" ]; then
echo "RustFS container not running" >&2
docker logs rustfs-mint || true
exit 1
fi
sleep 2
done
echo "Health check timed out" >&2
docker logs rustfs-mint || true
exit 1
- name: Run mint
run: |
mkdir -p artifacts/mint-log
# Mint exits non-zero when any test fails; that is inventory here,
# not a gate (see header comment). Capture the exit code and let the
# summary step decide.
set +e
# shellcheck disable=SC2086 # MINT_SUITES is intentionally word-split
docker run --rm --network rustfs-net \
-e SERVER_ENDPOINT=rustfs-mint:9000 \
-e ACCESS_KEY=${S3_ACCESS_KEY} \
-e SECRET_KEY=${S3_SECRET_KEY} \
-e ENABLE_HTTPS=0 \
-e SERVER_REGION=${S3_REGION} \
-e MINT_MODE=${MINT_MODE} \
-v "${PWD}/artifacts/mint-log:/mint/log" \
"${MINT_IMAGE}" ${MINT_SUITES}
echo "MINT_RC=$?" >> "$GITHUB_ENV"
- name: Summarize results
run: |
python3 - <<'EOF'
import json, os, pathlib, sys
from collections import Counter
log = pathlib.Path("artifacts/mint-log/log.json")
if not log.is_file():
print("mint produced no log.json - infrastructure failure", file=sys.stderr)
sys.exit(int(os.environ.get("MINT_RC", "1")) or 1)
per_suite: dict[str, Counter] = {}
failures: list[tuple[str, str, str]] = []
for line in log.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
suite = entry.get("name", "unknown")
status = entry.get("status", "unknown").upper()
per_suite.setdefault(suite, Counter())[status] += 1
if status == "FAIL":
failures.append((suite, entry.get("function", ""), entry.get("error") or entry.get("message") or ""))
lines = ["# Mint results", "", "| Suite | Pass | Fail | N/A |", "|-------|------|------|-----|"]
for suite in sorted(per_suite):
c = per_suite[suite]
lines.append(f"| {suite} | {c.get('PASS', 0)} | {c.get('FAIL', 0)} | {c.get('NA', 0)} |")
lines.append("")
if failures:
lines.append(f"## Failures ({len(failures)})")
lines.append("")
for suite, func, err in failures:
first = err.strip().splitlines()[0] if err.strip() else ""
lines.append(f"- `{suite}` / `{func}` {first}")
lines.append("")
lines.append("_Report-only: failures are inventory, not a gate (see workflow header)._")
report = "\n".join(lines)
print(report)
summary = os.environ.get("GITHUB_STEP_SUMMARY")
if summary:
with open(summary, "a", encoding="utf-8") as fh:
fh.write(report + "\n")
EOF
- name: Collect RustFS logs
if: always()
run: |
mkdir -p artifacts/rustfs-mint
docker logs rustfs-mint > artifacts/rustfs-mint/rustfs.log 2>&1 || true
- name: Upload artifacts
if: always() && env.ACT != 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: mint
path: artifacts/**
+11 -6
View File
@@ -31,20 +31,25 @@ jobs:
update-flake:
name: Update flake.lock
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 90
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install Nix
uses: DeterminateSystems/determinate-nix-action@v3
uses: DeterminateSystems/determinate-nix-action@629b284231c2a82554b724e357e47fc6020833c8 # v3
- name: Cache Nix
uses: DeterminateSystems/flakehub-cache-action@1f9a51a2959d3e26c7838c6f3bf9f48acae525ea # v3.20.0
- name: Check Nix flake inputs
uses: DeterminateSystems/flake-checker-action@v12
uses: DeterminateSystems/flake-checker-action@3164002371bc90729c68af0e24d5aacf20d7c9f6 # v12
- name: Update flake.lock
id: update
uses: DeterminateSystems/update-flake-lock@main
uses: DeterminateSystems/update-flake-lock@fd9359ac79d0e912f1b4b947a48470b3e2799b56 # main
with:
git-author-name: houseme
git-author-email: housemecn@gmail.com
@@ -56,7 +61,7 @@ jobs:
nix
automated
commit-msg: "chore(deps): update flake.lock"
pr-reviewers: houseme, overtrue, majinghe
pr-reviewers: overtrue, majinghe
token: ${{ secrets.FLAKE_UPDATE_TOKEN }}
- name: Log PR details
+52 -13
View File
@@ -24,6 +24,7 @@ on:
- 'Cargo.lock'
- '.github/workflows/nix.yml'
pull_request:
types: [ opened, synchronize, reopened, closed ]
branches: [ "main" ]
paths:
- 'flake.nix'
@@ -34,43 +35,81 @@ on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
jobs:
cancel-closed-pr-runs:
name: Cancel Closed PR Runs
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Explain cancellation run
run: echo "PR closed; this run only cancels older runs in the same concurrency group."
nix-validation:
name: Nix Build & Check
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 60
permissions:
contents: read
id-token: write
env:
NIX_CURL_FLAGS: -A cargo/stable
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install Nix
uses: cachix/install-nix-action@v31
uses: DeterminateSystems/determinate-nix-action@4eea0b33e3d1f02ecfe37cf16e7204c424009606 # v3.21.0
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
extra_nix_config: |
experimental-features = nix-command flakes
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: |
experimental-features = nix-command flakes configurable-impure-env
impure-env = NIX_CURL_FLAGS
max-jobs = 1
- name: Setup Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@v13
- name: Cache Nix
uses: DeterminateSystems/flakehub-cache-action@c01e819d047464c3edf6ba778f075952af5a3aa7 # v3.21.0
- name: Setup Flake Checker
uses: DeterminateSystems/flake-checker-action@v12
- name: Check Nix Flake Inputs
uses: DeterminateSystems/flake-checker-action@3164002371bc90729c68af0e24d5aacf20d7c9f6 # v12
with:
fail-mode: true
ignore-missing-flake-lock: false
- name: Verify Flake
run: |
echo "Checking flake structure and evaluation..."
nix flake show
nix flake check --print-build-logs
for attempt in 1 2 3; do
if nix flake check --print-build-logs --fallback --option fallback true; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "nix flake check failed after 3 attempts."
exit 1
fi
sleep $((attempt * 15))
done
- name: Build RustFS
run: |
echo "Building the default package..."
nix build .#default --print-build-logs
for attempt in 1 2 3; do
if nix build .#default --print-build-logs --fallback --option fallback true; then
break
fi
if [ "$attempt" -eq 3 ]; then
echo "nix build failed after 3 attempts."
exit 1
fi
sleep $((attempt * 15))
done
- name: Test Binary
run: |
+139
View File
@@ -0,0 +1,139 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Warp A/B budget gate for the hotpath series (rustfs/backlog#935 HP-14, item 4).
#
# Two entry points, honestly scoped:
# * schedule (nightly, on main): post-merge detection — catches a regression
# within 24h of landing, not before merge.
# * pull_request labeled `perf-ab`: opt-in pre-merge gate for a specific PR.
# The `perf-deliberate-tradeoff` label runs the gate with --allow-regression so
# a deliberate correctness cost (e.g. the #4221 fsync durability fix) is
# recorded but does not block (rustfs/backlog#935 correction 1).
name: Performance A/B
on:
schedule:
- cron: "0 6 * * *" # 06:00 UTC nightly, against main
workflow_dispatch:
inputs:
duration:
description: "warp duration per round"
required: false
default: "30s"
type: string
allow_regression:
description: "Pass the gate despite a FAIL (deliberate tradeoff)"
required: false
default: false
type: boolean
pull_request:
types: [labeled, synchronize, reopened]
permissions:
contents: read
pull-requests: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
warp-ab:
name: Warp A/B budget gate
# Opt-in on PRs: only run when the `perf-ab` label is present. Always run on
# schedule / manual dispatch.
if: >-
github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'perf-ab')
runs-on: sm-standard-2
timeout-minutes: 90
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0 # baseline is built from origin/main
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: warp-ab-${{ hashFiles('**/Cargo.lock') }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install warp
run: |
set -euo pipefail
WARP_VERSION="1.0.0"
curl -fsSL "https://github.com/minio/warp/releases/download/v${WARP_VERSION}/warp_Linux_x86_64.tar.gz" \
| sudo tar -xz -C /usr/local/bin warp
warp --version
- name: Decide exemption
id: exempt
run: |
allow="false"
if [[ "${{ github.event_name }}" == "pull_request" ]] \
&& ${{ contains(github.event.pull_request.labels.*.name, 'perf-deliberate-tradeoff') }}; then
allow="true"
fi
if [[ "${{ github.event.inputs.allow_regression }}" == "true" ]]; then
allow="true"
fi
echo "allow_regression=$allow" >> "$GITHUB_OUTPUT"
- name: Run warp A/B and gate
id: ab
run: |
set -euo pipefail
args=(--baseline-ref origin/main --duration "${{ github.event.inputs.duration || '30s' }}")
if [[ "${{ steps.exempt.outputs.allow_regression }}" == "true" ]]; then
args+=(--allow-regression --exemption-reason "labeled perf-deliberate-tradeoff / dispatch override")
fi
# Do not let a gate FAIL abort the job here; capture status and surface
# it after the PR comment is posted.
set +e
bash scripts/run_hotpath_warp_ab.sh "${args[@]}"
echo "status=$?" >> "$GITHUB_OUTPUT"
set -e
# Locate the newest gate.md for the comment/artifact steps.
gate_md="$(ls -t target/hotpath-ab/*/gate.md 2>/dev/null | head -n1 || true)"
echo "gate_md=$gate_md" >> "$GITHUB_OUTPUT"
- name: Upload A/B results
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: hotpath-warp-ab-${{ github.run_number }}
path: target/hotpath-ab/
if-no-files-found: warn
- name: Comment gate result on PR
if: always() && github.event_name == 'pull_request' && steps.ab.outputs.gate_md != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr comment "${{ github.event.pull_request.number }}" --body-file "${{ steps.ab.outputs.gate_md }}"
- name: Enforce gate
if: always()
run: |
status="${{ steps.ab.outputs.status }}"
if [[ "$status" != "0" ]]; then
echo "::error::warp A/B budget gate failed (exit $status). See the PR comment / gate.md artifact." >&2
exit "$status"
fi
echo "warp A/B budget gate passed."
+8 -8
View File
@@ -40,13 +40,13 @@ env:
jobs:
performance-profile:
name: Performance Profiling
runs-on: ubicloud-standard-2
runs-on: sm-standard-2
timeout-minutes: 30
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
@@ -60,7 +60,7 @@ jobs:
run: rustup component add llvm-tools-preview
- name: Install samply profiler
uses: taiki-e/cache-cargo-install-action@v2
uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # v2
with:
tool: samply
@@ -86,7 +86,7 @@ jobs:
- name: Build with profiling optimizations
run: |
RUSTFLAGS="-C force-frame-pointers=yes -C debug-assertions=off" \
RUSTFLAGS="-C force-frame-pointers=yes -C debug-assertions=off --cfg tokio_unstable" \
cargo +nightly build --profile profiling -p rustfs --bins
- name: Run performance profiling
@@ -109,7 +109,7 @@ jobs:
- name: Upload profile data
if: steps.profiling.outputs.profile_generated == 'true'
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: performance-profile-${{ github.run_number }}
path: samply-profile.json
@@ -117,13 +117,13 @@ jobs:
benchmark:
name: Benchmark Tests
runs-on: ubicloud-standard-2
runs-on: sm-standard-2
timeout-minutes: 45
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
@@ -139,7 +139,7 @@ jobs:
tee benchmark-results.json
- name: Upload benchmark results
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: benchmark-results-${{ github.run_number }}
path: benchmark-results.json
+17 -3
View File
@@ -1,3 +1,17 @@
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: "Mark stale issues"
on:
schedule:
@@ -7,14 +21,14 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
stale-issue-label: 'stale'
## Mark if there is no activity for more than 7 days
days-before-stale: 7
days-before-stale: 7
# If no one responds after 3 days, the tag will be closed.
days-before-close: 3
days-before-close: 3
# These tags are exempt and will not close automatically.
exempt-issue-labels: 'pinned,security'
+30
View File
@@ -4,6 +4,10 @@
.vscode
.cursor
.direnv/
# Ignore local Claude state but keep shared repo skills committable
.claude/*
!.claude/skills
.tmp/
/test
/logs
/data
@@ -21,6 +25,8 @@ deploy/data/*
.env
.rustfs.sys
.cargo/
!fuzz/.cargo/
!fuzz/.cargo/config.toml
!.cargo/config.toml
profile.json
.docker/openobserve-otel/data
@@ -42,6 +48,23 @@ artifacts/
PR_DESCRIPTION.md
scripts/s3-tests/selected_tests.txt
docs
__pycache__/
!docs/
docs/*
!docs/architecture/
!docs/architecture/**
!docs/operations/
!docs/operations/**
!docs/superpowers/
!docs/superpowers/**
docs/heal-scanner-logging-governance.md
docs/benchmark/rustfs-target-bench/
docs/benchmark/*.md
.codegraph/*
.docker/test/compat/data/*
.docker/test/compat/kms/*
reasonix.toml
.reasonix/*
# nix stuff
result*
@@ -53,3 +76,10 @@ rustfs-webdav.code-workspace
# Local test/benchmark artifacts
benchmarks.logs
tmp/
crates/*/docs
fuzz/target
outputs
worktrees/*
# Local AI-agent review artifacts (omo evidence dumps)
.omo/
+3 -24
View File
@@ -3,30 +3,9 @@
repos:
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --all --check
language: system
types: [rust]
pass_filenames: false
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy --all-targets --all-features -- -D warnings
language: system
types: [rust]
pass_filenames: false
- id: cargo-check
name: cargo check
entry: cargo check --all-targets
language: system
types: [rust]
pass_filenames: false
- id: cargo-test
name: cargo test
entry: bash -c 'cargo test --workspace --exclude e2e_test && cargo test --all --doc'
- id: rustfs-dev-check
name: rustfs dev-check
entry: make dev-check
language: system
types: [rust]
pass_filenames: false
+43 -1
View File
@@ -188,6 +188,7 @@
"RUSTFS_CONSOLE_ENABLE": "true",
"RUSTFS_CONSOLE_ADDRESS": "127.0.0.1:9001",
"RUSTFS_OBS_LOG_DIRECTORY": "./target/logs",
"RUSTFS_UNSAFE_BYPASS_DISK_CHECK": "true",
// "RUSTFS_OBS_TRACE_ENDPOINT": "http://127.0.0.1:4318/v1/traces", // jeager otlp http endpoint
// "RUSTFS_OBS_METRIC_ENDPOINT": "http://127.0.0.1:4318/v1/metrics", // default otlp http endpoint
// "RUSTFS_OBS_LOG_ENDPOINT": "http://127.0.0.1:4318/v1/logs", // default otlp http endpoint
@@ -223,6 +224,47 @@
"rust"
],
},
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'rustfs' with rio-v2 full matrix",
"cargo": {
"args": [
"build",
"--bin=rustfs",
"--package=rustfs",
"--features=full,rio-v2"
],
"filter": {
"name": "rustfs",
"kind": "bin"
}
},
"env": {
"RUST_LOG": "rustfs=error,ecstore=error,s3s=error,iam=error",
"RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS": "true",
"RUST_BACKTRACE": "full",
"RUSTFS_ACCESS_KEY": "rustfsadmin",
"RUSTFS_SECRET_KEY": "rustfsadmin",
"RUSTFS_VOLUMES": "./target/rio-v2/test{1...4}",
"RUSTFS_SCANNER_ENABLED": "false",
"RUSTFS_HEAL_ENABLED": "false",
"RUSTFS_REGION": "us-east-1",
"RUSTFS_ADDRESS": ":9000",
"RUSTFS_CONSOLE_ENABLE": "true",
"RUSTFS_CONSOLE_ADDRESS": "127.0.0.1:9001",
"RUSTFS_OBS_LOG_DIRECTORY": "./target/logs/rio-v2",
"RUSTFS_UNSAFE_BYPASS_DISK_CHECK": "true",
"RUSTFS_COMPRESSION_ENABLED": "true",
"RUSTFS_KMS_ENABLE": "true",
"RUSTFS_KMS_BACKEND": "local",
"RUSTFS_KMS_KEY_DIR": "./target/rio-v2/kms-key-dir",
"RUSTFS_KMS_LOCAL_MASTER_KEY": "my-secret-key", // Some Password
"RUSTFS_KMS_DEFAULT_KEY_ID": "rustfs-master-key",
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"name": "E2E test executable target/debug/rustfs",
"type": "lldb",
@@ -255,4 +297,4 @@
],
},
]
}
}
+55 -10
View File
@@ -31,6 +31,9 @@ If repo-level instructions conflict, follow the nearest file and keep behavior a
- Prefer direct, local code over extracting one-off helpers.
- Extract a helper only when logic is reused or the extraction materially clarifies a non-trivial flow.
- Use Rust's default module file layout (`mod foo;` with `foo.rs` or `foo/mod.rs`/`foo/*.rs`).
Avoid `#[path = "..."]` for module inclusion; move files into the canonical module tree instead.
If an unavoidable generated-code, FFI, or test-fixture exception remains, keep it local and document why the canonical layout cannot work.
- Solve only the requested problem; do not add speculative features, configurability, or adjacent improvements.
- Prefer editing existing code over rewriting files or reshaping unrelated logic.
- Modify only what is required and remove only artifacts introduced by your own changes.
@@ -53,6 +56,13 @@ If repo-level instructions conflict, follow the nearest file and keep behavior a
- Local quality commands: `Makefile` and `.config/make/`
- CI quality gates: `.github/workflows/ci.yml`
- PR template: `.github/pull_request_template.md`
- High-level architecture and crate map: `ARCHITECTURE.md`
- Migration guardrails, readiness contracts, support matrices:
`docs/architecture/README.md` (routes by audience)
- Historical implementation plans and trackers: `docs/superpowers/plans/`
- Shared agent skills (all tools): `.agents/skills/` — Claude Code reads them
through the `.claude/skills` symlink; add new skills to `.agents/skills/`
only, never as separate copies per tool
Avoid duplicating long crate lists or command matrices in instruction files.
Reference the source files above instead.
@@ -63,10 +73,21 @@ Convert changes into independently verifiable outcomes. Prefer focused tests for
For code changes, run and pass the following before opening a PR:
```bash
make pre-pr
```
Before committing code changes, prefer focused verification for the touched
surface and use the faster local gate when a broad smoke check is needed:
```bash
make pre-commit
```
For migration batches, do not run the full `make pre-pr` gate before every
intermediate commit. Use focused tests and `make pre-commit` during
development, then reserve `make pre-pr` for the final PR-ready branch.
Before pushing code changes, make sure formatting is clean:
- Run `cargo fmt --all`.
@@ -87,6 +108,10 @@ Do not open a PR with code changes when the required checks fail.
- Include verification commands in the PR description.
- When using `gh pr create`/`gh pr edit`, use `--body-file` instead of inline `--body` for multiline markdown.
- Do not include the literal sequence `\n` in any GitHub issue, pull request, or discussion comment.
- Do not hard-wrap prose in PR/issue/discussion bodies; write each paragraph as a
single line and let it reflow. GitHub renders single newlines inside a paragraph
as line breaks, so mid-sentence wrapping shows up as ugly breaks. Only break lines
for list items, code blocks, and deliberate separators.
- After fixing code review comments or CI findings, always mark corresponding review
comments/threads as resolved before returning to the user.
- In handling review comments, confirm the underlying issue before changing code.
@@ -99,15 +124,35 @@ Do not open a PR with code changes when the required checks fail.
- Use environment variables or vault tooling for sensitive configuration.
- For localhost-sensitive tests, verify proxy settings to avoid traffic leakage.
## Tools
### xl.meta decode tool Quick Use
```
cargo run -p rustfs-filemeta --example dump_fileinfo -- "/path/to/file/xl.meta"
```
## Serde Safety
- Add `#[serde(deny_unknown_fields)]` to structs deserialized from untrusted input (S3 API XML/JSON, lifecycle rules, bucket policies, replication configs).
- When `deny_unknown_fields` is impractical (backward compatibility), at minimum log unknown fields at `warn` level.
- Never use `#[serde(default)]` on security-critical fields without explicit validation of the resulting value.
## Naming Conventions
- Follow Rust API Guidelines for naming: `SCREAMING_SNAKE_CASE` for statics and constants, `snake_case` for functions and variables, `PascalCase` for types.
- Do not use camelCase or Hungarian notation (e.g., `globalDeploymentIDPtr``GLOBAL_DEPLOYMENT_ID`).
- If existing code violates naming conventions, do not widen the violation in new code. Fix opportunistically when touching the surrounding area.
## Scoped Guidance in This Repository
- `.github/AGENTS.md`
- `crates/AGENTS.md`
- `crates/config/AGENTS.md`
- `crates/ecstore/AGENTS.md`
- `crates/e2e_test/AGENTS.md`
- `crates/iam/AGENTS.md`
- `crates/kms/AGENTS.md`
- `crates/policy/AGENTS.md`
- `rustfs/src/admin/AGENTS.md`
- `rustfs/src/storage/AGENTS.md`
Many crates and modules carry their own `AGENTS.md` with path-specific rules
(security boundaries, lock ordering, domain invariants). Before editing a
path, check for the nearest one:
```bash
git ls-files '*AGENTS.md'
```
The nearest file wins. Do not maintain a hand-written index of these files
here — it goes stale.
+26 -15
View File
@@ -1,11 +1,13 @@
# ARCHITECTURE.md
> Last updated: 2026-04-13 · Revision: 1 (draft)
> Last updated: 2026-07-02 · Revision: 2
>
> This document describes the high-level architecture of RustFS.
> If you want to familiarize yourself with the code base, you are in the right place!
>
> See also [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow.
> See also [docs/architecture](docs/architecture/overview.md) for active
> architecture migration guardrails.
## Bird's Eye View
@@ -50,7 +52,7 @@ rustfs/ # Workspace root (virtual manifest)
│ ├── auth.rs # S3 request authentication
│ ├── config/ # CLI args, config parsing, workload profiles
│ └── ...
├── crates/ # 39 library crates
├── crates/ # library crates (authoritative list: Cargo.toml [workspace].members)
│ ├── ecstore/ # Erasure-coded storage engine (⚠️ 87K lines)
│ ├── rio/ # Reader I/O pipeline (encrypt, compress, hash)
│ ├── io-core/ # Zero-copy I/O, scheduling, buffer pool
@@ -81,6 +83,10 @@ A request flows **downward** through the layers. No layer should reach upward
### Crate Reference
> Depth levels, line counts, and crate counts in this section are a
> point-in-time snapshot and drift with refactors. Treat them as orders of
> magnitude; `Cargo.toml` and `cargo tree` are the source of truth.
Crates are organized in a dependency DAG with 9 depth levels (0 = leaf, 8 = top):
```
@@ -91,14 +97,14 @@ Depth 0 — LEAF (no internal deps):
Depth 1:
io-core (→ io-metrics)
policy (→ config, credentials, crypto)
utils (→ config) ⚠️ inverted: utils should be leaf
utils (historical → config edge removed; now effectively leaf)
Depth 2:
concurrency, filemeta, keystone, kms, lock, obs,
signer, targets, trusted-proxies
Depth 3:
common (→ filemeta, madmin) ⚠️ inverted: common should be leaf
common (historical → filemeta/madmin edges removed; now effectively leaf)
Depth 4:
object-capacity, protos, rio
@@ -207,7 +213,8 @@ Depth 8 — TOP:
2. **Leaf crates have zero internal dependencies.** `config`, `credentials`, `crypto`,
`io-metrics`, `madmin`, `s3-common` should depend only on external crates.
- ⚠️ VIOLATED: `utils` depends on `config`, `common` depends on `filemeta` and `madmin`.
- ✅ RESOLVED: the historical `utils → config` and `commonfilemeta`/`madmin`
edges were removed; do not reintroduce them (see Known Structural Issues).
3. **Each type has exactly one definition.** Types shared across crates must be defined
in one crate and re-exported or imported by others.
@@ -242,11 +249,15 @@ Depth 8 — TOP:
### High
- **Dependency inversions.** `utils → config` and `common → filemeta/madmin` break
the layering model. These need to be untangled.
- **Dependency inversions.** Historical `utils → config` and
`common → filemeta/madmin` edges must stay removed so leaf/helper crates do
not regain upward dependencies.
- **Three-layer BackpressureConfig/DeadlockConfig duplication** across io-core,
concurrency, and rustfs/storage. Should be defined once with builder/composition.
concurrency, and `rustfs/src/storage`. Storage policies now expose and consume
explicit projections into the concurrency/io-core policy shapes, and workload
admission snapshots are composed through provider registries; later work
should use those bridges before deleting compatibility wrappers.
### Medium
@@ -326,10 +337,10 @@ The binary (`main.rs`) boots in this order:
┌───────────────┼───────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐
│ server │ │ admin │ │ app │
│ (HTTP) │ │(console)│ │(use-cases) │
└────┬────┘ └────┬────┘ └─────┬─────┘
┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐
│ server │ │ admin │ │ app │
│ (HTTP) │ │(console)│ │(use-cases) │
└────┬────┘ └────┬────┘ └─────┬─────┘
│ │ │
└───────────────┼───────────────┘
@@ -341,13 +352,13 @@ The binary (`main.rs`) boots in this order:
┌──────────────────┼──────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ ecstore │ │ rio │ │ io-core │
│ (87K,core) │ │ (readers) │ │ (zero-copy) │
└─────┬──────┘ └─────────────┘ └─────────────┘
┌─────┬──┼──┬─────┬──────┐
│ │ │ │ │ │
┌─────┬──┼──┬─────┬──────┐
│ │ │ │ │ │
common utils config policy filemeta ...
```
+85 -1
View File
@@ -22,11 +22,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- XML-formatted error responses compatible with S3 API
- Comprehensive integration documentation with manual testing guide
- **32 unit and integration tests** covering middleware, auth handlers, task-local storage, and role detection
- **SFTPv3 Protocol Support**: SSH-hosted SFTPv3 subsystem that translates each file operation into S3 calls against the local object store. Authentication uses IAM credentials (SSH username = access key, SSH password = secret key).
- Full SFTPv3 packet coverage: open, read, write, stat, lstat, fstat, mkdir, rmdir, rename, remove, opendir, readdir, realpath, close, plus the rest of the 21-packet specification
- Streaming multipart write up to the part size times 10000 parts (156.25 GiB at the default part size)
- Per-handle read-ahead cache with configurable window size and process-wide memory ceiling
- Per-session liveness watchdog: Linux probes `/proc/net/tcp` and cancels wedged sessions on the order of 45 seconds; non-Linux falls back to an inactivity ceiling on the order of 30 minutes
- 30-second SSH handshake deadline, per-call backend operation timeout, bounded multipart-abort fan-out, graceful-shutdown cascade
- 34 SFTPv3 compliance test cases under `crates/e2e_test/src/protocols/sftp_compliance.rs` spread across three entry points: `test_sftp_compliance_suite` (shared session), `test_sftp_compliance_readonly` (read-only mode), and `test_sftp_compliance_standalone` (one rustfs spawn per case)
- Four-layer regression-prevention tests guard against silent feature deletion: compile-time module assertion, module-presence unit test, cross-module `Protocol` enum assertion, end-to-end SSH banner test against the running binary
### Changed
- **HTTP Server Stack**: Integrated `KeystoneAuthLayer` middleware from `rustfs-keystone` crate into service stack (positioned after ReadinessGateLayer)
- **IAMAuth**: Enhanced `get_secret_key()` to return empty secret for Keystone credentials (bypasses signature validation)
- **Auth Module**: Modified `check_key_valid()` to retrieve Keystone credentials from task-local storage and determine admin status
- **`StorageBackend` trait**: extended with multipart upload methods (`create_multipart_upload`, `upload_part`, `complete_multipart_upload`, `abort_multipart_upload`) plus `upload_part_copy`. Streaming-upload code path is now available to FTPS, WebDAV, and Swift drivers as well.
- **`Protocol` enum**: new `Protocol::Sftp` variant with corresponding `S3Action` mappings. Every match arm on `Protocol` updated to handle the new variant exhaustively.
### Technical Details
- Middleware is self-contained in `rustfs-keystone` crate following the trusted-proxies pattern for integration-specific middleware
@@ -35,12 +45,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Integration preserves existing S3 authentication flow while adding Keystone support
- Zero breaking changes to existing functionality
- No new top-level directories in main binary crate (middleware lives in integration crate)
- SSH/SFTP wire handling via the `russh` and `russh-sftp` crates. SFTPv3 framing is implemented by `russh-sftp`; the rustfs-side `SftpDriver` implements `russh_sftp::server::Handler` and dispatches to the storage backend
- Drop-time abort for in-flight multipart uploads honours IAM Deny on `AbortMultipartUpload`. `start_multipart_upload` caches the authorisation decision so the synchronous `Drop` path can honour Allow / Deny policies without re-querying IAM
- Per-handle read cache uses an `Arc<AtomicU64>` shared across every `SftpDriver` instance to enforce a process-wide memory ceiling. On ceiling breach the populate is skipped and the read serves correctly via a single-call backend fetch
- Per-session liveness watchdog runs as a tokio task per accepted connection. Reads `/proc/net/tcp` and `/proc/net/tcp6` to look up the (local, peer) tuple's TCP state and cancels via `tokio_util::sync::CancellationToken` when wedge conditions are confirmed across two consecutive ticks
- Path canonicalisation rejects paths containing `\0`, `\r`, or `\n` and resolves traversal via `path::clean()` before any backend dispatch
- Cipher / KEX / MAC / host-key algorithm allowlists are hardcoded with no environment override. Strict-KEX (CVE-2023-48795 / Terrapin) marker presence asserted by unit test
- Per-session handle cap (default 64, configurable 8 to 1024) with UUID-generated handle ids
- Crate-level `#![deny(unsafe_code)]` is in force across `crates/protocols`. Socket fd duplication for the watchdog uses the safe `AsFd::try_clone_to_owned` path (Linux). Non-Linux targets use the inactivity-ceiling watchdog
- Platform-specific imports are cfg-gated. Unix enforces owner-only host-key mode bits (no group or other permission bits). Windows loads host keys without a mode check and trusts operator-managed NTFS ACLs. Targets that are neither Unix nor Windows fail SFTP at config-load with SftpInitError::UnsupportedPlatform
### Documentation
- Updated `crates/keystone/README.md` with complete integration architecture and workflow
- Added detailed manual testing guide with 10 test scenarios
- Updated main `README.md` to list Keystone authentication as available feature
- Added troubleshooting section for common integration issues
- Module-level rustdoc on `crates/protocols/src/sftp/mod.rs` describing the public API surface, configuration contract, and the architecture of the read cache and the wedge watchdog
### Configuration
New environment variables:
@@ -54,6 +74,43 @@ New environment variables:
- `RUSTFS_KEYSTONE_CACHE_SIZE` - Token cache size (default: 10000)
- `RUSTFS_KEYSTONE_CACHE_TTL` - Token cache TTL in seconds (default: 300)
- `RUSTFS_KEYSTONE_VERIFY_SSL` - Verify SSL certificates (default: true)
- `RUSTFS_SFTP_ENABLE` - Enable/disable SFTP (default: false)
- `RUSTFS_SFTP_ADDRESS` - Listen address (default: 0.0.0.0:2222)
- `RUSTFS_SFTP_HOST_KEY_DIR` - Directory containing host key files (must exist). On Unix each file must grant no group or other permission bits (owner access only). On Windows the files load without a mode check and rustfs trusts the directory NTFS ACL
- `RUSTFS_SFTP_HOST_KEY_RELOAD_ENABLE` - Rescan the host-key directory without a restart (default: false)
- `RUSTFS_SFTP_HOST_KEY_RELOAD_INTERVAL` - Host-key rescan interval in seconds, minimum 5 (default: 30)
- `RUSTFS_SFTP_IDLE_TIMEOUT` - Session idle timeout in seconds (default: 600)
- `RUSTFS_SFTP_PART_SIZE` - Multipart part size in bytes (default: 16 MiB)
- `RUSTFS_SFTP_READ_ONLY` - Reject write packets at the protocol layer (default: false)
- `RUSTFS_SFTP_BANNER` - SSH protocol identification string, must begin with `SSH-2.0-` (default: `SSH-2.0-RustFS`)
- `RUSTFS_SFTP_HANDLES_PER_SESSION` - Per-session open-handle cap, 8 to 1024 (default: 64)
- `RUSTFS_SFTP_BACKEND_OP_TIMEOUT_SECS` - Per-call backend deadline in seconds, 5 to 600 (default: 60)
- `RUSTFS_SFTP_READ_CACHE_WINDOW_BYTES` - Per-handle read-cache window in bytes, 256 KiB to 64 MiB or 0 to disable (default: 4 MiB)
- `RUSTFS_SFTP_READ_CACHE_TOTAL_MEM_BYTES` - Process-wide read-cache memory ceiling in bytes, 16 MiB minimum (default: 256 MiB)
### Files Added
- `crates/protocols/src/sftp/mod.rs` - SFTP module entry point, public API surface, crate-level rustdoc, regression-prevention test
- `crates/protocols/src/sftp/config.rs` - `SftpConfig` and `SftpInitError` types, env-var resolvers, host-key directory loader with permission enforcement
- `crates/protocols/src/sftp/constants.rs` - Named constants grouped by purpose: S3 error codes, HTTP error codes, POSIX mode bits, protocol identifiers, operational limits
- `crates/protocols/src/sftp/server.rs` - `SftpServer` SSH server, russh handler, password authentication against IAM, accept loop, per-session task spawn
- `crates/protocols/src/sftp/driver.rs` - `SftpDriver` per-session SFTPv3 handler dispatching each operation onto the `StorageBackend`
- `crates/protocols/src/sftp/state.rs` - `HandleState` variants for read, write-buffering, write-streaming, write-failed handles
- `crates/protocols/src/sftp/lifecycle.rs` - Per-session activity stamp, weak-ref registry, `/proc/net/tcp` probe for the wedge watchdog
- `crates/protocols/src/sftp/wedge_watchdog.rs` - Per-session liveness watchdog cancelling sessions silent at the SFTP layer while the kernel reports CLOSE_WAIT
- `crates/protocols/src/sftp/fallback_watchdog.rs` - Per-session silence-only liveness backstop for non-Linux targets, cancelling sessions only at the fallback idle ceiling
- `crates/protocols/src/sftp/read_cache.rs` - Per-handle in-memory read-ahead cache with shared atomic accumulator for the process-wide memory ceiling
- `crates/protocols/src/sftp/attrs.rs` - SFTPv3 `FileAttributes` mapping for objects and directories, longname formatting, mtime clamping
- `crates/protocols/src/sftp/dir.rs` - OPENDIR / READDIR pagination, root-bucket listing, sub-directory listing under a prefix
- `crates/protocols/src/sftp/errors.rs` - `SftpError` thiserror enum and S3-error classification into SFTPv3 status codes
- `crates/protocols/src/sftp/paths.rs` - Path canonicalisation, traversal rejection, `\0` / `\r` / `\n` rejection, bucket+key decomposition
- `crates/protocols/src/sftp/read.rs` - READ packet handler, EOF semantics, `MAX_READ_LEN` bound, integration with the read cache
- `crates/protocols/src/sftp/write.rs` - WRITE packet handler, in-memory buffering up to part size, transition to streaming multipart, CLOSE finalisation
- `crates/protocols/src/sftp/test_support.rs` - Test fixtures and helper builders for SFTP unit tests
- `crates/protocols/src/common/dummy_storage.rs` - In-memory `StorageBackend` test backend covering every method, used by SFTP unit tests and the FTPS / Swift / WebDAV test suites
- `crates/e2e_test/src/protocols/sftp_core.rs` - End-to-end regressions for the handshake deadline, idle-timeout disconnect, and the wedge watchdog
- `crates/e2e_test/src/protocols/sftp_compliance.rs` - SFTPv3 compliance suite entry points (`test_sftp_compliance_suite`, `test_sftp_compliance_readonly`, `test_sftp_compliance_standalone`)
- `crates/e2e_test/src/protocols/sftp_compliance_tests.rs` - Per-case test bodies (CMPTST-01..34), shared fixture helpers, lifecycle counters
- `crates/e2e_test/src/protocols/sftp_helpers.rs` - SFTP-specific test helpers and fixture seeders
### Files Modified
- `crates/keystone/src/middleware.rs` - Created Keystone authentication middleware (self-contained in keystone crate)
@@ -63,6 +120,27 @@ New environment variables:
- `rustfs/src/auth.rs` - Enhanced IAMAuth and check_key_valid for Keystone support, imported KEYSTONE_CREDENTIALS from rustfs-keystone
- `crates/keystone/README.md` - Comprehensive integration documentation
- `README.md` - Added Keystone as available feature
- `Cargo.toml` - Added the `sftp` feature alongside the existing protocol features
- `Cargo.lock` - Updated to include the new `russh`, `russh-sftp`, `socket2`, `tokio-util`, `subtle`, `uuid` dependencies and their transitive crates
- `crates/protocols/Cargo.toml` - Declared `russh`, `russh-sftp`, `socket2`, `tokio-util`, `subtle`, `uuid` under the `sftp` feature flag
- `crates/protocols/src/lib.rs` - Added `pub mod sftp` behind `#[cfg(feature = "sftp")]` plus the crate-level `#![deny(unsafe_code)]` lint
- `crates/protocols/src/common/client/s3.rs` - Extended the `StorageBackend` trait with `create_multipart_upload`, `upload_part`, `complete_multipart_upload`, `abort_multipart_upload`, and `upload_part_copy`
- `crates/protocols/src/common/session.rs` - Added the `Protocol::Sftp` variant and its `S3Action` mappings
- `crates/protocols/src/common/gateway.rs` - Handles the new `Protocol::Sftp` variant exhaustively
- `crates/protocols/src/common/mod.rs` - Exposed the new `dummy_storage` module
- `crates/protocols/src/constants.rs` - Added shared POSIX mode-bit constants used by SFTP and other protocols
- `crates/config/src/constants/protocols.rs` - `RUSTFS_SFTP_*` environment variable names and defaults
- `crates/utils/src/retry.rs` - Added the generic exponential-backoff retry helper used by the SFTP write path
- `crates/e2e_test/Cargo.toml` - Added the e2e test dependencies for SFTP (paramiko fixture, SSH keypair generation)
- `crates/e2e_test/src/protocols/mod.rs` - Registered the new `sftp_core`, `sftp_compliance`, `sftp_compliance_tests`, and `sftp_helpers` modules
- `crates/e2e_test/src/protocols/README.md` - Documented the SFTP test entry points and case index
- `crates/e2e_test/src/protocols/test_env.rs` - Added SFTP host-key directory provisioning to the shared protocol test environment
- `crates/e2e_test/src/protocols/test_runner.rs` - Wired the SFTP entry points into the runner
- `rustfs/Cargo.toml` - Added the `sftp` feature flag
- `rustfs/src/lib.rs` - One-line addition exporting the SFTP wiring
- `rustfs/src/init.rs` - Build and start the `SftpServer` when `RUSTFS_SFTP_ENABLE` is true
- `rustfs/src/main.rs` - Routed shutdown signals to the SFTP server alongside the other protocols
- `rustfs/src/protocols/client.rs` - Client-builder support for the new `Protocol::Sftp` variant
### Testing
- 16 unit tests in rustfs-keystone crate (config, auth, middleware, identity)
@@ -70,7 +148,13 @@ New environment variables:
- 6 auth unit tests in rustfs crate (role detection, task-local storage, Keystone credential handling)
- **Total: 32 tests** passing with zero compilation errors
- Manual testing guide provided for end-to-end validation
- All tests passing with `cargo test --all --exclude e2e_test`
- All Keystone tests passing with `cargo test --all --exclude e2e_test`
- 34 SFTPv3 compliance test cases (CMPTST-01..34) split across three entry points: `test_sftp_compliance_suite` (shared session, cases 01-14), `test_sftp_compliance_readonly` (read-only mode, cases 15-23), `test_sftp_compliance_standalone` (one rustfs spawn per case, cases 24-34)
- Regression-prevention tests at four layers: compile-time module assertion in `crates/protocols/src/lib.rs`, module-presence unit test in `crates/protocols/src/sftp/mod.rs`, cross-module `Protocol` enum assertion, and end-to-end SSH banner test against the running binary
- Standalone end-to-end regressions for the SSH handshake deadline, the idle-timeout disconnect path, and the wedge watchdog (Linux fast-kill and the cross-platform fallback path)
- Inline unit tests in every SFTP source file covering pure helpers (path canonicalisation, attribute mapping, S3-error classification, env-var bound resolvers)
- Strict-KEX (CVE-2023-48795) marker presence assertion as a unit test in `crates/protocols/src/sftp/server.rs`
- All tests passing with `cargo test --all --features sftp` against a 64-bit Linux target
---
+47
View File
@@ -0,0 +1,47 @@
# RustFS — CLAUDE.md
S3-compatible object store in Rust, derived from MinIO. Erasure-coded,
multi-pool, supports ILM tiering/lifecycle.
Agent rules (change style, verification, PR conventions, scoped per-crate
guidance) live in [AGENTS.md](AGENTS.md) — follow it. This file only adds
what Claude Code needs on top: commands and pointers.
## Commands
```bash
cargo build --release --bin rustfs # production binary
cargo check -p <crate> # fast type-check one crate
cargo test -p <crate> # test one crate
cargo fmt --all # format (required before PR)
make pre-commit # fast gate: fmt + arch checks + quick-check (NO clippy/tests)
make pre-pr # full pre-PR gate: fmt + arch checks + clippy + tests
make build-docker BUILD_OS=ubuntu22.04
```
> **Docker build note**: `buildx build` without `--load` keeps the image in
> the buildx cache only — `docker run` will use a stale local image. The
> Makefile already includes `--load`; if you suspect a stale binary, add
> `--no-cache` to the `buildx build` invocation inside
> `.config/make/build-docker.mak`.
## Where to look (do not duplicate here)
- Crate membership: `Cargo.toml` `[workspace].members`
- Architecture, layering, crate map: [ARCHITECTURE.md](ARCHITECTURE.md)
- Migration guardrails & readiness contracts: [docs/architecture/](docs/architecture/README.md)
- CI gates: `.github/workflows/ci.yml` (source of truth; never copy its steps into docs)
- Tier/ILM transition debugging (xl.meta inspection, versionId tracing):
[docs/operations/tier-ilm-debugging.md](docs/operations/tier-ilm-debugging.md)
## Domain conventions worth knowing up front
- Internal object metadata is written under **both** `x-rustfs-internal-<suffix>`
and `x-minio-internal-<suffix>` keys for MinIO interop
(`crates/utils/src/http/metadata_compat.rs`; `get_bytes` prefers the RustFS
key). Never write only one of the two.
- Binary metadata values (UUIDs) must be read defensively:
`.and_then(|v| Uuid::from_slice(&v).ok()).filter(|u| !u.is_nil())`
absent/empty/nil all mean "no value", not `Uuid::nil()`.
- A remote-tier version of `None`/`""` means the tier bucket is unversioned:
send **no** `versionId` on tier GET/DELETE.
+1 -1
View File
@@ -2,7 +2,7 @@
## 📋 Code Quality Requirements
For instructions on setting up and running the local development environment, please see [Development Guide](docs/DEVELOPMENT.md).
This guide covers the local development environment and the checks expected before contributing.
### 🔧 Code Formatting Rules
Generated
+3289 -1978
View File
File diff suppressed because it is too large Load Diff
+166 -133
View File
@@ -15,10 +15,10 @@
[workspace]
members = [
"rustfs", # Core file system implementation
"crates/appauth", # Application authentication and authorization
"crates/audit", # Audit target management system with multi-target fan-out
"crates/checksums", # client checksums
"crates/common", # Shared utilities and data structures
"crates/data-usage", # Shared data usage models and algorithms
"crates/config", # Configuration management
"crates/credentials", # Credential management system
"crates/crypto", # Cryptography and security features
@@ -28,28 +28,36 @@ members = [
"crates/heal", # Erasure set and object healing
"crates/iam", # Identity and Access Management
"crates/keystone", # OpenStack Keystone integration
"crates/lifecycle", # Lifecycle rule evaluation contracts
"crates/kms", # Key Management Service
"crates/lock", # Distributed locking implementation
"crates/madmin", # Management dashboard and admin API interface
"crates/notify", # Notification system for events
"crates/obs", # Observability utilities
"crates/object-capacity", # Capacity scan and refresh core
"crates/object-data-cache", # Long-term object body cache engine
"crates/policy", # Policy management
"crates/protocols", # Protocol implementations (FTPS, SFTP, etc.)
"crates/protos", # Protocol buffer definitions
"crates/rio", # Rust I/O utilities and abstractions
"crates/rio-v2", # Next-generation Rust I/O compatibility layer
"crates/replication", # Replication contracts and wire formats
"crates/concurrency", # Concurrency management for RustFS - timeout, locking, backpressure, and I/O scheduling
"crates/s3-common", # Common utilities and data structures for S3 compatibility
"crates/s3-types", # S3 event type definitions
"crates/s3-ops", # S3 operation definitions and mapping
"crates/s3select-api", # S3 Select API interface
"crates/s3select-query", # S3 Select query engine
"crates/scanner", # Scanner for data integrity checks and health monitoring
"crates/security-governance", # Security governance contracts
"crates/extension-schema", # Extension schema contracts
"crates/signer", # client signer
"crates/storage-api", # Storage API contracts
"crates/targets", # Target-specific configurations and utilities
"crates/trusted-proxies", # Trusted proxies management
"crates/tls-runtime", # Project-wide TLS runtime foundation
"crates/utils", # Utility functions and helpers
"crates/workers", # Worker thread pools and task scheduling
"crates/io-metrics", # Zero-copy metrics collection for performance analysis
"crates/io-core", # Zero-copy core reader and writer implementations
"crates/io-metrics", # I/O metrics collection for performance analysis
"crates/io-core", # Buffered Bytes, mmap-then-copy, and aligned pread I/O helpers
"crates/zip", # ZIP file handling and compression
]
resolver = "3"
@@ -58,8 +66,8 @@ resolver = "3"
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/rustfs/rustfs"
rust-version = "1.93.0"
version = "0.0.5"
rust-version = "1.96.0"
version = "1.0.0-beta.8"
homepage = "https://rustfs.com"
description = "RustFS is a high-performance distributed object storage software built using Rust, one of the most popular languages worldwide. "
keywords = ["RustFS", "Minio", "object-storage", "filesystem", "s3"]
@@ -76,146 +84,169 @@ redundant_clone = "warn"
[workspace.dependencies]
# RustFS Internal Crates
rustfs = { path = "./rustfs", version = "0.0.5" }
rustfs-heal = { path = "crates/heal", version = "0.0.5" }
rustfs-appauth = { path = "crates/appauth", version = "0.0.5" }
rustfs-audit = { path = "crates/audit", version = "0.0.5" }
rustfs-checksums = { path = "crates/checksums", version = "0.0.5" }
rustfs-common = { path = "crates/common", version = "0.0.5" }
rustfs-config = { path = "./crates/config", version = "0.0.5" }
rustfs-concurrency = { path = "./crates/concurrency", version = "0.0.5" }
rustfs-credentials = { path = "crates/credentials", version = "0.0.5" }
rustfs-crypto = { path = "crates/crypto", version = "0.0.5" }
rustfs-ecstore = { path = "crates/ecstore", version = "0.0.5" }
rustfs-filemeta = { path = "crates/filemeta", version = "0.0.5" }
rustfs-iam = { path = "crates/iam", version = "0.0.5" }
rustfs-keystone = { path = "crates/keystone", version = "0.0.5" }
rustfs-kms = { path = "crates/kms", version = "0.0.5" }
rustfs-lock = { path = "crates/lock", version = "0.0.5" }
rustfs-madmin = { path = "crates/madmin", version = "0.0.5" }
rustfs-notify = { path = "crates/notify", version = "0.0.5" }
rustfs-io-metrics = { path = "crates/io-metrics", version = "0.0.5" }
rustfs-io-core = { path = "crates/io-core", version = "0.0.5" }
rustfs-object-capacity = { path = "crates/object-capacity", version = "0.0.5" }
rustfs-obs = { path = "crates/obs", version = "0.0.5" }
rustfs-policy = { path = "crates/policy", version = "0.0.5" }
rustfs-protos = { path = "crates/protos", version = "0.0.5" }
rustfs-protocols = { path = "crates/protocols", version = "0.0.5" }
rustfs-rio = { path = "crates/rio", version = "0.0.5" }
rustfs-s3-common = { path = "crates/s3-common", version = "0.0.5" }
rustfs-s3select-api = { path = "crates/s3select-api", version = "0.0.5" }
rustfs-s3select-query = { path = "crates/s3select-query", version = "0.0.5" }
rustfs-scanner = { path = "crates/scanner", version = "0.0.5" }
rustfs-signer = { path = "crates/signer", version = "0.0.5" }
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "0.0.5" }
rustfs-targets = { path = "crates/targets", version = "0.0.5" }
rustfs-utils = { path = "crates/utils", version = "0.0.5" }
rustfs-workers = { path = "crates/workers", version = "0.0.5" }
rustfs-zip = { path = "./crates/zip", version = "0.0.5" }
rustfs = { path = "./rustfs", version = "1.0.0-beta.8" }
rustfs-heal = { path = "crates/heal", version = "1.0.0-beta.8" }
rustfs-audit = { path = "crates/audit", version = "1.0.0-beta.8" }
rustfs-checksums = { path = "crates/checksums", version = "1.0.0-beta.8" }
rustfs-common = { path = "crates/common", version = "1.0.0-beta.8" }
rustfs-data-usage = { path = "crates/data-usage", version = "1.0.0-beta.8" }
rustfs-config = { path = "./crates/config", version = "1.0.0-beta.8" }
rustfs-concurrency = { path = "./crates/concurrency", version = "1.0.0-beta.8" }
rustfs-credentials = { path = "crates/credentials", version = "1.0.0-beta.8" }
rustfs-crypto = { path = "crates/crypto", version = "1.0.0-beta.8" }
rustfs-ecstore = { path = "crates/ecstore", version = "1.0.0-beta.8" }
rustfs-filemeta = { path = "crates/filemeta", version = "1.0.0-beta.8" }
rustfs-iam = { path = "crates/iam", version = "1.0.0-beta.8" }
rustfs-keystone = { path = "crates/keystone", version = "1.0.0-beta.8" }
rustfs-lifecycle = { path = "crates/lifecycle", version = "1.0.0-beta.8" }
rustfs-kms = { path = "crates/kms", version = "1.0.0-beta.8" }
rustfs-lock = { path = "crates/lock", version = "1.0.0-beta.8" }
rustfs-madmin = { path = "crates/madmin", version = "1.0.0-beta.8" }
rustfs-notify = { path = "crates/notify", version = "1.0.0-beta.8" }
rustfs-io-metrics = { path = "crates/io-metrics", version = "1.0.0-beta.8" }
rustfs-io-core = { path = "crates/io-core", version = "1.0.0-beta.8" }
rustfs-object-capacity = { path = "crates/object-capacity", version = "1.0.0-beta.8" }
rustfs-object-data-cache = { path = "crates/object-data-cache", version = "1.0.0-beta.8" }
rustfs-obs = { path = "crates/obs", version = "1.0.0-beta.8" }
rustfs-policy = { path = "crates/policy", version = "1.0.0-beta.8" }
rustfs-protos = { path = "crates/protos", version = "1.0.0-beta.8" }
rustfs-protocols = { path = "crates/protocols", version = "1.0.0-beta.8" }
rustfs-replication = { path = "crates/replication", version = "1.0.0-beta.8" }
rustfs-rio = { path = "crates/rio", version = "1.0.0-beta.8" }
rustfs-rio-v2 = { path = "crates/rio-v2", version = "1.0.0-beta.8" }
rustfs-s3-types = { path = "crates/s3-types", version = "1.0.0-beta.8" }
rustfs-s3-ops = { path = "crates/s3-ops", version = "1.0.0-beta.8" }
rustfs-s3select-api = { path = "crates/s3select-api", version = "1.0.0-beta.8" }
rustfs-s3select-query = { path = "crates/s3select-query", version = "1.0.0-beta.8" }
rustfs-scanner = { path = "crates/scanner", version = "1.0.0-beta.8" }
rustfs-security-governance = { path = "crates/security-governance", version = "1.0.0-beta.8" }
rustfs-extension-schema = { path = "crates/extension-schema", version = "1.0.0-beta.8" }
rustfs-signer = { path = "crates/signer", version = "1.0.0-beta.8" }
rustfs-storage-api = { path = "crates/storage-api", version = "1.0.0-beta.8" }
rustfs-trusted-proxies = { path = "crates/trusted-proxies", version = "1.0.0-beta.8" }
rustfs-targets = { path = "crates/targets", version = "1.0.0-beta.8" }
rustfs-tls-runtime = { path = "crates/tls-runtime", version = "1.0.0-beta.8" }
rustfs-utils = { path = "crates/utils", version = "1.0.0-beta.8" }
rustfs-zip = { path = "./crates/zip", version = "1.0.0-beta.8" }
# Async Runtime and Networking
async-channel = "2.5.0"
async_zip = { version = "0.0.18", default-features = false, features = ["tokio", "deflate"] }
mysql_async = { version = "0.37", default-features = false, features = ["default-rustls", "tracing"] }
async-compression = { version = "0.4.42" }
async-recursion = "1.1.1"
async-trait = "0.1.89"
async-nats = "0.47.0"
async-nats = "0.49.1"
axum = "0.8.9"
futures = "0.3.32"
futures-core = "0.3.32"
futures-lite = "2.6.1"
futures-util = "0.3.32"
pollster = "0.4.0"
pulsar = { version = "6.7.2", default-features = false, features = ["tokio-rustls-runtime"] }
hyper = { version = "1.9.0", features = ["http2", "http1", "server"] }
pulsar = { version = "6.8.0", default-features = false, features = ["tokio-rustls-runtime", "telemetry"] }
lapin = { version = "4.10.0", default-features = false, features = ["tokio", "rustls", "rustls--aws_lc_rs"] }
hyper = { version = "1.10.1", features = ["http2", "http1", "server"] }
hyper-rustls = { version = "0.27.9", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "http2", "aws-lc-rs", "webpki-roots"] }
hyper-util = { version = "0.1.20", features = ["tokio", "server-auto", "server-graceful", "tracing"] }
http = "1.4.0"
http = "1.4.2"
http-body = "1.0.1"
http-body-util = "0.1.3"
reqwest = { version = "0.13.2", default-features = false, features = ["rustls", "charset", "http2", "system-proxy", "stream", "json", "blocking", "query", "form"] }
minlz = "1.2.3"
reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "charset", "http2", "system-proxy", "stream", "json", "blocking", "query", "form"] }
rustfs-kafka-async = { version = "1.2.0" }
socket2 = { version = "0.6.3", features = ["all"] }
tokio = { version = "1.52.1", features = ["fs", "rt-multi-thread"] }
socket2 = { version = "0.6.4", features = ["all"] }
tokio = { version = "1.52.3", features = ["fs", "rt-multi-thread"] }
tokio-rustls = { version = "0.26.4", default-features = false, features = ["logging", "tls12", "aws-lc-rs"] }
tokio-stream = { version = "0.1.18" }
tokio-test = "0.4.5"
tokio-util = { version = "0.7.18", features = ["io", "compat"] }
tonic = { version = "0.14.5", features = ["gzip"] }
tonic-prost = { version = "0.14.5" }
tonic-prost-build = { version = "0.14.5" }
tonic = { version = "0.14.6", features = ["gzip", "deflate"] }
tonic-prost = { version = "0.14.6" }
tonic-prost-build = { version = "0.14.6" }
tower = { version = "0.5.3", features = ["timeout"] }
tower-http = { version = "0.6.8", features = ["cors"] }
tower-http = { version = "0.7.0", features = ["cors"] }
# Serialization and Data Formats
bytes = { version = "1.11.1", features = ["serde"] }
bytesize = "2.3.1"
apache-avro = "0.21.0"
bytes = { version = "1.12.0", features = ["serde"] }
bytesize = "2.4.2"
byteorder = "1.5.0"
flatbuffers = "25.12.19"
form_urlencoded = "1.2.2"
prost = "0.14.3"
quick-xml = "0.39.2"
prost = "0.14.4"
quick-xml = "0.41.0"
rmp = { version = "0.8.15" }
rmp-serde = { version = "1.3.1" }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149", features = ["raw_value"] }
serde_json = { version = "1.0.150", features = ["raw_value"] }
serde_urlencoded = "0.7.1"
# Cryptography and Security
aes-gcm = { version = "0.11.0-rc.3", features = ["rand_core"] }
argon2 = { version = "0.6.0-rc.8" }
blake2 = "0.11.0-rc.6"
chacha20poly1305 = { version = "0.11.0-rc.3" }
crc-fast = "1.9.0"
# NOTE: These direct cryptography dependencies use RC versions because the
# matching stable releases are not available yet, while previous stable lines
# have incompatible APIs. Keep them exact-pinned and monitor upstream for stable
# releases.
aes-gcm = { version = "=0.11.0", features = ["rand_core"] }
argon2 = { version = "=0.6.0-rc.8" }
blake2 = "=0.11.0-rc.6"
chacha20poly1305 = { version = "=0.11.0" }
crc-fast = "1.10.0"
hmac = { version = "0.13.0" }
jsonwebtoken = { version = "10.3.0", features = ["aws_lc_rs"] }
openidconnect = { version = "4.0", default-features = false }
jsonwebtoken = { version = "10.4.0", features = ["aws_lc_rs"] }
openidconnect = { version = "4.0", default-features = false, features = ["accept-rfc3339-timestamps"] }
pbkdf2 = "0.13.0"
rsa = { version = "0.10.0-rc.17" }
rustls = { version = "0.23.39", default-features = false, features = ["aws-lc-rs", "logging", "tls12", "prefer-post-quantum", "std"] }
rustls-pki-types = "1.14.1"
rsa = { version = "=0.10.0-rc.18" }
rustls = { version = "0.23.41", default-features = false, features = ["aws-lc-rs", "logging", "tls12", "prefer-post-quantum", "std"] }
rustls-native-certs = "0.8"
rustls-pki-types = "1.15.0"
sha1 = "0.11.0"
sha2 = "0.11.0"
subtle = "2.6"
zeroize = { version = "1.8.2", features = ["derive"] }
zeroize = { version = "1.9.0", features = ["derive"] }
# Time and Date
chrono = { version = "0.4.44", features = ["serde"] }
humantime = "2.3.0"
jiff = { version = "0.2.24", features = ["serde"] }
time = { version = "0.3.47", features = ["std", "parsing", "formatting", "macros", "serde"] }
chrono = { version = "0.4.45", features = ["serde"] }
humantime = "2.4.0"
jiff = { version = "0.2.31", features = ["serde"] }
time = { version = "0.3.53", features = ["std", "parsing", "formatting", "macros", "serde"] }
# Database
deadpool-postgres = { version = "0.14", features = ["rt_tokio_1"] }
tokio-postgres = { version = "0.7.18", default-features = false, features = ["runtime", "with-serde_json-1"] }
tokio-postgres-rustls = "0.14.0"
# Utilities and Tools
anyhow = "1.0.102"
arc-swap = "1.9.1"
astral-tokio-tar = "0.6.0"
atoi = "2.0.0"
anyhow = "1.0.103"
arc-swap = "1.9.2"
astral-tokio-tar = "0.6.3"
atoi = "3.1.0"
atomic_enum = "0.3.0"
aws-config = { version = "1.8.16" }
aws-config = { version = "1.8.18" }
aws-credential-types = { version = "1.2.14" }
aws-sdk-s3 = { version = "1.131.0", default-features = false, features = ["sigv4a", "default-https-client", "rt-tokio"] }
aws-smithy-http-client = { version = "1.1.12", default-features = false, features = ["default-client", "rustls-aws-lc"] }
aws-smithy-types = { version = "1.4.7" }
backtrace = "0.3.76"
aws-sdk-s3 = { version = "1.137.0", default-features = false, features = ["sigv4a", "default-https-client", "rt-tokio"] }
aws-smithy-http-client = { version = "1.1.13", default-features = false, features = ["default-client", "rustls-aws-lc"] }
aws-smithy-runtime-api = { version = "1.12.3", features = ["http-1x"] }
aws-smithy-types = { version = "1.5.0" }
base64 = "0.22.1"
base64-simd = "0.8.0"
brotli = "8.0.2"
cfg-if = "1.0.4"
brotli = "8.0.4"
clap = { version = "4.6.1", features = ["derive", "env"] }
const-str = { version = "1.1.0", features = ["std", "proc"] }
convert_case = "0.11.0"
criterion = { version = "0.8", features = ["html_reports"] }
crossbeam-queue = "0.3.12"
crossbeam-channel = "0.5.15"
crossbeam-deque = "0.8.6"
crossbeam-utils = "0.8.21"
datafusion = "53.1.0"
crossbeam-queue = "0.3.13"
crossbeam-channel = "0.5.16"
crossbeam-deque = "0.8.7"
crossbeam-utils = "0.8.22"
datafusion = { git = "https://github.com/apache/datafusion.git", rev = "dae03ee062b2abf986de8df12ea82fb1578a2d99" }
derive_builder = "0.20.2"
enumset = "1.1.10"
enumset = "1.1.13"
faster-hex = "0.10.0"
flate2 = "1.1.9"
glob = "0.3.3"
google-cloud-storage = "1.11.0"
google-cloud-auth = "1.9.0"
hashbrown = { version = "0.17.0", features = ["serde", "rayon"] }
google-cloud-storage = "1.15.0"
google-cloud-auth = "1.13.0"
hashbrown = { version = "0.17.1", features = ["serde", "rayon"] }
hex = "0.4.3"
hex-simd = "0.8.0"
highway = { version = "1.3.0" }
@@ -223,8 +254,8 @@ ipnetwork = { version = "0.21.1", features = ["serde"] }
lazy_static = "1.5.0"
libc = "0.2.186"
libsystemd = "0.7.2"
local-ip-address = "0.6.12"
memmap2 = "0.9.10"
local-ip-address = "0.6.13"
memmap2 = "0.9.11"
lz4 = "1.28.1"
matchit = "0.9.2"
md-5 = "0.11.0"
@@ -234,34 +265,34 @@ moka = { version = "0.12.15", features = ["future"] }
netif = "0.1.6"
num_cpus = { version = "1.17.0" }
nvml-wrapper = "0.12.1"
object_store = "0.13.2"
parking_lot = "0.12.5"
path-absolutize = "3.1.1"
path-clean = "1.0.1"
percent-encoding = "2.3.2"
pin-project-lite = "0.2.17"
pretty_assertions = "1.4.1"
rand = { version = "0.10.1", features = ["serde"] }
rand = { version = "0.10.2", features = ["serde"] }
ratelimit = "0.10.1"
rayon = "1.12.0"
reed-solomon-erasure = { version = "6.0", default-features = false, features = ["std", "simd-accel"] }
reed-solomon-erasure = { package = "rustfs-erasure-codec", version = "7.0.1", features = ["simd-accel"] }
#reed-solomon-erasure = { version = "6.0", features = ["simd-accel"], git = "https://github.com/houseme/reed-solomon-erasure",rev = "main" }
reed-solomon-simd = "3.1.0"
regex = { version = "1.12.3" }
rumqttc = { package = "rumqttc-next", version = "0.30.0", features = ["websocket"] }
regex = { version = "1.12.4" }
rumqttc = { package = "rumqttc-next", version = "0.33.2", features = ["websocket"] }
redis = { version = "1.3.0", features = ["connection-manager", "tokio-rustls-comp", "tls-rustls-insecure"] }
rustix = { version = "1.1.4", features = ["fs"] }
rust-embed = { version = "8.11.0" }
rustc-hash = { version = "2.1.2" }
s3s = { git = "https://github.com/rustfs/s3s", rev = "a3b16608df35aaeed8fff08b4988d03f4ca9445b", features = ["minio"] }
serial_test = "3.4.0"
rustc-hash = { version = "2.1.3" }
s3s = { version = "0.14.1", features = ["minio"] }
serial_test = "3.5.0"
shadow-rs = { version = "2.0.0", default-features = false }
siphasher = "1.0.2"
smallvec = { version = "1.15.1", features = ["serde"] }
siphasher = "1.0.3"
smallvec = { version = "1.15.2", features = ["serde"] }
smartstring = "1.0.1"
snafu = "0.9.0"
snap = "1.1.1"
starshard = { version = "1.1.0", features = ["rayon", "async", "serde"] }
starshard = { version = "2.2.1", features = ["rayon", "async", "serde"] }
strum = { version = "0.28.0", features = ["derive"] }
sysinfo = "0.38.4"
sysinfo = "0.39.5"
temp-env = "0.3.6"
tempfile = "3.27.0"
test-case = "3.3.1"
@@ -269,56 +300,58 @@ thiserror = "2.0.18"
tracing = { version = "0.1.44" }
tracing-appender = "0.2.5"
tracing-error = "0.2.1"
tracing-opentelemetry = "0.32.1"
tracing-opentelemetry = { version = "0.33" }
tracing-subscriber = { version = "0.3.23", features = ["env-filter", "time"] }
transform-stream = "0.3.1"
url = "2.5.8"
urlencoding = "2.1.3"
uuid = { version = "1.23.1", features = ["v4", "fast-rng", "macro-diagnostics"] }
uuid = { version = "1.23.4", features = ["v4", "fast-rng", "macro-diagnostics"] }
vaultrs = { version = "0.8.0" }
walkdir = "2.5.0"
wildmatch = { version = "2.6.1", features = ["serde"] }
windows = { version = "0.62.2" }
xxhash-rust = { version = "0.8.15", features = ["xxh64", "xxh3"] }
zip = "8.5.1"
xxhash-rust = { version = "0.8.16", features = ["xxh64", "xxh3"] }
zip = "8.6.0"
zstd = "0.13.3"
# Observability and Metrics
metrics = "0.24.3"
metrics = "0.24.6"
dial9-tokio-telemetry = "0.3"
opentelemetry = { version = "0.31.0" }
opentelemetry-appender-tracing = { version = "0.31.1", features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes", "spec_unstable_logs_enabled"] }
opentelemetry-otlp = { version = "0.31.1", features = ["gzip-http", "reqwest-rustls"] }
opentelemetry_sdk = { version = "0.31.0" }
opentelemetry-semantic-conventions = { version = "0.31.0", features = ["semconv_experimental"] }
opentelemetry-stdout = { version = "0.31.0" }
pyroscope = { version = "2.0.2", features = ["backend-pprof-rs"] }
opentelemetry = { version = "0.32.0" }
opentelemetry-appender-tracing = { version = "0.32.0", features = ["experimental_span_attributes", "experimental_metadata_attributes"] }
opentelemetry-otlp = { version = "0.32.0", features = ["gzip-http", "reqwest-rustls"] }
opentelemetry_sdk = { version = "0.32.1", features = ["rt-tokio"] }
opentelemetry-semantic-conventions = { version = "0.32.1", features = ["semconv_experimental"] }
opentelemetry-stdout = { version = "0.32.0" }
pyroscope = { version = "2.0.6", features = ["backend-pprof-rs"] }
# FTP and SFTP
libunftp = { version = "0.23.0", features = ["experimental"] }
unftp-core = "0.1.0"
suppaftp = { version = "8.0.3", features = ["tokio", "tokio-rustls-aws-lc-rs"] }
rcgen = "0.14.7"
suppaftp = { version = "10.0.0", features = ["tokio", "tokio-rustls-aws-lc-rs"] }
rcgen = "0.14.8"
russh = { version = "0.62.2", features = ["serde"] }
russh-sftp = "2.3.0"
# WebDAV
dav-server = "0.11.0"
# Performance Analysis and Memory Profiling
mimalloc = "0.1"
# Use tikv-jemallocator as memory allocator and enable performance analysis
tikv-jemallocator = { version = "0.6", features = ["profiling", "stats", "unprefixed_malloc_on_supported_platforms", "background_threads"] }
# Used to control and obtain statistics for jemalloc at runtime
tikv-jemalloc-ctl = { version = "0.6", features = ["use_std", "stats", "profiling"] }
# Used to generate pprof-compatible memory profiling data and support symbolization and flame graphs
jemalloc_pprof = { version = "0.8.2", features = ["symbolize", "flamegraph"] }
# Used to generate CPU performance analysis data and flame diagrams
# pprof = { version = "0.15.0", features = ["flamegraph", "protobuf-codec"] }
# Pyroscope uses a patched pprof, until they merge back upstream, replace all references. Otherwise, two pprof libs with symbol collision.
pprof = { package = "pprof-pyroscope-fork", version = "0.1500.3", features = ["flamegraph", "protobuf-codec"] }
hotpath = "0.21"
# Snapshot testing for output format regression detection
insta = { version = "1.48", features = ["yaml", "json"] }
[workspace.metadata.cargo-shear]
ignored = ["rustfs"]
[profile.dev]
# Full debuginfo roughly doubles compile+link time and produces multi-GB
# target dirs (slow CI cache save/restore, slow artifact upload). Line tables
# keep meaningful backtraces. For local debugging with full debuginfo, run:
# CARGO_PROFILE_DEV_DEBUG=full cargo build
debug = "line-tables-only"
[profile.release]
opt-level = 3
+25 -10
View File
@@ -17,7 +17,7 @@ FROM alpine:3.23.4 AS build
ARG TARGETARCH
ARG RELEASE=latest
RUN apk add --no-cache ca-certificates curl unzip
RUN apk add --no-cache ca-certificates curl jq unzip
WORKDIR /build
RUN set -eux; \
@@ -27,19 +27,35 @@ RUN set -eux; \
*) echo "Unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \
esac; \
if [ "$RELEASE" = "latest" ]; then \
TAG="$(curl -fsSL https://api.github.com/repos/rustfs/rustfs/releases \
| grep -o '"tag_name": "[^"]*"' | cut -d'"' -f4 | head -n 1)"; \
RELEASE_JSON="$(curl -fsSL https://api.github.com/repos/rustfs/rustfs/releases | jq -c '.[0]')"; \
TAG="$(printf '%s' "$RELEASE_JSON" | jq -r '.tag_name // empty')"; \
else \
TAG="$RELEASE"; \
RELEASE_JSON="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$TAG" 2>/dev/null || true)"; \
if [ -z "$RELEASE_JSON" ]; then \
if [ "${TAG#v}" = "$TAG" ]; then \
ALT_TAG="v$TAG"; \
else \
ALT_TAG="${TAG#v}"; \
fi; \
echo "Primary tag lookup failed, retrying with alternate tag: $ALT_TAG"; \
RELEASE_JSON="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$ALT_TAG")"; \
TAG="$ALT_TAG"; \
fi; \
fi; \
if [ -z "$TAG" ] || [ -z "$RELEASE_JSON" ]; then echo "Failed to resolve release metadata" >&2; exit 1; fi; \
echo "Using tag: $TAG (arch pattern: $ARCH_SUBSTR)"; \
# Find download URL in assets list for this tag that contains arch substring and ends with .zip
URL="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$TAG" \
| grep -o "\"browser_download_url\": \"[^\"]*${ARCH_SUBSTR}[^\"]*\\.zip\"" \
| cut -d'"' -f4 | head -n 1)"; \
if [ -z "$URL" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
ASSET_JSON="$(printf '%s' "$RELEASE_JSON" | jq -c --arg arch "$ARCH_SUBSTR" '.assets[] | select((.name | contains($arch)) and (.name | endswith(".zip"))) | {name: .name, url: .browser_download_url, digest: .digest}' | head -n 1)"; \
if [ -z "$ASSET_JSON" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
ASSET_NAME="$(printf '%s' "$ASSET_JSON" | jq -r '.name // empty')"; \
URL="$(printf '%s' "$ASSET_JSON" | jq -r '.url // empty')"; \
DIGEST="$(printf '%s' "$ASSET_JSON" | jq -r '.digest // empty')"; \
SHA256="$(printf '%s' "$DIGEST" | sed 's/^sha256://')"; \
if [ -z "$URL" ] || [ -z "$ASSET_NAME" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
if [ -z "$SHA256" ] || [ "$SHA256" = "$DIGEST" ]; then echo "Release asset $ASSET_NAME is missing a sha256 digest" >&2; exit 1; fi; \
echo "Downloading: $URL"; \
curl -fL "$URL" -o rustfs.zip; \
printf '%s rustfs.zip\n' "$SHA256" | sha256sum -c -; \
unzip -q rustfs.zip -d /build; \
# If binary is not in root directory, try to locate and move from zip to /build/rustfs
if [ ! -x /build/rustfs ]; then \
@@ -87,8 +103,7 @@ RUN addgroup -g 10001 -S rustfs && \
chown -R rustfs:rustfs /data /logs && \
chmod 0750 /data /logs
ENV RUSTFS_CORS_ALLOWED_ORIGINS="*" \
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \
ENV RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \
RUSTFS_VOLUMES="/data" \
RUSTFS_OBS_LOGGER_LEVEL=warn \
RUSTFS_OBS_LOG_DIRECTORY=/logs \
+1 -1
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM rust:1.91-trixie
FROM rust:1.95-trixie
RUN set -eux; \
export DEBIAN_FRONTEND=noninteractive; \
+25 -11
View File
@@ -20,6 +20,7 @@ ARG RELEASE=latest
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \
jq \
unzip \
&& rm -rf /var/lib/apt/lists/*
@@ -31,21 +32,37 @@ RUN set -eux; \
arm64) ARCH_SUBSTR="aarch64-gnu" ;; \
*) echo "Unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \
esac; \
\
if [ "$RELEASE" = "latest" ]; then \
TAG="$(curl -fsSL https://api.github.com/repos/rustfs/rustfs/releases \
| grep -o '"tag_name": "[^"]*"' | cut -d'"' -f4 | head -n 1)"; \
RELEASE_JSON="$(curl -fsSL https://api.github.com/repos/rustfs/rustfs/releases | jq -c '.[0]')"; \
TAG="$(printf '%s' "$RELEASE_JSON" | jq -r '.tag_name // empty')"; \
else \
TAG="$RELEASE"; \
RELEASE_JSON="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$TAG" 2>/dev/null || true)"; \
if [ -z "$RELEASE_JSON" ]; then \
if [ "${TAG#v}" = "$TAG" ]; then \
ALT_TAG="v$TAG"; \
else \
ALT_TAG="${TAG#v}"; \
fi; \
echo "Primary tag lookup failed, retrying with alternate tag: $ALT_TAG"; \
RELEASE_JSON="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$ALT_TAG")"; \
TAG="$ALT_TAG"; \
fi; \
fi; \
if [ -z "$TAG" ] || [ -z "$RELEASE_JSON" ]; then echo "Failed to resolve release metadata" >&2; exit 1; fi; \
echo "Using tag: $TAG (arch pattern: $ARCH_SUBSTR)"; \
ASSET_JSON="$(printf '%s' "$RELEASE_JSON" | jq -c --arg arch "$ARCH_SUBSTR" '.assets[] | select((.name | contains($arch)) and (.name | endswith(".zip"))) | {name: .name, url: .browser_download_url, digest: .digest}' | head -n 1)"; \
if [ -z "$ASSET_JSON" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
ASSET_NAME="$(printf '%s' "$ASSET_JSON" | jq -r '.name // empty')"; \
URL="$(printf '%s' "$ASSET_JSON" | jq -r '.url // empty')"; \
DIGEST="$(printf '%s' "$ASSET_JSON" | jq -r '.digest // empty')"; \
SHA256="$(printf '%s' "$DIGEST" | sed 's/^sha256://')"; \
\
URL="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$TAG" \
| grep -o "\"browser_download_url\": \"[^\"]*${ARCH_SUBSTR}[^\"]*\\.zip\"" \
| cut -d'"' -f4 | head -n 1)"; \
\
if [ -z "$URL" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
if [ -z "$URL" ] || [ -z "$ASSET_NAME" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
if [ -z "$SHA256" ] || [ "$SHA256" = "$DIGEST" ]; then echo "Release asset $ASSET_NAME is missing a sha256 digest" >&2; exit 1; fi; \
\
curl -fL "$URL" -o rustfs.zip; \
printf '%s rustfs.zip\n' "$SHA256" | sha256sum -c -; \
unzip -q rustfs.zip -d /build; \
\
if [ ! -x /build/rustfs ]; then \
@@ -94,10 +111,7 @@ RUN groupadd -g 10001 rustfs && \
ENV RUSTFS_ADDRESS=":9000" \
RUSTFS_CONSOLE_ADDRESS=":9001" \
RUSTFS_ACCESS_KEY="rustfsadmin" \
RUSTFS_SECRET_KEY="rustfsadmin" \
RUSTFS_CONSOLE_ENABLE="true" \
RUSTFS_CORS_ALLOWED_ORIGINS="*" \
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \
RUSTFS_VOLUMES="/data" \
RUSTFS_OBS_LOGGER_LEVEL=warn \
+40 -9
View File
@@ -26,15 +26,19 @@
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETARCH
ARG RUSTFS_BUILD_FEATURES=""
# -----------------------------
# Build stage
# -----------------------------
FROM rust:1.91-trixie AS builder
FROM rust:1.95-trixie AS builder
# Re-declare args after FROM
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETARCH
ARG RUSTFS_BUILD_FEATURES
# Debug: print platforms
RUN echo "Build info -> BUILDPLATFORM=${BUILDPLATFORM}, TARGETPLATFORM=${TARGETPLATFORM}"
@@ -60,7 +64,15 @@ RUN set -eux; \
# Optional: cross toolchain for aarch64 (only when targeting linux/arm64)
RUN set -eux; \
if [ "${TARGETPLATFORM:-linux/amd64}" = "linux/arm64" ]; then \
target_platform="${TARGETPLATFORM:-}"; \
if [ -z "${target_platform}" ]; then \
case "$(uname -m)" in \
x86_64) target_platform="linux/amd64" ;; \
aarch64|arm64) target_platform="linux/arm64" ;; \
*) target_platform="linux/amd64" ;; \
esac; \
fi; \
if [ "${target_platform}" = "linux/arm64" ]; then \
export DEBIAN_FRONTEND=noninteractive; \
apt-get update; \
apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu; \
@@ -79,6 +91,7 @@ ENV CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc
ENV CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc
ENV CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++
ENV CARGO_TARGET_DIR=/usr/src/rustfs/target/docker-build
WORKDIR /usr/src/rustfs
@@ -112,27 +125,44 @@ ENV CARGO_NET_GIT_FETCH_WITH_CLI=true \
# Generate protobuf/flatbuffers code (uses protoc/flatc from distro)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/usr/src/rustfs/target \
--mount=type=cache,target=/usr/src/rustfs/target/docker-build \
cargo run --bin gproto
# Build RustFS (target depends on TARGETPLATFORM)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/usr/src/rustfs/target \
--mount=type=cache,target=/usr/src/rustfs/target/docker-build \
set -eux; \
case "${TARGETPLATFORM:-linux/amd64}" in \
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; \
target_platform="${TARGETPLATFORM:-}"; \
if [ -z "${target_platform}" ]; then \
case "${TARGETARCH:-$(uname -m)}" in \
amd64|x86_64) target_platform="linux/amd64" ;; \
arm64|aarch64) target_platform="linux/arm64" ;; \
*) echo "Unsupported target architecture: ${TARGETARCH:-$(uname -m)}" >&2; exit 1 ;; \
esac; \
fi; \
case "${target_platform}" in \
linux/amd64) \
echo "Building for x86_64-unknown-linux-gnu"; \
if [ -n "${RUSTFS_BUILD_FEATURES}" ]; then \
cargo build --release --locked --target x86_64-unknown-linux-gnu --bin rustfs --features "${RUSTFS_BUILD_FEATURES}" -j "$(nproc)"; \
else \
cargo build --release --locked --target x86_64-unknown-linux-gnu --bin rustfs -j "$(nproc)"; \
install -m 0755 target/x86_64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \
fi; \
install -m 0755 "${CARGO_TARGET_DIR}/x86_64-unknown-linux-gnu/release/rustfs" /usr/local/bin/rustfs \
;; \
linux/arm64) \
echo "Building for aarch64-unknown-linux-gnu"; \
if [ -n "${RUSTFS_BUILD_FEATURES}" ]; then \
cargo build --release --locked --target aarch64-unknown-linux-gnu --bin rustfs --features "${RUSTFS_BUILD_FEATURES}" -j "$(nproc)"; \
else \
cargo build --release --locked --target aarch64-unknown-linux-gnu --bin rustfs -j "$(nproc)"; \
install -m 0755 target/aarch64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \
fi; \
install -m 0755 "${CARGO_TARGET_DIR}/aarch64-unknown-linux-gnu/release/rustfs" /usr/local/bin/rustfs \
;; \
*) \
echo "Unsupported TARGETPLATFORM=${TARGETPLATFORM}" >&2; exit 1 \
echo "Unsupported target platform=${target_platform}" >&2; exit 1 \
;; \
esac
@@ -178,7 +208,7 @@ CMD ["cargo", "run", "--bin", "rustfs", "--"]
# -----------------------------
# Runtime stage (Ubuntu minimal)
# -----------------------------
FROM ubuntu:22.04
FROM ubuntu:24.04
ARG BUILD_DATE
ARG VCS_REF
@@ -195,6 +225,7 @@ RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
tzdata \
coreutils; \
rm -rf /var/lib/apt/lists/*
+2 -2
View File
@@ -66,7 +66,8 @@ How to use me:
make fmt # Format code
make clippy # Run clippy checks
make test # Run tests
make pre-commit # Run all pre-commit checks
make pre-commit # Run fast pre-commit checks
make pre-pr # Run full pre-PR checks
🚀 Quick Start:
make build # Build RustFS binary
@@ -78,4 +79,3 @@ endef
export HEADER
-include $(addsuffix /*.mak, $(shell find .config/make -type d))
+52 -9
View File
@@ -7,6 +7,7 @@
<a href="https://github.com/rustfs/rustfs/actions/workflows/docker.yml"><img alt="Build and Push Docker Images" src="https://github.com/rustfs/rustfs/actions/workflows/docker.yml/badge.svg" /></a>
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/rustfs/rustfs"/>
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/rustfs/rustfs"/>
<a href="https://discord.gg/NcKBCEJp6P"><img alt="Discord" src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white" /></a>
<a href="https://hellogithub.com/repository/rustfs/rustfs" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=b95bcb72bdc340b68f16fdf6790b7d5b&claim_uid=MsbvjYeLDKAH457&theme=small" alt="FeaturedHelloGitHub" /></a>
</p>
@@ -33,7 +34,7 @@ English | <a href="https://github.com/rustfs/rustfs/blob/main/README_ZH.md">简
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ru">Русский</a>
</p>
RustFS is a high-performance, distributed object storage system built in Rust—one of the most loved programming languages worldwide. RustFS combines the simplicity of MinIO with the memory safety and raw performance of Rust. It offers full S3 compatibility, is completely open-source, and is optimized for data lakes, AI, and big data workloads.
RustFS is a high-performance, distributed object storage system built in Rust—one of the most loved programming languages worldwide. RustFS combines the simplicity of MinIO with the memory safety and raw performance of Rust. It offers broad S3 API compatibility for supported features, is completely open-source, and is optimized for data lakes, AI, and big data workloads.
Unlike other storage systems, RustFS is released under the permissible Apache 2.0 license, avoiding the restrictions of AGPL. With Rust as its foundation, RustFS delivers superior speed and secure distributed features for next-generation object storage.
@@ -41,7 +42,7 @@ Unlike other storage systems, RustFS is released under the permissible Apache 2.
- **High Performance**: Built with Rust to ensure maximum speed and resource efficiency.
- **Distributed Architecture**: Scalable and fault-tolerant design suitable for large-scale deployments.
- **S3 Compatibility**: Seamless integration with existing S3-compatible applications and tools.
- **S3 Compatibility**: Seamless integration with common S3-compatible applications and tools; current coverage is tracked in the [S3 compatibility matrix](docs/architecture/s3-compatibility-matrix.md).
- **OpenStack Swift API**: Native support for Swift protocol with Keystone authentication.
- **OpenStack Keystone Integration**: Native support for OpenStack Keystone authentication with X-Auth-Token headers.
- **Data Lake Support**: Optimized for high-throughput big data and AI workloads.
@@ -80,7 +81,7 @@ Unlike other storage systems, RustFS is released under the permissible Apache 2.
| **Language & Safety** | **Rust-based**<br>Memory safety by design. | **Go or C-based**<br>Potential for memory GC pauses or leaks. |
| **Data Sovereignty** | **No Telemetry / Full Compliance**<br>Guards against unauthorized cross-border data egress. Compliant with GDPR (EU/UK), CCPA (US), and APPI (Japan). | **Potential Risk**<br>Possible legal exposure and unwanted data telemetry. |
| **Licensing** | **Permissive Apache 2.0**<br>Business-friendly, no "poison pill" clauses. | **Restrictive AGPL v3**<br>Risk of license traps and intellectual property pollution. |
| **Compatibility** | **100% S3 Compatible**<br>Works with any cloud provider or client, anywhere. | **Variable Compatibility**<br>May lack support for local cloud vendors or specific APIs. |
| **Compatibility** | **S3-Compatible Core**<br>Works with common S3-compatible clients, with coverage tracked in the compatibility matrix. | **Variable Compatibility**<br>May lack support for local cloud vendors or specific APIs. |
| **Edge & IoT** | **Strong Edge Support**<br>Ideal for secure, innovative edge devices. | **Weak Edge Support**<br>Often too heavy for edge gateways. |
| **Risk Profile** | **Enterprise Risk Mitigation**<br>Clear IP rights and safe for commercial use. | **Legal Risks**<br>Intellectual property ambiguity and usage restrictions. |
@@ -102,7 +103,7 @@ curl -O https://rustfs.com/install_rustfs.sh && bash install_rustfs.sh
### 2. Docker Quick Start (Option 2)
The RustFS container runs as a non-root user `rustfs` (UID `10001`). If you run Docker with `-v` to mount a host directory, please ensure the host directory owner is set to `10001`, otherwise you will encounter permission denied errors.
The RustFS container runs as a non-root user `rustfs` (UID/GID `10001:10001`). If you bind-mount host directories with Docker or Compose, every mounted path must be writable by that user, otherwise startup may fail with permission denied errors. This applies to data directories, log directories, and TLS certificate directories when `RUSTFS_TLS_PATH` is enabled.
```bash
# Create data and logs directories
@@ -115,27 +116,62 @@ chown -R 10001:10001 data logs
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
# Using specific version
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:1.0.0-alpha.76
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:1.0.0-beta.8
```
If you use [podman](https://github.com/containers/podman) instead of docker, you can install the RustFS with the below command
```bash
podman run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
# Create data and logs directories
mkdir -p data logs
# Run the container (podman will automatically set the folders ownership)
podman run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data:Z,U -v $(pwd)/logs:/logs:Z,U rustfs/rustfs:latest
```
You can also use Docker Compose. Using the `docker-compose.yml` file in the root directory:
If you enable TLS with a bind-mounted certificate directory, prepare that mount the same way:
```bash
docker compose --profile observability up -d
mkdir -p certs
chown -R 10001:10001 certs
```
You can also use Docker Compose. Using the `docker-compose-simple.yml` file in the root directory:
```bash
docker compose -f docker-compose-simple.yml up -d
```
Before running Compose with host bind mounts:
- Ensure every mounted host path is writable by `10001:10001`.
- If you enable TLS, ensure the certificate mount for `/opt/tls` is also readable by `10001:10001`.
- If matching host ownership is not practical, run the `rustfs` service with `user: "<host-uid>:<host-gid>"` instead.
- `docker-compose-simple.yml` includes a `volume-permission-helper` service for named volumes. `docker-compose-simple.yml` relies on you to prepare bind-mounted host paths in advance.
Similarly, you can run the command with podman
```bash
podman compose --profile observability up -d
podman compose -f docker-compose-simple.yml up -d
```
Webhook notification quick start (Docker):
```bash
docker run -d --name rustfs -p 9000:9000 \
-e RUSTFS_NOTIFY_ENABLE=true \
-e RUSTFS_NOTIFY_WEBHOOK_ENABLE_PRIMARY=on \
-e RUSTFS_NOTIFY_WEBHOOK_ENDPOINT_PRIMARY=http://<host-ip>:3020/webhook \
-e RUSTFS_NOTIFY_WEBHOOK_QUEUE_DIR_PRIMARY=/tmp/rustfs-events \
rustfs/rustfs:latest
```
Notes:
- `RUSTFS_NOTIFY_ENABLE=true` enables the global notify module switch.
- For ARN `arn:rustfs:sqs::primary:webhook`, use instance-scoped env vars with `_PRIMARY`.
- If queue dir is omitted, default is `/opt/rustfs/events`; ensure it is writable by the container runtime user.
- `RUSTFS_NOTIFY_WEBHOOK_SKIP_TLS_VERIFY_PRIMARY` defaults to `false`; enabling it skips webhook TLS certificate verification, allows MITM attacks, and emits a startup warning. Prefer `RUSTFS_NOTIFY_WEBHOOK_CLIENT_CA_PRIMARY` for private CAs.
**NOTE**: We recommend reviewing the `docker-compose.yml` file before running. It defines several services including Grafana, Prometheus, and Jaeger, which are helpful for RustFS observability. If you wish to start Redis or Nginx containers, you can specify the corresponding profiles.
### 3\. Build from Source (Option 3) - Advanced Users
@@ -178,6 +214,12 @@ make help-docker # Show all Docker-related commands
Follow the instructions in the [Helm Chart README](https://charts.rustfs.com/) to install RustFS on a Kubernetes cluster.
For scanner pacing, cycle budgets, bitrot cadence, lifecycle transition status,
and single-node single-disk idle CPU tuning, see
[Scanner Runtime Controls](docs/operations/scanner-runtime-controls.md). For
repeatable scanner-pressure validation, see
[Scanner Benchmark Runbook](docs/operations/scanner-benchmark-runbook.md).
### 5\. Nix Flake (Option 5)
If you have [Nix with flakes enabled](https://nixos.wiki/wiki/Flakes#Enable_flakes):
@@ -278,6 +320,7 @@ If you have any questions or need assistance:
- [Documentation](https://docs.rustfs.com) - The manual you should read
- [Changelog](https://github.com/rustfs/rustfs/releases) - What we broke and fixed
- [GitHub Discussions](https://github.com/rustfs/rustfs/discussions) - Where the community lives
- [Discord](https://discord.gg/NcKBCEJp6P) - Chat with the RustFS community
## Contact
+28 -16
View File
@@ -1,4 +1,4 @@
[![RustFS](https://github.com/user-attachments/assets/1b5afcd6-a2c3-47ff-8bc3-ce882b0ddca7)](https://rustfs.com.cn)
[![RustFS](https://repository-images.githubusercontent.com/722597620/0fa936a2-8164-4f53-867f-def4beb64b21)](https://rustfs.com.cn)
<p align="center">RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。</p>
@@ -34,7 +34,7 @@
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ru">Русский</a>
</p>
RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。Rust 是全球最受开发者喜爱的编程语言之一,RustFS 完美结合了 MinIO 的简洁性与 Rust 的内存安全及高性能优势。它提供完整的 S3 兼容性,完全开源,并专为数据湖、人工智能(AI)和大数据负载进行了优化。
RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。Rust 是全球最受开发者喜爱的编程语言之一,RustFS 结合了 MinIO 的简洁性与 Rust 的内存安全及高性能优势。它为已支持功能提供广泛的 S3 API 兼容性,完全开源,并专为数据湖、人工智能(AI)和大数据负载进行了优化。
与其他存储系统不同,RustFS 采用更宽松、商业友好的 Apache 2.0 许可证,避免了 AGPL 协议的限制。以 Rust 为基石,RustFS 为下一代对象存储提供了更快的速度和更安全的分布式特性。
@@ -42,7 +42,7 @@ RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。Rust
- **高性能**:基于 Rust 构建,确保极致的速度和资源效率。
- **分布式架构**:可扩展且容错的设计,适用于大规模部署。
- **S3 兼容性**:与现有的 S3 兼容应用和工具无缝集成。
- **S3 兼容性**:与常见 S3 兼容应用和工具集成,当前覆盖范围见 [S3 兼容矩阵](docs/architecture/s3-compatibility-matrix.md)
- **数据湖支持**:专为高吞吐量的大数据和 AI 工作负载优化。
- **完全开源**:采用 Apache 2.0 许可证,鼓励社区贡献和商业使用。
- **简单易用**:设计简洁,易于部署和管理。
@@ -77,7 +77,7 @@ RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。Rust
| **语言与安全** | **基于 Rust 开发**<br>天生的内存安全。 | **基于 Go 或 C 开发**<br>存在内存 GC 停顿或内存泄漏的潜在风险。 |
| **数据主权** | **无遥测 / 完全合规**<br>防止未经授权的数据跨境传输。完全符合 GDPR (欧盟/英国)、CCPA (美国) 和 APPI (日本) 等法规。 | **潜在风险**<br>可能存在法律风险和隐蔽的数据遥测(Telemetry)。 |
| **开源协议** | **宽松的 Apache 2.0**<br>商业友好,无“毒丸”条款。 | **受限的 AGPL v3**<br>存在许可证陷阱和知识产权污染的风险。 |
| **兼容性** | **100% S3 兼容**<br>适用于任何云提供商和客户端,随处运行。 | **兼容性不一**<br>虽然支持 S3,但可能缺乏对本地云厂商或特定 API 的支持。 |
| **兼容性** | **S3 核心兼容**<br>适配常见 S3 兼容客户端,覆盖范围以兼容矩阵为准。 | **兼容性不一**<br>虽然支持 S3,但可能缺乏对本地云厂商或特定 API 的支持。 |
| **边缘与 IoT** | **强大的边缘支持**<br>非常适合安全、创新的边缘设备。 | **边缘支持较弱**<br>对于边缘网关来说通常过于沉重。 |
| **成本** | **稳定且免费**<br>免费社区支持,稳定的商业定价。 | **高昂成本**<br>1PiB 的成本可能高达 250,000 美元。 |
| **风险控制** | **企业级风险规避**<br>清晰的知识产权,商业使用安全无忧。 | **法律风险**<br>知识产权归属模糊及使用限制风险。 |
@@ -100,29 +100,41 @@ curl -O https://rustfs.com/install_rustfs.sh && bash install_rustfs.sh
### 2\. Docker 快速启动 (选项 2)
RustFS 容器以非 root 用户 `rustfs` (UID `10001`) 运行。如果您使用 Docker `-v` 参数挂载宿主机目录,请务必确保宿主机目录的所有者已更改为 `10001`,否则会遇到权限拒绝错误
RustFS 容器以非 root 用户 `rustfs` (UID/GID `10001:10001`) 运行。如果您通过 Docker 或 Compose 绑定挂载宿主机目录,请务必确保所有挂载路径都对该用户可写,否则启动时可能出现权限拒绝错误。这不仅适用于数据目录和日志目录,也适用于启用 `RUSTFS_TLS_PATH` 时挂载的 TLS 证书目录
```bash
# 创建数据和日志目录
mkdir -p data logs
# 创建数据和日志目录
mkdir -p data logs
# 更改这两个目录的所有者
chown -R 10001:10001 data logs
# 更改这两个目录的所有者
chown -R 10001:10001 data logs
# 使用最新版本运行
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
# 使用最新版本运行
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
# 使用指定版本运行
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:1.0.0.alpha.68
# 使用指定版本运行
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:1.0.0-beta.8
```
您也可以使用 Docker Compose。使用根目录下的 `docker-compose.yml` 文件
如果您通过绑定挂载启用 TLS 证书目录,也请用同样方式准备该目录
```bash
docker compose --profile observability up -d
mkdir -p certs
chown -R 10001:10001 certs
```
**注意**: 我们建议您在运行前查看 `docker-compose.yml` 文件。该文件定义了包括 Grafana、Prometheus 和 Jaeger 在内的多个服务,有助于 RustFS 的可观测性监控。如果您还想启动 Redis 或 Nginx 容器,可以指定相应的 profile。
您也可以使用 Docker Compose。使用根目录下的 `docker-compose-simple.yml` 文件
```bash
docker compose -f docker-compose-simple.yml up -d
```
在使用 Compose 且包含宿主机绑定挂载时,请先确认:
- 所有挂载到容器内的宿主机路径都对 `10001:10001` 可写。
- 如果启用了 TLS,挂载到 `/opt/tls` 的证书目录也需要对 `10001:10001` 可读。
- 如果不方便调整宿主机目录归属,可以为 `rustfs` 服务显式设置 `user: "<host-uid>:<host-gid>"`
- `docker-compose-simple.yml` 为命名 volume 提供了 `volume-permission-helper` 服务;`docker-compose-simple.yml` 使用宿主机绑定挂载,因此需要您在启动前自行准备目录权限。
### 3\. 源码编译 (选项 3) - 进阶用户
+23 -1
View File
@@ -109,6 +109,7 @@ FORCE_CONSOLE_UPDATE=false
CONSOLE_VERSION="latest"
SKIP_VERIFICATION=false
CUSTOM_PLATFORM=""
FEATURES=""
# Print usage
usage() {
@@ -141,6 +142,7 @@ usage() {
echo " --no-console Skip console static assets"
echo " --force-console-update Force update console assets even if they exist"
echo " --console-version VERSION Console version to download (default: latest)"
echo " -f, --features FEATURES Cargo features to enable (e.g. 'webdav', 'full')"
echo " --skip-verification Skip binary verification after build"
echo " -h, --help Show this help message"
echo ""
@@ -148,6 +150,8 @@ usage() {
echo " $0 # Build for current platform (includes console assets)"
echo " $0 --dev # Development build"
echo " $0 --sign # Build and sign binary (release CI)"
echo " $0 --features webdav # Build with WebDAV support"
echo " $0 --features full # Build with all protocol features"
echo " $0 --no-console # Build without console static assets"
echo " $0 --force-console-update # Force update console assets"
echo " $0 --platform x86_64-unknown-linux-musl # Build for specific platform"
@@ -213,7 +217,7 @@ setup_rust_environment() {
# Set up environment variables for musl targets
if [[ "$PLATFORM" == *"musl"* ]]; then
print_message $YELLOW "Setting up environment for musl target..."
export RUSTFLAGS="'--cfg tokio_unstable -C target-feature=-crt-static'"
export RUSTFLAGS="--cfg tokio_unstable -C target-feature=-crt-static"
# For cargo-zigbuild, set up additional environment variables
if command -v cargo-zigbuild &> /dev/null; then
@@ -440,6 +444,12 @@ build_binary() {
build_cmd+=" --target $PLATFORM"
build_cmd+=" -p rustfs --bins"
if [ -n "$FEATURES" ]; then
local quoted_features
printf -v quoted_features '%q' "$FEATURES"
build_cmd+=" --features $quoted_features"
fi
print_message $BLUE "📦 Executing: $build_cmd"
# Execute build (this matches exactly what the working version does)
@@ -497,6 +507,9 @@ build_rustfs() {
print_message $YELLOW " Force Console Update: $FORCE_CONSOLE_UPDATE"
fi
print_message $YELLOW " Skip Verification: $SKIP_VERIFICATION"
if [ -n "$FEATURES" ]; then
print_message $YELLOW " Features: $FEATURES"
fi
echo ""
# Setup environment
@@ -565,6 +578,15 @@ while [[ $# -gt 0 ]]; do
SKIP_VERIFICATION=true
shift
;;
-f|--features)
if [ -z "${2:-}" ]; then
print_message $RED "❌ Missing value for $1"
usage
exit 1
fi
FEATURES="$2"
shift 2
;;
-h|--help)
usage
exit 0
+29
View File
@@ -8,11 +8,40 @@ Applies to all paths under `crates/`.
- Prefer `thiserror` for library-facing error types.
- Do not use `unwrap()`, `expect()`, or panic-driven control flow outside tests.
## Error Type Design
- Public API functions must return a typed error enum (preferably `thiserror`-derived), never `Result<_, String>`.
- Do not use `Box<dyn Error>` or `Box<dyn Error + Send + Sync>` in public trait methods or struct methods. Define a concrete error type with specific variants.
- When implementing `std::error::Error`, always override `fn source()` if you store an inner error. Breaking the error chain makes debugging impossible.
- Internal helpers that return `Result<_, String>` and are immediately wrapped via `.map_err(Error::other)` should return the actual error type directly.
## Concurrency
- Document lock acquisition order when a module uses multiple locks. Never acquire the same set of locks in different orders across code paths.
- Never hold a `tokio::sync::RwLock`/`Mutex` write guard across `.await` points unless the critical section is unavoidably async and the hold time is bounded.
- Prefer `compare_exchange` loops over load-then-store for concurrent counters (peak values, adaptive heuristics).
- When resetting multi-field atomic statistics, use a version/sequence counter or accept that concurrent readers may see partial snapshots; document the tradeoff.
- `std::sync::Mutex` is acceptable in async context only when held for a brief, non-`await`-containing critical section. If in doubt, use `tokio::sync::Mutex`.
## Recursion Safety
- Recursive tree/graph traversals must have a depth limit (e.g., `max_depth` counter) or use an iterative approach with an explicit `Vec` stack.
- This applies to cache trees, directory walks, and any user-influenced hierarchy.
- A corrupted or malicious input must not be able to overflow the thread stack.
## Type Casting
- Never use `as` for numeric conversions that may truncate or overflow. Use `try_into()` with explicit error handling, or clamp with `value.max(0) as usize` when the domain is bounded.
- `f64 as usize` saturates but is fragile; clamp to `[0, usize::MAX as f64]` first.
- Treat every `as` cast in a PR review as a potential bug; require justification.
## Testing
- Keep unit tests close to the module they test.
- Keep integration tests under each crate's `tests/` directory.
- Add regression tests for bug fixes and behavior changes.
- Every test function must contain at least one `assert!`/`assert_eq!`/`assert_matches!`. A test that only calls code without asserting is not a test.
- In tests, prefer `.expect("context: what was being tested")` over bare `.unwrap()`. A test failure should tell you which operation failed and with what input.
## Async and Performance
-37
View File
@@ -1,37 +0,0 @@
[![RustFS](https://rustfs.com/images/rustfs-github.png)](https://rustfs.com)
# RustFS AppAuth - Application Authentication
<p align="center">
<strong>Application-level authentication and authorization module for RustFS distributed object storage</strong>
</p>
<p align="center">
<a href="https://github.com/rustfs/rustfs/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/rustfs/rustfs/actions/workflows/ci.yml/badge.svg" /></a>
<a href="https://docs.rustfs.com/">📖 Documentation</a>
· <a href="https://github.com/rustfs/rustfs/issues">🐛 Bug Reports</a>
· <a href="https://github.com/rustfs/rustfs/discussions">💬 Discussions</a>
</p>
---
## 📖 Overview
**RustFS AppAuth** provides application-level authentication and authorization capabilities for the [RustFS](https://rustfs.com) distributed object storage system. For the complete RustFS experience, please visit the [main RustFS repository](https://github.com/rustfs/rustfs).
## ✨ Features
- JWT-based authentication with secure token management
- RBAC (Role-Based Access Control) for fine-grained permissions
- Multi-tenant application isolation and management
- OAuth 2.0 and OpenID Connect integration
- API key management and rotation
- Session management with configurable expiration
## 📚 Documentation
For comprehensive documentation, examples, and usage guides, please visit the main [RustFS repository](https://github.com/rustfs/rustfs).
## 📄 License
This project is licensed under the Apache License 2.0 - see the [LICENSE](../../LICENSE) file for details.

Some files were not shown because too many files have changed in this diff Show More