Files
dcstephenson 7e08638f50 Preserve links from other depts during filtered scans
clear_links() now only runs on full scans (dept=None). Dept-filtered
scans upsert without wiping, so GW links survive a Scan ELEC run
and vice versa.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 18:07:43 +00:00

144 lines
4.8 KiB
Python

# scanner.py - Orchestrates the full scan pipeline
import logging
from nocodb_client import get_switches
from ssh_client import scan_all_switches
from db import (
upsert_switch, upsert_link, clear_links,
log_scan_start, log_scan_finish, merge_duplicate_switches
)
from parser import parse_neighbor_description
from exports import run_all_exports
logger = logging.getLogger(__name__)
# Global scan state (shared with Flask via import)
scan_state = {
"running": False,
"done": 0,
"total": 0,
"current_ip": None,
"current_hostname": None,
"ok": 0,
"fail": 0,
"errors": [],
"log_lines": [],
"last_scan": None,
"dept_filter": None, # None = all, "ELEC" or "GW" = dept-only
}
def run_scan(dept: str = None, workers: int = 5, login_delay: int = 3):
"""
Full scan: fetch switch IPs from NocoDB → SSH all → parse → store → export.
dept: None = all active switches, "ELEC" or "GW" = dept-filtered.
"""
global scan_state
if scan_state["running"]:
logger.warning("Scan already running, skipping.")
return
# Fetch switch list from NocoDB (or fallback)
switches = get_switches(dept=dept)
if not switches:
logger.error("NocoDB returned no switches — aborting scan.")
scan_state["running"] = False
return
logger.error("No switches returned from NocoDB, aborting scan.")
return
scan_state.update({
"running": True,
"done": 0,
"total": len(switches),
"current_ip": None,
"current_hostname": None,
"ok": 0,
"fail": 0,
"errors": [],
"log_lines": [],
"dept_filter": dept,
})
scan_id = log_scan_start()
if dept is None:
clear_links() # Full scan only — dept scans upsert without wiping other depts
def on_progress(done, total, ip, result):
from datetime import datetime
ts = datetime.now().strftime("%H:%M:%S")
scan_state["done"] = done
scan_state["total"] = total
scan_state["current_ip"] = ip
if result["success"]:
scan_state["ok"] += 1
scan_state["current_hostname"] = result.get("hostname", ip)
vendor = result.get("vendor", "")
n = len(result.get("neighbors", []))
scan_state["log_lines"].append({
"ts": ts, "ok": True,
"text": f"{result['hostname']} ({ip}) [{vendor}] — {n} neighbor{'s' if n != 1 else ''}",
})
upsert_switch(
chassis_id=result["chassis_id"],
hostname=result["hostname"],
mgmt_ip=result["mgmt_ip"],
description=result.get("description", ""),
firmware=result.get("firmware", ""),
vendor=result.get("vendor", ""),
)
for neighbor in result["neighbors"]:
if neighbor.get("chassis_id") and neighbor.get("system_name"):
nbr_model, nbr_firmware = parse_neighbor_description(
neighbor.get("system_desc", "")
)
upsert_switch(
chassis_id=neighbor["chassis_id"],
hostname=neighbor["system_name"],
mgmt_ip=neighbor.get("mgmt_ip", ""),
description=nbr_model, # empty for FS neighbors; direct scan fills it in
firmware=nbr_firmware,
)
upsert_link(
chassis_a=result["chassis_id"],
port_a=neighbor["local_port"],
chassis_b=neighbor["chassis_id"],
port_b=neighbor["remote_port"],
)
else:
scan_state["fail"] += 1
error = result.get("error", "Unknown error")
scan_state["errors"].append({"ip": ip, "error": error})
error_short = error.splitlines()[0][:120]
scan_state["log_lines"].append({
"ts": ts, "ok": False,
"text": f"{ip}{error_short}",
})
scan_all_switches(switches, progress_callback=on_progress, max_workers=workers, login_delay=login_delay)
try:
merge_duplicate_switches()
except Exception as e:
logger.error(f"Merge error: {e}")
try:
run_all_exports()
except Exception as e:
logger.error(f"Export error: {e}")
log_scan_finish(scan_id, scan_state["ok"], scan_state["fail"])
from datetime import datetime
scan_state["last_scan"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
scan_state["running"] = False
scan_state["current_ip"] = None
scan_state["current_hostname"] = None
scan_state["dept_filter"] = None
logger.info(f"Scan complete. OK: {scan_state['ok']}, Failed: {scan_state['fail']}")