mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-07 13:53:12 +00:00
fix(health): decouple liveness and lock RPC deadlines (#3824)
This commit is contained in:
@@ -19,7 +19,7 @@ use crate::license::has_valid_license;
|
||||
use crate::server::has_path_prefix;
|
||||
use crate::server::{
|
||||
CONSOLE_PREFIX, FAVICON_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, HeaderMapCarrier, LICENSE, RUSTFS_ADMIN_PREFIX,
|
||||
RequestContextLayer, VERSION, liveness_dependency_readiness_report,
|
||||
RequestContextLayer, VERSION,
|
||||
};
|
||||
use crate::storage::request_context::RequestContext;
|
||||
use crate::version::build;
|
||||
@@ -597,16 +597,17 @@ async fn health_check(method: Method, uri: Uri) -> Response {
|
||||
} else {
|
||||
HealthProbe::Liveness
|
||||
};
|
||||
let readiness_report = match probe {
|
||||
HealthProbe::Liveness => liveness_dependency_readiness_report(),
|
||||
HealthProbe::Readiness => collect_dependency_readiness().await,
|
||||
let readiness_report = if probe == HealthProbe::Readiness {
|
||||
Some(collect_dependency_readiness().await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let uptime = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let response_parts =
|
||||
build_health_response_parts(method.clone(), probe, &readiness_report, "rustfs-console", Some(uptime), None);
|
||||
build_health_response_parts(method.clone(), probe, readiness_report.as_ref(), "rustfs-console", Some(uptime), None);
|
||||
|
||||
let builder = Response::builder()
|
||||
.status(response_parts.status_code)
|
||||
|
||||
@@ -16,7 +16,7 @@ use super::profile::{TriggerProfileCPU, TriggerProfileMemory};
|
||||
use crate::admin::router::{AdminOperation, Operation, S3Router};
|
||||
use crate::server::{
|
||||
HEALTH_PREFIX, HEALTH_READY_PATH, PROFILE_CPU_PATH, PROFILE_MEMORY_PATH,
|
||||
collect_dependency_readiness_report as collect_runtime_dependency_readiness_report, liveness_dependency_readiness_report,
|
||||
collect_dependency_readiness_report as collect_runtime_dependency_readiness_report,
|
||||
};
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use hyper::{Method, StatusCode};
|
||||
@@ -73,6 +73,7 @@ pub(crate) struct HealthPayloadContext<'a> {
|
||||
pub(crate) service: &'a str,
|
||||
pub(crate) uptime: Option<u64>,
|
||||
pub(crate) kms_ready: Option<bool>,
|
||||
pub(crate) include_dependency_details: bool,
|
||||
}
|
||||
|
||||
pub(crate) async fn collect_dependency_readiness() -> crate::server::DependencyReadinessReport {
|
||||
@@ -85,13 +86,21 @@ pub(crate) fn health_check_state(
|
||||
lock_quorum_ready: bool,
|
||||
probe: HealthProbe,
|
||||
) -> HealthCheckState {
|
||||
if probe == HealthProbe::Liveness {
|
||||
return HealthCheckState {
|
||||
status_code: StatusCode::OK,
|
||||
status: "ok",
|
||||
ready: true,
|
||||
};
|
||||
}
|
||||
|
||||
let ready = storage_ready && iam_ready && lock_quorum_ready;
|
||||
let status = if ready { "ok" } else { "degraded" };
|
||||
|
||||
let status_code = match probe {
|
||||
HealthProbe::Liveness => StatusCode::OK,
|
||||
HealthProbe::Readiness if ready => StatusCode::OK,
|
||||
HealthProbe::Readiness => StatusCode::SERVICE_UNAVAILABLE,
|
||||
let status_code = if ready {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
};
|
||||
|
||||
HealthCheckState {
|
||||
@@ -159,16 +168,42 @@ pub(crate) fn probe_from_path(path: &str) -> HealthProbe {
|
||||
pub(crate) fn build_health_response_parts(
|
||||
method: Method,
|
||||
probe: HealthProbe,
|
||||
readiness_report: &crate::server::DependencyReadinessReport,
|
||||
readiness_report: Option<&crate::server::DependencyReadinessReport>,
|
||||
service: &str,
|
||||
uptime: Option<u64>,
|
||||
kms_ready: Option<bool>,
|
||||
) -> HealthResponseParts {
|
||||
let storage_ready = readiness_report.readiness.storage_ready;
|
||||
let iam_ready = readiness_report.readiness.iam_ready;
|
||||
let lock_quorum_ready = readiness_report.readiness.lock_quorum_ready;
|
||||
let mut health = health_check_state(storage_ready, iam_ready, lock_quorum_ready, probe);
|
||||
let mut degraded_reasons = readiness_report.degraded_reasons.clone();
|
||||
let (storage_ready, iam_ready, lock_quorum_ready, mut health, mut degraded_reasons, include_dependency_details) =
|
||||
match (probe, readiness_report) {
|
||||
(HealthProbe::Readiness, Some(readiness_report)) => {
|
||||
let storage_ready = readiness_report.readiness.storage_ready;
|
||||
let iam_ready = readiness_report.readiness.iam_ready;
|
||||
let lock_quorum_ready = readiness_report.readiness.lock_quorum_ready;
|
||||
(
|
||||
storage_ready,
|
||||
iam_ready,
|
||||
lock_quorum_ready,
|
||||
health_check_state(storage_ready, iam_ready, lock_quorum_ready, probe),
|
||||
readiness_report.degraded_reasons.clone(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
(HealthProbe::Readiness, None) => (
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
HealthCheckState {
|
||||
status_code: StatusCode::SERVICE_UNAVAILABLE,
|
||||
status: "degraded",
|
||||
ready: false,
|
||||
},
|
||||
vec![crate::server::ReadinessDegradedReason::StorageIamAndLockUnavailable],
|
||||
true,
|
||||
),
|
||||
(HealthProbe::Liveness, _) => {
|
||||
(false, false, false, health_check_state(false, false, false, probe), Vec::new(), false)
|
||||
}
|
||||
};
|
||||
|
||||
if probe == HealthProbe::Readiness && matches!(kms_ready, Some(false)) {
|
||||
health = HealthCheckState {
|
||||
@@ -193,6 +228,7 @@ pub(crate) fn build_health_response_parts(
|
||||
service,
|
||||
uptime,
|
||||
kms_ready,
|
||||
include_dependency_details,
|
||||
}))
|
||||
};
|
||||
|
||||
@@ -216,10 +252,13 @@ pub(crate) fn build_health_payload(ctx: HealthPayloadContext<'_>) -> Value {
|
||||
"service": ctx.service,
|
||||
"timestamp": jiff::Zoned::now().to_string(),
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
"details": build_component_details(ctx.storage_ready, ctx.iam_ready, ctx.lock_quorum_ready, ctx.kms_ready),
|
||||
"degradedReasons": build_degraded_reasons(ctx.degraded_reasons),
|
||||
});
|
||||
|
||||
if ctx.include_dependency_details {
|
||||
payload["details"] = build_component_details(ctx.storage_ready, ctx.iam_ready, ctx.lock_quorum_ready, ctx.kms_ready);
|
||||
payload["degradedReasons"] = build_degraded_reasons(ctx.degraded_reasons);
|
||||
}
|
||||
|
||||
if let Some(uptime) = ctx.uptime {
|
||||
payload["uptime"] = json!(uptime);
|
||||
}
|
||||
@@ -245,12 +284,14 @@ impl Operation for HealthCheckHandler {
|
||||
}
|
||||
|
||||
let probe = probe_from_path(req.uri.path());
|
||||
let readiness_report = match probe {
|
||||
HealthProbe::Liveness => liveness_dependency_readiness_report(),
|
||||
HealthProbe::Readiness => collect_dependency_readiness().await,
|
||||
let readiness_report = if probe == HealthProbe::Readiness {
|
||||
Some(collect_dependency_readiness().await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response_parts = build_health_response_parts(method.clone(), probe, &readiness_report, "rustfs-endpoint", None, None);
|
||||
let response_parts =
|
||||
build_health_response_parts(method.clone(), probe, readiness_report.as_ref(), "rustfs-endpoint", None, None);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||
@@ -291,17 +332,8 @@ mod tests {
|
||||
fn test_liveness_state_iam_not_ready() {
|
||||
let state = health_check_state(true, false, true, HealthProbe::Liveness);
|
||||
assert_eq!(state.status_code, StatusCode::OK);
|
||||
assert_eq!(state.status, "degraded");
|
||||
assert!(!state.ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_liveness_dependency_readiness_report_is_lightweight_ready() {
|
||||
let report = liveness_dependency_readiness_report();
|
||||
assert!(report.readiness.storage_ready);
|
||||
assert!(report.readiness.iam_ready);
|
||||
assert!(report.readiness.lock_quorum_ready);
|
||||
assert!(report.degraded_reasons.is_empty());
|
||||
assert_eq!(state.status, "ok");
|
||||
assert!(state.ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -361,8 +393,14 @@ mod tests {
|
||||
},
|
||||
degraded_reasons: vec![crate::server::ReadinessDegradedReason::StorageQuorumUnavailable],
|
||||
};
|
||||
let parts =
|
||||
build_health_response_parts(Method::GET, HealthProbe::Readiness, &readiness_report, "rustfs-endpoint", None, None);
|
||||
let parts = build_health_response_parts(
|
||||
Method::GET,
|
||||
HealthProbe::Readiness,
|
||||
Some(&readiness_report),
|
||||
"rustfs-endpoint",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(parts.status_code, StatusCode::SERVICE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
@@ -376,8 +414,14 @@ mod tests {
|
||||
},
|
||||
degraded_reasons: Vec::new(),
|
||||
};
|
||||
let parts =
|
||||
build_health_response_parts(Method::GET, HealthProbe::Readiness, &readiness_report, "rustfs-endpoint", None, None);
|
||||
let parts = build_health_response_parts(
|
||||
Method::GET,
|
||||
HealthProbe::Readiness,
|
||||
Some(&readiness_report),
|
||||
"rustfs-endpoint",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(parts.status_code, StatusCode::OK);
|
||||
}
|
||||
|
||||
@@ -391,9 +435,20 @@ mod tests {
|
||||
},
|
||||
degraded_reasons: vec![crate::server::ReadinessDegradedReason::StorageAndIamUnavailable],
|
||||
};
|
||||
let parts =
|
||||
build_health_response_parts(Method::GET, HealthProbe::Liveness, &readiness_report, "rustfs-endpoint", None, None);
|
||||
let parts = build_health_response_parts(
|
||||
Method::GET,
|
||||
HealthProbe::Liveness,
|
||||
Some(&readiness_report),
|
||||
"rustfs-endpoint",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(parts.status_code, StatusCode::OK);
|
||||
let payload = parts.payload.expect("GET should include payload");
|
||||
assert_eq!(payload["status"], "ok");
|
||||
assert_eq!(payload["ready"], true);
|
||||
assert!(payload.get("details").is_none());
|
||||
assert!(payload.get("degradedReasons").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -406,8 +461,14 @@ mod tests {
|
||||
},
|
||||
degraded_reasons: vec![crate::server::ReadinessDegradedReason::StorageAndIamUnavailable],
|
||||
};
|
||||
let parts =
|
||||
build_health_response_parts(Method::HEAD, HealthProbe::Readiness, &readiness_report, "rustfs-endpoint", None, None);
|
||||
let parts = build_health_response_parts(
|
||||
Method::HEAD,
|
||||
HealthProbe::Readiness,
|
||||
Some(&readiness_report),
|
||||
"rustfs-endpoint",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
assert_eq!(parts.status_code, StatusCode::SERVICE_UNAVAILABLE);
|
||||
assert!(parts.payload.is_none());
|
||||
}
|
||||
@@ -425,6 +486,7 @@ mod tests {
|
||||
service: "rustfs-endpoint",
|
||||
uptime: Some(123),
|
||||
kms_ready: None,
|
||||
include_dependency_details: true,
|
||||
});
|
||||
assert_eq!(payload["status"], "degraded");
|
||||
assert_eq!(payload["ready"], false);
|
||||
@@ -447,6 +509,7 @@ mod tests {
|
||||
service: "rustfs-endpoint",
|
||||
uptime: None,
|
||||
kms_ready: None,
|
||||
include_dependency_details: true,
|
||||
});
|
||||
assert_eq!(payload["degradedReasons"][0], "storage_and_iam_unavailable");
|
||||
}
|
||||
@@ -461,7 +524,8 @@ mod tests {
|
||||
},
|
||||
degraded_reasons: Vec::new(),
|
||||
};
|
||||
let parts = build_health_response_parts(Method::HEAD, HealthProbe::Readiness, &report, "rustfs-endpoint", None, None);
|
||||
let parts =
|
||||
build_health_response_parts(Method::HEAD, HealthProbe::Readiness, Some(&report), "rustfs-endpoint", None, None);
|
||||
assert_eq!(parts.status_code, StatusCode::OK);
|
||||
assert!(parts.payload.is_none());
|
||||
}
|
||||
@@ -476,7 +540,8 @@ mod tests {
|
||||
},
|
||||
degraded_reasons: vec![crate::server::ReadinessDegradedReason::StorageQuorumUnavailable],
|
||||
};
|
||||
let parts = build_health_response_parts(Method::GET, HealthProbe::Readiness, &report, "rustfs-endpoint", None, None);
|
||||
let parts =
|
||||
build_health_response_parts(Method::GET, HealthProbe::Readiness, Some(&report), "rustfs-endpoint", None, None);
|
||||
assert_eq!(parts.status_code, StatusCode::SERVICE_UNAVAILABLE);
|
||||
let payload = parts.payload.expect("GET should include payload");
|
||||
assert_eq!(payload["status"], "degraded");
|
||||
@@ -495,7 +560,7 @@ mod tests {
|
||||
degraded_reasons: Vec::new(),
|
||||
};
|
||||
let parts =
|
||||
build_health_response_parts(Method::GET, HealthProbe::Readiness, &report, "rustfs-endpoint", None, Some(false));
|
||||
build_health_response_parts(Method::GET, HealthProbe::Readiness, Some(&report), "rustfs-endpoint", None, Some(false));
|
||||
assert_eq!(parts.status_code, StatusCode::SERVICE_UNAVAILABLE);
|
||||
let payload = parts.payload.expect("GET should include payload");
|
||||
assert_eq!(payload["ready"], false);
|
||||
|
||||
@@ -23,7 +23,6 @@ use crate::server::{
|
||||
ADMIN_PREFIX, CONSOLE_PREFIX, HEALTH_COMPAT_LIVE_PATH, HEALTH_PREFIX, HEALTH_READY_PATH, MINIO_ADMIN_PREFIX,
|
||||
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,
|
||||
liveness_dependency_readiness_report,
|
||||
};
|
||||
use crate::storage::apply_cors_headers;
|
||||
use crate::storage::request_context::{
|
||||
@@ -939,9 +938,10 @@ where
|
||||
.expect("failed to build health busy response");
|
||||
}
|
||||
|
||||
let readiness_report = match probe {
|
||||
HealthProbe::Liveness => liveness_dependency_readiness_report(),
|
||||
HealthProbe::Readiness => collect_dependency_readiness_report().await,
|
||||
let readiness_report = if probe == HealthProbe::Readiness {
|
||||
Some(collect_dependency_readiness_report().await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let kms_ready = if probe == HealthProbe::Readiness && health_compat_kms_ready_check_enabled() {
|
||||
Some(health_kms_ready().await)
|
||||
@@ -949,7 +949,8 @@ where
|
||||
None
|
||||
};
|
||||
|
||||
let response_parts = build_health_response_parts(method, probe, &readiness_report, "rustfs-endpoint", None, kms_ready);
|
||||
let response_parts =
|
||||
build_health_response_parts(method, probe, readiness_report.as_ref(), "rustfs-endpoint", None, kms_ready);
|
||||
let body = response_parts
|
||||
.payload
|
||||
.map(|payload| Bytes::from(serde_json::to_vec(&payload).unwrap_or_else(|_| b"{}".to_vec())))
|
||||
@@ -1654,7 +1655,12 @@ mod tests {
|
||||
);
|
||||
|
||||
let body = BodyExt::collect(response.into_body()).await.expect("body").to_bytes();
|
||||
assert!(body.windows(br#""status":"#.len()).any(|window| window == br#""status":"#));
|
||||
let payload: serde_json::Value =
|
||||
serde_json::from_slice(&body).expect("public liveness health response should be valid JSON");
|
||||
assert_eq!(payload["status"], "ok");
|
||||
assert_eq!(payload["ready"], true);
|
||||
assert!(payload.get("details").is_none());
|
||||
assert!(payload.get("degradedReasons").is_none());
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ pub(crate) use readiness::ReadinessDegradedReason;
|
||||
pub(crate) use readiness::ReadinessGateLayer;
|
||||
pub(crate) use readiness::collect_dependency_readiness;
|
||||
pub(crate) use readiness::collect_dependency_readiness_report;
|
||||
pub(crate) use readiness::liveness_dependency_readiness_report;
|
||||
pub use readiness::publish_ready_when_runtime_ready;
|
||||
pub(crate) use readiness::snapshot_dependency_readiness_report;
|
||||
|
||||
|
||||
@@ -442,17 +442,6 @@ fn dependency_readiness_report_from_readiness(readiness: DependencyReadiness) ->
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn liveness_dependency_readiness_report() -> DependencyReadinessReport {
|
||||
DependencyReadinessReport {
|
||||
readiness: DependencyReadiness {
|
||||
storage_ready: true,
|
||||
iam_ready: true,
|
||||
lock_quorum_ready: true,
|
||||
},
|
||||
degraded_reasons: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn collect_dependency_readiness() -> DependencyReadiness {
|
||||
collect_dependency_readiness_report().await.readiness
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user