fix(mcp): get_monitor says how often a monitor on the global interval runs (#5433)
check_interval_s was `m.check_interval_seconds or None`, so a monitor following monitors.poll_interval_seconds read as null and a reader couldn't tell how often it ran. Monitor results now carry the effective interval and `interval_from: "monitor" | "global"`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import json
|
||||
from sqlalchemy import select
|
||||
|
||||
from steward.core import renames
|
||||
from steward.mcp._context import mcp_app
|
||||
from steward.models.hosts import Host
|
||||
from steward.models.monitors import MONITOR_TYPE_VALUES, Monitor
|
||||
from steward.models.users import UserRole
|
||||
@@ -27,6 +28,15 @@ def _state(m: Monitor, latest) -> str:
|
||||
return "up" if latest.is_up else "down"
|
||||
|
||||
|
||||
def _interval(m: Monitor) -> dict:
|
||||
"""How often a monitor runs: its own interval, or the global one it
|
||||
follows when it has none (#5433), and which."""
|
||||
if m.check_interval_seconds:
|
||||
return {"check_interval_s": m.check_interval_seconds, "interval_from": "monitor"}
|
||||
return {"check_interval_s": int(mcp_app().config.get("MONITORS_POLL_INTERVAL", 60)),
|
||||
"interval_from": "global"}
|
||||
|
||||
|
||||
def _tls_days(latest) -> float | None:
|
||||
if latest is None or latest.tls_expires_at is None:
|
||||
return None
|
||||
@@ -112,8 +122,10 @@ async def get_monitor(monitor: str, failures: int = 10) -> dict:
|
||||
newest first, each with its error message.
|
||||
|
||||
`heartbeat` is its last 30 checks oldest-first as a string, "+" up and "-"
|
||||
down, to show a flapping check at a glance. Header values in an HTTP
|
||||
monitor's configuration are redacted.
|
||||
down, to show a flapping check at a glance. `check_interval_s` is how
|
||||
often it runs; `interval_from` says whether that is its own setting
|
||||
("monitor") or the global monitors interval it follows ("global").
|
||||
Header values in an HTTP monitor's configuration are redacted.
|
||||
"""
|
||||
failures = clamp_limit(failures, 10, 50)
|
||||
async with db_session() as db:
|
||||
@@ -123,7 +135,7 @@ async def get_monitor(monitor: str, failures: int = 10) -> dict:
|
||||
failed = await monitor_svc.recent_failures(db, m.id, failures)
|
||||
return {
|
||||
**_row(m, st, host_names),
|
||||
"check_interval_s": m.check_interval_seconds or None,
|
||||
**_interval(m),
|
||||
"config": _config(m),
|
||||
"heartbeat": "".join("+" if r.is_up else "-" for r in st["recent"]),
|
||||
"recent_failures": [
|
||||
@@ -156,7 +168,7 @@ def _saved(m: Monitor, host_names: dict[str, str]) -> dict:
|
||||
"target": redact_url(m.target),
|
||||
"host_name": host_names.get(m.host_id) if m.host_id else None,
|
||||
"enabled": m.enabled,
|
||||
"check_interval_s": m.check_interval_seconds or None,
|
||||
**_interval(m),
|
||||
"config": _config(m),
|
||||
"next": f'get_monitor(monitor="{m.id}")',
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ def test_monitor_rule_ack_and_maintenance_writes(app):
|
||||
assert made["target"] == "https://nas.lan" and made["host_name"] == host_name
|
||||
assert made["config"]["headers"] == {"Authorization": "<redacted>"}
|
||||
assert made["config"]["verify_ssl"] is True
|
||||
# No interval of its own: it says which it follows, and how often (#5433).
|
||||
assert (made["check_interval_s"], made["interval_from"]) == (
|
||||
int(app.config.get("MONITORS_POLL_INTERVAL", 60)), "global")
|
||||
|
||||
# ── rules ──────────────────────────────────────────────────────────
|
||||
opts = await a.get_alert_rule_options("http")
|
||||
@@ -100,7 +103,9 @@ def test_monitor_rule_ack_and_maintenance_writes(app):
|
||||
assert renamed["carried"]["from"] == mon_name and renamed["carried"]["rules"] == 1
|
||||
async with app.db_sessionmaker() as s:
|
||||
assert (await s.get(AlertRule, rule["rule_id"])).resource_name == f"{mon_name}-2"
|
||||
assert "carried" not in await m.update_monitor(f"{mon_name}-2", check_interval_s=30)
|
||||
own = await m.update_monitor(f"{mon_name}-2", check_interval_s=30)
|
||||
assert "carried" not in own
|
||||
assert (own["check_interval_s"], own["interval_from"]) == (30, "monitor")
|
||||
# Another http monitor may not take the same name: they would share one history.
|
||||
with pytest.raises(ValueError, match="already named"):
|
||||
await m.create_monitor(f"{mon_name}-2", "http", "other.lan")
|
||||
|
||||
Reference in New Issue
Block a user