get_anomaly gives each occurrence against what is usual at that hour, the metric around the newest one with its normal, what else happened on the resource's host around each occurrence (alert transitions, Ansible runs, other anomalies there and a count elsewhere, plus plugins' own events through a new mcp_events hook: the host agent's maintenance jobs and downtime, Docker's container starts, deaths and OOM kills), and how earlier anomalies on the same metric were judged. get_metric_normal reads the learned slots and the level that would be flagged. review_anomaly takes one id or several, all or nothing, with a note. get_anomaly_settings and (admin) set_anomaly_settings read and change the review window and each normal's floor, as Settings → Thresholds does. metric_series.points takes an optional `until`; core.alerts gains host_of_resource; floor_setting names its floor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
4.0 KiB
Python
98 lines
4.0 KiB
Python
# plugins/docker/__init__.py
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from quart import Quart
|
|
|
|
_app: "Quart | None" = None
|
|
|
|
|
|
def setup(app: "Quart") -> None:
|
|
global _app
|
|
_app = app
|
|
from .models import DockerContainer # noqa: F401 — register with Base
|
|
|
|
# Publish the per-host persist hook so the host agent can hand off the
|
|
# `docker` array from each sample without importing our models (opportunistic
|
|
# synergy — host_agent gates on has_capability and no-ops if we're disabled).
|
|
# required_role=viewer: this is a trusted server-side data-plane write driven
|
|
# by the authenticated agent ingest, not a user-facing privileged action.
|
|
from steward.core.capabilities import register_capability
|
|
from steward.models.users import UserRole
|
|
from .ingest import persist_host_docker
|
|
from .retention import prune_docker_logs, run_docker_retention
|
|
register_capability(
|
|
"docker.persist_host_samples", persist_host_docker,
|
|
label="Persist host Docker samples",
|
|
description="Store per-host container state + metrics pushed by the host agent.",
|
|
required_role=UserRole.viewer,
|
|
)
|
|
# Prune lifecycle events, driven by the core cleanup task without it
|
|
# importing our models. Same trusted server-side data-plane role as above.
|
|
# Container CPU and memory live in the one metric store and its retention.
|
|
register_capability(
|
|
"docker.run_retention", run_docker_retention,
|
|
label="Run Docker retention",
|
|
description="Prune Docker lifecycle events past their window.",
|
|
required_role=UserRole.viewer,
|
|
)
|
|
register_capability(
|
|
"docker.prune_logs", prune_docker_logs,
|
|
label="Prune Docker logs",
|
|
description="Keep each container's logs within the age window and byte cap.",
|
|
required_role=UserRole.viewer,
|
|
)
|
|
# Each container's CPU (100% is one core) and memory share learn their
|
|
# normal (#5742), each with its floor from Settings → Thresholds.
|
|
from steward.core.norms import floor_setting, learn_normal
|
|
learn_normal("docker", "cpu_pct", floor_setting(app, "container_cpu_pct"))
|
|
learn_normal("docker", "mem_pct", floor_setting(app, "container_mem_pct"))
|
|
# A host's agent carries its containers' figures: they go silent with it (#5721).
|
|
from steward.core.alerts import agent_fed
|
|
agent_fed("docker", lambda resource: resource.split("/", 1)[0])
|
|
|
|
|
|
def get_scheduled_tasks() -> list:
|
|
# Collection is now agent-driven (pushed to the host_agent ingest); the
|
|
# central socket scrape was removed. No periodic task to register.
|
|
return []
|
|
|
|
|
|
def get_blueprint():
|
|
from .routes import docker_bp
|
|
return docker_bp
|
|
|
|
|
|
def register_mcp_tools(register) -> None:
|
|
from .mcp_tools import register_tools
|
|
register_tools(register)
|
|
|
|
|
|
async def mcp_overview(db) -> list[dict]:
|
|
from .mcp_tools import mcp_overview as findings
|
|
return await findings(db)
|
|
|
|
|
|
async def mcp_events(db, host_id: str, host_name: str, since, until) -> list[dict]:
|
|
from .mcp_tools import mcp_events as events
|
|
return await events(db, host_id, host_name, since, until)
|
|
|
|
|
|
def get_nav() -> list[dict]:
|
|
# Surfaces in the sidebar's Infrastructure group (fleet-wide container view).
|
|
return [{"label": "Docker", "href": "/plugins/docker/"}]
|
|
|
|
|
|
def get_alert_metrics() -> dict[str, list[str]]:
|
|
"""What ingest.py records per container (resource "<host>/<container>"):
|
|
restart_count is cumulative (alert on crash-looping); is_healthy is 1.0 /
|
|
0.0 from the container's HEALTHCHECK, only for containers that define one.
|
|
Per host (resource "<host>"), what Docker keeps on its disk (#5304).
|
|
Swarm-wide (resource "swarm"), from any manager's report: services short
|
|
of their replicas, nodes down and managers unreachable, drained nodes
|
|
aside (#5788, swarm_health.py)."""
|
|
from .disk_view import DISK_METRICS
|
|
from .swarm_health import SWARM_METRICS
|
|
return {"docker": ["cpu_pct", "mem_pct", "restart_count", "is_healthy", *DISK_METRICS, *SWARM_METRICS]}
|