Initial commit — LLDP network mapper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# parser.py - Parse 'show lldp neighbors' output from FS switches
|
||||
import re
|
||||
|
||||
|
||||
def shorten_interface(iface):
|
||||
"""GigabitEthernet 1/9 -> Gi1/9, TenGigabitEthernet 1/1 -> Te1/1 etc."""
|
||||
replacements = [
|
||||
(r'GigabitEthernet\s*', 'Gi'),
|
||||
(r'TenGigabitEthernet\s*', 'Te'),
|
||||
(r'TwentyFiveGigE\s*', 'Twe'),
|
||||
(r'FortyGigabitEthernet\s*', 'Fo'),
|
||||
(r'HundredGigE\s*', 'Hu'),
|
||||
(r'FastEthernet\s*', 'Fa'),
|
||||
(r'Ethernet\s*', 'Eth'),
|
||||
(r'mgmt\s*', 'mgmt'),
|
||||
]
|
||||
for pattern, short in replacements:
|
||||
iface = re.sub(pattern, short, iface, flags=re.IGNORECASE)
|
||||
return iface.strip()
|
||||
|
||||
|
||||
def parse_lldp_neighbors(raw_output, local_chassis_id, local_hostname, local_mgmt_ip):
|
||||
"""
|
||||
Parse raw 'show lldp neighbors' output from an FS switch.
|
||||
|
||||
Returns:
|
||||
neighbors: list of dicts with parsed neighbor info
|
||||
local_info: dict with this switch's details (enriched from LLDP data if needed)
|
||||
"""
|
||||
neighbors = []
|
||||
|
||||
# Split output into per-neighbor blocks (blank line separated)
|
||||
# Each block starts with "Local Interface"
|
||||
blocks = re.split(r'\n\s*\n', raw_output.strip())
|
||||
|
||||
for block in blocks:
|
||||
if not block.strip():
|
||||
continue
|
||||
if 'Local Interface' not in block and 'Local Port' not in block:
|
||||
continue
|
||||
|
||||
neighbor = {}
|
||||
|
||||
def extract(pattern, text, default=''):
|
||||
m = re.search(pattern, text, re.IGNORECASE)
|
||||
return m.group(1).strip() if m else default
|
||||
|
||||
neighbor['local_port'] = shorten_interface(extract(r'Local Interface\s*:\s*(.+)', block))
|
||||
neighbor['chassis_id'] = extract(r'Chassis ID\s*:\s*(.+)', block)
|
||||
neighbor['port_id'] = extract(r'Port ID\s*:\s*(.+)', block)
|
||||
neighbor['port_desc'] = shorten_interface(extract(r'Port Description\s*:\s*(.+)', block))
|
||||
neighbor['system_name'] = extract(r'System Name\s*:\s*(.+)', block)
|
||||
neighbor['system_desc'] = extract(r'System Description\s*:\s*(.+)', block)
|
||||
# FS switches report Management Address as MAC (e.g. '64-9D-99-AA-50-B0 (Other)')
|
||||
# or as IP (e.g. '10.214.0.192'). Extract only valid IPv4.
|
||||
raw_mgmt = extract(r'Management Address\s*:\s*([\d\.A-Fa-f\-:]+)', block)
|
||||
ipv4_match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', raw_mgmt)
|
||||
neighbor['mgmt_ip'] = ipv4_match.group(1) if ipv4_match else ''
|
||||
neighbor['capabilities'] = extract(r'System Capabilities\s*:\s*(.+)', block)
|
||||
|
||||
# Only include bridge/switch neighbors (skip phones, APs listed as endpoints)
|
||||
if neighbor['chassis_id'] and neighbor['system_name']:
|
||||
# Use port_desc as remote port if available, fallback to port_id
|
||||
neighbor['remote_port'] = neighbor['port_desc'] if neighbor['port_desc'] else neighbor['port_id']
|
||||
neighbors.append(neighbor)
|
||||
|
||||
return neighbors
|
||||
|
||||
|
||||
def parse_hostname_from_prompt(prompt_line):
|
||||
"""Extract hostname from CLI prompt like 'ls-vhls-sw01#'"""
|
||||
m = re.match(r'^([A-Za-z0-9_\-]+)[>#]', prompt_line.strip())
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def parse_mgmt_ip_from_interfaces(raw_output):
|
||||
"""
|
||||
Parse 'show ip interface brief' to find management VLAN IP.
|
||||
Looks for Vlan interfaces with an IP assigned.
|
||||
Returns first Vlan IP found (typically the management VLAN).
|
||||
"""
|
||||
lines = raw_output.splitlines()
|
||||
for line in lines:
|
||||
# Match lines like: Vlan100 192.168.1.10 YES ...
|
||||
m = re.match(r'\s*(Vlan\S+)\s+([\d\.]+)\s+', line, re.IGNORECASE)
|
||||
if m and not m.group(2).startswith('0.0.0.0'):
|
||||
return m.group(2)
|
||||
return None
|
||||
Reference in New Issue
Block a user