From f5814e325dc083523337d34c7f459ca5b7f8ca07 Mon Sep 17 00:00:00 2001 From: Ludovic Pouzenc Date: Mon, 16 May 2016 13:08:46 +0200 Subject: Inspectors : add ToughSwitch. Tests : static hinting for switches. Discovery : go through all neighbours until all are inspected. --- haircontrol/data.py | 1 + haircontrol/discovery.py | 71 +++++++++++++++++++++++++++++++------------ haircontrol/inspectors.py | 77 ++++++++++++++++++++++++++++++++--------------- tests/test_discovery.py | 8 ++++- 4 files changed, 112 insertions(+), 45 deletions(-) diff --git a/haircontrol/data.py b/haircontrol/data.py index 8adf75f..463e38a 100644 --- a/haircontrol/data.py +++ b/haircontrol/data.py @@ -33,6 +33,7 @@ class Equipment: self.name = name self.mgmtip = mgmtip self.ifaces = {} + self.inspected = False def __repr__(self): return repr( (self.name, self.mgmtip, list(self.ifaces.values())) ) diff --git a/haircontrol/discovery.py b/haircontrol/discovery.py index 5f63349..7c3c9f0 100644 --- a/haircontrol/discovery.py +++ b/haircontrol/discovery.py @@ -8,9 +8,14 @@ class Discovery: # XXX Use only one Inspector self.linux_inspector = LinuxInspector() self.ubnt_inspector = UbntInspector() + self.toughswitch_inspector = ToughSwitchInspector() self.net = EtherDomain() - def discover_hinting_from_lldp(self, e_lldp): + def discover_static_hinting(self, name_ip_tuples): + for name, ip in name_ip_tuples: + self.net.add_equipment(Equipment(name, ip)) + + def discover_lldp_hinting(self, e_lldp): self.net.add_equipment(e_lldp) self.linux_inspector.connect(e_lldp) @@ -36,7 +41,7 @@ class Discovery: self.net.add_equipment(e_root) self.linux_inspector.connect(e_root) - # Learn root neighbours + # Learn root neighbours (directly or indirectly connected via trasparent bridges) result = self.linux_inspector.command('ip-neigh') for (ip, ifname, mac) in result: self.net.index_mac_ip(mac, ip) @@ -44,22 +49,48 @@ class Discovery: self.linux_inspector.disconnect() - # Inspect antennas bridge tables - for ip in self.net.equipments: - if ip.startswith('172.16.1'): # XXX Use neighbours, filter with OUI - e = self.net.equipments[ip] - self.ubnt_inspector.connect(e) - - # Learn local interfaces - result = self.ubnt_inspector.command('status.cgi') - for (ifname, mac) in result: - if ifname not in [ 'lo', 'wifi0', 'br0' ]: # XXX configurable filter - e.add_iface(ifname, mac) - - # Learn bridge tables - result = self.ubnt_inspector.command('brmacs.cgi') - for (ifname, mac) in result: - e.add_seen_mac(ifname, mac) - - self.ubnt_inspector.disconnect() + # Create Equipment object for all neighbours (if not already previously done by hinting) + for iface in e_root.ifaces.values(): + local_ifname = iface.name + local_mac = iface.mac + for remote_mac in iface.mac_seen: + remote_ip = self.net.mac2ip.get(remote_mac) + if remote_ip: + e = self.net.equipments.get(remote_ip) + if not e: + e = Equipment('?', remote_ip) + e.add_iface('?', remote_mac) + self.net.add_equipment(e) + + # Inspect all non-already inspected equipement + done = False + while not done: + done = True + for ip,e in self.net.equipments.items(): + if not e.inspected: + done = False + # Inspect antennas bridge tables + if ip.startswith('172.16.1'): # XXX Filter with OUI + self.ubnt_inspector.connect(e) + # Learn local interfaces + result = self.ubnt_inspector.command('status.cgi') + for (ifname, mac) in result: + if ifname not in [ 'lo', 'wifi0', 'br0' ]: # XXX configurable filter + e.add_iface(ifname, mac) + # Learn bridge tables + result = self.ubnt_inspector.command('brmacs.cgi') + for (ifname, mac) in result: + e.add_seen_mac(ifname, mac) + self.ubnt_inspector.disconnect() + # Inspect switches + elif ip.startswith('172.16.3'): # XXX Filter with OUI + self.toughswitch_inspector.connect(e) + result = self.toughswitch_inspector.command('mactable_data.cgi') + for (ifname, mac) in result: + e.add_seen_mac(ifname, mac) + self.toughswitch_inspector.disconnect() + # Flag unknowns as inspected (and warn) + else: + e.inspected = 'cannot' + print("Notice: Unimplemented inspector for %s"%e) diff --git a/haircontrol/inspectors.py b/haircontrol/inspectors.py index b81d832..3d7b766 100644 --- a/haircontrol/inspectors.py +++ b/haircontrol/inspectors.py @@ -12,26 +12,36 @@ class Inspector(): def connect(self, e): self.e = e + self.e.inspected = 'in-progress' def disconnect(self): + self.e.inspected = True self.e = None def command(self, cmdname): cmddef = self.cmds.get(cmdname) if not cmddef: return None - fd = self._exec(cmdname, cmddef['cmd']) + cmd = 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() + fd = None + try: + fd = self._exec(cmdname, cmd) + 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) + except Exception: + print("Error : can't exec/read %s (%s) on %s (%s)"%(cmdname, cmd, self.e.name, self.e.mgmtip)) + finally: + if fd: + fd.close() + return result def _exec(self, cmdname, cmd): @@ -39,6 +49,18 @@ class Inspector(): mockfile = self.testDataPath + '/' + self.e.name + '-' + cmdname + '.out' return open(mockfile) + def _parse_cgi_json(self, fd): + for cgi_headers in fd: + if cgi_headers == '\n': + break + js = {} + try: + js = json.load(fd) + except ValueError: + print("Warn : unparsable json for %s (%s)"%(self.e.name, self.e.mgmtip)) + finally: + fd.close() + return js class LinuxInspector(Inspector): @@ -101,21 +123,8 @@ class LinuxInspector(Inspector): } - class UbntInspector(Inspector): - def _parse_cgi_json(self, fd): - for cgi_headers in fd: - if cgi_headers == '\n': - break - js = {} - try: - js = json.load(fd) - except ValueError: - print("Warn : unparsable json") - fd.close() - return js - def parse_status_json(self, fd): result = [] interfaces = self._parse_cgi_json(fd).get('interfaces') @@ -151,3 +160,23 @@ class UbntInspector(Inspector): } } +class ToughSwitchInspector(Inspector): + + def parse_mactable_data_cgi(self, fd): + result = [] + macs = self._parse_cgi_json(fd).get('macs') + if macs: + for line in macs: + if isinstance(line, dict): + ifname = "p%i"%line.get('port') + mac = line.get('mac') + result.append( (ifname, mac) ) + return result + + cmds = { + 'mactable_data.cgi': { + 'cmd':'/usr/www/mactable_data.cgi', + 'func': parse_mactable_data_cgi + }, + } + diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 06c61e3..ba30dab 100755 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -14,8 +14,14 @@ class TestDiscovery(unittest.TestCase): def test_wire_graph(self): ref_net = data.EtherDomain() #json.load('../test-data/ref-output/equipments.json') - self.discovery.discover_hinting_from_lldp(data.Equipment('stg2', '172.16.0.253')) + self.discovery.discover_lldp_hinting(data.Equipment('stg2', '172.16.0.253')) + self.discovery.discover_static_hinting([ + ('SW_SergeGOUSSE', '172.16.30.23'), + ('SW_PI_EGL', '172.16.30.27'), + ('SW_Eglise_ESTANCARBON', '172.16.30.38'), + ]) self.discovery.discover_from_root(data.Equipment('stg', '172.16.0.254')) + self.assertEqual(ref_net.get_equipment_list_sorted(), list(self.discovery.net.get_equipment_list_sorted())) if __name__ == '__main__': -- cgit v1.1