phase: 32_gateway_resolved_target_liveness
Fixture and live stream.conf are byte-identical (modulo header comment). Let me test the parser directly:
This commit is contained in:
@@ -21,6 +21,7 @@ a unit test.
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
@@ -44,6 +45,31 @@ from ups_contract import assert_ups_hero_contract
|
||||
pytestmark = pytest.mark.live
|
||||
|
||||
|
||||
def endpoint_identity(out: Any) -> tuple[Any, ...] | None:
|
||||
"""One endpoint in the kernel's currency, derived **here** rather than by the source.
|
||||
|
||||
An address endpoint is its own address; a name endpoint is the addresses the agent's resolver
|
||||
published for it (`out.resolved`), normalised so `::ffff:10.1.2.64` and `10.1.2.64` are one
|
||||
key. `None` is "nothing can name this far end", which is counted nowhere. Phase 32 made this
|
||||
helper necessary: a live router can have two route lines reaching one address through two
|
||||
different names, and a total keyed on the config's text would book it twice.
|
||||
"""
|
||||
port = out.get("port")
|
||||
if not isinstance(port, int):
|
||||
return None
|
||||
spellings = ([out["host"]] if out.get("address") is True
|
||||
else list(out.get("resolved") or []))
|
||||
keys: list[str] = []
|
||||
for spelling in spellings:
|
||||
try:
|
||||
address = ipaddress.ip_address(str(spelling))
|
||||
except ValueError:
|
||||
continue
|
||||
mapped = getattr(address, "ipv4_mapped", None)
|
||||
keys.append(str(mapped if mapped is not None else address))
|
||||
return (min(keys), port) if keys else None
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def settings() -> Settings:
|
||||
"""Live env, not the import-time SETTINGS singleton."""
|
||||
@@ -270,6 +296,11 @@ async def test_the_gateway_source_joins_the_live_ingress(settings: Settings,
|
||||
against the kernel rather than against a fixture. The totals are an inequality by design
|
||||
(`sum(listeners) + sum(endpoints) <= established`): a socket neither config file describes
|
||||
is counted nowhere, and on this router that is most of the table.
|
||||
|
||||
Phase 32 widened **which** routes can be counted — an endpoint the config wrote as an address,
|
||||
or one the gateway's own resolver published for the name it wrote — and the two assertions near
|
||||
the end are stated in those terms, with the endpoint identity derived here so the outbound total
|
||||
is not simply the source's own arithmetic re-added.
|
||||
"""
|
||||
target = next((item for item in settings.host_targets if item.name == settings.gateway_host),
|
||||
None)
|
||||
@@ -307,11 +338,14 @@ async def test_the_gateway_source_joins_the_live_ingress(settings: Settings,
|
||||
for route in payload["connections"]:
|
||||
assert route["in"]["port"] in configured, "a route hangs off no trunk the page draws"
|
||||
out = route["out"]
|
||||
assert (route["connections"] is None) is (out["address"] is not True), (
|
||||
f"{route['id']}: only an endpoint the config wrote as an address can be counted")
|
||||
if out["address"] is True and isinstance(out["port"], int):
|
||||
placed = out["address"] is True or bool(out.get("resolved"))
|
||||
assert (route["connections"] is None) is (not placed), (
|
||||
f"{route['id']}: only an endpoint whose address is known — written, or resolved by the "
|
||||
"gateway itself — can carry a count")
|
||||
key = endpoint_identity(out)
|
||||
if key is not None:
|
||||
joinable.add(str(route["metric"]))
|
||||
endpoints.setdefault((out["host"], out["port"]), route["connections"])
|
||||
endpoints.setdefault(key, route["connections"])
|
||||
else:
|
||||
assert route["metric"] is None, "a route nobody can count publishes no series"
|
||||
live_out = sum(value for value in endpoints.values() if isinstance(value, int))
|
||||
@@ -325,8 +359,61 @@ async def test_the_gateway_source_joins_the_live_ingress(settings: Settings,
|
||||
stored = gateway.samples(payload, 0.0)
|
||||
assert all(sample.source == gateway.SOURCE for sample in stored)
|
||||
assert {sample.metric for sample in stored if "out_conns" in sample.metric} <= joinable, (
|
||||
"only an address endpoint publishes an outbound gauge — a domain-keyed series is the one "
|
||||
"phase 30 stopped writing, because the figure it carried belonged to the port")
|
||||
"only an endpoint whose address the join knows publishes an outbound gauge — written as an "
|
||||
"address, or resolved by the gateway itself (phase 32); an unplaced name publishes nothing")
|
||||
|
||||
|
||||
async def test_a_resolved_target_on_the_live_ingress_is_never_a_dash(
|
||||
settings: Settings, agent_tls: dict[str, collectors.AgentTls]) -> None:
|
||||
"""Phase 32 task 03 against the real ingress: `ok` in the resolver means a number on the page.
|
||||
|
||||
The operator's complaint was that one line out of thirty-three moved, and the acceptance line for
|
||||
removing the dash is that it goes because the gateway published the address — not because a
|
||||
column was hidden. So every route the resolver placed carries a **finite** `connections` (`0`
|
||||
allowed: resolved and idle is a reading), a non-empty `out.resolved`, the agent's own `via`, the
|
||||
timestamp of the lookup, and the gauge that goes with it. Skips when the ingress runs an agent
|
||||
with no `resolver` section yet — which is every host until task 05 re-stages it.
|
||||
"""
|
||||
target = next((item for item in settings.host_targets if item.name == settings.gateway_host),
|
||||
None)
|
||||
if target is None:
|
||||
pytest.skip(f"GATEWAY_HOST={settings.gateway_host!r} is not in HOST_TARGETS")
|
||||
async with httpx.AsyncClient(timeout=settings.gateway_timeout) as client:
|
||||
try:
|
||||
snapshot = await collectors.fetch_snapshot(client, target,
|
||||
timeout=settings.gateway_timeout,
|
||||
tls=agent_tls.get(target.name))
|
||||
except collectors.UpstreamError as exc:
|
||||
pytest.skip(f"{target.name} did not answer: {exc}")
|
||||
section = snapshot.get("resolver")
|
||||
if not isinstance(section, dict):
|
||||
pytest.skip(f"{target.name} publishes no resolver section — agent 1.3.0 is task 05's "
|
||||
"re-stage; until then every name-written route is a dash by design")
|
||||
payload = gateway.parse(snapshot, target.name)
|
||||
if section.get("available") is not True:
|
||||
pytest.skip(f"{target.name}'s resolver did not answer "
|
||||
f"(available={section.get('available')!r}): nothing is placed, so nothing "
|
||||
"is expected to be counted")
|
||||
|
||||
placed = [route for route in payload["connections"] if route["out"].get("resolved")]
|
||||
assert placed, (f"{target.name} published {len(section.get('entries') or [])} resolver "
|
||||
"entries and the join placed none of them")
|
||||
for route in placed:
|
||||
out = route["out"]
|
||||
# The count lives on the **route** (`connections[]`), not on its `out` endpoint — the same
|
||||
# shape `test_the_gateway_source_joins_the_live_ingress` reads. This path only ever ran once
|
||||
# agent 1.3.0 was installed (before that the whole test skipped for want of a `resolver`
|
||||
# section), which is how a wrong key survived task 03: a KeyError is not a dash.
|
||||
assert isinstance(route["connections"], int) and not isinstance(route["connections"], bool), (
|
||||
f"{route['id']}: resolved to {out['resolved']} and the line is still a dash")
|
||||
assert out["via"] == section.get("via") == "libc", (
|
||||
"the vantage is the agent's string, copied, never one this module invented")
|
||||
assert isinstance(out["resolved_ts"], int | float), (
|
||||
f"{route['id']}: a count obtained through a lookup must say when the lookup happened")
|
||||
assert route["metric"] is not None, "a counted route always has its gauge"
|
||||
stored = {sample.metric for sample in gateway.samples(payload, 0.0)}
|
||||
assert {route["metric"] for route in placed} <= stored, (
|
||||
"a resolved route's count must reach the store, or its chart stays empty forever")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("engine", ["gufo", "vllm"])
|
||||
|
||||
Reference in New Issue
Block a user