diff --git a/app.py b/app.py index 3de273d..d208004 100644 --- a/app.py +++ b/app.py @@ -436,6 +436,7 @@ def api_diagnose_start(): if pd.get('port_idx') == port_idx: port_data = dict(pd) port_data['name'] = pname + port_data['switch_name'] = switch_name break if not port_data: return jsonify({'error': f'Port {port_idx} not found on switch "{switch_name}"'}), 404 diff --git a/diagnose.py b/diagnose.py index e7ec513..b6e4300 100644 --- a/diagnose.py +++ b/diagnose.py @@ -530,15 +530,16 @@ class DiagnosticsRunner: add(warnings, 'KERNEL_EVENTS', f'{len(err_events)} recent kernel error event(s) for this interface in dmesg') - # LLDP validation + # LLDP validation: the neighbour the *server's* lldpd sees should be the switch + # that this port belongs to. (The switch port's own LLDP entry describes the server, + # so it must not be compared with the server's view of the switch.) if lldp.get('available'): - sw_lldp = switch_port_data.get('lldp') or {} - sw_system = (sw_lldp.get('system_name') or '').lower() + expected_switch = (switch_port_data.get('switch_name') or '').lower() srv_neighbor = (lldp.get('neighbor_system') or '').lower() - if sw_system and srv_neighbor and sw_system not in srv_neighbor and srv_neighbor not in sw_system: + if expected_switch and srv_neighbor and expected_switch not in srv_neighbor and srv_neighbor not in expected_switch: add(warnings, 'LLDP_MISMATCH', - f'LLDP mismatch: switch sees "{sw_lldp.get("system_name")}" but ' - f'server lldpctl sees "{lldp.get("neighbor_system")}" — cross-cabled port?') + f'LLDP mismatch: this port is on "{switch_port_data.get("switch_name")}" but the server\'s ' + f'lldpctl sees "{lldp.get("neighbor_system")}" — cross-cabled port?') else: add(info, 'LLDP_MISSING', 'lldpd not running on server — install lldpd for full path validation') diff --git a/tests/test_diagnose.py b/tests/test_diagnose.py index e70425d..0b924f2 100644 --- a/tests/test_diagnose.py +++ b/tests/test_diagnose.py @@ -473,12 +473,27 @@ class TestAnalyze: assert 'LLDP_MISSING' in codes def test_lldp_mismatch_is_warning(self): + # The server's lldpd sees a different switch than the one this port belongs to. sections = self._sections(lldpctl={'available': True, 'neighbor_system': 'wrong-switch'}) - switch_data = {'speed_mbps': 1000, 'lldp': {'system_name': 'core-sw-01'}} + switch_data = {'speed_mbps': 1000, 'switch_name': 'core-sw-01', 'lldp': {'system_name': 'server-a'}} result = DiagnosticsRunner.analyze(sections, switch_data) codes = [w['code'] for w in result['warnings']] assert 'LLDP_MISMATCH' in codes + def test_lldp_match_is_not_a_warning(self): + # Correct cabling: the switch sees the server, the server sees the switch. + sections = self._sections(lldpctl={'available': True, 'neighbor_system': 'core-sw-01'}) + switch_data = {'speed_mbps': 1000, 'switch_name': 'core-sw-01', 'lldp': {'system_name': 'server-a'}} + result = DiagnosticsRunner.analyze(sections, switch_data) + codes = [w['code'] for w in result['warnings']] + assert 'LLDP_MISMATCH' not in codes + + def test_lldp_without_switch_name_skips_check(self): + sections = self._sections(lldpctl={'available': True, 'neighbor_system': 'anything'}) + result = DiagnosticsRunner.analyze(sections, {'speed_mbps': 1000, 'lldp': {'system_name': 'server-a'}}) + codes = [w['code'] for w in result['warnings']] + assert 'LLDP_MISMATCH' not in codes + def test_healthy_link_no_issues(self): result = DiagnosticsRunner.analyze(self._sections(), {}) assert result['issues'] == []