fix(runtime): remove startup panic fallbacks (#3754)

* fix(runtime): remove startup panic fallbacks

* test(runtime): cover buffer profile fallback safety

* test(runtime): reduce panic-style assertions

* fix(runtime): expose fallible env config setup

* test(runtime): simplify permit acquisition assertion

* test(runtime): tighten operation helper assertions

* fix(filemeta): stop panicking on invalid free version ids

* fix(init): satisfy buffer profile clippy lints

* fix(lock): harden fast lock config construction

* chore(checks): refresh layer dependency baseline

---------

Signed-off-by: houseme <housemecn@gmail.com>
This commit is contained in:
houseme
2026-06-23 12:31:26 +08:00
committed by GitHub
parent 583a23bdf2
commit a6878e8fce
11 changed files with 285 additions and 82 deletions
+13
View File
@@ -15,6 +15,7 @@
#[cfg(test)]
#[allow(unsafe_op_in_unsafe_fn)]
mod tests {
use crate::config::cli::default_server_opts;
use crate::config::{CommandResult, Config, Opt, TlsCommands};
use crate::storage::DisksLayout;
use rustfs_config::{DEFAULT_CONSOLE_ADDRESS, DEFAULT_CONSOLE_ENABLE, DEFAULT_OBS_ENDPOINT, RUSTFS_REGION};
@@ -100,6 +101,18 @@ mod tests {
}
}
#[test]
#[serial]
fn test_parse_from_non_server_commands_falls_back_without_panicking() {
let info_opt = Opt::parse_from(["rustfs", "info"]);
let tls_opt = Opt::parse_from(["rustfs", "tls", "inspect", "--path", "/tmp/certs"]);
assert!(info_opt.volumes.is_empty());
assert!(tls_opt.volumes.is_empty());
assert_eq!(info_opt.address, default_server_opts().address);
assert_eq!(tls_opt.address, default_server_opts().address);
}
#[test]
#[serial]
fn test_default_console_configuration() {
+1 -7
View File
@@ -98,13 +98,7 @@ impl Opt {
let cli = Cli::parse_from(args);
match cli.command {
Some(Commands::Server(opts)) => Self::from_server_opts(*opts),
Some(Commands::Info(_)) => {
// This should not happen in parse_from, as it's handled by parse_command
panic!("Info command should be handled by parse_command");
}
Some(Commands::Tls(_)) => {
panic!("TLS command should be handled by parse_command");
}
Some(Commands::Info(_)) | Some(Commands::Tls(_)) => Self::from_server_opts(default_server_opts()),
None => {
// Default to server with empty volumes (will be filled from env)
Self::from_server_opts(default_server_opts())
+91 -31
View File
@@ -491,7 +491,7 @@ pub async fn init_kms_system(config: &config::Config) -> std::io::Result<()> {
/// # Arguments
/// * `config` - The application configuration options
pub fn init_buffer_profile_system(config: &config::Config) {
use crate::config::{RustFSBufferConfig, WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled};
use crate::config::{WorkloadProfile, init_global_buffer_config, set_buffer_profile_enabled};
// Whether buffer profiling is disabled or not, it is enabled by default, unless the user explicitly sets '--buffer-profile-disable' or 'RUSTFS_BUFFER_PROFILE_DISABLE=true'
if config.buffer_profile_disable {
@@ -563,43 +563,20 @@ pub fn init_buffer_profile_system(config: &config::Config) {
"Selected buffer profile"
);
// Create and validate buffer configuration
let mut buffer_config = RustFSBufferConfig::new(profile);
if let Err(e) = buffer_config.validate() {
let fallback_profile = WorkloadProfile::from_name(DEFAULT_BUFFER_PROFILE);
let Some(buffer_config) = resolve_buffer_profile_config(profile, fallback_profile) else {
warn!(
target: "rustfs::init",
event = "buffer_profile_validation_failed",
component = LOG_COMPONENT_INIT,
subsystem = LOG_SUBSYSTEM_BUFFER,
error = %e,
error = "all buffer profile configurations rejected",
fallback_profile = DEFAULT_BUFFER_PROFILE,
"Buffer profile validation failed"
"Buffer profile initialization disabled after validation failures"
);
// Fall back to a known-good profile to avoid installing an invalid configuration
let fallback_profile = WorkloadProfile::from_name(DEFAULT_BUFFER_PROFILE);
info!(
target: "rustfs::init",
event = "buffer_profile_fallback",
component = LOG_COMPONENT_INIT,
subsystem = LOG_SUBSYSTEM_BUFFER,
profile = ?fallback_profile,
"Using fallback buffer profile"
);
let fallback_config = RustFSBufferConfig::new(fallback_profile);
if let Err(e2) = fallback_config.validate() {
error!(
target: "rustfs::init",
event = "buffer_profile_validation_failed",
component = LOG_COMPONENT_INIT,
subsystem = LOG_SUBSYSTEM_BUFFER,
error = %e2,
fallback_profile = DEFAULT_BUFFER_PROFILE,
"Fallback buffer profile validation failed"
);
panic!("Failed to initialize a valid RustFS buffer configuration");
}
buffer_config = fallback_config;
}
set_buffer_profile_enabled(false);
return;
};
// Log the workload profile name
let workload_name = buffer_config.workload_name();
@@ -630,6 +607,53 @@ pub fn init_buffer_profile_system(config: &config::Config) {
}
}
fn resolve_buffer_profile_config(
profile: crate::config::WorkloadProfile,
fallback_profile: crate::config::WorkloadProfile,
) -> Option<crate::config::RustFSBufferConfig> {
use crate::config::RustFSBufferConfig;
let buffer_config = RustFSBufferConfig::new(profile);
if let Err(err) = buffer_config.validate() {
warn!(
target: "rustfs::init",
event = "buffer_profile_validation_failed",
component = LOG_COMPONENT_INIT,
subsystem = LOG_SUBSYSTEM_BUFFER,
error = %err,
fallback_profile = DEFAULT_BUFFER_PROFILE,
"Buffer profile validation failed"
);
info!(
target: "rustfs::init",
event = "buffer_profile_fallback",
component = LOG_COMPONENT_INIT,
subsystem = LOG_SUBSYSTEM_BUFFER,
profile = ?fallback_profile,
"Using fallback buffer profile"
);
let fallback_config = RustFSBufferConfig::new(fallback_profile);
if let Err(fallback_err) = fallback_config.validate() {
error!(
target: "rustfs::init",
event = "buffer_profile_validation_failed",
component = LOG_COMPONENT_INIT,
subsystem = LOG_SUBSYSTEM_BUFFER,
error = %fallback_err,
fallback_profile = DEFAULT_BUFFER_PROFILE,
"Fallback buffer profile validation failed"
);
return None;
}
Some(fallback_config)
} else {
Some(buffer_config)
}
}
/// Parse and normalize server address for FTP/FTPS
/// Forces IPv4 binding to avoid libunftp IPv6 compatibility issues
#[allow(dead_code)]
@@ -1258,3 +1282,39 @@ pub async fn init_sftp_system() -> Result<Option<ShutdownHandle>, Box<dyn std::e
Ok(Some(ShutdownHandle::new(shutdown_tx, task_handle)))
}
}
#[cfg(test)]
mod tests {
use super::resolve_buffer_profile_config;
use crate::config::{BufferConfig, WorkloadProfile};
use rustfs_config::KI_B;
#[test]
fn resolve_buffer_profile_config_returns_fallback_when_primary_is_invalid() {
let invalid_primary = WorkloadProfile::Custom(BufferConfig {
min_size: 64 * KI_B,
max_size: 1024,
default_unknown: 64 * KI_B,
thresholds: vec![(1024, 64 * KI_B)],
});
let resolved = resolve_buffer_profile_config(invalid_primary, WorkloadProfile::GeneralPurpose)
.expect("fallback profile should be accepted");
assert_eq!(resolved.workload, WorkloadProfile::GeneralPurpose);
}
#[test]
fn resolve_buffer_profile_config_returns_none_when_primary_and_fallback_are_invalid() {
let invalid = WorkloadProfile::Custom(BufferConfig {
min_size: 64 * KI_B,
max_size: 1024,
default_unknown: 64 * KI_B,
thresholds: vec![(1024, 64 * KI_B)],
});
let resolved = resolve_buffer_profile_config(invalid.clone(), invalid);
assert!(resolved.is_none());
}
}
+1 -4
View File
@@ -684,10 +684,7 @@ mod integration_tests {
let permit = manager.acquire_disk_read_permit().await;
assert!(permit.is_ok());
let _permit = match permit {
Ok(permit) => permit,
Err(error) => panic!("disk read permit acquisition failed: {error}"),
};
let _permit = permit.ok();
let snapshot = manager.get_object_admission_snapshot();
assert_eq!(snapshot.active, Some(1));
+12 -14
View File
@@ -500,10 +500,10 @@ mod tests {
});
let helper = OperationHelper::new(&req, EventName::ObjectTaggingPut, S3Operation::PutObjectTagging);
let event_args = match &helper {
OperationHelper::Enabled(state) => state.event_builder.clone().expect("event builder should exist").build(),
OperationHelper::Disabled => panic!("helper should be enabled when notify/audit switches are on"),
let OperationHelper::Enabled(state) = &helper else {
panic!("helper should be enabled when notify/audit switches are on");
};
let event_args = state.event_builder.clone().expect("event builder should exist").build();
assert_eq!(event_args.bucket_name, "issue-2292-bucket");
assert_eq!(event_args.object.bucket, "issue-2292-bucket");
@@ -552,13 +552,11 @@ mod tests {
let helper = OperationHelper::new(&req, EventName::ObjectAccessedGet, S3Operation::GetObject);
// Verify the helper stored the RequestContext
match &helper {
OperationHelper::Enabled(state) => {
assert!(state.request_context.is_some());
assert_eq!(state.request_context.as_ref().unwrap().request_id, "ingress-canonical-uuid");
}
OperationHelper::Disabled => panic!("helper should be enabled when notify/audit switches are on"),
}
let OperationHelper::Enabled(state) = &helper else {
panic!("helper should be enabled when notify/audit switches are on");
};
assert!(state.request_context.is_some());
assert_eq!(state.request_context.as_ref().unwrap().request_id, "ingress-canonical-uuid");
},
);
}
@@ -595,10 +593,10 @@ mod tests {
let helper = OperationHelper::new(&req, EventName::ObjectAccessedGet, S3Operation::GetObject);
// Verify the helper has no RequestContext
match &helper {
OperationHelper::Enabled(state) => assert!(state.request_context.is_none()),
OperationHelper::Disabled => panic!("helper should be enabled when notify/audit switches are on"),
}
let OperationHelper::Enabled(state) = &helper else {
panic!("helper should be enabled when notify/audit switches are on");
};
assert!(state.request_context.is_none());
},
);
}