diff --git a/docs/architecture/global-state-inventory.md b/docs/architecture/global-state-inventory.md index 375b9dc43..8e864d8df 100644 --- a/docs/architecture/global-state-inventory.md +++ b/docs/architecture/global-state-inventory.md @@ -42,7 +42,7 @@ migration PR removes or replaces each item. | `GLOBAL_OBJECT_API`, `GLOBAL_OBJECT_STORE_RESOLVER` | `crates/ecstore/src/runtime/global.rs` | Runtime migration target | Do not migrate first; it is tied to storage startup, IAM-after-storage AppContext publication, and data-plane resolver compatibility. | | `GLOBAL_ENDPOINTS`, `GLOBAL_IS_ERASURE`, `GLOBAL_IS_DIST_ERASURE`, `GLOBAL_IS_ERASURE_SD`, `GLOBAL_ROOT_DISK_THRESHOLD` | `crates/ecstore/src/runtime/global.rs` and `crates/ecstore/src/runtime/sources.rs` | Runtime migration target | Endpoint, setup-type, and root-disk-threshold access now stays behind ECStore runtime helpers; move endpoint ownership only after readiness and quorum behavior have explicit coverage. | | `GLOBAL_LOCAL_DISK_MAP`, `GLOBAL_LOCAL_DISK_ID_MAP`, `GLOBAL_LOCAL_DISK_SET_DRIVES` | `crates/ecstore/src/runtime/global.rs` and `crates/ecstore/src/runtime/sources.rs` | Runtime migration target | Local disk map, disk-id cache, and set-drive access now stay behind ECStore runtime-source helpers instead of direct global access; preserve disk lookup, remote/local classification, and test reset hooks in later ownership changes. | -| `GLOBAL_EXPIRY_STATE`, `GLOBAL_TRANSITION_STATE`, `GLOBAL_LIFECYCLE_SYS` | `crates/ecstore/src/bucket/lifecycle/*`, `crates/ecstore/src/runtime/global.rs`, and `crates/ecstore/src/runtime/sources.rs` | Runtime migration target | Lifecycle state globals now stay behind ECStore lifecycle owner helpers and ECStore runtime-source helpers; scanner expiry-state access now uses the ECStore runtime `expiry_state_handle` boundary; `GLOBAL_EXPIRY_STATE` remains the first code-bearing ownership candidate because AppContext already has an `ExpiryStateInterface`, resolver, and tests. | +| `GLOBAL_EXPIRY_STATE`, `GLOBAL_TRANSITION_STATE`, `GLOBAL_LIFECYCLE_SYS` | `crates/ecstore/src/bucket/lifecycle/*`, `crates/ecstore/src/runtime/global.rs`, and `crates/ecstore/src/runtime/sources.rs` | Runtime migration target | Lifecycle state globals now stay behind ECStore lifecycle owner helpers and ECStore runtime-source helpers; RustFS AppContext has expiry/transition state interfaces and resolver coverage; scanner expiry-state access still uses the ECStore runtime `expiry_state_handle` boundary until scanner gets an injected provider. | | `GLOBAL_REPLICATION_POOL`, `GLOBAL_REPLICATION_STATS`, `GLOBAL_BUCKET_MONITOR` | `crates/ecstore/src/bucket/replication/*`, `crates/ecstore/src/runtime/global.rs` | Runtime migration target | Replication pool/stat access now stays behind replication owner and ECStore runtime-source helpers; bucket-monitor direct access now stays behind ECStore runtime helpers while AppContext/runtime-source resolvers remain the caller boundary. | | `GLOBAL_TIER_CONFIG_MGR`, `GLOBAL_STORAGE_CLASS`, `GLOBAL_CONFIG_SYS`, `GLOBAL_SERVER_CONFIG` | `crates/ecstore/src/config`, `crates/config`, `rustfs/src/app/context/runtime_sources.rs` | Runtime migration target | Tier config manager reads and reloads now use the ECStore runtime-source helper; move remaining config state through config/runtime-source owners only, without combining storage-class behavior or persistence changes. | | `GLOBAL_EVENT_NOTIFIER`, `GLOBAL_NOTIFICATION_SYS` | `crates/ecstore/src/runtime/global.rs`, `crates/ecstore/src/runtime/sources.rs`, and `crates/ecstore/src/services/*` | Runtime migration target | `GLOBAL_EVENT_NOTIFIER` and `GLOBAL_NOTIFICATION_SYS` access now stay behind ECStore runtime-source and notification owner helpers; move remaining notification ownership only through notify/runtime-source boundaries. | diff --git a/rustfs/src/app/context.rs b/rustfs/src/app/context.rs index 031527f0f..c29c96868 100644 --- a/rustfs/src/app/context.rs +++ b/rustfs/src/app/context.rs @@ -29,7 +29,7 @@ pub use interfaces::*; use super::storage_api::context::bucket::metadata_sys::BucketMetadataSys; use super::storage_api::context::runtime::{ BucketBandwidthMonitor, DailyAllTierStats, DynReplicationPool, ExpiryState, NotificationSys, ReplicationStats, - ScannerMetricsReport, StorageClassConfig, TierConfigMgr, + ScannerMetricsReport, StorageClassConfig, TierConfigMgr, TransitionState, }; use super::storage_api::context::{ECStore, EndpointServerPools}; use crate::config::RustFSBufferConfig; @@ -248,6 +248,11 @@ pub fn resolve_expiry_state_handle() -> Option>> { resolve_expiry_state_handle_with(get_global_app_context()) } +/// Resolve lifecycle transition state using AppContext-first precedence. +pub fn resolve_transition_state_handle() -> Option> { + resolve_transition_state_handle_with(get_global_app_context()) +} + /// Resolve server config using AppContext-first precedence. pub fn resolve_server_config() -> Option { resolve_server_config_with(get_global_app_context()) @@ -444,6 +449,10 @@ fn resolve_expiry_state_handle_with(context: Option>) -> Option< context.map(|context| context.expiry_state().handle()) } +fn resolve_transition_state_handle_with(context: Option>) -> Option> { + context.map(|context| context.transition_state().handle()) +} + fn resolve_server_config_with(context: Option>) -> Option { context.and_then(|context| context.server_config().get()) } @@ -485,7 +494,7 @@ mod tests { EndpointsInterface, IamInterface, InternodeMetricsInterface, KmsInterface, KmsRuntimeInterface, LocalNodeNameInterface, LockClientInterface, LockClientsInterface, OidcInterface, OutboundTlsRuntimeInterface, PerformanceMetricsInterface, RegionInterface, ReplicationStatsInterface, RuntimePortInterface, S3SelectDbInterface, ScannerMetricsInterface, - ServerConfigInterface, StorageClassInterface, TierConfigInterface, TierStatsInterface, + ServerConfigInterface, StorageClassInterface, TierConfigInterface, TierStatsInterface, TransitionStateInterface, }; use crate::config::{RustFSBufferConfig, WorkloadProfile}; use async_trait::async_trait; @@ -798,6 +807,16 @@ mod tests { } } + struct TestTransitionStateInterface { + transition_state: Arc, + } + + impl TransitionStateInterface for TestTransitionStateInterface { + fn handle(&self) -> Arc { + self.transition_state.clone() + } + } + struct TestServerConfigInterface { config: Option, published: Arc, @@ -937,6 +956,7 @@ mod tests { }; let tier_config = TierConfigMgr::new(); let context_expiry_state = ExpiryState::new(); + let context_transition_state = TransitionState::new(); let server_config = Config::new(); let context_server_config_published = Arc::new(AtomicUsize::new(0)); let context_storage_class_published = Arc::new(AtomicUsize::new(0)); @@ -1047,6 +1067,9 @@ mod tests { expiry_state: Arc::new(TestExpiryStateInterface { expiry_state: context_expiry_state.clone(), }), + transition_state: Arc::new(TestTransitionStateInterface { + transition_state: context_transition_state.clone(), + }), server_config: Arc::new(TestServerConfigInterface { config: Some(server_config.clone()), published: context_server_config_published.clone(), @@ -1176,6 +1199,10 @@ mod tests { &resolve_expiry_state_handle_with(Some(context.clone())).expect("context expiry state"), &context_expiry_state )); + assert!(Arc::ptr_eq( + &resolve_transition_state_handle_with(Some(context.clone())).expect("context transition state"), + &context_transition_state + )); assert_eq!( resolve_server_config_with(Some(context.clone())).expect("context server config"), server_config @@ -1221,6 +1248,7 @@ mod tests { assert!(resolve_region_with(None).is_none()); assert!(resolve_tier_config_handle_with(None).is_none()); assert!(resolve_expiry_state_handle_with(None).is_none()); + assert!(resolve_transition_state_handle_with(None).is_none()); assert!(resolve_server_config_with(None).is_none()); assert!(!publish_server_config_with(None, Config::new())); assert!(!publish_storage_class_config_with(None, StorageClassConfig::default())); diff --git a/rustfs/src/app/context/global.rs b/rustfs/src/app/context/global.rs index b37a1bf4e..248e9e3b6 100644 --- a/rustfs/src/app/context/global.rs +++ b/rustfs/src/app/context/global.rs @@ -23,7 +23,8 @@ use super::handles::{ default_outbound_tls_runtime_interface, default_performance_metrics_interface, default_region_interface, default_replication_pool_interface, default_replication_stats_interface, default_runtime_port_interface, default_s3select_db_interface, default_scanner_metrics_interface, default_server_config_interface, - default_storage_class_interface, default_tier_config_interface, default_tier_stats_interface, oidc_interface, + default_storage_class_interface, default_tier_config_interface, default_tier_stats_interface, + default_transition_state_interface, oidc_interface, }; use super::interfaces::{ ActionCredentialInterface, BootTimeInterface, BucketMetadataInterface, BucketMonitorInterface, BufferConfigInterface, @@ -31,7 +32,7 @@ use super::interfaces::{ KmsRuntimeInterface, LocalNodeNameInterface, LockClientInterface, LockClientsInterface, NotificationSystemInterface, NotifyInterface, OidcInterface, OutboundTlsRuntimeInterface, PerformanceMetricsInterface, RegionInterface, ReplicationPoolInterface, ReplicationStatsInterface, RuntimePortInterface, S3SelectDbInterface, ScannerMetricsInterface, - ServerConfigInterface, StorageClassInterface, TierConfigInterface, TierStatsInterface, + ServerConfigInterface, StorageClassInterface, TierConfigInterface, TierStatsInterface, TransitionStateInterface, }; use rustfs_iam::{oidc::OidcSys, store::object::ObjectStore, sys::IamSys}; use rustfs_kms::KmsServiceManager; @@ -69,6 +70,7 @@ pub struct AppContext { region: Arc, tier_config: Arc, expiry_state: Arc, + transition_state: Arc, server_config: Arc, storage_class: Arc, buffer_config: Arc, @@ -105,6 +107,7 @@ impl AppContext { region: default_region_interface(), tier_config: default_tier_config_interface(), expiry_state: default_expiry_state_interface(), + transition_state: default_transition_state_interface(), server_config: default_server_config_interface(), storage_class: default_storage_class_interface(), buffer_config: default_buffer_config_interface(), @@ -238,6 +241,10 @@ impl AppContext { self.expiry_state.clone() } + pub fn transition_state(&self) -> Arc { + self.transition_state.clone() + } + pub fn server_config(&self) -> Arc { self.server_config.clone() } @@ -280,6 +287,7 @@ pub(super) struct AppContextTestInterfaces { pub(super) region: Arc, pub(super) tier_config: Arc, pub(super) expiry_state: Arc, + pub(super) transition_state: Arc, pub(super) server_config: Arc, pub(super) storage_class: Arc, pub(super) buffer_config: Arc, @@ -317,6 +325,7 @@ impl AppContext { region: interfaces.region, tier_config: interfaces.tier_config, expiry_state: interfaces.expiry_state, + transition_state: interfaces.transition_state, server_config: interfaces.server_config, storage_class: interfaces.storage_class, buffer_config: interfaces.buffer_config, diff --git a/rustfs/src/app/context/handles.rs b/rustfs/src/app/context/handles.rs index 5f8b46432..0a7e964c3 100644 --- a/rustfs/src/app/context/handles.rs +++ b/rustfs/src/app/context/handles.rs @@ -16,7 +16,7 @@ use super::super::storage_api::context::EndpointServerPools; use super::super::storage_api::context::bucket::metadata_sys::BucketMetadataSys; use super::super::storage_api::context::runtime::{ BucketBandwidthMonitor, DailyAllTierStats, DynReplicationPool, ExpiryState, NotificationSys, ReplicationStats, - ScannerMetricsReport, StorageClassConfig, TierConfigMgr, + ScannerMetricsReport, StorageClassConfig, TierConfigMgr, TransitionState, }; use super::interfaces::{ ActionCredentialInterface, BootTimeInterface, BucketMetadataInterface, BucketMonitorInterface, BufferConfigInterface, @@ -24,7 +24,7 @@ use super::interfaces::{ KmsRuntimeInterface, LocalNodeNameInterface, LockClientInterface, LockClientsInterface, NotificationSystemInterface, NotifyInterface, OidcInterface, OutboundTlsRuntimeInterface, PerformanceMetricsInterface, RegionInterface, ReplicationPoolInterface, ReplicationStatsInterface, RuntimePortInterface, S3SelectDbInterface, ScannerMetricsInterface, - ServerConfigInterface, StorageClassInterface, TierConfigInterface, TierStatsInterface, + ServerConfigInterface, StorageClassInterface, TierConfigInterface, TierStatsInterface, TransitionStateInterface, }; use super::runtime_sources; use crate::config::RustFSBufferConfig; @@ -390,6 +390,16 @@ impl ExpiryStateInterface for ExpiryStateHandle { } } +/// Default lifecycle transition state interface adapter. +#[derive(Default)] +pub struct TransitionStateHandle; + +impl TransitionStateInterface for TransitionStateHandle { + fn handle(&self) -> Arc { + runtime_sources::transition_state() + } +} + /// Default server config interface adapter. #[derive(Default)] pub struct ServerConfigHandle; @@ -528,6 +538,10 @@ pub fn default_expiry_state_interface() -> Arc { Arc::new(ExpiryStateHandle) } +pub fn default_transition_state_interface() -> Arc { + Arc::new(TransitionStateHandle) +} + pub fn default_server_config_interface() -> Arc { Arc::new(ServerConfigHandle) } diff --git a/rustfs/src/app/context/interfaces.rs b/rustfs/src/app/context/interfaces.rs index 8f1687e19..24100cc5c 100644 --- a/rustfs/src/app/context/interfaces.rs +++ b/rustfs/src/app/context/interfaces.rs @@ -16,7 +16,7 @@ use super::super::storage_api::context::EndpointServerPools; use super::super::storage_api::context::bucket::metadata_sys::BucketMetadataSys; use super::super::storage_api::context::runtime::{ BucketBandwidthMonitor, DailyAllTierStats, DynReplicationPool, ExpiryState, NotificationSys, ReplicationStats, - ScannerMetricsReport, StorageClassConfig, TierConfigMgr, + ScannerMetricsReport, StorageClassConfig, TierConfigMgr, TransitionState, }; use crate::config::RustFSBufferConfig; use async_trait::async_trait; @@ -197,6 +197,11 @@ pub trait ExpiryStateInterface: Send + Sync { fn handle(&self) -> Arc>; } +/// Lifecycle transition state interface for transition queues and tier stats. +pub trait TransitionStateInterface: Send + Sync { + fn handle(&self) -> Arc; +} + /// Server config interface for application-layer and server modules. pub trait ServerConfigInterface: Send + Sync { fn get(&self) -> Option; diff --git a/rustfs/src/app/context/runtime_sources.rs b/rustfs/src/app/context/runtime_sources.rs index 5686ffc29..714e6eb8d 100644 --- a/rustfs/src/app/context/runtime_sources.rs +++ b/rustfs/src/app/context/runtime_sources.rs @@ -18,10 +18,11 @@ use super::super::storage_api::context::EndpointServerPools; use super::super::storage_api::context::bucket::metadata_sys::BucketMetadataSys; use super::super::storage_api::context::runtime::{ BucketBandwidthMonitor, DailyAllTierStats, DynReplicationPool, ExpiryState, NotificationSys, ReplicationStats, - ScannerMetricsReport, StorageClassConfig, TierConfigMgr, collect_scanner_metrics_report, get_daily_all_tier_stats, - get_global_boot_time, get_global_bucket_monitor, get_global_deployment_id, get_global_endpoints_opt, get_global_expiry_state, - get_global_lock_client, get_global_lock_clients, get_global_notification_sys, get_global_region, get_global_replication_pool, - get_global_replication_stats, get_global_tier_config_mgr, global_rustfs_port, set_global_storage_class, + ScannerMetricsReport, StorageClassConfig, TierConfigMgr, TransitionState, collect_scanner_metrics_report, + get_daily_all_tier_stats, get_global_boot_time, get_global_bucket_monitor, get_global_deployment_id, + get_global_endpoints_opt, get_global_expiry_state, get_global_lock_client, get_global_lock_clients, + get_global_notification_sys, get_global_region, get_global_replication_pool, get_global_replication_stats, + get_global_tier_config_mgr, get_global_transition_state, global_rustfs_port, set_global_storage_class, }; use crate::config::{RustFSBufferConfig, get_global_buffer_config}; use rustfs_config::server_config::{Config, get_global_server_config, set_global_server_config}; @@ -180,6 +181,10 @@ pub fn expiry_state() -> Arc> { get_global_expiry_state() } +pub fn transition_state() -> Arc { + get_global_transition_state() +} + #[cfg(test)] pub fn object_store() -> Option> { super::super::storage_api::context::runtime::new_object_layer_fn() diff --git a/rustfs/src/app/storage_api.rs b/rustfs/src/app/storage_api.rs index 645a7f6e0..3d48761ec 100644 --- a/rustfs/src/app/storage_api.rs +++ b/rustfs/src/app/storage_api.rs @@ -125,6 +125,7 @@ pub(crate) mod runtime { pub(crate) type ScannerMetricsReport = rustfs_common::metrics::ScannerMetricsReport; pub(crate) type StorageClassConfig = crate::storage::storage_api::ecstore_config::storageclass::Config; pub(crate) type TierConfigMgr = crate::storage::storage_api::TierConfigMgr; + pub(crate) type TransitionState = crate::storage::storage_api::TransitionState; #[cfg(test)] pub(crate) type TierConfig = crate::storage::storage_api::ecstore_tier::tier_config::TierConfig; @@ -171,6 +172,10 @@ pub(crate) mod runtime { crate::storage::storage_api::get_global_expiry_state() } + pub(crate) fn get_global_transition_state() -> Arc { + crate::storage::storage_api::get_global_transition_state() + } + #[cfg(test)] pub(crate) fn new_object_layer_fn() -> Option> { crate::storage::storage_api::ecstore_global::new_object_layer_fn() diff --git a/rustfs/src/storage/storage_api.rs b/rustfs/src/storage/storage_api.rs index b45477f3c..d79d2987c 100644 --- a/rustfs/src/storage/storage_api.rs +++ b/rustfs/src/storage/storage_api.rs @@ -515,6 +515,7 @@ pub(crate) type ReplicationStats = ecstore_bucket::replication::ReplicationStats pub(crate) type SetupType = ecstore_layout::SetupType; pub(crate) type StorageError = ecstore_error::StorageError; pub(crate) type TierConfigMgr = ecstore_tier::TierConfigMgr; +pub(crate) type TransitionState = ecstore_bucket::lifecycle::bucket_lifecycle_ops::TransitionState; pub(crate) type Error = ecstore_error::Error; pub(crate) type Result = ecstore_error::Result; pub(crate) type UpdateMetadataOpts = ecstore_disk::UpdateMetadataOpts; @@ -582,6 +583,10 @@ pub(crate) fn get_global_expiry_state() -> Arc> ecstore_bucket::lifecycle::bucket_lifecycle_ops::get_global_expiry_state() } +pub(crate) fn get_global_transition_state() -> Arc { + ecstore_bucket::lifecycle::bucket_lifecycle_ops::get_global_transition_state() +} + pub(crate) async fn try_migrate_bucket_metadata(store: Arc) { ecstore_bucket::migration::try_migrate_bucket_metadata(store).await; }