The host page gains a "Who's sending" panel, and the MCP a tool, that put the host's outbound (or inbound) traffic down to its containers and NFS mounts: read in shared time buckets, so the host's busiest bucket says who was sending in it. A swarm service's tasks add up under its name; what none of them accounts for is the host itself. Also fixes CI 9488: per-container net_rx_bps / net_tx_bps join the docker alert catalog, so a rule can catch a container flooding the uplink. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.3 KiB
Python
58 lines
2.3 KiB
Python
"""Integration (#6115): a host's traffic put down to its containers and NFS
|
|
mounts, read from plugin_metrics in shared time buckets on Postgres."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
@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)
|
|
|
|
|
|
def test_the_host_s_peak_is_put_down_to_its_senders(app):
|
|
from steward.models.metrics import PluginMetric
|
|
from plugins.host_agent.senders import senders
|
|
|
|
now = datetime.now(timezone.utc)
|
|
host = "senders-host-" + uuid.uuid4().hex[:8]
|
|
peak, later = now - timedelta(minutes=30), now - timedelta(minutes=10)
|
|
task = "a" * 25
|
|
|
|
def m(source, resource, metric, value, at):
|
|
return PluginMetric(source_module=source, resource_name=resource, metric_name=metric,
|
|
value=value, recorded_at=at)
|
|
|
|
async def _go():
|
|
async with app.db_sessionmaker() as s:
|
|
async with s.begin():
|
|
s.add_all([
|
|
m("host_agent", host, "net_tx_bps", 1000.0, peak),
|
|
m("host_agent", host, "net_tx_bps", 100.0, later),
|
|
# one service's two tasks add up; another host's don't count
|
|
m("docker", f"{host}/web.1.{task}", "net_tx_bps", 600.0, peak),
|
|
m("docker", f"{host}/web.2.{task}", "net_tx_bps", 200.0, peak),
|
|
m("docker", f"{host}/web.1.{task}", "net_tx_bps", 50.0, later),
|
|
m("docker", f"{host}x/web.1.{task}", "net_tx_bps", 9999.0, peak),
|
|
m("host_agent", f"{host}:/nfs/data", "nfs_tx_bps", 100.0, peak),
|
|
m("host_agent", f"{host}:net:eno1", "net_tx_bps", 1000.0, peak),
|
|
])
|
|
async with app.db_sessionmaker() as s:
|
|
return await senders(s, host, now - timedelta(hours=1))
|
|
|
|
got = asyncio.run(_go())
|
|
assert got["host"]["peak_bps"] == 1000.0 and got["bucket_s"] == 60
|
|
assert [(r["kind"], r["name"], r["at_host_peak_bps"]) for r in got["senders"]] == [
|
|
("container", "web", 800.0), ("nfs", "/nfs/data", 100.0)]
|
|
assert got["unaccounted_at_peak_bps"] == 100.0
|