From 250e067316d2789c02b253e24d670d2d8f85302d Mon Sep 17 00:00:00 2001 From: Ludovic Pouzenc Date: Sun, 15 May 2016 23:26:10 +0200 Subject: inspectors : refactor done --- haircontrol/discovery.py | 57 +++++------- haircontrol/inspectors.py | 114 ++++++++++++++++++++++-- test-data/input/stg2-lldp.out | 185 --------------------------------------- test-data/input/stg2-lldpctl.out | 185 +++++++++++++++++++++++++++++++++++++++ tests/test_discovery.py | 22 +---- 5 files changed, 316 insertions(+), 247 deletions(-) delete mode 100644 test-data/input/stg2-lldp.out create mode 100644 test-data/input/stg2-lldpctl.out diff --git a/haircontrol/discovery.py b/haircontrol/discovery.py index 680eb4c..243e3b6 100644 --- a/haircontrol/discovery.py +++ b/haircontrol/discovery.py @@ -1,56 +1,41 @@ -import re -import xml.etree.ElementTree from haircontrol.data import * +from haircontrol.inspectors import * class Discovery: - IPNEIGH = re.compile("(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") - IPLINKSHOW = re.compile("(?P\d+):\s+(?P[^:]*):.+\s+(?:link/ether\s+(?P[a-f0-9:]*)\s+brd|link/none)") - def __init__(self, inspector): - self.inspector = inspector + def __init__(self): #, inspector): + self.linux_inspector = LinuxInspector() + self.ubnt_inspector = UbntInspector() self.net = EtherDomain() def discover_hinting_from_lldp(self, e_lldp): self.net.add_equipment(e_lldp) - self.inspector.connect(e_lldp) + self.linux_inspector.connect(e_lldp) # Learn local interfaces of e_lldp - fd = self.inspector.command('ip-link') - for line in fd: - matches = Discovery.IPLINKSHOW.match(line) - if matches: - ifname, mac = [ matches.group(k) for k in ['ifname','mac'] ] - e_lldp.ifaces[ifname] = Interface(ifname, mac) - fd.close() + result = self.linux_inspector.command('ip-link') + for (_, ifname, mac) in result: + e_lldp.ifaces[ifname] = Interface(ifname, mac) # Create equipments and ifaces from LLDP neighbour discovery - fd = self.inspector.command('lldp') - root = xml.etree.ElementTree.parse(fd).getroot() - for iface in root.iter('interface'): - local_ifname = iface.get('name') - local_mac = e_lldp.ifaces[local_ifname].mac - chassis = iface.find('chassis') - remote_name = chassis.find('name').text - remote_ipmgmt = chassis.find('mgmt-ip').text + result = self.linux_inspector.command('lldpctl') + for (local_ifname, local_mac, remote_name, remote_ipmgmt, ports) in result: e = Equipment(remote_name, remote_ipmgmt) self.net.add_equipment(e) - for port in iface.findall('port'): - remote_ifname = port.find('id').text + for remote_ifname in ports: e.add_seen_mac(remote_ifname, local_mac) - fd.close() - self.inspector.disconnect() + + self.linux_inspector.disconnect() def discover_from_root(self, e_root): self.net.add_equipment(e_root) - self.inspector.connect(e_root) - fd = self.inspector.command('ip-neigh') - for line in fd: - matches = Discovery.IPNEIGH.match(line) - if matches: - ip, ifname, mac = [ matches.group(k) for k in ['ip','ifname','mac'] ] - self.net.index_mac_ip(mac, ip) - e_root.add_seen_mac(ifname, mac) - fd.close() - self.inspector.disconnect() + self.linux_inspector.connect(e_root) + + result = self.linux_inspector.command('ip-neigh') + for (ip, ifname, mac) in result: + self.net.index_mac_ip(mac, ip) + e_root.add_seen_mac(ifname, mac) + + self.linux_inspector.disconnect() diff --git a/haircontrol/inspectors.py b/haircontrol/inspectors.py index 9b797fe..33bf360 100644 --- a/haircontrol/inspectors.py +++ b/haircontrol/inspectors.py @@ -1,21 +1,123 @@ import re +import xml.etree.ElementTree class Inspector(): cmds = {} - fd = None - def parse(self): - return None #XXX Implement + def __init__(self): + #XXX in a mockup class ? + self.testDataPath = '../test-data/input' + self.e = None + + def connect(self, e): + self.e = e + + def disconnect(self): + self.e = None + + def command(self, cmdname): + cmddef = self.cmds.get(cmdname) + if not cmddef: + return None + fd = self._exec(cmdname, cmddef['cmd']) + result = [] + re = cmddef.get('re') + func = cmddef.get('func') + if re: + for line in fd: + matches = re.match(line) + if matches: + result.append(matches.groups()) + elif func: + result = func(self, fd) + fd.close() + return result + + def _exec(self, cmdname, cmd): + #XXX in a mockup class ? + mockfile = self.testDataPath + '/' + self.e.name + '-' + cmdname + '.out' + return open(mockfile) + + class LinuxInspector(Inspector): + + def parse_lldpctl_xml(self, fd): + result = [] + root = xml.etree.ElementTree.parse(fd).getroot() + for iface in root.iter('interface'): + local_ifname = iface.get('name') + local_mac = self.e.ifaces[local_ifname].mac + chassis = iface.find('chassis') + remote_name = chassis.find('name').text + remote_ipmgmt = chassis.find('mgmt-ip').text + ports = [] + for port in iface.findall('port'): + remote_ifname = port.find('id').text + ports.append(remote_ifname) + result.append( (local_ifname, local_mac, remote_name, remote_ipmgmt, ports) ) + return result + cmds = { 'ip-neigh': { 'cmd': 'ip neigh', + 're': re.compile("(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") # fe80::8300 dev eth1 lladdr 10:fe:ed:f1:e1:f3 router STALE # 172.16.20.210 dev eth1 lladdr c0:4a:00:fe:1f:87 REACHABLE + }, + 'ip-link': { + 'cmd': 'ip -o link', 'kind': 'text', - 'fields': ['ip','ifname','mac'], - 're': re.compile("(?P[a-f0-9:.]+) dev (?P.*) lladdr (?P[a-f0-9:]*)") - } + 're': re.compile("(?P\d+):\s+(?P[^:]*):.+\s+(?:link/ether\s+(?P[a-f0-9:]*)\s+brd|link/none)") + # 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default \ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + # 2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000\ link/ether 8c:89:a5:c7:be:88 brd ff:ff:ff:ff:ff:ff + }, + 'lldpctl': { + 'cmd': 'lldpctl -f xml', + 'func': parse_lldpctl_xml + # + # + # + # + # Robert_VILO + # Robert_VILO + # LM5 running on + #XM.v5.5.10 + # 172.16.10.26 + # 169.254.227.212 + # + # + # + # br0 + # br0 + # + # + # + # ... + # + # + }, } + + +class UbntInspector(Inspector): + + def parse_status_json(self, fd): + return 'parse_status_json' + + def parse_brmacs_json(self, fd): + return 'parse_brmacs_json' + + cmds = { + 'status.cgi': { + 'cmd':'..', + 'kind': 'cgi-json', + 'func': parse_status_json + }, + 'brmacs.cgi': { + 'cmd':'..', + 'kind': 'cgi-json', + 'func': parse_brmacs_json + } + } diff --git a/test-data/input/stg2-lldp.out b/test-data/input/stg2-lldp.out deleted file mode 100644 index 2200b95..0000000 --- a/test-data/input/stg2-lldp.out +++ /dev/null @@ -1,185 +0,0 @@ - - - - - TTN_CG_EST - TTN_CG_EST - N5N running on -XM.v5.5.10 - 172.16.10.8 - - - - br0 - br0 - - - - - Daniel_POUZENC - Daniel_POUZENC - NB5 running on -XM.v5.5.10 - 172.16.10.28 - - - - br0 - br0 - - - - - TTN_PI_EGL_NORD_EST - TTN_PI_EGL_NORD_EST - N5N running on -XM.v5.5.10 - 172.16.10.30 - - - - br0 - br0 - - - - - TTN_PI_EGL_RX_CG - TTN_PI_EGL_RX_CG - N5B-400 running on -XW.v5.5.10 - 172.16.10.37 - - - - br0 - br0 - - - - - TTN_CG_POINTIS - TTN_CG_POINTIS - N5B-400 running on -XW.v5.5.10 - 172.16.10.123 - - - - br0 - br0 - - - - - Joel_PINCE - Joel_PINCE - N5B-400 running on -XW.v5.5.10 - 172.16.11.41 - - - - br0 - br0 - - - - - Samuel_BOURILLON - Samuel_BOURILLON - NB5 running on -XM.v5.5.10 - 172.16.11.44 - - - - br0 - br0 - - - - - TTN_JP_TX_NE - TTN_JP_TX_NE - N5N running on -XM.v5.5.10 - 172.16.11.46 - - - - br0 - br0 - - - - - TTN_ESTAN_SUD - TTN_ESTAN_SUD - N5N running on -XM.v5.5.10 - 172.16.11.104 - - - - br0 - br0 - - - - - TTN_PI_ESTAN - TTN_PI_ESTAN - N5B-400 running on -XW.v5.5.10 - 172.16.11.105 - - - - br0 - br0 - - - - - Eglise_ESTANCARBON - Eglise_ESTANCARBON - N5B-400 running on -XW.v5.5.10 - 172.16.11.106 - - - - br0 - br0 - - - - - VINSONNEAU_RX_CG_EST - VINSONNEAU_RX_CG_EST - N5B-19 running on -XW.v5.5.10 - 172.16.11.158 - - - - br0 - br0 - - - - - Denis_MAUBE - Denis_MAUBE - N5N running on -XM.v5.5.10 - 172.16.20.3 - - - - ath0 - ath0 - - - diff --git a/test-data/input/stg2-lldpctl.out b/test-data/input/stg2-lldpctl.out new file mode 100644 index 0000000..2200b95 --- /dev/null +++ b/test-data/input/stg2-lldpctl.out @@ -0,0 +1,185 @@ + + + + + TTN_CG_EST + TTN_CG_EST + N5N running on +XM.v5.5.10 + 172.16.10.8 + + + + br0 + br0 + + + + + Daniel_POUZENC + Daniel_POUZENC + NB5 running on +XM.v5.5.10 + 172.16.10.28 + + + + br0 + br0 + + + + + TTN_PI_EGL_NORD_EST + TTN_PI_EGL_NORD_EST + N5N running on +XM.v5.5.10 + 172.16.10.30 + + + + br0 + br0 + + + + + TTN_PI_EGL_RX_CG + TTN_PI_EGL_RX_CG + N5B-400 running on +XW.v5.5.10 + 172.16.10.37 + + + + br0 + br0 + + + + + TTN_CG_POINTIS + TTN_CG_POINTIS + N5B-400 running on +XW.v5.5.10 + 172.16.10.123 + + + + br0 + br0 + + + + + Joel_PINCE + Joel_PINCE + N5B-400 running on +XW.v5.5.10 + 172.16.11.41 + + + + br0 + br0 + + + + + Samuel_BOURILLON + Samuel_BOURILLON + NB5 running on +XM.v5.5.10 + 172.16.11.44 + + + + br0 + br0 + + + + + TTN_JP_TX_NE + TTN_JP_TX_NE + N5N running on +XM.v5.5.10 + 172.16.11.46 + + + + br0 + br0 + + + + + TTN_ESTAN_SUD + TTN_ESTAN_SUD + N5N running on +XM.v5.5.10 + 172.16.11.104 + + + + br0 + br0 + + + + + TTN_PI_ESTAN + TTN_PI_ESTAN + N5B-400 running on +XW.v5.5.10 + 172.16.11.105 + + + + br0 + br0 + + + + + Eglise_ESTANCARBON + Eglise_ESTANCARBON + N5B-400 running on +XW.v5.5.10 + 172.16.11.106 + + + + br0 + br0 + + + + + VINSONNEAU_RX_CG_EST + VINSONNEAU_RX_CG_EST + N5B-19 running on +XW.v5.5.10 + 172.16.11.158 + + + + br0 + br0 + + + + + Denis_MAUBE + Denis_MAUBE + N5N running on +XM.v5.5.10 + 172.16.20.3 + + + + ath0 + ath0 + + + diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 7fb0bd2..06c61e3 100755 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -3,30 +3,12 @@ import unittest #import pudb; pudb.set_trace() from context import haircontrol -from haircontrol import discovery, data, inspectors - -class MockInspector(inspectors.Inspector): - def __init__(self, testDataPath): - self.testDataPath = testDataPath - self.e = None - - def connect(self, e): - self.e = e - - def disconnect(self): - self.e = None - - def command(self, command): - if not self.e: - return None - mockfile = self.testDataPath + '/' + self.e.name + '-' + command + '.out' - return open(mockfile) #XXX use Inspector.parse() - +from haircontrol import discovery, data #, inspectors class TestDiscovery(unittest.TestCase): def setUp(self): - self.discovery = discovery.Discovery(MockInspector('../test-data/input')) + self.discovery = discovery.Discovery() #MockInspector('../test-data/input')) self.maxDiff=None def test_wire_graph(self): -- cgit v1.1