diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index b24be8986..f17390487 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,22 +5,17 @@ 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-rebalance-entry-flow` -- Baseline: completed `E-015/E-REBALANCE-009`. -- Stacked on: merged ECStore layout foundation, format layout ownership, and - endpoint layout move slices plus the set-format heal, pool-space layout - helper, rebalance support helper, pool-space builder, rebalance metadata - helper, rebalance worker helper, rebalance migration helper, rebalance state - impl, rebalance control impl, rebalance runtime loop, rebalance entry flow, - rebalance unit-test split, and rebalance type-owner slices. +- Branch: `overtrue/arch-embedded-builder-startup-context` +- Baseline: completed `R-034/R-035`. +- Stacked on: embedded runtime hook and shutdown glue extraction slices. - PR type for this branch: `pure-move` - Runtime behavior changes: none. -- Rust code changes: move ECStore rebalance control, runtime, entry flow, - inline tests, and type contracts into dedicated rebalance submodules while - preserving public root paths. +- Rust code changes: move embedded startup config preparation and S3-only HTTP + server startup into startup server helpers while preserving embedded builder + behavior. - CI/script changes: none. -- Docs changes: record the combined ECStore rebalance control/runtime/entry - flow, test-module split, and type-owner split slices. +- Docs changes: record the embedded startup config and HTTP startup owner + slices. ## Phase 0 Tasks @@ -2245,6 +2240,32 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block migration/layer guards, formatting, diff hygiene, Rust risk scan, branch freshness check, pre-commit quality gate, and three-expert review. +- [x] `R-036` Extract embedded startup config preparation boundary. + - Do: move embedded temporary volume allocation, custom volume directory + creation, and embedded `Config` construction behind startup server helpers. + - Acceptance: embedded builder still creates a temporary volume when none is + provided, creates missing custom volume directories, disables console for + embedded S3 startup, and keeps the temp-dir guard alive until success. + - Must preserve: temp-dir cleanup-on-failure behavior, configured address, + access key, secret key, region, volume ordering, directory creation error + text, and no new normal startup behavior. + - Verification: focused startup server and embedded checks, RustFS lib check, + migration/layer guards, formatting, diff hygiene, Rust risk scan, branch + freshness check, pre-commit quality gate, and three-expert review. + +- [x] `R-037` Extract embedded S3-only HTTP startup boundary. + - Do: move embedded S3-only HTTP server startup behind a startup server + helper that returns the bound address and shutdown handle. + - Acceptance: embedded startup keeps console disabled for the HTTP server, + keeps using the same readiness object, and preserves the shutdown handle + and bound address used by `RustFSServer`. + - Must preserve: S3-only embedded HTTP config, readiness sharing, startup + error propagation, shutdown signaling, bound endpoint reporting, and no + public embedded API behavior changes. + - Verification: focused startup server and embedded checks, RustFS lib check, + migration/layer guards, formatting, diff hygiene, Rust risk scan, branch + freshness check, pre-commit quality gate, and three-expert review. + - [x] `E-001/E-SET-001` Add ECStore layout skeleton and set-layout boundary. - Do: create the ECStore internal layout ownership buckets and pin static set layout versus runtime `Sets`/`SetDisks` orchestration boundaries before any @@ -2488,20 +2509,33 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block ## Next PRs 1. `pure-move`: continue pruning residual embedded startup-only orchestration - after the runtime hook and shutdown glue boundaries land. + after the builder config and S3-only HTTP startup boundaries land. ## Pre-Push Review Log | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | R-034 and R-035 move embedded runtime hooks and async shutdown glue into startup owners while keeping the embedded builder focused on startup orchestration. | -| Migration preservation | passed | Observability error prefixes, embedded crypto log fields, trusted-proxy timing, cancellation, audit cleanup, HTTP shutdown, temp-dir cleanup, and stopped logs stay in the same order. | -| Testing/verification | passed | Focused runtime/embedded checks, RustFS lib check, architecture/layer guards, formatting, diff hygiene, Rust risk scan, and pre-commit gate passed. | +| Quality/architecture | passed | R-036 and R-037 move embedded builder config preparation and S3-only HTTP startup into startup server helpers without exposing new public API. | +| Migration preservation | passed | Temp-dir guard lifetime, volume directory creation, embedded config fields, console-disabled S3 startup, readiness sharing, shutdown handle, and bound address behavior stay unchanged. | +| Testing/verification | passed | Focused startup server/embedded checks, RustFS lib check, architecture/layer guards, formatting, diff hygiene, Rust risk scan, and pre-commit gate passed. | ## Verification Notes Passed before push: +- Issue #660 R-036/R-037 current slice: + - `cargo test -p rustfs --lib startup_server -- --nocapture`: passed. + - `cargo test -p rustfs --lib embedded -- --nocapture`: passed. + - `cargo check -p rustfs --lib`: 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 test-only + `expect` calls and the existing embedded temp-dir cleanup path were + present. + - `make pre-commit`: passed. + - Issue #660 R-034/R-035 current slice: - `cargo test -p rustfs --lib startup_runtime_hooks -- --nocapture`: passed. diff --git a/rustfs/src/embedded.rs b/rustfs/src/embedded.rs index 0030a1d52..92eff5476 100644 --- a/rustfs/src/embedded.rs +++ b/rustfs/src/embedded.rs @@ -46,16 +46,17 @@ //! storage engine uses process-global singletons (`OnceLock`). Attempting to //! start a second server will return an error. -use crate::config::Config; -use crate::server::{ShutdownHandle, start_http_server}; +use crate::server::ShutdownHandle; use crate::startup_lifecycle::{log_embedded_server_ready, publish_embedded_startup_ready}; use crate::startup_runtime_hooks::init_embedded_runtime_hooks; -use crate::startup_server::init_embedded_startup_listen_context; +use crate::startup_server::{ + EmbeddedStartupConfig, init_embedded_startup_listen_context, prepare_embedded_startup_config, start_embedded_http_server, +}; use crate::startup_services::init_embedded_startup_runtime_services; use crate::startup_shutdown::run_embedded_server_shutdown; use crate::startup_storage::{init_embedded_startup_storage_foundation, init_embedded_startup_storage_runtime}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use tokio_util::sync::CancellationToken; @@ -237,31 +238,15 @@ impl RustFSServerBuilder { Ok(()) }; - // Keep a TempDir guard alive so that if build fails the directory is - // cleaned up automatically. We disarm (keep) on success. - let mut temp_dir_guard: Option = None; - if self.volumes.is_empty() { - let dir = tempfile::tempdir().map_err(|e| ServerError::Init(format!("failed to create temp dir: {e}")))?; - self.volumes.push(dir.path().display().to_string()); - temp_dir_guard = Some(dir); - } - - // Ensure volume directories exist. - for v in &self.volumes { - let p = Path::new(v); - if !p.exists() { - tokio::fs::create_dir_all(p) - .await - .map_err(|e| ServerError::Init(format!("failed to create volume dir {v}: {e}")))?; - } - } - - // Build Config. - let mut config = Config::new(&self.address, self.volumes.clone()); - config.access_key = self.access_key.clone(); - config.secret_key = self.secret_key.clone(); - config.region = Some(self.region.clone()); - config.console_enable = false; + let EmbeddedStartupConfig { config, temp_dir_guard } = prepare_embedded_startup_config( + self.address.clone(), + self.access_key.clone(), + self.secret_key.clone(), + self.volumes.clone(), + self.region.clone(), + ) + .await + .map_err(|e| ServerError::Init(e.to_string()))?; // --- Initialization sequence (mirrors main.rs::run) --- @@ -279,10 +264,9 @@ impl RustFSServerBuilder { .await .map_err(|e| ServerError::Init(e.to_string()))?; - // Start HTTP server. - let mut s3_config = config.clone(); - s3_config.console_enable = false; - let (shutdown_handle, bound_addr) = start_http_server(&s3_config, listen_context.readiness.clone()).await?; + let http_server = start_embedded_http_server(&config, listen_context.readiness.clone()).await?; + let shutdown_handle = http_server.shutdown_handle; + let bound_addr = http_server.bound_addr; let ctx = CancellationToken::new(); let shutdown_embedded_server = || { shutdown_handle.signal(); diff --git a/rustfs/src/startup_server.rs b/rustfs/src/startup_server.rs index 6af4ff2f8..baab7232b 100644 --- a/rustfs/src/startup_server.rs +++ b/rustfs/src/startup_server.rs @@ -24,8 +24,10 @@ use rustfs_utils::net::parse_and_resolve_address; use std::{ io::{Error, Result}, net::SocketAddr, + path::Path, sync::Arc, }; +use tempfile::TempDir; use tracing::{debug, error, info, warn}; const LOG_COMPONENT_MAIN: &str = "main"; @@ -50,6 +52,16 @@ pub struct EmbeddedStartupListenContext { pub server_address: String, } +pub(crate) struct EmbeddedStartupConfig { + pub config: Config, + pub(crate) temp_dir_guard: Option, +} + +pub(crate) struct EmbeddedHttpServer { + pub shutdown_handle: ShutdownHandle, + pub bound_addr: SocketAddr, +} + pub struct StartupHttpServers { pub state_manager: Arc, pub s3_shutdown_tx: Option, @@ -105,6 +117,38 @@ pub async fn init_startup_listen_context(config: &Config) -> Result, + region: String, +) -> Result { + let mut temp_dir_guard = None; + if volumes.is_empty() { + let dir = tempfile::tempdir().map_err(|err| Error::other(format!("failed to create temp dir: {err}")))?; + volumes.push(dir.path().display().to_string()); + temp_dir_guard = Some(dir); + } + + for volume in &volumes { + let path = Path::new(volume); + if !path.exists() { + tokio::fs::create_dir_all(path) + .await + .map_err(|err| Error::other(format!("failed to create volume dir {volume}: {err}")))?; + } + } + + let mut config = Config::new(&address, volumes); + config.access_key = access_key; + config.secret_key = secret_key; + config.region = Some(region); + config.console_enable = false; + + Ok(EmbeddedStartupConfig { config, temp_dir_guard }) +} + pub async fn init_embedded_startup_listen_context(config: &Config) -> Result { let readiness = Arc::new(GlobalReadiness::new()); @@ -138,6 +182,16 @@ pub async fn init_embedded_startup_listen_context(config: &Config) -> Result) -> Result { + let s3_config = s3_http_server_config(config); + let (shutdown_handle, bound_addr) = start_http_server(&s3_config, readiness).await?; + + Ok(EmbeddedHttpServer { + shutdown_handle, + bound_addr, + }) +} + pub async fn init_startup_http_servers(config: &Config, readiness: Arc) -> Result { init_capacity_management().await; let state_manager = Arc::new(ServiceStateManager::new()); @@ -224,7 +278,9 @@ fn console_http_server_config(config: &Config) -> Option { #[cfg(test)] mod tests { - use super::{DEFAULT_CREDENTIALS_WARNING_MESSAGE, console_http_server_config, s3_http_server_config}; + use super::{ + DEFAULT_CREDENTIALS_WARNING_MESSAGE, console_http_server_config, prepare_embedded_startup_config, s3_http_server_config, + }; use crate::config::Config; #[test] @@ -292,4 +348,46 @@ mod tests { assert!(!DEFAULT_CREDENTIALS_WARNING_MESSAGE.contains(rustfs_credentials::DEFAULT_ACCESS_KEY)); assert!(!DEFAULT_CREDENTIALS_WARNING_MESSAGE.contains(rustfs_credentials::DEFAULT_SECRET_KEY)); } + + #[tokio::test] + async fn prepare_embedded_startup_config_creates_temp_volume_when_missing() { + let prepared = prepare_embedded_startup_config( + "127.0.0.1:9000".to_string(), + "access".to_string(), + "secret".to_string(), + Vec::new(), + "us-west-2".to_string(), + ) + .await + .expect("embedded startup config should be prepared"); + + assert_eq!(prepared.config.address, "127.0.0.1:9000"); + assert_eq!(prepared.config.access_key, "access"); + assert_eq!(prepared.config.secret_key, "secret"); + assert_eq!(prepared.config.region.as_deref(), Some("us-west-2")); + assert!(!prepared.config.console_enable); + assert_eq!(prepared.config.volumes.len(), 1); + assert!(std::path::Path::new(&prepared.config.volumes[0]).exists()); + assert!(prepared.temp_dir_guard.is_some()); + } + + #[tokio::test] + async fn prepare_embedded_startup_config_creates_missing_custom_volume() { + let parent = tempfile::tempdir().expect("temp parent"); + let volume = parent.path().join("data"); + + let prepared = prepare_embedded_startup_config( + "127.0.0.1:9000".to_string(), + "access".to_string(), + "secret".to_string(), + vec![volume.display().to_string()], + "us-east-1".to_string(), + ) + .await + .expect("embedded startup config should create custom volume"); + + assert_eq!(prepared.config.volumes, vec![volume.display().to_string()]); + assert!(volume.exists()); + assert!(prepared.temp_dir_guard.is_none()); + } }