Add SEC/AMI scans, scan abort, connection test, runtime credentials, serial tracking
Also fixes several LLDP parsing edge cases (blank-field regex bleed, endpoint-device filtering, HP ProCurve chassis ID format, Aruba port naming), corrects link dedup to prefer self-reported port names over neighbor guesses, and repoints NetBox sync at the new instance where devices are hand-curated rather than auto-created. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bp1HutYUh9fmTFgwB4qBLz
This commit is contained in:
+45
-59
@@ -16,13 +16,8 @@ logger = logging.getLogger(__name__)
|
||||
# --- Configuration ---
|
||||
DB_PATH = "/opt/lldp-mapper/data/network.db"
|
||||
|
||||
NETBOX_URL = "http://192.168.16.130:8001"
|
||||
NETBOX_TOKEN = "nbt_T6aq9XpwNFQG.HtZziXuSATgabbeagWKk3vEhc2Ask1EMV210PWMM"
|
||||
|
||||
SITE_NAME = "Field Sites"
|
||||
DEVICE_ROLE = "Access Switch"
|
||||
MANUFACTURER = "FS"
|
||||
DEVICE_TYPE = "FS Switch"
|
||||
NETBOX_URL = "http://192.168.16.137:8000"
|
||||
NETBOX_TOKEN = "nbt_LYkI6iSsflIU.9N4ziQxpbrV1AGPmpk0fwhfhJOSrSr0nGXEF4Ze2"
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
@@ -72,54 +67,21 @@ def load_db():
|
||||
|
||||
|
||||
# --- NetBox Sync ---
|
||||
#
|
||||
# This NetBox instance is hand-curated (real sites, device types, roles) —
|
||||
# this script never creates devices. It only looks up devices that already
|
||||
# exist by hostname and syncs their interfaces/cables/IP. A switch lldp-mapper
|
||||
# knows about but that has no matching device in NetBox is skipped with a
|
||||
# warning so it can be added properly (correct site/manufacturer) by hand.
|
||||
|
||||
def ensure_site():
|
||||
return nb_get_or_create(
|
||||
"dcim/sites/",
|
||||
{"name": SITE_NAME},
|
||||
{"name": SITE_NAME, "slug": SITE_NAME.lower().replace(" ", "-")}
|
||||
)
|
||||
|
||||
|
||||
def ensure_manufacturer():
|
||||
return nb_get_or_create(
|
||||
"dcim/manufacturers/",
|
||||
{"name": MANUFACTURER},
|
||||
{"name": MANUFACTURER, "slug": MANUFACTURER.lower()}
|
||||
)
|
||||
|
||||
|
||||
def ensure_device_type(manufacturer_id):
|
||||
return nb_get_or_create(
|
||||
"dcim/device-types/",
|
||||
{"slug": "fs-switch"},
|
||||
{"model": DEVICE_TYPE, "slug": "fs-switch", "manufacturer": manufacturer_id}
|
||||
)
|
||||
|
||||
|
||||
def ensure_device_role():
|
||||
return nb_get_or_create(
|
||||
"dcim/device-roles/",
|
||||
{"name": DEVICE_ROLE},
|
||||
{"name": DEVICE_ROLE, "slug": "access-switch", "color": "2196f3"}
|
||||
)
|
||||
|
||||
|
||||
def ensure_device(switch, site_id, device_type_id, role_id):
|
||||
def ensure_device(switch):
|
||||
hostname = switch["hostname"] or switch["mgmt_ip"]
|
||||
results = nb_get("dcim/devices/", params={"name": hostname}).get("results", [])
|
||||
if results:
|
||||
logger.info(f" Device exists: {hostname}")
|
||||
return results[0]
|
||||
logger.info(f" Creating device: {hostname}")
|
||||
return nb_post("dcim/devices/", {
|
||||
"name": hostname,
|
||||
"site": site_id,
|
||||
"device_type": device_type_id,
|
||||
"role": role_id,
|
||||
"status": "active",
|
||||
"comments": f"Chassis ID: {switch['chassis_id']}\nDiscovered by lldp-mapper",
|
||||
})
|
||||
logger.warning(f" Skipping {hostname} — no matching device in NetBox (add it manually first)")
|
||||
return None
|
||||
|
||||
|
||||
def ensure_interface(device_id, port_name):
|
||||
@@ -136,6 +98,35 @@ def ensure_interface(device_id, port_name):
|
||||
})
|
||||
|
||||
|
||||
def ensure_serial(switch, device):
|
||||
serial = switch.get("serial") or ""
|
||||
if not serial or device.get("serial") == serial:
|
||||
return
|
||||
logger.info(f" Updating serial: {serial}")
|
||||
requests.patch(
|
||||
f"{NETBOX_URL}/api/dcim/devices/{device['id']}/",
|
||||
headers=NB_HEADERS,
|
||||
json={"serial": serial}
|
||||
)
|
||||
|
||||
|
||||
def ensure_mac_comment(switch, device):
|
||||
mac = switch.get("chassis_id") or ""
|
||||
if not mac or "-" not in mac: # only real MACs, not IP-based chassis IDs
|
||||
return
|
||||
line = f"Chassis MAC: {mac}"
|
||||
comments = device.get("comments") or ""
|
||||
if line in comments:
|
||||
return
|
||||
logger.info(f" Recording chassis MAC in comments: {mac}")
|
||||
new_comments = f"{comments}\n{line}".strip() if comments else line
|
||||
requests.patch(
|
||||
f"{NETBOX_URL}/api/dcim/devices/{device['id']}/",
|
||||
headers=NB_HEADERS,
|
||||
json={"comments": new_comments}
|
||||
)
|
||||
|
||||
|
||||
def ensure_ip(switch, device_id):
|
||||
if not switch.get("mgmt_ip"):
|
||||
return
|
||||
@@ -187,29 +178,24 @@ def ensure_cable(device_map, link):
|
||||
"a_terminations": [{"object_type": "dcim.interface", "object_id": iface_a["id"]}],
|
||||
"b_terminations": [{"object_type": "dcim.interface", "object_id": iface_b["id"]}],
|
||||
"status": "connected",
|
||||
"label": f"{hn_a}→{hn_b}",
|
||||
"type": "smf-os2",
|
||||
})
|
||||
|
||||
|
||||
def sync_netbox(switches, links):
|
||||
logger.info("=== Syncing to NetBox ===")
|
||||
|
||||
site = ensure_site()
|
||||
manufacturer = ensure_manufacturer()
|
||||
device_type = ensure_device_type(manufacturer["id"])
|
||||
role = ensure_device_role()
|
||||
|
||||
site_id = site["id"]
|
||||
device_type_id = device_type["id"]
|
||||
role_id = role["id"]
|
||||
|
||||
device_map = {}
|
||||
|
||||
for sw in switches:
|
||||
hostname = sw["hostname"] or sw["mgmt_ip"]
|
||||
logger.info(f"Processing device: {hostname}")
|
||||
device = ensure_device(sw, site_id, device_type_id, role_id)
|
||||
device = ensure_device(sw)
|
||||
if device:
|
||||
device_map[hostname] = device
|
||||
ensure_serial(sw, device)
|
||||
ensure_mac_comment(sw, device)
|
||||
ensure_ip(sw, device["id"])
|
||||
|
||||
logger.info(f"Devices synced: {len(device_map)}")
|
||||
|
||||
Reference in New Issue
Block a user