Skip to content

Commit 54b74a2

Browse files
authored
[LLDP] Fix lldpshow script to enable display multiple MAC addresses on the same remote physical interface (sonic-net#1657)
Scenario: 1- remote interface has 2 MACs on the same physical interface. 2- "show lldp table" command displays one entry for only one MAC address Root cause: "show lldp table" command uses lldpshow script to get, parse and display data from the lldp open source package (lldpctl script). lldpctl script returns a proper info about the 2 MACs but the issue is with the lldpshow script parser where it built a dictionary which its key is the local physical interface. Therefore when having 2 MACs, lldpctl will return 2 entries but the lldpshow parser will overwrite the first enrty. Fix: Change the key to be a string of "interface#MAC". This will enable having 2 entries for 2 different MAC addresses. In addition: - update display_sum()-->get_summary_output() to return a string instead of printing it directly. this to allow checking the returned value inside the new unit test. - add a new unit test for this scenario. Signed-off-by: Basim Shalata <[email protected]>
1 parent 0d53b7a commit 54b74a2

File tree

2 files changed

+107
-20
lines changed

2 files changed

+107
-20
lines changed

scripts/lldpshow

+28-20
Original file line numberDiff line numberDiff line change
@@ -127,53 +127,60 @@ class Lldpshow(object):
127127
l_intf = intf.attrib['name']
128128
if l_intf.startswith(BACKEND_ASIC_INTERFACE_NAME_PREFIX):
129129
continue
130-
self.lldpsum[l_intf] = {}
130+
remote_port = intf.find('port')
131+
r_portid = remote_port.find('id').text
132+
key = l_intf + "#" + r_portid
133+
self.lldpsum[key] = {}
134+
self.lldpsum[key]['l_intf'] = l_intf
135+
self.lldpsum[key]['r_portid'] = r_portid
131136
chassis = intf.find('chassis')
132137
capabs = chassis.findall('capability')
133138
capab = self.parse_cap(capabs)
134139
rmt_name = chassis.find('name')
135140
if rmt_name is not None:
136-
self.lldpsum[l_intf]['r_name'] = rmt_name.text
141+
self.lldpsum[key]['r_name'] = rmt_name.text
137142
else:
138-
self.lldpsum[l_intf]['r_name'] = ''
139-
remote_port = intf.find('port')
140-
self.lldpsum[l_intf]['r_portid'] = remote_port.find('id').text
143+
self.lldpsum[key]['r_name'] = ''
141144
rmt_desc = remote_port.find('descr')
142145
if rmt_desc is not None:
143-
self.lldpsum[l_intf]['r_portname'] = rmt_desc.text
146+
self.lldpsum[key]['r_portname'] = rmt_desc.text
144147
else:
145-
self.lldpsum[l_intf]['r_portname'] = ''
146-
self.lldpsum[l_intf]['capability'] = capab
148+
self.lldpsum[key]['r_portname'] = ''
149+
self.lldpsum[key]['capability'] = capab
147150

148151
def sort_sum(self, summary):
149152
""" Sort the summary information in the way that is expected(natural string)."""
150-
def alphanum_key(key): return [re.findall('[A-Za-z]+', key) + [int(port_num)
151-
for port_num in re.findall('\d+', key)]]
153+
def alphanum_key(key):
154+
key = key.split("#")[0]
155+
return [re.findall('[A-Za-z]+', key) + [int(port_num)
156+
for port_num in re.findall('\d+', key)]]
152157
return sorted(summary, key=alphanum_key)
153158

154-
def display_sum(self, lldp_detail_info):
159+
def get_summary_output(self, lldp_detail_info):
155160
"""
156-
print out summary result of lldp neighbors
161+
returns summary result of lldp neighbors
157162
"""
163+
output_summary = ''
158164
# In detail mode output is plain text
159165
if self.lldpraw and lldp_detail_info:
160166
lldp_output = ''
161167
for lldp_detail_output in self.lldpraw:
162168
lldp_output += lldp_detail_output
163-
print(lldp_output)
169+
output_summary += lldp_output + "\n"
164170
elif self.lldpraw:
165171
lldpstatus = []
166-
print('Capability codes: (R) Router, (B) Bridge, (O) Other')
172+
output_summary += "Capability codes: (R) Router, (B) Bridge, (O) Other\n"
167173
header = ['LocalPort', 'RemoteDevice', 'RemotePortID', 'Capability', 'RemotePortDescr']
168174
sortedsum = self.sort_sum(self.lldpsum)
169175
for key in sortedsum:
170-
lldpstatus.append([key, self.lldpsum[key]['r_name'], self.lldpsum[key]['r_portid'],
176+
lldpstatus.append([self.lldpsum[key]['l_intf'], self.lldpsum[key]['r_name'], self.lldpsum[key]['r_portid'],
171177
self.lldpsum[key]['capability'], self.lldpsum[key]['r_portname']])
172-
print(tabulate(lldpstatus, header))
173-
print('-'.rjust(50, '-'))
174-
print('Total entries displayed: ', len(self.lldpsum))
178+
output_summary += tabulate(lldpstatus, header) + "\n"
179+
output_summary += ('-'.rjust(50, '-')) + "\n"
180+
output_summary += "Total entries displayed: {}".format(len(self.lldpsum))
175181
elif self.err is not None:
176-
print('Error:', self.err)
182+
output_summary += "Error: {}".format(self.err)
183+
return output_summary
177184

178185

179186
def main():
@@ -202,7 +209,8 @@ def main():
202209
lldp = Lldpshow()
203210
lldp.get_info(lldp_detail_info, lldp_port)
204211
lldp.parse_info(lldp_detail_info)
205-
lldp.display_sum(lldp_detail_info)
212+
output_summary = lldp.get_summary_output(lldp_detail_info)
213+
print(output_summary)
206214
except Exception as e:
207215
print(str(e), file=sys.stderr)
208216
sys.exit(1)

tests/lldp_test.py

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import os
2+
3+
from click.testing import CliRunner
4+
from utilities_common.general import load_module_from_source
5+
6+
test_path = os.path.dirname(os.path.abspath(__file__))
7+
modules_path = os.path.dirname(test_path)
8+
scripts_path = os.path.join(modules_path, "scripts")
9+
10+
# Load the file under test
11+
lldpshow_path = os.path.join(scripts_path, 'lldpshow')
12+
lldpshow = load_module_from_source('lldpshow', lldpshow_path)
13+
14+
# Expected output for 2 remote MACs on same physical interface
15+
expected_2MACs_Ethernet0_output = \
16+
('Capability codes: (R) Router, (B) Bridge, (O) Other\n'
17+
'LocalPort RemoteDevice RemotePortID Capability '
18+
'RemotePortDescr\n'
19+
'----------- -------------- ----------------- ------------ '
20+
'-----------------\n'
21+
'Ethernet0 dummy 00:00:00:00:00:01 BR First MAC\n'
22+
'Ethernet0 dummy 00:00:00:00:00:02 R Second MAC\n'
23+
'--------------------------------------------------\n'
24+
'Total entries displayed: 2')
25+
26+
expected_lldpctl_xml_output = \
27+
['<?xml version="1.0" encoding="UTF-8"?>\n\
28+
<lldp label="LLDP neighbors">\n\
29+
<interface label="Interface" name="Ethernet0" via="LLDP" rid="2" age="7 days, 22:11:33">\n\
30+
<chassis label="Chassis">\n\
31+
<id label="ChassisID" type="mac">00:00:00:00:00:01</id>\n\
32+
<name label="SysName">dummy</name>\n\
33+
<descr label="SysDescr">NA</descr>\n\
34+
<mgmt-ip label="MgmtIP">00:00:00:00:00:00</mgmt-ip>\n\
35+
<capability label="Capability" type="Bridge" enabled="on"/>\n\
36+
<capability label="Capability" type="Router" enabled="on"/>\n\
37+
<capability label="Capability" type="Wlan" enabled="off"/>\n\
38+
<capability label="Capability" type="Station" enabled="off"/>\n\
39+
</chassis>\n\
40+
<port label="Port">\n\
41+
<id label="PortID" type="mac">00:00:00:00:00:01</id>\n\
42+
<descr label="PortDescr">First MAC</descr>\n\
43+
<ttl label="TTL">120</ttl>\n\
44+
</port>\n\
45+
</interface>\n\
46+
<interface label="Interface" name="Ethernet0" via="LLDP" rid="4" age="7 days, 22:11:34">\n\
47+
<chassis label="Chassis">\n\
48+
<id label="ChassisID" type="mac">00:00:00:00:00:02</id>\n\
49+
<name label="SysName">dummy</name>\n\
50+
<descr label="SysDescr">NA</descr>\n\
51+
<mgmt-ip label="MgmtIP">00:00:00:00:00:00</mgmt-ip>\n\
52+
<capability label="Capability" type="Router" enabled="on"/>\n\
53+
</chassis>\n\
54+
<port label="Port">\n\
55+
<id label="PortID" type="mac">00:00:00:00:00:02</id>\n\
56+
<descr label="PortDescr">Second MAC</descr>\n\
57+
<ttl label="TTL">120</ttl>\n\
58+
</port>\n\
59+
</interface>\n\
60+
</lldp>\n']
61+
62+
class TestLldp(object):
63+
@classmethod
64+
def setup_class(cls):
65+
print("SETUP")
66+
67+
def test_show_lldp_2_macs_same_phy_interface(self):
68+
runner = CliRunner()
69+
# Create lldpshow instance
70+
lldp = lldpshow.Lldpshow()
71+
# Mock lldpraw to check new functionality in parse_info()
72+
lldp.lldpraw = expected_lldpctl_xml_output
73+
lldp.parse_info(lldp_detail_info=False)
74+
output_summary = lldp.get_summary_output(lldp_detail_info=False)
75+
assert output_summary == expected_2MACs_Ethernet0_output
76+
77+
@classmethod
78+
def teardown_class(cls):
79+
print("TEARDOWN")

0 commit comments

Comments
 (0)