mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
refactor(ecstore): move deployment id, endpoints, and tier config into InstanceContext (#4465)
This commit is contained in:
@@ -18,7 +18,6 @@ use crate::{
|
||||
bucket::lifecycle::bucket_lifecycle_ops::LifecycleSys,
|
||||
disk::DiskStore,
|
||||
layout::endpoints::{EndpointServerPools, PoolEndpoints, SetupType},
|
||||
services::event_notification::EventNotifier,
|
||||
services::tier::tier::TierConfigMgr,
|
||||
store::ECStore,
|
||||
};
|
||||
@@ -47,21 +46,18 @@ pub const DISK_RESERVE_FRACTION: f64 = 0.15;
|
||||
// GLOBAL_ROOT_DISK_THRESHOLD, GLOBAL_LIFECYCLE_SYS, GLOBAL_EVENT_NOTIFIER, etc.
|
||||
// Tier B (keep as static): GLOBAL_RUSTFS_PORT, env var caches, etc.
|
||||
//
|
||||
// Phase 5 (backlog#939): the erasure setup type and the S3 region moved into
|
||||
// the per-instance `InstanceContext` (see `super::instance`); their facades
|
||||
// below now forward to the current instance's context.
|
||||
// Phase 5 (backlog#939): identity/runtime state (erasure setup, S3 region,
|
||||
// deployment id, endpoint topology, tier config manager, ...) is moving into
|
||||
// the per-instance `InstanceContext` (see `super::instance`); the facades below
|
||||
// forward to the current instance's context.
|
||||
lazy_static! {
|
||||
static ref GLOBAL_RUSTFS_PORT: OnceLock<u16> = OnceLock::new();
|
||||
static ref GLOBAL_DEPLOYMENT_ID: OnceLock<Uuid> = OnceLock::new();
|
||||
pub static ref GLOBAL_OBJECT_API: OnceLock<Arc<ECStore>> = OnceLock::new();
|
||||
pub static ref GLOBAL_LOCAL_DISK_MAP: Arc<RwLock<HashMap<String, Option<DiskStore>>>> = Arc::new(RwLock::new(HashMap::new()));
|
||||
pub static ref GLOBAL_LOCAL_DISK_ID_MAP: Arc<RwLock<HashMap<Uuid, String>>> = Arc::new(RwLock::new(HashMap::new()));
|
||||
pub static ref GLOBAL_LOCAL_DISK_SET_DRIVES: Arc<RwLock<TypeLocalDiskSetDrives>> = Arc::new(RwLock::new(Vec::new()));
|
||||
pub static ref GLOBAL_ENDPOINTS: OnceLock<EndpointServerPools> = OnceLock::new();
|
||||
pub static ref GLOBAL_ROOT_DISK_THRESHOLD: RwLock<u64> = RwLock::new(0);
|
||||
pub static ref GLOBAL_TIER_CONFIG_MGR: Arc<RwLock<TierConfigMgr>> = TierConfigMgr::new();
|
||||
pub static ref GLOBAL_LIFECYCLE_SYS: Arc<LifecycleSys> = LifecycleSys::new();
|
||||
pub static ref GLOBAL_EVENT_NOTIFIER: Arc<RwLock<EventNotifier>> = EventNotifier::new();
|
||||
pub static ref GLOBAL_BOOT_TIME: OnceCell<SystemTime> = OnceCell::new();
|
||||
pub static ref GLOBAL_LOCAL_NODE_NAME_FALLBACK: String = "127.0.0.1:9000".to_string();
|
||||
pub static ref GLOBAL_LOCAL_NODE_NAME_HEX_FALLBACK: String =
|
||||
@@ -125,9 +121,7 @@ pub fn set_global_rustfs_port(value: u16) {
|
||||
/// * None
|
||||
///
|
||||
pub fn set_global_deployment_id(id: Uuid) {
|
||||
GLOBAL_DEPLOYMENT_ID
|
||||
.set(id)
|
||||
.expect("GLOBAL_DEPLOYMENT_ID should be initialized once during startup");
|
||||
current_ctx().set_deployment_id(id);
|
||||
}
|
||||
|
||||
/// Get the global deployment id
|
||||
@@ -136,7 +130,7 @@ pub fn set_global_deployment_id(id: Uuid) {
|
||||
/// * `Option<String>` - The global deployment id as a string, if set
|
||||
///
|
||||
pub fn get_global_deployment_id() -> Option<String> {
|
||||
GLOBAL_DEPLOYMENT_ID.get().map(|v| v.to_string())
|
||||
current_ctx().deployment_id().map(|v| v.to_string())
|
||||
}
|
||||
/// Set the global endpoints
|
||||
///
|
||||
@@ -147,9 +141,7 @@ pub fn get_global_deployment_id() -> Option<String> {
|
||||
/// * None
|
||||
///
|
||||
pub fn set_global_endpoints(eps: Vec<PoolEndpoints>) {
|
||||
GLOBAL_ENDPOINTS
|
||||
.set(EndpointServerPools::from(eps))
|
||||
.expect("GLOBAL_ENDPOINTS should be initialized once during storage startup")
|
||||
current_ctx().set_endpoints(EndpointServerPools::from(eps));
|
||||
}
|
||||
|
||||
/// Get the global endpoints
|
||||
@@ -158,15 +150,11 @@ pub fn set_global_endpoints(eps: Vec<PoolEndpoints>) {
|
||||
/// * `EndpointServerPools` - The global endpoints
|
||||
///
|
||||
pub fn get_global_endpoints() -> EndpointServerPools {
|
||||
if let Some(eps) = GLOBAL_ENDPOINTS.get() {
|
||||
eps.clone()
|
||||
} else {
|
||||
EndpointServerPools::default()
|
||||
}
|
||||
current_ctx().endpoints().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_global_endpoints_opt() -> Option<EndpointServerPools> {
|
||||
GLOBAL_ENDPOINTS.get().cloned()
|
||||
current_ctx().endpoints()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -181,7 +169,7 @@ pub async fn is_first_cluster_node_local() -> bool {
|
||||
}
|
||||
|
||||
pub fn get_global_tier_config_mgr() -> Arc<RwLock<TierConfigMgr>> {
|
||||
GLOBAL_TIER_CONFIG_MGR.clone()
|
||||
current_ctx().tier_config_mgr()
|
||||
}
|
||||
|
||||
/// Create a new object layer instance
|
||||
|
||||
@@ -39,18 +39,20 @@
|
||||
//! startup writes and post-construction reads hit the same cell and
|
||||
//! single-instance behavior is byte-for-byte unchanged.
|
||||
|
||||
use crate::layout::endpoints::SetupType;
|
||||
use crate::layout::endpoints::{EndpointServerPools, SetupType};
|
||||
use crate::services::event_notification::EventNotifier;
|
||||
use crate::services::tier::tier::TierConfigMgr;
|
||||
use rustfs_lock::{GlobalLockManager, get_global_lock_manager};
|
||||
use s3s::region::Region;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Runtime state owned by a single `ECStore` instance.
|
||||
///
|
||||
/// This is intentionally minimal in the first migration slice; subsequent
|
||||
/// slices move additional identity/runtime state (topology, disk registry,
|
||||
/// service handles, cancellation token) into this struct.
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceContext {
|
||||
/// The deployment's erasure setup type.
|
||||
///
|
||||
@@ -72,6 +74,28 @@ pub struct InstanceContext {
|
||||
/// Write-once like the process global it replaces: startup sets it exactly
|
||||
/// once and a duplicate write fails fast (see [`InstanceContext::set_region`]).
|
||||
region: OnceLock<Region>,
|
||||
/// This instance's deployment id (Phase 5 Slice 5, backlog#939).
|
||||
///
|
||||
/// Write-once identity, mirrored by `ECStore::id` (both come from the same
|
||||
/// startup value). Replaces the process-global deployment-id static.
|
||||
deployment_id: OnceLock<Uuid>,
|
||||
/// This instance's endpoint topology (Phase 5 Slice 6, backlog#939).
|
||||
///
|
||||
/// Write-once: storage startup publishes the server pools exactly once.
|
||||
/// Replaces the process-global endpoints static.
|
||||
endpoints: OnceLock<EndpointServerPools>,
|
||||
/// This instance's tier configuration manager (Phase 5 Slice 7, backlog#939).
|
||||
///
|
||||
/// Lazily materialized on first access so `InstanceContext::new()` stays
|
||||
/// cheap: single-instance creates one shared manager on first use — exactly
|
||||
/// the "create-once, then share" semantics of the eager process static it
|
||||
/// replaces — while a genuinely independent instance gets its own.
|
||||
tier_config_mgr: OnceLock<Arc<RwLock<TierConfigMgr>>>,
|
||||
/// This instance's event notifier (Phase 5 Slice 8, backlog#939).
|
||||
///
|
||||
/// Lazily materialized like the tier config manager; holds the instance's
|
||||
/// notification target list. Replaces the eager process static.
|
||||
event_notifier: OnceLock<Arc<RwLock<EventNotifier>>>,
|
||||
}
|
||||
|
||||
impl InstanceContext {
|
||||
@@ -92,6 +116,10 @@ impl InstanceContext {
|
||||
erasure_kind: RwLock::new(SetupType::Unknown),
|
||||
lock_manager,
|
||||
region: OnceLock::new(),
|
||||
deployment_id: OnceLock::new(),
|
||||
endpoints: OnceLock::new(),
|
||||
tier_config_mgr: OnceLock::new(),
|
||||
event_notifier: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +143,48 @@ impl InstanceContext {
|
||||
self.region.get().cloned()
|
||||
}
|
||||
|
||||
/// Set this instance's deployment id.
|
||||
///
|
||||
/// Write-once: panics on a second write, preserving the startup fail-fast
|
||||
/// contract of the process global it replaces.
|
||||
pub fn set_deployment_id(&self, id: Uuid) {
|
||||
self.deployment_id
|
||||
.set(id)
|
||||
.expect("instance deployment id should be initialized once during startup");
|
||||
}
|
||||
|
||||
/// This instance's deployment id, if it has been set.
|
||||
pub fn deployment_id(&self) -> Option<Uuid> {
|
||||
self.deployment_id.get().copied()
|
||||
}
|
||||
|
||||
/// Set this instance's endpoint topology.
|
||||
///
|
||||
/// Write-once: panics on a second write, preserving the startup fail-fast
|
||||
/// contract of the process global it replaces.
|
||||
pub fn set_endpoints(&self, endpoints: EndpointServerPools) {
|
||||
self.endpoints
|
||||
.set(endpoints)
|
||||
.expect("instance endpoints should be initialized once during storage startup");
|
||||
}
|
||||
|
||||
/// This instance's endpoint topology, if it has been set.
|
||||
pub fn endpoints(&self) -> Option<EndpointServerPools> {
|
||||
self.endpoints.get().cloned()
|
||||
}
|
||||
|
||||
/// This instance's tier configuration manager, materializing it on first
|
||||
/// access. Shared for the lifetime of the instance.
|
||||
pub fn tier_config_mgr(&self) -> Arc<RwLock<TierConfigMgr>> {
|
||||
self.tier_config_mgr.get_or_init(TierConfigMgr::new).clone()
|
||||
}
|
||||
|
||||
/// This instance's event notifier, materializing it on first access.
|
||||
/// Shared for the lifetime of the instance.
|
||||
pub fn event_notifier(&self) -> Arc<RwLock<EventNotifier>> {
|
||||
self.event_notifier.get_or_init(EventNotifier::new).clone()
|
||||
}
|
||||
|
||||
/// Update this instance's erasure setup type.
|
||||
pub async fn update_erasure_type(&self, setup_type: SetupType) {
|
||||
*self.erasure_kind.write().await = setup_type;
|
||||
@@ -147,6 +217,21 @@ impl Default for InstanceContext {
|
||||
}
|
||||
}
|
||||
|
||||
// Manual Debug: service handles (e.g. the tier config manager) are not Debug,
|
||||
// so summarize their materialization state rather than their contents.
|
||||
impl std::fmt::Debug for InstanceContext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("InstanceContext")
|
||||
.field("erasure_kind", &self.erasure_kind)
|
||||
.field("region", &self.region)
|
||||
.field("deployment_id", &self.deployment_id)
|
||||
.field("endpoints", &self.endpoints)
|
||||
.field("tier_config_mgr_set", &self.tier_config_mgr.get().is_some())
|
||||
.field("event_notifier_set", &self.event_notifier.get().is_some())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Process-level bootstrap instance context.
|
||||
///
|
||||
/// Storage startup writes erasure state before any `ECStore` exists (see
|
||||
@@ -285,4 +370,123 @@ mod tests {
|
||||
ctx.set_region(region("us-east-1"));
|
||||
ctx.set_region(region("eu-west-1"));
|
||||
}
|
||||
|
||||
// A fresh context has no deployment id until set; set-then-get round-trips.
|
||||
#[tokio::test]
|
||||
async fn deployment_id_set_and_get() {
|
||||
let ctx = InstanceContext::new();
|
||||
assert_eq!(ctx.deployment_id(), None);
|
||||
let id = Uuid::new_v4();
|
||||
ctx.set_deployment_id(id);
|
||||
assert_eq!(ctx.deployment_id(), Some(id));
|
||||
}
|
||||
|
||||
// Two contexts hold independent deployment ids.
|
||||
#[tokio::test]
|
||||
async fn distinct_contexts_have_distinct_deployment_id() {
|
||||
let ctx_a = InstanceContext::new();
|
||||
let ctx_b = InstanceContext::new();
|
||||
let id_a = Uuid::new_v4();
|
||||
let id_b = Uuid::new_v4();
|
||||
ctx_a.set_deployment_id(id_a);
|
||||
ctx_b.set_deployment_id(id_b);
|
||||
assert_eq!(ctx_a.deployment_id(), Some(id_a));
|
||||
assert_eq!(ctx_b.deployment_id(), Some(id_b));
|
||||
}
|
||||
|
||||
// The deployment id is write-once: a second set fails fast.
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "initialized once")]
|
||||
async fn set_deployment_id_twice_panics() {
|
||||
let ctx = InstanceContext::new();
|
||||
ctx.set_deployment_id(Uuid::new_v4());
|
||||
ctx.set_deployment_id(Uuid::new_v4());
|
||||
}
|
||||
|
||||
fn endpoints_with_pools(n: usize) -> EndpointServerPools {
|
||||
use crate::layout::endpoint::Endpoint;
|
||||
use crate::layout::endpoints::{Endpoints, PoolEndpoints};
|
||||
let pool = PoolEndpoints {
|
||||
legacy: false,
|
||||
set_count: 1,
|
||||
drives_per_set: 1,
|
||||
endpoints: Endpoints::from(vec![Endpoint::try_from("http://127.0.0.1:9000/data0").expect("valid endpoint")]),
|
||||
cmd_line: "test".to_string(),
|
||||
platform: "test".to_string(),
|
||||
};
|
||||
EndpointServerPools((0..n).map(|_| pool.clone()).collect())
|
||||
}
|
||||
|
||||
// A fresh context has no endpoints until set; set-then-get round-trips.
|
||||
#[tokio::test]
|
||||
async fn endpoints_set_and_get() {
|
||||
let ctx = InstanceContext::new();
|
||||
assert!(ctx.endpoints().is_none());
|
||||
ctx.set_endpoints(endpoints_with_pools(1));
|
||||
assert_eq!(ctx.endpoints().expect("endpoints set").0.len(), 1);
|
||||
}
|
||||
|
||||
// Two contexts hold independent endpoint topologies.
|
||||
#[tokio::test]
|
||||
async fn distinct_contexts_have_distinct_endpoints() {
|
||||
let ctx_a = InstanceContext::new();
|
||||
let ctx_b = InstanceContext::new();
|
||||
ctx_a.set_endpoints(endpoints_with_pools(1));
|
||||
ctx_b.set_endpoints(endpoints_with_pools(2));
|
||||
assert_eq!(ctx_a.endpoints().expect("a").0.len(), 1);
|
||||
assert_eq!(ctx_b.endpoints().expect("b").0.len(), 2);
|
||||
}
|
||||
|
||||
// Endpoints are write-once: a second set fails fast.
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "initialized once")]
|
||||
async fn set_endpoints_twice_panics() {
|
||||
let ctx = InstanceContext::new();
|
||||
ctx.set_endpoints(endpoints_with_pools(1));
|
||||
ctx.set_endpoints(endpoints_with_pools(2));
|
||||
}
|
||||
|
||||
// The tier config manager is materialized once and shared for the
|
||||
// instance's lifetime — the "create-once, then share" contract of the
|
||||
// eager process static it replaced.
|
||||
#[tokio::test]
|
||||
async fn tier_config_mgr_is_stable_within_an_instance() {
|
||||
let ctx = InstanceContext::new();
|
||||
assert!(
|
||||
Arc::ptr_eq(&ctx.tier_config_mgr(), &ctx.tier_config_mgr()),
|
||||
"repeated access must return the same manager"
|
||||
);
|
||||
}
|
||||
|
||||
// Two contexts own distinct tier config managers.
|
||||
#[tokio::test]
|
||||
async fn distinct_contexts_have_distinct_tier_config_mgr() {
|
||||
let ctx_a = InstanceContext::new();
|
||||
let ctx_b = InstanceContext::new();
|
||||
assert!(
|
||||
!Arc::ptr_eq(&ctx_a.tier_config_mgr(), &ctx_b.tier_config_mgr()),
|
||||
"fresh contexts must not share a tier config manager"
|
||||
);
|
||||
}
|
||||
|
||||
// The event notifier is materialized once and shared within an instance.
|
||||
#[tokio::test]
|
||||
async fn event_notifier_is_stable_within_an_instance() {
|
||||
let ctx = InstanceContext::new();
|
||||
assert!(
|
||||
Arc::ptr_eq(&ctx.event_notifier(), &ctx.event_notifier()),
|
||||
"repeated access must return the same notifier"
|
||||
);
|
||||
}
|
||||
|
||||
// Two contexts own distinct event notifiers.
|
||||
#[tokio::test]
|
||||
async fn distinct_contexts_have_distinct_event_notifier() {
|
||||
let ctx_a = InstanceContext::new();
|
||||
let ctx_b = InstanceContext::new();
|
||||
assert!(
|
||||
!Arc::ptr_eq(&ctx_a.event_notifier(), &ctx_b.event_notifier()),
|
||||
"fresh contexts must not share an event notifier"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,13 @@ use crate::{
|
||||
error::Result,
|
||||
layout::endpoints::{EndpointServerPools, SetupType},
|
||||
runtime::global::{
|
||||
GLOBAL_BOOT_TIME, GLOBAL_EVENT_NOTIFIER, GLOBAL_LIFECYCLE_SYS, GLOBAL_LOCAL_DISK_ID_MAP, GLOBAL_LOCAL_DISK_MAP,
|
||||
GLOBAL_LOCAL_DISK_SET_DRIVES, GLOBAL_LOCAL_NODE_NAME_FALLBACK, GLOBAL_ROOT_DISK_THRESHOLD, GLOBAL_TIER_CONFIG_MGR,
|
||||
TypeLocalDiskSetDrives, get_background_services_cancel_token, get_global_bucket_monitor, get_global_deployment_id,
|
||||
get_global_endpoints, get_global_endpoints_opt, get_global_lock_client, get_global_lock_clients, get_global_region,
|
||||
get_global_tier_config_mgr, global_rustfs_port, init_global_bucket_monitor, is_dist_erasure, is_erasure, is_erasure_sd,
|
||||
is_first_cluster_node_local, resolve_object_store_handle, set_global_deployment_id, set_global_lock_client,
|
||||
set_global_lock_clients, set_object_layer, update_erasure_type,
|
||||
GLOBAL_BOOT_TIME, GLOBAL_LIFECYCLE_SYS, GLOBAL_LOCAL_DISK_ID_MAP, GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES,
|
||||
GLOBAL_LOCAL_NODE_NAME_FALLBACK, GLOBAL_ROOT_DISK_THRESHOLD, TypeLocalDiskSetDrives,
|
||||
get_background_services_cancel_token, get_global_bucket_monitor, get_global_deployment_id, get_global_endpoints,
|
||||
get_global_endpoints_opt, get_global_lock_client, get_global_lock_clients, get_global_region, get_global_tier_config_mgr,
|
||||
global_rustfs_port, init_global_bucket_monitor, is_dist_erasure, is_erasure, is_erasure_sd, is_first_cluster_node_local,
|
||||
resolve_object_store_handle, set_global_deployment_id, set_global_lock_client, set_global_lock_clients, set_object_layer,
|
||||
update_erasure_type,
|
||||
},
|
||||
services::batch_processor::{GlobalBatchProcessors, get_global_processors},
|
||||
services::event_notification::EventNotifier,
|
||||
@@ -387,7 +387,7 @@ pub(crate) fn local_disk_set_drives_handle() -> Arc<RwLock<TypeLocalDiskSetDrive
|
||||
}
|
||||
|
||||
pub(crate) fn tier_config_mgr_handle() -> Arc<RwLock<TierConfigMgr>> {
|
||||
GLOBAL_TIER_CONFIG_MGR.clone()
|
||||
get_global_tier_config_mgr()
|
||||
}
|
||||
|
||||
pub fn expiry_state_handle() -> Arc<RwLock<ExpiryState>> {
|
||||
@@ -399,7 +399,7 @@ pub fn transition_state_handle() -> Arc<TransitionState> {
|
||||
}
|
||||
|
||||
pub(crate) fn event_notifier_handle() -> Arc<RwLock<EventNotifier>> {
|
||||
GLOBAL_EVENT_NOTIFIER.clone()
|
||||
crate::runtime::global::current_ctx().event_notifier()
|
||||
}
|
||||
|
||||
pub(crate) async fn local_disk_by_path(path: &str) -> Option<DiskStore> {
|
||||
@@ -521,7 +521,7 @@ pub(crate) async fn initialize_local_disk_maps(endpoint_pools: EndpointServerPoo
|
||||
}
|
||||
|
||||
pub(crate) async fn init_tier_config_mgr(store: Arc<ECStore>) -> Result<()> {
|
||||
GLOBAL_TIER_CONFIG_MGR.write().await.init(store).await
|
||||
get_global_tier_config_mgr().write().await.init(store).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user