mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 16:07:05 +00:00
refactor(ecstore): migrate the S3 region into InstanceContext (Phase 5 Slice 4) (#4462)
Phase 5 Slice 4 (backlog#939): move the S3 region — a write-once identity scalar — out of the GLOBAL_REGION process static into the per-instance InstanceContext, so two instances can serve different regions. - InstanceContext gains `region: OnceLock<Region>` with `set_region()` / `region()`. `set_region` keeps the write-once fail-fast contract: a second write panics, exactly as the process global did (not downgraded to a warn). - global.rs `set_global_region` / `get_global_region` keep their signatures and forward to the current instance's context; the GLOBAL_REGION static is removed. Single-instance: startup writes the bootstrap context (which the ECStore adopts), so reads are unchanged. Tests: set/get round-trip, two contexts hold distinct regions, and a second set_region panics (fail-fast preserved). Verification: cargo test -p rustfs-ecstore (9 instance-context tests green), cargo clippy -p rustfs-ecstore --all-targets (clean), make pre-commit (pass). Refs: backlog#939 (Phase 5, Slice 4). Stacked on Slice 3 (#4417).
This commit is contained in:
@@ -45,11 +45,11 @@ pub const DISK_RESERVE_FRACTION: f64 = 0.15;
|
|||||||
//
|
//
|
||||||
// Tier A (needs migration): GLOBAL_OBJECT_API, GLOBAL_LOCAL_DISK_*,
|
// Tier A (needs migration): GLOBAL_OBJECT_API, GLOBAL_LOCAL_DISK_*,
|
||||||
// GLOBAL_ROOT_DISK_THRESHOLD, GLOBAL_LIFECYCLE_SYS, GLOBAL_EVENT_NOTIFIER, etc.
|
// GLOBAL_ROOT_DISK_THRESHOLD, GLOBAL_LIFECYCLE_SYS, GLOBAL_EVENT_NOTIFIER, etc.
|
||||||
// Tier B (keep as static): GLOBAL_RUSTFS_PORT, GLOBAL_REGION, env var caches, etc.
|
// Tier B (keep as static): GLOBAL_RUSTFS_PORT, env var caches, etc.
|
||||||
//
|
//
|
||||||
// Phase 5 (backlog#939): the erasure setup type moved into the per-instance
|
// Phase 5 (backlog#939): the erasure setup type and the S3 region moved into
|
||||||
// `InstanceContext` (see `super::instance`); the erasure predicates below now
|
// the per-instance `InstanceContext` (see `super::instance`); their facades
|
||||||
// forward to the current instance's context.
|
// below now forward to the current instance's context.
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref GLOBAL_RUSTFS_PORT: OnceLock<u16> = OnceLock::new();
|
static ref GLOBAL_RUSTFS_PORT: OnceLock<u16> = OnceLock::new();
|
||||||
static ref GLOBAL_DEPLOYMENT_ID: OnceLock<Uuid> = OnceLock::new();
|
static ref GLOBAL_DEPLOYMENT_ID: OnceLock<Uuid> = OnceLock::new();
|
||||||
@@ -66,7 +66,6 @@ lazy_static! {
|
|||||||
pub static ref GLOBAL_LOCAL_NODE_NAME_FALLBACK: String = "127.0.0.1:9000".to_string();
|
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 =
|
pub static ref GLOBAL_LOCAL_NODE_NAME_HEX_FALLBACK: String =
|
||||||
rustfs_utils::crypto::hex(GLOBAL_LOCAL_NODE_NAME_FALLBACK.as_bytes());
|
rustfs_utils::crypto::hex(GLOBAL_LOCAL_NODE_NAME_FALLBACK.as_bytes());
|
||||||
pub static ref GLOBAL_REGION: OnceLock<s3s::region::Region> = OnceLock::new();
|
|
||||||
pub static ref GLOBAL_LOCAL_LOCK_CLIENT: OnceLock<Arc<dyn LockClient>> = OnceLock::new();
|
pub static ref GLOBAL_LOCAL_LOCK_CLIENT: OnceLock<Arc<dyn LockClient>> = OnceLock::new();
|
||||||
pub static ref GLOBAL_LOCK_CLIENTS: OnceLock<HashMap<String, Arc<dyn LockClient>>> = OnceLock::new();
|
pub static ref GLOBAL_LOCK_CLIENTS: OnceLock<HashMap<String, Arc<dyn LockClient>>> = OnceLock::new();
|
||||||
pub static ref GLOBAL_BUCKET_MONITOR: OnceLock<Arc<Monitor>> = OnceLock::new();
|
pub static ref GLOBAL_BUCKET_MONITOR: OnceLock<Arc<Monitor>> = OnceLock::new();
|
||||||
@@ -291,9 +290,7 @@ pub(crate) type TypeLocalDiskSetDrives = Vec<Vec<Vec<Option<DiskStore>>>>;
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// * None
|
/// * None
|
||||||
pub fn set_global_region(region: s3s::region::Region) {
|
pub fn set_global_region(region: s3s::region::Region) {
|
||||||
GLOBAL_REGION
|
current_ctx().set_region(region);
|
||||||
.set(region)
|
|
||||||
.expect("GLOBAL_REGION should be initialized once during startup");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the global region
|
/// Get the global region
|
||||||
@@ -302,7 +299,7 @@ pub fn set_global_region(region: s3s::region::Region) {
|
|||||||
/// * `Option<s3s::region::Region>` - The global region, if set
|
/// * `Option<s3s::region::Region>` - The global region, if set
|
||||||
///
|
///
|
||||||
pub fn get_global_region() -> Option<s3s::region::Region> {
|
pub fn get_global_region() -> Option<s3s::region::Region> {
|
||||||
GLOBAL_REGION.get().cloned()
|
current_ctx().region()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the global background services cancellation token
|
/// Initialize the global background services cancellation token
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
use crate::layout::endpoints::SetupType;
|
use crate::layout::endpoints::SetupType;
|
||||||
use rustfs_lock::{GlobalLockManager, get_global_lock_manager};
|
use rustfs_lock::{GlobalLockManager, get_global_lock_manager};
|
||||||
|
use s3s::region::Region;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
@@ -66,6 +67,11 @@ pub struct InstanceContext {
|
|||||||
/// contend). Single-instance aliases the process singleton, so behavior is
|
/// contend). Single-instance aliases the process singleton, so behavior is
|
||||||
/// unchanged; see [`bootstrap_ctx`].
|
/// unchanged; see [`bootstrap_ctx`].
|
||||||
lock_manager: Arc<GlobalLockManager>,
|
lock_manager: Arc<GlobalLockManager>,
|
||||||
|
/// This instance's S3 region (Phase 5 Slice 4, backlog#939).
|
||||||
|
///
|
||||||
|
/// 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceContext {
|
impl InstanceContext {
|
||||||
@@ -85,6 +91,7 @@ impl InstanceContext {
|
|||||||
Self {
|
Self {
|
||||||
erasure_kind: RwLock::new(SetupType::Unknown),
|
erasure_kind: RwLock::new(SetupType::Unknown),
|
||||||
lock_manager,
|
lock_manager,
|
||||||
|
region: OnceLock::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +100,21 @@ impl InstanceContext {
|
|||||||
self.lock_manager.clone()
|
self.lock_manager.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set this instance's S3 region.
|
||||||
|
///
|
||||||
|
/// Write-once: panics on a second write, preserving the startup fail-fast
|
||||||
|
/// contract of the process global it replaces.
|
||||||
|
pub fn set_region(&self, region: Region) {
|
||||||
|
self.region
|
||||||
|
.set(region)
|
||||||
|
.expect("instance region should be initialized once during startup");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This instance's S3 region, if it has been set.
|
||||||
|
pub fn region(&self) -> Option<Region> {
|
||||||
|
self.region.get().cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Update this instance's erasure setup type.
|
/// Update this instance's erasure setup type.
|
||||||
pub async fn update_erasure_type(&self, setup_type: SetupType) {
|
pub async fn update_erasure_type(&self, setup_type: SetupType) {
|
||||||
*self.erasure_kind.write().await = setup_type;
|
*self.erasure_kind.write().await = setup_type;
|
||||||
@@ -229,4 +251,38 @@ mod tests {
|
|||||||
"fresh contexts must not share a lock manager"
|
"fresh contexts must not share a lock manager"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn region(name: &str) -> Region {
|
||||||
|
name.parse().expect("valid test region")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fresh context has no region until set; set-then-get round-trips.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn region_set_and_get() {
|
||||||
|
let ctx = InstanceContext::new();
|
||||||
|
assert_eq!(ctx.region(), None);
|
||||||
|
ctx.set_region(region("us-east-1"));
|
||||||
|
assert_eq!(ctx.region(), Some(region("us-east-1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two contexts hold independent regions.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn distinct_contexts_have_distinct_region() {
|
||||||
|
let ctx_a = InstanceContext::new();
|
||||||
|
let ctx_b = InstanceContext::new();
|
||||||
|
ctx_a.set_region(region("us-east-1"));
|
||||||
|
ctx_b.set_region(region("eu-west-1"));
|
||||||
|
assert_eq!(ctx_a.region(), Some(region("us-east-1")));
|
||||||
|
assert_eq!(ctx_b.region(), Some(region("eu-west-1")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The region is write-once: a second set fails fast, preserving the
|
||||||
|
// startup contract of the process global it replaced.
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "initialized once")]
|
||||||
|
async fn set_region_twice_panics() {
|
||||||
|
let ctx = InstanceContext::new();
|
||||||
|
ctx.set_region(region("us-east-1"));
|
||||||
|
ctx.set_region(region("eu-west-1"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user