The RIB handles swallowed every failure of their actor: an UPDATE applied to a stopped Adj-RIB-In vanished, and a snapshot of it came back empty. Sessions stayed up, peers' routes went nowhere, and the status page showed a healthy router with no routes. An actor stops only by panicking, so this is the state the server would be in after a bug. - Every operation on AdjRibIn and LocalRib returns Result<_, RibStopped>. LocalRibError is replaced by it. - The server waits on both actors, the advertiser and the dialers. If one stops it sends every peer Cease / Administrative Shutdown and returns an error, so the process exits and can be restarted. Before, the tasks' handles were held and never looked at. - A session that cannot store an UPDATE ends with the same Cease. - The HTTP API answers 503 with the reason for anything that needs a table it cannot read, including /metricz: a scrape that fails is noticed, last known sizes are not. Also: - The routes of a session are now removed even if its task is torn down without running its cleanup (a panic), instead of staying in the table as routes of a peer that is gone. - Table sizes for the status page and metrics are asked for as a count instead of copying the whole table to take its length. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
39 lines
1.4 KiB
Rust
39 lines
1.4 KiB
Rust
//! Prometheus scrape endpoint. Counters are updated by the server crate as
|
|
//! messages flow; table-size gauges are refreshed at scrape time.
|
|
|
|
use axum::extract::State;
|
|
use axum::http::{StatusCode, header};
|
|
use axum::response::IntoResponse;
|
|
use bgp_rib::RibStopped;
|
|
use bgp_server::metrics::{ADJ_RIB_IN_ROUTES, LOCAL_ROUTES};
|
|
use prometheus::{Encoder, TextEncoder};
|
|
|
|
use crate::AppState;
|
|
|
|
pub async fn metricz(State(state): State<AppState>) -> impl IntoResponse {
|
|
// A scrape that cannot read the tables fails. Serving the last known
|
|
// sizes would hide that the daemon is broken from the one system whose
|
|
// job is to notice.
|
|
let sizes = async {
|
|
Ok::<_, RibStopped>((state.local_rib.len().await?, state.adj_rib_in.len().await?))
|
|
};
|
|
match sizes.await {
|
|
Ok((local, received)) => {
|
|
LOCAL_ROUTES.set(i64::try_from(local).unwrap_or(i64::MAX));
|
|
ADJ_RIB_IN_ROUTES.set(i64::try_from(received).unwrap_or(i64::MAX));
|
|
}
|
|
Err(e) => return (StatusCode::SERVICE_UNAVAILABLE, e.to_string()).into_response(),
|
|
}
|
|
|
|
let mut buf = Vec::new();
|
|
match TextEncoder::new().encode(&prometheus::gather(), &mut buf) {
|
|
Ok(()) => (
|
|
StatusCode::OK,
|
|
[(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
|
|
buf,
|
|
)
|
|
.into_response(),
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
}
|
|
}
|