diff options
-rw-r--r-- | haircontrol/discovery.py | 57 | ||||
-rw-r--r-- | haircontrol/inspectors.py | 114 | ||||
-rw-r--r-- | test-data/input/stg2-lldpctl.out (renamed from test-data/input/stg2-lldp.out) | 0 | ||||
-rwxr-xr-x | tests/test_discovery.py | 22 |
4 files changed, 131 insertions, 62 deletions
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<ip>[a-f0-9:.]+) dev (?P<ifname>.*) lladdr (?P<mac>[a-f0-9:]*)") - IPLINKSHOW = re.compile("(?P<id>\d+):\s+(?P<ifname>[^:]*):.+\s+(?:link/ether\s+(?P<mac>[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<ip>[a-f0-9:.]+) dev (?P<ifname>.*) lladdr (?P<mac>[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<ip>[a-f0-9:.]+) dev (?P<ifname>.*) lladdr (?P<mac>[a-f0-9:]*)") - } + 're': re.compile("(?P<id>\d+):\s+(?P<ifname>[^:]*):.+\s+(?:link/ether\s+(?P<mac>[a-f0-9:]*)\s+brd|link/none)") + # 1: lo: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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 + #<?xml version="1.0" encoding="UTF-8"?> + #<lldp label="LLDP neighbors"> + # <interface label="Interface" name="eth0" via="CDPv1" rid="2" age="0 day, 21:41:18"> + # <chassis label="Chassis"> + # <id label="ChassisID" type="local">Robert_VILO</id> + # <name label="SysName">Robert_VILO</name> + # <descr label="SysDescr">LM5 running on + #XM.v5.5.10</descr> + # <mgmt-ip label="MgmtIP">172.16.10.26</mgmt-ip> + # <mgmt-ip label="MgmtIP">169.254.227.212</mgmt-ip> + # <capability label="Capability" type="Bridge" enabled="on"/> + # </chassis> + # <port label="Port"> + # <id label="PortID" type="ifname">br0</id> + # <descr label="PortDescr">br0</descr> + # </port> + # </interface> + # <interface label="Interface" name="eth0" via="CDPv1" rid="4" age="0 day, 21:41:18"> + # ... + # </interface> + #</lldp> + }, } + + +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-lldpctl.out index 2200b95..2200b95 100644 --- a/test-data/input/stg2-lldp.out +++ b/test-data/input/stg2-lldpctl.out 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): |