#5475 "Rule form: pick the host, then its part…". CI run 8909 caught it: moving from the whole host to a mount kept "CPU" in the mount's Metric list, because a chosen metric always stayed on offer. Only a rule's own page keeps its resource and metric after they go quiet (keep=True from edit_rule); the pickers' swaps drop a metric the new part doesn't report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
3.5 KiB
Python
73 lines
3.5 KiB
Python
"""The rule form's target pickers (#5475): for a host source, the hosts in
|
|
one picker and the chosen host's parts in another, grouped by kind, each
|
|
offering only the metrics it reports that a rule can watch; a part that
|
|
reports nothing a rule can watch (an interface's traffic) is left out."""
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from steward.alerts import service as alert_svc
|
|
|
|
CATALOG = {"host_agent": ["cpu_pct", "disk_used_pct", "uptime_secs", "seconds_since_seen"]}
|
|
REPORTED = [("h1", "cpu_pct"), ("h1", "uptime_secs"), ("h1", "seconds_since_seen"),
|
|
("h1:/", "disk_used_pct"), ("h1:/var", "disk_used_pct"),
|
|
("h1:core10", "cpu_pct"), ("h1:core2", "cpu_pct"),
|
|
("h1:net:eth0", "net_rx_bps"), ("h1:temp:Core 0", "temp_c"),
|
|
("h2", "cpu_pct")]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _catalog(monkeypatch):
|
|
monkeypatch.setattr(alert_svc, "metric_catalog", lambda: CATALOG)
|
|
|
|
|
|
def _db(rows=REPORTED):
|
|
async def execute(stmt):
|
|
return SimpleNamespace(all=lambda: list(rows))
|
|
return SimpleNamespace(execute=execute)
|
|
|
|
|
|
@pytest.mark.parametrize("part, kind, label", [
|
|
("/", "mount", "/"), ("/nfs/media", "mount", "/nfs/media"), ("core7", "core", "core 7"),
|
|
("diskio:sda", "disk", "sda"), ("net:bond0", "net", "bond0"),
|
|
("temp:Package id 0", "temp", "Package id 0"), ("corex", "other", "corex")])
|
|
def test_a_part_is_known_by_its_name(part, kind, label):
|
|
assert alert_svc.part_kind(part) == (kind, label)
|
|
|
|
|
|
async def test_hosts_and_their_parts_are_picked_apart():
|
|
got = await alert_svc.rule_target(_db(), "host_agent")
|
|
assert got["hosts"] == ["h1", "h2"] and (got["host"], got["resource"]) == ("h1", "h1")
|
|
assert got["parts"] == [
|
|
("", [("h1", "The whole host")]),
|
|
("Mounts", [("h1:/", "/"), ("h1:/var", "/var")]),
|
|
("CPU cores", [("h1:core2", "core 2"), ("h1:core10", "core 10")])]
|
|
assert got["metrics"] == ["cpu_pct", "uptime_secs", "seconds_since_seen"] # catalog order
|
|
|
|
|
|
async def test_a_part_offers_only_what_it_reports():
|
|
got = await alert_svc.rule_target(_db(), "host_agent", resource="h1:/var", metric="cpu_pct")
|
|
assert (got["host"], got["resource"]) == ("h1", "h1:/var")
|
|
assert got["metrics"] == ["disk_used_pct"] and got["metric"] == "" # cpu_pct isn't a mount's
|
|
|
|
|
|
async def test_picking_another_host_starts_at_the_whole_host():
|
|
got = await alert_svc.rule_target(_db(), "host_agent", host="h2", resource="h1:/var")
|
|
assert (got["host"], got["resource"]) == ("h2", "h2")
|
|
assert got["parts"] == [("", [("h2", "The whole host")])]
|
|
|
|
|
|
async def test_a_rule_s_own_resource_stays_on_offer_after_it_goes_quiet():
|
|
got = await alert_svc.rule_target(_db(), "host_agent", resource="h3:/data",
|
|
metric="disk_used_pct", keep=True)
|
|
assert "h3" in got["hosts"] and got["host"] == "h3"
|
|
assert got["parts"] == [("Mounts", [("h3:/data", "/data")])]
|
|
assert got["metric"] == "disk_used_pct"
|
|
# A reporting part whose metric went quiet keeps it on the rule's own page.
|
|
got = await alert_svc.rule_target(_db(), "host_agent", resource="h1:/var", metric="disk_free_pct",
|
|
keep=True)
|
|
assert got["metric"] == "" # not in the catalog: not offered
|
|
got = await alert_svc.rule_target(_db(), "host_agent", resource="h1:/var", metric="uptime_secs",
|
|
keep=True)
|
|
assert got["metrics"] == ["disk_used_pct", "uptime_secs"] and got["metric"] == "uptime_secs"
|