diff --git a/rustfs/src/server/compress.rs b/rustfs/src/server/compress.rs index 7404ed85b..9dc975d50 100644 --- a/rustfs/src/server/compress.rs +++ b/rustfs/src/server/compress.rs @@ -418,7 +418,11 @@ impl PathCategory { PathCategory::AdminApi } else if path.starts_with("/rustfs/console") { PathCategory::Console - } else if path == "/health" || path.starts_with("/health/") { + } else if path == "/health" + || path.starts_with("/health/") + || path == "/minio/health/live" + || path == "/minio/health/ready" + { PathCategory::Probe } else { PathCategory::S3DataPlane @@ -766,6 +770,8 @@ mod tests { assert_eq!(PathCategory::classify("/health"), PathCategory::Probe); assert_eq!(PathCategory::classify("/health/live"), PathCategory::Probe); assert_eq!(PathCategory::classify("/health/ready"), PathCategory::Probe); + assert_eq!(PathCategory::classify("/minio/health/live"), PathCategory::Probe); + assert_eq!(PathCategory::classify("/minio/health/ready"), PathCategory::Probe); } #[test] diff --git a/rustfs/src/server/layer.rs b/rustfs/src/server/layer.rs index d0914ebc7..e30487307 100644 --- a/rustfs/src/server/layer.rs +++ b/rustfs/src/server/layer.rs @@ -20,8 +20,8 @@ use crate::server::cors; use crate::server::hybrid::HybridBody; use crate::server::{ ADMIN_PREFIX, CONSOLE_PREFIX, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX, - MINIO_ADMIN_V3_PREFIX, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, active_http_requests, collect_dependency_readiness_report, - has_path_prefix, is_admin_path, is_table_catalog_path, + MINIO_ADMIN_V3_PREFIX, MINIO_HEALTH_LIVE_PATH, MINIO_HEALTH_READY_PATH, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, + active_http_requests, collect_dependency_readiness_report, has_path_prefix, is_admin_path, is_table_catalog_path, }; use crate::storage::apply_cors_headers; use crate::storage::request_context::{ @@ -883,8 +883,8 @@ fn resolve_public_health_probe(method: &Method, path: &str) -> Option Some(HealthProbe::Liveness), - HEALTH_READY_PATH => Some(HealthProbe::Readiness), + HEALTH_PREFIX | HEALTH_COMPAT_LIVE_PATH | MINIO_HEALTH_LIVE_PATH => Some(HealthProbe::Liveness), + HEALTH_READY_PATH | MINIO_HEALTH_READY_PATH => Some(HealthProbe::Readiness), _ => None, } } @@ -1279,8 +1279,15 @@ impl ConditionalCorsLayer { } /// Exact paths that should be excluded from being treated as S3 paths. - const EXCLUDED_EXACT_PATHS: &'static [&'static str] = - &["/health", "/health/live", "/health/ready", "/profile/cpu", "/profile/memory"]; + const EXCLUDED_EXACT_PATHS: &'static [&'static str] = &[ + "/health", + "/health/live", + "/health/ready", + "/minio/health/live", + "/minio/health/ready", + "/profile/cpu", + "/profile/memory", + ]; fn is_s3_path(path: &str) -> bool { // Exclude Admin, Console, RPC, and configured special paths @@ -1854,6 +1861,59 @@ mod tests { .await; } + #[tokio::test] + #[serial] + async fn public_health_endpoint_layer_handles_minio_health_live_path() { + async_with_vars([(rustfs_config::ENV_HEALTH_ENDPOINT_ENABLE, Some("true"))], async { + let inner = CountingHybridService::default(); + let calls = inner.calls(); + let mut service = PublicHealthEndpointLayer.layer(inner); + + let response = service + .call( + Request::builder() + .method(Method::GET) + .uri(MINIO_HEALTH_LIVE_PATH) + .body(Full::::from(Bytes::new())) + .expect("request"), + ) + .await + .expect("health response"); + + assert_eq!(response.status(), StatusCode::OK); + assert_eq!(calls.load(Ordering::SeqCst), 0); + }) + .await; + } + + #[tokio::test] + #[serial] + async fn public_health_endpoint_layer_handles_minio_health_ready_head_before_inner_service() { + async_with_vars([(rustfs_config::ENV_HEALTH_ENDPOINT_ENABLE, Some("true"))], async { + let inner = CountingHybridService::default(); + let calls = inner.calls(); + let mut service = PublicHealthEndpointLayer.layer(inner); + + let response = service + .call( + Request::builder() + .method(Method::HEAD) + .uri(MINIO_HEALTH_READY_PATH) + .body(Full::::from(Bytes::new())) + .expect("request"), + ) + .await + .expect("health response"); + + assert!(response.status() == StatusCode::OK || response.status() == StatusCode::SERVICE_UNAVAILABLE); + assert_eq!(calls.load(Ordering::SeqCst), 0); + + let body = BodyExt::collect(response.into_body()).await.expect("body").to_bytes(); + assert!(body.is_empty()); + }) + .await; + } + #[tokio::test] #[serial] async fn public_health_endpoint_layer_forwards_unknown_health_path_when_endpoint_disabled() { @@ -1879,6 +1939,31 @@ mod tests { .await; } + #[tokio::test] + #[serial] + async fn public_health_endpoint_layer_forwards_minio_health_alias_when_endpoint_disabled() { + async_with_vars([(rustfs_config::ENV_HEALTH_ENDPOINT_ENABLE, Some("false"))], async { + let inner = CountingHybridService::default(); + let calls = inner.calls(); + let mut service = PublicHealthEndpointLayer.layer(inner); + + let response = service + .call( + Request::builder() + .method(Method::GET) + .uri(MINIO_HEALTH_LIVE_PATH) + .body(Full::::from(Bytes::new())) + .expect("request"), + ) + .await + .expect("inner response"); + + assert_eq!(response.status(), StatusCode::IM_A_TEAPOT); + assert_eq!(calls.load(Ordering::SeqCst), 1); + }) + .await; + } + #[test] fn alias_busy_threshold_exceeded_requires_switch_and_positive_threshold() { with_var(rustfs_config::ENV_HEALTH_COMPAT_BUSY_CHECK_ENABLE, Some("true"), || { diff --git a/rustfs/src/server/mod.rs b/rustfs/src/server/mod.rs index f085c0d13..a90baecbe 100644 --- a/rustfs/src/server/mod.rs +++ b/rustfs/src/server/mod.rs @@ -52,9 +52,9 @@ pub(crate) use module_switch::{ }; pub(crate) use prefix::{ ADMIN_PREFIX, CONSOLE_PREFIX, FAVICON_PATH, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, LICENSE, - MINIO_ADMIN_PREFIX, MINIO_ADMIN_V3_PREFIX, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, - TABLE_CATALOG_COMPAT_PREFIX, TABLE_CATALOG_PREFIX, TONIC_PREFIX, VERSION, has_path_prefix, is_admin_path, - is_table_catalog_path, + MINIO_ADMIN_PREFIX, MINIO_ADMIN_V3_PREFIX, MINIO_HEALTH_LIVE_PATH, MINIO_HEALTH_READY_PATH, PROFILE_CPU_PATH, + PROFILE_MEMORY_PATH, RPC_PREFIX, RUSTFS_ADMIN_PREFIX, TABLE_CATALOG_COMPAT_PREFIX, TABLE_CATALOG_PREFIX, TONIC_PREFIX, + VERSION, has_path_prefix, is_admin_path, is_table_catalog_path, }; pub(crate) use readiness::DependencyReadiness; pub(crate) use readiness::DependencyReadinessReport; diff --git a/rustfs/src/server/prefix.rs b/rustfs/src/server/prefix.rs index fe664c7b0..b4e6cb3ad 100644 --- a/rustfs/src/server/prefix.rs +++ b/rustfs/src/server/prefix.rs @@ -34,6 +34,12 @@ pub(crate) const HEALTH_READY_PATH: &str = "/health/ready"; /// Health liveness probe compatibility alias path. pub(crate) const HEALTH_COMPAT_LIVE_PATH: &str = "/health/live"; +/// MinIO-compatible health liveness probe alias path. +pub(crate) const MINIO_HEALTH_LIVE_PATH: &str = "/minio/health/live"; + +/// MinIO-compatible health readiness probe alias path. +pub(crate) const MINIO_HEALTH_READY_PATH: &str = "/minio/health/ready"; + /// Predefined administrative prefix for RustFS server routes. /// This prefix is used for endpoints that handle administrative tasks /// such as configuration, monitoring, and management. diff --git a/rustfs/src/server/readiness.rs b/rustfs/src/server/readiness.rs index 220b42327..3adf88a23 100644 --- a/rustfs/src/server/readiness.rs +++ b/rustfs/src/server/readiness.rs @@ -136,6 +136,8 @@ fn is_probe_path(path: &str) -> bool { | crate::server::HEALTH_PREFIX | crate::server::HEALTH_COMPAT_LIVE_PATH | crate::server::HEALTH_READY_PATH + | crate::server::MINIO_HEALTH_LIVE_PATH + | crate::server::MINIO_HEALTH_READY_PATH | crate::server::FAVICON_PATH );