From 996e58fd9a591542244cc8c4fcde73f09756f56c Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Sat, 27 Jun 2026 13:10:00 +0800 Subject: [PATCH] refactor: route admin runtime config through app context (#3942) --- .../global-state-crate-split-plan.md | 3 + docs/architecture/migration-progress.md | 60 ++++++++++----- .../admin/handlers/audit_runtime_config.rs | 27 +++++-- rustfs/src/admin/handlers/config_admin.rs | 12 ++- rustfs/src/admin/handlers/kms_dynamic.rs | 9 ++- rustfs/src/admin/handlers/oidc.rs | 13 +++- rustfs/src/admin/runtime_sources.rs | 10 +-- rustfs/src/admin/service/config.rs | 74 +++++++++++++------ rustfs/src/app/context.rs | 12 +++ rustfs/src/runtime_sources.rs | 12 +-- 10 files changed, 166 insertions(+), 66 deletions(-) diff --git a/docs/architecture/global-state-crate-split-plan.md b/docs/architecture/global-state-crate-split-plan.md index 374775a22..74061ac7b 100644 --- a/docs/architecture/global-state-crate-split-plan.md +++ b/docs/architecture/global-state-crate-split-plan.md @@ -44,6 +44,9 @@ update this plan and the guard in the same reviewed migration PR. cover both paths. 2. Move one remaining consumer group at a time to explicit context or owner-local runtime-source handles. + - Current handoff: admin config, OIDC config, audit runtime config, and + dynamic KMS config storage reads/writes resolve through current + AppContext-aware boundaries before using the legacy object-store fallback. 3. Remove one fallback family per PR only after scans prove no production caller depends on it. 4. Keep embedded startup and tests working before deleting any fallback. diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 79d646ae2..50ed004c4 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,24 +5,24 @@ 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-global-runtime-boundary-batch` +- Branch: `overtrue/arch-global-fallback-removal-batch` - 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/API-167/API-168/API-169/API-170/API-171/API-172/API-173/API-174/API-175/API-176/API-177/API-178/API-179/API-180/API-181/API-182/API-183/API-184/API-185/API-186/API-187/API-188/API-189/API-190/API-191/API-192/API-193/API-194/API-195/API-196/API-197/API-198/API-199/API-200/API-201/API-202/API-203/API-204/API-205/API-206/API-207/API-208/API-209/API-210/API-211/API-212/API-213/API-214/API-215/API-216/API-217/API-218/API-219/API-220/API-221/API-222/API-223/API-224/API-225/API-226/API-227/API-228/API-229/API-230/API-231/API-232/API-233/API-234/API-235/API-236/API-237/API-238/API-239/API-240/API-241/API-242/API-243/API-244/API-245/API-246/API-247/API-248/API-249/API-250/API-251/API-252/API-253/API-254/CTX-002`. - Current baseline also includes API-255 from PR #3923, API-256 from PR #3925, CFG-009 from PR #3927, C-007/C-009 from PR #3935, C-008/C-010 from PR #3936, and DOC-001/DOC-002/DOC-003/DOC-004/DOC-005/ - TEST-DOC-001 from PR #3938, plus GLOB-001/GLOB-002/GLOB-003/GLOB-004/ - GLOB-005/GLOB-006/CRATE-001/CRATE-002 from PR #3939. -- Current phase PR: GLOB-006 ECStore runtime facade boundary batch. -- Based on: `origin/main` after PR #3939 merged. + TEST-DOC-001 from PR #3938, GLOB-001/GLOB-002/GLOB-003/GLOB-004/ + GLOB-005/CRATE-001/CRATE-002 from PR #3939, and GLOB-006 from PR #3941. +- Current phase PR: GLOB-007 admin runtime context handoff batch. +- Based on: `origin/main` after PR #3941 merged. - PR type for this branch: `ci-gate`. - Runtime behavior changes: none intended. -- Rust code changes: add the selected `rustfs_ecstore::api::runtime` facade and - route external crate-local `storage_api` boundaries away from direct - `rustfs_ecstore::api::global` imports. -- CI/script changes: shrink the direct ECStore global facade allowlist to the - root storage owner boundary. +- Rust code changes: add context-aware notification-system and server-config + resolution, then route admin config, OIDC config, audit runtime config, and + dynamic KMS config storage access through current AppContext-aware resolver + boundaries before falling back to legacy globals. +- CI/script changes: none intended. - Docs changes: update the global-state plan and this progress ledger for the - runtime facade batch. + admin runtime context handoff batch. ## Phase 0 Tasks @@ -2764,20 +2764,27 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block [`global-state-crate-split-plan.md`](global-state-crate-split-plan.md) to record the remaining owner boundaries and fallback removal sequence. - Verification: architecture migration guard and diff hygiene. -- [~] `GLOB-006` Shrink `ecstore::global`. - - Current slice: expose selected ECStore runtime-source reads through +- [x] `GLOB-006` Shrink `ecstore::global`. + - Completed slice: expose selected ECStore runtime-source reads through `rustfs_ecstore::api::runtime`, migrate heal, IAM, notify, observability, Swift, S3 Select, and scanner storage API boundaries away from direct `rustfs_ecstore::api::global` imports, and keep the direct global facade confined to the root storage owner boundary. - Remaining work: move additional ECStore bootstrap/runtime globals behind - explicit owner handles where safe; do not remove bootstrap state in this - runtime facade PR. + explicit owner handles where safe; do not remove bootstrap state until all + dependent runtime owners have explicit handles. - Verification: focused compile coverage, architecture migration guard, diff hygiene, Rust risk scan, and full PR gate. -- [ ] `GLOB-007` Remove fallbacks. +- [~] `GLOB-007` Remove fallbacks. + - Current slice: move admin config, OIDC config, audit runtime config, and + dynamic KMS config storage access paths to current AppContext-aware + resolver boundaries, and expose notification-system/server-config + resolution for explicit AppContext handoff. - Remaining work: remove one fallback family per PR only after scans prove no - production caller depends on it. + production caller depends on it; this slice keeps the fallback behavior + because broader admin/server consumers still use the root resolvers. + - Verification: focused RustFS compile, resolver residual scan, formatting, + diff hygiene, architecture guard, Rust risk scan, and full PR gate. - [~] `CRATE-001/CRATE-002` Evaluate future crate splits. - Current slice: record evidence required before `ecstore-erasure` or `storage-cluster` split proposals. @@ -6041,6 +6048,9 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Expert | Status | Notes | |---|---|---| +| Quality/architecture | pass | GLOB-007 adds AppContext-aware notification-system and server-config resolver boundaries, then routes admin config, OIDC config, audit runtime config, and dynamic KMS config storage access through current AppContext-aware owners before legacy fallbacks. | +| Migration preservation | pass | Existing admin config reload, OIDC restart detection, audit config writes, and dynamic KMS config load/save behavior stay unchanged because context lookup falls back to the same root object-store, notification, and server-config owners. | +| Testing/verification | pass | Focused RustFS compile/tests, resolver residual scan, formatting, architecture guard, diff hygiene, Rust risk scan, and full `make pre-pr` passed before PR. | | Quality/architecture | pass | GLOB-006 exposes selected ECStore runtime-source reads through `rustfs_ecstore::api::runtime` and removes external crate direct global facade imports while leaving ECStore owner state intact. | | Migration preservation | pass | Object-store, bucket-monitor, tier-config, erasure flags, IAM first-node, and local-disk-map reads delegate to the same ECStore runtime/global owners; startup, readiness, IAM/KMS, lock quorum, scanner, heal, Swift, S3 Select, and notification behavior stay unchanged. | | Testing/verification | pass | Focused compile, formatting, shell syntax, architecture guard, global facade scan, diff hygiene, Rust risk scan, full `make pre-pr`, and post-rebase focused checks passed before PR. | @@ -6398,6 +6408,22 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block Passed before push: +- Issue #660 GLOB-007 current slice: + - Branch freshness check: rebased onto `origin/main` after PR #3941 merged. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Resolver residual scan over migrated admin files: passed. + - Diff-added Rust risk scan: passed; no new production unwrap/expect, + unchecked narrow casts, stringly errors, or ad-hoc stdout/stderr. + - `cargo check -p rustfs --lib`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `cargo test -p rustfs --lib app::context::tests::resolver_helpers_are_context_first_and_fallback_when_context_is_absent`: + passed. + - `cargo test -p rustfs --lib admin::service::config`: passed. + - Three-expert review: passed. + - `make pre-pr`: passed, including 6911 nextest tests passed, 112 skipped, + and doctests. + - Issue #660 GLOB-001/GLOB-002/GLOB-003/GLOB-004/GLOB-005/GLOB-006/ CRATE-001/CRATE-002 current slice: - Branch freshness check: rebased onto `origin/main` after PR #3938 merged. diff --git a/rustfs/src/admin/handlers/audit_runtime_config.rs b/rustfs/src/admin/handlers/audit_runtime_config.rs index 7c7c94cdf..679a913d3 100644 --- a/rustfs/src/admin/handlers/audit_runtime_config.rs +++ b/rustfs/src/admin/handlers/audit_runtime_config.rs @@ -13,15 +13,15 @@ // limitations under the License. use crate::admin::handlers::target_descriptor::AdminTargetSpec; -use crate::admin::runtime_sources::resolve_object_store_handle; +use crate::admin::runtime_sources::{AppContext, current_app_context, resolve_object_store_handle_for_context}; use crate::admin::storage_api::config::{read_admin_config_without_migrate, save_admin_server_config}; use rustfs_audit::{audit_system, start_audit_system as start_global_audit_system, system::AuditSystemState}; use rustfs_config::DEFAULT_DELIMITER; use rustfs_config::server_config::Config; use s3s::{S3Result, s3_error}; -pub(crate) async fn load_server_config_from_store() -> S3Result { - let Some(store) = resolve_object_store_handle() else { +pub(crate) async fn load_server_config_from_store_for_context(context: Option<&AppContext>) -> S3Result { + let Some(store) = resolve_object_store_handle_for_context(context) else { return Ok(Config::new()); }; @@ -30,6 +30,11 @@ pub(crate) async fn load_server_config_from_store() -> S3Result { .map_err(|e| s3_error!(InternalError, "failed to read server config: {}", e)) } +pub(crate) async fn load_server_config_from_store() -> S3Result { + let context = current_app_context(); + load_server_config_from_store_for_context(context.as_deref()).await +} + fn has_any_audit_targets(specs: &[AdminTargetSpec], config: &Config) -> bool { specs.iter().any(|spec| { config @@ -75,11 +80,15 @@ pub(crate) async fn apply_audit_runtime_config(specs: &[AdminTargetSpec], config Ok(()) } -pub(crate) async fn update_audit_config_and_reload(specs: &[AdminTargetSpec], mut modifier: F) -> S3Result<()> +async fn update_audit_config_and_reload_for_context( + context: Option<&AppContext>, + specs: &[AdminTargetSpec], + mut modifier: F, +) -> S3Result<()> where F: FnMut(&mut Config) -> bool, { - let Some(store) = resolve_object_store_handle() else { + let Some(store) = resolve_object_store_handle_for_context(context) else { return Err(s3_error!(InternalError, "server storage not initialized")); }; @@ -98,6 +107,14 @@ where apply_audit_runtime_config(specs, config).await } +pub(crate) async fn update_audit_config_and_reload(specs: &[AdminTargetSpec], modifier: F) -> S3Result<()> +where + F: FnMut(&mut Config) -> bool, +{ + let context = current_app_context(); + update_audit_config_and_reload_for_context(context.as_deref(), specs, modifier).await +} + pub(crate) async fn set_audit_target_config( specs: &[AdminTargetSpec], subsystem: &str, diff --git a/rustfs/src/admin/handlers/config_admin.rs b/rustfs/src/admin/handlers/config_admin.rs index 18a7c0352..dbafc7d7c 100644 --- a/rustfs/src/admin/handlers/config_admin.rs +++ b/rustfs/src/admin/handlers/config_admin.rs @@ -14,7 +14,9 @@ use crate::admin::auth::validate_admin_request; use crate::admin::router::{AdminOperation, Operation, S3Router}; -use crate::admin::runtime_sources::{publish_server_config, resolve_object_store_handle, resolve_server_config}; +use crate::admin::runtime_sources::{ + current_app_context, publish_server_config, resolve_object_store_handle_for_context, resolve_server_config_for_context, +}; use crate::admin::service::config::{ apply_dynamic_config_for_subsystem, is_dynamic_config_subsystem, signal_config_snapshot_reload, signal_dynamic_config_reload, validate_server_config, @@ -709,7 +711,9 @@ fn success_response(config_applied: bool) -> S3Result S3Result> { - resolve_object_store_handle().ok_or_else(|| s3_error!(InternalError, "server storage not initialized")) + let context = current_app_context(); + resolve_object_store_handle_for_context(context.as_deref()) + .ok_or_else(|| s3_error!(InternalError, "server storage not initialized")) } async fn load_server_config_from_store() -> S3Result { @@ -725,7 +729,9 @@ async fn load_active_server_config() -> S3Result { return Ok(config); } - resolve_server_config().ok_or_else(|| s3_error!(InternalError, "server config is not initialized")) + let context = current_app_context(); + resolve_server_config_for_context(context.as_deref()) + .ok_or_else(|| s3_error!(InternalError, "server config is not initialized")) } async fn save_server_config_to_store(config: &ServerConfig) -> S3Result<()> { diff --git a/rustfs/src/admin/handlers/kms_dynamic.rs b/rustfs/src/admin/handlers/kms_dynamic.rs index 659453e7c..a61f05611 100644 --- a/rustfs/src/admin/handlers/kms_dynamic.rs +++ b/rustfs/src/admin/handlers/kms_dynamic.rs @@ -17,7 +17,8 @@ use crate::admin::auth::validate_admin_request; use crate::admin::router::{AdminOperation, Operation, S3Router}; use crate::admin::runtime_sources::{ - resolve_kms_runtime_service_manager, resolve_object_store_handle, resolve_or_init_kms_runtime_service_manager, + current_app_context, resolve_kms_runtime_service_manager, resolve_object_store_handle_for_context, + resolve_or_init_kms_runtime_service_manager, }; use crate::admin::storage_api::config::{read_admin_config, save_admin_config}; use crate::auth::{check_key_valid, get_session_token}; @@ -105,7 +106,8 @@ fn normalize_configure_request_auth( /// Save KMS configuration to cluster storage #[instrument(skip(config))] async fn save_kms_config(config: &KmsConfig) -> Result<(), String> { - let Some(store) = resolve_object_store_handle() else { + let context = current_app_context(); + let Some(store) = resolve_object_store_handle_for_context(context.as_deref()) else { return Err("Storage layer not initialized".to_string()); }; @@ -129,7 +131,8 @@ async fn save_kms_config(config: &KmsConfig) -> Result<(), String> { /// Load KMS configuration from cluster storage #[instrument] pub async fn load_kms_config() -> Option { - let Some(store) = resolve_object_store_handle() else { + let context = current_app_context(); + let Some(store) = resolve_object_store_handle_for_context(context.as_deref()) else { warn!( component = LOG_COMPONENT_ADMIN, subsystem = LOG_SUBSYSTEM_KMS, diff --git a/rustfs/src/admin/handlers/oidc.rs b/rustfs/src/admin/handlers/oidc.rs index 19d0b9fdc..8dd0f5567 100644 --- a/rustfs/src/admin/handlers/oidc.rs +++ b/rustfs/src/admin/handlers/oidc.rs @@ -15,7 +15,9 @@ use super::sts::create_oidc_sts_credentials; use crate::admin::auth::validate_admin_request; use crate::admin::router::{AdminOperation, Operation, S3Router}; -use crate::admin::runtime_sources::{resolve_object_store_handle, resolve_oidc_handle, resolve_server_config}; +use crate::admin::runtime_sources::{ + current_app_context, resolve_object_store_handle_for_context, resolve_oidc_handle, resolve_server_config_for_context, +}; use crate::admin::storage_api::config::{read_admin_config_without_migrate, save_admin_server_config}; use crate::auth::{check_key_valid, get_session_token}; use crate::server::{ADMIN_PREFIX, MINIO_ADMIN_PREFIX, RemoteAddr}; @@ -792,7 +794,8 @@ fn json_response(status: StatusCode, payload: &T) -> S3Result S3Result { - let Some(store) = resolve_object_store_handle() else { + let context = current_app_context(); + let Some(store) = resolve_object_store_handle_for_context(context.as_deref()) else { return Err(s3_error!(InternalError, "storage layer not initialized")); }; @@ -802,7 +805,8 @@ async fn load_server_config_from_store() -> S3Result { } async fn save_server_config_to_store(config: &ServerConfig) -> S3Result<()> { - let Some(store) = resolve_object_store_handle() else { + let context = current_app_context(); + let Some(store) = resolve_object_store_handle_for_context(context.as_deref()) else { return Err(s3_error!(InternalError, "storage layer not initialized")); }; @@ -826,7 +830,8 @@ fn provider_instance_key(provider_id: &str) -> String { } fn oidc_restart_required(config: &ServerConfig) -> bool { - let active_config = resolve_server_config(); + let context = current_app_context(); + let active_config = resolve_server_config_for_context(context.as_deref()); oidc_restart_required_from_active_config(config, active_config.as_ref()) } diff --git a/rustfs/src/admin/runtime_sources.rs b/rustfs/src/admin/runtime_sources.rs index 09ca705f9..8c3520f4a 100644 --- a/rustfs/src/admin/runtime_sources.rs +++ b/rustfs/src/admin/runtime_sources.rs @@ -20,11 +20,11 @@ pub(crate) use crate::runtime_sources::{ AppContext, publish_server_config, publish_storage_class_config, resolve_action_credentials, resolve_boot_time, resolve_bucket_metadata_handle, resolve_bucket_monitor_handle, resolve_daily_tier_stats, resolve_deployment_id, resolve_endpoints_handle, resolve_iam_handle, resolve_kms_runtime_service_manager, resolve_notification_system, - resolve_object_store_handle, resolve_object_store_handle_for_context, resolve_oidc_handle, - resolve_or_init_kms_runtime_service_manager, resolve_outbound_tls_generation, resolve_outbound_tls_state, - resolve_ready_iam_handle, resolve_region, resolve_replication_pool_handle, resolve_replication_stats_handle, - resolve_runtime_port, resolve_scanner_metrics_report, resolve_server_config, resolve_tier_config_handle, - resolve_token_signing_key, + resolve_notification_system_for_context, resolve_object_store_handle, resolve_object_store_handle_for_context, + resolve_oidc_handle, resolve_or_init_kms_runtime_service_manager, resolve_outbound_tls_generation, + resolve_outbound_tls_state, resolve_ready_iam_handle, resolve_region, resolve_replication_pool_handle, + resolve_replication_stats_handle, resolve_runtime_port, resolve_scanner_metrics_report, resolve_server_config, + resolve_server_config_for_context, resolve_tier_config_handle, resolve_token_signing_key, }; use std::sync::Arc; diff --git a/rustfs/src/admin/service/config.rs b/rustfs/src/admin/service/config.rs index 1e4735e6a..6ce3177fd 100644 --- a/rustfs/src/admin/service/config.rs +++ b/rustfs/src/admin/service/config.rs @@ -13,8 +13,8 @@ // limitations under the License. use crate::admin::runtime_sources::{ - AppContext, current_app_context, publish_server_config, publish_storage_class_config, resolve_notification_system, - resolve_object_store_handle, resolve_object_store_handle_for_context, + AppContext, current_app_context, publish_server_config, publish_storage_class_config, + resolve_notification_system_for_context, resolve_object_store_handle_for_context, }; use crate::admin::storage_api::config::{STORAGE_CLASS_SUB_SYS, read_admin_config_without_migrate, storageclass}; use crate::admin::storage_api::contract::admin::StorageAdminApi; @@ -71,10 +71,8 @@ fn resolve_runtime_config_store_for_context(context: Option<&AppContext>) -> S3R resolve_object_store_handle_for_context(context).ok_or_else(|| internal_error("storage layer not initialized")) } -async fn apply_storage_class_runtime_config(config: &ServerConfig) -> S3Result<()> { - let Some(store) = resolve_object_store_handle() else { - return Err(internal_error("storage layer not initialized")); - }; +async fn apply_storage_class_runtime_config_for_context(context: Option<&AppContext>, config: &ServerConfig) -> S3Result<()> { + let store = resolve_runtime_config_store_for_context(context)?; let kvs = config.get_value(STORAGE_CLASS_SUB_SYS, DEFAULT_DELIMITER).unwrap_or_default(); let set_drive_count = StorageAdminApi::set_drive_counts(store.as_ref()) @@ -96,10 +94,8 @@ fn validate_storage_class_kvs(kvs: &KVS, set_drive_counts: &[usize]) -> S3Result Ok(()) } -async fn validate_storage_class_config(config: &ServerConfig) -> S3Result<()> { - let Some(store) = resolve_object_store_handle() else { - return Err(internal_error("storage layer not initialized")); - }; +async fn validate_storage_class_config_for_context(context: Option<&AppContext>, config: &ServerConfig) -> S3Result<()> { + let store = resolve_runtime_config_store_for_context(context)?; let kvs = config.get_value(STORAGE_CLASS_SUB_SYS, DEFAULT_DELIMITER).unwrap_or_default(); let set_drive_counts = StorageAdminApi::set_drive_counts(store.as_ref()); @@ -252,9 +248,13 @@ fn validate_identity_openid_config(config: &ServerConfig) -> S3Result<()> { Ok(()) } -pub async fn validate_server_config(config: &ServerConfig, sub_system: Option<&str>) -> S3Result<()> { +pub async fn validate_server_config_for_context( + context: Option<&AppContext>, + config: &ServerConfig, + sub_system: Option<&str>, +) -> S3Result<()> { match sub_system { - Some(STORAGE_CLASS_SUB_SYS) => validate_storage_class_config(config).await, + Some(STORAGE_CLASS_SUB_SYS) => validate_storage_class_config_for_context(context, config).await, Some(NOTIFY_WEBHOOK_SUB_SYS) => validate_notify_subsystem_config(config, NOTIFY_WEBHOOK_SUB_SYS), Some(NOTIFY_MQTT_SUB_SYS) => validate_notify_subsystem_config(config, NOTIFY_MQTT_SUB_SYS), Some(AUDIT_WEBHOOK_SUB_SYS) => validate_audit_subsystem_config(config, AUDIT_WEBHOOK_SUB_SYS), @@ -264,7 +264,7 @@ pub async fn validate_server_config(config: &ServerConfig, sub_system: Option<&s .map_err(|err| invalid_request(format!("invalid scanner config: {err}"))), Some(_) => Ok(()), None => { - validate_storage_class_config(config).await?; + validate_storage_class_config_for_context(context, config).await?; validate_notify_subsystem_config(config, NOTIFY_WEBHOOK_SUB_SYS)?; validate_notify_subsystem_config(config, NOTIFY_MQTT_SUB_SYS)?; validate_audit_subsystem_config(config, AUDIT_WEBHOOK_SUB_SYS)?; @@ -277,13 +277,22 @@ pub async fn validate_server_config(config: &ServerConfig, sub_system: Option<&s } } -pub async fn apply_dynamic_config_for_subsystem(config: &ServerConfig, sub_system: &str) -> S3Result { +pub async fn validate_server_config(config: &ServerConfig, sub_system: Option<&str>) -> S3Result<()> { + let context = current_app_context(); + validate_server_config_for_context(context.as_deref(), config, sub_system).await +} + +pub async fn apply_dynamic_config_for_subsystem_for_context( + context: Option<&AppContext>, + config: &ServerConfig, + sub_system: &str, +) -> S3Result { if dynamic_config_reload_plan(sub_system).is_none() { return Ok(false); } match sub_system { - STORAGE_CLASS_SUB_SYS => apply_storage_class_runtime_config(config).await?, + STORAGE_CLASS_SUB_SYS => apply_storage_class_runtime_config_for_context(context, config).await?, AUDIT_WEBHOOK_SUB_SYS | AUDIT_MQTT_SUB_SYS => reload_audit_config(config.clone()) .await .map_err(|err| internal_error(format!("failed to reload audit config: {err}")))?, @@ -297,6 +306,11 @@ pub async fn apply_dynamic_config_for_subsystem(config: &ServerConfig, sub_syste Ok(true) } +pub async fn apply_dynamic_config_for_subsystem(config: &ServerConfig, sub_system: &str) -> S3Result { + let context = current_app_context(); + apply_dynamic_config_for_subsystem_for_context(context.as_deref(), config, sub_system).await +} + pub async fn reload_dynamic_config_runtime_state_for_context(context: Option<&AppContext>, sub_system: &str) -> S3Result<()> { if !is_dynamic_config_subsystem(sub_system) { return Err(internal_error(format!("unsupported dynamic config subsystem: {sub_system}"))); @@ -308,10 +322,12 @@ pub async fn reload_dynamic_config_runtime_state_for_context(context: Option<&Ap warn!("peer reload_dynamic_config: failed to load server config for {sub_system}: {err}"); internal_error(format!("failed to load server config: {err}")) })?; - apply_dynamic_config_for_subsystem(&config, sub_system).await.map_err(|err| { - warn!("peer reload_dynamic_config: failed to apply {sub_system}: {err}"); - err - })?; + apply_dynamic_config_for_subsystem_for_context(context, &config, sub_system) + .await + .map_err(|err| { + warn!("peer reload_dynamic_config: failed to apply {sub_system}: {err}"); + err + })?; Ok(()) } @@ -337,7 +353,7 @@ pub async fn reload_runtime_config_snapshot_for_context(context: Option<&AppCont SCANNER_SUB_SYS, HEAL_SUB_SYS, ] { - if let Err(err) = apply_dynamic_config_for_subsystem(&config, sub_system).await { + if let Err(err) = apply_dynamic_config_for_subsystem_for_context(context, &config, sub_system).await { warn!("peer reload_runtime_config_snapshot: failed to apply {sub_system}: {err}"); } } @@ -351,12 +367,12 @@ pub async fn reload_runtime_config_snapshot() -> S3Result<()> { reload_runtime_config_snapshot_for_context(context.as_deref()).await } -pub async fn signal_dynamic_config_reload(sub_system: &str) { +pub async fn signal_dynamic_config_reload_for_context(context: Option<&AppContext>, sub_system: &str) { if !is_dynamic_config_subsystem(sub_system) { return; } - let Some(notification_sys) = resolve_notification_system() else { + let Some(notification_sys) = resolve_notification_system_for_context(context) else { return; }; @@ -367,8 +383,13 @@ pub async fn signal_dynamic_config_reload(sub_system: &str) { } } -pub async fn signal_config_snapshot_reload() { - let Some(notification_sys) = resolve_notification_system() else { +pub async fn signal_dynamic_config_reload(sub_system: &str) { + let context = current_app_context(); + signal_dynamic_config_reload_for_context(context.as_deref(), sub_system).await; +} + +pub async fn signal_config_snapshot_reload_for_context(context: Option<&AppContext>) { + let Some(notification_sys) = resolve_notification_system_for_context(context) else { return; }; @@ -379,6 +400,11 @@ pub async fn signal_config_snapshot_reload() { } } +pub async fn signal_config_snapshot_reload() { + let context = current_app_context(); + signal_config_snapshot_reload_for_context(context.as_deref()).await; +} + #[cfg(test)] mod tests { use super::*; diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs index 82e64044d..906415313 100644 --- a/rustfs/src/app/context.rs +++ b/rustfs/src/app/context.rs @@ -141,6 +141,13 @@ pub fn resolve_notification_system() -> Option<&'static NotificationSys> { resolve_notification_system_with(get_global_app_context(), || default_notification_system_interface().handle()) } +/// Resolve notification system handle using an explicit AppContext, falling back to the legacy global notification system. +pub fn resolve_notification_system_for_context(context: Option<&AppContext>) -> Option<&'static NotificationSys> { + context + .and_then(|context| context.notification_system().handle()) + .or_else(|| default_notification_system_interface().handle()) +} + /// Resolve endpoints using AppContext-first precedence. pub fn resolve_endpoints_handle() -> Option { resolve_endpoints_handle_with(get_global_app_context(), || default_endpoints_interface().handle()) @@ -248,6 +255,11 @@ pub fn resolve_server_config() -> Option { resolve_server_config_with(get_global_app_context(), || default_server_config_interface().get()) } +/// Resolve server config using an explicit AppContext, falling back to the legacy global server config. +pub fn resolve_server_config_for_context(context: Option<&AppContext>) -> Option { + context.map_or_else(|| default_server_config_interface().get(), |context| context.server_config().get()) +} + /// Publish server config using AppContext-first precedence. pub fn publish_server_config(config: Config) { publish_server_config_with(get_global_app_context(), config, |config| default_server_config_interface().set(config)); diff --git a/rustfs/src/runtime_sources.rs b/rustfs/src/runtime_sources.rs index ffeeb1caf..a4804e0a1 100644 --- a/rustfs/src/runtime_sources.rs +++ b/rustfs/src/runtime_sources.rs @@ -21,11 +21,13 @@ pub(crate) use context::{ resolve_buffer_config, resolve_daily_tier_stats, resolve_deployment_id, resolve_encryption_service, resolve_endpoints_handle, resolve_expiry_state_handle, resolve_iam_handle, resolve_iam_ready, resolve_internode_metrics, resolve_kms_runtime_service_manager, resolve_local_node_name, resolve_lock_client, resolve_lock_clients_handle, - resolve_notification_system, resolve_notify_interface, resolve_notify_interface_for_context, resolve_object_store_handle, - resolve_object_store_handle_for_context, resolve_oidc_handle, resolve_or_init_kms_runtime_service_manager, - resolve_outbound_tls_generation, resolve_outbound_tls_state, resolve_performance_metrics, resolve_ready_iam_handle, - resolve_region, resolve_replication_pool_handle, resolve_replication_stats_handle, resolve_runtime_port, resolve_s3select_db, - resolve_scanner_metrics_report, resolve_server_config, resolve_tier_config_handle, resolve_token_signing_key, + resolve_notification_system, resolve_notification_system_for_context, resolve_notify_interface, + resolve_notify_interface_for_context, resolve_object_store_handle, resolve_object_store_handle_for_context, + resolve_oidc_handle, resolve_or_init_kms_runtime_service_manager, resolve_outbound_tls_generation, + resolve_outbound_tls_state, resolve_performance_metrics, resolve_ready_iam_handle, resolve_region, + resolve_replication_pool_handle, resolve_replication_stats_handle, resolve_runtime_port, resolve_s3select_db, + resolve_scanner_metrics_report, resolve_server_config, resolve_server_config_for_context, resolve_tier_config_handle, + resolve_token_signing_key, }; #[cfg(test)]