refactor: route admin runtime config through app context (#3942)

This commit is contained in:
Zhengchao An
2026-06-27 13:10:00 +08:00
committed by GitHub
parent 1b3dea012e
commit 996e58fd9a
10 changed files with 166 additions and 66 deletions
@@ -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<Config> {
let Some(store) = resolve_object_store_handle() else {
pub(crate) async fn load_server_config_from_store_for_context(context: Option<&AppContext>) -> S3Result<Config> {
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<Config> {
.map_err(|e| s3_error!(InternalError, "failed to read server config: {}", e))
}
pub(crate) async fn load_server_config_from_store() -> S3Result<Config> {
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<F>(specs: &[AdminTargetSpec], mut modifier: F) -> S3Result<()>
async fn update_audit_config_and_reload_for_context<F>(
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<F>(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,
+9 -3
View File
@@ -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<S3Response<(StatusCode, Bo
}
fn object_store() -> S3Result<std::sync::Arc<crate::admin::storage_api::runtime::ECStore>> {
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<ServerConfig> {
@@ -725,7 +729,9 @@ async fn load_active_server_config() -> S3Result<ServerConfig> {
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<()> {
+6 -3
View File
@@ -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<KmsConfig> {
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,
+9 -4
View File
@@ -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<T: Serialize>(status: StatusCode, payload: &T) -> S3Result<S3Re
}
async fn load_server_config_from_store() -> S3Result<ServerConfig> {
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<ServerConfig> {
}
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())
}
+5 -5
View File
@@ -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;
+50 -24
View File
@@ -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<bool> {
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<bool> {
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<bool> {
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::*;
+12
View File
@@ -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<EndpointServerPools> {
resolve_endpoints_handle_with(get_global_app_context(), || default_endpoints_interface().handle())
@@ -248,6 +255,11 @@ pub fn resolve_server_config() -> Option<Config> {
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<Config> {
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));
+7 -5
View File
@@ -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)]