fix(storage): add scoped timeout policy and startup fs guardrail (#3056)

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

* style: fix cargo fmt formatting in disk_store.rs

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

Extend the TimeoutHealthAction introduced in #2996 to read_metadata,
list_dir, and disk_info operations when RUSTFS_NETWORK_MOUNT_MODE=true.
Also raises all drive operation timeouts to 60s (explicit per-operation
overrides still take precedence).

Closes #2790

* feat(startup): add unsupported filesystem policy guardrail

* chore(deps): refresh lockfile and dependency pins

* feat(ecstore): add scoped timeout health-action policy

* docs(config): document drive timeout health-action policy

* refactor(ecstore): cache timeout health policy per disk wrapper

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends (#2838)

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

* style: fix cargo fmt formatting in disk_store.rs

* fix(storage): add RUSTFS_NETWORK_MOUNT_MODE for CIFS/NFS backends

Extend the TimeoutHealthAction introduced in #2996 to read_metadata,
list_dir, and disk_info operations when RUSTFS_NETWORK_MOUNT_MODE=true.
Also raises all drive operation timeouts to 60s (explicit per-operation
overrides still take precedence).

Closes #2790

* fix(utils): map verified Linux filesystem magic values (#3051)

* fix(utils): cover sha256 checksum validation (#3052)

* fix(utils): cover sha256 checksum validation

* docs: clarify sha256 checksum validation

---------

Co-authored-by: houseme <housemecn@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>

* refactor(config): replace network mount mode with timeout profile preset

* fix(review): align fallback defaults and extend fs-type detection

* fix(review): cache timeout profile and restore probe timeout semantics

* refactor(ecstore): cache timeout health policy lookup

* perf(ecstore): cache active probe timeout per monitor task

---------

Co-authored-by: mistik <mistiklord4@gmail.com>
Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
houseme
2026-05-22 14:37:30 +08:00
committed by GitHub
parent 69345fe059
commit 6264be437c
13 changed files with 520 additions and 26 deletions
+2
View File
@@ -50,6 +50,7 @@ use crate::app::context::{AppContext, init_global_app_context};
use crate::config::Config;
use crate::init::{add_bucket_notification_configuration, init_buffer_profile_system, init_kms_system};
use crate::server::{init_event_notifier, shutdown_event_notifier, start_audit_system, start_http_server, stop_audit_system};
use crate::startup_fs_guard::enforce_unsupported_fs_policy;
use rustfs_common::{GlobalReadiness, SystemStage, set_global_addr};
use rustfs_config::ENV_RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS;
use rustfs_credentials::init_global_action_credentials;
@@ -350,6 +351,7 @@ impl RustFSServerBuilder {
let (endpoint_pools, setup_type) = EndpointServerPools::from_volumes(server_addr_str.as_str(), config.volumes.clone())
.await
.map_err(|e| ServerError::Init(format!("endpoints: {e}")))?;
enforce_unsupported_fs_policy(&endpoint_pools).map_err(|e| ServerError::Init(format!("unsupported fs guard: {e}")))?;
set_global_endpoints(endpoint_pools.as_ref().clone());
update_erasure_type(setup_type).await;
+1
View File
@@ -67,6 +67,7 @@ pub mod profiling;
#[cfg(any(feature = "ftps", feature = "webdav", feature = "sftp"))]
pub mod protocols;
pub mod server;
pub mod startup_fs_guard;
pub mod storage;
pub mod update;
pub mod version;
+2
View File
@@ -33,6 +33,7 @@ use rustfs::server::{
SHUTDOWN_TIMEOUT, ServiceState, ServiceStateManager, ShutdownSignal, init_event_notifier, shutdown_event_notifier,
start_audit_system, start_http_server, stop_audit_system, wait_for_shutdown,
};
use rustfs::startup_fs_guard::enforce_unsupported_fs_policy;
use rustfs_common::{GlobalReadiness, SystemStage, set_global_addr};
use rustfs_config::ENV_RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS;
use rustfs_credentials::init_global_action_credentials;
@@ -301,6 +302,7 @@ async fn run(config: rustfs::config::Config) -> Result<()> {
let (endpoint_pools, setup_type) = EndpointServerPools::from_volumes(server_address.clone().as_str(), config.volumes.clone())
.await
.map_err(Error::other)?;
enforce_unsupported_fs_policy(&endpoint_pools)?;
set_global_endpoints(endpoint_pools.as_ref().clone());
update_erasure_type(setup_type).await;
+170
View File
@@ -0,0 +1,170 @@
// 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 rustfs_config::{
DEFAULT_RUSTFS_UNSUPPORTED_FS_POLICY, ENV_RUSTFS_UNSUPPORTED_FS_POLICY, RUSTFS_UNSUPPORTED_FS_POLICY_FAIL,
RUSTFS_UNSUPPORTED_FS_POLICY_WARN,
};
use rustfs_ecstore::endpoints::EndpointServerPools;
use std::collections::BTreeSet;
use std::io::{Error, Result};
use tracing::warn;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum UnsupportedFsPolicy {
Warn,
Fail,
}
impl UnsupportedFsPolicy {
fn parse(raw: &str) -> Option<Self> {
match raw.trim().to_ascii_lowercase().as_str() {
RUSTFS_UNSUPPORTED_FS_POLICY_WARN => Some(Self::Warn),
RUSTFS_UNSUPPORTED_FS_POLICY_FAIL => Some(Self::Fail),
_ => None,
}
}
}
fn get_unsupported_fs_policy() -> UnsupportedFsPolicy {
let raw = rustfs_utils::get_env_str(ENV_RUSTFS_UNSUPPORTED_FS_POLICY, DEFAULT_RUSTFS_UNSUPPORTED_FS_POLICY);
if let Some(policy) = UnsupportedFsPolicy::parse(&raw) {
return policy;
}
warn!(
env = ENV_RUSTFS_UNSUPPORTED_FS_POLICY,
value = %raw,
default = DEFAULT_RUSTFS_UNSUPPORTED_FS_POLICY,
"Invalid unsupported filesystem policy; falling back to default"
);
UnsupportedFsPolicy::parse(DEFAULT_RUSTFS_UNSUPPORTED_FS_POLICY).unwrap_or(UnsupportedFsPolicy::Warn)
}
fn is_unsupported_fs_type(fs_type: &str) -> bool {
let normalized = fs_type.trim().to_ascii_lowercase();
matches!(
normalized.as_str(),
"nfs" | "cifs" | "smb2" | "fuse" | "fuseblk" | "overlayfs" | "9p" | "v9fs" | "ceph" | "glusterfs" | "gfs" | "gfs2"
) || normalized.starts_with("fuse.")
}
fn collect_local_paths(endpoint_pools: &EndpointServerPools) -> Vec<String> {
let mut local_paths = BTreeSet::new();
for pool in endpoint_pools.as_ref() {
for endpoint in pool.endpoints.as_ref() {
if endpoint.is_local {
local_paths.insert(endpoint.get_file_path());
}
}
}
local_paths.into_iter().collect()
}
pub fn enforce_unsupported_fs_policy(endpoint_pools: &EndpointServerPools) -> Result<()> {
let local_paths = collect_local_paths(endpoint_pools);
if local_paths.is_empty() {
return Ok(());
}
let mut unsupported = Vec::new();
for path in local_paths {
let info = match rustfs_utils::os::get_info(&path) {
Ok(info) => info,
Err(err) => {
warn!(
path = %path,
error = %err,
"Failed to inspect filesystem type for startup boundary guard; skipping this path"
);
continue;
}
};
if is_unsupported_fs_type(&info.fstype) {
unsupported.push((path, info.fstype));
}
}
if unsupported.is_empty() {
return Ok(());
}
let detail = unsupported
.iter()
.map(|(path, fs_type)| format!("{path} ({fs_type})"))
.collect::<Vec<_>>()
.join(", ");
let message = format!(
"Unsupported filesystem type detected for RustFS local endpoints: {detail}. \
RustFS only supports direct-attached local POSIX filesystems for production workloads."
);
match get_unsupported_fs_policy() {
UnsupportedFsPolicy::Warn => {
warn!(
env = ENV_RUSTFS_UNSUPPORTED_FS_POLICY,
expected = format!("{RUSTFS_UNSUPPORTED_FS_POLICY_WARN}|{RUSTFS_UNSUPPORTED_FS_POLICY_FAIL}"),
policy = RUSTFS_UNSUPPORTED_FS_POLICY_WARN,
"{message}"
);
Ok(())
}
UnsupportedFsPolicy::Fail => Err(Error::other(format!(
"{message} Startup aborted by policy: {ENV_RUSTFS_UNSUPPORTED_FS_POLICY}={RUSTFS_UNSUPPORTED_FS_POLICY_FAIL}"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unsupported_fs_type_matcher_covers_network_like_types() {
assert!(is_unsupported_fs_type("NFS"));
assert!(is_unsupported_fs_type("cifs"));
assert!(is_unsupported_fs_type("smb2"));
assert!(is_unsupported_fs_type("fuse"));
assert!(is_unsupported_fs_type("fuse.sshfs"));
assert!(is_unsupported_fs_type("v9fs"));
assert!(is_unsupported_fs_type("overlayfs"));
}
#[test]
fn unsupported_fs_type_matcher_keeps_local_posix_types_supported() {
assert!(!is_unsupported_fs_type("XFS"));
assert!(!is_unsupported_fs_type("EXT4"));
assert!(!is_unsupported_fs_type("BTRFS"));
}
#[test]
fn policy_parser_handles_valid_and_invalid_values() {
assert_eq!(UnsupportedFsPolicy::parse("warn"), Some(UnsupportedFsPolicy::Warn));
assert_eq!(UnsupportedFsPolicy::parse("FAIL"), Some(UnsupportedFsPolicy::Fail));
assert_eq!(UnsupportedFsPolicy::parse("unknown"), None);
}
#[test]
#[serial_test::serial]
fn invalid_policy_falls_back_to_configured_default() {
temp_env::with_var(ENV_RUSTFS_UNSUPPORTED_FS_POLICY, Some("invalid"), || {
assert_eq!(
get_unsupported_fs_policy(),
UnsupportedFsPolicy::parse(DEFAULT_RUSTFS_UNSUPPORTED_FS_POLICY).unwrap_or(UnsupportedFsPolicy::Warn)
);
});
}
}