summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--haircontrol/data.py27
-rw-r--r--haircontrol/discovery.py68
-rw-r--r--test-data/ref-outpout/tree.txt21
-rw-r--r--tests/context.py5
-rwxr-xr-xtests/test_discovery.py43
6 files changed, 165 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/haircontrol/data.py b/haircontrol/data.py
new file mode 100644
index 0000000..599d322
--- /dev/null
+++ b/haircontrol/data.py
@@ -0,0 +1,27 @@
+class Equipment:
+ def __init__(self, name=None, mgmtip=None):
+ self.name = name
+ self.mgmtip = mgmtip
+ self.ifaces = {}
+
+ def __repr__(self):
+ return repr( (self.name, self.mgmtip, self.ifaces.values()) )
+
+ def add_seen_mac(self, ifname, mac):
+ iface = self.ifaces.get(ifname)
+ if not iface:
+ iface = Interface()
+ iface.name = ifname
+ self.ifaces[ifname] = iface
+ iface.mac_seen.append(mac)
+
+class Interface:
+ def __init__(self, name=None, mac=None):
+ self.name = name
+ self.mac = mac
+ self.mac_seen = []
+ self.remote = []
+
+ def __repr__(self):
+ return repr( (self.mac, self.name) )
+
diff --git a/haircontrol/discovery.py b/haircontrol/discovery.py
new file mode 100644
index 0000000..d454283
--- /dev/null
+++ b/haircontrol/discovery.py
@@ -0,0 +1,68 @@
+
+import re
+import xml.etree.ElementTree
+from haircontrol.data import Equipment
+
+class Discovery:
+ def __init__(self, inspector):
+ self.inspector = inspector
+ self.equipments = {}
+ self.ip2mac = {}
+ self.mac2ip = {}
+
+ def add_equipment(self,e):
+ old = self.equipments.get(e.mgmtip)
+ if old:
+ print("Warn : %s replaced by %s"%(old,e))
+ self.equipments[e.mgmtip] = e
+
+ def index_mac_ip(self,mac, ip):
+ oldmac = self.ip2mac.get(ip)
+ if oldmac:
+ print("Warn : %s replaced by %s for %s"%(oldmac, mac, ip))
+ oldip = self.mac2ip.get(mac)
+ if oldip:
+ print("Warn : %s replaced by %s for %s"%(oldip, ip, mac))
+ self.ip2mac[ip] = mac
+ self.mac2ip[mac] = ip
+
+ def discover_hinting_from_lldp(self, lldpnode):
+ self.inspector.connect(lldpnode)
+ fd = self.inspector.command('lldp')
+ root = xml.etree.ElementTree.parse(fd).getroot()
+ for iface in root.iter('interface'):
+ ifname = iface.get('name')
+ chassis = iface.find('chassis')
+ e = Equipment()
+ e.name = chassis.find('name').text
+ e.mgmtip = chassis.find('mgmt-ip').text
+ self.add_equipment(e)
+ for port in iface.findall('port'):
+ #remote_ifname = port.find('id').text
+ e.add_seen_mac(ifname, 'lldp') #XXX
+ fd.close()
+ self.inspector.disconnect()
+
+ def discover_from_root(self, rootnode):
+ self.inspector.connect(rootnode)
+ fd = self.inspector.command('ip-neigh')
+ IPNEIGH = re.compile("^(?P<ip>[a-f0-9:.]+) dev (?P<ifname>.*) lladdr (?P<mac>[a-f0-9:]*)")
+ for line in fd:
+ matches = IPNEIGH.search(line)
+ if matches:
+ ip, ifname, mac = [ matches.group(k) for k in ['ip','ifname','mac'] ]
+ self.index_mac_ip(mac, ip)
+ rootnode.add_seen_mac(ifname, mac)
+ fd.close()
+ self.inspector.disconnect()
+
+# fe80::8300 dev eth1 lladdr 10:fe:ed:f1:e1:f3 router STALE
+# 172.16.11.46 dev eth1 lladdr 24:a4:3c:ee:89:ca STALE
+# 172.16.20.3 dev eth1 lladdr 00:27:22:0e:74:15 STALE
+# 172.16.20.210 dev eth1 lladdr c0:4a:00:fe:1f:87 REACHABLE
+# 172.16.21.69 dev eth1 lladdr e8:de:27:b5:f2:b1 DELAY
+# 172.16.20.216 dev eth1 lladdr c0:4a:00:fe:09:bd PERMANENT
+# 172.16.11.41 dev eth1 lladdr 04:18:d6:0e:37:d4 STALE
+# 172.16.11.104 dev eth1 lladdr 00:15:6d:8e:22:46 STALE
+# 172.16.10.8 dev eth1 lladdr 00:27:22:0e:67:f9 STALE
+#
diff --git a/test-data/ref-outpout/tree.txt b/test-data/ref-outpout/tree.txt
new file mode 100644
index 0000000..99523d0
--- /dev/null
+++ b/test-data/ref-outpout/tree.txt
@@ -0,0 +1,21 @@
+stg.chd.sx
+ SW_SergeGOUSSE
+ TTN_CG_EST
+ Joel_PINCE
+ rt-pince
+ TTN_JP_TX_NE
+ Denis_MAUBE
+ VINSONNEAU_RX_CG_EST
+ rt-vinsonneau
+ TTN_CG_POINTIS
+ TTN_PI_EGL_RX_CG
+ SW_PI_EGL
+ TTN_PI_EGL_NORD_EST
+ Daniel_POUZENC
+ rt-pouzenc
+ TTN_PI_ESTAN
+ Eglise_ESTANCARBON
+ SW_Eglise_ESTANCARBON
+ TTN_ESTAN_SUD
+ Samuel_BOURILLON
+ rt-bourillon
diff --git a/tests/context.py b/tests/context.py
new file mode 100644
index 0000000..57d10fb
--- /dev/null
+++ b/tests/context.py
@@ -0,0 +1,5 @@
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
+
+import haircontrol
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
new file mode 100755
index 0000000..96d262e
--- /dev/null
+++ b/tests/test_discovery.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+import unittest
+import pprint
+
+from context import haircontrol
+from haircontrol import discovery, data
+
+class MockInspector(object):
+ def __init__(self, testDataPath):
+ self.testDataPath = testDataPath
+
+ def connect(self, e):
+ self.name = e.name
+
+ def disconnect(self):
+ self.name = None
+
+ def command(self, command):
+ if not self.name:
+ return None
+ mockfile = self.testDataPath + '/' + self.name + '-' + command + '.out'
+ return open(mockfile)
+
+
+class TestDiscovery(unittest.TestCase):
+
+ # http://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable
+ def setUp(self):
+ self.discovery = discovery.Discovery(MockInspector('../test-data/input'))
+ self.maxDiff=None
+ self.ref_equipments = [] #json.load('../test-data/ref-output/equipments.json')
+
+ def test_wire_graph(self):
+ self.discovery.discover_hinting_from_lldp(data.Equipment('stg2'))
+ self.discovery.discover_from_root(data.Equipment('stg'))
+ for e in self.discovery.equipments:
+ print(e)
+ for i in e.ifaces:
+ print(i)
+ self.assertEqual(self.discovery.equipments, self.ref_equipments)
+
+if __name__ == '__main__':
+ unittest.main()