Monitoring: implementation status, and the compat test for INBUXA's data (test 26)
monitoring_compat, ignored, checks the observed settings against a copy of INBUXA's data, reads the old history without an error, and purges it. The status names where each part lives, which suite covers each acceptance test, the tests not exercised, and the known limits.
This commit is contained in:
@@ -530,6 +530,40 @@ these needs a write.
|
|||||||
domains' traces is wanted (MON-31).
|
domains' traces is wanted (MON-31).
|
||||||
12. Size of INBUXA's `o` and `x` subspaces, to check MON-19's estimate.
|
12. Size of INBUXA's `o` and `x` subspaces, to check MON-19's estimate.
|
||||||
|
|
||||||
|
## Implementation status
|
||||||
|
|
||||||
|
Built 2026-09-19 from this spec, clean-room, under the multi-tenancy hand-off
|
||||||
|
brief's rules. Metric history is in `crates/common/src/telemetry/metrics/store.rs`,
|
||||||
|
trace history in `crates/common/src/telemetry/tracers/store.rs`, alerts in
|
||||||
|
`crates/common/src/telemetry/alerts.rs`, the live streams in
|
||||||
|
`crates/http/src/live.rs`, and `x:Metric` and `x:Trace` in
|
||||||
|
`crates/jmap/src/inbuxa/telemetry.rs`, at the paths the shared tests name;
|
||||||
|
upstream files carry hooks marked `inbuxa:`. All four suites in
|
||||||
|
`tests/src/telemetry` are un-gated and pass, so the table above is history:
|
||||||
|
they cover tests 10 and 12 (`tracing.rs`), 13 (`metrics.rs`) and 18
|
||||||
|
(`alerts.rs`). Tests 1, 3, 5 to 9, 11, 14 to 17, 19 to 21, 23 and 24 pass as
|
||||||
|
`tests/src/system/monitoring.rs`.
|
||||||
|
|
||||||
|
- **MON-1 to MON-39:** built, with the limits below.
|
||||||
|
- **Test 26 (compat)** is written as `monitoring_compat`, ignored, and unrun
|
||||||
|
until a copy of INBUXA's data is provided. It checks observed 1, reads the
|
||||||
|
old history without an error, and purges it. It deletes history, so only
|
||||||
|
ever run it against a copy.
|
||||||
|
- **Not exercised by a test:** 2 (an unreachable PostgreSQL tracing store),
|
||||||
|
4 (`queue.count` after a restart; the gauge is set from the queue on each
|
||||||
|
calculation), 22 (a webhook for `telemetry.alert-event`; webhooks are the
|
||||||
|
shared, unchanged path), 25 (a full buffer; the subscriber is lossy by
|
||||||
|
construction), and test 17's 30-minute close.
|
||||||
|
- **Known limits, not requirements of this spec:**
|
||||||
|
- A trace cut at 1000 events (MON-15) carries the number cut as a `total`
|
||||||
|
value on its closing event, since no event type fits a separate marker.
|
||||||
|
- The `inbuxa:admin` scope check on live tokens (MON-23) waits for the auth
|
||||||
|
contract; until then the permission and server-level checks apply.
|
||||||
|
- A live token can be reused within its 60 seconds (Decision, MON-23).
|
||||||
|
- The alert edge trigger (MON-26) is held in memory, so an alert whose
|
||||||
|
condition holds across a restart fires once more.
|
||||||
|
- The stream limit (MON-24) is per node.
|
||||||
|
|
||||||
## Observed
|
## Observed
|
||||||
|
|
||||||
Settled on 2026-09-18 against INBUXA's live Enterprise server (Stalwart
|
Settled on 2026-09-18 against INBUXA's live Enterprise server (Stalwart
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
//! names the test number or requirement.
|
//! names the test number or requirement.
|
||||||
|
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
|
account::Account,
|
||||||
server::{TestServer, TestServerBuilder},
|
server::{TestServer, TestServerBuilder},
|
||||||
smtp::SmtpConnection,
|
smtp::SmtpConnection,
|
||||||
};
|
};
|
||||||
@@ -24,6 +25,7 @@ use registry::schema::{
|
|||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use trc::{ClusterEvent, Collector, EventType, MetricType};
|
use trc::{ClusterEvent, Collector, EventType, MetricType};
|
||||||
|
use types::id::Id;
|
||||||
|
|
||||||
const SECRET: &str = "monitoring test user passphrase";
|
const SECRET: &str = "monitoring test user passphrase";
|
||||||
|
|
||||||
@@ -382,6 +384,86 @@ pub async fn test(test: &mut TestServer) {
|
|||||||
test.wait_for_tasks().await;
|
test.wait_for_tasks().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Acceptance test 26 (compat): INBUXA's settings read back as observed 1,
|
||||||
|
/// its stored history in the stripped encoding is skipped, not an error, and
|
||||||
|
/// one purge past its age removes it. Run against a copy of its data with
|
||||||
|
/// `INBUXA_COMPAT_ADMIN` (`name:password`), `NO_INSERT=1`, and the store's
|
||||||
|
/// `TMPDIR`/`STORE` pointing at the copy. The purge deletes history, so never
|
||||||
|
/// point it at the live store.
|
||||||
|
#[ignore]
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
pub async fn monitoring_compat() {
|
||||||
|
let admin = std::env::var("INBUXA_COMPAT_ADMIN").expect("INBUXA_COMPAT_ADMIN");
|
||||||
|
assert!(std::env::var("NO_INSERT").is_ok(), "NO_INSERT must be set");
|
||||||
|
let test = TestServerBuilder::new("monitoring_compat")
|
||||||
|
.await
|
||||||
|
.with_default_listeners()
|
||||||
|
.await
|
||||||
|
.build_with_opts(false)
|
||||||
|
.await;
|
||||||
|
let (name, secret) = admin.split_once(':').expect("name:password");
|
||||||
|
let admin = Account::new(
|
||||||
|
Box::leak(name.to_string().into_boxed_str()),
|
||||||
|
Box::leak(secret.to_string().into_boxed_str()),
|
||||||
|
&[],
|
||||||
|
"Compat admin",
|
||||||
|
Id::from(u32::MAX),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Observed 1: 30 and 90 days, hourly, both stores Default, no alerts
|
||||||
|
let retention = admin
|
||||||
|
.jmap_method_call("x:DataRetention/get", json!({"ids": ["singleton"]}))
|
||||||
|
.await;
|
||||||
|
let retention = &retention.list()[0];
|
||||||
|
assert_eq!(retention["holdTracesFor"], 30 * 86_400_000u64, "{retention}");
|
||||||
|
assert_eq!(retention["holdMetricsFor"], 90 * 86_400_000u64, "{retention}");
|
||||||
|
assert_eq!(retention["metricsCollectionInterval"]["@type"], "Hourly");
|
||||||
|
for object in ["x:TracingStore/get", "x:MetricsStore/get"] {
|
||||||
|
let store = admin
|
||||||
|
.jmap_method_call(object, json!({"ids": ["singleton"]}))
|
||||||
|
.await;
|
||||||
|
assert_eq!(store.list()[0]["@type"], "Default", "{object}");
|
||||||
|
}
|
||||||
|
let search = admin
|
||||||
|
.jmap_method_call("x:Search/get", json!({"ids": ["singleton"]}))
|
||||||
|
.await;
|
||||||
|
let search = &search.list()[0];
|
||||||
|
assert_eq!(search["indexTelemetry"], true, "{search}");
|
||||||
|
let alerts = admin
|
||||||
|
.jmap_method_call("x:Alert/get", json!({"ids": null}))
|
||||||
|
.await;
|
||||||
|
assert!(alerts.list().is_empty(), "{alerts:?}");
|
||||||
|
|
||||||
|
// Old history: read without an error, whatever can't be decoded skipped
|
||||||
|
for method in ["x:Trace/get", "x:Metric/get"] {
|
||||||
|
let response = admin.jmap_method_call(method, json!({"ids": null})).await;
|
||||||
|
assert!(
|
||||||
|
response.0.pointer("/methodResponses/0/1/list").is_some(),
|
||||||
|
"{method}: {response:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// One purge past its age, and nothing old is left
|
||||||
|
test.server
|
||||||
|
.tracing_store()
|
||||||
|
.purge_spans(Duration::ZERO, Some(test.server.search_store()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
test.server
|
||||||
|
.metrics_store()
|
||||||
|
.purge_metrics(Duration::ZERO)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
for method in ["x:Trace/query", "x:Metric/query"] {
|
||||||
|
let response = admin.jmap_method_call(method, json!({})).await;
|
||||||
|
assert_eq!(
|
||||||
|
response.0.pointer("/methodResponses/0/1/ids"),
|
||||||
|
Some(&json!([])),
|
||||||
|
"{method}: {response:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The first `event:` frame of that name on an event stream, its data.
|
/// The first `event:` frame of that name on an event stream, its data.
|
||||||
async fn first_frame(http: &reqwest::Client, url: &str, event: &str) -> String {
|
async fn first_frame(http: &reqwest::Client, url: &str, event: &str) -> String {
|
||||||
let mut response = http.get(url).send().await.unwrap();
|
let mut response = http.get(url).send().await.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user