feat(syslog): a device through Docker's address is placed by its name, or a device address set on it (#6096)
The SNMP poll asks each device's sysName in the same GET and keeps it (snmp_003_sys_name). A syslog source told apart by name finds its own address from the SNMP device (reported or given name) or host of that name, so its lines reach the SNMP device's and host's pages again; the edit page gains Device address, which always wins (syslog_004). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,8 +48,9 @@ def test_a_poll_records_why_a_device_is_silent_and_what_another_answered(app):
|
||||
return PollResult(error="No reply from 10.9.9.9:161 within 5 s.")
|
||||
keys = dict((k, o) for o, k in oids)
|
||||
assert {"uptime", "if1_in", "if1_status"} <= set(keys) and creds.community == "public"
|
||||
assert keys["_sys_name"] == "1.3.6.1.2.1.1.5.0" # the device's own name rides along
|
||||
return PollResult(readings={"uptime": 8_640_000.0, "if1_status": 1.0, "if1_in": next(counts)},
|
||||
missing=["if1_out"])
|
||||
texts={"_sys_name": "SW-ONE"}, missing=["if1_out"])
|
||||
|
||||
async def go():
|
||||
from steward.core.crypto import encrypt_secret
|
||||
@@ -79,6 +80,8 @@ def test_a_poll_records_why_a_device_is_silent_and_what_another_answered(app):
|
||||
|
||||
first, second, rows = asyncio.run(go())
|
||||
assert rows[up].last_error is None and rows[up].last_ok_at is not None and rows[up].missing == ["if1_out"]
|
||||
assert rows[up].sys_name == "SW-ONE" and rows[down].sys_name == ""
|
||||
assert "_sys_name" not in (rows[up].texts or {})
|
||||
assert rows[down].last_error == "No reply from 10.9.9.9:161 within 5 s." and rows[down].last_ok_at is None
|
||||
assert rows[down].last_polled_at is not None
|
||||
assert first == {"uptime": 8_640_000.0, "if1_status": 1.0} # a counter needs two samples
|
||||
|
||||
@@ -6,7 +6,7 @@ from types import SimpleNamespace as NS
|
||||
import pytest
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
from plugins.syslog.service import DEFAULT_RANGE, line_filter, severity_level, sources_for
|
||||
from plugins.syslog.service import DEFAULT_RANGE, device_address, line_filter, severity_level, sources_for
|
||||
|
||||
|
||||
def test_a_severity_reads_by_name_alias_or_number():
|
||||
@@ -24,8 +24,8 @@ def test_a_page_filter_drops_what_does_not_read():
|
||||
assert line_filter(MultiDict({"severity": "4", "range": "7d"})).args() == {"severity": "4", "range": "7d"}
|
||||
|
||||
|
||||
def _src(sid, state="accepted", host_id=None, address="10.0.0.5"):
|
||||
return NS(id=sid, state=state, host_id=host_id, address=address)
|
||||
def _src(sid, state="accepted", host_id=None, address="10.0.0.5", sender="", device_address=""):
|
||||
return NS(id=sid, state=state, host_id=host_id, address=address, sender=sender, device_address=device_address)
|
||||
|
||||
|
||||
def test_a_page_shows_sources_bound_to_its_host_or_unbound_at_its_address():
|
||||
@@ -36,3 +36,21 @@ def test_a_page_shows_sources_bound_to_its_host_or_unbound_at_its_address():
|
||||
# An SNMP device bound to no host matches by its address alone.
|
||||
assert [s.id for s in sources_for(sources, None, "10.0.0.5")] == ["by-address"]
|
||||
assert sources_for(sources, None, None) == []
|
||||
|
||||
|
||||
def test_a_device_through_dockers_address_is_placed_by_its_name_or_the_address_set():
|
||||
"""#6096: through Swarm's ingress every line comes from Docker's address;
|
||||
the device's own is the one set on it, else that of the SNMP device or
|
||||
host its name is."""
|
||||
named = {"vdnt-ds923": "192.168.0.7", "vdnt-as6508t": "192.168.0.5"}
|
||||
syn = _src("syn", address="10.0.0.19", sender="VDNT-DS923")
|
||||
asus = _src("asus", address="10.0.0.19", sender="VDNT-AS6508T", device_address="192.168.0.50")
|
||||
lone = _src("lone", address="10.0.0.19", sender="router")
|
||||
assert device_address(syn, named) == "192.168.0.7"
|
||||
assert device_address(asus, named) == "192.168.0.50" # set by hand wins
|
||||
assert device_address(lone, named) == "10.0.0.19"
|
||||
assert device_address(_src("plain", sender=""), named) == "10.0.0.5"
|
||||
sources = [syn, asus, lone]
|
||||
assert [s.id for s in sources_for(sources, None, "192.168.0.7", named)] == ["syn"]
|
||||
assert [s.id for s in sources_for(sources, None, "192.168.0.50", named)] == ["asus"]
|
||||
assert sources_for(sources, None, "192.168.0.7") == [] # no names: Docker's address only
|
||||
|
||||
@@ -20,7 +20,7 @@ def _source(name, state, level=6, line="nfsd: server not responding", host=None)
|
||||
"hostname_seen": "synnas", "host": host, "first_seen_at": now - timedelta(days=1),
|
||||
"last_seen_at": now - timedelta(minutes=3), "lines_total": 1204,
|
||||
"severity": "err" if level == 3 else "info", "level": level, "last_line": line,
|
||||
"suggested_host": "h1", "rate": 12 if state == "accepted" else None, "sender": ""}
|
||||
"suggested_host": "h1", "rate": 12 if state == "accepted" else None, "sender": "", "device_address": ""}
|
||||
|
||||
|
||||
async def _render(app, role, **ctx):
|
||||
@@ -89,7 +89,11 @@ async def test_a_sender_on_dockers_own_network_is_called_out(app):
|
||||
async def test_devices_through_dockers_address_read_by_their_own_names(app):
|
||||
via = {**_source("SynNAS", "offered"), "address": "10.0.0.2", "docker_address": True, "sender": "SynNAS"}
|
||||
page = await _render(app, "admin", offered=[via, {**via, "id": "id-2", "sender": "AsusNAS"}])
|
||||
assert "through Docker's ingress, 10.0.0.2" in page and '<span class="mono">AsusNAS</span>' in page
|
||||
assert "through Docker's ingress" in page and '<span class="mono">AsusNAS</span>' in page
|
||||
assert '<span class="mono">192.168.0.7</span>, through Docker' not in page
|
||||
placed = await _render(app, "admin", offered=[{**via, "device_address": "192.168.0.7"}])
|
||||
assert '<span class="mono">192.168.0.7</span>, through Docker's ingress' not in placed
|
||||
assert '<span class="mono">192.168.0.7</span>, through Docker\'s ingress' in placed
|
||||
assert "can't be told apart" not in page and "not the device's" not in page
|
||||
alone = await _render(app, "admin", offered=[{**via, "sender": ""}])
|
||||
assert "carry no device name" in alone
|
||||
|
||||
Reference in New Issue
Block a user