diff --git a/crates/ecstore/src/layout/mod.rs b/crates/ecstore/src/layout/mod.rs index 62c58f125..16ee4be1d 100644 --- a/crates/ecstore/src/layout/mod.rs +++ b/crates/ecstore/src/layout/mod.rs @@ -8,5 +8,6 @@ pub(crate) mod disks_layout; pub(crate) mod endpoint; pub(crate) mod endpoints; pub(crate) mod format; +pub(crate) mod pool_space; pub(crate) mod set_heal; pub(crate) mod set_layout; diff --git a/crates/ecstore/src/layout/pool_space.rs b/crates/ecstore/src/layout/pool_space.rs new file mode 100644 index 000000000..13fc92cc5 --- /dev/null +++ b/crates/ecstore/src/layout/pool_space.rs @@ -0,0 +1,126 @@ +// Copyright 2024 RustFS Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::slice::Iter; + +#[derive(Debug, Default, Clone)] +pub struct PoolAvailableSpace { + pub index: usize, + pub available: u64, // in bytes + pub max_used_pct: u64, // Used disk percentage of most filled disk, rounded down. +} + +#[derive(Debug, Default, Clone)] +pub struct ServerPoolsAvailableSpace(pub(crate) Vec); + +impl ServerPoolsAvailableSpace { + pub fn iter(&self) -> Iter<'_, PoolAvailableSpace> { + self.0.iter() + } + + // TotalAvailable - total available space + pub fn total_available(&self) -> u64 { + let mut total = 0; + for pool in &self.0 { + total += pool.available; + } + total + } + + // FilterMaxUsed will filter out any pools that has used percent bigger than max, + // unless all have that, in which case all are preserved. + pub fn filter_max_used(&mut self, max: u64) { + if self.0.len() <= 1 { + // Nothing to do. + return; + } + let mut ok = false; + for pool in &self.0 { + if pool.available > 0 && pool.max_used_pct < max { + ok = true; + break; + } + } + if !ok { + // All above limit. + // Do not modify + return; + } + + // Remove entries that are above. + for pool in self.0.iter_mut() { + if pool.available > 0 && pool.max_used_pct < max { + continue; + } + pool.available = 0 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pool_available_space_creation_preserves_fields() { + let space = PoolAvailableSpace { + index: 0, + available: 1000, + max_used_pct: 50, + }; + + assert_eq!(space.index, 0); + assert_eq!(space.available, 1000); + assert_eq!(space.max_used_pct, 50); + } + + #[test] + fn server_pools_available_space_filter_keeps_shape_and_updates_availability() { + let mut spaces = ServerPoolsAvailableSpace(vec![ + PoolAvailableSpace { + index: 0, + available: 1000, + max_used_pct: 50, + }, + PoolAvailableSpace { + index: 1, + available: 2000, + max_used_pct: 80, + }, + ]); + + assert_eq!(spaces.total_available(), 3000); + + spaces.filter_max_used(60); + + assert_eq!(spaces.0.len(), 2); + assert_eq!(spaces.0[0].index, 0); + assert_eq!(spaces.0[0].available, 1000); + assert_eq!(spaces.0[1].available, 0); + assert_eq!(spaces.total_available(), 1000); + } + + #[test] + fn server_pools_available_space_iter_preserves_pool_order() { + let spaces = ServerPoolsAvailableSpace(vec![PoolAvailableSpace { + index: 0, + available: 1000, + max_used_pct: 50, + }]); + + let indexes = spaces.iter().map(|space| space.index).collect::>(); + + assert_eq!(indexes, vec![0]); + } +} diff --git a/crates/ecstore/src/store.rs b/crates/ecstore/src/store.rs index daa89f5b2..f8bbc1fb6 100644 --- a/crates/ecstore/src/store.rs +++ b/crates/ecstore/src/store.rs @@ -87,7 +87,6 @@ use s3s::dto::{BucketVersioningStatus, ObjectLockConfiguration, ObjectLockEnable use std::cmp::Ordering; use std::net::SocketAddr; use std::process::exit; -use std::slice::Iter; use std::time::SystemTime; use std::{ collections::HashMap, @@ -107,6 +106,8 @@ type ListObjectVersionsInfo = StorageListObjectVersionsInfo; type ObjectInfoOrErr = StorageObjectInfoOrErr; type WalkOptions = StorageWalkOptions bool>; +pub use crate::layout::pool_space::{PoolAvailableSpace, ServerPoolsAvailableSpace}; + /// Check if a directory contains any xl.meta files (indicating actual S3 objects) /// This is used to determine if a bucket is empty for deletion purposes. async fn has_xlmeta_files(path: &std::path::Path) -> bool { @@ -791,59 +792,6 @@ impl rustfs_storage_api::StorageAdminApi for ECStore { } } -#[derive(Debug, Default, Clone)] -pub struct PoolAvailableSpace { - pub index: usize, - pub available: u64, // in bytes - pub max_used_pct: u64, // Used disk percentage of most filled disk, rounded down. -} - -#[derive(Debug, Default, Clone)] -pub struct ServerPoolsAvailableSpace(Vec); - -impl ServerPoolsAvailableSpace { - pub fn iter(&self) -> Iter<'_, PoolAvailableSpace> { - self.0.iter() - } - // TotalAvailable - total available space - pub fn total_available(&self) -> u64 { - let mut total = 0; - for pool in &self.0 { - total += pool.available; - } - total - } - - // FilterMaxUsed will filter out any pools that has used percent bigger than max, - // unless all have that, in which case all are preserved. - pub fn filter_max_used(&mut self, max: u64) { - if self.0.len() <= 1 { - // Nothing to do. - return; - } - let mut ok = false; - for pool in &self.0 { - if pool.available > 0 && pool.max_used_pct < max { - ok = true; - break; - } - } - if !ok { - // All above limit. - // Do not modify - return; - } - - // Remove entries that are above. - for pool in self.0.iter_mut() { - if pool.available > 0 && pool.max_used_pct < max { - continue; - } - pool.available = 0 - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -872,32 +820,6 @@ mod tests { assert!(result.is_err()); } - #[test] - fn test_server_pools_available_space() { - let mut spaces = ServerPoolsAvailableSpace(vec![ - PoolAvailableSpace { - index: 0, - available: 1000, - max_used_pct: 50, - }, - PoolAvailableSpace { - index: 1, - available: 2000, - max_used_pct: 80, - }, - ]); - - assert_eq!(spaces.total_available(), 3000); - - spaces.filter_max_used(60); - // filter_max_used sets available to 0 for filtered pools, doesn't remove them - assert_eq!(spaces.0.len(), 2); // Length remains the same - assert_eq!(spaces.0[0].index, 0); - assert_eq!(spaces.0[0].available, 1000); // First pool should still be available - assert_eq!(spaces.0[1].available, 0); // Second pool should be filtered (available = 0) - assert_eq!(spaces.total_available(), 1000); // Only first pool contributes to total - } - #[tokio::test] async fn test_find_local_disk() { let result = find_local_disk(&"/nonexistent/path".to_string()).await; @@ -995,33 +917,4 @@ mod tests { assert!(!should_enqueue_transition_immediately(&oi)); } - - // Test that we can create the basic structures without global state - #[test] - fn test_pool_available_space_creation() { - let space = PoolAvailableSpace { - index: 0, - available: 1000, - max_used_pct: 50, - }; - assert_eq!(space.index, 0); - assert_eq!(space.available, 1000); - assert_eq!(space.max_used_pct, 50); - } - - #[test] - fn test_server_pools_available_space_iter() { - let spaces = ServerPoolsAvailableSpace(vec![PoolAvailableSpace { - index: 0, - available: 1000, - max_used_pct: 50, - }]); - - let mut count = 0; - for space in spaces.iter() { - assert_eq!(space.index, 0); - count += 1; - } - assert_eq!(count, 1); - } } diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 232c8b74f..bc3471073 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,16 +5,16 @@ 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-ecstore-layout-set-heal-helpers` -- Baseline: merged `E-003/E-LAYOUT-002`. +- Branch: `overtrue/arch-ecstore-layout-pool-space` +- Baseline: completed `E-004/E-LAYOUT-003`. - Stacked on: merged ECStore layout foundation, format layout ownership, and - endpoint layout move slices. + endpoint layout move slices plus the set-format heal helper layout slice. - PR type for this branch: `pure-move` - Runtime behavior changes: none. -- Rust code changes: move runtime-neutral ECStore set-format heal helpers into - the internal layout bucket while preserving `Sets` runtime orchestration. +- Rust code changes: move ECStore pool-space selection helper types into the + internal layout bucket while preserving the old `store` export path. - CI/script changes: none. -- Docs changes: record the ECStore set-format heal helper layout slice. +- Docs changes: record the ECStore pool-space helper layout slice. ## Phase 0 Tasks @@ -2267,10 +2267,25 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, branch freshness check, pre-commit quality gate, and three-expert review. +- [x] `E-005/E-LAYOUT-004` Move ECStore pool-space selection helpers. + - Do: move runtime-neutral pool-space selection helper structs into the + ECStore layout bucket while keeping the old `store` export path available. + - Acceptance: `layout::pool_space` owns `PoolAvailableSpace` and + `ServerPoolsAvailableSpace`, rebalance pool selection keeps the same tuple + storage access inside the crate, and external `store` imports remain + source-compatible through re-export. + - Must preserve: pool index ordering, available-space summation, + max-used-percent filtering semantics, excluded-pool zeroing, object + placement pool selection, and rebalance pool-space behavior. + - Verification: focused ECStore pool-space tests, ECStore/RustFS/Heal + compile checks, migration/layer guards, formatting, diff hygiene, Rust risk + scan, branch freshness check, pre-commit quality gate, and three-expert + review. + ## Next PRs 1. `pure-move`: continue moving runtime-neutral pool/set layout helpers once - E-004/E-LAYOUT-003 lands. + E-005/E-LAYOUT-004 lands. 2. `pure-move`: continue pruning residual embedded startup-only orchestration once the lifecycle helpers are merged. @@ -2278,9 +2293,9 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | E-004/E-LAYOUT-003 extracts runtime-neutral set-format heal helpers into ECStore layout without moving disk init or `Sets` orchestration. | -| Migration preservation | passed | Drive-info state mapping, unformatted format regeneration, current disk-info preservation, dry-run/save behavior, and `Sets` runtime control flow remain preserved. | -| Testing/verification | passed | Focused set-heal helper tests, compile checks, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. | +| Quality/architecture | passed | E-005/E-LAYOUT-004 extracts runtime-neutral pool-space helper types into ECStore layout without moving rebalance or store orchestration. | +| Migration preservation | passed | Pool index ordering, available-space summation, max-used-percent filtering, excluded-pool zeroing, and old `store` import compatibility remain preserved. | +| Testing/verification | passed | Focused pool-space helper tests, compile checks, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. | ## Verification Notes @@ -2380,6 +2395,19 @@ Passed before push: - `make pre-commit`: passed. - Three-expert review: passed. +- Issue #660 E-005/E-LAYOUT-004 current slice: + - `cargo test -p rustfs-ecstore layout::pool_space -- --nocapture`: passed. + - `cargo check -p rustfs-ecstore -p rustfs -p rustfs-heal`: passed. + - `./scripts/check_architecture_migration_rules.sh`: passed. + - `./scripts/check_layer_dependencies.sh`: passed. + - `cargo fmt --all --check`: passed. + - `git diff --check`: passed. + - Rust risk scan on changed Rust files: passed; only existing `store.rs` + test-only `expect` calls and an existing `Result` method signature + were present outside the moved helper body. + - `make pre-commit`: passed. + - Three-expert review: passed. + - Issue #660 X-012 current slice: - `cargo test -p rustfs-extension-schema`: passed. - `cargo check -p rustfs-extension-schema`: passed.