78 lines
3.7 KiB
Python
78 lines
3.7 KiB
Python
"""Each router's 4xx and 5xx responses by code (#5711): counted from
|
||
traefik_service_requests_total{code} between scrapes, and shown on the
|
||
problem card so it can say whether it's scanners, a login wall or the app."""
|
||
|
||
from plugins.traefik.routes import router_state
|
||
from plugins.traefik.scraper import compute_router_metrics, compute_status_codes
|
||
from steward.core.settings import to_thresholds_cfg
|
||
from tests._templates import render_plugin_template
|
||
from tests.plugins.traefik.test_traefik_page import _latest
|
||
|
||
REQ = "traefik_service_requests_total"
|
||
|
||
|
||
def _req(*series):
|
||
"""{REQ: {labels: value}} from (service, code, method, value) tuples."""
|
||
return {REQ: {frozenset({"service": s, "code": c, "method": m}.items()): v for s, c, m, v in series}}
|
||
|
||
|
||
def test_the_codes_since_the_last_scrape_are_counted_per_router():
|
||
before = _req(("app@docker", "200", "GET", 1000), ("app@docker", "404", "GET", 100),
|
||
("app@docker", "404", "POST", 10), ("api@docker", "401", "GET", 5))
|
||
after = _req(("app@docker", "200", "GET", 1500), ("app@docker", "404", "GET", 160),
|
||
("app@docker", "404", "POST", 12), ("api@docker", "401", "GET", 5),
|
||
("app@docker", "502", "GET", 3)) # a series the last scrape didn't have
|
||
got = compute_status_codes(after, before)
|
||
# 2xx is left out; 404 sums over methods; a new 502 series counts whole; api's 401 didn't move.
|
||
assert got == {"app@docker": {"404": 62, "502": 3}}
|
||
|
||
|
||
def test_the_first_scrape_counts_nothing_and_a_reset_counts_nothing():
|
||
series = _req(("app@docker", "500", "GET", 40))
|
||
assert compute_status_codes(series, None) == {}
|
||
assert compute_status_codes(_req(("app@docker", "500", "GET", 2)), series) == {} # Traefik restarted
|
||
|
||
|
||
def test_a_router_first_502s_count_in_its_5xx_share():
|
||
"""A series new since the last scrape started from 0: before #5711 its
|
||
whole first burst was dropped, so a router's first 502s read as 0%."""
|
||
before = _req(("app@docker", "200", "GET", 100))
|
||
after = _req(("app@docker", "200", "GET", 190), ("app@docker", "502", "GET", 10))
|
||
got = compute_router_metrics(after, before, 60.0)["app@docker"]
|
||
assert got["error_rate_5xx_pct"] == 10.0 and abs(got["request_rate"] - 100 / 60) < 1e-9
|
||
|
||
|
||
TH = to_thresholds_cfg({})
|
||
|
||
|
||
def _problem(**codes_ctx):
|
||
latest = _latest(e4=6.0)
|
||
state, why = router_state(latest, TH)
|
||
return {"name": "app", "latest": latest, "state": state, "why": why, **codes_ctx,
|
||
**{f"sparkline_{k}": "" for k in ("req", "bytes", "p95", "4xx", "5xx")}}
|
||
|
||
|
||
async def _card(app, router, codes_known=True):
|
||
async with app.test_request_context("/plugins/traefik/rows"):
|
||
page = await render_plugin_template(
|
||
app, "traefik", "traefik/rows.html", problems=[router], healthy=[], gone=[], global_stat=None,
|
||
certs=[], old_certs=[], range_key="24h", current_range="24h", error_4xx=1.0, error_5xx=0.1,
|
||
codes_known=codes_known)
|
||
page = " ".join(page.split())
|
||
return page[page.index(">app</h3>"):]
|
||
|
||
|
||
async def test_the_card_lists_the_codes_most_first_before_its_figures(app):
|
||
codes = [("404", 1812), ("502", 40), ("401", 9), ("403", 3), ("429", 2), ("499", 1), ("500", 1)]
|
||
card = await _card(app, _problem(codes=codes))
|
||
assert card.index('Last 15 min: <span class="tone-warn">404</span> ×1,812, <span class="tone-crit">502</span> ×40') \
|
||
< card.index("<table")
|
||
assert "429</span> ×2, 2 more codes" in card and "499" not in card
|
||
|
||
|
||
async def test_a_card_without_codes_says_whether_they_were_recorded(app):
|
||
none = await _card(app, _problem(codes=[]), codes_known=True)
|
||
assert "No 4xx or 5xx responses in the last 15 min." in none
|
||
early = await _card(app, _problem(codes=[]), codes_known=False)
|
||
assert "Status codes not recorded yet" in early
|