mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-09-29 17:13:33 +01:00
* Add one-shot mode and docker-given totals to stats API - Add one-shot query option to container stats to skip the calculated CPU percentage window, returning docker-given totals directly. - Refactor container_stats() with translatable, structured Docker errors and timeout handling instead of generic Docker exceptions. - Translate low-level Docker errors into component-flavored errors (NotRunningError/StatsTimeoutError/UnknownError) for Home Assistant, Supervisor, and each plugin (Audio, Cli, CoreDNS, Multicast, Observer) as well as apps, mirroring the existing App.stats() pattern for consistent, informative API responses. - Add a shared api_return_stats() helper to remove duplicated stats handling logic across the 8 stats API endpoints. - DockerContainerNotFoundError now inherits from DockerNotFound. * Contain aiodocker _query access and split v1/v2 stats response model - Contain the reach into aiodocker's protected _query internals (needed for one-shot stats until aio-libs/aiodocker#1054 lands) to a single DockerAPI._query_one_shot_stats() helper. - Split the stats API response model between v1 (legacy, windowed CPU percentage still available via opt-in one_shot query string) and v2 (always one-shot, no query string, cpu_percent dropped since it would never carry a meaningful value). Both share the same api_return_stats(stats, legacy=...) helper so the response model stays in one place. - DockerAPI.container_stats() keeps its existing one_shot keyword only; the legacy/v1 vs v2 distinction is handled entirely in the API layer. * Detect stopped/restarting containers in one-shot stats response Docker returns a stub response containing only the container's id/name (no cpu_stats/memory_stats/networks data) for a stopped or restarting container instead of an error, even in one-shot mode - the not-running check in daemon.ContainerStats happens before the one-shot branch, so requesting one-shot stats doesn't change this. Detect the missing online_cpus field (only ever present while running) and raise DockerContainerNotRunningError instead of returning a payload that DockerStats would otherwise turn into a successful all-zero response. * Add tests for stats error paths introduced in one-shot/totals PR - Cover DockerAPI.container_stats/_query_one_shot_stats edge cases: empty one-shot response, empty windowed response, and the raw _query_one_shot_stats helper. - Add stats() error-translation tests (not-running, timeout, unknown) for the audio, cli, multicast and observer plugins, none of which had any coverage. - Add stats endpoint tests (v1 + v2) for the cli, audio, dns, multicast and observer REST APIs, plus a v1 success-path test for the apps stats endpoint. These close the codecov-flagged gaps for newly introduced code in this PR. * Unify one-shot and windowed container_stats() code paths Both modes now share the same error handling and not-running detection: - The windowed path no longer inspects the container first to check its running state. Docker returns the same id/name-only stub response (no online_cpus in cpu_stats) for a stopped or restarting container in windowed mode as it does in one-shot mode, so the existing online_cpus presence check now covers both, closing a race window where a container stopping between the inspect and the stats call would previously reach the DockerStats parser with an incomplete payload. - The windowed path builds its container handle via containers.container(), which constructs a DockerContainer from the name with no I/O, instead of containers.get(), which performs an unnecessary inspect call just to fetch the same information the stats call already returns. * Fix protected access pylint issue
43 lines
1.3 KiB
Python
43 lines
1.3 KiB
Python
"""Test observer api."""
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from aiodocker.containers import DockerContainer
|
|
from aiohttp.test_utils import TestClient
|
|
|
|
from supervisor.docker.manager import DockerAPI
|
|
|
|
from tests.common import load_json_fixture
|
|
|
|
|
|
async def test_api_observer_stats(
|
|
api_client_with_prefix: tuple[TestClient, str], container: DockerContainer
|
|
):
|
|
"""Test observer stats."""
|
|
api_client, prefix = api_client_with_prefix
|
|
container.show.return_value["State"]["Status"] = "running"
|
|
container.show.return_value["State"]["Running"] = True
|
|
|
|
if prefix == "/v2":
|
|
stats_fixture = load_json_fixture("container_stats.json")
|
|
del stats_fixture["precpu_stats"]
|
|
with patch.object(
|
|
DockerAPI,
|
|
"_query_one_shot_stats",
|
|
AsyncMock(return_value=stats_fixture),
|
|
):
|
|
resp = await api_client.get(f"{prefix}/observer/stats")
|
|
else:
|
|
container.stats = AsyncMock(
|
|
return_value=[load_json_fixture("container_stats.json")]
|
|
)
|
|
resp = await api_client.get(f"{prefix}/observer/stats")
|
|
|
|
assert resp.status == 200
|
|
result = await resp.json()
|
|
if prefix == "/v2":
|
|
assert "cpu_percent" not in result["data"]
|
|
else:
|
|
assert result["data"]["cpu_percent"] == 90.0
|
|
assert result["data"]["memory_usage"] == 59700000
|