aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/openapi.yaml5
-rw-r--r--src/api/health.rs21
-rw-r--r--src/api/mod.rs2
3 files changed, 24 insertions, 4 deletions
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
index 96132cf..2067aac 100644
--- a/docs/openapi.yaml
+++ b/docs/openapi.yaml
@@ -348,7 +348,10 @@ paths:
get:
tags: [Admin]
summary: Liveness
- description: The HTTP API is up. Unauthenticated.
+ description: |
+ The HTTP API is up. Unauthenticated. A second line while this node's clock is
+ off from most of its peers' (`measurements.md`, Clocks), still `200`:
+ `clock off: 40s behind most of its peers'`.
security: []
responses:
'200':
diff --git a/src/api/health.rs b/src/api/health.rs
index b1e4523..3bd374e 100644
--- a/src/api/health.rs
+++ b/src/api/health.rs
@@ -1,15 +1,32 @@
//! `GET /health` and `GET /ready`: unauthenticated, for supervisors and load balancers.
-//! They say whether the node is up and whether it serves the store, nothing more.
+//! They say whether the node is up and whether it serves the store, and `/health` on a
+//! node whether its clock is off, nothing more.
+
+use std::time::Duration;
use axum::{extract::State, http::StatusCode};
use crate::state::AppState;
-/// The HTTP API is up.
+/// The HTTP API is up (a relay's).
pub(crate) async fn health() -> &'static str {
"ok\n"
}
+/// The HTTP API is up (a node's); a second line while its clock is off from its peers'
+/// (`swarm::skew`), which doesn't make it less up.
+pub(crate) async fn node_health(State(state): State<AppState>) -> String {
+ let skew = crate::swarm::skew::measure(&state).await;
+ match skew.offset_ms {
+ Some(offset) if skew.off => format!(
+ "ok\nclock off: {} {} most of its peers'\n",
+ crate::units::format_duration(Duration::from_millis(offset.unsigned_abs())),
+ if offset < 0 { "behind" } else { "ahead of" }
+ ),
+ _ => "ok\n".to_owned(),
+ }
+}
+
/// The node serves reads and writes: ready, not joining or rejoining.
pub(crate) async fn ready(State(state): State<AppState>) -> (StatusCode, &'static str) {
if state.is_ready().await {
diff --git a/src/api/mod.rs b/src/api/mod.rs
index fb860c7..528df35 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -77,7 +77,7 @@ pub(crate) fn router(state: AppState, auth_state: AuthState, debug_api: bool) ->
SwaggerUi::new("/swagger-ui").config(utoipa_swagger_ui::Config::from("/openapi.yaml")),
)
.route("/openapi.yaml", get(openapi_spec))
- .route("/health", get(health::health))
+ .route("/health", get(health::node_health))
.route("/ready", get(health::ready))
// Outermost, so authentication failures get the error body too.
.layer(middleware::from_fn(json_errors))