diff --git a/netdiff/batman.py b/netdiff/batman.py new file mode 100644 index 0000000..7428d48 --- /dev/null +++ b/netdiff/batman.py @@ -0,0 +1,47 @@ +import json +import networkx +from netdiff.nxparser import Parser + + +class BatmanParser(Parser): + """ Batman Topology Parser """ + def _get_primary(self, mac, collection): + for node in collection: + for interface in node: + if mac == interface: + return node[0] + return 0 + + def _get_ag_node_list(self, data): + agn = [] + for node in data: + agi = [] + agi.append(node['primary']) + if('secondary'in node): + for interface in node['secondary']: + agi.append(interface) + agn.append(agi) + return agn + + def _parse(self, data): + """ + Converts a topology in a NetworkX MultiGraph object. + + :param str topology: The OLSR1 topology to be converted (JSON or dict) + :return: the NetworkX MultiGraph object + """ + # if data is not a python dict it must be a json string + if type(data) is not dict: + data = json.loads(data) + # initialize graph and list of aggregated nodes + graph = networkx.MultiGraph() + agn = self._get_ag_node_list(data['vis']) + # loop over topology section and create networkx graph + for node in data["vis"]: + for neigh in node["neighbors"]: + p_neigh = self._get_primary(neigh['neighbor'], agn) + if not graph.has_edge(node['primary'], p_neigh): + graph.add_edge(node['primary'], + p_neigh, + weight=neigh['metric']) + return graph diff --git a/netdiff/nxparser.py b/netdiff/nxparser.py new file mode 100644 index 0000000..5ec978b --- /dev/null +++ b/netdiff/nxparser.py @@ -0,0 +1,52 @@ +import json +import networkx + + +class Parser(object): + """ Generic Topology Parser """ + + def __init__(self, old, new): + """ + Initializes a new Parser + + :param str old: a JSON or dict representing the old topology + :param str new: a JSON or dict representing the new topology + """ + self.old_graph = self._parse(old) + self.new_graph = self._parse(new) + + def diff(self): + """ + Returns netdiff in a python dictionary + """ + return { + "added": self._make_diff(self.new_graph, self.old_graph), + "removed": self._make_diff(self.old_graph, self.new_graph) + } + + def diff_json(self, **kwargs): + """ + Returns netdiff in a JSON string + """ + return json.dumps(self.diff(), **kwargs) + + # --- private methods --- # + + def _make_diff(self, old, new): + """ + calculates differences between topologies 'old' and 'new' + returns a list of links + """ + # make a copy of old topology to avoid tampering with it + diff = old.copy() + not_different = [] + # loop over all links + for oedge in old.edges(): + # if link is also in new topology add it to the list + for nedge in new.edges(): + if ((oedge[0] == nedge[0]) and (oedge[1] == nedge[1])) or ((oedge[1] == nedge[0]) and (oedge[0] == nedge[1])): + not_different.append(oedge) + # keep only differences + diff.remove_edges_from(not_different) + # return list of links + return diff.edges() diff --git a/netdiff/olsr1.py b/netdiff/olsr1.py index 9e3106a..0be74fe 100755 --- a/netdiff/olsr1.py +++ b/netdiff/olsr1.py @@ -1,37 +1,10 @@ import json import networkx +from netdiff.nxparser import Parser -class Olsr1Parser(object): +class Olsr1Parser(Parser): """ OLSR v1 Topology Parser """ - - def __init__(self, old, new): - """ - Initializes a new Olsr1Parser - - :param str old: a JSON or dict representing the old OLSR1 topology - :param str new: a JSON or dict representing the new OLSR1 topology - """ - self.old_graph = self._parse(old) - self.new_graph = self._parse(new) - - def diff(self): - """ - Returns netdiff in a python dictionary - """ - return { - "added": self._make_diff(self.new_graph, self.old_graph), - "removed": self._make_diff(self.old_graph, self.new_graph) - } - - def diff_json(self, **kwargs): - """ - Returns netdiff in a JSON string - """ - return json.dumps(self.diff(), **kwargs) - - # --- private methods --- # - def _parse(self, topology): """ Converts a topology in a NetworkX MultiGraph object. @@ -50,21 +23,3 @@ def _parse(self, topology): link["destinationIP"], weight=link["tcEdgeCost"]) return graph - - def _make_diff(self, old, new): - """ - calculates differences between topologies 'old' and 'new' - returns a list of links - """ - # make a copy of old topology to avoid tampering with it - diff = old.copy() - not_different = [] - # loop over all links - for edge in old.edges(): - # if link is also in new topology add it to the list - if edge in new.edges(): - not_different.append(edge) - # keep only differences - diff.remove_edges_from(not_different) - # return list of links - return diff.edges() diff --git a/tests/batman/__init__.py b/tests/batman/__init__.py new file mode 100644 index 0000000..ae9afb9 --- /dev/null +++ b/tests/batman/__init__.py @@ -0,0 +1 @@ +from .tests import TestBatmanParser diff --git a/tests/batman/batman-1+1.json b/tests/batman/batman-1+1.json new file mode 100644 index 0000000..f1e0a8e --- /dev/null +++ b/tests/batman/batman-1+1.json @@ -0,0 +1,88 @@ +{ + "source_version" : "2014.3.0", + "algorithm" : 4, + "vis" : [ + { "primary" : "a0:f3:c1:96:94:10", + "neighbors" : [ + { "router" : "a0:f3:c1:96:94:10", + "neighbor" : "90:f6:52:f2:8c:2d", + "metric" : "1.000" } + ], + "clients" : [ + "86:a6:e8:ff:fd:6c", + "64:76:ba:7e:06:44", + "68:72:51:05:79:10", + "86:a6:e8:ff:fd:6c", + "a0:f3:c1:96:94:05" + ] + }, + { "primary" : "a0:f3:c1:ac:6c:44", + "neighbors" : [ + { "router" : "a0:f3:c1:ac:6c:44", + "neighbor" : "10:fe:ed:37:3a:39", + "metric" : "1.000" } + ], + "clients" : [ + "a0:f3:c1:ac:6c:4c", + "dc:9f:db:f1:d7:a9", + "46:1f:b2:8b:c2:a1", + "46:1f:b2:8b:c2:a1" + ] + }, + { "primary" : "90:f6:52:f2:8c:2c", + "secondary" : [ "90:f6:52:f2:8c:2b","90:f6:52:f2:8c:2d" + ], + "neighbors" : [ + { "router" : "90:f6:52:f2:8c:2c", + "neighbor" : "00:05:1c:06:35:8e", + "metric" : "1.000" }, + { "router" : "90:f6:52:f2:8c:2b", + "neighbor" : "10:fe:ed:37:3a:39", + "metric" : "1.000" }, + { "router" : "90:f6:52:f2:8c:2d", + "neighbor" : "a0:f3:c1:96:94:10", + "metric" : "1.000" } + ], + "clients" : [ + "ee:74:50:85:fd:67", + "dc:9f:db:f1:d8:aa", + "68:72:51:05:7c:86", + "90:f6:52:f2:8c:2a", + "ee:74:50:85:fd:67", + "24:a4:3c:6d:fd:81" + ] + }, + { "primary" : "00:05:1c:06:35:8e", + "neighbors" : [ + { "router" : "00:05:1c:06:35:8e", + "neighbor" : "90:f6:52:f2:8c:2c", + "metric" : "1.000" } + ], + "clients" : [ + "00:ff:aa:00:00:01", + "00:18:7d:0b:b3:31", + "52:fe:31:1d:aa:4e", + "24:a4:3c:47:53:9a", + "dc:9f:db:0b:af:b9", + "52:fe:31:1d:aa:4e", + "dc:9f:db:f1:d8:a7" + ] + }, + { "primary" : "10:fe:ed:37:3a:39", + "neighbors" : [ + { "router" : "10:fe:ed:37:3a:39", + "neighbor" : "90:f6:52:f2:8c:2b", + "metric" : "1.000" }, + { "router" : "10:fe:ed:37:3a:39", + "neighbor" : "a0:f3:c1:ac:6c:44", + "metric" : "1.016" } + ], + "clients" : [ + "10:fe:ed:37:3a:38", + "24:a4:3c:6d:fd:ce", + "a6:86:18:b1:00:7b", + "a6:86:18:b1:00:7b" + ] + } + ] +} diff --git a/tests/batman/batman.json b/tests/batman/batman.json new file mode 100644 index 0000000..df642c1 --- /dev/null +++ b/tests/batman/batman.json @@ -0,0 +1,89 @@ +{ + "source_version" : "2014.3.0", + "algorithm" : 4, + "vis" : [ + { "primary" : "a0:f3:c1:96:94:06", + "neighbors" : [ + { "router" : "a0:f3:c1:96:94:06", + "neighbor" : "90:f6:52:f2:8c:2d", + "metric" : "1.000" } + ], + "clients" : [ + "86:a6:e8:ff:fd:6c", + "64:76:ba:7e:06:44", + "68:72:51:05:79:10", + "86:a6:e8:ff:fd:6c", + "a0:f3:c1:96:94:05" + ] + }, + { "primary" : "a0:f3:c1:ac:6c:44", + "neighbors" : [ + { "router" : "a0:f3:c1:ac:6c:44", + "neighbor" : "10:fe:ed:37:3a:39", + "metric" : "1.000" } + ], + "clients" : [ + "a0:f3:c1:ac:6c:4c", + "dc:9f:db:f1:d7:a9", + "46:1f:b2:8b:c2:a1", + "46:1f:b2:8b:c2:a1" + ] + }, + { "primary" : "90:f6:52:f2:8c:2c", + "secondary" : [ "90:f6:52:f2:8c:2b","90:f6:52:f2:8c:2d" + ], + "neighbors" : [ + { "router" : "90:f6:52:f2:8c:2c", + "neighbor" : "00:05:1c:06:35:8e", + "metric" : "1.000" }, + { "router" : "90:f6:52:f2:8c:2b", + "neighbor" : "10:fe:ed:37:3a:39", + "metric" : "1.000" }, + { "router" : "90:f6:52:f2:8c:2d", + "neighbor" : "a0:f3:c1:96:94:06", + "metric" : "1.000" } + ], + "clients" : [ + "ee:74:50:85:fd:67", + "dc:9f:db:f1:d8:aa", + "68:72:51:05:7c:86", + "90:f6:52:f2:8c:2a", + "ee:74:50:85:fd:67", + "24:a4:3c:6d:fd:81" + ] + }, + { "primary" : "00:05:1c:06:35:8e", + "neighbors" : [ + { "router" : "00:05:1c:06:35:8e", + "neighbor" : "90:f6:52:f2:8c:2c", + "metric" : "1.000" } + ], + "clients" : [ + "00:ff:aa:00:00:01", + "00:18:7d:0b:b3:31", + "52:fe:31:1d:aa:4e", + "24:a4:3c:47:53:9a", + "dc:9f:db:0b:af:b9", + "52:fe:31:1d:aa:4e", + "dc:9f:db:f1:d8:a7" + ] + }, + { "primary" : "10:fe:ed:37:3a:39", + "neighbors" : [ + { "router" : "10:fe:ed:37:3a:39", + "neighbor" : "90:f6:52:f2:8c:2b", + "metric" : "1.000" }, + { "router" : "10:fe:ed:37:3a:39", + "neighbor" : "a0:f3:c1:ac:6c:44", + "metric" : "1.016" } + ], + "clients" : [ + "10:fe:ed:37:3a:38", + "24:a4:3c:6d:fd:ce", + "a6:86:18:b1:00:7b", + "a6:86:18:b1:00:7b" + ] + } + ] +} + diff --git a/tests/batman/tests.py b/tests/batman/tests.py new file mode 100644 index 0000000..a2f5551 --- /dev/null +++ b/tests/batman/tests.py @@ -0,0 +1,37 @@ +import os +import unittest +import json +from netdiff.batman import BatmanParser + + +__all__ = ['TestBatmanParser'] + + +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) +iulinet = open('{0}/batman.json'.format(CURRENT_DIR)).read() +iulinet2 = open('{0}/batman-1+1.json'.format(CURRENT_DIR)).read() + + +class TestBatmanParser(unittest.TestCase): + + def test_added_removed_1_node(self): + parser = BatmanParser(old=iulinet, new=iulinet2) + result = parser.diff() + self.assertTrue(type(result) is dict) + self.assertTrue(type(result['added']) is list) + self.assertTrue(type(result['removed']) is list) + # ensure there are no differences + self.assertEqual(len(result['added']), 1) + self.assertEqual(len(result['removed']), 1) + self.assertEqual(result['added'][0], ('a0:f3:c1:96:94:10', '90:f6:52:f2:8c:2c')) + self.assertEqual(result['removed'][0], ('a0:f3:c1:96:94:06', '90:f6:52:f2:8c:2c')) + + def test_no_changes(self): + parser = BatmanParser(old=iulinet, new=iulinet) + result = parser.diff() + self.assertTrue(type(result) is dict) + self.assertTrue(type(result['added']) is list) + self.assertTrue(type(result['removed']) is list) + # ensure there are no differences + self.assertEqual(len(result['added']), 0) + self.assertEqual(len(result['removed']), 0)