diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md
index ddfdaaaa2..35768119f 100644
--- a/docs/architecture/migration-progress.md
+++ b/docs/architecture/migration-progress.md
@@ -5,13 +5,14 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
## Current Context
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
-- Branch: `overtrue/arch-admin-replication-stats-context`
-- Baseline: completed `C-011/C-012/C-013/API-055/API-059/API-079/API-080/API-081/API-082/API-083/API-084/API-085/API-086/API-087/API-088/API-089/API-090/API-091/API-092/API-093/API-094/API-095/API-096/API-097/API-098/API-099/API-100/API-101/API-102/API-103/API-104/API-105/API-106/API-107/API-108/API-109/API-110/API-111/API-112/API-113/API-114/API-115/API-116/API-117/API-118/API-119/API-120/API-121/API-122/API-123/API-124/API-125/API-126/API-127/API-128/API-129/API-130/API-131/API-132/API-133/API-134/API-135/API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165`.
-- Based on: API-164/API-165 local branch stacked on API-163 PR #3777.
+- Branch: `overtrue/arch-admin-status-metrics-context`
+- Baseline: completed `C-011/C-012/C-013/API-055/API-059/API-079/API-080/API-081/API-082/API-083/API-084/API-085/API-086/API-087/API-088/API-089/API-090/API-091/API-092/API-093/API-094/API-095/API-096/API-097/API-098/API-099/API-100/API-101/API-102/API-103/API-104/API-105/API-106/API-107/API-108/API-109/API-110/API-111/API-112/API-113/API-114/API-115/API-116/API-117/API-118/API-119/API-120/API-121/API-122/API-123/API-124/API-125/API-126/API-127/API-128/API-129/API-130/API-131/API-132/API-133/API-134/API-135/API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165/API-166`.
+- Based on: API-166 local branch stacked on API-164/API-165 local branch after API-163 PR #3777 merged.
- PR type for this branch: `consumer-migration`
- Runtime behavior changes: none.
-- Rust code changes: route admin replication stats reads through AppContext
- resolvers with legacy global fallback.
+- Rust code changes: route admin status and metrics reads for boot time, tier
+ transition stats, and scanner metrics through AppContext resolvers with
+ legacy global fallback.
- CI/script changes: lock completed owner and test/fuzz boundaries against
bare/glob imports, scattered raw ECStore facade subpaths, and startup
runtime/root-server/table/S3/app shared/app bucket/app ECStore/admin facade
@@ -20,7 +21,7 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
and storage owner thin bridge regressions, plus app context and notify
event-bridge thin module regressions; accept the reviewed AppContext resolver
reverse dependencies in the layer baseline.
-- Docs changes: record the API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165/API-166 owner facade cleanup.
+- Docs changes: record the API-136/API-137/API-138/API-139/API-140/API-141/API-142/API-143/API-144/API-145/API-146/API-147/API-148/API-149/API-150/API-151/API-152/API-153/API-154/API-155/API-156/API-157/API-158/API-159/API-160/API-161/API-162/API-163/API-164/API-165/API-166/API-167 owner facade cleanup.
## Phase 0 Tasks
@@ -4401,6 +4402,21 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
stats global-read scan, Rust risk scan, branch freshness check, and
three-expert review.
+- [x] `API-167` Route admin status and metrics reads through AppContext.
+ - Do: add AppContext interfaces for boot time, daily tier transition stats,
+ and scanner metrics report reads, then route admin replication uptime, tier
+ stats, and scanner status through those resolvers.
+ - Acceptance: admin production handlers no longer directly read
+ `GLOBAL_BOOT_TIME`, `GLOBAL_TransitionState`, or scanner `global_metrics`,
+ while AppContext default adapters keep the existing global fallback.
+ - Must preserve: replication metrics uptime defaults, tier stats filtering,
+ scanner status payload shape, scanner runtime-config reporting, and existing
+ storage owner global initialization.
+ - Verification: RustFS compile coverage, targeted context resolver tests,
+ migration guard, layer guard, formatting, diff hygiene, residual admin
+ status global-read scan, Rust risk scan, branch freshness check, and
+ three-expert review.
+
## Next PRs
1. `consumer-migration`: continue reducing direct global reads behind AppContext resolver boundaries.
@@ -4455,6 +4471,9 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| Quality/architecture | pass | API-166 keeps admin replication stats reads behind AppContext resolver boundaries with a storage-owner fallback wrapper. |
| Migration preservation | pass | Admin replication metrics, site-replication summaries, bandwidth enrichment, and missing-stats defaults are preserved. |
| Testing/verification | pass | RustFS focused compile, targeted context tests, formatting, migration/layer guards, diff hygiene, residual stats scan, and Rust risk scan passed for API-166. |
+| Quality/architecture | pass | API-167 keeps admin boot-time, tier-transition, and scanner metrics reads behind AppContext resolver boundaries. |
+| Migration preservation | pass | Replication uptime enrichment, tier stats filtering, scanner metrics JSON, and scanner runtime-config reporting are preserved. |
+| Testing/verification | pass | RustFS focused compile, targeted context tests, formatting, migration/layer guards, diff hygiene, residual admin status scan, and Rust risk scan passed for API-167. |
## Verification Notes
@@ -4505,6 +4524,22 @@ Passed before push:
- Rust risk scan: no new production unwrap/expect, panic/todo/unsafe, or cast
risks added.
+- Issue #660 API-167 current slice:
+ - `cargo check --tests -p rustfs`: passed.
+ - `cargo test -p rustfs resolver_helpers_are_context_first_and_fallback_when_context_is_absent --lib`:
+ passed.
+ - `cargo fmt --all`: passed.
+ - `cargo fmt --all --check`: passed.
+ - `git diff --check`: passed.
+ - `bash -n scripts/check_architecture_migration_rules.sh`: passed.
+ - `./scripts/check_architecture_migration_rules.sh`: passed.
+ - `./scripts/check_layer_dependencies.sh`: passed.
+ - AppContext admin status resolver scan: passed; direct admin production
+ `GLOBAL_BOOT_TIME`, `GLOBAL_TransitionState`, and scanner `global_metrics`
+ reads are removed.
+ - Rust risk scan: no new production unwrap/expect, panic/todo/unsafe, or cast
+ risks added.
+
- Issue #660 API-163 current slice:
- `cargo check --tests -p rustfs`: passed.
- `cargo test -p rustfs resolver_helpers_are_context_first_and_fallback_when_context_is_absent --lib`:
diff --git a/rustfs/src/admin/handlers/scanner.rs b/rustfs/src/admin/handlers/scanner.rs
index b6b8d149c..bcead3793 100644
--- a/rustfs/src/admin/handlers/scanner.rs
+++ b/rustfs/src/admin/handlers/scanner.rs
@@ -14,12 +14,13 @@
use crate::admin::auth::validate_admin_request;
use crate::admin::router::{AdminOperation, Operation, S3Router};
+use crate::app::context::resolve_scanner_metrics_report;
use crate::auth::{check_key_valid, get_session_token};
use crate::server::{ADMIN_PREFIX, RemoteAddr};
use http::{HeaderMap, HeaderValue};
use hyper::{Method, StatusCode};
use matchit::Params;
-use rustfs_common::metrics::{ScannerMetricsReport, global_metrics};
+use rustfs_common::metrics::ScannerMetricsReport;
use rustfs_credentials::Credentials;
use rustfs_policy::policy::action::{Action, AdminAction};
use s3s::header::CONTENT_TYPE;
@@ -84,7 +85,7 @@ impl Operation for ScannerStatusHandler {
async fn call(&self, req: S3Request
, _params: Params<'_, '_>) -> S3Result> {
let _cred = validate_scanner_status_request(&req).await?;
let response = ScannerStatusResponse {
- metrics: global_metrics().report().await,
+ metrics: resolve_scanner_metrics_report().await,
runtime_config: rustfs_scanner::scanner_runtime_config_status(),
};
let body = serde_json::to_vec(&response).map_err(|err| {
diff --git a/rustfs/src/admin/handlers/tier.rs b/rustfs/src/admin/handlers/tier.rs
index 3298f8197..0b06d7edd 100644
--- a/rustfs/src/admin/handlers/tier.rs
+++ b/rustfs/src/admin/handlers/tier.rs
@@ -13,7 +13,6 @@
// limitations under the License.
#![allow(unused_variables, unused_mut, unused_must_use)]
-use super::super::lifecycle::bucket_lifecycle_ops::GLOBAL_TransitionState;
use super::super::{
AdminError, DailyAllTierStats, ERR_TIER_ALREADY_EXISTS, ERR_TIER_BACKEND_IN_USE, ERR_TIER_BACKEND_NOT_EMPTY,
ERR_TIER_CONNECT_ERR, ERR_TIER_INVALID_CREDENTIALS, ERR_TIER_MISSING_CREDENTIALS, ERR_TIER_NAME_NOT_UPPERCASE,
@@ -24,7 +23,9 @@ use crate::{
auth::validate_admin_request,
router::{AdminOperation, Operation, S3Router},
},
- app::context::{resolve_notification_system, resolve_object_store_handle, resolve_tier_config_handle},
+ app::context::{
+ resolve_daily_tier_stats, resolve_notification_system, resolve_object_store_handle, resolve_tier_config_handle,
+ },
auth::{check_key_valid, get_session_token},
server::{ADMIN_PREFIX, RemoteAddr},
storage::request_context::spawn_traced,
@@ -748,7 +749,7 @@ impl Operation for GetTierInfo {
} else {
None
};
- let info = filter_tier_stats(GLOBAL_TransitionState.get_daily_all_tier_stats(), tier_name);
+ let info = filter_tier_stats(resolve_daily_tier_stats(), tier_name);
let data = serde_json::to_vec(&info)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("marshal tier err {e}")))?;
diff --git a/rustfs/src/admin/mod.rs b/rustfs/src/admin/mod.rs
index a62873c6d..ae7b6a2ab 100644
--- a/rustfs/src/admin/mod.rs
+++ b/rustfs/src/admin/mod.rs
@@ -135,10 +135,6 @@ mod ecstore_error {
pub(crate) use crate::storage::ecstore_error::StorageError;
}
-mod ecstore_global {
- pub(crate) use crate::storage::ecstore_global::GLOBAL_BOOT_TIME;
-}
-
#[allow(unused_imports)]
mod ecstore_layout {
pub(crate) use crate::storage::ecstore_layout::{EndpointServerPools, Endpoints, PoolEndpoints};
@@ -264,21 +260,6 @@ pub(crate) mod bucket_target_sys {
}
pub(crate) mod lifecycle {
- pub(crate) mod bucket_lifecycle_ops {
- use super::super::DailyAllTierStats;
-
- pub(crate) struct GlobalTransitionStateCompat;
-
- #[allow(non_upper_case_globals)]
- pub(crate) static GLOBAL_TransitionState: GlobalTransitionStateCompat = GlobalTransitionStateCompat;
-
- impl GlobalTransitionStateCompat {
- pub(crate) fn get_daily_all_tier_stats(&self) -> DailyAllTierStats {
- super::super::ecstore_bucket::lifecycle::bucket_lifecycle_ops::GLOBAL_TransitionState.get_daily_all_tier_stats()
- }
- }
- }
-
pub(crate) mod tier_last_day_stats {
#[cfg(test)]
pub(crate) type LastDayTierStats = super::super::ecstore_bucket::lifecycle::tier_last_day_stats::LastDayTierStats;
@@ -498,16 +479,6 @@ pub(crate) async fn collect_local_metrics(
ecstore_metrics::collect_local_metrics(types, opts).await
}
-pub(crate) struct BootTimeCompat;
-
-pub(crate) static GLOBAL_BOOT_TIME: BootTimeCompat = BootTimeCompat;
-
-impl BootTimeCompat {
- pub(crate) fn get(&self) -> Option<&'static std::time::SystemTime> {
- ecstore_global::GLOBAL_BOOT_TIME.get()
- }
-}
-
pub(crate) struct AdminErrorRef(fn() -> &'static AdminError);
impl Deref for AdminErrorRef {
diff --git a/rustfs/src/admin/router.rs b/rustfs/src/admin/router.rs
index 2d338689f..f8d240dc5 100644
--- a/rustfs/src/admin/router.rs
+++ b/rustfs/src/admin/router.rs
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use super::GLOBAL_BOOT_TIME;
use super::PeerRestClient;
use super::bandwidth::monitor::BandwidthDetails;
use super::bucket_target_sys::{BucketTargetSys, PutObjectOptions, RemoveObjectOptions, S3ClientError, TargetClient};
@@ -26,8 +25,9 @@ use super::{AdminReplicationConfigExt as _, AdminVersioningConfigExt as _};
use crate::admin::console::{is_console_path, make_console_server};
use crate::admin::handlers::oidc::is_oidc_path;
use crate::app::context::{
- resolve_bucket_monitor_handle, resolve_deployment_id, resolve_notification_system, resolve_object_store_handle,
- resolve_region, resolve_replication_pool_handle, resolve_replication_stats_handle, resolve_server_config,
+ resolve_boot_time, resolve_bucket_monitor_handle, resolve_deployment_id, resolve_notification_system,
+ resolve_object_store_handle, resolve_region, resolve_replication_pool_handle, resolve_replication_stats_handle,
+ resolve_server_config,
};
use crate::app::object_usecase::DefaultObjectUsecase;
use crate::auth::{check_key_valid, get_session_token};
@@ -1437,9 +1437,8 @@ async fn build_replication_metrics_response(bucket: &str, route: ReplicationExtR
}
fn replication_metrics_uptime_seconds() -> i64 {
- GLOBAL_BOOT_TIME
- .get()
- .and_then(|boot_time| SystemTime::now().duration_since(*boot_time).ok())
+ resolve_boot_time()
+ .and_then(|boot_time| SystemTime::now().duration_since(boot_time).ok())
.map(|uptime| uptime.as_secs() as i64)
.unwrap_or_default()
}
diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs
index b41ec7653..b6d16ee65 100644
--- a/rustfs/src/app/context.rs
+++ b/rustfs/src/app/context.rs
@@ -25,8 +25,10 @@ pub use global::*;
pub use handles::*;
pub use interfaces::*;
+use super::DailyAllTierStats;
use super::ECStore;
use super::EndpointServerPools;
+use super::ScannerMetricsReport;
use super::TierConfigMgr;
use super::metadata_sys::BucketMetadataSys;
use super::new_object_layer_fn;
@@ -38,7 +40,7 @@ use rustfs_iam::{store::object::ObjectStore, sys::IamSys};
use rustfs_kms::KmsServiceManager;
use rustfs_lock::LockClient;
use rustfs_tls_runtime::{GlobalPublishedOutboundTlsState, TlsGeneration};
-use std::{future::Future, sync::Arc};
+use std::{future::Future, sync::Arc, time::SystemTime};
use tokio::sync::RwLock;
/// Resolve KMS runtime service manager using AppContext-first precedence.
@@ -116,6 +118,22 @@ pub fn resolve_replication_stats_handle() -> Option> {
resolve_replication_stats_handle_with(get_global_app_context(), || default_replication_stats_interface().handle())
}
+/// Resolve boot time using AppContext-first precedence.
+pub fn resolve_boot_time() -> Option {
+ resolve_boot_time_with(get_global_app_context(), || default_boot_time_interface().get())
+}
+
+/// Resolve daily tier transition statistics using AppContext-first precedence.
+pub fn resolve_daily_tier_stats() -> DailyAllTierStats {
+ resolve_daily_tier_stats_with(get_global_app_context(), || default_tier_stats_interface().daily_all())
+}
+
+/// Resolve scanner metrics report using AppContext-first precedence.
+pub async fn resolve_scanner_metrics_report() -> ScannerMetricsReport {
+ resolve_scanner_metrics_report_with(get_global_app_context(), || async { default_scanner_metrics_interface().report().await })
+ .await
+}
+
/// Resolve deployment identity using AppContext-first precedence.
pub fn resolve_deployment_id() -> Option {
resolve_deployment_id_with(get_global_app_context(), || default_deployment_id_interface().get())
@@ -241,6 +259,29 @@ fn resolve_replication_stats_handle_with(
.or_else(fallback)
}
+fn resolve_boot_time_with(context: Option>, fallback: impl FnOnce() -> Option) -> Option {
+ context.and_then(|context| context.boot_time().get()).or_else(fallback)
+}
+
+fn resolve_daily_tier_stats_with(
+ context: Option>,
+ fallback: impl FnOnce() -> DailyAllTierStats,
+) -> DailyAllTierStats {
+ context.map_or_else(fallback, |context| context.tier_stats().daily_all())
+}
+
+async fn resolve_scanner_metrics_report_with(context: Option>, fallback: F) -> ScannerMetricsReport
+where
+ F: FnOnce() -> Fut,
+ Fut: Future