Debug the Broker
Goal
Triage a misbehaving broker using the typed errors, metric labels, and tracing fields the daemon emits. The broker fails closed by design; most live-incident classes surface as a typed error before any backend dispatch.
Recipe
- Read
docs/public/broker-operator/README.md§ Debug runbook for the structured walkthrough. - For startup failures, run
cargo run -p ovstorage-cli -- list-routes --config /etc/ovstorage/broker.tomlagainst the broker's config. The same TOML validator the daemon runs at startup will report bad routes, unresolvable secret refs, and invalid listener authn config without binding any sockets. - For authz denies, look at the tracing output for
broker_authz_decisions_total{outcome="deny"}. The deny path threads the plugin'sreasonandexplanationinto the gRPCPermissionDeniedmessage; theexplanationis typically a stable rule id from the built-in policy evaluator. - For
PolicyEpochStaleerrors, the request was stamped with a pre-reload epoch. Nothing in the SDK refreshes and retries it: it reaches the caller asFailedPrecondition(HTTP 409), which the caller re-issues itself against the reloaded policy. - For listener authn failures (
Unauthenticated), confirm the mode matches what your callers send:jwt_verifymode: bearer JWT against the configuredissuer/audience/jwks_url. JWKS is cached 5 minutes; an unknownkidtriggers one refetch beforeUnauthenticated.peer_credmode: UDS / npipe only. Invalid on TCP listeners.trusted_*modes: only ontrusted_proxy = truelisteners withtrusted_peersCIDR allow-list.mtls: reserved; startup fails withUnsupportedif configured.
- For "broker is up but no routes are visible," check
[[connections]]blocks: backends register at startup; a missing or misnamed plugin manifest fails startup. ConfirmOVSTORAGE_PLUGIN_DIRis set and readable. - For streaming-write issues (slow, corrupted, aborted),
distinguish:
- Graceful EOF (
Ok(None)from the inbound stream) — broker commits with the chunks received. - RST_STREAM(CANCEL) (
Err(status)withstatus.code() == Cancelled) — broker aborts withStatus::cancelled. Look forbroker.writespans ending inCancelledto identify these. - Inline upload is bounded by
WRITE_BODY_BYTE_CAP(64 MiB) after authz. Larger writes need the redirect branch (S3 multipart, GCS resumable, etc.); the plugin must mint redirects viaWriteStep::Redirects.
- Graceful EOF (
- For lifecycle issues:
broker_lifecycle_events_total{event="reload_failed"}increments on validation failure; old broker stays live.broker_policy_epoch_advances_totalincrements on successful reload.- Drain hangs: tune
drain_timeout(defaultDEFAULT_DRAIN_TIMEOUT). A wedged plugin / upstream pushes the broker past the deadline and forces truncated in-flight RPCs.
Common live-incident classes
- Cert expired. TCP+TLS handshake fails; rotate via SIGHUP (Unix) or process restart (Windows). Cert hot-reload is not implemented.
- JWKS unreachable.
Unauthenticatedfor everyjwt_verifyrequest. Check the IdP's reachability from the broker host. - Authorization latency. The built-in policy evaluator is local and
synchronous; rising RPC latency points to listener authentication, storage
dispatch, or an outer timeout rather than a remote policy plugin. Watch
histogram_quantile(0.99, rate(broker_rpc_seconds_bucket[5m]))trending up — though note the RPC-latency histogram is dormant today (registered but not observed), so this query may return empty. - State root I/O error. Policy-epoch persistence fails;
broker_lifecycle_events_total{event="reload_failed"}increments and the broker keeps the previous epoch. - Watch fan-out exhausted. The 257th concurrent watcher for one
principal (summed across every watch key on the connection) receives
ResourceExhausted(the per-principal cap is 256).
Pre-deploy validation
Always run
cargo run -p ovstorage-cli -- list-routes --config <broker-config>
against a config change before SIGHUP or restart. The same
validator the daemon runs at startup will surface typed errors
without binding any socket.
What's not debuggable today
- No
explain-decision <audit-id>tool. Trace theaudit_idthrough your log aggregator. - No durable audit sink; rely on
tracingoutput. route.idandbackend.idare not stamped on per-RPC spans yet — closing that gap is a tracked work item.
References
docs/public/broker-operator/README.md— full debug runbook + observability + implementation gaps.ovstorage-operator-monitor-broker— what to scrape and which spans carry which fields.ovstorage-operator-configure-broker-policy— interpreting authz denies and policy-epoch advances.ovstorage-operator-deploy-broker— baseline deployment checklist.