dashboard-v2 PR 3: fleet aggregator via DashboardStatus RPC
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 15s
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 15s
Pivot from per-node dashboard to single-pane-of-glass. The
aggregator (typically the operator's laptop) holds a fleet-CA
leaf cert + the peer list; each dashboard request fans out to
every peer over the existing QUIC/mTLS cluster port and issues
the new DashboardStatus RPC. Peers don't need to run any HTTP
server of their own.
Backend:
* New RPC method DashboardStatus = 0x1c
* Server handler reads BlobStore / TagStore / RefStore /
SnapshotStore / RefTracking counts + on-disk bytes + rustc
release. Cheap: 5 filesystem walks per request.
* Client wrapper call_dashboard_status
* serve_v2 rewritten as aggregator: V2State holds a QuicClient +
peer list from `[[cluster.peers]]`. Endpoints:
GET /api/v2/fleet fan-out to every peer, parallel
GET /api/v2/node/:name/status one peer, on-demand
Failed peers surface as { online: false, error: "..." } cards
instead of dropping.
serve.rs graceful degrade: v2 aggregator routes only mount when
[cluster.tls] is set. Static SPA still serves at /v2/* even
without an aggregator config so operators see the SPA's built-in
"config missing" error.
Deployment model (this session):
* Aggregator runs on quantum (Mac) with a signed leaf.
* Fleet daemons run cluster-only — no HTTP dashboard anywhere
on tank/architect/morpheus. The clawstor-dashboard.service
systemd units on the fleet are being retired.
Frontend rework to consume /api/v2/fleet ships in the next PR.
This commit is contained in:
@@ -169,6 +169,19 @@ pub async fn call_peer_status(conn: &Connection) -> Result<PeerStatusReply> {
|
||||
serde_json::from_slice(&reply).context("decoding PeerStatusReply JSON")
|
||||
}
|
||||
|
||||
/// Convenience wrapper for [`Method::DashboardStatus`]. Feeds the
|
||||
/// dashboard-v2 aggregator. Empty payload → JSON reply with counts
|
||||
/// + on-disk bytes.
|
||||
pub async fn call_dashboard_status(conn: &Connection) -> Result<DashboardStatusReply> {
|
||||
let reply = rpc_call(conn, Method::DashboardStatus, &[]).await?;
|
||||
if reply.len() == 1 {
|
||||
if let Some(code) = decode_error(reply[0]) {
|
||||
bail!("peer replied with error: {}", code.describe());
|
||||
}
|
||||
}
|
||||
serde_json::from_slice(&reply).context("decoding DashboardStatusReply JSON")
|
||||
}
|
||||
|
||||
/// Recognise a single-byte reply as one of our error codes. Returns
|
||||
/// `None` for any other single-byte value (which is a valid reply,
|
||||
/// just an unusually short one).
|
||||
|
||||
Reference in New Issue
Block a user