mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-01 09:48:20 +00:00
refactor: extract embedded startup server helpers (#3660)
This commit is contained in:
@@ -5,22 +5,17 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
|||||||
## Current Context
|
## Current Context
|
||||||
|
|
||||||
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
||||||
- Branch: `overtrue/arch-ecstore-rebalance-entry-flow`
|
- Branch: `overtrue/arch-embedded-builder-startup-context`
|
||||||
- Baseline: completed `E-015/E-REBALANCE-009`.
|
- Baseline: completed `R-034/R-035`.
|
||||||
- Stacked on: merged ECStore layout foundation, format layout ownership, and
|
- Stacked on: embedded runtime hook and shutdown glue extraction slices.
|
||||||
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.
|
|
||||||
- PR type for this branch: `pure-move`
|
- PR type for this branch: `pure-move`
|
||||||
- Runtime behavior changes: none.
|
- Runtime behavior changes: none.
|
||||||
- Rust code changes: move ECStore rebalance control, runtime, entry flow,
|
- Rust code changes: move embedded startup config preparation and S3-only HTTP
|
||||||
inline tests, and type contracts into dedicated rebalance submodules while
|
server startup into startup server helpers while preserving embedded builder
|
||||||
preserving public root paths.
|
behavior.
|
||||||
- CI/script changes: none.
|
- CI/script changes: none.
|
||||||
- Docs changes: record the combined ECStore rebalance control/runtime/entry
|
- Docs changes: record the embedded startup config and HTTP startup owner
|
||||||
flow, test-module split, and type-owner split slices.
|
slices.
|
||||||
|
|
||||||
## Phase 0 Tasks
|
## 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
|
migration/layer guards, formatting, diff hygiene, Rust risk scan, branch
|
||||||
freshness check, pre-commit quality gate, and three-expert review.
|
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.
|
- [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
|
- Do: create the ECStore internal layout ownership buckets and pin static set
|
||||||
layout versus runtime `Sets`/`SetDisks` orchestration boundaries before any
|
layout versus runtime `Sets`/`SetDisks` orchestration boundaries before any
|
||||||
@@ -2488,20 +2509,33 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
|||||||
## Next PRs
|
## Next PRs
|
||||||
|
|
||||||
1. `pure-move`: continue pruning residual embedded startup-only orchestration
|
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
|
## Pre-Push Review Log
|
||||||
|
|
||||||
| Expert | Status | Notes |
|
| 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. |
|
| 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 | 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. |
|
| 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 runtime/embedded checks, RustFS lib check, architecture/layer guards, formatting, diff hygiene, Rust risk scan, and pre-commit gate passed. |
|
| 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
|
## Verification Notes
|
||||||
|
|
||||||
Passed before push:
|
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:
|
- Issue #660 R-034/R-035 current slice:
|
||||||
- `cargo test -p rustfs --lib startup_runtime_hooks -- --nocapture`:
|
- `cargo test -p rustfs --lib startup_runtime_hooks -- --nocapture`:
|
||||||
passed.
|
passed.
|
||||||
|
|||||||
+17
-33
@@ -46,16 +46,17 @@
|
|||||||
//! storage engine uses process-global singletons (`OnceLock`). Attempting to
|
//! storage engine uses process-global singletons (`OnceLock`). Attempting to
|
||||||
//! start a second server will return an error.
|
//! start a second server will return an error.
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::server::ShutdownHandle;
|
||||||
use crate::server::{ShutdownHandle, start_http_server};
|
|
||||||
use crate::startup_lifecycle::{log_embedded_server_ready, publish_embedded_startup_ready};
|
use crate::startup_lifecycle::{log_embedded_server_ready, publish_embedded_startup_ready};
|
||||||
use crate::startup_runtime_hooks::init_embedded_runtime_hooks;
|
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_services::init_embedded_startup_runtime_services;
|
||||||
use crate::startup_shutdown::run_embedded_server_shutdown;
|
use crate::startup_shutdown::run_embedded_server_shutdown;
|
||||||
use crate::startup_storage::{init_embedded_startup_storage_foundation, init_embedded_startup_storage_runtime};
|
use crate::startup_storage::{init_embedded_startup_storage_foundation, init_embedded_startup_storage_runtime};
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
@@ -237,31 +238,15 @@ impl RustFSServerBuilder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keep a TempDir guard alive so that if build fails the directory is
|
let EmbeddedStartupConfig { config, temp_dir_guard } = prepare_embedded_startup_config(
|
||||||
// cleaned up automatically. We disarm (keep) on success.
|
self.address.clone(),
|
||||||
let mut temp_dir_guard: Option<tempfile::TempDir> = None;
|
self.access_key.clone(),
|
||||||
if self.volumes.is_empty() {
|
self.secret_key.clone(),
|
||||||
let dir = tempfile::tempdir().map_err(|e| ServerError::Init(format!("failed to create temp dir: {e}")))?;
|
self.volumes.clone(),
|
||||||
self.volumes.push(dir.path().display().to_string());
|
self.region.clone(),
|
||||||
temp_dir_guard = Some(dir);
|
)
|
||||||
}
|
.await
|
||||||
|
.map_err(|e| ServerError::Init(e.to_string()))?;
|
||||||
// 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;
|
|
||||||
|
|
||||||
// --- Initialization sequence (mirrors main.rs::run) ---
|
// --- Initialization sequence (mirrors main.rs::run) ---
|
||||||
|
|
||||||
@@ -279,10 +264,9 @@ impl RustFSServerBuilder {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| ServerError::Init(e.to_string()))?;
|
.map_err(|e| ServerError::Init(e.to_string()))?;
|
||||||
|
|
||||||
// Start HTTP server.
|
let http_server = start_embedded_http_server(&config, listen_context.readiness.clone()).await?;
|
||||||
let mut s3_config = config.clone();
|
let shutdown_handle = http_server.shutdown_handle;
|
||||||
s3_config.console_enable = false;
|
let bound_addr = http_server.bound_addr;
|
||||||
let (shutdown_handle, bound_addr) = start_http_server(&s3_config, listen_context.readiness.clone()).await?;
|
|
||||||
let ctx = CancellationToken::new();
|
let ctx = CancellationToken::new();
|
||||||
let shutdown_embedded_server = || {
|
let shutdown_embedded_server = || {
|
||||||
shutdown_handle.signal();
|
shutdown_handle.signal();
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ use rustfs_utils::net::parse_and_resolve_address;
|
|||||||
use std::{
|
use std::{
|
||||||
io::{Error, Result},
|
io::{Error, Result},
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
|
path::Path,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
use tempfile::TempDir;
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
const LOG_COMPONENT_MAIN: &str = "main";
|
const LOG_COMPONENT_MAIN: &str = "main";
|
||||||
@@ -50,6 +52,16 @@ pub struct EmbeddedStartupListenContext {
|
|||||||
pub server_address: String,
|
pub server_address: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct EmbeddedStartupConfig {
|
||||||
|
pub config: Config,
|
||||||
|
pub(crate) temp_dir_guard: Option<TempDir>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct EmbeddedHttpServer {
|
||||||
|
pub shutdown_handle: ShutdownHandle,
|
||||||
|
pub bound_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct StartupHttpServers {
|
pub struct StartupHttpServers {
|
||||||
pub state_manager: Arc<ServiceStateManager>,
|
pub state_manager: Arc<ServiceStateManager>,
|
||||||
pub s3_shutdown_tx: Option<ShutdownHandle>,
|
pub s3_shutdown_tx: Option<ShutdownHandle>,
|
||||||
@@ -105,6 +117,38 @@ pub async fn init_startup_listen_context(config: &Config) -> Result<StartupListe
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn prepare_embedded_startup_config(
|
||||||
|
address: String,
|
||||||
|
access_key: String,
|
||||||
|
secret_key: String,
|
||||||
|
mut volumes: Vec<String>,
|
||||||
|
region: String,
|
||||||
|
) -> Result<EmbeddedStartupConfig> {
|
||||||
|
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<EmbeddedStartupListenContext> {
|
pub async fn init_embedded_startup_listen_context(config: &Config) -> Result<EmbeddedStartupListenContext> {
|
||||||
let readiness = Arc::new(GlobalReadiness::new());
|
let readiness = Arc::new(GlobalReadiness::new());
|
||||||
|
|
||||||
@@ -138,6 +182,16 @@ pub async fn init_embedded_startup_listen_context(config: &Config) -> Result<Emb
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn start_embedded_http_server(config: &Config, readiness: Arc<GlobalReadiness>) -> Result<EmbeddedHttpServer> {
|
||||||
|
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<GlobalReadiness>) -> Result<StartupHttpServers> {
|
pub async fn init_startup_http_servers(config: &Config, readiness: Arc<GlobalReadiness>) -> Result<StartupHttpServers> {
|
||||||
init_capacity_management().await;
|
init_capacity_management().await;
|
||||||
let state_manager = Arc::new(ServiceStateManager::new());
|
let state_manager = Arc::new(ServiceStateManager::new());
|
||||||
@@ -224,7 +278,9 @@ fn console_http_server_config(config: &Config) -> Option<Config> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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;
|
use crate::config::Config;
|
||||||
|
|
||||||
#[test]
|
#[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_ACCESS_KEY));
|
||||||
assert!(!DEFAULT_CREDENTIALS_WARNING_MESSAGE.contains(rustfs_credentials::DEFAULT_SECRET_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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user