a78af23793
The previous commit's integration test failed at collection-time import:
importing plugins.host_agent.routes (which imports the host_agent ORM models at
top level) double-registers host_agent_registrations against the app-loaded
plugin's metadata ("Table already defined").
Move the two pure read helpers (_latest_metrics_for_host, _history_for_host)
plus SOURCE_MODULE / HISTORY_METRICS into plugins/host_agent/metrics_query.py,
which imports only the core PluginMetric model — no plugin models. routes.py
imports them back. The integration test now imports from metrics_query and no
longer trips the loader's registration guard. No behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Jg27rgypiW2efULXJDtMC
81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
"""Integration: host-metrics read paths (DISTINCT ON latest + SQL date_bin history).
|
|
|
|
Validates the two host_agent query helpers that back the slow host views, against
|
|
a live Postgres: the latest-per-(resource,metric) lookup and the bucket-averaged
|
|
history (which now aggregates in SQL instead of shipping raw rows to Python).
|
|
Requires STEWARD_DATABASE_URL.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
_NEEDS_DB = pytest.mark.skipif(
|
|
not os.environ.get("STEWARD_DATABASE_URL"),
|
|
reason="integration test needs a live Postgres (STEWARD_DATABASE_URL)",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
if not os.environ.get("STEWARD_DATABASE_URL"):
|
|
pytest.skip("needs Postgres")
|
|
from steward.app import create_app
|
|
return create_app(testing=False)
|
|
|
|
|
|
@_NEEDS_DB
|
|
def test_latest_distinct_on_and_sql_bucketed_history(app):
|
|
from sqlalchemy import text
|
|
from steward.models.metrics import PluginMetric
|
|
from plugins.host_agent.metrics_query import (
|
|
SOURCE_MODULE, _history_for_host, _latest_metrics_for_host,
|
|
)
|
|
|
|
now = datetime.now(timezone.utc)
|
|
hostname = "metrics-host-" + uuid.uuid4().hex[:8]
|
|
|
|
async def _go():
|
|
async with app.db_sessionmaker() as s:
|
|
async with s.begin():
|
|
await s.execute(
|
|
text("DELETE FROM plugin_metrics WHERE resource_name LIKE :p"),
|
|
{"p": hostname + "%"},
|
|
)
|
|
rows = []
|
|
# Host-level CPU samples, oldest → newest (10, 20, 30).
|
|
for i, val in enumerate([10.0, 20.0, 30.0]):
|
|
rows.append(PluginMetric(
|
|
source_module=SOURCE_MODULE, resource_name=hostname,
|
|
metric_name="cpu_pct", value=val,
|
|
recorded_at=now - timedelta(minutes=30 - i * 10),
|
|
))
|
|
# A sub-resource (root mount) to exercise the host:% match.
|
|
rows.append(PluginMetric(
|
|
source_module=SOURCE_MODULE, resource_name=hostname + ":/",
|
|
metric_name="disk_used_pct", value=80.0,
|
|
recorded_at=now - timedelta(minutes=5),
|
|
))
|
|
s.add_all(rows)
|
|
|
|
latest = await _latest_metrics_for_host(s, hostname)
|
|
hist = await _history_for_host(s, hostname, now - timedelta(hours=1))
|
|
return latest, hist
|
|
|
|
latest, hist = asyncio.run(_go())
|
|
|
|
# DISTINCT ON returns the newest sample per (resource, metric).
|
|
assert latest[hostname]["cpu_pct"] == 30.0
|
|
assert latest[hostname + ":/"]["disk_used_pct"] == 80.0
|
|
|
|
# SQL date_bin aggregation returns cpu_pct buckets; averages stay in range.
|
|
cpu = hist["cpu_pct"]
|
|
assert cpu, "expected bucketed cpu_pct history"
|
|
assert all(10.0 <= v <= 30.0 for _, v in cpu)
|