This commit is contained in:
2026-03-23 17:35:41 +02:00
parent 26450a767b
commit 674f7c84f9

View File

@@ -2,7 +2,7 @@
""" """
NATS Registration Listener (fox100 + NetBox hostname/action_next lookup + timing + problem counter) NATS Registration Listener (fox100 + NetBox hostname/action_next lookup + timing + problem counter)
------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------
- One device GET (status + tags + custom_fields.action_next), no duplicate fetch - One device GET (custom_fields.action_next), no duplicate fetch
- Keeps: nb_problems counter, timings, iface_id diagnostics, same formatting - Keeps: nb_problems counter, timings, iface_id diagnostics, same formatting
- For fox100: - For fox100:
* If action_next is empty/absent -> print a simple stdout note and do nothing else * If action_next is empty/absent -> print a simple stdout note and do nothing else
@@ -275,16 +275,16 @@ async def nb_problem(log_status, msg: str):
def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
Optional[str], Optional[int], Optional[int], Optional[str], Optional[Set[str]], Optional[Any], Optional[Any], Optional[Any], Optional[Any], Optional[Any] Optional[str], Optional[int], Optional[int], Optional[str], Optional[Any], Optional[Any], Optional[Any], Optional[Any], Optional[Any]
]: ]:
""" """
Resolve MAC -> (hostname, iface_id, device_id, device_status_value, tag_slugs_set, action_next, action_last, action_next_timestamp, action_state, sot_ts) Resolve MAC -> (hostname, iface_id, device_id, action_next, action_last, action_next_timestamp, action_state, sot_ts)
- Logs problems for anomalies (mac not found, unassigned, wrong type, iface fetch fail). - Logs problems for anomalies (mac not found, unassigned, wrong type, iface fetch fail).
- If device detail fetch fails, returns host/id with status/tags/custom_fields as None. - If device detail fetch fails, returns host/id with custom_fields as None.
""" """
mac_norm = normalize_mac(mac) mac_norm = normalize_mac(mac)
if not mac_norm: if not mac_norm:
return None, None, None, None, None, None, None, None, None, None return None, None, None, None, None, None, None, None, None
# Step 0: short TTL cache (avoid repeated NetBox GETs for chatty devices) # Step 0: short TTL cache (avoid repeated NetBox GETs for chatty devices)
if NB_LOOKUP_CACHE_TTL > 0: if NB_LOOKUP_CACHE_TTL > 0:
@@ -293,13 +293,8 @@ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
if cached: if cached:
exp, val = cached exp, val = cached
if exp > nowm: if exp > nowm:
host, iface_id, dev_id, status_val, tag_slugs, action_next, action_last, action_next_timestamp, action_state, sot_ts = val host, iface_id, dev_id, action_next, action_last, action_next_timestamp, action_state, sot_ts = val
if tag_slugs is not None: return host, iface_id, dev_id, action_next, action_last, action_next_timestamp, action_state, sot_ts
try:
tag_slugs = set(tag_slugs)
except Exception:
pass
return host, iface_id, dev_id, status_val, tag_slugs, action_next, action_last, action_next_timestamp, action_state, sot_ts
NB_LOOKUP_CACHE.pop(mac_norm, None) NB_LOOKUP_CACHE.pop(mac_norm, None)
base = NB_URL.rstrip("/") base = NB_URL.rstrip("/")
@@ -319,12 +314,12 @@ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
data, code = http_get_json(f"{base}/api/dcim/mac-addresses/", params={"mac_address": mac_norm, "limit": "2"}, headers=h) data, code = http_get_json(f"{base}/api/dcim/mac-addresses/", params={"mac_address": mac_norm, "limit": "2"}, headers=h)
if code != 200 or not data: if code != 200 or not data:
asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac query http={code} mac={mac_norm}")) asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac query http={code} mac={mac_norm}"))
return None, None, None, None, None, None, None, None, None, None return None, None, None, None, None, None, None, None, None
results = (data or {}).get("results") or [] results = (data or {}).get("results") or []
if not results: if not results:
asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac not found mac={mac_norm}")) asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac not found mac={mac_norm}"))
return None, None, None, None, None, None, None, None, None, None return None, None, None, None, None, None, None, None, None
rec = results[0] rec = results[0]
aot = (rec.get("assigned_object_type") or "").strip() aot = (rec.get("assigned_object_type") or "").strip()
@@ -335,10 +330,10 @@ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
if not aot or aoid is None: if not aot or aoid is None:
asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac unassigned mac={mac_norm}")) asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac unassigned mac={mac_norm}"))
return None, None, None, None, None, None, None, None, None, None return None, None, None, None, None, None, None, None, None
if aot != "dcim.interface": if aot != "dcim.interface":
asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac assigned to {aot} mac={mac_norm} iface_id={aoid}")) asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: mac assigned to {aot} mac={mac_norm} iface_id={aoid}"))
return None, aoid, None, None, None, None, None, None, None, None return None, aoid, None, None, None, None, None, None, None
# Step 2: Interface -> Device (shallow) # Step 2: Interface -> Device (shallow)
iface, code2 = http_get_json(f"{base}/api/dcim/interfaces/{aoid}/", params={"fields": "device"}, headers=h) iface, code2 = http_get_json(f"{base}/api/dcim/interfaces/{aoid}/", params={"fields": "device"}, headers=h)
@@ -346,30 +341,22 @@ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
iface, code2 = http_get_json(f"{base}/api/dcim/interfaces/{aoid}/", headers=h) iface, code2 = http_get_json(f"{base}/api/dcim/interfaces/{aoid}/", headers=h)
if code2 != 200 or not iface: if code2 != 200 or not iface:
asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: iface fetch http={code2} iface_id={aoid}")) asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: iface fetch http={code2} iface_id={aoid}"))
return None, aoid, None, None, None, None, None, None, None, None return None, aoid, None, None, None, None, None, None, None
dev = iface.get("device") or {} dev = iface.get("device") or {}
host = dev.get("name") or dev.get("display") host = dev.get("name") or dev.get("display")
dev_id = dev.get("id") dev_id = dev.get("id")
if not host or dev_id is None: if not host or dev_id is None:
asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: iface has no device iface_id={aoid}")) asyncio.create_task(nb_problem(log_status, f"[{ts()}] nb: iface has no device iface_id={aoid}"))
return None, aoid, None, None, None, None, None, None, None, None return None, aoid, None, None, None, None, None, None, None
# Step 3: Device detail (single fetch for status, tags, custom_fields.*) # Step 3: Device detail (single fetch for custom_fields.*)
device, code3 = http_get_json(f"{base}/api/dcim/devices/{dev_id}/", params={"fields": "status,tags,custom_fields"}, headers=h) device, code3 = http_get_json(f"{base}/api/dcim/devices/{dev_id}/", params={"fields": "custom_fields"}, headers=h)
if code3 == 400: if code3 == 400:
device, code3 = http_get_json(f"{base}/api/dcim/devices/{dev_id}/", headers=h) device, code3 = http_get_json(f"{base}/api/dcim/devices/{dev_id}/", headers=h)
if code3 != 200 or not device: if code3 != 200 or not device:
# treat as "no extra info" # treat as "no extra info"
return host, aoid, dev_id, None, None, None, None, None, None, None return host, aoid, dev_id, None, None, None, None, None, None
status_val = ((device.get("status") or {}).get("value")) or None
tags = device.get("tags") or []
tag_slugs = set()
for t in tags:
slug = t.get("slug")
if isinstance(slug, str):
tag_slugs.add(slug)
cf = device.get("custom_fields") or {} cf = device.get("custom_fields") or {}
action_next = cf.get("action_next") action_next = cf.get("action_next")
@@ -387,8 +374,6 @@ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
host, host,
aoid, aoid,
dev_id, dev_id,
(status_val if isinstance(status_val, str) else None),
(tuple(tag_slugs) if tag_slugs is not None else None),
action_next, action_next,
action_last, action_last,
action_next_timestamp, action_next_timestamp,
@@ -399,7 +384,7 @@ def nb_lookup_device_by_mac(mac: str, log_status) -> Tuple[
except Exception: except Exception:
pass pass
return host, aoid, dev_id, (status_val if isinstance(status_val, str) else None), tag_slugs, action_next, action_last, action_next_timestamp, action_state, sot_ts return host, aoid, dev_id, action_next, action_last, action_next_timestamp, action_state, sot_ts
# ========================= # =========================
@@ -538,7 +523,7 @@ async def main():
if product == "fox100": if product == "fox100":
nb_start = time.perf_counter() nb_start = time.perf_counter()
try: try:
host, iface_id, dev_id, status_val, tag_slugs, action_next, action_last, action_next_timestamp, action_state, sot_ts = nb_lookup_device_by_mac( host, iface_id, dev_id, action_next, action_last, action_next_timestamp, action_state, sot_ts = nb_lookup_device_by_mac(
mac=mac, log_status=log_status mac=mac, log_status=log_status
) )
event["iface_id"] = iface_id event["iface_id"] = iface_id