The operator, reviewing SNMP on #5214: "this system looks incomplete like there's something missing from it". The one device shown was plugin.yaml's example (core-switch 192.168.1.1, community public), polled every minute; devices could only be defined by editing YAML in the image; a failed poll was logged at DEBUG and stored nowhere, so the page said "waiting for the next poll" forever; v3 ran as v2c; counters showed as lifetime totals. - Devices are rows (snmp_devices, migration snmp_001): address, port, v1/v2c community or a v3 user with auth/priv protocols and keys (secrets encrypted), presets plus custom readings, an optional host binding, and the newest poll's outcome. The YAML device list and its example are gone. - The SNMP page adds, edits and deletes them (admin, audited, with get_audit_entities so the audit log names and links a device). Readings come from presets (system uptime, interface traffic by number, UPS-MIB, Linux net-snmp) and custom "OID name [unit]" lines. - One GET per device with every OID, devices polled at once. The outcome is written to the row in words ("No reply from … within 5 s. Check the address and port, that SNMP is on, and the community"), with readings it lacks. v1's whole-request error drops that OID and asks again. SNMPv3 signs in with USM (MD5/SHA-1/SHA-2, DES/3DES/AES). - Test asks the unsaved form's device for its name, description, uptime and interfaces by number. It is an admin's explicit action; every page reads only stored rows (System 178's ruling). - Each reading has a unit; a counter is stored as a per-second rate (bytes as bit/s), a reset or wrap skipped. The page, widget and host panel say values in units and why a device is silent; the device page draws a chart per reading on a dated axis. Tests: readings and presets, the form and its secrets, v3 checks, rates, the poller against a stand-in pysnmp (one GET, v1 retry, timeout wording, USM), the pages, and an integration test of the poll writing outcomes and rates on Postgres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38 lines
2.1 KiB
Python
38 lines
2.1 KiB
Python
"""The dashboard SNMP widget (#5255, milestone 491): a verdict, devices not
|
|
answering first with since when, status in words, two readings in units."""
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from tests._templates import render_plugin_template
|
|
|
|
|
|
def _dev(name, reach, ok_minutes=None, key=()):
|
|
now = datetime.now(timezone.utc)
|
|
return {"id": f"id-{name}", "name": name, "reach": reach, "says": "No reply.",
|
|
"polled_at": now - timedelta(seconds=30),
|
|
"ok_at": now - timedelta(minutes=ok_minutes) if ok_minutes is not None else None,
|
|
"key": [{"name": n, "text": t} for n, t in key]}
|
|
|
|
|
|
async def test_a_silent_device_leads_with_since_when(app):
|
|
devices = [_dev("ups", "down", 14, [("Charge", "100%")]), _dev("printer", "pending"),
|
|
_dev("switch", "up", 1, [("Uptime", "12d 4h")])]
|
|
async with app.test_request_context("/plugins/snmp/widget"):
|
|
page = await render_plugin_template(app, "snmp", "snmp/widget.html", devices=devices, more=0,
|
|
counts={"down": 1, "stale": 0, "pending": 1, "up": 1}, total=3)
|
|
page = " ".join(page.split())
|
|
assert "ups isn't answering." in page and "no reply since" in page and ">14m ago<" in page
|
|
assert page.count("<th>Device</th>") == 1
|
|
assert page.index('title="ups"') < page.index('title="printer"') < page.index('title="switch"')
|
|
assert "not polled yet" in page and "Uptime</span> 12d 4h" in page
|
|
|
|
|
|
async def test_all_answering_says_so(app):
|
|
async with app.test_request_context("/plugins/snmp/widget"):
|
|
page = await render_plugin_template(app, "snmp", "snmp/widget.html",
|
|
devices=[_dev("switch", "up", 1)], more=0,
|
|
counts={"down": 0, "stale": 0, "pending": 0, "up": 1}, total=1)
|
|
empty = await render_plugin_template(app, "snmp", "snmp/widget.html", devices=[], more=0,
|
|
counts={}, total=0)
|
|
assert "All 1 device answering." in page and "—" in page
|
|
assert "No SNMP devices yet." in empty
|