# 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