Commit Graph

4220 Commits

Author SHA1 Message Date
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