1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! `POST /api/v1/build-lease/decisions` — the daemon's log of every build-lease
//! admission decision (#8261 increment two).
//!
//! Why: `tm build-lease` decides locally (a flock needs no daemon), but an
//! operator reconstructing why a build waited needs one place that saw every
//! decision on the machine with its readings. The daemon log is that place.
//! What: accepts the decision JSON `tm build-lease` posts and writes one
//! `tracing` INFO line (WARN for a timeout) carrying the verdict, command,
//! readings, reasons, degraded signals, holders and ceiling. It stores nothing
//! and gates nothing.
//! Test: `a_decision_is_logged_and_acknowledged`.
use std::sync::Arc;
use axum::{Json, Router, http::StatusCode, routing::post};
use serde_json::Value;
use super::state::DaemonState;
/// The route, merged by `builder_slot_routes::router` (`api.rs` sits at its
/// frozen line-cap budget).
pub fn router() -> Router<Arc<DaemonState>> {
Router::new().route("/api/v1/build-lease/decisions", post(log_decision))
}
/// Log one decision.
///
/// Test: `a_decision_is_logged_and_acknowledged`.
async fn log_decision(Json(body): Json<Value>) -> StatusCode {
log_decision_op(&body);
StatusCode::NO_CONTENT
}
/// The transport-neutral body of the decision log, shared by the HTTP route
/// and the socket's `mpm.build_lease.decision` (#6288 step 1).
///
/// Test: `a_decision_is_logged_and_acknowledged`,
/// `rpc_build_lease_decision_is_acknowledged`.
pub(crate) fn log_decision_op(body: &Value) {
let field = |key: &str| body.get(key).map(ToString::to_string).unwrap_or_default();
let verdict = body
.get("verdict")
.and_then(Value::as_str)
.unwrap_or("unknown");
if verdict == "admitted" {
tracing::info!(
target: "build_lease",
verdict,
command = %field("command"),
pid = %field("pid"),
held = %field("held"),
n_effective = %field("n_effective"),
ceiling = %field("ceiling"),
degraded = %field("degraded"),
readings = %field("readings"),
"build-lease admission decision"
);
} else {
tracing::warn!(
target: "build_lease",
verdict,
command = %field("command"),
pid = %field("pid"),
held = %field("held"),
n_effective = %field("n_effective"),
ceiling = %field("ceiling"),
withheld = %field("withheld"),
degraded = %field("degraded"),
holders = %field("holders"),
readings = %field("readings"),
"build-lease admission decision"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_decision_is_logged_and_acknowledged() {
let body = serde_json::json!({
"verdict": "timed-out",
"command": "cargo test",
"readings": "host h, memory pressure warn",
"withheld": ["memory pressure is warn"],
"ceiling": 4,
});
assert_eq!(log_decision(Json(body)).await, StatusCode::NO_CONTENT);
}
}