From c73b5dcdeaefb37f1d322e35fa7ad736c9cbab3e Mon Sep 17 00:00:00 2001 From: Simon Knight Date: Mon, 13 Jun 2016 16:39:22 +0930 Subject: [PATCH 1/2] thesis modifications --- autonetkit/anm/interface.py | 51 +++++++++++++++++++++++-------------- setup.py | 2 +- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/autonetkit/anm/interface.py b/autonetkit/anm/interface.py index 61ce7ed5..4ec8ac84 100644 --- a/autonetkit/anm/interface.py +++ b/autonetkit/anm/interface.py @@ -19,7 +19,6 @@ def __init__(self, anm, overlay_id, node_id, interface_id): object.__setattr__(self, 'log', logger) self.init_logging("port") - def __key(self): """Note: key doesn't include overlay_id to allow fast cross-layer comparisons""" @@ -32,16 +31,20 @@ def __hash__(self): return hash(self.__key()) - def __deepcopy__(self, memo): - #TODO: workaround - need to fix - # from http://stackoverflow.com/questions/1500718/what-is-the-right-way-to-override-the-copy-deepcopy-operations-on-an-object-in-p - pass - + # TODO: workaround - need to fix + # from + # http://stackoverflow.com/questions/1500718/what-is-the-right-way-to-override-the-copy-deepcopy-operations-on-an-object-in-p + pass def __repr__(self): - description = self.id or self.description - return '%s.%s' % (description, self.node) + try: + description = self.id or self.description + return '%s.%s' % (description, self.node) + except TypeError: + return "" + log.warning("No label for %s %s" % + (self.interface_id, self.node_id)) def __eq__(self, other): return self.__key() == other.__key() @@ -50,6 +53,8 @@ def __nonzero__(self): # TODO: work out why description and category being set/copied to each # overlay + if self.interface_id not in self.node.raw_interfaces: + return False try: interface = self._interface @@ -65,16 +70,16 @@ def __lt__(self, other): return (self.node, self.interface_id) < (other.node, other.interface_id) - @property def id(self): """Returns id of node, falls-through to phy if not set on this overlay """ - #TODO: make generic function for fall-through properties stored on the anm + # TODO: make generic function for fall-through properties stored on the + # anm key = "id" if key in self._interface: - return self._interface.get(key) + return self._interface.get(key) else: if self.overlay_id == "input": return # Don't fall upwards from input -> phy as may not exist @@ -88,10 +93,11 @@ def id(self): return try: - #return self.anm.overlay_nx_graphs['phy'].node[self.node_id]['asn'] + # return + # self.anm.overlay_nx_graphs['phy'].node[self.node_id]['asn'] return self['phy'].id except KeyError: - return # can't get from this overlay or phy -> not found + return # can't get from this overlay or phy -> not found @property def is_bound(self): @@ -120,7 +126,7 @@ def _interface(self): """Return data dict for the interface""" try: return self.node.raw_interfaces[self.interface_id] - except KeyError: + except (KeyError, IndexError): if not self.node_id in self._graph: # node not in overlay return @@ -143,13 +149,13 @@ def __getitem__(self, overlay_id): """Returns corresponding interface in specified overlay""" if not self.anm.has_overlay(overlay_id): - log.warning('Trying to access interface %s for non-existent overlay %s' - , self, overlay_id) + log.warning( + 'Trying to access interface %s for non-existent overlay %s', self, overlay_id) return None if not self.node_id in self.anm.overlay_nx_graphs[overlay_id]: - log.debug('Trying to access interface %s for non-existent node %s in overlay %s' - , self, self.node_id, self.overlay_id) + log.debug('Trying to access interface %s for non-existent node %s in overlay %s', + self, self.node_id, self.overlay_id) return None try: @@ -234,7 +240,14 @@ def get(self, key): """For consistency, node.get(key) is neater than getattr(interface, key)""" - return getattr(self, key) + try: + return getattr(self, key) + except AttributeError, exc: + if self._interface is None: + raise KeyError("Interface %s not found on node %s" % + (self.interface_id, self.node_id)) + + raise exc def __setattr__(self, key, val): """Sets interface property""" diff --git a/setup.py b/setup.py index 281001d7..b173e82c 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ download_url = ("http://pypi.python.org/pypi/autonetkit"), install_requires = [ - 'netaddr==0.7.10', + 'netaddr>=0.7.10', 'mako==0.8', 'networkx==1.7', 'configobj==4.7.1', From e5b390ca96a295dcb2a1235421e4e2b8aba6991d Mon Sep 17 00:00:00 2001 From: simonknight Date: Wed, 28 Apr 2021 21:02:18 +0930 Subject: [PATCH 2/2] rework to python3 --- .bumpversion.cfg | 6 - .gitignore | 115 +- LICENCE.txt | 2 +- MANIFEST.in | 7 +- README.md | 234 +- ank query language.ipynb | 1055 - autonetkit/Find Results | 343 - autonetkit/__init__.py | 9 - autonetkit/ank.py | 819 - autonetkit/ank_json.py | 426 - autonetkit/ank_messaging.py | 142 - autonetkit/ank_utils.py | 110 - autonetkit/ank_validate.py | 196 - autonetkit/anm.py | 36 - autonetkit/anm/__init__.py | 5 - autonetkit/anm/ank_element.py | 47 - autonetkit/anm/base.py | 517 - autonetkit/anm/edge.py | 348 - autonetkit/anm/graph.py | 481 - autonetkit/anm/graph_data.py | 46 - autonetkit/anm/interface.py | 285 - autonetkit/anm/network_model.py | 265 - autonetkit/anm/node.py | 748 - autonetkit/anm/subgraph.py | 17 - autonetkit/anm/tests/test_anm.py | 207 - autonetkit/anm/tests/test_base.py | 3 - autonetkit/anm/tests/test_multi_edge.py | 136 - autonetkit/anm/tests/test_network_model.py | 22 - autonetkit/build_network.py | 317 - autonetkit/build_wheel.py | 0 autonetkit/collection/__init__.py | 0 autonetkit/collection/process.py | 222 - autonetkit/collection/server.py | 216 - autonetkit/collection/utils.py | 67 - autonetkit/collection/verify.py | 93 - autonetkit/compiler.py | 20 - autonetkit/compilers/__init__.py | 0 autonetkit/compilers/device/__init__.py | 0 autonetkit/compilers/device/cisco.py | 913 - autonetkit/compilers/device/device_base.py | 9 - autonetkit/compilers/device/quagga.py | 64 - autonetkit/compilers/device/router_base.py | 519 - autonetkit/compilers/device/server_base.py | 14 - autonetkit/compilers/device/ubuntu.py | 135 - autonetkit/compilers/platform/__init__.py | 0 autonetkit/compilers/platform/cisco.py | 402 - autonetkit/compilers/platform/netkit.py | 173 - .../compilers/platform/platform_base.py | 101 - autonetkit/config.py | 45 - autonetkit/config/configspec.cfg | 109 - autonetkit/console_script.py | 199 - autonetkit/deploy/__init__.py | 0 autonetkit/deploy/netkit.py | 234 - autonetkit/design/__init__.py | 0 autonetkit/design/bgp.py | 262 - autonetkit/design/igp.py | 324 - autonetkit/design/ip.py | 74 - autonetkit/design/ip_addressing/__init__.py | 0 autonetkit/design/ip_addressing/ipv4.py | 335 - autonetkit/design/ip_addressing/ipv6.py | 329 - autonetkit/design/layer1.py | 90 - autonetkit/design/layer2.py | 350 - autonetkit/design/mpls.py | 344 - autonetkit/design/osi_layers.py | 36 - autonetkit/design/tests/test_igp.py | 130 - autonetkit/diff.py | 151 - autonetkit/example.py | 87 - autonetkit/exception.py | 17 - autonetkit/load/__init__.py | 0 autonetkit/load/graphml.py | 266 - autonetkit/load/initialize.py | 8 - autonetkit/load/load_json.py | 169 - autonetkit/log.py | 41 - autonetkit/log2.py | 25 - autonetkit/nidb/__init__.py | 5 - autonetkit/nidb/base.py | 339 - autonetkit/nidb/config_stanza.py | 81 - autonetkit/nidb/device_model.py | 137 - autonetkit/nidb/edge.py | 144 - autonetkit/nidb/interface.py | 190 - autonetkit/nidb/node.py | 284 - autonetkit/plugins/__init__.py | 0 autonetkit/plugins/ipv4.py | 687 - autonetkit/plugins/ipv6.py | 152 - autonetkit/plugins/naming.py | 9 - autonetkit/render.py | 301 - autonetkit/render2.py | 416 - autonetkit/templates/dynagen.mako | 30 - autonetkit/templates/ios.mako | 476 - autonetkit/templates/linux/static_route.mako | 8 - autonetkit/templates/netkit_lab_conf.mako | 15 - autonetkit/templates/netkit_startup.mako | 25 - autonetkit/templates/quagga.conf.mako | 116 - autonetkit/templates/quagga/etc/hostname.mako | 1 - autonetkit/templates/quagga/etc/shadow | 36 - .../templates/quagga/etc/ssh/sshd_config | 2 - .../templates/quagga/etc/zebra/bgpd.conf.mako | 61 - .../templates/quagga/etc/zebra/daemons.mako | 42 - .../quagga/etc/zebra/isisd.conf.mako | 27 - .../templates/quagga/etc/zebra/motd.txt.mako | 3 - .../quagga/etc/zebra/ospfd.conf.mako | 26 - .../quagga/etc/zebra/zebra.conf.mako | 20 - .../quagga/root/.ssh/authorized_keys | 2 - .../Untitled0-checkpoint.ipynb | 9 - autonetkit/topologies/__init__.py | 5 - autonetkit/topologies/four_chain_topology.py | 30 - autonetkit/topologies/graphml2json.py | 20 - autonetkit/topologies/house_topology.py | 88 - autonetkit/topologies/mixed_topology.py | 85 - autonetkit/topologies/multi_as.json | 0 autonetkit/topologies/multi_as_topology.py | 378 - autonetkit/topologies/multi_edge.json | 244 - autonetkit/topologies/multi_edge_topology.py | 112 - autonetkit/untitled | 238 - autonetkit/webserver.py | 424 - autonetkit/workflow.py | 264 - autonetkit/yaml_utils.py | 27 - changelog.html | 4399 --- changelog.rst | 4574 --- docs/Makefile | 177 - docs/autonetkit.anm.rst | 78 - docs/autonetkit.collection.rst | 46 - docs/autonetkit.compilers.device.rst | 62 - docs/autonetkit.compilers.platform.rst | 54 - docs/autonetkit.compilers.rst | 18 - docs/autonetkit.deploy.rst | 22 - docs/autonetkit.design.rst | 54 - docs/autonetkit.load.rst | 30 - docs/autonetkit.nidb.rst | 62 - docs/autonetkit.plugins.rst | 54 - docs/autonetkit.rst | 164 - docs/conf.py | 265 - docs/extending.rst | 405 - docs/images/four_chain.png | Bin 32052 -> 0 bytes docs/images/house.png | Bin 40013 -> 0 bytes docs/images/mixed.png | Bin 35553 -> 0 bytes docs/images/multi_as.png | Bin 63487 -> 0 bytes docs/images/multi_edge.png | Bin 47749 -> 0 bytes docs/index.rst | 30 - docs/modules.rst | 7 - docs/topologies.rst | 23 - example/extending.html | 2835 -- example/extending.ipynb | 1109 - example/house.json | 163 - example/small_internet.graphml | 538 - house.json | 180 - junit-py27.xml | 28 - requirements.txt | 6 + setup.py | 96 +- tests/Aarnet.graphml | 317 - tests/asn_zero.graphml | 826 - tests/bgp_pol/policy_0.txt | 14 - tests/bgp_pol/policy_1.txt | 16 - tests/bgp_pol/policy_2.txt | 16 - tests/bgp_pol/policy_3.txt | 24 - tests/bgp_pol/policy_4.txt | 13 - tests/bgp_pol/policy_5.txt | 10 - tests/bgp_pol/test_bgp_pol.py | 22 - tests/big.graphml | 2648 -- tests/bigger.graphml | 28925 ---------------- tests/blank_labels.graphml | 829 - tests/deploy/test_deploy.py | 46 - tests/diff/expected_diff.json | 1 - tests/diff/result.json | 1 - tests/diff/test_diff.py | 97 - tests/duplicate_labels.graphml | 826 - tests/house.json | 180 - tests/multigraph.graphml | 820 - tests/small_internet.graphml | 538 - tests/small_internet/as1r1_bgpd.conf | 31 - tests/small_internet/as1r1_ospfd.conf | 16 - tests/small_internet/as1r1_zebra.conf | 13 - tests/small_internet/as20r3_bgpd.conf | 35 - tests/small_internet/as20r3_ospfd.conf | 22 - tests/small_internet/as20r3_zebra.conf | 13 - tests/small_internet/lab.conf | 57 - tests/small_internet/small_internet.graphml | 538 - tests/small_internet/test_small_internet.py | 88 - tests/test_2.py | 28 - tests/test_console_script.py | 24 - tests/test_graphml.py | 75 - tests/test_json_load.py | 21 - tests/test_serialization.py | 45 - tests/webserver/expected_nidb.json | 1 - tests/webserver/expected_phy.json | 1 - tests/webserver/test_webserver.py | 72 - tox.ini | 7 - 187 files changed, 231 insertions(+), 71580 deletions(-) delete mode 100644 .bumpversion.cfg delete mode 100644 ank query language.ipynb delete mode 100644 autonetkit/Find Results delete mode 100644 autonetkit/ank.py delete mode 100644 autonetkit/ank_json.py delete mode 100644 autonetkit/ank_messaging.py delete mode 100644 autonetkit/ank_utils.py delete mode 100644 autonetkit/ank_validate.py delete mode 100644 autonetkit/anm.py delete mode 100644 autonetkit/anm/__init__.py delete mode 100644 autonetkit/anm/ank_element.py delete mode 100644 autonetkit/anm/base.py delete mode 100644 autonetkit/anm/edge.py delete mode 100644 autonetkit/anm/graph.py delete mode 100644 autonetkit/anm/graph_data.py delete mode 100644 autonetkit/anm/interface.py delete mode 100644 autonetkit/anm/network_model.py delete mode 100644 autonetkit/anm/node.py delete mode 100644 autonetkit/anm/subgraph.py delete mode 100644 autonetkit/anm/tests/test_anm.py delete mode 100644 autonetkit/anm/tests/test_base.py delete mode 100644 autonetkit/anm/tests/test_multi_edge.py delete mode 100644 autonetkit/anm/tests/test_network_model.py delete mode 100644 autonetkit/build_network.py delete mode 100644 autonetkit/build_wheel.py delete mode 100644 autonetkit/collection/__init__.py delete mode 100644 autonetkit/collection/process.py delete mode 100644 autonetkit/collection/server.py delete mode 100644 autonetkit/collection/utils.py delete mode 100644 autonetkit/collection/verify.py delete mode 100644 autonetkit/compiler.py delete mode 100644 autonetkit/compilers/__init__.py delete mode 100644 autonetkit/compilers/device/__init__.py delete mode 100644 autonetkit/compilers/device/cisco.py delete mode 100644 autonetkit/compilers/device/device_base.py delete mode 100644 autonetkit/compilers/device/quagga.py delete mode 100644 autonetkit/compilers/device/router_base.py delete mode 100644 autonetkit/compilers/device/server_base.py delete mode 100644 autonetkit/compilers/device/ubuntu.py delete mode 100644 autonetkit/compilers/platform/__init__.py delete mode 100644 autonetkit/compilers/platform/cisco.py delete mode 100644 autonetkit/compilers/platform/netkit.py delete mode 100644 autonetkit/compilers/platform/platform_base.py delete mode 100644 autonetkit/config.py delete mode 100644 autonetkit/config/configspec.cfg delete mode 100644 autonetkit/console_script.py delete mode 100644 autonetkit/deploy/__init__.py delete mode 100644 autonetkit/deploy/netkit.py delete mode 100644 autonetkit/design/__init__.py delete mode 100644 autonetkit/design/bgp.py delete mode 100644 autonetkit/design/igp.py delete mode 100644 autonetkit/design/ip.py delete mode 100644 autonetkit/design/ip_addressing/__init__.py delete mode 100644 autonetkit/design/ip_addressing/ipv4.py delete mode 100644 autonetkit/design/ip_addressing/ipv6.py delete mode 100644 autonetkit/design/layer1.py delete mode 100644 autonetkit/design/layer2.py delete mode 100644 autonetkit/design/mpls.py delete mode 100644 autonetkit/design/osi_layers.py delete mode 100644 autonetkit/design/tests/test_igp.py delete mode 100644 autonetkit/diff.py delete mode 100644 autonetkit/example.py delete mode 100644 autonetkit/exception.py delete mode 100644 autonetkit/load/__init__.py delete mode 100644 autonetkit/load/graphml.py delete mode 100644 autonetkit/load/initialize.py delete mode 100644 autonetkit/load/load_json.py delete mode 100644 autonetkit/log.py delete mode 100644 autonetkit/log2.py delete mode 100644 autonetkit/nidb/__init__.py delete mode 100644 autonetkit/nidb/base.py delete mode 100644 autonetkit/nidb/config_stanza.py delete mode 100644 autonetkit/nidb/device_model.py delete mode 100644 autonetkit/nidb/edge.py delete mode 100644 autonetkit/nidb/interface.py delete mode 100644 autonetkit/nidb/node.py delete mode 100644 autonetkit/plugins/__init__.py delete mode 100644 autonetkit/plugins/ipv4.py delete mode 100644 autonetkit/plugins/ipv6.py delete mode 100644 autonetkit/plugins/naming.py delete mode 100644 autonetkit/render.py delete mode 100644 autonetkit/render2.py delete mode 100644 autonetkit/templates/dynagen.mako delete mode 100644 autonetkit/templates/ios.mako delete mode 100644 autonetkit/templates/linux/static_route.mako delete mode 100644 autonetkit/templates/netkit_lab_conf.mako delete mode 100644 autonetkit/templates/netkit_startup.mako delete mode 100644 autonetkit/templates/quagga.conf.mako delete mode 100644 autonetkit/templates/quagga/etc/hostname.mako delete mode 100644 autonetkit/templates/quagga/etc/shadow delete mode 100644 autonetkit/templates/quagga/etc/ssh/sshd_config delete mode 100644 autonetkit/templates/quagga/etc/zebra/bgpd.conf.mako delete mode 100644 autonetkit/templates/quagga/etc/zebra/daemons.mako delete mode 100644 autonetkit/templates/quagga/etc/zebra/isisd.conf.mako delete mode 100644 autonetkit/templates/quagga/etc/zebra/motd.txt.mako delete mode 100644 autonetkit/templates/quagga/etc/zebra/ospfd.conf.mako delete mode 100644 autonetkit/templates/quagga/etc/zebra/zebra.conf.mako delete mode 100644 autonetkit/templates/quagga/root/.ssh/authorized_keys delete mode 100644 autonetkit/topologies/.ipynb_checkpoints/Untitled0-checkpoint.ipynb delete mode 100644 autonetkit/topologies/__init__.py delete mode 100644 autonetkit/topologies/four_chain_topology.py delete mode 100644 autonetkit/topologies/graphml2json.py delete mode 100644 autonetkit/topologies/house_topology.py delete mode 100644 autonetkit/topologies/mixed_topology.py delete mode 100644 autonetkit/topologies/multi_as.json delete mode 100644 autonetkit/topologies/multi_as_topology.py delete mode 100644 autonetkit/topologies/multi_edge.json delete mode 100644 autonetkit/topologies/multi_edge_topology.py delete mode 100644 autonetkit/untitled delete mode 100644 autonetkit/webserver.py delete mode 100644 autonetkit/workflow.py delete mode 100644 autonetkit/yaml_utils.py delete mode 100644 changelog.html delete mode 100644 changelog.rst delete mode 100644 docs/Makefile delete mode 100644 docs/autonetkit.anm.rst delete mode 100644 docs/autonetkit.collection.rst delete mode 100644 docs/autonetkit.compilers.device.rst delete mode 100644 docs/autonetkit.compilers.platform.rst delete mode 100644 docs/autonetkit.compilers.rst delete mode 100644 docs/autonetkit.deploy.rst delete mode 100644 docs/autonetkit.design.rst delete mode 100644 docs/autonetkit.load.rst delete mode 100644 docs/autonetkit.nidb.rst delete mode 100644 docs/autonetkit.plugins.rst delete mode 100644 docs/autonetkit.rst delete mode 100644 docs/conf.py delete mode 100644 docs/extending.rst delete mode 100644 docs/images/four_chain.png delete mode 100644 docs/images/house.png delete mode 100644 docs/images/mixed.png delete mode 100644 docs/images/multi_as.png delete mode 100644 docs/images/multi_edge.png delete mode 100644 docs/index.rst delete mode 100644 docs/modules.rst delete mode 100644 docs/topologies.rst delete mode 100644 example/extending.html delete mode 100644 example/extending.ipynb delete mode 100644 example/house.json delete mode 100644 example/small_internet.graphml delete mode 100644 house.json delete mode 100644 junit-py27.xml create mode 100644 requirements.txt delete mode 100644 tests/Aarnet.graphml delete mode 100644 tests/asn_zero.graphml delete mode 100755 tests/bgp_pol/policy_0.txt delete mode 100755 tests/bgp_pol/policy_1.txt delete mode 100755 tests/bgp_pol/policy_2.txt delete mode 100755 tests/bgp_pol/policy_3.txt delete mode 100755 tests/bgp_pol/policy_4.txt delete mode 100755 tests/bgp_pol/policy_5.txt delete mode 100755 tests/bgp_pol/test_bgp_pol.py delete mode 100644 tests/big.graphml delete mode 100644 tests/bigger.graphml delete mode 100644 tests/blank_labels.graphml delete mode 100644 tests/deploy/test_deploy.py delete mode 100644 tests/diff/expected_diff.json delete mode 100644 tests/diff/result.json delete mode 100644 tests/diff/test_diff.py delete mode 100644 tests/duplicate_labels.graphml delete mode 100644 tests/house.json delete mode 100644 tests/multigraph.graphml delete mode 100644 tests/small_internet.graphml delete mode 100644 tests/small_internet/as1r1_bgpd.conf delete mode 100644 tests/small_internet/as1r1_ospfd.conf delete mode 100644 tests/small_internet/as1r1_zebra.conf delete mode 100644 tests/small_internet/as20r3_bgpd.conf delete mode 100644 tests/small_internet/as20r3_ospfd.conf delete mode 100644 tests/small_internet/as20r3_zebra.conf delete mode 100644 tests/small_internet/lab.conf delete mode 100644 tests/small_internet/small_internet.graphml delete mode 100644 tests/small_internet/test_small_internet.py delete mode 100644 tests/test_2.py delete mode 100644 tests/test_console_script.py delete mode 100644 tests/test_graphml.py delete mode 100644 tests/test_json_load.py delete mode 100644 tests/test_serialization.py delete mode 100644 tests/webserver/expected_nidb.json delete mode 100644 tests/webserver/expected_phy.json delete mode 100644 tests/webserver/test_webserver.py delete mode 100644 tox.ini diff --git a/.bumpversion.cfg b/.bumpversion.cfg deleted file mode 100644 index c6c6a218..00000000 --- a/.bumpversion.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[bumpversion] -current_version = 0.11.0 -files = setup.py -commit = True -tag = True - diff --git a/.gitignore b/.gitignore index 0190b8c3..e1605b97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,117 +1,8 @@ - .DS_Store build/* - -*.swp - -rendered/* -versions/* - *.pyc - doc/build - -*.graphml - -*.gz - -*.log - -autonetkit.log.* -autonetkit.cfg - -erl_crash.dump - -autonetkit_v3.egg-info/* - -test.xml -output.xml - dist/* - -diff.json - -*.virl - -*.pickle -*.gml - -autonetkit.egg-info/* - -example/rendered/* -build/lib/autonetkit/__init__.py -autonetkit/example/rendered/* -example/rendered/* - -cache/* - -test.* -*.graphml* - -.coverage -htmlcov/* -.tox/* -*.sublime-project - -*.sublime-workspace - -tests/small_internet/rendered/* -tests/diff/rendered/* -tests/rendered/* -tests/deploy/rendered/* - -.coverage.gw0 - -example/build.py - -example/compile.py - -example/diff.py - -example/replay_measurement.py - -example/console_script.py - -icon.graffle - -rsync.sh - -icon.pdf - -cleanup_nren.py - -run_tests.sh - -*.yaml - -.ipynb_checkpoints/Untitled0-checkpoint.ipynb - -Untitled0.ipynb - -example/.ipynb_checkpoints/Extending AutoNetkit-checkpoint.ipynb - -example/.ipynb_checkpoints/Extending AutoNetkit-checkpoint.ipynb - -example/router.mako - -example/topology.mako - - -pycallgraph2.png - -pycallgraph.png - -*.conf - -example.json -tests/multi.json - -docs/_build/* - -tox.ini - -cover/* -docs/html/* -setup_tornado4.py - -multi.json +/autonetkit/tests/output/ +/autonetkit/output/ +/autonetkit/tutorial/output/ diff --git a/LICENCE.txt b/LICENCE.txt index e08017b3..a387fc1a 100644 --- a/LICENCE.txt +++ b/LICENCE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2012-2015 AutoNetkit authors +Copyright (c) 2012-2021 AutoNetkit authors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/MANIFEST.in b/MANIFEST.in index c18bec21..54fa3bae 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ -include autonetkit/templates/*.mako -recursive-include autonetkit/templates/quagga * -recursive-include autonetkit/templates/linux * -include autonetkit/config/configspec.cfg +recursive-include autonetkit/render/templates/* +recursive-include autonetkit/webserver/static/* +recursive-include autonetkit/webserver/templates/* diff --git a/README.md b/README.md index ceb57353..34aa0079 100644 --- a/README.md +++ b/README.md @@ -33,59 +33,179 @@ AutoNetkit 0.9 allows for JSON input. An example JSON input is: ```JSON { - "directed": false, "graph": [], "multigraph": false, - "links": [ - {"dst": "r2", "dst_port": "eth0", "src": "r1", "src_port": "eth0"}, - {"dst": "r3", "dst_port": "eth0", "src": "r1", "src_port": "eth1"}, - {"dst": "r3", "dst_port": "eth1", "src": "r2", "src_port": "eth1"}, - {"dst": "r2", "dst_port": "eth2", "src": "r4", "src_port": "eth0"}, - {"dst": "r5", "dst_port": "eth0", "src": "r4", "src_port": "eth1"}, - {"dst": "r3", "dst_port": "eth2", "src": "r5", "src_port": "eth1"} - ], - "nodes": [ + "directed": false, + "graph": [], + "multigraph": false, + "links": [ { - "asn": 1, "device_type": "router", "id": "r1", "x": 350, "y": 400, - "ports": [ - {"category": "loopback", "description": null, "id": "Loopback0"}, - {"category": "physical", "description": "r1 to r2", "id": "eth0"}, - {"category": "physical", "description": "r1 to r3", "id": "eth1"} - ] + "dst": "r2", + "dst_port": "eth0", + "src": "r1", + "src_port": "eth0" }, { - "asn": 1, "device_type": "router", "id": "r2", "x": 500, "y": 300, - "ports": [ - {"category": "loopback", "description": null, "id": "Loopback0"}, - {"category": "physical", "description": "r2 to r1", "id": "eth0"}, - {"category": "physical", "description": "r2 to r3", "id": "eth1"}, - {"category": "physical", "description": "r2 to r4", "id": "eth2"} - ] + "dst": "r3", + "dst_port": "eth0", + "src": "r1", + "src_port": "eth1" }, { - "asn": 1, "device_type": "router", "id": "r3", "x": 500, "y": 500, - "ports": [ - {"category": "loopback", "description": null, "id": "Loopback0"}, - {"category": "physical", "description": "r3 to r1", "id": "eth0"}, - {"category": "physical", "description": "r3 to r2", "id": "eth1"}, - {"category": "physical", "description": "r3 to r5", "id": "eth2"} - ] + "dst": "r3", + "dst_port": "eth1", + "src": "r2", + "src_port": "eth1" }, { - "asn": 2, "device_type": "router", "id": "r4", "x": 675, "y": 300, - "ports": [ - {"category": "loopback", "description": null, "id": "Loopback0"}, - {"category": "physical", "description": "r4 to r2", "id": "eth0"}, - {"category": "physical", "description": "r4 to r5", "id": "eth1"} - ] + "dst": "r2", + "dst_port": "eth2", + "src": "r4", + "src_port": "eth0" }, { - "asn": 2, "device_type": "router", "id": "r5", "x": 675, "y": 500, - "ports": [ - {"category": "loopback", "description": null, "id": "Loopback0"}, - {"category": "physical", "description": "r5 to r4", "id": "eth0"}, - {"category": "physical", "description": "r5 to r3", "id": "eth1"} - ] + "dst": "r5", + "dst_port": "eth0", + "src": "r4", + "src_port": "eth1" + }, + { + "dst": "r3", + "dst_port": "eth2", + "src": "r5", + "src_port": "eth1" } - ] + ], + "nodes": [ + { + "asn": 1, + "device_type": "router", + "id": "r1", + "x": 350, + "y": 400, + "ports": [ + { + "category": "loopback", + "description": null, + "id": "Loopback0" + }, + { + "category": "physical", + "description": "r1 to r2", + "id": "eth0" + }, + { + "category": "physical", + "description": "r1 to r3", + "id": "eth1" + } + ] + }, + { + "asn": 1, + "device_type": "router", + "id": "r2", + "x": 500, + "y": 300, + "ports": [ + { + "category": "loopback", + "description": null, + "id": "Loopback0" + }, + { + "category": "physical", + "description": "r2 to r1", + "id": "eth0" + }, + { + "category": "physical", + "description": "r2 to r3", + "id": "eth1" + }, + { + "category": "physical", + "description": "r2 to r4", + "id": "eth2" + } + ] + }, + { + "asn": 1, + "device_type": "router", + "id": "r3", + "x": 500, + "y": 500, + "ports": [ + { + "category": "loopback", + "description": null, + "id": "Loopback0" + }, + { + "category": "physical", + "description": "r3 to r1", + "id": "eth0" + }, + { + "category": "physical", + "description": "r3 to r2", + "id": "eth1" + }, + { + "category": "physical", + "description": "r3 to r5", + "id": "eth2" + } + ] + }, + { + "asn": 2, + "device_type": "router", + "id": "r4", + "x": 675, + "y": 300, + "ports": [ + { + "category": "loopback", + "description": null, + "id": "Loopback0" + }, + { + "category": "physical", + "description": "r4 to r2", + "id": "eth0" + }, + { + "category": "physical", + "description": "r4 to r5", + "id": "eth1" + } + ] + }, + { + "asn": 2, + "device_type": "router", + "id": "r5", + "x": 675, + "y": 500, + "ports": [ + { + "category": "loopback", + "description": null, + "id": "Loopback0" + }, + { + "category": "physical", + "description": "r5 to r4", + "id": "eth0" + }, + { + "category": "physical", + "description": "r5 to r3", + "id": "eth1" + } + ] + } + ] } ``` @@ -101,7 +221,6 @@ Examples of topology files can be found in the example directory. INFO Rendering Configuration Files INFO Finished - This will generate Quagga configurations and Netkit topology files.: $ tree rendered/localhost/netkit/ @@ -148,9 +267,8 @@ and an example of the resulting configuration file: network 192.168.0.1/32 area 0 ! - - ## Visualization + You can start the visualization webserver, and topologies will automatically be sent: $ ank_webserver --ank_vis @@ -165,7 +283,6 @@ Physical topology with interfaces: ![phy interfaces](http://sk2.github.io/autonetkit/json_house/phy_int.png) - IPv4 topology: ![ipv4](http://sk2.github.io/autonetkit/json_house/ipv4.png) @@ -182,10 +299,10 @@ eBGP topology: ![ebgp](http://sk2.github.io/autonetkit/json_house/ebgp.png) - ## Extending -A tutorial on extending using the API can be found [here](http://sk2.github.io/autonetkit/tutorial/extending.html) or as an iPython Notebook [here](http://sk2.github.io/autonetkit/tutorial/extending.ipynb). +A tutorial on extending using the API can be found [here](http://sk2.github.io/autonetkit/tutorial/extending.html) or as +an iPython Notebook [here](http://sk2.github.io/autonetkit/tutorial/extending.ipynb). # Users @@ -195,16 +312,17 @@ Users from industry, academia, and university teaching. More information on AutoNetkit: -* [AutoNetkit YouTube Channel](http://www.youtube.com/autonetkit) -* [CoNEXT 2013 Slides](https://db.tt/JkRrU5q5) (Dec 13) -* [CoNext 2013 Conference Paper](http://conferences.sigcomm.org/co-next/2013/program/p235.pdf) -* [Extended recording of CoNext Slides](https://www.youtube.com/watch?v=0W73HLdlwOs) -* [PyCon Australia 2013 Presentation on Autonetkit](http://t.co/H4NWROoAJK) [(Slides)](http://t.co/x0NXLMATEq) (July 13) +* [AutoNetkit YouTube Channel](http://www.youtube.com/autonetkit) +* [CoNEXT 2013 Slides](https://db.tt/JkRrU5q5) (Dec 13) +* [CoNext 2013 Conference Paper](http://conferences.sigcomm.org/co-next/2013/program/p235.pdf) +* [Extended recording of CoNext Slides](https://www.youtube.com/watch?v=0W73HLdlwOs) +* [PyCon Australia 2013 Presentation on Autonetkit](http://t.co/H4NWROoAJK) [(Slides)](http://t.co/x0NXLMATEq) (July 13) -* [API Documentation](https://autonetkit.readthedocs.org/) -* [AutoNetkit website](http://www.autonetkit.org) +* [API Documentation](https://autonetkit.readthedocs.org/) +* [AutoNetkit website](http://www.autonetkit.org) # Contact -* [Twitter](https://twitter.com/autonetkit) -* [Mailing list](https://groups.google.com/group/autonetkit) + +* [Twitter](https://twitter.com/autonetkit) +* [Mailing list](https://groups.google.com/group/autonetkit) diff --git a/ank query language.ipynb b/ank query language.ipynb deleted file mode 100644 index 1beb2deb..00000000 --- a/ank query language.ipynb +++ /dev/null @@ -1,1055 +0,0 @@ -{ - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from pyparsing import Word, alphas, nums, alphanums, Suppress, OneOrMore, Group, ZeroOrMore, Literal\n", - "\n", - "# create table overlay (directed False, multi_edge False)\n", - "overlay = Word(alphas + \"_\")(\"overlay\")\n", - "category = (Literal(\"nodes\")(\"nodes\") ^ Literal(\"edges\")(\"edges\") ^ Literal(\"ports\")(\"ports\"))\n", - "overlay_with_category = (overlay + Suppress(\".\") + category)\n", - "\n", - "lbrack = Suppress(\"(\")\n", - "rbrack = Suppress(\")\")\n", - "sqoute = Suppress(\"'\")\n", - "\n", - "#TODO: look for quoted\n", - "#TODO: define comma suppressed literal\n", - "\n", - "\n", - "create_overlay = (Suppress(\"CREATE OVERLAY\") + overlay)(\"create_overlay\")\n", - "\n", - "value = Word(alphanums + \"_\" + \"-\" + \".\")\n", - "node_keys = Group(value + ZeroOrMore(Suppress(\",\") + value))(\"keys\")\n", - "node_values = Group(sqoute + value + sqoute + ZeroOrMore(Suppress(\",\") + sqoute + value + sqoute))(\"values\")\n", - "\n", - "insert_node = (Suppress(\"INSERT INTO\") + overlay + Suppress(\".\") + Suppress(\"nodes\") + lbrack \n", - " + node_keys + rbrack + \"VALUES\" + lbrack + node_values + rbrack)(\"query\")\n", - "\n", - "\n", - "\n", - "#####\n", - "\n", - "def fn_insert_edge(strg, loc, toks):\n", - " data = toks.asDict()\n", - " # data.get(\"query\").get(\"values\")\n", - " src = data.get(\"src\")\n", - " dst = data.get(\"dst\")\n", - " overlay = data.get(\"overlay\")\n", - " return {\"action\": \"insert_edge\", \"overlay\": overlay,\n", - " \"src\": src, \"dst\": dst}\n", - " \n", - "\n", - "edge_keys = Suppress(\"src, dst\") # TODO: later allow insert by ports too\n", - "edge_values = sqoute + value(\"src\") + sqoute + Suppress(\",\") + sqoute + value(\"dst\") + sqoute\n", - "insert_edge = (Suppress(\"INSERT INTO\") + overlay + Suppress(\".\") + Suppress(\"edges\") \n", - " + lbrack + edge_keys + rbrack + \"VALUES\" + lbrack + edge_values.setResultsName(\"values\") + rbrack)(\"query\").setParseAction(fn_insert_edge)\n", - "\n", - "where_or_set_element = Group(value.setResultsName(\"key\") + Suppress(\"=\") + value.setResultsName(\"value\"))\n", - "where_clause = Group(where_or_set_element + ZeroOrMore(Suppress(\"AND\") + where_or_set_element))(\"where\")\n", - "\n", - "set_clause = Group(where_or_set_element + ZeroOrMore(Suppress(\",\") + where_or_set_element))(\"set\")\n", - "\n", - "\n", - "def fn_delete_edge(strg, loc, toks):\n", - " data = toks.asDict()\n", - " print \"keys\", data.keys()\n", - " print data.get(\"where\")\n", - " overlay = data.get(\"overlay\")\n", - " where = data.get(\"where\")\n", - " querystring = {x.get(\"key\"): x.get(\"value\") for x in where}\n", - " print querystring\n", - " #TODO: extract out src, dst, rest of the query string\n", - " return {\"action\": \"delete_edge\", \"overlay\": overlay, \"query\": querystring}\n", - "\n", - "def fn_update_edge(strg, loc, toks):\n", - " data = toks.asDict()\n", - " overlay = data.get(\"overlay\")\n", - " where = data.get(\"where\")\n", - " querystring = {x.get(\"key\"): x.get(\"value\") for x in where}\n", - " set_clause = data.get(\"set\")\n", - " setstring = {x.get(\"key\"): x.get(\"value\") for x in set_clause}\n", - " #TODO: extract out src, dst, rest of the query string\n", - " return {\"action\": \"update_edge\", \"overlay\": overlay, \"query\": querystring, \"set\": setstring}\n", - "\n", - " \n", - "#TODO: also allow edge sets composed of node queries\n", - "# TODO: allow the parser to be used inside ANK as well (from Python)\n", - "delete_edge = (Suppress(\"DELETE FROM\") + overlay + Suppress(\".\") + Suppress(\"edges\") \n", - " + \"WHERE\" + lbrack + where_clause + rbrack)(\"query\").setParseAction(fn_delete_edge)\n", - "\n", - "update_edge = (Suppress(\"UPDATE\") + overlay + Suppress(\".\") + Suppress(\"edges\") \n", - " + \"SET\" + set_clause\n", - " + \"WHERE\" + lbrack + where_clause + rbrack)(\"query\").setParseAction(fn_update_edge)\n", - "\n", - "\n", - "query = (create_overlay ^ insert_node ^ insert_edge ^ delete_edge ^ update_edge)\n", - "\n", - "queries = [\n", - " #\"CREATE OVERLAY ospf\",\n", - " #\"CREATE OVERLAY ospf_live\",\n", - " # \"INSERT INTO ospf.nodes (id) VALUES ('a', 'b');\",\n", - " #\"INSERT INTO ospf.nodes (id) VALUES ('1');\",\n", - " #\"INSERT INTO ospf.nodes (id, asn, area) VALUES ('1','2','3');\"\n", - " #\"INSERT INTO ospf.nodes (id) VALUES ('1');\",\n", - " \"DELETE FROM ospf.edges WHERE (src=iosv-7) ;\",\n", - " \"UPDATE ospf.edges SET abc=123,def=456 WHERE (src=iosv-7) ;\",\n", - "\n", - " \"INSERT INTO ospf.edges (src, dst) VALUES ('iosv-6', 'iosv-7');\",\n", - " #\"DELETE FROM ospf.edges WHERE (type=physical) ;\",\n", - "\n", - " ]\n", - "\n", - "# set results parse action\n", - "\n", - "results = []\n", - "for q in queries:\n", - " import pprint\n", - " result = query.parseString(q).asDict()\n", - " #pprint.pprint(result)\n", - " print\n", - " print result\n", - " results.append(result)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "keys ['query', 'where', 'overlay']\n", - "[['src', 'iosv-7']]\n", - "{'src': 'iosv-7'}\n", - "\n", - "{'query': {'action': 'delete_edge', 'query': {'src': 'iosv-7'}, 'overlay': 'ospf'}}\n", - "keys ['query', 'set', 'where', 'overlay']\n", - "[['src', 'iosv-7']]\n", - "\n", - "{'query': {'action': 'update_edge', 'query': {'src': 'iosv-7'}, 'set': {'abc': '123', 'def': '456'}, 'overlay': 'ospf'}}\n", - "\n", - "{'query': {'action': 'insert_edge', 'src': 'iosv-6', 'dst': 'iosv-7', 'overlay': 'ospf'}}\n" - ] - } - ], - "prompt_number": 15 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for result in results:\n", - " \n", - " query_type = result['query']['action']\n", - " if query_type == \"delete_edge\":\n", - " print result\n", - " overlay = result['query']['overlay']\n", - " g_overlay = anm[overlay]\n", - " query = result['query']['query']\n", - " print query\n", - " src = query.get(\"src\")\n", - " dst = query.get(\"dst\")\n", - " try:\n", - " del query['src']\n", - " except KeyError:\n", - " pass\n", - "\n", - " try:\n", - " del query['dst']\n", - " except KeyError:\n", - " pass\n", - " \n", - " print src\n", - " #TODO: remove this once allow direct string query of edges src_bunch and dst_bunch\n", - " \n", - " src_nbunch = None\n", - " try:\n", - " src_node = g_in.node(src)\n", - " if src_node:\n", - " src_nbunch = [src_node]\n", - " except:\n", - " pass\n", - "\n", - " dst_nbunch = None\n", - " try:\n", - " dst_node = g_in.node(dst)\n", - " if dst_node:\n", - " dst_nbunch = [dst_node]\n", - " except:\n", - " pass\n", - "\n", - " print dst_nbunch\n", - "\n", - " edges = g_overlay.edges(src_nbunch = src_nbunch, dst_nbunch=dst_nbunch, **query)\n", - " if len(edges):\n", - " print result\n", - " g_overlay.remove_edges_from(edges)\n", - "\n", - " elif query_type == \"insert_edge\":\n", - " overlay = result['query']['overlay']\n", - " g_overlay = anm[overlay]\n", - "\n", - " src = result['query']['src']\n", - " dst = result['query']['dst']\n", - " src_node = g_overlay.node(src)\n", - " dst_node = g_overlay.node(dst)\n", - " print src_node, dst_node\n", - " print g_overlay\n", - " \n", - " g_overlay.add_edge(src_node, dst_node) \n", - " print g_overlay.edges()\n", - " print len(g_overlay.edges())\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " import autonetkit\n", - " autonetkit.update_vis(anm)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'anm' is not defined", - "output_type": "pyerr", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mprint\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0moverlay\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'query'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'overlay'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mg_overlay\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0manm\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0moverlay\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0mquery\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'query'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'query'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mprint\u001b[0m \u001b[0mquery\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mNameError\u001b[0m: name 'anm' is not defined" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "{'query': {'action': 'delete_edge', 'query': {'src': 'iosv-7'}, 'overlay': 'ospf'}}\n" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print g_in.edges()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "[input: (iosxrv-1, iosv-7), input: (iosxrv-1, iosv-3), input: (iosxrv-1, nx-osv-1), input: (iosxrv-1, iosxrv-2), input: (server-1, nx-osv-2), input: (server-1, csr1000v-1), input: (iosv-6, iosv-1), input: (iosv-6, iosv-4), input: (iosv-6, iosxrv-2), input: (csr1000v-1, nx-osv-1), input: (csr1000v-1, iosxrv-2), input: (iosv-2, iosv-5), input: (iosv-2, iosv-7), input: (iosv-2, iosv-3), input: (iosv-8, iosv-5), input: (iosv-8, iosv-3), input: (iosv-8, iosv-4), input: (iosxrv-2, iosv-1), input: (nx-osv-1, nx-osv-2), input: (iosv-4, iosv-3), input: (iosv-3, iosv-1)]\n" - ] - } - ], - "prompt_number": 195 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from autonetkit_cisco.wsgi_client import get_virl_to_anm\n", - "\n", - "simulation_id = \"Sample_Topologies@multiplatform-CQM4Jf\"\n", - "\n", - "server = \"ank-dev\"\n", - "auth_data = {\n", - " 'server': server,\n", - " 'username': \"guest\",\n", - " 'password': \"guest\",\n", - " 'sim_id': simulation_id,\n", - "}\n", - "\n", - "anm = get_virl_to_anm(**auth_data)\n", - "\n", - "from autonetkit.build_network import apply_design_rules\n", - "apply_design_rules(anm)\n", - "\n", - "\n", - "import autonetkit\n", - "autonetkit.update_vis(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Getting VIRL as ANM\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Getting VIRL as ANM\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Getting VIRL file\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Getting VIRL file\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Returning VIRL file\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Returning VIRL file\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Converting graph input to undirected\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Converting graph input to undirected\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Returning VIRL as ANM\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Returning VIRL as ANM\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building overlay topologies\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building overlay topologies\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building layer2\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building layer2\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building layer3\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building layer3\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Allocating IP addresses\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Allocating IP addresses\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO IPv4 allocations: Infrastructure: 10.0.0.0/8, Loopback: 192.168.0.0/16\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:IPv4 allocations: Infrastructure: 10.0.0.0/8, Loopback: 192.168.0.0/16\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Using specified IPv4 infrastructure allocation\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Using specified IPv4 infrastructure allocation\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building IGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building IGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building BGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building BGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building eBGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building eBGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Building iBGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Building iBGP\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosxrv-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosxrv-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-6), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-6), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, csr1000v-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, csr1000v-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-2), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-2), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-8), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-8), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosxrv-2), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosxrv-2), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-7), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-7), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-4), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-4), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-3), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-3), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-5), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-5), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, nx-osv-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, nx-osv-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, nx-osv-2), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, nx-osv-2), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (server-1, iosv-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-6, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-6, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-7, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-7, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (csr1000v-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (csr1000v-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-2, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-2, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-8, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-8, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosxrv-2, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosxrv-2, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosxrv-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosxrv-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (nx-osv-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (nx-osv-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-4, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-4, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-5, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-5, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-3, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-3, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (nx-osv-2, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (nx-osv-2, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING [Overlay: ibgp_v4]: Not adding edge bgp: (iosv-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:[Overlay: ibgp_v4]: Not adding edge bgp: (iosv-1, server-1), src/dst not in overlay\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Finished building network\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Finished building network\n" - ] - } - ], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "result" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'result' is not defined", - "output_type": "pyerr", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mresult\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mNameError\u001b[0m: name 'result' is not defined" - ] - } - ], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from autonetkit_cisco.collection import query_lang" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "result = query_lang.process_query(\"UPDATE ospf.edges SET abc=123,def=456 WHERE (src=iosv-7) ;\")\n", - "print result" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "{'query': {'action': 'update_edge', 'query': {'src': 'iosv-7'}, 'set': {'abc': '123', 'def': '456'}, 'overlay': 'ospf'}}\n" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "query_lang.apply_query(anm, result)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING Unable to find node None in ospf \n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "WARNING:ANK:Unable to find node None in ospf \n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "{'query': {'action': 'update_edge', 'query': {'src': 'iosv-7'}, 'set': {'abc': '123', 'def': '456'}, 'overlay': 'ospf'}}\n", - "{'src': 'iosv-7'}\n", - "{'abc': '123', 'def': '456'}\n", - "iosv-7\n", - "bunch [iosv-7] None\n", - "edges [ospf: (iosv-7, iosxrv-1), ospf: (iosv-7, iosv-2)]\n" - ] - }, - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 5, - "text": [ - "ANM 20140508_154939" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "autonetkit.update_vis(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 8 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "g_ospf = anm['ospf']\n", - "for edge in g_ospf.edges():\n", - " print edge.dump()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "{'_ports': {'3430016387549': 2, '3430011387534': 2}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430016387549': 1, '3430010387531': 3}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430013387540': 1, '3430019387558': 1}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430013387540': 2, '3430022387567': 2}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430013387540': 3, '3430012387537': 3}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430024387573': 3, '3430014387543': 1}, 'cost': 1, 'abc': '123', 'def': '456', 'area': 0}\n", - "{'_ports': {'3430014387543': 2, '3430020387561': 1}, 'cost': 1, 'abc': '123', 'def': '456', 'area': 0}\n", - "{'_ports': {'3430025387576': 2, '3430010387531': 2}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430010387531': 1, '3430012387537': 2}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430023387570': 1, '3430020387561': 3}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430021387564': 2, '3430020387561': 2}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430023387570': 2, '3430015387546': 1}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430021387564': 3, '3430015387546': 2}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430015387546': 3, '3430022387567': 3}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430024387573': 4, '3430012387537': 4}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430019387558': 2, '3430012387537': 1}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430024387573': 2, '3430021387564': 1}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430024387573': 1, '3430025387576': 3}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430011387534': 1, '3430025387576': 1}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430021387564': 4, '3430022387567': 1}, 'cost': 1, 'area': 0}\n", - "{'_ports': {'3430021387564': 5, '3430019387558': 3}, 'cost': 1, 'area': 0}\n" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print " - ], - "language": "python", - "metadata": {}, - "outputs": [] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/autonetkit/Find Results b/autonetkit/Find Results deleted file mode 100644 index fdd51fa1..00000000 --- a/autonetkit/Find Results +++ /dev/null @@ -1,343 +0,0 @@ -Searching 553 files for "config_stanza" - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/ank_json.py: - 29 log.warning("%s is anm overlay_edge. Use attribute rather than object in compiler." % obj) - 30 return str(obj) - 31: if isinstance(obj, autonetkit.nidb.config_stanza): - 32 retval = obj.to_json() - 33 return retval - .. - 99 pass # not a string - 100 # handle lists of IP addresses - 101: if isinstance(val, dict) and val.get("_config_stanza") == True: - 102: val = autonetkit.nidb.config_stanza(**val) - 103 inst[key] = val # update with (possibly) updated list - 104 - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/device/cisco.py: - 8 from autonetkit.compiler import sort_sessions - 9 from autonetkit.compilers.device.router_base import RouterCompiler - 10: from autonetkit.nidb import config_stanza - 11 - 12 - .. - 195 if session.exclude and session.vrf: - 196 data = self.ibgp_session_data(session, ip_version=4) - 197: stanza = config_stanza(data) - 198 vrf_ibgp_neighbors[session.vrf].append(stanza) - 199 - ... - 202 if session.exclude and session.vrf: - 203 data = self.ibgp_session_data(session, ip_version=6) - 204: stanza = config_stanza(data) - 205 vrf_ibgp_neighbors[session.vrf].append(stanza) - 206 - ... - 212 if session.exclude and session.vrf: - 213 data = self.ebgp_session_data(session, ip_version=4) - 214: stanza = config_stanza(data) - 215 vrf_ebgp_neighbors[session.vrf].append(stanza) - 216 - ... - 218 if session.exclude and session.vrf: - 219 data = self.ebgp_session_data(session, ip_version=6) - 220: stanza = config_stanza(data) - 221 vrf_ebgp_neighbors[session.vrf].append(stanza) - 222 - ... - 224 rd_index = vrf_node.rd_indices[vrf] - 225 rd = '%s:%s' % (node.asn, rd_index) - 226: stanza = config_stanza( - 227 vrf=vrf, - 228 rd=rd, - ... - 241 if vpnv4_node.retain_route_target: - 242 retain = True - 243: node.bgp.vpnv4 = config_stanza(retain_route_target = retain) - 244 - 245 def vrf_igp_interfaces(self, node): - ... - 269 rd = '%s:%s' % (node.asn, rd_index) - 270 - 271: stanza = config_stanza(vrf = vrf, rd = rd, route_target = route_target) - 272 node.vrf.vrfs.append(stanza) - 273 - ... - 317 continue # don't configure IGP for this interface - 318 - 319: #TODO: use config_stanza here - 320 interface.ospf = { - 321 'cost': ospf_int.cost, - ... - 489 - 490 for peer in node.bgp.ibgp_neighbors: - 491: peer = config_stanza(peer) - 492 peer.remote_ip = peer.loopback - 493 if peer.use_ipv4: - ... - 501 - 502 for peer in node.bgp.ibgp_rr_parents: - 503: peer = config_stanza(peer) - 504 peer.remote_ip = peer.loopback - 505 if peer.use_ipv4: - ... - 513 - 514 for peer in node.bgp.ibgp_rr_clients: - 515: peer = config_stanza(peer) - 516 peer.rr_client = True - 517 peer.remote_ip = peer.loopback - ... - 526 - 527 for peer in node.bgp.ebgp_neighbors: - 528: peer = config_stanza(peer) - 529 peer.is_ebgp = True - 530 peer.remote_ip = peer.dst_int_ip - ... - 545 continue - 546 - 547: neigh_data = config_stanza(neigh) - 548 vpnv4_neighbors.append(neigh_data) - 549 - ... - 551 if not neigh.use_ipv4: - 552 continue - 553: neigh_data = config_stanza(neigh) - 554 neigh_data.rr_client = True - 555 vpnv4_neighbors.append(neigh_data) - ... - 558 if not neigh.use_ipv4: - 559 continue - 560: neigh_data = config_stanza(neigh) - 561 vpnv4_neighbors.append(neigh_data) - 562 - ... - 586 for interface in mpls_te_node.physical_interfaces: - 587 nidb_interface = self.nidb.interface(interface) - 588: stanza = config_stanza(id = nidb_interface.id, - 589 bandwidth_percent = 100) - 590 rsvp_interfaces.append(stanza) - ... - 614 #TODO: for here and below use stanza directly - 615 data = {'id': interface.id, 'passive': False} - 616: stanza = config_stanza(**data) - 617 if node.eigrp.use_ipv4: - 618 ipv4_interfaces.append(stanza) - ... - 622 loopback_zero = node.loopback_zero - 623 data = {'id': node.loopback_zero.id, 'passive': True} - 624: stanza = config_stanza(**data) - 625 if node.eigrp.use_ipv4: - 626 ipv4_interfaces.append(stanza) - ... - 653 - 654 #TODO: make stanza - 655: stanza = config_stanza(**data) - 656 node.isis.isis_links.append(stanza) - 657 - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/device/quagga.py: - 2 # -*- coding: utf-8 -*- - 3 from autonetkit.compilers.device.router_base import RouterCompiler - 4: from autonetkit.nidb import config_stanza - 5 - 6 - . - 47 bgp_int = self.anm['ebgp_v4'].interface(interface) - 48 if bgp_int.is_bound: # ebgp interface - 49: node.ospf.passive_interfaces.append(config_stanza(id=interface.id)) - 50 subnet = bgp_int['ipv4'].subnet - 51 default_ebgp_area = 0 - 52 node.ospf.ospf_links.append( - 53: config_stanza(network=subnet, - 54 area=default_ebgp_area)) - 55 - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/device/router_base.py: - 3 from autonetkit.compiler import sort_sessions - 4 from autonetkit.compilers.device.device_base import DeviceCompiler - 5: from autonetkit.nidb import config_stanza - 6 - 7 import autonetkit.plugins.naming as naming - . - 36 use_ipv6 = True - 37 - 38: #TODO: return config_stanza rather than a dict - 39 data = { # TODO: this is platform dependent??? - 40 'neighbor': neigh.label, - .. - 63 use_ipv6 = True - 64 - 65: #TODO: return config_stanza rather than a dict - 66 data = { # TODO: change templates to access from node.bgp.lo_int - 67 'neighbor': neigh.label, - .. - 266 area = str(area) # can't serialize IPAddress object to JSON - 267 #TODO: put in interface rather than interface.id for consistency - 268: stanza = config_stanza(id = interface.id, - 269 cost = int(ospf_int.cost), passive = False) - 270 - ... - 282 router_area = ospf_loopback_zero.area # area assigned to router - 283 router_area = str(router_area) # can't serialize IPAddress object to JSON - 284: stanza = config_stanza(id = node.loopback_zero.id, - 285 cost = 0, passive = True) - 286 interfaces_by_area[router_area].append(stanza) - 287 - 288: node.ospf.interfaces_by_area = config_stanza(**interfaces_by_area) - 289 - 290 added_networks = set() - ... - 311 not in added_networks: # don't add more than once - 312 added_networks.add(network) - 313: link_stanza = config_stanza(network = network, interface = interface, area = ospf_int.area) - 314 node.ospf.ospf_links.append(link_stanza) - 315 - ... - 348 - 349 data = self.ibgp_session_data(session, ip_version=4) - 350: bgp_stanza = config_stanza(**data) - 351 - 352 direction = session.direction - ... - 367 continue # exclude from regular ibgp config (eg VRF, VPLS, etc) - 368 data = self.ibgp_session_data(session, ip_version=6) - 369: bgp_stanza = config_stanza(**data) - 370 - 371 direction = session.direction - ... - 392 continue # exclude from regular ibgp config (eg VRF, VPLS, etc) - 393 data = self.ebgp_session_data(session, ip_version=4) - 394: bgp_stanza = config_stanza(**data) - 395 ebgp_neighbors.append(bgp_stanza) - 396 - ... - 403 continue # exclude from regular ibgp config (eg VRF, VPLS, etc) - 404 data = self.ebgp_session_data(session, ip_version=6) - 405: bgp_stanza = config_stanza(**data) - 406 ebgp_neighbors.append(bgp_stanza) - 407 - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/device/server_base.py: - 3 import autonetkit.log as log - 4 from autonetkit.compilers.device.device_base import DeviceCompiler - 5: from autonetkit.nidb import config_stanza - 6 - 7 class ServerCompiler(DeviceCompiler): - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/device/ubuntu.py: - 3 import autonetkit.log as log - 4 from autonetkit.compilers.device.server_base import ServerCompiler - 5: from autonetkit.nidb import config_stanza - 6 - 7 class UbuntuCompiler(ServerCompiler): - . - 80 % (asn, gateway), - 81 } - 82: route_entry = config_stanza(**route_entry) - 83 if infra_route.prefixlen == 32: - 84 host_routes_v4.append(route_entry) - .. - 99 % (asn, gateway), - 100 } - 101: route_entry = config_stanza(**route_entry) - 102 if asn_route.prefixlen == 32: - 103 host_routes_v4.append(route_entry) - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/platform/cisco.py: - 14 StarOsCompiler) - 15 from autonetkit.compilers.platform.platform_base import PlatformCompiler - 16: from autonetkit.nidb import config_stanza - 17 - 18 - .. - 314 interface.id = self.numeric_to_interface_label_nxos(interface.numeric_id) - 315 - 316: DmNode.supported_features = config_stanza(mpls_te = False, mpls_oam = False, vrf = False) - 317 - 318 nxos_compiler.compile(DmNode) - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/platform/dynagen.py: - 5 import autonetkit.ank as ank - 6 from autonetkit.compilers.device.cisco import IosClassicCompiler - 7: from autonetkit.nidb import config_stanza - 8 - 9 class DynagenCompiler(PlatformCompiler): - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/platform/junosphere.py: - 5 import autonetkit.ank as ank - 6 #from autonetkit.compilers.device. - 7: from autonetkit.nidb import config_stanza - 8 - 9 class JunosphereCompiler(PlatformCompiler): - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/compilers/platform/netkit.py: - 10 from autonetkit.ank_utils import alphabetical_sort as alpha_sort - 11 from autonetkit.compilers.device.quagga import QuaggaCompiler - 12: from autonetkit.nidb import config_stanza - 13 - 14 class NetkitCompiler(PlatformCompiler): - .. - 116 #netkit lab.conf uses 1 instead of eth1 - 117 numeric_id = interface.numeric_id - 118: stanza = config_stanza( - 119 device=naming.network_hostname(node), - 120 key=numeric_id, - ... - 126 for node in subgraph: - 127 if node.tap: - 128: stanza = config_stanza( - 129 device=naming.network_hostname(node), - 130 id=node.tap.id.replace("eth", ""), # strip ethx -> x - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/nidb/config_stanza.py: - 1 # based on http://docs.python.org/2.7/library/collections#collections.OrderedDict - 2 # and http://stackoverflow.com/q/455059 - 3: class config_stanza(object): - 4 def __init__(self, *args, **kwargs): - 5: if len(args) == 1 and isinstance(args[0], config_stanza): - 6 # Clone the data (shallow copy) - 7 #TODO: check how this relates to calling dict() on a dict - same? - . - 23 def to_json(self): - 24 retval = OrderedDict(self._odict) # clone to append to - 25: retval['_config_stanza'] = True - 26 return retval - 27 - 28 def add_stanza(self, name, **kwargs): - 29 """Adds a sub-stanza to this stanza""" - 30: stanza = config_stanza(**kwargs) - 31 self[name] = stanza - 32 return stanza - .. - 37 def __setitem__(self, key, value): - 38 if isinstance(value, dict): - 39: log.warning("Adding dictionary %s: did you mean to add a config_stanza?" % key) - 40 self._odict[key] = value - 41 - -/Users/simonknight/Dropbox/PhD/Dev/autonetkit/autonetkit/nidb/devices_model.py: - 300 if self.get(name): - 301 value = self.get(name) - 302: if isinstance(value, config_stanza): - 303 # Don't recreate - 304 self.log.debug("Stanza %s already exists" % name) - ... - 308 log.warning("Creating stanza: %s already set as %s for %s" % (name, type(value), self)) - 309 - 310: stanza = config_stanza(**kwargs) - 311 self.__setattr__(name, stanza) - 312 return stanza - ... - 505 - 506 #TODO: remove once deprecated DmNode_category - 507: if isinstance(data, config_stanza): - 508 return data - 509 - ... - 542 class lab_topology(object): - 543 """API to access lab topology in network""" - 544: #TODO: replace this with config_stanza - 545 - 546 def __init__(self, nidb, topology_id): - -57 matches across 12 files diff --git a/autonetkit/__init__.py b/autonetkit/__init__.py index 4dd8cf1a..e69de29b 100644 --- a/autonetkit/__init__.py +++ b/autonetkit/__init__.py @@ -1,9 +0,0 @@ -from autonetkit.anm import NetworkModel as NetworkModel -from autonetkit.nidb import DeviceModel as DeviceModel - -from autonetkit.ank_messaging import update_vis -# for legacy compatability -from autonetkit.ank_messaging import update_vis as update_http -from autonetkit.load.initialize import initialize - -import autonetkit.topologies as topos diff --git a/autonetkit/ank.py b/autonetkit/ank.py deleted file mode 100644 index a8933611..00000000 --- a/autonetkit/ank.py +++ /dev/null @@ -1,819 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import itertools -from collections import namedtuple -import autonetkit - -import autonetkit.log as log -import networkx as nx -from ank_utils import unwrap_graph, unwrap_nodes -from autonetkit.anm import NmEdge, NmNode - -# helper namedtuples - until have a more complete schema (such as from Yang) -static_route_v4 = namedtuple("static_route_v4", - ["prefix", "netmask", "nexthop", "metric"]) - -static_route_v6 = namedtuple("static_route_v6", - ["prefix", "nexthop", "metric"]) - -# TODO: add ability to specify labels to unwrap too - -# TODO: split into a utils module - - -def sn_preflen_to_network(address, prefixlen): - """Workaround for creating an IPNetwork from an address and a prefixlen - TODO: check if this is part of netaddr module - """ - - import netaddr - return netaddr.IPNetwork('%s/%s' % (address, prefixlen)) - - -def fqdn(node): - """ - - Example: - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> fqdn(r1) - 'r1.1' - - """ - return '%s.%s' % (node.label, node.asn) - - -def name_folder_safe(foldername): - for illegal_char in [' ', '/', '_', ',', '.', '&', '-', '(', ')', ]: - foldername = foldername.replace(illegal_char, '_') - - # Don't want double _ - - while '__' in foldername: - foldername = foldername.replace('__', '_') - return foldername - - -def set_node_default(nm_graph, nbunch=None, **kwargs): - """Sets all nodes in nbunch to value if key not already set - Note: this won't apply to future nodes added - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> r1 = g_phy.node("r1") - >>> r1.color = "blue" - >>> [(n, n.color) for n in g_phy] - [(r4, None), (r5, None), (r1, 'blue'), (r2, None), (r3, None)] - >>> set_node_default(g_phy, color="red") - >>> [(n, n.color) for n in g_phy] - [(r4, 'red'), (r5, 'red'), (r1, 'blue'), (r2, 'red'), (r3, 'red')] - - Can also set for a specific bunch of nodes - - >>> nodes = ["r1", "r2", "r3"] - >>> set_node_default(g_phy, nodes, role="core") - >>> [(n, n.role) for n in g_phy] - [(r4, None), (r5, None), (r1, 'core'), (r2, 'core'), (r3, 'core')] - - """ - - # work with the underlying NetworkX graph for efficiency - graph = unwrap_graph(nm_graph) - if nbunch is None: - nbunch = graph.nodes() - else: - nbunch = unwrap_nodes(nbunch) - for node in nbunch: - for (key, val) in kwargs.items(): - if key not in graph.node[node]: - graph.node[node][key] = val - -# TODO: also add ability to copy multiple attributes - -# TODO: rename to copy_node_attr_from - - -def copy_attr_from(overlay_src, overlay_dst, src_attr, dst_attr=None, - nbunch=None, type=None, default=None): - """ - - >>> anm = autonetkit.topos.house() - >>> g_in = anm['input'] - >>> g_phy = anm['phy'] - >>> [n.color for n in g_phy] - [None, None, None, None, None] - >>> set_node_default(g_in, color="red") - >>> copy_attr_from(g_in, g_phy, "color") - >>> [n.color for n in g_phy] - ['red', 'red', 'red', 'red', 'red'] - - Can specify a default value if unset - - >>> nodes = ["r1", "r2", "r3"] - >>> set_node_default(g_in, nodes, role="core") - >>> copy_attr_from(g_in, g_phy, "role", default="edge") - >>> [(n, n.role) for n in g_phy] - [(r4, 'edge'), (r5, 'edge'), (r1, 'core'), (r2, 'core'), (r3, 'core')] - - - Can specify the remote attribute to set - - >>> copy_attr_from(g_in, g_phy, "role", "device_role", default="edge") - - Can specify the type to cast to - - >>> g_in.update(memory = "32") - >>> copy_attr_from(g_in, g_phy, "memory", type=int) - >>> [n.memory for n in g_phy] - [32, 32, 32, 32, 32] - - - Supported types to case to are float and int - - """ - - if not dst_attr: - dst_attr = src_attr - - graph_src = unwrap_graph(overlay_src) - graph_dst = unwrap_graph(overlay_dst) - if not nbunch: - nbunch = graph_src.nodes() - - for node in nbunch: - try: - val = graph_src.node[node].get(src_attr, default) - except KeyError: - - # TODO: check if because node doesn't exist in dest, or because - # attribute doesn't exist in graph_src - - log.debug('Unable to copy node attribute %s for %s in %s', - src_attr, node, overlay_src) - else: - - # TODO: use a dtype to take an int, float, etc - - if type is float: - val = float(val) - elif type is int: - val = int(val) - - if node in graph_dst: - graph_dst.node[node][dst_attr] = val - - -def copy_int_attr_from(overlay_src, overlay_dst, src_attr, dst_attr=None, - nbunch=None, type=None, default=None): - - #TODO: check if copies to loopbacks as well - """ - - >>> anm = autonetkit.topos.house() - >>> g_in = anm['input'] - >>> g_phy = anm['phy'] - >>> [iface.ospf_cost for node in g_phy for iface in node] - [None, None, None, None, None, None, None, None, None, None, None, None] - >>> for node in g_in: - ... for interface in node: - ... interface.ospf_cost = 10 - >>> copy_int_attr_from(g_in, g_phy, "ospf_cost") - >>> [iface.ospf_cost for node in g_phy for iface in node] - [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] - - - Supported types to case to are float and int - - """ - - # note; uses high-level API for practicality over raw speed - - if not dst_attr: - dst_attr = src_attr - - if not nbunch: - nbunch = overlay_src.nodes() - - for node in nbunch: - for src_int in node: - val = src_int.get(src_attr) - if val is None: - val = default - - if type is float: - val = float(val) - elif type is int: - val = int(val) - - if node not in overlay_dst: - continue - - dst_int = overlay_dst.interface(src_int) - if dst_int is not None: - dst_int.set(dst_attr, val) - - -def copy_edge_attr_from(overlay_src, overlay_dst, src_attr, - dst_attr=None, type=None, default=None): - # note this won't work if merge/aggregate edges - - if not dst_attr: - dst_attr = src_attr - - for edge in overlay_src.edges(): - try: - val = edge.get(src_attr) - if val is None: - val = default - except KeyError: - - # TODO: check if because edge doesn't exist in dest, or because - # attribute doesn't exist in graph_src - - log.debug('Unable to copy edge attribute %s for (%s, %s) in %s', - src_attr, edge.src, edge.dst, overlay_src) - - else: - - # TODO: use a dtype to take an int, float, etc - - if type is float: - val = float(val) - elif type is int: - val = int(val) - - try: - overlay_dst.edge(edge).set(dst_attr, val) - except AttributeError: - # fail to debug - as attribute may not have been set - log.debug('Unable to set edge attribute on %s in %s', - edge, overlay_dst) - - -# TODO: make edges own module - -def wrap_edges(nm_graph, edges): - """ wraps edge ids into edge overlay - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> elist = [("r1", "r2"), ("r2", "r3")] - >>> edges = wrap_edges(g_phy, elist) - - The edges are now NetworkModel edge objects - - >>> edges - [(r1, r2), (r2, r3)] - - """ - - # TODO: make support multigraphs - - edges = list(edges) - if not any(len(e) for e in edges): - return [] # each edge tuple is empty - - try: - - # strip out data from (src, dst, data) tuple - - edges = [(s, t) for (s, t, _) in edges] - except ValueError: - pass # already of form (src, dst) - - return list(NmEdge(nm_graph._anm, nm_graph._overlay_id, src, dst) - for (src, dst) in edges) - - -def wrap_nodes(nm_graph, nodes): - """ wraps node id into node overlay - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> nlist = ["r1", "r2", "r3"] - >>> nodes = wrap_nodes(g_phy, nlist) - - The nodes are now NetworkModel node objects - - >>> nodes - [r1, r2, r3] - - This is generally used in internal functions. - An alternative method is: - - >>> [g_phy.node(n) for n in nlist] - [r1, r2, r3] - - """ - - return [NmNode(nm_graph._anm, nm_graph._overlay_id, node) - for node in nodes] - - -def in_edges(nm_graph, nodes=None): - - # TODO: make support multigraphs - - graph = unwrap_graph(nm_graph) - edges = graph.in_edges(nodes) - return wrap_edges(nm_graph, edges) - - -def split(nm_graph, edges, retain=None, id_prepend=''): - """ - Splits edges in two, retaining any attributes specified. - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> edge = g_phy.edge("r1", "r2") - >>> new_nodes = split(g_phy, edge) - >>> new_nodes - [r1_r2] - >>> [n.neighbors() for n in new_nodes] - [[r1, r2]] - - - For multiple edges and specifying a prepend for the new nodes - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> edges = g_phy.node("r2").edges() - >>> new_nodes = split(g_phy, edges, id_prepend="split_") - >>> new_nodes - [split_r2_r4, split_r1_r2, split_r2_r3] - >>> [n.neighbors() for n in new_nodes] - [[r4, r2], [r1, r2], [r2, r3]] - - """ - - if retain is None: - retain = [] - - try: - # TODO: find more efficient operation to test if string-like - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - graph = unwrap_graph(nm_graph) - edges_to_add = [] - added_nodes = [] - - # handle single edge - if edges in nm_graph.edges(): - edges = [edges] # place into list for iteration - - edges = list(edges) - - for edge in edges: - src = edge.src - dst = edge.dst - - # Form ID for the new node - - if graph.is_directed(): - new_id = '%s%s_%s' % (id_prepend, src, dst) - else: - - # undirected, make id deterministic across ank runs - - # use sorted for consistency - (node_a, node_b) = sorted([src, dst]) - new_id = '%s%s_%s' % (id_prepend, node_a, node_b) - - if nm_graph.is_multigraph(): - new_id = new_id + '_%s' % edge.ekey - - ports = edge.raw_interfaces - data = edge._data - src_data = data.copy() - if src in ports: - src_int_id = ports[src.node_id] - src_data['_ports'] = {src.node_id: src_int_id} - dst_data = data.copy() - if dst in ports: - dst_int_id = ports[dst.node_id] - dst_data['_ports'] = {dst.node_id: dst_int_id} - - # Note: don't retain ekey since adding to a new node - - append = (src.node_id, new_id, src_data) - edges_to_add.append(append) - append = (dst.node_id, new_id, dst_data) - edges_to_add.append(append) - - added_nodes.append(new_id) - - nm_graph.add_nodes_from(added_nodes) - nm_graph.add_edges_from(edges_to_add) - - # remove the pre-split edges - - nm_graph.remove_edges_from(edges) - - return wrap_nodes(nm_graph, added_nodes) - - -def explode_nodes(nm_graph, nodes, retain=None): - """Explodes all nodes in nodes - TODO: explain better - TODO: Add support for digraph - check if nm_graph.is_directed() - - >>> anm = autonetkit.topos.mixed() - >>> g_phy = anm['phy'] - >>> switches = g_phy.switches() - >>> exploded_edges = explode_nodes(g_phy, switches) - >>> exploded_edges - [(r1, r2)] - - - Or to explode a specific node - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> g_phy.nodes() - [r4, r5, r1, r2, r3] - - >>> sorted(g_phy.edges()) - [(r1, r2), (r1, r3), (r2, r3), (r4, r2), (r4, r5), (r5, r3)] - - >>> r2 = g_phy.node("r2") - >>> exploded_edges = explode_nodes(g_phy, r2) - >>> exploded_edges - [(r1, r4), (r3, r4), (r1, r3)] - >>> g_phy.nodes() - [r4, r5, r1, r3] - - >>> sorted(g_phy.edges()) - [(r1, r3), (r4, r1), (r4, r3), (r4, r5), (r5, r3)] - - """ - if retain is None: - retain = [] - - log.debug('Exploding nodes') - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - total_added_edges = [] # keep track to return - - if nodes in nm_graph: - nodes = [nodes] # place into list for iteration - - for node in nodes: - - edges = node.edges() - edge_pairs = [(e1, e2) for e1 in edges for e2 in edges if e1 - != e2] - added_pairs = set() - for edge_pair in edge_pairs: - (src_edge, dst_edge) = sorted(edge_pair) - if (src_edge, dst_edge) in added_pairs: - continue # already added this link pair in other direction - else: - added_pairs.add((src_edge, dst_edge)) - - src = src_edge.dst # src is the exploded node - dst = dst_edge.dst # src is the exploded node - - if src == dst: - continue # don't add self-loop - - data = dict((key, src_edge._data.get(key)) for key in - retain) - node_to_dst_data = dict((key, dst_edge._data.get(key)) - for key in retain) - data.update(node_to_dst_data) - - data['_ports'] = {} - try: - src_int_id = src_edge.raw_interfaces[src.node_id] - except KeyError: - pass # not set - else: - data['_ports'][src.node_id] = src_int_id - - try: - dst_int_id = dst_edge.raw_interfaces[dst.node_id] - except KeyError: - pass # not set - else: - data['_ports'][dst.node_id] = dst_int_id - - new_edge = (src.node_id, dst.node_id, data) - - # TODO: use add_edge - - nm_graph.add_edges_from([new_edge]) - total_added_edges.append(new_edge) - - nm_graph.remove_node(node) - return wrap_edges(nm_graph, total_added_edges) - - -def label(nm_graph, nodes): - return list(nm_graph._anm.node_label(node) for node in nodes) - - -def connected_subgraphs(nm_graph, nodes = None): - """ - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> connected_subgraphs(g_phy) - [[r4, r5, r1, r2, r3]] - >>> edges = [("r2", "r4"), ("r3", "r5")] - >>> g_phy.remove_edges_from(edges) - >>> connected_subgraphs(g_phy) - [[r1, r2, r3], [r4, r5]] - - - """ - if nodes is None: - nodes = nm_graph.nodes() - else: - nodes = list(unwrap_nodes(nodes)) - graph = unwrap_graph(nm_graph) - subgraph = graph.subgraph(nodes) - if not len(subgraph.edges()): - - # print "Nothing to aggregate for %s: no edges in subgraph" - - pass - if graph.is_directed(): - component_nodes_list = \ - nx.strongly_connected_components(subgraph) - else: - component_nodes_list = nx.connected_components(subgraph) - - wrapped = [] - for component in component_nodes_list: - wrapped.append(list(wrap_nodes(nm_graph, component))) - - return wrapped - - -def aggregate_nodes(nm_graph, nodes, retain=None): - """Combines connected into a single node""" - if retain is None: - retain = [] - - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - nodes = list(unwrap_nodes(nodes)) - graph = unwrap_graph(nm_graph) - subgraph = graph.subgraph(nodes) - if not len(subgraph.edges()): - - # print "Nothing to aggregate for %s: no edges in subgraph" - - pass - total_added_edges = [] - if graph.is_directed(): - component_nodes_list = \ - nx.strongly_connected_components(subgraph) - else: - component_nodes_list = nx.connected_components(subgraph) - for component_nodes in component_nodes_list: - if len(component_nodes) > 1: - component_nodes = [nm_graph.node(n) - for n in component_nodes] - - # TODO: could choose most connected, or most central? - # TODO: refactor so use nodes_to_remove - - nodes_to_remove = list(component_nodes) - base = nodes_to_remove.pop() # choose a base device to retain - log.debug('Retaining %s, removing %s', base, - nodes_to_remove) - - external_edges = [] - for node in nodes_to_remove: - external_edges += [e for e in node.edges() if e.dst - not in component_nodes] - # all edges out of component - - log.debug('External edges %s', external_edges) - edges_to_add = [] - for edge in external_edges: - dst = edge.dst - data = dict((key, edge._data.get(key)) for key in - retain) - ports = edge.raw_interfaces - dst_int_id = ports[dst.node_id] - - # TODO: bind to (and maybe add) port on the new switch? - - data['_ports'] = {dst.node_id: dst_int_id} - - append = (base.node_id, dst.node_id, data) - edges_to_add.append(append) - - nm_graph.add_edges_from(edges_to_add) - total_added_edges += edges_to_add - nm_graph.remove_nodes_from(nodes_to_remove) - - return wrap_edges(nm_graph, total_added_edges) - - -def most_frequent(iterable): - """returns most frequent item in iterable""" - -# from http://stackoverflow.com/q/1518522 - - gby = itertools.groupby - try: - return max(gby(sorted(iterable)), key=lambda (x, v): - (len(list(v)), -iterable.index(x)))[0] - except ValueError, error: - log.warning('Unable to calculate most_frequent, %s', error) - return None - - -def neigh_most_frequent(nm_graph, node, attribute, - attribute_graph=None, allow_none=False): - """Used to explicitly force most frequent - - useful if integers such as ASN which would otherwise return mean""" - - # TODO: rename to median? - - graph = unwrap_graph(nm_graph) - if attribute_graph: - attribute_graph = unwrap_graph(attribute_graph) - else: - attribute_graph = graph # use input graph - node = unwrap_nodes(node) - values = [attribute_graph.node[n].get(attribute) for n in - graph.neighbors(node)] - values = sorted(values) - if not allow_none: - values = [v for v in values if v is not None] - return most_frequent(values) - - -def neigh_average(nm_graph, node, attribute, attribute_graph=None): - """ - averages out attribute from neighbors in specified nm_graph - attribute_graph is the graph to read the attribute from - if property is numeric, then return mean - else return most frequently occuring value - """ - - graph = unwrap_graph(nm_graph) - if attribute_graph: - attribute_graph = unwrap_graph(attribute_graph) - else: - attribute_graph = graph # use input graph - node = unwrap_nodes(node) - values = [attribute_graph.node[n].get(attribute) for n in - graph.neighbors(node)] - -# TODO: use neigh_attr - - try: - values = [float(val) for val in values] - return sum(values) / len(values) - except ValueError: - return most_frequent(values) - - -def neigh_attr(nm_graph, node, attribute, attribute_graph=None): - """TODO: - tidy up parameters to take attribute_graph first, and - then evaluate if attribute_graph set, if not then use attribute_graph - as attribute - explain how nm_graph and attribute_graph work, eg for G_ip and - G_phy - """ - - graph = unwrap_graph(nm_graph) - node = unwrap_nodes(node) - if attribute_graph: - attribute_graph = unwrap_graph(attribute_graph) - else: - attribute_graph = graph # use input graph - - # Only look at nodes which exist in attribute_graph - - neighs = (n for n in graph.neighbors(node)) - valid_nodes = (n for n in neighs if n in attribute_graph) - return (attribute_graph.node[node].get(attribute) for node in - valid_nodes) - - -def neigh_equal(nm_graph, node, attribute, attribute_graph=None): - """Boolean, True if neighbors in nm_graph - all have same attribute in attribute_graph""" - - neigh_attrs = neigh_attr(nm_graph, node, attribute, attribute_graph) - return len(set(neigh_attrs)) == 1 - - -def unique_attr(nm_graph, attribute): - graph = unwrap_graph(nm_graph) - return set(graph.node[node].get(attribute) for node in graph) - - -def groupby(attribute, nodes): - """Takes a group of nodes and returns a generator of (attribute, nodes) - for each attribute value A simple wrapped around itertools.groupby - that creates a lambda for the attribute - """ - - keyfunc = lambda x: x.get(attribute) - nodes = sorted(nodes, key=keyfunc) - return itertools.groupby(nodes, key=keyfunc) - -def shortest_path(nm_graph, src, dst): - - # TODO: move to utils -# TODO: use networkx boundary nodes directly: does the same thing - - graph = unwrap_graph(nm_graph) - src_id = unwrap_nodes(src) - dst_id = unwrap_nodes(dst) - - #TODO: check path works for muli-edge graphs too - path = nx.shortest_path(graph, src_id, dst_id) - - return wrap_nodes(nm_graph, path) - - -def boundary_nodes(nm_graph, nodes): - """ returns nodes at boundary of G based on - edge_boundary from networkx """ - - # TODO: move to utils -# TODO: use networkx boundary nodes directly: does the same thing - - graph = unwrap_graph(nm_graph) - nodes = list(nodes) - nbunch = list(unwrap_nodes(nodes)) - - # find boundary - - b_edges = nx.edge_boundary(graph, nbunch) # boundary edges - internal_nodes = [s for (s, _) in b_edges] - assert all(n in nbunch for n in internal_nodes) # check internal - - return wrap_nodes(nm_graph, internal_nodes) - -def shallow_copy_nx_graph(nx_graph): - """Convenience wrapper for nx shallow copy - - >>> import networkx - >>> G = nx.Graph() - >>> H = shallow_copy_nx_graph(G) - >>> isinstance(H, nx.Graph) - True - >>> isinstance(H, nx.DiGraph) - False - >>> isinstance(H, nx.MultiGraph) - False - >>> isinstance(H, nx.MultiDiGraph) - False - - >>> G = nx.DiGraph() - >>> H = shallow_copy_nx_graph(G) - >>> isinstance(H, nx.DiGraph) - True - - >>> G = nx.MultiGraph() - >>> H = shallow_copy_nx_graph(G) - >>> isinstance(H, nx.MultiGraph) - True - - >>> G = nx.MultiDiGraph() - >>> H = shallow_copy_nx_graph(G) - >>> isinstance(H, nx.MultiDiGraph) - True - - """ - import networkx - directed = nx_graph.is_directed() - multi = nx_graph.is_multigraph() - - if directed: - if multi: - return nx.MultiDiGraph(nx_graph) - else: - return nx.DiGraph(nx_graph) - else: - if multi: - return nx.MultiGraph(nx_graph) - else: - return nx.Graph(nx_graph) - - - - diff --git a/autonetkit/ank_json.py b/autonetkit/ank_json.py deleted file mode 100644 index 1785dc2a..00000000 --- a/autonetkit/ank_json.py +++ /dev/null @@ -1,426 +0,0 @@ -try: - import simplejson as json -except ImportError: - import json - -import logging -import string - -import autonetkit.anm -import autonetkit.log as log -import autonetkit.nidb -import autonetkit.plugins -import autonetkit.plugins.ipv4 -import netaddr -import networkx as nx -from networkx.readwrite import json_graph -from autonetkit.render2 import NodeRender, PlatformRender -from autonetkit.ank import shallow_copy_nx_graph - - -class AnkEncoder(json.JSONEncoder): - - """Handles netaddr objects by converting to string form""" - # TODO: look at using skipkeys = True to skip non-basic type keys (can we - # warn on this?) - - def default(self, obj): - if isinstance(obj, set): - return str(obj) - if isinstance(obj, netaddr.IPAddress): - return str(obj) - if isinstance(obj, netaddr.IPNetwork): - return str(obj) - if isinstance(obj, autonetkit.nidb.node.DmNode): - # TODO: need to unserialize nidb nodes... - return str(obj) - if isinstance(obj, autonetkit.anm.node.NmNode): - # TODO: need to unserialize nidb nodes... - return str(obj) - if isinstance(obj, autonetkit.anm.NmEdge): - log.warning( - "%s is anm overlay_edge. Use attribute rather than object in compiler." % obj) - return str(obj) - if isinstance(obj, autonetkit.nidb.ConfigStanza): - retval = obj.to_json() - return retval - if isinstance(obj, autonetkit.render2.NodeRender): - retval = obj.to_json() - return retval - if isinstance(obj, autonetkit.render2.PlatformRender): - retval = obj.to_json() - return retval - if isinstance(obj, autonetkit.nidb.DmInterface): - # TODO: check this is consistent with deserialization - return str(obj) - - if isinstance(obj, autonetkit.anm.interface.NmPort): - # TODO: check this is consistent with deserialization - return str(obj) - if isinstance(obj, nx.classes.Graph): - # TODO: remove now? - return json_graph.node_link_data(obj) - - if isinstance(obj, logging.LoggerAdapter): - # TODO: filter this out in the to_json methods - return "" - - return json.JSONEncoder.default(self, obj) - - -def ank_json_dumps(graph, indent=4): - data = json_graph.node_link_data(graph) -# TODO: use regex to convert IPAddress and IPNetwork back to respective -# form in decoder - data = json.dumps(data, cls=AnkEncoder, indent=indent, sort_keys=True) - return data - - -def string_to_netaddr(val): - retval = None - dot_count = string.count(val, ".") - if dot_count: - # could be an IP or network address - if "/" in val: - try: - retval = netaddr.IPNetwork(val) - except netaddr.core.AddrFormatError: - return # unable to convert, leave as string - else: - try: - retval = netaddr.IPAddress(val) - except netaddr.core.AddrFormatError: - return # unable to convert, leave as string - - return retval - - -def restore_anm_nidb_from_json(data): - # This can be used to extract from the json used to send to webserver - - d = ank_json_custom_loads(data) - anm = autonetkit.anm.AbstractNetworkModel() - nidb = autonetkit.nidb.DeviceModel() - - for overlay_id, overlay_data in d.items(): - if overlay_id == "nidb": - continue # don't restore nidb graph to anm - anm._overlays[overlay_id] = json_graph.node_link_graph(overlay_data) - - nidb._graph = json_graph.node_link_graph(d['nidb']) - rebind_interfaces(anm) - - return anm, nidb - - -def ank_json_custom_loads(data): - # data = json.loads(data) # this is needed if dicts contain anm overlays, - # nidb, etc - def dict_to_object(d): - inst = d - for key, val in d.items(): - try: - newval = string_to_netaddr(val) - if newval: - inst[key] = newval - except AttributeError: - pass # not a string -# handle lists of IP addresses - if isinstance(val, dict) and val.get("_ConfigStanza") == True: - val = autonetkit.nidb.ConfigStanza(**val) - inst[key] = val # update with (possibly) updated list - - if isinstance(val, list): - if any(isinstance(elem, basestring) for elem in val): - # list contains a string - for index, elem in enumerate(val): - try: - new_elem = string_to_netaddr(elem) - if new_elem: - val[index] = new_elem # in-place replacement - except AttributeError: - pass # not a string - - inst[key] = val # update with (possibly) updated list - - return inst - - d = json.loads(data, object_hook=dict_to_object) - return d - - -def rebind_interfaces(anm): - for overlay_id in anm.overlays(): - overlay = anm[overlay_id] - # for edge in overlay.edges(): - #unbound_ports = edge._ports -# map nodes -> node objects, values to integers (not strings) - # interfaces = {overlay.node(key): val for key, val in unbound_ports.items()} - # edge._ports = interfaces # store with remapped node - for node in overlay.nodes(): - unbound_ports = node.raw_interfaces - if len(unbound_ports): # is list if none set - interfaces = {int(key): val for key, val in unbound_ports.items()} - node.raw_interfaces = interfaces - -# TODO: need to also rebind_interfaces for nidb - - -def rebind_nidb_interfaces(nidb): - for node in nidb.nodes(): - unbound_ports = node.raw_interfaces - if len(unbound_ports): # is list if none set - interfaces = {int(key): val for key, val in unbound_ports.items()} - node.raw_interfaces = interfaces - - -def ank_json_loads(data): - d = ank_json_custom_loads(data) - # TODO: map back edge keys for parallel links - or is this automatic? - return json_graph.node_link_graph(d) - - -def jsonify_anm(anm): - """ Returns a dictionary of json-ified overlay graphs""" - anm_json = {} - for overlay_id in anm.overlays(): - NmGraph = anm[overlay_id]._graph.copy() - for n in NmGraph: - try: - del NmGraph.node[n]['id'] - except KeyError: - pass - anm_json[overlay_id] = ank_json_dumps(NmGraph) - return json.dumps(anm_json) - - -def shortened_interface(name): - """Condenses interface name. Not canonical - mainly for brevity""" - name = name.replace("GigabitEthernet", "ge") - name = name.replace("0/0/0/", "") - return name - - -def jsonify_anm_with_graphics(anm, nidb=None): - """ Returns a dictionary of json-ified overlay graphs, with graphics data appended to each overlay""" - from collections import defaultdict - import math - anm_json = {} - test_anm_data = {} - graphics_graph = anm["graphics"]._graph.copy() - phy_graph = anm["phy"]._graph # to access ASNs - - """simple layout of deps - more advanced layout could - export to dot and import to omnigraffle, etc - """ - g_deps = anm['_dependencies'] - nm_graph = g_deps._graph - # build tree - layers = defaultdict(list) - nodes_by_layer = {} - if len(nm_graph) > 0: - topo_sort = nx.topological_sort(nm_graph) - # trim out any nodes with no sucessors - - tree_root = topo_sort[0] - # Note: topo_sort ensures that once reach node, would have reached its predecessors - # start at first element after root - for node in topo_sort: - preds = nm_graph.predecessors(node) - if len(preds): - pred_level = max(nm_graph.node[p].get('level') for p in preds) - else: - # a root node - pred_level = -1 # this node becomes level 0 - level = pred_level + 1 - nm_graph.node[node]['level'] = level - layers[level].append(node) - - data = nm_graph.node[node] - data['y'] = 100 * data['level'] - data['device_type'] = "ank_internal" - - MIDPOINT = 50 # assign either side of - for layer, nodes in layers.items(): - # TODO: since sort is stable, first sort by parent x (avoids - # zig-zags) - nodes = sorted(nodes, reverse=True, - key=lambda x: nm_graph.degree(x)) - for index, node in enumerate(nodes): - # TODO: work out why weird offset due to the math.pow * - #node_x = MIDPOINT + 125*index * math.pow(-1, index) - node_x = MIDPOINT + 125 * index - nm_graph.node[node]['x'] = node_x - nodes_by_layer[node] = layer - - import random - attribute_cache = defaultdict(dict) - # the attributes to copy - # TODO: check behaviour for None if explicitly set - # TODO: need to check if attribute is set in overlay..... using API - copy_attrs = ["x", "y", "asn", "label", "device_type", "device_subtype"] - for node, in_data in phy_graph.nodes(data=True): - out_data = {key: in_data.get(key) for key in copy_attrs - if key in in_data} - attribute_cache[node].update(out_data) - - # Update for graphics (over-rides phy) - for node, in_data in graphics_graph.nodes(data=True): - out_data = {key: in_data.get(key) for key in copy_attrs - if key in in_data} - attribute_cache[node].update(out_data) - - # append label from function - for node in anm['phy']: - attribute_cache[node.id]['label'] = str(node) - - overlay_ids = sorted(anm.overlays(), - key=lambda x: nodes_by_layer.get(x, 0)) - - for overlay_id in overlay_ids: - try: - #make a shallow copy - # input_graph = anm[overlay_id]._graph - # nm_graph = shallow_copy_nx_graph(input_graph) - nm_graph = anm[overlay_id]._graph.copy() - except Exception, e: - log.warning("Unable to copy overlay %s: %s", overlay_id, e) - continue - - if overlay_id == "_dependencies": - # convert to undirected for visual clarify - nm_graph = nx.Graph(nm_graph) - - for node in nm_graph: - node_data = dict(attribute_cache.get(node, {})) - # update with node data from this overlay - # TODO: check is not None won't clobber specifically set in - # overlay... - graph_node_data = nm_graph.node[node] - overlay_node_data = {key: graph_node_data.get(key) - for key in graph_node_data} - node_data.update(overlay_node_data) - - # check for any non-set properties - if node_data.get("x") is None: - new_x = random.randint(0, 800) - node_data['x'] = new_x - # store for other graphs to use - log.debug("Allocated random x %s to node %s in overlay %s" % - (new_x, node, overlay_id)) - attribute_cache[node]['x'] = new_x - else: - # cache for next time, such as vswitch in l2 for l2_bc - attribute_cache[node]['x'] = node_data['x'] - if node_data.get("y") is None: - new_y = random.randint(0, 800) - node_data['y'] = new_y - # store for other graphs to use - attribute_cache[node]['y'] = new_y - log.debug("Allocated random y %s to node %s in overlay %s" % - (new_y, node, overlay_id)) - else: - attribute_cache[node]['y'] = node_data['y'] - - # TODO: may want to re-introduce graphics to store cross-layer data for virtual nodes - # and cache device type and device subtype - # TODO: catch for each, if node not in cache - try: - attribute_cache[node]['device_type'] = node_data['device_type'] - except KeyError: - pass # not set - try: - attribute_cache[node][ - 'device_subtype'] = node_data['device_subtype'] - except KeyError: - pass # not set - - if node_data.get("label") == node: - # try from cache - node_data['label'] = attribute_cache.get(node, {}).get("label") - if node_data.get("label") is None: - node_data['label'] = str(node) # don't need to cache - - # store on graph - nm_graph.node[node] = node_data - - try: - del nm_graph.node[node]['id'] - except KeyError: - pass - - if nidb: - nidb_graph = nidb.raw_graph() - if node in nidb: - DmNode_data = nidb_graph.node[node] - try: - # TODO: check why not all nodes have _ports initialised - overlay_interfaces = nm_graph.node[node]["_ports"] - except KeyError: - continue # skip copying interface data for this node - - for interface_id in overlay_interfaces.keys(): - # TODO: use raw_interfaces here - try: - nidb_interface_id = DmNode_data[ - '_ports'][interface_id]['id'] - except KeyError: - # TODO: check why arrive here - something not - # initialised? - continue - nm_graph.node[node]['_ports'][ - interface_id]['id'] = nidb_interface_id - id_brief = shortened_interface(nidb_interface_id) - nm_graph.node[node]['_ports'][ - interface_id]['id_brief'] = id_brief - - anm_json[overlay_id] = ank_json_dumps(nm_graph) - test_anm_data[overlay_id] = nm_graph - - if nidb: - test_anm_data['nidb'] = prepare_nidb(nidb) - - result = json.dumps( - test_anm_data, cls=AnkEncoder, indent=4, sort_keys=True) - return result - - -def prepare_nidb(nidb): - graph = nidb.raw_graph() - for node in graph: - if graph.node[node].get("graphics"): - graph.node[node]['x'] = graph.node[node]['graphics']['x'] - graph.node[node]['y'] = graph.node[node]['graphics']['y'] - graph.node[node]['device_type'] = graph.node[ - node]['graphics']['device_type'] - graph.node[node]['device_subtype'] = graph.node[ - node]['graphics']['device_subtype'] - - for interface_index in graph.node[node]['_ports']: - try: - interface_id = graph.node[node][ - "_ports"][interface_index]['id'] - except KeyError: # interface doesn't exist, eg for a lan segment - interface_id = "" - id_brief = shortened_interface(interface_id) - graph.node[node]["_ports"][interface_index]['id_brief'] = id_brief - - x = (graph.node[n]['x'] for n in graph) - y = (graph.node[n]['y'] for n in graph) - x_min = min(x) - y_min = min(y) - for n in graph: - graph.node[n]['x'] += - x_min - graph.node[n]['y'] += - y_min - - return graph - - -def jsonify_nidb(nidb): - graph = prepare_nidb(nidb) - data = ank_json_dumps(graph) - return data - - -def dumps(anm, nidb=None, indent=4): - return jsonify_anm_with_graphics(anm, nidb) diff --git a/autonetkit/ank_messaging.py b/autonetkit/ank_messaging.py deleted file mode 100644 index 4261a37d..00000000 --- a/autonetkit/ank_messaging.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import autonetkit.ank_json -import autonetkit.config as config -import autonetkit.log as log -from autonetkit.ank_utils import call_log - -use_http_post = config.settings['Http Post']['active'] -if use_http_post: - import urllib - -#@call_log - - -def format_http_url(host=None, port=None, route='publish'): - if not host and not port: - host = config.settings['Http Post']['server'] - port = config.settings['Http Post']['port'] - return 'http://%s:%s/%s' % (host, port, route) - - -default_http_url = format_http_url() - -#@call_log - - -def update_vis(anm=None, nidb=None, http_url=None, uuid=None): - if http_url is None: - http_url = default_http_url - - if anm and nidb: - body = autonetkit.ank_json.dumps(anm, nidb) - elif anm: - body = autonetkit.ank_json.dumps(anm) - else: - import json - body = json.dumps({}) # blank to test visualisation server running - - if uuid is None: - uuid = get_uuid(anm) - - params = urllib.urlencode({'body': body, 'type': 'anm', - 'uuid': uuid}) - try: - data = urllib.urlopen(http_url, params).read() - log.debug(data) - except IOError, e: - log.info('Unable to connect to visualisation server %s', http_url) - return - - if not anm: - - # testing - - log.info('Visualisation server running') - -#@call_log - - -def get_uuid(anm): - try: - return config.settings['Http Post']['uuid'] - except KeyError: - log.warning('UUID not set, returning singleuser uuid') - return 'singleuser' - - -#@call_log -def highlight(nodes=None, edges=None, paths=None, path=None, - uuid='singleuser', http_url=None): - if http_url is None: - http_url = default_http_url - if not paths: - paths = [] - - if path: - paths.append(path) - - if nodes is None: - nodes = [] - if edges is None: - edges = [] - - def nfilter(n): - try: - return n.id - except AttributeError: - return n # likely already a node id (string) - - def efilter(e): - try: - return (e.src.id, e.dst.id) - except AttributeError: - return e # likely already edge (src, dst) id tuple (string) - - nodes = [nfilter(n) for n in nodes] - # TODO: allow node annotations also - - filtered_edges = [] - for edge in edges: - if isinstance(edge, dict) and 'edge' in edge: - edge_data = dict(edge) # use as-is (but make copy) - else: - edge_data = {'edge': edge} # no extra data - - edge_data['src'] = edge_data['edge'].src.id - edge_data['dst'] = edge_data['edge'].dst.id - del edge_data['edge'] # remove now have extracted the src/dst - filtered_edges.append(edge_data) - - #edges = [efilter(e) for e in edges] - filtered_paths = [] - for path in paths: - - # TODO: tidy this logic - - if isinstance(path, dict) and 'path' in path: - path_data = dict(path) # use as-is (but make copy) - else: - # path_data = {'path': path, 'verified': is_verified} - - path_data = {'path': path} - - path_data['path'] = [nfilter(n) for n in path_data['path']] - filtered_paths.append(path_data) - - # TODO: remove "highlight" from json, use as url params to distinguish - - import json - body = json.dumps({'nodes': nodes, 'edges': filtered_edges, - 'paths': filtered_paths}) - - params = urllib.urlencode({'body': body, 'type': 'highlight', - 'uuid': uuid}) - - # TODO: split this common function out, create at runtime so don't need to - # keep reading config - - try: - urllib.urlopen(http_url, params).read() - except IOError, error: - log.info('Unable to connect to HTTP Server %s: %s', http_url, error) diff --git a/autonetkit/ank_utils.py b/autonetkit/ank_utils.py deleted file mode 100644 index 7bd1ce68..00000000 --- a/autonetkit/ank_utils.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -AutoNetkit Utilities -""" - -#from anm import overlay_node, overlay_edge -import autonetkit - - -def call_log(fn, *args, **kwargs): - def decorator(*args, **kwargs): - # print "\t" + fn.__name__ - return fn(*args, **kwargs) - - return decorator - - -def unwrap_nodes(nodes): - """Unwrap nodes""" - from autonetkit.anm import NmNode - - try: - return nodes.node_id # treat as single node - except AttributeError: - if isinstance(nodes, basestring): - return nodes # string - - return [node.node_id if isinstance(node, NmNode) - else node - for node in nodes] # treat as list - - -def unwrap_edges(edges): - """Unwrap edges""" - retval = [] - for edge in edges: - if edge.is_multigraph(): - retval.append((edge.src_id, edge.dst_id, edge.ekey)) - else: - retval.append((edge.src_id, edge.dst_id)) - - return retval - - -def unwrap_graph(nm_graph): - """Unwrap graph""" - return nm_graph._graph - - -def alphabetical_sort(l): - """From http://stackoverflow.com/questions/2669059/how-to-sort-alpha-numeric-set-in-python""" -# TODO: fix as currently only handles strings - not objects with repr? - import re - """ Sort the given iterable in the way that humans expect.""" - convert = lambda text: int(text) if text.isdigit() else text - alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] - return sorted(l, key=alphanum_key) - - -def wrap_nodes(nm_graph, nodes): - """ wraps node id into node overlay """ -# TODO: remove duplicate of this in ank.py - return (autonetkit.anm.overlay_node(nm_graph._anm, nm_graph._overlay_id, node) - for node in nodes) - - -def merge_quagga_conf(): - # helper function to merge the quagga config files into a single file for - # http://www.nongnu.org/quagga/docs/docs-multi/VTY-shell-integrated-configuration.html - import pkg_resources - import os - import time - - templates_path = pkg_resources.resource_filename(__name__, "templates") - zebra_dir = os.path.join(templates_path, "quagga", "etc", "zebra") - # TODO: check the ordering - seems to matter - conf_files = ["zebra.conf.mako", "bgpd.conf.mako", - "ospfd.conf.mako", "isisd.conf.mako"] - timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime()) - data = ["##Merged by AutoNetkit merge_quagga_conf on %s" % timestamp] - # TODO: replace newlines with ! - for conf_file in conf_files: - filename = os.path.join(zebra_dir, conf_file) - with open(filename) as fh: - data.append(fh.read()) - - ospfd_conf = os.path.join(templates_path, "quagga.conf.mako") - with open(ospfd_conf, "w") as fh: - fh.write("\n".join(data)) - - -"""Potential edge utils: -# TODO: see if these are still used -def attr_equal(self, *args): - ""Return edges which both src and dst have attributes equal"" - - return all(getattr(self.src, key) == getattr(self.dst, key) - for key in args) - -def attr_both(self, *args): - ""Return edges which both src and dst have attributes set"" - - return all(getattr(self.src, key) and getattr(self.dst, key) - for key in args) - -def attr_any(self, *args): - ""Return edges which either src and dst have attributes set"" - - return all(getattr(self.src, key) or getattr(self.dst, key) - for key in args) -""" diff --git a/autonetkit/ank_validate.py b/autonetkit/ank_validate.py deleted file mode 100644 index 22619aec..00000000 --- a/autonetkit/ank_validate.py +++ /dev/null @@ -1,196 +0,0 @@ -import autonetkit.ank as ank_utils -import autonetkit.log as log - -"""TODO: map the log info/warning/debug to functions here -# which then map to the appropriate log function, so that can handle either be verbose or not verbose to console with test information. -""" - - -def validate(anm): - log.debug("Validating overlay topologies") - tests_passed = True - tests_passed = validate_ipv4(anm) and tests_passed - - try: - from autonetkit_cisco import ank_validate as cisco_validate - except ImportError, e: - log.debug("Unable to load autonetkit_cisco %s" % e) - else: - cisco_validate.validate(anm) - - validate_ibgp(anm) - validate_igp(anm) - check_for_selfloops(anm) - all_nodes_have_asn(anm) - - if tests_passed: - log.debug("All validation tests passed.") - else: - log.warning("Some validation tests failed.") - - -def check_for_selfloops(anm): - # checks each overlay for selfloops - for overlay in anm: - selfloop_count = overlay._graph.number_of_selfloops() - if selfloop_count > 0: - log.warning("%s has %s self-loops" % (overlay, selfloop_count)) - - -def all_nodes_have_asn(anm): - g_phy = anm['phy'] - for node in g_phy.l3devices(): - if node.asn is None: - log.warning("No ASN set for physical device %s" % node) - - -def validate_ibgp(anm): - import networkx as nx - # TODO: repeat for ibgp v6 - # TODO: test if overlay is present, if not then warn - if not anm.has_overlay("ibgp_v4"): - return # no ibgp v4 - eg if ip addressing disabled - - g_ibgp_v4 = anm['ibgp_v4'] - - for asn, devices in ank_utils.groupby("asn", g_ibgp_v4): - asn_subgraph = g_ibgp_v4.subgraph(devices) - graph = asn_subgraph._graph - # get subgraph - if not nx.is_strongly_connected(graph): - g_ibgp_v4.log.warning( - "iBGP v4 topology for ASN%s is disconnected" % asn) - # TODO: list connected components - but not the primary? - else: - g_ibgp_v4.log.debug( - "iBGP v4 topology for ASN%s is connected" % asn) - - -def validate_igp(anm): - import networkx as nx - # TODO: test if overlay is present, if not then warn - if not anm.has_overlay("igp"): - return # no ibgp v4 - eg if ip addressing disabled - - g_igp = anm['igp'] - - for asn, devices in ank_utils.groupby("asn", g_igp): - if asn is None: - continue - asn_subgraph = g_igp.subgraph(devices) - graph = asn_subgraph._graph - # get subgraph - if not nx.is_connected(graph): - g_igp.log.warning("IGP topology for ASN%s is disconnected" % asn) - # TODO: list connected components - but not the primary? - else: - g_igp.log.debug("IGP topology for ASN%s is connected" % asn) - - -def all_same(items): - # based on http://stackoverflow.com/q/3787908 - # use all with generator as shortcuts to false as soon as one invalid - # TODO: place this into generic ANK functions, use similar shortcut for - # speed elsewhere, in other utility functions - return all(x == items[0] for x in items) - - -def all_unique(items): - # based on http://stackoverflow.com/q/3787908 - # shortcuts for efficiency - seen = set() - return not any(i in seen or seen.add(i) for i in items) - - -def duplicate_items(items): - unique = set(items) - counts = {i: items.count(i) for i in unique} - return [i for i in counts if counts[i] > 1] - -# TODO: add high-level symmetry, anti-summetry, uniqueness, etc functions -# as per NCGuard - -# TODO: make generic interface equal or unique function that takes attr - - -def validate_ipv4(anm): - # TODO: make this generic to also handle IPv6 - if not anm.has_overlay("ipv4"): - log.debug("No IPv4 overlay created, skipping ipv4 validation") - return - g_ipv4 = anm['ipv4'] - # interface IP uniqueness - tests_passed = True - - # TODO: only include bound interfaces - - # check globally unique ip addresses - all_ints = [i for n in g_ipv4.l3devices() - for i in n.physical_interfaces() - if i.is_bound] # don't include unbound interfaces - all_int_ips = [i.ip_address for i in all_ints if i.ip_address] - - if all_unique(all_int_ips): - g_ipv4.log.debug("All interface IPs globally unique") - else: - tests_passed = False - duplicates = duplicate_items(all_int_ips) - duplicate_ips = set(duplicate_items(all_int_ips)) - duplicate_ints = [n for n in all_ints - if n.ip_address in duplicate_ips] - duplicates = ", ".join("%s: %s" % (i.node, i.ip_address) - for i in duplicate_ints) - g_ipv4.log.warning("Global duplicate IP addresses %s" % duplicates) - - for bc in g_ipv4.nodes("broadcast_domain"): - bc.log.debug("Verifying subnet and interface IPs") - if not bc.allocate: - log.debug("Skipping validation of manually allocated broadcast " - "domain %s" % bc) - continue - - neigh_ints = list(bc.neighbor_interfaces()) - neigh_ints = [i for i in neigh_ints if i.node.is_l3device()] - neigh_int_subnets = [i.subnet for i in neigh_ints] - if all_same(neigh_int_subnets): - # log ok - pass - else: - subnets = ", ".join("%s: %s" % (i.node, i.subnets) - for i in neigh_int_subnets) - tests_passed = False - log.warning("Different subnets on %s. %s" % - (bc, subnets)) - # log warning - - ip_subnet_mismatches = [i for i in neigh_ints - if i.ip_address not in i.subnet] - if len(ip_subnet_mismatches): - tests_passed = False - mismatches = ", ".join("%s not in %s on %s" % - (i.ip_address, i.subnet, i.node) - for i in ip_subnet_mismatches) - bc.log.warning("Mismatched IP subnets: %s" % - mismatches) - else: - bc.log.debug("All subnets match") - - neigh_int_ips = [i.ip_address for i in neigh_ints] - if all_unique(neigh_int_ips): - bc.log.debug("All interface IP addresses are unique") - duplicates = duplicate_items(neigh_int_ips) - else: - tests_passed = False - duplicate_ips = set(duplicate_items(neigh_int_ips)) - duplicate_ints = [n for n in neigh_ints - if n.ip_address in duplicate_ips] - duplicates = ", ".join("%s: %s" % (i.node, i.ip_address) - for i in duplicate_ints) - bc.log.warning("Duplicate IP addresses: %s" % duplicates) - - if tests_passed: - g_ipv4.log.debug("All IP tests passed.") - else: - g_ipv4.log.warning("Some IP tests failed.") - - return tests_passed diff --git a/autonetkit/anm.py b/autonetkit/anm.py deleted file mode 100644 index 4b5d4f33..00000000 --- a/autonetkit/anm.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import functools -import itertools -import pprint -import string -import time -from functools import total_ordering - -import autonetkit.log as log -import networkx as nx -from autonetkit.ank_utils import unwrap_edges, unwrap_nodes - -try: - import cPickle as pickle -except ImportError: - import pickle - -import logging - - -#TODO: rename duplicate use of logger var in log setup -class CustomAdapter(logging.LoggerAdapter): - def process(self, msg, kwargs): - return '[%s]: %s' % (self.extra['item'], msg), kwargs - -class AutoNetkitException(Exception): - - pass - - - - -# TODO: rename to NmPort - - diff --git a/autonetkit/anm/__init__.py b/autonetkit/anm/__init__.py deleted file mode 100644 index 4ee754a1..00000000 --- a/autonetkit/anm/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from autonetkit.anm.network_model import NetworkModel as NetworkModel -from autonetkit.anm.graph import NmGraph as NmGraph -from autonetkit.anm.node import NmNode as NmNode -from autonetkit.anm.edge import NmEdge as NmEdge -from autonetkit.anm.interface import NmPort as NmPort \ No newline at end of file diff --git a/autonetkit/anm/ank_element.py b/autonetkit/anm/ank_element.py deleted file mode 100644 index e4af31ff..00000000 --- a/autonetkit/anm/ank_element.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging -import autonetkit.log2 -ank_logger_2 = logging.getLogger("ANK2") - -class AnkElement(object): - - #TODO: put this into parent __init__? - def init_logging(self, my_type): - return - try: - self_id = str(self) - except Exception, e: - #TODO: log warning here - import autonetkit.log as log - log.warning("Unable to set per-element logger %s", e) - self_id = "" - - - log_extra={"type": my_type, "id": self_id} - object.__setattr__(self, 'log_extra', log_extra) - - #self.log_info = partial(ank_logger_2.info, extra=extra) - #self.log_warning = partial(ank_logger_2.warning, extra=extra) - #self.log_error = partial(ank_logger_2.error, extra=extra) - #self.log_critical = partial(ank_logger_2.critical, extra=extra) - #self.log_exception = partial(ank_logger_2.exception, extra=extra) - #self.log_debug = partial(ank_logger_2.debug, extra=extra) - - def log_info(self, message): - ank_logger_2.info(message, extra = self.log_extra) - - def log_warning(self, message): - ank_logger_2.warning(message, extra = self.log_extra) - - def log_error(self, message): - ank_logger_2.error(message, extra = self.log_extra) - - def log_critical(self, message): - ank_logger_2.critical(message, extra = self.log_extra) - - def log_exception(self, message): - ank_logger_2.exception(message, extra = self.log_extra) - - def log_debug(self, message): - ank_logger_2.debug(message, extra = self.log_extra) - - diff --git a/autonetkit/anm/base.py b/autonetkit/anm/base.py deleted file mode 100644 index 0322dcc1..00000000 --- a/autonetkit/anm/base.py +++ /dev/null @@ -1,517 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import itertools -import logging - -import autonetkit -import autonetkit.log as log -from autonetkit.anm.edge import NmEdge -from autonetkit.anm.graph_data import NmGraphData -from autonetkit.anm.interface import NmPort -from autonetkit.anm.node import NmNode -from autonetkit.exception import OverlayNotFound -# TODO: check if this is still a performance hit -from autonetkit.log import CustomAdapter - -from autonetkit.anm.ank_element import AnkElement - -class OverlayBase(AnkElement): - - '''Base class for overlays - overlay graphs, subgraphs, projections, etc''' - - def __init__(self, anm, overlay_id): - """""" - - if overlay_id not in anm.overlay_nx_graphs: - raise OverlayNotFound(overlay_id) - - # TODO: return False instead? - - self._overlay_id = overlay_id - self._anm = anm - #logger = logging.getLogger('ANK') - #logstring = 'Overlay: %s' % str(overlay_id) - #logger = CustomAdapter(logger, {'item': logstring}) - #object.__setattr__(self, 'log', logger) - #self.init_logging("graph") - logger = log - object.__setattr__(self, 'log', logger) - self.init_logging("graph") - - def __repr__(self): - """ - - Example: - - >>> anm = autonetkit.topos.house() - >>> anm['phy'] - phy - - """ - - return self._overlay_id - - def is_multigraph(self): - """ - Example: - - >>> anm = autonetkit.topos.house() - >>> anm['phy'].is_multigraph() - False - >>> anm = autonetkit.topos.multi_edge() - >>> anm['phy'].is_multigraph() - True - - """ - return self._graph.is_multigraph() - - @property - def data(self): - """Returns data stored on this overlay graph""" - - return NmGraphData(self._anm, self._overlay_id) - - def __contains__(self, n): - """ - Example: - - >>> anm = autonetkit.topos.house() - >>> "r1" in anm['phy'] - True - >>> "test" in anm['phy'] - False - """ - - try: - return n.node_id in self._graph - except AttributeError: - - # try with node_id as a string - - return n in self._graph - - def interface(self, interface): - """""" - - return NmPort(self._anm, self._overlay_id, interface.node_id, - interface.interface_id) - - def edge(self, edge_to_find, dst_to_find=None, key=0): - '''returns edge in this graph with same src and dst - and key for parallel edges (default is to return first edge) - #TODO: explain parameter overloading: strings, edges, nodes... - - Example: - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> e_r1_r2 = g_phy.edge("r1", "r2") - - Can also find from an edge - - >>> e_r1_r2_input = anm['input'].edge(e_r1_r2) - - And for multi-edge graphs can specify key - - >>> anm = autonetkit.topos.multi_edge() - >>> e1 = anm['phy'].edge("r1", "r2", 0) - >>> e2 = anm['phy'].edge("r1", "r2", 1) - >>> e1 == e2 - False - - >>> autonetkit.update_http(anm) - >>> eth0_r1 = anm["phy"].node("r1").interface("eth0") - >>> eth3_r1 = anm["phy"].node("r1").interface("eth3") - >>> eth0_r2 = anm["phy"].node("r2").interface("eth0") - >>> anm["phy"].has_edge(eth0_r1, eth0_r2) - True - >>> anm["phy"].has_edge(eth3_r1, eth0_r2) - False - - ''' - - # TODO: handle multigraphs - if isinstance(edge_to_find, NmEdge): - # TODO: tidy this logic - edge = edge_to_find # alias for neater code - if (edge.is_multigraph() and self.is_multigraph() - and self._graph.has_edge(edge.src, - edge.dst, key=edge.ekey)): - return NmEdge(self._anm, self._overlay_id, - edge.src, edge.dst, edge.ekey) - elif (self._graph.has_edge(edge.src, edge.dst)): - return NmEdge(self._anm, self._overlay_id, - edge.src, edge.dst) - - if isinstance(edge_to_find, NmEdge): - src_id = edge_to_find.src - dst_id = edge_to_find.dst - search_key = key - - if self.is_multigraph(): - for (src, dst, rkey) in self._graph.edges(src_id, - keys=True): - if dst == dst_id and rkey == search_key: - return NmEdge(self._anm, self._overlay_id, src, - dst, search_key) - - for (src, dst) in self._graph.edges(src_id): - if dst == dst_id: - return NmEdge(self._anm, self._overlay_id, src, dst) - - # from here on look for (src, dst) pairs - src = edge_to_find - dst = dst_to_find - - if (isinstance(src, basestring) and isinstance(dst, basestring)): - src = src.lower() - dst = dst.lower() - if self.is_multigraph(): - if self._graph.has_edge(src, dst, key=key): - return NmEdge(self._anm, self._overlay_id, src, - dst, key) - elif self._graph.has_edge(src, dst): - return NmEdge(self._anm, self._overlay_id, src, dst) - - if isinstance(src, NmNode) and isinstance(dst, NmNode): - src_id = src.node_id - dst_id = dst.node_id - - if self.is_multigraph(): - if self._graph.has_edge(src_id, dst_id, key): - return NmEdge(self._anm, self._overlay_id, src, dst, key) - - else: - if self._graph.has_edge(src_id, dst_id): - return NmEdge(self._anm, self._overlay_id, src, dst) - - - if isinstance(src, NmPort) and isinstance(dst, NmPort): - # further filter result by ports - src_id = src.node_id - dst_id = dst.node_id - src_int = src.interface_id - dst_int = dst.interface_id - - - # TODO: combine duplicated logic from above - #TODO: test with directed graph - - if self.is_multigraph(): - # search edges from src to dst - for src, iter_dst, iter_key in self._graph.edges(src_id, keys=True): - if iter_dst != dst_id: - continue # to a different node - - ports = self._graph[src][iter_dst][iter_key]["_ports"] - if ports[src_id] == src_int and ports[dst_id] == dst_int: - return NmEdge(self._anm, self._overlay_id, src_id, dst_id, iter_key) - - else: - #TODO: add test case for here - for src, iter_dst in self._graph.edges(src_id): - if iter_dst != dst_id: - continue # to a different node - - ports = self._graph[src][iter_dst]["_ports"] - if ports[src_id] == src_int and ports[dst_id] == dst_int: - return NmEdge(self._anm, self._overlay_id, src_id, dst_id) - - - - - - - def __getitem__(self, key): - """""" - - return self.node(key) - - def node(self, key): - """Returns node based on name - This is currently O(N). Could use a lookup table - - Example: - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> r1 = g_phy.node("r1") - - Can also find across layers - >>> r1_input = anm['input'].node(r1) - - """ - - # TODO: refactor - - try: - if key.node_id in self._graph: - return NmNode(self._anm, self._overlay_id, key.node_id) - except AttributeError: - - # try as string id - - if key in self._graph: - return NmNode(self._anm, self._overlay_id, key) - - # doesn't have node_id, likely a label string, search based on this - # label - - for node in self: - if str(node) == key: - return node - # TODO: change warning to an exception - log.warning('Unable to find node %s in %s ' % (key, self)) - return None - - def overlay(self, key): - """Get to other overlay graphs in functions""" - - # TODO: refactor: shouldn't be returning concrete instantiation from - # abstract parent! - - from autonetkit.anm.graph import NmGraph - return NmGraph(self._anm, key) - - @property - def name(self): - """""" - - return self.__repr__() - - def __nonzero__(self): - return self.anm.has_overlay(self._overlay_id) - - def node_label(self, node): - """""" - - return repr(NmNode(self._anm, self._overlay_id, node)) - - def has_edge(self, edge_to_find, dst_to_find=None,): - """Tests if edge in graph - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> r1 = g_phy.node("r1") - >>> r2 = g_phy.node("r2") - >>> r5 = g_phy.node("r5") - >>> g_phy.has_edge(r1, r2) - True - >>> g_phy.has_edge(r1, r5) - False - - >>> e_r1_r2 = anm['input'].edge(r1, r2) - >>> g_phy.has_edge(e_r1_r2) - True - """ - - if dst_to_find is None: - if self.is_multigraph(): - return self._graph.has_edge(edge_to_find.src, - edge_to_find.dst, edge_to_find.ekey) - - return self._graph.has_edge(edge_to_find.src, edge_to_find.dst) - - else: - return bool(self.edge(edge_to_find, dst_to_find)) - - def __iter__(self): - """""" - - return iter(self.nodes()) - - def __len__(self): - """""" - - return len(self._graph) - - def nodes(self, *args, **kwargs): - """ - - >>> anm = autonetkit.topos.multi_as() - >>> g_phy = anm["phy"] - >>> g_phy.nodes() - [r4, r5, r6, r7, r1, r2, r3, r8, r9, r10] - - >>> g_phy.nodes(asn=1) - [r4, r5, r1, r2, r3] - - >>> g_phy.nodes(asn=3) - [r7, r8, r9, r10] - - >>> g_phy.nodes(asn=1, ibgp_role="RR") - [r4, r5] - - >>> g_phy.nodes(asn=1, ibgp_role="RRC") - [r1, r2, r3] - - """ - - result = list(NmNode(self._anm, self._overlay_id, node) - for node in self._graph) - - if len(args) or len(kwargs): - result = self.filter(result, *args, **kwargs) - return result - - def routers(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be router - - >>> anm = autonetkit.topos.mixed() - >>> anm['phy'].routers() - [r1, r2, r3] - - """ - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_router()] - - def switches(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be switch - - >>> anm = autonetkit.topos.mixed() - >>> anm['phy'].switches() - [sw1] - - """ - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_switch()] - - def servers(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be server - - >>> anm = autonetkit.topos.mixed() - >>> anm['phy'].servers() - [s1] - - """ - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_server()] - - def l3devices(self, *args, **kwargs): - """Shortcut for nodes(), tests if device is_l3device - - >>> anm = autonetkit.topos.mixed() - >>> anm['phy'].l3devices() - [s1, r1, r2, r3] - - """ - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_l3device()] - - def device(self, key): - """To access programatically""" - - return NmNode(self._anm, self._overlay_id, key) - - def groupby(self, attribute, nodes=None): - """Returns a dictionary sorted by attribute - - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> g_phy.groupby("asn") - {1: [r1, r2, r3], 2: [r4, r5]} - - Can also specify a subset to work from - - >>> nodes = [n for n in g_phy if n.degree() > 2] - >>> g_phy.groupby("asn", nodes=nodes) - {1: [r2, r3]} - - """ - - result = {} - - if not nodes: - data = self.nodes() - else: - data = nodes - data = sorted(data, key=lambda x: x.get(attribute)) - for (key, grouping) in itertools.groupby(data, key=lambda x: - x.get(attribute)): - result[key] = list(grouping) - - return result - - def filter(self, nbunch=None, *args, **kwargs): - """""" - - if nbunch is None: - nbunch = self.nodes() - - def filter_func(node): - """Filter based on args and kwargs""" - - return all(getattr(node, key) for key in args) \ - and all(getattr(node, key) == val for (key, val) in - kwargs.items()) - - return [n for n in nbunch if filter_func(n)] - - def edges(self, src_nbunch=None, dst_nbunch=None, *args, - **kwargs): - """ - >>> anm = autonetkit.topos.house() - >>> g_phy = anm['phy'] - >>> g_phy.edges() - [(r4, r5), (r4, r2), (r5, r3), (r1, r2), (r1, r3), (r2, r3)] - - >>> g_phy.edge("r1", "r2").color = "red" - >>> g_phy.edges(color = "red") - [(r1, r2)] - - """ - -# src_nbunch or dst_nbunch may be single node -# TODO: refactor this - - if src_nbunch: - nbunch_out = [] - try: - src_nbunch = src_nbunch.node_id - except AttributeError: - src_nbunch = (n.node_id for n in src_nbunch) - - # only store the id in overlay - - def filter_func(edge): - """Filter based on args and kwargs""" - - return all(getattr(edge, key) for key in args) \ - and all(getattr(edge, key) == val for (key, val) in - kwargs.items()) - - if self.is_multigraph(): - valid_edges = list((src, dst, key) for (src, dst, key) in - self._graph.edges(src_nbunch, keys=True)) - else: - default_key = 0 - valid_edges = list((src, dst, default_key) - for (src, dst) in self._graph.edges(src_nbunch)) - - if dst_nbunch: - try: - dst_nbunch = dst_nbunch.node_id - dst_nbunch = set([dst_nbunch]) - except AttributeError: - dst_nbunch = (n.node_id for n in dst_nbunch) - dst_nbunch = set(dst_nbunch) - - valid_edges = list((src, dst, key) for (src, dst, key) in - valid_edges if dst in dst_nbunch) - - if len(args) or len(kwargs): - all_edges = [NmEdge(self._anm, self._overlay_id, src, dst, - key) for (src, dst, key) in valid_edges] - result = list(edge for edge in all_edges - if filter_func(edge)) - else: - result = list(NmEdge(self._anm, self._overlay_id, src, dst, - key) for (src, dst, key) in valid_edges) - - return list(result) - diff --git a/autonetkit/anm/edge.py b/autonetkit/anm/edge.py deleted file mode 100644 index 33aa7419..00000000 --- a/autonetkit/anm/edge.py +++ /dev/null @@ -1,348 +0,0 @@ -import logging -from functools import total_ordering - -import autonetkit -import autonetkit.log as log -from autonetkit.anm.interface import NmPort -from autonetkit.anm.node import NmNode -from autonetkit.log import CustomAdapter - -from autonetkit.anm.ank_element import AnkElement - -@total_ordering -class NmEdge(AnkElement): - - """API to access link in network""" - - def __init__(self, anm, overlay_id, src_id, dst_id, ekey=0): - - object.__setattr__(self, 'anm', anm) - object.__setattr__(self, 'overlay_id', overlay_id) - object.__setattr__(self, 'src_id', src_id) - object.__setattr__(self, 'dst_id', dst_id) - object.__setattr__(self, 'ekey', ekey) # for multigraphs - #logger = logging.getLogger("ANK") - #logstring = "Interface: %s" % str(self) - #logger = CustomAdapter(logger, {'item': logstring}) - logger = log - object.__setattr__(self, 'log', logger) - self.init_logging("edge") - - - def __key(self): - """Note: key doesn't include overlay_id to allow fast cross-layer comparisons""" - - # based on http://stackoverflow.com/q/2909106 - - return (self.src_id, self.dst_id) - - def __hash__(self): - """""" - - return hash(self.__key()) - - def is_multigraph(self): - return self._graph.is_multigraph() - - def is_parallel(self): - """If there is more than one edge between the src, dst of this edge - - >>> anm = autonetkit.topos.multi_edge() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.is_parallel() - True - >>> edge = anm['phy'].edge("r2", "r3") - >>> edge.is_parallel() - False - - - """ - # TODO: check this for digraph, multiidigraph - return self._overlay().number_of_edges(self.src, self.dst) > 1 - - def __eq__(self, other): - """ - - >>> anm = autonetkit.topos.house() - >>> e1 = anm['phy'].edge("r1", "r2") - >>> e2 = anm['phy'].edge("r1", "r2") - >>> e1 == e2 - True - - Can also compare across layers - >>> e2 = anm['input'].edge("r1", "r2") - >>> e2 - (r1, r2) - >>> e1 == e2 - True - - For multi-edge graphs can specify the key - - >>> anm = autonetkit.topos.multi_edge() - >>> e1 = anm['phy'].edge("r1", "r2", 0) - >>> e2 = anm['phy'].edge("r1", "r2", 1) - >>> e1 == e2 - False - - #TODO: compare single-edge overlays to multi-edge - - - """ - if self.is_multigraph(): - try: - if other.is_multigraph(): - return (self.src_id, self.dst_id, self.ekey) == (other.src_id, - other.dst_id, other.ekey) - else: - # multi, single - return (self.src_id, self.dst_id) == (other.src_id, - other.dst_id) - - except AttributeError: - if len(other) == 2: - # (src, dst) - return (self.src_id, self.dst_id) == other - elif len(other) == 3: - # (src, dst, key) - return (self.src_id, self.dst_id, self.ekey) == other - - try: - # self is single, other is single or multi -> only compare (src, - # dst) - return (self.src_id, self.dst_id) == (other.src_id, other.dst_id) - except AttributeError: - # compare to strings - return (self.src_id, self.dst_id) == other - - def __repr__(self): - """String of node""" - if self.is_multigraph(): - return '(%s, %s, %s)' % (self.src, - self.dst, self.ekey) - - return '(%s, %s)' % (self.src, self.dst) - - def __getitem__(self, key): - """""" - - from autonetkit.anm.graph import NmGraph - overlay = NmGraph(self.anm, key) - return overlay.edge(self) - - def _overlay(self): - from autonetkit.anm import NmGraph - return NmGraph(self.anm, self.overlay_id) - - def __lt__(self, other): - """ - - >>> anm = autonetkit.topos.house() - >>> e1 = anm['phy'].edge("r1", "r2") - >>> e2 = anm['phy'].edge("r1", "r3") - >>> e1 < e2 - True - >>> e2 < e1 - False - - """ - if self.is_multigraph() and other.is_multigraph(): - return (self.src.node_id, self.dst.node_id, self.ekey) \ - < (other.src.node_id, other.dst.node_id, other.ekey) - - return (self.src.node_id, self.dst.node_id) \ - < (other.src.node_id, other.dst.node_id) - - # Internal properties - def __nonzero__(self): - """Allows for checking if edge exists - - >>> anm = autonetkit.topos.house() - >>> e1 = anm['phy'].edge("r1", "r2") - >>> bool(e1) - True - - For a non-existent link, will return False - (NOTE: doesn't throw exception) - >>> e2 = anm['phy'].edge("r1", "r5") - >>> bool(e2) - False - - - - """ - if self.is_multigraph(): - return self._graph.has_edge(self.src_id, self.dst_id, key=self.ekey) - - return self._graph.has_edge(self.src_id, self.dst_id) - - @property - def raw_interfaces(self): - """Direct access to the interfaces dictionary, used by ANK modules""" - return self._ports - - @raw_interfaces.setter - def raw_interfaces(self, value): - self._ports = value - - @property - def _graph(self): - """Return graph the edge belongs to""" - - return self.anm.overlay_nx_graphs[self.overlay_id] - - @property - def _data(self): - """Return data the edge belongs to""" - if self.is_multigraph(): - return self._graph[self.src_id][self.dst_id][self.ekey] - - return self._graph[self.src_id][self.dst_id] - - # Nodes - - @property - def src(self): - """Source node of edge - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.src - r1 - - """ - - return NmNode(self.anm, self.overlay_id, self.src_id) - - @property - def dst(self): - """Destination node of edge - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.dst - r2 - - """ - - return NmNode(self.anm, self.overlay_id, self.dst_id) - - # Interfaces - - def apply_to_interfaces(self, attribute): - """" - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.src_int.color = edge.dst_int.color = "blue" - >>> (edge.src_int.color, edge.dst_int.color) - ('blue', 'blue') - >>> edge.color = "red" - >>> edge.apply_to_interfaces("color") - >>> (edge.src_int.color, edge.dst_int.color) - ('red', 'red') - """ - - val = self.__getattr__(attribute) - self.src_int.__setattr__(attribute, val) - self.dst_int.__setattr__(attribute, val) - - @property - def src_int(self): - """Interface bound to source node of edge - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.src_int - eth0.r1 - - """ - - src_int_id = self._ports[self.src_id] - return NmPort(self.anm, self.overlay_id, - self.src_id, src_int_id) - - @property - def dst_int(self): - """Interface bound to destination node of edge - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.dst_int - eth0.r2 - - """ - - dst_int_id = self._ports[self.dst_id] - return NmPort(self.anm, self.overlay_id, - self.dst_id, dst_int_id) - - def bind_interface(self, node, interface): - """Bind this edge to specified index""" - - self._ports[node.id] = interface - - def interfaces(self): - """ - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> list(edge.interfaces()) - [eth0.r1, eth0.r2] - - """ - - # TODO: warn if interface doesn't exist on node - - return [NmPort(self.anm, self.overlay_id, - node_id, interface_id) for (node_id, - interface_id) in self._ports.items()] - - # - - def dump(self): - return str(self._graph[self.src_id][self.dst_id]) - - def get(self, key): - """For consistency, edge.get(key) is neater than getattr(edge, key) - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.color = "red" - >>> edge.get("color") - 'red' - - """ - - return self.__getattr__(key) - - def set(self, key, val): - """For consistency, edge.set(key, value) is neater than - setattr(edge, key, value) - - >>> anm = autonetkit.topos.house() - >>> edge = anm['phy'].edge("r1", "r2") - >>> edge.color = "blue" - >>> edge.color - 'blue' - >>> edge.set("color", "red") - >>> edge.color - 'red' - - - """ - - return self.__setattr__(key, val) - - def __getattr__(self, key): - """Returns edge property""" - return self._data.get(key) - - def __setattr__(self, key, val): - """Sets edge property""" - - if key == 'raw_interfaces': - # TODO: fix workaround for - # http://docs.python.org/2/reference/datamodel.html#customizing-attribute-access - object.__setattr__(self, 'raw_interfaces', val) - - self._data[key] = val diff --git a/autonetkit/anm/graph.py b/autonetkit/anm/graph.py deleted file mode 100644 index 0155ee04..00000000 --- a/autonetkit/anm/graph.py +++ /dev/null @@ -1,481 +0,0 @@ -import autonetkit.log as log -from autonetkit.ank_utils import unwrap_edges, unwrap_nodes -from autonetkit.anm.base import OverlayBase -from autonetkit.anm.edge import NmEdge -from autonetkit.anm.interface import NmPort -from autonetkit.anm.node import NmNode -import autonetkit - - -class NmGraph(OverlayBase): - - """API to interact with an overlay graph in ANM""" - - @property - def anm(self): - """Returns anm for this overlay - - >>> anm = autonetkit.topos.house() - - """ - - return self._anm - - @property - def _graph(self): - """Access underlying graph for this NmNode""" - - return self._anm.overlay_nx_graphs[self._overlay_id] - - def _replace_graph(self, graph): - """""" - - self._anm.overlay_nx_graphs[self._overlay_id] = graph - - # these work similar to their nx counterparts: just need to strip the - # node_id - - def _record_overlay_dependencies(self, nbunch): - # TODO: add this logic to anm so can call when instantiating overlays too - # TODO: make this able to be disabled for performance - g_deps = self.anm['_dependencies'] - overlays = {n.overlay_id for n in nbunch if isinstance(n, NmNode)} - if len(overlays) and self._overlay_id not in g_deps: - g_deps.add_node(self._overlay_id) - for overlay_id in overlays: - if overlay_id not in g_deps: - g_deps.add_node(overlay_id) - - if g_deps.number_of_edges(self._overlay_id, overlay_id) == 0: - edge = (overlay_id, self._overlay_id) - g_deps.add_edges_from([edge]) - - def add_nodes_from( self, nbunch, retain=None, update=False, - **kwargs): - """Update won't append data (which could clobber) if node exists""" - nbunch = list(nbunch) # listify in case consumed in try/except - - if not retain: - retain = [] - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - self._record_overlay_dependencies(nbunch) - - if not update: - # TODO: what is update used for? - # filter out existing nodes - nbunch = (n for n in nbunch if n not in self._graph) - - nbunch = list(nbunch) - node_ids = list(nbunch) # before appending retain data - - if len(retain): - add_nodes = [] - for node in nbunch: - data = dict((key, node.get(key)) for key in retain) - add_nodes.append((node.node_id, data)) - nbunch = add_nodes - else: - try: - # only store the id in overlay - nbunch = [n.node_id for n in nbunch] - except AttributeError: - pass # use nbunch directly as the node IDs - - self._graph.add_nodes_from(nbunch, **kwargs) - for node in self._graph.nodes(): - node_data = self._graph.node[node] - if "label" not in node_data: - # TODO: should this just fall through instead to phy/anm label - # function? - node_data["label"] = str(node) # use node id - - self._copy_interfaces(node_ids) - - def _copy_interfaces(self, nbunch): - """Copies ports from one overlay to another""" - - nbunch = [n.node_id if isinstance(n, NmNode) else n - for n in nbunch] - - if self._overlay_id == "phy": - # note: this will auto copy data from input if present - by default - # TODO: provide an option to add_nodes to skip this - # or would it be better to just provide a function in ank_utils to - # wipe interfaces in the rare case it's needed? - input_graph = self._anm.overlay_nx_graphs['input'] - for node_id in nbunch: - if node_id not in input_graph: - log.debug("Not copying interfaces for %s: ", - "not in input graph %s" % node_id) - self._graph.node[node_id]['_ports'] = { - 0: {'description': 'loopback', 'category': 'loopback'}} - continue - - try: - input_interfaces = input_graph.node[node_id]['_ports'] - except KeyError: - #Node not in input - # Just do base initialisation of loopback zero - self._graph.node[node_id]['_ports'] = { - 0: {'description': 'loopback', 'category': 'loopback'}} - else: - interface_data = {'description': None, - 'category': 'physical'} - # need to do dict() to copy, otherwise all point to same memory - # location -> clobber - # TODO: update this to also get subinterfaces? - # TODO: should description and category auto fall through? - data = dict((key, dict(interface_data)) for key in - input_interfaces) - ports = {} - for key, vals in input_interfaces.items(): - port_data = {} - ports[key] = dict(vals) - - # force 0 to be loopback - # TODO: could warn if already set - ports[0] = { - 'description': 'loopback', 'category': 'loopback'} - self._graph.node[node_id]['_ports'] = ports - return - - if self._overlay_id == "graphics": - # TODO: remove once graphics removed - return - - phy_graph = self._anm.overlay_nx_graphs['phy'] - for node_id in nbunch: - try: - phy_interfaces = phy_graph.node[node_id]['_ports'] - except KeyError: - # Node not in phy (eg broadcast domain) - # Just do base initialisation of loopback zero - self._graph.node[node_id]['_ports'] = { - 0: {'description': 'loopback', 'category': 'loopback'}} - else: - interface_data = {'description': None, - 'category': 'physical'} - # need to do dict() to copy, otherwise all point to same memory - # location -> clobber - # TODO: update this to also get subinterfaces? - # TODO: should description and category auto fall through? - data = dict((key, dict(interface_data)) for key in - phy_interfaces) - self._graph.node[node_id]['_ports'] = data - - def add_node(self, node, retain=None, **kwargs): - """Adds node to overlay""" - nbunch = [node] - self.add_nodes_from(nbunch, - retain, **kwargs) - #TODO: test what this code is meant to do - try: - node_id = node.id - except AttributeError: - node_id = node # use the string node id - return NmNode(self.anm, self._overlay_id, node_id) - - def allocate_input_interfaces(self): - """allocates edges to interfaces""" - # TODO: move this to ank utils? or extra step in the anm? - if self._overlay_id != "input": - log.debug("Tried to allocate interfaces to %s" % overlay_id) - return - - if all(len(node['input'].raw_interfaces) > 0 for node in self) \ - and all(len(edge['input'].raw_interfaces) > 0 for edge in - self.edges()): - log.debug("Input interfaces allocated") - return # interfaces allocated - elif self.data.interfaces_allocated == True: - # explicitly flagged as allocated - return - else: - log.info('Automatically assigning input interfaces') - - # Initialise loopback zero on node - for node in self: - node.raw_interfaces = {0: - {'description': 'loopback', 'category': 'loopback'}} - - ebunch = sorted(self.edges()) - for edge in ebunch: - src = edge.src - dst = edge.dst - src_int_id = src._add_interface('%s to %s' % (src.label, - dst.label)) - dst_int_id = dst._add_interface('%s to %s' % (dst.label, - src.label)) - edge.raw_interfaces = { - src.id: src_int_id, - dst.id: dst_int_id} - - def number_of_edges(self, node_a, node_b): - return self._graph.number_of_edges(node_a, node_b) - - def __delitem__(self, key): - """Alias for remove_node. Allows - del overlay[node] - """ - # TODO: needs to support node types - self.remove_node(key) - - def remove_nodes_from(self, nbunch): - """Removes set of nodes from nbunch""" - - try: - nbunch = unwrap_nodes(nbunch) - except AttributeError: - pass # don't need to unwrap - - self._graph.remove_nodes_from(nbunch) - - def remove_node(self, node_id): - """Removes a node from the overlay""" - if isinstance(node_id, NmNode): - node_id = node_id.node_id - - self._graph.remove_node(node_id) - - def add_edge(self, src, dst, retain=None, **kwargs): - """Adds an edge to the overlay""" - - if not retain: - retain = [] - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - retval = self.add_edges_from([(src, dst)], retain, **kwargs) - return retval[0] - - def remove_edges_from(self, ebunch): - """Removes set of edges from ebunch""" - - try: - ebunch = unwrap_edges(ebunch) - except AttributeError: - pass # don't need to unwrap - - self._graph.remove_edges_from(ebunch) - - def add_edges(self, *args, **kwargs): - """Adds a set of edges. Alias for add_edges_from""" - - self.add_edges_from(args, kwargs) - - def add_edges_from(self, ebunch, bidirectional=False, retain=None, - warn=True, **kwargs): - """Add edges. Unlike NetworkX, can only add an edge if both - src and dst in graph already. - If they are not, then they will not be added (silently ignored) - - - Retains interface mappings if they are present (this is why ANK - stores the interface reference on the edges, as it simplifies - cross-layer access, as well as split, aggregate, etc retaining the - interface bindings)_ - - Bidirectional will add edge in both directions. Useful if going - from an undirected graph to a - directed, eg G_in to G_bgp - #TODO: explain "retain" and ["retain"] logic - - if user wants to add from another overlay, first go g_x.edges() - then add from the result - - allow (src, dst, ekey), (src, dst, ekey, data) for the ank utils - """ - - if not retain: - retain = [] - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - if self.is_multigraph(): - #used_keys = self._graph.adj[u][v] - from collections import defaultdict - used_keys = defaultdict(dict) - - all_edges = [] - for in_edge in ebunch: - """Edge could be one of: - - NmEdge - - (NmNode, NmNode) - - (NmPort, NmPort) - - (NmNode, NmPort) - - (NmPort, NmNode) - - (string, string) - """ - # This is less efficient than nx add_edges_from, but cleaner logic - # TODO: could put the interface data into retain? - data = {'_ports': {}} # to retain - ekey = None # default is None (nx auto-allocates next int) - - # convert input to a NmEdge - src = dst = None - if isinstance(in_edge, NmEdge): - edge = in_edge # simple case - ekey = edge.ekey # explictly set ekey - src = edge.src.node_id - dst = edge.dst.node_id - - # and copy retain data - data = dict((key, edge.get(key)) for key in retain) - ports = {k: v for k, v in edge.raw_interfaces.items() - if k in self._graph} # only if exists in this overlay - # TODO: debug log if skipping a binding? - data['_ports'] = ports - - # this is the only case where copy across data - # but want to copy attributes for all cases - - elif len(in_edge) == 2: - in_a, in_b = in_edge[0], in_edge[1] - - if isinstance(in_a, NmNode) and isinstance(in_b, NmNode): - src = in_a.node_id - dst = in_b.node_id - - elif isinstance(in_a, NmPort) and isinstance(in_b, NmPort): - src = in_a.node.node_id - dst = in_b.node.node_id - ports = {} - if src in self: - ports[src] = in_a.interface_id - if dst in self: - ports[dst] = in_b.interface_id - data['_ports'] = ports - - elif isinstance(in_a, NmNode) and isinstance(in_b, NmPort): - src = in_a.node_id - dst = in_b.node.node_id - ports = {} - if dst in self: - ports[dst] = in_b.interface_id - data['_ports'] = ports - - elif isinstance(in_a, NmPort) and isinstance(in_b, NmNode): - src = in_a.node.node_id - dst = in_b.node_id - ports = {} - if src in self: - ports[src] = in_a.interface_id - data['_ports'] = ports - - elif in_a in self and in_b in self: - src = in_a - dst = in_b - - elif len(in_edge) == 3: - # (src, dst, ekey) format - # or (src, dst, data) format - in_a, in_b, in_c = in_edge[0], in_edge[1], in_edge[2] - if in_a in self and in_b in self: - src = in_a - dst = in_b - # TODO: document the following logic - if self.is_multigraph() and not isinstance(in_c, dict): - ekey = in_c - else: - data = in_c - - elif len(in_edge) == 4: - # (src, dst, ekey, data) format - in_a, in_b = in_edge[0], in_edge[1] - if in_a in self and in_b in self: - src = in_a - dst = in_b - ekey = in_edge[2] - data = in_edge[3] - - # TODO: if edge not set at this point, give error/warn - - # TODO: add check that edge.src and edge.dst exist - if (src is None or dst is None) and warn: - log.warning("Unsupported edge %s" % str(in_edge)) - if not(src in self and dst in self): - if warn: - self.log.debug("Not adding edge %s, src/dst not in overlay" - % str(in_edge)) - continue - - # TODO: warn if not multigraph and edge already exists - don't - # add/clobber - #TODO: double check this logic + add test case - data.update(**kwargs) - if self.is_multigraph() and ekey is None: - # specifically allocate a key - if src in used_keys and dst in used_keys[src]: - pass # already established - else: - try: - used_keys[src][dst] = self._graph.adj[src][dst].keys() - except KeyError: - # no edges exist - used_keys[src][dst] = [] - - # now have the keys mapping - ekey=len(used_keys[src][dst]) - while ekey in used_keys[src][dst]: - ekey+=1 - - used_keys[src][dst].append(ekey) - - edges_to_add = [] - if self.is_multigraph(): - edges_to_add.append((src, dst, ekey, dict(data))) - if bidirectional: - edges_to_add.append((dst, src, ekey, dict(data))) - else: - edges_to_add.append((src, dst, dict(data))) - if bidirectional: - edges_to_add.append((dst, src, dict(data))) - - - #TODO: warn if not multigraph - - self._graph.add_edges_from(edges_to_add) - all_edges += edges_to_add - - if self.is_multigraph(): - return [ - NmEdge(self.anm, self._overlay_id, src, dst, ekey) if ekey - else NmEdge(self.anm, self._overlay_id, src, dst) # default no ekey set - for src, dst, ekey, _ in all_edges] - else: - return [NmEdge(self.anm, self._overlay_id, src, dst) - for src, dst, _ in all_edges] - - def update(self, nbunch=None, **kwargs): - """Sets property defined in kwargs to all nodes in nbunch""" - - if nbunch is None: - nbunch = self.nodes() - - if nbunch in self: - nbunch = [nbunch] # single node in the list for iteration - - # if the node is in the underlying networkx graph, then map to an - # overlay node - nbunch = [self.node(n) if n in self._graph else n for n in nbunch] - for node in nbunch: - for (key, value) in kwargs.items(): - node.set(key, value) - - def subgraph(self, nbunch, name=None): - """""" - - nbunch = (n.node_id for n in nbunch) # only store the id in overlay - from autonetkit.anm.subgraph import OverlaySubgraph - return OverlaySubgraph(self._anm, self._overlay_id, - self._graph.subgraph(nbunch), name) diff --git a/autonetkit/anm/graph_data.py b/autonetkit/anm/graph_data.py deleted file mode 100644 index 3f1e2f83..00000000 --- a/autonetkit/anm/graph_data.py +++ /dev/null @@ -1,46 +0,0 @@ -class NmGraphData(object): - - """API to access link in network""" - - def __init__(self, anm, overlay_id): - - # Set using this method to bypass __setattr__ - - object.__setattr__(self, 'anm', anm) - object.__setattr__(self, 'overlay_id', overlay_id) - - def __repr__(self): - """""" - - return 'Data for (%s, %s)' % (self.anm, self.overlay_id) - - def dump(self): - """""" - - print str(self._graph.graph) - - @property - def _graph(self): - - # access underlying graph for this NmNode - - return self.anm.overlay_nx_graphs[self.overlay_id] - - def __contains__(self, key): - return key in self._graph.graph - - def __getattr__(self, key): - return self._graph.graph.get(key) - - def __setattr__(self, key, val): - self._graph.graph[key] = val - - def __getitem__(self, key): - """""" - - return self._graph.graph.get(key) - - def __setitem__(self, key, val): - """""" - - self._graph.graph[key] = val diff --git a/autonetkit/anm/interface.py b/autonetkit/anm/interface.py deleted file mode 100644 index 4ec8ac84..00000000 --- a/autonetkit/anm/interface.py +++ /dev/null @@ -1,285 +0,0 @@ -import logging - -import autonetkit.log as log -from autonetkit.log import CustomAdapter -from autonetkit.anm.ank_element import AnkElement - - -class NmPort(AnkElement): - - def __init__(self, anm, overlay_id, node_id, interface_id): - object.__setattr__(self, 'anm', anm) - object.__setattr__(self, 'overlay_id', overlay_id) - object.__setattr__(self, 'node_id', node_id) - object.__setattr__(self, 'interface_id', interface_id) - #logger = logging.getLogger("ANK") - #logstring = "Interface: %s" % str(self) - #logger = CustomAdapter(logger, {'item': logstring}) - logger = log - object.__setattr__(self, 'log', logger) - self.init_logging("port") - - def __key(self): - """Note: key doesn't include overlay_id to allow fast cross-layer comparisons""" - - # based on http://stackoverflow.com/q/2909106 - - return (self.interface_id, self.node_id) - - def __hash__(self): - """""" - - return hash(self.__key()) - - def __deepcopy__(self, memo): - # TODO: workaround - need to fix - # from - # http://stackoverflow.com/questions/1500718/what-is-the-right-way-to-override-the-copy-deepcopy-operations-on-an-object-in-p - pass - - def __repr__(self): - try: - description = self.id or self.description - return '%s.%s' % (description, self.node) - except TypeError: - return "" - log.warning("No label for %s %s" % - (self.interface_id, self.node_id)) - - def __eq__(self, other): - return self.__key() == other.__key() - - def __nonzero__(self): - - # TODO: work out why description and category being set/copied to each - # overlay - if self.interface_id not in self.node.raw_interfaces: - return False - - try: - interface = self._interface - except KeyError: - return False - - return len(interface) > 0 # if interface data set - - def __lt__(self, other): - - # TODO: check how is comparing the node - - return (self.node, self.interface_id) < (other.node, - other.interface_id) - - @property - def id(self): - """Returns id of node, falls-through to phy if not set on this overlay - - """ - # TODO: make generic function for fall-through properties stored on the - # anm - key = "id" - if key in self._interface: - return self._interface.get(key) - else: - if self.overlay_id == "input": - return # Don't fall upwards from input -> phy as may not exist - if self.overlay_id == "phy": - return # Can't fall from phy -> phy (loop) - - # try from phy - - if not self.node['phy']: - # node not in phy - return - - try: - # return - # self.anm.overlay_nx_graphs['phy'].node[self.node_id]['asn'] - return self['phy'].id - except KeyError: - return # can't get from this overlay or phy -> not found - - @property - def is_bound(self): - # TODO: make this a function - """Returns if this interface is bound to an edge on this layer""" - - return len(self.edges()) > 0 - - def __str__(self): - return self.__repr__() - - @property - def _graph(self): - """Return graph the node belongs to""" - - return self.anm.overlay_nx_graphs[self.overlay_id] - - @property - def _node(self): - """Return graph data the node belongs to""" - - return self._graph.node[self.node_id] - - @property - def _interface(self): - """Return data dict for the interface""" - try: - return self.node.raw_interfaces[self.interface_id] - except (KeyError, IndexError): - if not self.node_id in self._graph: - # node not in overlay - return - log.warning("Unable to access interface %s in %s", - "node %s not present in overlay" % (self.interface_id, - self.overlay_id, self.node_id)) - return - - log.warning('Unable to find interface %s in %s' - % (self.interface_id, self.node)) - return None - - @property - def phy(self): - if self.overlay_id == 'phy': - return self - return NmPort(self.anm, 'phy', self.node_id, self.interface_id) - - def __getitem__(self, overlay_id): - """Returns corresponding interface in specified overlay""" - - if not self.anm.has_overlay(overlay_id): - log.warning( - 'Trying to access interface %s for non-existent overlay %s', self, overlay_id) - return None - - if not self.node_id in self.anm.overlay_nx_graphs[overlay_id]: - log.debug('Trying to access interface %s for non-existent node %s in overlay %s', - self, self.node_id, self.overlay_id) - return None - - try: - return NmPort(self.anm, overlay_id, - self.node_id, self.interface_id) - except KeyError: - return - - @property - def is_loopback(self): - """""" - - return self.category == 'loopback' or self.phy.category == 'loopback' - - @property - def is_physical(self): - """""" - - return self.category == 'physical' or self.phy.category == 'physical' - - @property - def description(self): - """""" - - return_val = None - try: - return_val = self._interface.get('description') - except IndexError: - return_val = self.interface_id - - if return_val is not None: - return return_val - - if self.overlay_id != 'phy': # prevent recursion - self.phy._interface.get('description') - - @property - def is_loopback_zero(self): - return self.interface_id == 0 and self.is_loopback - - @property - def category(self): - """""" - -# TODO: make 0 correctly access interface 0 -> copying problem -# TODO: this needs a bugfix rather than the below hard-coded workaround - - if self.interface_id == 0: - return 'loopback' - - if self.overlay_id == 'input': - return object.__getattr__(self, 'category') - elif self.overlay_id != 'phy': - - # prevent recursion - - return self.phy._interface.get('category') - - retval = self._interface.get('category') - if retval: - return retval - - @property - def node(self): - """Returns parent node of this interface""" - - from autonetkit.anm.node import NmNode - return NmNode(self.anm, self.overlay_id, self.node_id) - - def dump(self): - return str(self._interface.items()) - - def __getattr__(self, key): - """Returns interface property""" - - try: - return self._interface.get(key) - except KeyError: - return - - def get(self, key): - """For consistency, node.get(key) is neater - than getattr(interface, key)""" - - try: - return getattr(self, key) - except AttributeError, exc: - if self._interface is None: - raise KeyError("Interface %s not found on node %s" % - (self.interface_id, self.node_id)) - - raise exc - - def __setattr__(self, key, val): - """Sets interface property""" - - try: - self._interface[key] = val - except KeyError, e: - log.warning(e) - - # self.set(key, val) - - def set(self, key, val): - """For consistency, node.set(key, value) is neater - than setattr(interface, key, value)""" - - return self.__setattr__(key, val) - - def edges(self): - """Returns all edges from node that have this interface ID - This is the convention for binding an edge to an interface""" - - # edges have _interfaces stored as a dict of {node_id: interface_id, } - - valid_edges = [e for e in self.node.edges() if self.node_id - in e.raw_interfaces and e.raw_interfaces[self.node_id] - == self.interface_id] - return list(valid_edges) - - def neighbors(self): - """Returns interfaces on nodes that are linked to this interface - Can get nodes using [i.node for i in interface.neighbors()] - """ - - edges = self.edges() - return [e.dst_int for e in edges] diff --git a/autonetkit/anm/network_model.py b/autonetkit/anm/network_model.py deleted file mode 100644 index 4598ab15..00000000 --- a/autonetkit/anm/network_model.py +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import time - -import autonetkit.log as log -import networkx as nx -from autonetkit.anm.graph import NmGraph -from autonetkit.anm.ank_element import AnkElement - - -class NetworkModel(AnkElement): - - """""" - - def __init__(self, all_multigraph=False): - """""" - - self.all_multigraph = all_multigraph - self._overlays = {} - self.add_overlay('input') - self.add_overlay('phy') - self.add_overlay('graphics') - self.add_overlay('_dependencies', directed=True) - - self.label_seperator = '_' - self.label_attrs = ['label'] - self._build_node_label() - self.timestamp = time.strftime('%Y%m%d_%H%M%S', - time.localtime()) - - # TODO: make this a proper method - self.init_logging("ANM") - - def __repr__(self): - """""" - - return 'ANM %s' % self.timestamp - - def __len__(self): - return len(self._overlays) - - @property - def overlay_nx_graphs(self): - """""" - - return self._overlays - - def has_overlay(self, overlay_id): - """""" - - return overlay_id in self._overlays - - def dump(self): - import autonetkit.ank_json as ank_json - data = ank_json.jsonify_anm(self) - - # data = data.replace("\\n", "\n") - # data = data.replace('\\"', '\"') - - return data - - def save(self): - """""" - - # TODO: take optional filename as parameter - - import autonetkit.ank_json as ank_json - import os - import gzip - archive_dir = os.path.join('versions', 'anm') - if not os.path.isdir(archive_dir): - os.makedirs(archive_dir) - - data = ank_json.jsonify_anm(self) - json_file = 'anm_%s.json.gz' % self.timestamp - json_path = os.path.join(archive_dir, json_file) - log.debug('Saving to %s' % json_path) - with gzip.open(json_path, 'wb') as json_fh: - json_fh.write(data) - - def restore_latest(self, directory=None): - """Restores latest saved ANM""" - - import os - import glob - if not directory: - directory = os.path.join('versions', 'anm') - - glob_dir = os.path.join(directory, '*.json.gz') - pickle_files = glob.glob(glob_dir) - pickle_files = sorted(pickle_files) - try: - latest_file = pickle_files[-1] - except IndexError: - - # No files loaded - - log.warning('No previous ANM saved. Please compile new ANM') - return - self.restore(latest_file) - - def restore(self, pickle_file): - """""" - - import json - import gzip - import autonetkit.ank_json as ank_json - log.debug('Restoring %s' % pickle_file) - with gzip.open(pickle_file, 'r') as filehandle: - data = json.load(filehandle) - for (overlay_id, graph_data) in data.items(): - - self._overlays[overlay_id] = \ - ank_json.ank_json_loads(graph_data) - - ank_json.rebind_interfaces(self) - - def restore_from_json(self, in_data): - import json - import autonetkit.ank_json as ank_json - data = json.loads(in_data) - for (overlay_id, graph_data) in data.items(): - - self._overlays[overlay_id] = \ - ank_json.ank_json_loads(graph_data) - - ank_json.rebind_interfaces(self) - - @property - def _phy(self): - """""" - - return NmGraph(self, 'phy') - - def initialise_graph(self, graph): - """Sets input graph. Converts to undirected. - Initialises graphics overlay.""" - - # TODO: remove this dependency from workflow - - graph = nx.Graph(graph) - g_graphics = self['graphics'] - g_in = self.add_overlay('input', graph=graph, directed=False) - g_graphics.add_nodes_from(g_in, retain=['x', 'y', 'device_type', - 'device_subtype', 'pop', 'label', 'asn']) - return g_in - - def initialise_input(self, graph): - """Initialises input graph""" - - # remove current input - del self._overlays['input'] - - overlay = self.add_overlay('input', graph=graph) - overlay.allocate_input_interfaces() - return overlay - - def add_overlay(self, name, nodes=None, graph=None, - directed=False, multi_edge=False, retain=None): - """Adds overlay graph of name name""" - # TODO: refactor this logic - log.debug("Adding overlay %s" % name) - - multi_edge = multi_edge or self.all_multigraph - - if graph: - if not directed and graph.is_directed(): - if multi_edge or graph.is_multigraph(): - new_graph = nx.MultiGraph(graph) - else: - # TODO: put into dev log - log.debug('Converting graph %s to undirected' % name) - new_graph = nx.Graph(graph) - else: - if multi_edge or graph.is_multigraph(): - new_graph = nx.MultiGraph(graph) - else: - new_graph = nx.Graph(graph) - elif directed: - - if multi_edge: - new_graph = nx.MultiDiGraph() - else: - new_graph = nx.DiGraph() - else: - if multi_edge: - new_graph = nx.MultiGraph() - else: - new_graph = nx.Graph() - - self._overlays[name] = new_graph - overlay = NmGraph(self, name) - - if nodes: - retain = retain or [] # default is an empty list - overlay.add_nodes_from(nodes, retain) - - return overlay - - def __iter__(self): - return iter(NmGraph(self, name) for name in self.overlays()) - - def overlays(self): - # TODO: rename to overlay ids - """""" - - return self._overlays.keys() - - def devices(self, *args, **kwargs): - """""" - - return self._phy.filter(*args, **kwargs) - - def __getitem__(self, key): - """""" - - return NmGraph(self, key) - - def overlay(self, key): - """""" - - return NmGraph(self, key) - - def node_label(self, node): - """Returns node label from physical graph""" - - # TODO: refactor out node label - return self.default_node_label(node) - - def _build_node_label(self): - """""" - - def custom_label(node): - """ - #TODO: rewrite this logic/retire this function - retval = [] - if node in self['phy']: - for key in self.label_attrs: - val = self._overlays['phy'].node[node.node_id].get(key) - if val is not None: - retval.append(val) - - if len(retval): - return self.label_seperator.join(retval) - """ - - - return self.label_seperator.join(str(self._overlays['phy' - ].node[node.node_id].get(val)) for val in - self.label_attrs - if self._overlays['phy'].node[node.node_id].get(val) is not None) - - self.node_label = custom_label - - def set_node_label(self, seperator, label_attrs): - """""" - - try: - label_attrs.lower() - label_attrs = [label_attrs] # was a string, put into list - except AttributeError: - pass # already a list - - self.label_seperator = seperator - self.label_attrs = label_attrs diff --git a/autonetkit/anm/node.py b/autonetkit/anm/node.py deleted file mode 100644 index 23e7221a..00000000 --- a/autonetkit/anm/node.py +++ /dev/null @@ -1,748 +0,0 @@ -import itertools -import logging -from functools import total_ordering - -import autonetkit -import autonetkit.log as log -from autonetkit.anm.interface import NmPort -from autonetkit.log import CustomAdapter -from autonetkit.anm.ank_element import AnkElement - - - -@total_ordering -class NmNode(AnkElement): - - """NmNode""" - - def __init__(self, anm, overlay_id, node_id): - - # Set using this method to bypass __setattr__ - - object.__setattr__(self, 'anm', anm) - object.__setattr__(self, 'overlay_id', overlay_id) -# should be able to use _graph from here as anm and overlay_id are defined - object.__setattr__(self, 'node_id', node_id) - #logger = logging.getLogger("ANK") - #logstring = "Node: %s" % str(self) - #logger = CustomAdapter(logger, {'item': logstring}) - logger = log - object.__setattr__(self, 'log', logger) - self.init_logging("node") - - def __hash__(self): - """""" - - return hash(self.node_id) - - def __nonzero__(self): - """Allows for checking if node exists - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> bool(r1) - True - >>> r_test = anm['phy'].node("test") - >>> bool(r_test) - False - - """ - - return self.node_id in self._graph - - def __iter__(self): - """Shortcut to iterate over the physical interfaces of this node - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> list(r1) - [eth0.r1, eth1.r1] - - - """ - - return iter(self.interfaces(category='physical')) - - def __len__(self): - """Number of phyiscal interfaces of a node - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> len(r1) - 2 - >>> r2 = anm['phy'].node("r2") - >>> len(r2) - 3 - - - """ - return len(list(self.__iter__())) - - def __eq__(self, other): - """ - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> rb = anm['phy'].node("r1") - >>> r1 == rb - True - >>> r2 = anm['phy'].node("r2") - >>> r1 == r2 - False - - Can also compare to a label - - >>> r1 == "r1" - True - >>> r1 == "r2" - False - - """ - - try: - return self.node_id == other.node_id - except AttributeError: - return self.node_id == other # eg compare Node to label - - def __ne__(self, other): - """ - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r2 = anm['phy'].node("r2") - >>> r1 != r2 - True - - - """ - return not self.__eq__(other) - - @property - def loopback_zero(self): - """ - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> lo_zero = r1.loopback_zero - - """ - - # TODO: initialise id for loopback zero? - # TODO: Set category for loopback zero to be "loopback" - - return (i for i in self.interfaces('is_loopback_zero')).next() - - def physical_interfaces(self, *args, **kwargs): - """""" - kwargs['category'] = "physical" - return self.interfaces(*args, **kwargs) - - def loopback_interfaces(self, *args, **kwargs): - #TODO: allow abiility to skip loopback zero - """""" - kwargs['category'] = "loopback" - return self.interfaces(*args, **kwargs) - - def is_multigraph(self): - return self._graph.is_multigraph() - - def __lt__(self, other): - """ - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r2 = anm['phy'].node("r2") - >>> r1 < r2 - True - - """ - -# want [r1, r2, ..., r11, r12, ..., r21, r22] not [r1, r11, r12, r2, r21, r22] -# so need to look at numeric part - import string - # TODO: use human sort from StackOverflow - - # sort on label if available - if self.label is not None: - self_node_id = self.label - else: - self_node_id = self.node_id - - if other.label is not None: - other_node_id = other.label - else: - other_node_id = other_node_id - - try: - self_node_string = [x for x in self_node_id if x - not in string.digits] - other_node_string = [x for x in self_node_id if x - not in string.digits] - except TypeError: - - # e.g. non-iterable category, such as an int node_id - - pass - else: - if self_node_string == other_node_string: - self_node_id = """""".join([x for x in self_node_id - if x in string.digits]) - other_node_id = """""".join([x for x in other_node_id - if x in string.digits]) - try: - self_node_id = int(self_node_id) - except ValueError: - pass # not a number - try: - other_node_id = int(other_node_id) - except ValueError: - pass # not a number - - return (self.asn, self_node_id) < (other.asn, other_node_id) - - def _next_int_id(self): - """""" - -# returns next free interface I - - for int_id in itertools.count(1): # start at 1 as 0 is loopback - if int_id not in self._ports: - return int_id - - # TODO: interface function access needs to be cleaned up - - def _add_interface(self, description=None, category='physical', - **kwargs): - """""" - - data = dict(kwargs) - - if self.overlay_id != 'phy' and self['phy']: - next_id = self['phy']._next_int_id() - self['phy']._ports[next_id] = {'category': category, - 'description': description} - - # TODO: fix this workaround for not returning description from phy - # graph - - data['description'] = description - else: - next_id = self._next_int_id() - data['category'] = category # store category on node - data['description'] = description - - self._ports[next_id] = data - return next_id - - def _sync_loopbacks(self, interface_id): - """Syncs a newly added loopback across all overlays the - node is in""" - for overlay in self.anm: - if self not in overlay: - continue - - if overlay._overlay_id in ("phy", self.overlay_id): - # don't copy to self or phy as already there - continue - - if overlay._overlay_id in ("graphics"): - """ - TODO: debug why get problem for graphics - File "/autonetkit/autonetkit/anm/node.py", line 257, in _sync_loopbacks - o_node._ports[interface_id] = {"category": "loopback"} - IndexError: list assignment index out of range - None - """ - # don't copy to self or phy as already there - continue - - o_node = overlay.node(self) - if interface_id in o_node._ports: - # something has gone wrong - allocated the id over the top - # shouldn't happen since the next free id is from the phy - - log.warning("Internal consistency error with copying loopback " - "interface %s to %s in %s", interface_id, self, overlay) - continue - - o_node._ports[interface_id] = {"category": "loopback"} - - def add_loopback(self, *args, **kwargs): - '''Public function to add a loopback interface''' - - interface_id = self._add_interface(category='loopback', *args, - **kwargs) - - #TODO: want to add loopbacks to all overlays the node is in - self._sync_loopbacks(interface_id) - return NmPort(self.anm, self.overlay_id, - self.node_id, interface_id) - - def add_interface(self, *args, **kwargs): - """Public function to add interface""" - - interface_id = self._add_interface(*args, **kwargs) - return NmPort(self.anm, self.overlay_id, - self.node_id, interface_id) - - def interfaces(self, *args, **kwargs): - """Public function to view interfaces""" - - def filter_func(interface): - """Filter based on args and kwargs""" - - return all(getattr(interface, key) for key in args) \ - and all(getattr(interface, key) == val for (key, - val) in kwargs.items()) - - all_interfaces = iter(NmPort(self.anm, - self.overlay_id, self.node_id, - interface_id) for interface_id in - self._interface_ids()) - - retval = [i for i in all_interfaces if filter_func(i)] - return retval - - def interface(self, key): - """Returns interface based on interface id""" - - try: - if key.interface_id in self._interface_ids(): - return NmPort(self.anm, self.overlay_id, - self.node_id, key.interface_id) - except AttributeError: - - # try with key as id - - try: - if key in self._interface_ids(): - return NmPort(self.anm, self.overlay_id, - self.node_id, key) - except AttributeError: - - # no match for either - - log.warning('Unable to find interface %s in %s ' - % (key, self)) - return None - - # try searching for the "id" attribute of the interface eg - # GigabitEthernet0/0 if set - search = list(self.interfaces(id=key)) - # TODO: warn if more than one match ie len > 1 - if len(search): - return search[0] # first result - - search = list(self.interfaces(description=key)) - # TODO: warn if more than one match ie len > 1 - if len(search): - return search[0] # first result - - def _interface_ids(self): - """Returns interface ids for this node""" - # TODO: use from this layer, otherwise can get errors iterating when eg - # vrfs - return self._ports.keys() - - if self.overlay_id != 'phy' and self['phy']: - - # graph isn't physical, and node exists in physical graph -> use - # the interface mappings from phy - - return self['phy']._graph.node[self.node_id]['_ports' - ].keys() - else: - try: - return self._ports.keys() - except KeyError: - self.log.debug('No interfaces initialised') - return [] - - @property - def _ports(self): - """Returns underlying interface dict""" - - try: - return self._graph.node[self.node_id]['_ports'] - except KeyError: - self.log.debug('No interfaces initialised for') - return [] - - @property - def _graph(self): - """Return graph the node belongs to""" - - try: - return self.anm.overlay_nx_graphs[self.overlay_id] - except Exception, e: - log.warning("Error accessing overlay %s for node %s: %s" % - (self.overlay_id, self.node_id, e)) - - @property - def _nx_node_data(self): - """Return NetworkX node data for the node""" - try: - return self._graph.node[self.node_id] - except Exception, e: - log.warning("Error accessing node data %s for node %s: %s" % - (self.overlay_id, self.node_id, e)) - - def is_router(self): - """Either from this graph or the physical graph - - >>> anm = autonetkit.topos.mixed() - >>> r1 = anm['phy'].node("r1") - >>> r1.is_router() - True - >>> s1 = anm['phy'].node("s1") - >>> s1.is_router() - False - >>> sw1 = anm['phy'].node("sw1") - >>> sw1.is_router() - False - - """ - #self.log_info("add int") - - return self.device_type == 'router' or self['phy'].device_type \ - == 'router' - - def is_hub(self): - """Either from this graph or the physical graph - - """ - #self.log_info("add int") - - return self.device_type == 'hub' or self['phy'].device_type \ - == 'hub' - - def is_device_type(self, device_type): - """Generic user-defined cross-overlay search for device_type - either from this graph or the physical graph""" - - return self.device_type == device_type or self['phy'].device_type \ - == device_type - - def is_switch(self): - """Returns if device is a switch - - >>> anm = autonetkit.topos.mixed() - >>> r1 = anm['phy'].node("r1") - >>> r1.is_switch() - False - >>> s1 = anm['phy'].node("s1") - >>> s1.is_switch() - False - >>> sw1 = anm['phy'].node("sw1") - >>> sw1.is_switch() - True - - """ - - return self.device_type == 'switch' or self['phy'].device_type \ - == 'switch' - - def is_server(self): - """Returns if device is a server - - >>> anm = autonetkit.topos.mixed() - >>> r1 = anm['phy'].node("r1") - >>> r1.is_server() - False - >>> s1 = anm['phy'].node("s1") - >>> s1.is_server() - True - >>> sw1 = anm['phy'].node("sw1") - >>> sw1.is_server() - False - - """ - - return self.device_type == 'server' or self['phy'].device_type \ - == 'server' - - def is_l3device(self): - """Layer 3 devices: router, server, cloud, host - ie not switch - - >>> anm = autonetkit.topos.mixed() - >>> r1 = anm['phy'].node("r1") - >>> r1.is_l3device() - True - >>> s1 = anm['phy'].node("s1") - >>> s1.is_l3device() - True - >>> sw1 = anm['phy'].node("sw1") - >>> sw1.is_l3device() - False - - """ - return self.is_router() or self.is_server() - - def __getitem__(self, key): - """Get item key""" - - return NmNode(self.anm, key, self.node_id) - - @property - def raw_interfaces(self): - """Direct access to the interfaces dictionary, used by ANK modules""" - return self._ports - - @raw_interfaces.setter - def raw_interfaces(self, value): - self._ports = value - - @property - def asn(self): - """Returns ASN of this node - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r1.asn - 1 - >>> r5 = anm['phy'].node("r5") - >>> r5.asn - 2 - - """ - # TODO: make a function (not property) - # TODO: refactor, for nodes created such as virtual switches - - try: - return self._graph.node[self.node_id]['asn'] # not in this graph - except KeyError: - - # try from phy - - try: - return self.anm.overlay_nx_graphs['phy' - ].node[self.node_id]['asn'] - except KeyError: - if self.node_id not in self.anm.overlay_nx_graphs['phy' - ]: - message = \ - 'Node id %s not found in physical overlay' \ - % self.node_id - if self.overlay_id == 'input': - - # don't warn, most likely node not copied across - - log.debug(message) - else: - log.debug(message) - return - - @asn.setter - def asn(self, value): - # TODO: make a function (not property) - - # TODO: double check this logic - - try: - self.anm.overlay_nx_graphs['phy'].node[self.node_id]['asn' - ] = value - except KeyError: - - # set ASN directly on the node, eg for collision domains - - self._graph.node[self.node_id]['asn'] = value - - @property - def id(self): - """Returns node id""" - - return self.node_id - - @property - def _overlay(self): - """Access overlay graph for this node""" - - from autonetkit.anm.graph import NmGraph - return NmGraph(self.anm, self.overlay_id) - - def degree(self): - """Returns degree of node - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r1.degree() - 2 - >>> r2 = anm['phy'].node("r2") - >>> r2.degree() - 3 - - """ - - # TODO: add example for multi-edge - - return self._graph.degree(self.node_id) - - def neighbors(self, *args, **kwargs): - """Returns neighbors of node - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r1.neighbors() - [r2, r3] - >>> r2 = anm['phy'].node("r2") - >>> r2.neighbors() - [r4, r1, r3] - - Can also filter - - >>> r2.neighbors(asn=1) - [r1, r3] - >>> r2.neighbors(asn=2) - [r4] - - - """ - - neighs = list(NmNode(self.anm, self.overlay_id, node) - for node in self._graph.neighbors(self.node_id)) - - return self._overlay.filter(neighs, *args, **kwargs) - - def neighbor_interfaces(self, *args, **kwargs): - """ - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r1.neighbor_interfaces() - [eth0.r2, eth0.r3] - >>> r2 = anm['phy'].node("r2") - >>> r2.neighbor_interfaces() - [eth0.r4, eth0.r1, eth1.r3] - - """ - - # TODO: implement filtering for args and kwargs - - if len(args) or len(kwargs): - log.warning("Attribute-based filtering not currently", - "supported for neighbor_interfaces") - - return list(edge.dst_int for edge in self.edges()) - - @property - # TODO: make a function to reflect dynamic nature: constructed from other - # attributes - def label(self): - """Returns node label (mapped from ANM)""" - - return self.__repr__() - - def dump(self): - """Dump attributes of this node""" - - data = dict(self._nx_node_data) - try: - del data['_ports'] - except KeyError: - pass # no interfaces set - return str(data) - - def edges(self, *args, **kwargs): - """Edges to/from this node - - >>> anm = autonetkit.topos.house() - >>> r1 = anm['phy'].node("r1") - >>> r1.edges() - [(r1, r2), (r1, r3)] - >>> r2 = anm['phy'].node("r2") - >>> r2.edges() - [(r2, r4), (r2, r1), (r2, r3)] - - """ - - return list(self._overlay.edges(self, *args, **kwargs)) - - def __str__(self): - return str(self.__repr__()) - - def __repr__(self): - """Try label if set in overlay, otherwise from physical, - otherwise node id""" - - try: - return self.anm.node_label(self) - except KeyError: - try: - return self._graph.node[self.node_id]['label'] - except KeyError: - return self.node_id # node not in physical graph - except AttributeError: - return self.node_id - - def __getattr__(self, key): - """Returns node property - This is useful for accesing attributes passed through from graphml""" - # TODO: refactor/document this logic - - try: - node_data = self._graph.node[self.node_id] - except KeyError: - # TODO: only carry out this logic if "Strict mode" - if key == "device_type": - # TODO: tidy accessors so this doesn't occur, and remove the - # suppress - return - self.log.debug("Cannot access %s: node not present in %s" - % (key, self.overlay_id)) - return - else: - try: - result = node_data[key] - return result - except KeyError: - #TODO: check if in self['phy'[ here] - if self.overlay_id != "phy": - if key == "device_type": - return self['phy'].device_type - if key == "device_subtype": - return self['phy'].device_subtype - - # from http://stackoverflow.com/q/2654113 - #self.log.debug( - #"Accessing unset attribute %s in %s" % (key, - #self.overlay_id)) - return - - # map through to phy - - def get(self, key): - """For consistency, node.get(key) is neater than getattr(node, key)""" - - return getattr(self, key) - - def __setattr__(self, key, val): - """Sets node property - This is useful for accesing attributes passed through from graphml""" - - """TODO: - TODO: look at mapping the object __dict__ straight to the graph.node[self.node_id] - TODO: fix wrt using @x.setter won't work due to following: - as per - http://docs.python.org/2/reference/datamodel.html#customizing-attribute-access - """ - - # TODO: fix workaround for asn - - if key == 'asn': - object.__setattr__(self, 'asn', val) - - if key == 'raw_interfaces': - object.__setattr__(self, 'raw_interfaces', val) - - try: - self._graph.node[self.node_id][key] = val - except KeyError: - self._graph.add_node(self.node_id) - self.set(key, val) - - def set(self, key, val): - """For consistency, node.set(key, value) is neater - than setattr(node, key, value)""" - - return self.__setattr__(key, val) diff --git a/autonetkit/anm/subgraph.py b/autonetkit/anm/subgraph.py deleted file mode 100644 index 0e70cb99..00000000 --- a/autonetkit/anm/subgraph.py +++ /dev/null @@ -1,17 +0,0 @@ -import autonetkit.log as log -from autonetkit.anm.base import OverlayBase - - -class OverlaySubgraph(OverlayBase): - - """OverlaySubgraph""" - - def __init__(self, anm, overlay_id, graph, name=None): - """""" - - object.__setattr__(self, '_subgraph_name', name) - super(OverlaySubgraph, self).__init__(anm, overlay_id) - self._graph = graph - - def __repr__(self): - return self._subgraph_name or 'subgraph' diff --git a/autonetkit/anm/tests/test_anm.py b/autonetkit/anm/tests/test_anm.py deleted file mode 100644 index 1961e836..00000000 --- a/autonetkit/anm/tests/test_anm.py +++ /dev/null @@ -1,207 +0,0 @@ -import autonetkit -import autonetkit.log as log - -#TODO: break into separate test functions, with setup and teardown - -def test(): - log.info("Testing ANM") - - anm = autonetkit.NetworkModel() - - g_in = anm.add_overlay("input") - - router_ids = ["r1", "r2", "r3", "r4", "r5"] - g_in.add_nodes_from(router_ids) - - g_in.update(device_type = "router") - g_in.update(asn = 1) - - positions = {'r3': (107, 250), 'r5': (380, 290), 'r1': (22, 50), 'r2': (377, 9), 'r4': (571, 229)} - for node in g_in: - node.x = positions[node][0] - node.y = positions[node][1] - eth0 = node.add_interface("eth0") - eth0.speed = 100 - - # node->node edges - input_edges = [("r1", "r2"), ("r2", "r4"), ("r3", "r4"), ("r3", "r5"), ("r1", "r3")] - input_interface_edges = [(g_in.node(src).interface(1), g_in.node(dst).interface(1)) for src, dst in input_edges] - g_in.add_edges_from(input_interface_edges) - - g_phy = anm['phy'] - g_phy.add_nodes_from(g_in, retain=["device_type", "x", "y", "asn"]) - g_phy.add_edges_from(g_in.edges()) - - g_test = anm.add_overlay("test") - g_test.add_node("test_node") - - # test interfaces - for node in g_phy: - pass - - test_node = g_phy.node("r1") - - #TODO: assert here - node_interfaces = list(test_node.interfaces()) - # test equality - #TODO: check why == uses eq() but != doesn't... - assert(not node_interfaces[0] == node_interfaces[1]) - - print list(test_node.interfaces()) - for i in test_node.interfaces(): - print i, i.dump() - - print [i.description for i in sorted(node_interfaces)] - - assert ([i.description for i in sorted(node_interfaces)] == ['loopback', 'eth0']) - - - for interface in test_node: - # test exists - assert(interface is not None) - # test __nonzero__ - assert(interface) - set_value = 123 - interface.test = set_value - get_value = interface.test - assert(set_value == get_value) # TODO: could check is indeed same object reference - - - loopback0 = test_node.interface(0) - assert(not loopback0.is_bound) - assert(loopback0.is_loopback) - assert(not loopback0.is_physical) - assert(loopback0.is_loopback_zero) - - #TODO: need to add more loopbacks to test - print loopback0 - assert(str(loopback0) == "loopback.r1") - - eth0 = test_node.interface(1) - assert(eth0.is_bound) - assert(not eth0.is_loopback) - assert(not eth0.is_loopback_zero) - assert(eth0.is_physical) - - assert(eth0.phy == eth0) # should be itself as phy overlay - - #print eth0.dump() - - #Cross-layer access - assert(eth0['input'] is not None) - - assert(sorted(eth0.neighbors()) == [g_phy.node("r2").interface(1), g_phy.node("r3").interface(1)]) - - # access non existent overlay - #TODO: decide if worth trying to assert the logged item - eth0["non_existent_overlay"] - # Need to test cross-layer interface access to phy - - #test accessing overlay for node that doesnt exist in that overlay - test_overlay_node = g_test.node("test_node") - test_overlay_interface = test_overlay_node.add_interface() - test_overlay_interface['phy'] - - #Want to assertRaises - - - # Test edges - autonetkit.update_http(anm) - test_edge = g_in.edge("r1", "r2") - assert(test_edge is not None) - - # test overlays - - # test ANM - test_node = g_phy.node("r1") - assert(test_node.asn == 1) - assert(test_node.device_type == "router") - #TODO: also need to test for servers - assert(test_node.is_l3device()) - assert(test_node.is_router()) - assert(not test_node.is_switch()) - assert(not test_node.is_server()) - assert(str(sorted(list(test_node.neighbors()))) == "[r2, r3]") - assert(str(sorted(list(test_node.neighbor_interfaces()))) == "[eth0.r2, eth0.r3]") - # Test getting from another overlay - assert(test_node['input'].asn == 1) - - assert(str(sorted(g_phy.nodes())) == "[r1, r2, r3, r4, r5]") - - assert(test_node.label == "r1") - - - """ - g_in.update("r4", asn = 2) - g_in.update(["r1", "r2", "r3"], asn = 1) - - test = g_in.node("r5") - - """ - # More graph level tests - #TODO: need to allow searching for interface by .interface("eth1", "r1") - - #TODO: need to provide remove_nodes_from - - test_node = g_phy.node("r1") - search_int = list(test_node.interfaces())[0] - result_int = g_phy.interface(search_int) - assert(search_int == result_int) - - search_int = list(test_node.interfaces())[1] - result_int = g_phy.interface(search_int) - assert(search_int == result_int) - - assert(g_phy.node("r1").degree() == 2) - assert(g_phy.node("r3").degree() == 3) - - g_phy.add_edge("r1", "r4") - assert(g_phy.edge("r1", "r4") is not None) - - edges_to_remove = g_phy.edge("r1", "r4") - #TODO: remove_edges_from needs to support single/list - g_phy.remove_edges_from([edges_to_remove]) - assert(g_phy.edge("r1", "r4") is None) - - src_int = g_phy.node("r1").add_interface("eth1") - dst_int = g_phy.node("r5").add_interface("eth1") - g_phy.add_edges_from([(src_int, dst_int)]) - edge = g_phy.edge("r1", "r5") - assert((edge.src_int, edge.dst_int) == (src_int, dst_int)) - - - edge_a1 = g_phy.edge("r1", "r2") - edge_a2 = g_phy.edge("r1", "r2") - assert(edge_a1 == edge_a2) - edge_b = g_phy.edge("r1", "r2") - assert(edge_a1 != edge_b) - - # compare to string - assert(edge_a1 == ("r1", "r2")) - assert(edge_a1 != ("r1", "r3")) - - assert(bool(edge_a1) is True) #exists - edge_non_existent = g_phy.edge("r1", "r8") - #TODO: need to document API to check exists/doesn't exist for nodes, edges, interfaces, graphs,.... - assert(bool(edge_non_existent) is False) #doesn't exist - - # add node - #TODO: better handling of nodes with no x,y, asn, etc in jsonify - r6 = g_phy.add_node("r6") - assert(r6 in g_phy) - del g_phy[r6] - assert(r6 not in g_phy) - - - # graph data - g_phy.data.test = 123 - assert(g_phy.data.test == 123) - assert(g_phy.data['test'] == 123) - - # test adding a node with not attributes for jsonify - - #TODO: test for directed graph - - autonetkit.update_http(anm) - -test() \ No newline at end of file diff --git a/autonetkit/anm/tests/test_base.py b/autonetkit/anm/tests/test_base.py deleted file mode 100644 index aab34e92..00000000 --- a/autonetkit/anm/tests/test_base.py +++ /dev/null @@ -1,3 +0,0 @@ -from autonetkit.topologies import house -from mock import patch - diff --git a/autonetkit/anm/tests/test_multi_edge.py b/autonetkit/anm/tests/test_multi_edge.py deleted file mode 100644 index cdc705f5..00000000 --- a/autonetkit/anm/tests/test_multi_edge.py +++ /dev/null @@ -1,136 +0,0 @@ -import autonetkit -import autonetkit.log as log - -log.info("Testing ANM") - -def test(): - - anm = autonetkit.NetworkModel() - g_phy = anm['phy'] - g_phy.add_nodes_from(["r1", "r2", "r3", "r4", "r5"]) - for node in g_phy: - node.device_type = "router" - - g_phy.node("r1").x = 100 - g_phy.node("r1").y = 100 - g_phy.node("r2").x = 250 - g_phy.node("r2").y = 250 - g_phy.node("r3").x = 100 - g_phy.node("r3").y = 300 - g_phy.node("r4").x = 600 - g_phy.node("r4").y = 600 - g_phy.node("r5").x = 600 - g_phy.node("r5").y = 300 - - g_phy.add_edges_from(([("r1", "r2")])) - g_phy.add_edges_from(([("r1", "r3")])) - #g_phy.add_edges_from(([("r2", "r3")])) - #g_phy.add_edges_from(([("r2", "r4")])) - #g_phy.add_edges_from(([("r4", "r3")])) - #g_phy.add_edges_from(([("r4", "r5")])) - - g_simple = anm.add_overlay("simple") - g_simple.add_nodes_from(g_phy) - g_simple.add_edges_from(([("r1", "r2")])) - g_simple.add_edges_from(([("r4", "r3")])) - - - g_me = anm.add_overlay("multi", multi_edge = True) - graph = g_me._graph - - g_me.add_nodes_from(g_phy) - - # add two edges - g_me.add_edges_from(([("r1", "r2")])) - g_me.add_edges_from(([("r1", "r2")])) - g_me.add_edges_from(([("r1", "r2")])) - g_me.add_edges_from(([("r1", "r2")])) - g_me.add_edges_from(([("r1", "r2")])) - g_me.add_edges_from(([("r1", "r3")])) - g_me.add_edges_from(([("r2", "r3")])) - g_me.add_edges_from(([("r2", "r3")])) - - r1 = g_me.node("r1") - - for index, edge in enumerate(g_me.edges()): - #print index, edge - edge.index = "i_%s" % index - - for edge in r1.edges(): - #print edge, edge.index - pass - - - """ - e1 = r1.edges()[0] - e1a = g_me.edge(e1) - assert(e1 == e1a) - e2 = r1.edges()[1] - assert(e1 != e2) - #TODO: check why neq != also returns true for e1 != e1a - """ - - #print g_me.edge("r1", "r2", 0).index - #print g_me.edge("r1", "r2", 1).index - - print "edges" - for edge in g_me.edges(): - print edge - - out_of_order = [g_me.edge("r1", "r2", x) for x in [4, 1, 3, 2, 0]] - #print [e.index for e in out_of_order] - in_order = sorted(out_of_order) - #print in_order - #print [e.index for e in in_order] - - # test adding to another mutli edge graph - print "adding" - g_me2 = anm.add_overlay("multi2", multi_edge = True) - g_me2.add_nodes_from(g_me) - print "add", len(g_me.edges()) - g_me2.add_edges_from(g_me.edges(), retain = "index") - for edge in g_me2.edges(): - print edge, edge.index - - # examine underlying nx structure - - - #print graph - #print type(graph) - - for u, v, k in graph.edges(keys=True): - pass - #print u, v, k - #print graph[u][v][k].items() - #graph[u][v][k]['test'] = 123 - - g_dir = anm.add_overlay("dir", directed=True) - g_dir.add_nodes_from(g_phy) - g_dir.add_edges_from(([("r1", "r2")])) - g_dir.add_edges_from(([("r2", "r1")])) - g_dir.add_edges_from(([("r1", "r3")])) - - g_dir_multi = anm.add_overlay("dir_multi", directed = True, multi_edge = True) - g_dir_multi.add_nodes_from(g_phy) - g_dir_multi.add_edges_from(([("r1", "r2")])) - g_dir_multi.add_edges_from(([("r1", "r2")])) - g_dir_multi.add_edges_from(([("r1", "r2")])) - g_dir_multi.add_edges_from(([("r1", "r2")])) - g_dir_multi.add_edges_from(([("r2", "r1")])) - g_dir_multi.add_edges_from(([("r2", "r1")])) - g_dir_multi.add_edges_from(([("r2", "r1")])) - g_dir_multi.add_edges_from(([("r2", "r1")])) - g_dir_multi.add_edges_from(([("r2", "r1")])) - g_dir_multi.add_edges_from(([("r1", "r3")])) - - for index, edge in enumerate(g_dir_multi.edges()): - #print index, edge - edge.index = "i_%s" % index - - from networkx.readwrite import json_graph - import json - data = json_graph.node_link_data(graph) - with open("multi.json", "w") as fh: - fh.write(json.dumps(data, indent=2)) - - autonetkit.update_http(anm) diff --git a/autonetkit/anm/tests/test_network_model.py b/autonetkit/anm/tests/test_network_model.py deleted file mode 100644 index c16cf223..00000000 --- a/autonetkit/anm/tests/test_network_model.py +++ /dev/null @@ -1,22 +0,0 @@ -from autonetkit.topologies import house -from autonetkit.anm.network_model import NetworkModel -from mock import patch - -def test_init(): - anm = NetworkModel() - print anm - - -def test_len(): - anm = NetworkModel() - """ default overlays are: - input, phy, _dependencies, graphics - """ - assert(len(anm) == 4) - anm.add_overlay("test") - assert(len(anm) == 5) - -def test_init(): - anm = NetworkModel() - assert(anm.has_overlay(("phy")) is True) - assert(anm.has_overlay(("test")) is False) diff --git a/autonetkit/build_network.py b/autonetkit/build_network.py deleted file mode 100644 index e110cf21..00000000 --- a/autonetkit/build_network.py +++ /dev/null @@ -1,317 +0,0 @@ -"""Module to build overlay graphs for network design""" - -import autonetkit -import autonetkit.ank as ank_utils -import autonetkit.anm -import autonetkit.config -import autonetkit.exception -import autonetkit.log as log -import networkx as nx - -SETTINGS = autonetkit.config.settings - -# TODO: revisit phy_neighbors for eg ASN and use layer3 instead - -__all__ = ['build'] - - -def load(input_graph_string, defaults = True): - - # TODO: look at XML header for file type - import autonetkit.load.graphml as graphml - import autonetkit.load.load_json as load_json - try: - input_graph = graphml.load_graphml(input_graph_string, defaults=defaults) - except autonetkit.exception.AnkIncorrectFileFormat: - try: - input_graph = load_json.load_json(input_graph_string, defaults=defaults) - except (ValueError, autonetkit.exception.AnkIncorrectFileFormat): - # try a different reader - try: - from autonetkit_cisco import load as cisco_load - except ImportError, error: - log.debug("Unable to load autonetkit_cisco %s", error) - return # module not present (development module) - else: - input_graph = cisco_load.load(input_graph_string) - # add local deployment host - SETTINGS['General']['deploy'] = True - SETTINGS['Deploy Hosts']['internal'] = { - 'VIRL': { - 'deploy': True, - }, - } - - return input_graph - - -def grid_2d(dim): - """Creates a 2d grid of dimension dim""" - graph = nx.grid_2d_graph(dim, dim) - - for node in graph: - graph.node[node]['asn'] = 1 - graph.node[node]['x'] = node[0] * 150 - graph.node[node]['y'] = node[1] * 150 - graph.node[node]['device_type'] = 'router' - graph.node[node]['platform'] = 'cisco' - graph.node[node]['syntax'] = 'ios_xr' - graph.node[node]['host'] = 'internal' - graph.node[node]['ibgp_role'] = "Peer" - - mapping = {node: "%s_%s" % (node[0], node[1]) for node in graph} - # Networkx wipes data if remap with same labels - nx.relabel_nodes(graph, mapping, copy=False) - for src, dst in graph.edges(): - graph[src][dst]['type'] = "physical" - # add global index for sorting - - SETTINGS['General']['deploy'] = True - SETTINGS['Deploy Hosts']['internal'] = { - 'cisco': { - 'deploy': True, - }, - } - - return graph - - -def initialise(input_graph): - """Initialises the input graph with from a NetworkX graph""" - all_multigraph = input_graph.is_multigraph() - anm = autonetkit.anm.NetworkModel(all_multigraph=all_multigraph) - - g_in = anm.initialise_input(input_graph) - # autonetkit.update_vis(anm) - -# set defaults - if not g_in.data.specified_int_names: - # if not specified then automatically assign interface names - g_in.data.specified_int_names = False - - #import autonetkit.plugins.graph_product as graph_product - # graph_product.expand(g_in) # apply graph products if relevant - - expand_fqdn = False - # TODO: make this set from config and also in the input file - if expand_fqdn and len(ank_utils.unique_attr(g_in, "asn")) > 1: - # Multiple ASNs set, use label format device.asn - anm.set_node_label(".", ['label', 'asn']) - - g_in.update(g_in.routers(platform="junosphere"), syntax="junos") - g_in.update(g_in.routers(platform="dynagen"), syntax="ios") - g_in.update(g_in.routers(platform="netkit"), syntax="quagga") - # TODO: is this used? - g_in.update(g_in.servers(platform="netkit"), syntax="quagga") - - #TODO: check this is needed - #autonetkit.ank.set_node_default(g_in, specified_int_names=None) - - g_graphics = anm.add_overlay("graphics") # plotting data - g_graphics.add_nodes_from(g_in, retain=['x', 'y', 'device_type', - 'label', 'device_subtype', 'asn']) - - return anm - - -def check_server_asns(anm): - """Checks that servers have appropriate ASN allocated. - Warns and auto-corrects servers connected to routers of a different AS - #TODO: provide manual over-ride for this auto-correct. - """ - # TODO: Move to validate module? - g_phy = anm['phy'] - - for server in g_phy.servers(): - #TODO: remove now have external_connector device_type? - if server.device_subtype in ("SNAT", "FLAT"): - continue # Don't warn on ASN for NAT elements - l3_neighbors = list(server['layer3'].neighbors()) - l3_neighbor_asns = set(n.asn for n in l3_neighbors) - if server.asn not in l3_neighbor_asns: - neighs_with_asn = ["%s: AS %s" % (n, n.asn) - for n in l3_neighbors] - # tuples for warning message - server.log.warning("Server does not belong to same ASN " - "as neighbors %s" % (neighs_with_asn)) - - if len(l3_neighbors) == 1: - # single ASN of neighbor -> auto correct - if server['input'].default_asn: - neigh_asn = l3_neighbor_asns.pop() - log.warning("Updating server %s AS from %s" - " to %s", server, server.asn, neigh_asn) - server.asn = neigh_asn - else: - log.info("Server %s ASN %s explictly set by user, " - "not auto-correcting", server, server.asn) - - -def apply_design_rules(anm): - """Applies appropriate design rules to ANM""" - # log.info("Building overlay topologies") - g_in = anm['input'] - - build_phy(anm) - - try: - from autonetkit_cisco import build_network as cisco_build_network - except ImportError, e: - log.debug("Unable to load autonetkit_cisco %s", e) - else: - cisco_build_network.post_phy(anm) - - g_phy = anm['phy'] - from autonetkit.design.osi_layers import build_layer1, build_layer2, build_layer3 - # log.info("Building layer2") - build_layer1(anm) - build_layer2(anm) - # autonetkit.update_http(anm) - - # log.info("Building layer3") - build_layer3(anm) - - check_server_asns(anm) - - from autonetkit.design.mpls import build_vrf - build_vrf(anm) # do before to add loopbacks before ip allocations - from autonetkit.design.ip import build_ip, build_ipv4, build_ipv6 - # TODO: replace this with layer2 overlay topology creation - # log.info("Allocating IP addresses") - build_ip(anm) # ip infrastructure topology - - address_family = g_in.data.address_family or "v4" # default is v4 -# TODO: can remove the infrastructure now create g_ip seperately - if address_family == "None": - log.info("IP addressing disabled, disabling routing protocol ", - "configuration") - anm['phy'].data.enable_routing = False - - if address_family == "None": - log.info("IP addressing disabled, skipping IPv4") - anm.add_overlay("ipv4") # create empty so rest of code follows - g_phy.update(g_phy, use_ipv4=False) - elif address_family in ("v4", "dual_stack"): - build_ipv4(anm, infrastructure=True) - g_phy.update(g_phy, use_ipv4=True) - elif address_family == "v6": - # Allocate v4 loopbacks for router ids - build_ipv4(anm, infrastructure=False) - g_phy.update(g_phy, use_ipv4=False) - - # TODO: Create collision domain overlay for ip addressing - l2 overlay? - if address_family == "None": - log.info("IP addressing disabled, not allocating IPv6") - anm.add_overlay("ipv6") # create empty so rest of code follows - g_phy.update(g_phy, use_ipv6=False) - elif address_family in ("v6", "dual_stack"): - build_ipv6(anm) - g_phy.update(g_phy, use_ipv6=True) - else: - anm.add_overlay("ipv6") # placeholder for compiler logic - - default_igp = g_in.data.igp or "ospf" - ank_utils.set_node_default(g_in, igp=default_igp) - ank_utils.copy_attr_from(g_in, g_phy, "igp") - - ank_utils.copy_attr_from(g_in, g_phy, "include_csr") - - try: - from autonetkit_cisco import build_network as cisco_build_network - except ImportError, error: - log.debug("Unable to load autonetkit_cisco %s" % error) - else: - cisco_build_network.pre_design(anm) - - # log.info("Building IGP") - from autonetkit.design.igp import build_igp - build_igp(anm) - - # log.info("Building BGP") - from autonetkit.design.bgp import build_bgp - build_bgp(anm) - # autonetkit.update_vis(anm) - - from autonetkit.design.mpls import mpls_te, mpls_oam - mpls_te(anm) - mpls_oam(anm) - -# post-processing - if anm['phy'].data.enable_routing: - from autonetkit.design.mpls import (mark_ebgp_vrf, - build_ibgp_vpn_v4) - mark_ebgp_vrf(anm) - build_ibgp_vpn_v4(anm) # build after bgp as is based on - # autonetkit.update_vis(anm) - - try: - from autonetkit_cisco import build_network as cisco_build_network - except ImportError, error: - log.debug("Unable to load autonetkit_cisco %s", error) - else: - cisco_build_network.post_design(anm) - - # log.info("Finished building network") - return anm - - -def build(input_graph): - """Main function to build network overlay topologies""" - anm = None - anm = initialise(input_graph) - anm = apply_design_rules(anm) - return anm - -def build_phy(anm): - """Build physical overlay""" - g_in = anm['input'] - g_phy = anm['phy'] - - g_phy.data.enable_routing = g_in.data.enable_routing - if g_phy.data.enable_routing is None: - g_in.data.enable_routing = True # default if not set - - g_phy.add_nodes_from(g_in, retain=['label', 'update', 'device_type', - 'asn', 'specified_int_names', 'x', 'y', - 'device_subtype', 'platform', 'host', 'syntax']) - - if g_in.data.Creator == "Topology Zoo Toolset": - ank_utils.copy_attr_from(g_in, g_phy, "Network") - - ank_utils.set_node_default(g_phy, Network=None) - g_phy.add_edges_from(g_in.edges(type="physical")) - # TODO: make this automatic if adding to the physical graph? - - ank_utils.set_node_default(g_phy, use_ipv4=False, use_ipv6=False) - ank_utils.copy_attr_from(g_in, g_phy, "custom_config_global", - dst_attr="custom_config") - - for node in g_phy: - if node['input'].custom_config_loopback_zero: - lo_zero_config = node['input'].custom_config_loopback_zero - node.loopback_zero.custom_config = lo_zero_config - custom_config_phy_ints = node['input'].custom_config_phy_ints - for interface in node: - if custom_config_phy_ints: - interface.custom_config = custom_config_phy_ints - specified_id = interface['input'].get("specified_id") - if specified_id: - interface.specified_id = specified_id # map across - - #TODO: tidy this code up - for node in g_phy: - for interface in node: - remote_edges = interface.edges() - if len(remote_edges): - interface.description = 'to %s' \ - % remote_edges[0].dst.label - - -def build_conn(anm): - """Build connectivity overlay""" - g_in = anm['input'] - g_conn = anm.add_overlay("conn", directed=True) - g_conn.add_nodes_from(g_in, retain=['label']) - g_conn.add_edges_from(g_in.edges(type="physical")) - - return diff --git a/autonetkit/build_wheel.py b/autonetkit/build_wheel.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/collection/__init__.py b/autonetkit/collection/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/collection/process.py b/autonetkit/collection/process.py deleted file mode 100644 index 79f23c26..00000000 --- a/autonetkit/collection/process.py +++ /dev/null @@ -1,222 +0,0 @@ -import autonetkit.log as log - - -def build_reverse_mappings_from_nidb(nidb): - """Builds IP reverse mappings from DeviceModel""" - rev_map = { - "subnets": {}, - "loopbacks": {}, - "infra_interfaces": {}, - } - - for node in nidb: - if node.broadcast_domain: - rev_map["subnets"][str(node.ipv4_subnet)] = node - else: - rev_map["loopbacks"][str(node.loopback)] = node - for interface in node.physical_interfaces(): - rev_map["infra_interfaces"][ - str(interface.ipv4_address)] = interface - - return rev_map - - -def build_reverse_mappings_from_anm_input(anm): - """Builds reverse mappings from ANM input graph, - assumes addresses have already been allocated onto input graph, - either externally or by previous run""" - - from collections import defaultdict - import netaddr - - g_in = anm['input'] - rev_map = { - "loopbacks": {}, - "infra_interfaces": {}, - "subnets": {}, - } - - subnets = defaultdict(list) - - for node in g_in: - rev_map["loopbacks"][str(node.loopback_v4)] = node - for interface in node.physical_interfaces(): - rev_map["infra_interfaces"][ - str(interface.ipv4_address)] = interface - - prefixlen = interface.ipv4_prefixlen - cidr_string = "%s/%s" % (interface.ipv4_address, prefixlen) - intermediate_subnet = netaddr.IPNetwork(cidr_string) - subnet_cidr_string = "%s/%s" % ( - intermediate_subnet.network, prefixlen) - subnet = netaddr.IPNetwork(subnet_cidr_string) - subnets[subnet].append(interface) - - for subnet, interfaces in subnets.items(): - subnet_str = str(subnet) - rev_map['subnets'][subnet_str] = "_".join( - str(i.node) for i in interfaces) - - return rev_map - - -def process_textfsm(template_file, data): - """ - TODO: make template return data other than just hops, and have reverse_map_path() handle accordingly - """ - import textfsm - with open(template_file, "r") as template: - re_table = textfsm.TextFSM(template) - - data = re_table.ParseText(data) - header = re_table.header - return header, data - - -def extract_route_from_parsed_routing_table(header, routes, proto_id="Proto", - network_id="Network", via_id="Via"): - network_index = header.index(network_id) - proto_index = header.index(proto_id) - via_index = header.index(via_id) - return [(item[proto_index], item[network_index], item[via_index]) for item in routes] - - -def extract_path_from_parsed_traceroute(header, routes, hop_id="Hop"): - """Returns the hop IPs from the TextFSM returned data""" - hop_index = header.index(hop_id) - return [item[hop_index] for item in routes] - - -def reverse_map_routing(rev_map, data): - """Returns list of nodes in path - interfaces selects whether to return only nodes, or interfaces - e.g. eth0.r1 or just r1 - """ - - # print data - - result = [] - for protocol, network, via in data: - print "reversing", protocol, network, via - if network in rev_map['subnets']: - cd = rev_map['subnets'][network] - if via is None: - result.append((protocol, cd, None)) - - if via in rev_map['infra_interfaces']: - iface = rev_map['infra_interfaces'][via] - print "adding", protocol, cd, iface.node - result.append((protocol, cd, iface.node)) - return result - - -def reverse_map_address(rev_map, address, interfaces=False): - if address in rev_map['infra_interfaces']: - iface = rev_map['infra_interfaces'][address] - if interfaces: - return iface - else: - return iface.node - elif address in rev_map['loopbacks']: - node = rev_map['loopbacks'][address] - return node - - -def extract_node_path_info(header, parsed_data, mapped_data, exclude_keys=None): - if len(parsed_data) != len(mapped_data): - log.warning( - "Parsed data different length to mapped data, not extracting node data") - - if exclude_keys: - exclude_keys = set(exclude_keys) - else: - exclude_keys = set() # empty set for simpler test logic - - retval = [] - for index, hop in enumerate(mapped_data): - node_vals = parsed_data[index] # TextFSM output for this hop - node_data = dict(zip(header, node_vals)) - - filtered_data = {k: v for k, v in node_data.items() - if len(v) and k not in exclude_keys} - filtered_data['host'] = str(hop.id) - retval.append(filtered_data) - - return retval - - -def reverse_map_path(rev_map, path, interfaces=False): - """Returns list of nodes in path - interfaces selects whether to return only nodes, or interfaces - e.g. eth0.r1 or just r1 - """ - - result = [] - for hop in path: - if hop in rev_map['infra_interfaces']: - iface = rev_map['infra_interfaces'][hop] - if interfaces: - result.append(iface) - else: - result.append(iface.node) - elif hop in rev_map['loopbacks']: - node = rev_map['loopbacks'][hop] - result.append(node) - - return result - - -def substitute_ips(data, rev_map, interfaces=False): - import re - re_ip_address = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" - re_ip_loopback = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/32" - re_ip_subnet = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}" - if not len(data): - log.info("No data provided to IP substitution, returning") - return data - - def replace_ip(match): - match = match.group() - if match in rev_map['infra_interfaces']: - iface = rev_map['infra_interfaces'][match] - if interfaces: - named = "%s.%s" % (iface.id, iface.node) - return named - else: - return str(iface.node) - return match # no match, return the raw IP - - def replace_loopbacks(match): - match = match.group() - # strip off the /32 - loopback_ip = match[:-3] - if loopback_ip in rev_map['loopbacks']: - node = rev_map['loopbacks'][loopback_ip] - return str(node) - return match # no match, return the raw IP - - def replace_loopbacks_no_mask(match): - # TODO: refactor - match = match.group() - # strip off the /32 - loopback_ip = match - if loopback_ip in rev_map['loopbacks']: - node = rev_map['loopbacks'][loopback_ip] - return str(node) - return match # no match, return the raw IP - - def replace_subnet(match): - match = match.group() - - if match in rev_map['subnets']: - subnet = rev_map['subnets'][match] - return str(subnet) - return match # no match, return the raw IP - - # do loopbacks first - data = re.sub(re_ip_loopback, replace_loopbacks, data) - data = re.sub(re_ip_address, replace_ip, data) - # try for ip addresses in loopback - data = re.sub(re_ip_address, replace_loopbacks_no_mask, data) - # and for subnets ie ip/netmask - return re.sub(re_ip_subnet, replace_subnet, data) diff --git a/autonetkit/collection/server.py b/autonetkit/collection/server.py deleted file mode 100644 index 369509b2..00000000 --- a/autonetkit/collection/server.py +++ /dev/null @@ -1,216 +0,0 @@ -"""Zmq based measurement server""" -# based on -# https://learning-0mq-with-pyzmq.readthedocs.org/en/latest/pyzmq/patterns/pushpull.html - -# TODO: rewrite as callbacks rather than threads -import autonetkit.log as log - -try: - import zmq -except ImportError: - log.warning("Unable to import zmq") -import json -import socket as python_socket -import telnetlib -from threading import Thread -import time -import sys - - -def streamer_device(port_in, port_out): - from zmq.devices import ProcessDevice - - pd = ProcessDevice(zmq.QUEUE, zmq.PULL, zmq.PUSH) - pd.bind_in('tcp://*:%s' % port_in) - pd.bind_out('tcp://*:%s' % port_out) - pd.setsockopt_in(zmq.IDENTITY, 'PULL') - - pd.setsockopt_out(zmq.IDENTITY, 'PUSH') - pd.start() -# it will now be running in a background process - - -def forwarder_device(port_in, port_out): - from zmq.devices import ProcessDevice - - pd = ProcessDevice(zmq.FORWARDER, zmq.SUB, zmq.PUB) - pd.bind_in('tcp://*:%s' % port_in) - pd.bind_out('tcp://*:%s' % port_out) - pd.setsockopt_in(zmq.IDENTITY, 'SUB') - pd.setsockopt_in(zmq.SUBSCRIBE, "") - pd.setsockopt_out(zmq.IDENTITY, 'PUB') - pd.start() -# it will now be running in a background process - -CONNECTORS = {} - -# TODO: inherit from base autonetkit connector abstract function - - -def netkit_connector(host, username, password, command, *args, **kwargs): - # Note: user prompt and priv prompt have same password - vtysh = kwargs.get("vtysh", False) - - print host, username, password, command, vtysh - - print "Connecting to %s" % (host) - try: - tn = telnetlib.Telnet(host, timeout=10) - except Exception, error: - print "Unable to connect to %s: %s" % (host, error) - return - - tn.set_debuglevel(0) - print "Connected to %s" % host - - welcome_banner = tn.read_until("login:", timeout=10) - last_line = welcome_banner.splitlines()[-1] - hostname = last_line.replace("login:", "").strip() - - linux_prompt = hostname + ":~#" - - print "Hostname is %s" % hostname - - # TODO: check why need the below for ascii/unicode/pzmq? - - tn.write(username + '\n') - tn.read_until("Password:", timeout=10) - tn.write(password + '\n') - tn.read_until(linux_prompt, timeout=10) - if vtysh: - vtysh_prompt = hostname + "#" - tn.write("vtysh" + "\n") - tn.read_until(vtysh_prompt, timeout=10) - tn.write("terminal length 0" + "\n") - tn.read_until(vtysh_prompt, timeout=10) - tn.write(command + "\n") - result = tn.read_until(vtysh_prompt, timeout=10) - tn.write("exit" + "\n") - # TODO: check if need to parse result also to strip out prompt - else: - tn.write(command + "\n") - result = tn.read_until(linux_prompt, timeout=10) - result = "\n".join(result.splitlines()[1:-1]) - - print "Finished for %s" % hostname - - tn.write("exit" + "\n") - return hostname, result - -CONNECTORS['netkit'] = netkit_connector -try: - import autonetkit_cisco - import autonetkit_cisco.measure_connectors -except ImportError: - pass # not installed -else: - CONNECTORS[ - 'iosv_ns'] = autonetkit_cisco.measure_connectors.iosv_ns_connector - CONNECTORS[ - 'csr1000v_ns'] = autonetkit_cisco.measure_connectors.iosv_ns_connector - CONNECTORS[ - 'ios_xrv_ns'] = autonetkit_cisco.measure_connectors.ios_xrv_ns_connector - CONNECTORS[ - 'nx_osv_ns'] = autonetkit_cisco.measure_connectors.nx_osv_ns_connector - CONNECTORS[ - 'ubuntu_ns'] = autonetkit_cisco.measure_connectors.linux_ns_connector - - -def do_connect(**kwargs): - # TODO: use a function map - connector = kwargs.get("connector") - connector_fn = CONNECTORS[connector] # TODO: capture if not found - try: - return connector_fn(**kwargs) - except EOFError: - print "Unable to connect with connector %s" % connector - return "" - - -def worker(): - context = zmq.Context() - # recieve work - consumer_receiver = context.socket(zmq.PULL) - consumer_receiver.connect("tcp://127.0.0.1:5560") - # send work - consumer_sender = context.socket(zmq.PUB) - consumer_sender.connect("tcp://127.0.0.1:5561") - while True: - # Wait for next request from client - print "Waiting for message" - work = consumer_receiver.recv_json() - # socket.send(json.dumps("hello")) - # continue - print "Received request: ", work - data = json.loads(work) - host = data['host'] # TODO: rename this to host_ip - # TODO: add support for host port (default 23) - connector = data['connector'] - username = data['username'] - password = data['password'] - command = data['command'] - message_key = data['message_key'] - vtysh = data.get('vtysh', False) - message_key = str(message_key) - username = str(username) - password = str(password) - command = str(command) - print "command is", command - data = {k: str(v) for k, v in data.items()} - try: - hostname, result = do_connect(**data) - success = True - except Exception, e: - import traceback - traceback.print_exc() - print e - hostname = "" - success = False - result = str(e) - if "No route to host" in e: - # simpler message - result = "No route to host" - if "pexpect.TIMEOUT" in str(e): - # TODO: test for timeout exception directly - result = "Pexpect timeout" - finally: - try: - hostname = str(hostname) - result = str(result) - send_data = dict(data) - send_data.update({'command': work, - "success": success, - 'hostname': hostname, - 'result': result}) - del send_data['username'] - del send_data['password'] - message = json.dumps(send_data) - except Exception, e: - print "cant dump", e - else: - consumer_sender.send_multipart([message_key, message]) - print "Sent to zmq" - - -def main(): - num_worker_threads = 5 - try: - num_worker_threads = int(sys.argv[1]) - except IndexError: - pass - # NOTE: need pts/x available for worst-case of all threads at once - for i in range(num_worker_threads): - t = Thread(target=worker) - t.daemon = True - t.start() - - # start the streamer device - # TODO: double check what these are used for - streamer_device(5559, 5560) - forwarder_device(5561, 5562) - - while True: - time.sleep(1) - -if __name__ == "__main__": - main() diff --git a/autonetkit/collection/utils.py b/autonetkit/collection/utils.py deleted file mode 100644 index b135897d..00000000 --- a/autonetkit/collection/utils.py +++ /dev/null @@ -1,67 +0,0 @@ -def get_results(server, commands, send_port=5559, receive_port=5562): - import zmq - import json - import autonetkit.log as log - import uuid # create key for unique zmq channel for replies - message_key = uuid.uuid4() - message_key = str(message_key) - - context = zmq.Context() - zmq_socket = context.socket(zmq.PUSH) - zmq_socket.connect("tcp://%s:%s" % (server, send_port)) - - context = zmq.Context() - results_receiver = context.socket(zmq.SUB) - results_receiver.connect("tcp://%s:%s" % (server, receive_port)) - results_receiver.setsockopt(zmq.SUBSCRIBE, message_key) - # NOTE: need to connect *before* send commands in order to capture replies - send_commands(server, commands, message_key, - send_port, receive_port) - - log.info("Collecting results") - - json_results = [] - log.info("Expecting %s replies" % len(commands)) - for index in range(len(commands)): - reply = results_receiver.recv_multipart() - data = reply[1] - result = json.loads(data) - hostname = result["hostname"].replace(".", "_") - json_results.append(result) - command = json.loads(result["command"]) - host = command["host"] - log.debug("%s/%s: Reply from %s (%s)" % (index, - len(commands), hostname, host)) - yield result - else: - # cleanup - results_receiver.close() - - -def send_commands(server, commands, message_key, send_port=5559, - receive_port=5562): - import zmq - import json - import autonetkit.log as log - message_key = str(message_key) - import autonetkit.ank_json as ank_json - - context = zmq.Context() - zmq_socket = context.socket(zmq.PUSH) - zmq_socket.connect("tcp://%s:%s" % (server, send_port)) - - context = zmq.Context() - results_receiver = context.socket(zmq.SUB) - results_receiver.connect("tcp://%s:%s" % (server, receive_port)) - results_receiver.setsockopt(zmq.SUBSCRIBE, message_key) - # NOTE: need to connect *before* send commands in order to capture replies - - for command in commands: - command["message_key"] = message_key - - work_message = json.dumps(command, cls=ank_json.AnkEncoder, indent=4) - # print "sending", work_message - log.debug("Sending %s to %s" % (command['command'], command['host'])) - zmq_socket.send_json(work_message) - - zmq_socket.close() diff --git a/autonetkit/collection/verify.py b/autonetkit/collection/verify.py deleted file mode 100644 index 335f40ce..00000000 --- a/autonetkit/collection/verify.py +++ /dev/null @@ -1,93 +0,0 @@ -import autonetkit.log as log -from autonetkit.ank_utils import unwrap_graph, unwrap_nodes -import pprint -from collections import defaultdict - - -def igp_routes(anm, measured): - # TODO: split up expected calculation and comparison so don't need to calculate SPF multiple times - # TODO: allow choice of IGP - for now is just OSPF - import networkx as nx - g_ipv4 = anm['ipv4'] - g_ospf = anm['ospf'] - - log.info("Verifying IGP routes") - - # extract source node from measured data - try: - src_node = measured[0][0] - except IndexError: - log.info("Unable to parse measured results, returning") - return - - # calculate expected routes - - prefixes_by_router = {} - prefix_reachability = defaultdict(list) - for router in g_ospf: - ipv4_node = g_ipv4.node(router) - neighbors = ipv4_node.neighbors() - neighbor_subnets = [n.subnet for n in neighbors] - prefixes_by_router[router] = neighbor_subnets - for subnet in neighbor_subnets: - # use strings for faster matching from here on - # (can convert back to overlay_nodes later) - prefix_reachability[str(subnet)].append(str(router)) - - # print prefixes_by_router - # print prefix_reachability - - graph = unwrap_graph(g_ospf) - - shortest_paths = nx.shortest_path(graph, source=src_node, weight='cost') - shortest_path_lengths = nx.shortest_path_length( - graph, source=src_node, weight='cost') - # pprint.pprint(shortest_path_lengths) - - optimal_prefixes = {} - for prefix, routers in prefix_reachability.items(): - # decorate with cost - try: - routers_with_costs = [ - (shortest_path_lengths[r], r) for r in routers] - except KeyError: - continue # no router, likely from eBGP - min_cost = min(rwc[0] for rwc in routers_with_costs) - shortest_routers = [rwc[1] - for rwc in routers_with_costs if rwc[0] == min_cost] - optimal_prefixes[str(prefix)] = shortest_routers - # print optimal_prefixes - - verified_prefixes = {} - for route in measured: - dst_cd = route[-1] - prefix = str(g_ipv4.node(dst_cd).subnet) - try: - optimal_routers = optimal_prefixes[prefix] - except KeyError: - continue # prefix not present - if src_node in optimal_routers: - continue # target is self - - optimal_routes = [shortest_paths[r] for r in optimal_routers] - optimal_next_hop = [p[1] for p in optimal_routes] - actual_next_hop = route[1] - - log.info("Match: %s, %s, optimal: %s, actual: %s" % ( - actual_next_hop in optimal_next_hop, prefix, ", ".join(optimal_next_hop), actual_next_hop)) - - if actual_next_hop not in optimal_next_hop: - log.info("Unverified prefix: %s on %s. Expected: %s. Received: %s" % (prefix, dst_cd, - ", ".join(optimal_next_hop), actual_next_hop)) - - verified_prefixes[prefix] = actual_next_hop in optimal_next_hop - - verified_count = verified_prefixes.values().count(True) - try: - verified_fraction = round( - 100 * verified_count / len(verified_prefixes), 2) - except ZeroDivisionError: - verified_fraction = 0 - log.info("%s%% verification rate" % verified_fraction) - - return verified_prefixes diff --git a/autonetkit/compiler.py b/autonetkit/compiler.py deleted file mode 100644 index 4a83bef8..00000000 --- a/autonetkit/compiler.py +++ /dev/null @@ -1,20 +0,0 @@ -def dot_to_underscore(instring): - """Replace dots with underscores""" - return instring.replace(".", "_") - -def natural_sort(sequence, key=lambda s:s): - """ - Sort the sequence into natural alphanumeric order. - """ - import re - sequence = list(sequence) - def get_alphanum_key_func(key): - convert = lambda text: int(text) if text.isdigit() else text - return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))] - sort_key = get_alphanum_key_func(key) - sequence.sort(key=sort_key) - return sequence - -def sort_sessions(sequence): - """Wrapper around natural_sort for bgp sessions""" - return natural_sort(sequence, key = lambda x: x.dst.label) diff --git a/autonetkit/compilers/__init__.py b/autonetkit/compilers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/compilers/device/__init__.py b/autonetkit/compilers/device/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/compilers/device/cisco.py b/autonetkit/compilers/device/cisco.py deleted file mode 100644 index 39129e1e..00000000 --- a/autonetkit/compilers/device/cisco.py +++ /dev/null @@ -1,913 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -from collections import defaultdict - -import autonetkit.log as log -import netaddr -from autonetkit.ank import sn_preflen_to_network -from autonetkit.compiler import sort_sessions -from autonetkit.compilers.device.router_base import RouterCompiler -from autonetkit.nidb import ConfigStanza - - -class IosBaseCompiler(RouterCompiler): - - """Base IOS compiler""" - - lo_interface_prefix = 'Loopback' - lo_interface = '%s%s' % (lo_interface_prefix, 0) - - def ibgp_session_data(self, session, ip_version): - """Wraps RouterCompiler ibgp_session_data - adds vpnv4 = True if ip_version == 4 and session is in g_ibgp_vpn_v4""" - - data = super(IosBaseCompiler, self).ibgp_session_data(session, - ip_version) - if ip_version == 4: - g_ibgp_vpn_v4 = self.anm['ibgp_vpn_v4'] - if g_ibgp_vpn_v4.has_edge(session): - data['use_vpnv4'] = True - return data - - def compile(self, node): - self.vrf_igp_interfaces(node) - phy_node = self.anm['phy'].node(node) - - node.use_cdp = phy_node.use_cdp - - if node in self.anm['ospf']: - node.add_stanza("ospf") - node.ospf.use_ipv4 = phy_node.use_ipv4 - node.ospf.use_ipv6 = phy_node.use_ipv6 - - if node in self.anm['eigrp']: - node.add_stanza("eigrp") - node.eigrp.use_ipv4 = phy_node.use_ipv4 - node.eigrp.use_ipv6 = phy_node.use_ipv6 - - if node in self.anm['isis']: - node.add_stanza("isis") - node.isis.use_ipv4 = phy_node.use_ipv4 - node.isis.use_ipv6 = phy_node.use_ipv6 - - if node in self.anm['rip']: - node.add_stanza("rip") - node.rip.use_ipv4 = phy_node.use_ipv4 - node.rip.use_ipv6 = phy_node.use_ipv6 - - super(IosBaseCompiler, self).compile(node) - if node in self.anm['isis']: - self.isis(node) - - node.label = self.anm['phy'].node(node).label - self.vrf(node) - - def interfaces(self, node): - phy_loopback_zero = self.anm['phy' - ].interface(node.loopback_zero) - if node.ip.use_ipv4: - ipv4_loopback_subnet = netaddr.IPNetwork('0.0.0.0/32') - ipv4_loopback_zero = phy_loopback_zero['ipv4'] - ipv4_address = ipv4_loopback_zero.ip_address - node.loopback_zero.use_ipv4 = True - node.loopback_zero.ipv4_address = ipv4_address - node.loopback_zero.ipv4_subnet = ipv4_loopback_subnet - node.loopback_zero.ipv4_cidr = \ - sn_preflen_to_network(ipv4_address, - ipv4_loopback_subnet.prefixlen) - - if node.ip.use_ipv6: - # TODO: clean this up so can set on router_base: call cidr not - # address and update templates - node.loopback_zero.use_ipv6 = True - ipv6_loopback_zero = phy_loopback_zero['ipv6'] - node.loopback_zero.ipv6_address = \ - sn_preflen_to_network(ipv6_loopback_zero.ip_address, - 128) - - super(IosBaseCompiler, self).interfaces(node) - - for interface in node.physical_interfaces(): - interface.use_cdp = node.use_cdp # use node value - - for interface in node.interfaces: - interface.sub_ints = [] # temporary until full subinterfaces - - for interface in node.physical_interfaces(): - g_ext_conn = self.anm['ext_conn'] - if node not in g_ext_conn: - continue - - node_ext_conn = g_ext_conn.node(node) - ext_int = node_ext_conn.interface(interface) - for sub_int in ext_int.sub_int or []: - stanza = ConfigStanza( - id=sub_int['id'], - ipv4_address=sub_int['ipv4_address'], - ipv4_prefixlen=sub_int['ipv4_prefixlen'], - ipv4_subnet=sub_int['ipv4_subnet'], - dot1q=sub_int['dot1q'], - ) - interface.sub_ints.append(stanza) - - def mpls_oam(self, node): - g_mpls_oam = self.anm['mpls_oam'] - node.add_stanza("mpls") - node.mpls.oam = False - - if node not in g_mpls_oam: - return # no mpls oam configured - - # Node is present in mpls OAM - node.mpls.oam = True - - def mpls_te(self, node): - g_mpls_te = self.anm['mpls_te'] - - if node not in g_mpls_te: - return # no mpls te configured - - node.mpls_te = True - - if node.isis: - node.isis.ipv4_mpls_te = True - node.isis.mpls_te_router_id = self.lo_interface - - if node.ospf: - node.ospf.ipv4_mpls_te = True - node.ospf.mpls_te_router_id = self.lo_interface - - def nailed_up_routes(self, node): - log.debug('Configuring nailed up routes') - phy_node = self.anm['phy'].node(node) - - if node.is_ebgp_v4 and node.ip.use_ipv4: - infra_blocks = self.anm['ipv4'].data['infra_blocks' - ].get(phy_node.asn) or [] - for infra_route in infra_blocks: - stanza = ConfigStanza( - prefix=str(infra_route.network), - netmask=str(infra_route.netmask), - nexthop="Null0", - metric=254, - ) - node.ipv4_static_routes.append(stanza) - - if node.is_ebgp_v6 and node.ip.use_ipv6: - infra_blocks = self.anm['ipv6'].data['infra_blocks' - ].get(phy_node.asn) or [] - # TODO: setup schema with defaults - for infra_route in infra_blocks: - stanza = ConfigStanza( - prefix=str(infra_route), - nexthop="Null0", - metric=254, - ) - node.ipv6_static_routes.append(stanza) - - def bgp(self, node): - node.add_stanza("bgp") - node.bgp.lo_interface = self.lo_interface - super(IosBaseCompiler, self).bgp(node) - phy_node = self.anm['phy'].node(node) - asn = phy_node.asn - g_ebgp_v4 = self.anm['ebgp_v4'] - g_ebgp_v6 = self.anm['ebgp_v6'] - - if node in g_ebgp_v4 \ - and len(list(g_ebgp_v4.node(node).edges())) > 0: - node.is_ebgp_v4 = True - else: - node.is_ebgp_v4 = False - - if node in g_ebgp_v6 \ - and len(list(g_ebgp_v6.node(node).edges())) > 0: - node.is_ebgp_v6 = True - else: - node.is_ebgp_v6 = False - - node.bgp.ipv4_advertise_subnets = [] - node.bgp.ipv6_advertise_subnets = [] - - # Advertise loopbacks into BGP - - if node.ip.use_ipv4: - node.bgp.ipv4_advertise_subnets = \ - [node.loopback_zero.ipv4_cidr] - if node.ip.use_ipv6: - node.bgp.ipv6_advertise_subnets = \ - [node.loopback_zero.ipv6_address] - - # Advertise infrastructure into eBGP - - if node.ip.use_ipv4 and node.is_ebgp_v4: - infra_blocks = self.anm['ipv4'].data['infra_blocks' - ].get(asn) or [] - for infra_route in infra_blocks: - node.bgp.ipv4_advertise_subnets.append(infra_route) - - if node.ip.use_ipv6 and node.is_ebgp_v6: - infra_blocks = self.anm['ipv6'].data['infra_blocks' - ].get(asn) or [] - for infra_route in infra_blocks: - node.bgp.ipv6_advertise_subnets.append(infra_route) - - self.nailed_up_routes(node) - - # vrf - # TODO: this should be inside vrf section? - - node.bgp.vrfs = [] - - vrf_node = self.anm['vrf'].node(node) - if vrf_node and vrf_node.vrf_role is 'PE': - - # iBGP sessions for this VRF - - vrf_ibgp_neighbors = defaultdict(list) - - g_ibgp_v4 = self.anm['ibgp_v4'] - for session in sort_sessions(g_ibgp_v4.edges(vrf_node)): - if session.exclude and session.vrf: - data = self.ibgp_session_data(session, ip_version=4) - stanza = ConfigStanza(data) - vrf_ibgp_neighbors[session.vrf].append(stanza) - - g_ibgp_v6 = self.anm['ibgp_v6'] - for session in sort_sessions(g_ibgp_v6.edges(vrf_node)): - if session.exclude and session.vrf: - data = self.ibgp_session_data(session, ip_version=6) - stanza = ConfigStanza(data) - vrf_ibgp_neighbors[session.vrf].append(stanza) - - # eBGP sessions for this VRF - - vrf_ebgp_neighbors = defaultdict(list) - - for session in sort_sessions(g_ebgp_v4.edges(vrf_node)): - if session.exclude and session.vrf: - data = self.ebgp_session_data(session, ip_version=4) - stanza = ConfigStanza(data) - vrf_ebgp_neighbors[session.vrf].append(stanza) - - for session in sort_sessions(g_ebgp_v6.edges(vrf_node)): - if session.exclude and session.vrf: - data = self.ebgp_session_data(session, ip_version=6) - stanza = ConfigStanza(data) - vrf_ebgp_neighbors[session.vrf].append(stanza) - - for vrf in vrf_node.node_vrf_names: - rd_index = vrf_node.rd_indices[vrf] - rd = '%s:%s' % (node.asn, rd_index) - stanza = ConfigStanza( - vrf=vrf, - rd=rd, - use_ipv4=node.ip.use_ipv4, - use_ipv6=node.ip.use_ipv6, - vrf_ebgp_neighbors=vrf_ebgp_neighbors[vrf], - vrf_ibgp_neighbors=vrf_ibgp_neighbors[vrf], - ) - node.bgp.vrfs.append(stanza) - - # Retain route_target if in ibgp_vpn_v4 and RR or HRR (set in design) - - vpnv4_node = self.anm['ibgp_vpn_v4'].node(node) - if vpnv4_node: - retain = False - if vpnv4_node.retain_route_target: - retain = True - node.bgp.vpnv4 = ConfigStanza(retain_route_target=retain) - - def vrf_igp_interfaces(self, node): - - # marks physical interfaces to exclude from IGP - - vrf_node = self.anm['vrf'].node(node) - if vrf_node and vrf_node.vrf_role is 'PE': - for interface in node.physical_interfaces(): - vrf_int = self.anm['vrf'].interface(interface) - if vrf_int.vrf_name: - interface.exclude_igp = True - - def vrf(self, node): - g_vrf = self.anm['vrf'] - vrf_node = self.anm['vrf'].node(node) - node.add_stanza("vrf") - node.add_stanza("mpls") - node.vrf.vrfs = [] - if vrf_node and vrf_node.vrf_role is 'PE': - - # TODO: check if mpls ldp already set elsewhere - - for vrf in vrf_node.node_vrf_names: - route_target = g_vrf.data.route_targets[node.asn][vrf] - rd_index = vrf_node.rd_indices[vrf] - rd = '%s:%s' % (node.asn, rd_index) - - stanza = ConfigStanza( - vrf=vrf, rd=rd, route_target=route_target) - node.vrf.vrfs.append(stanza) - - for interface in node.interfaces: - vrf_int = self.anm['vrf'].interface(interface) - if vrf_int.vrf_name: - # mark interface as being part of vrf - interface.vrf = vrf_int.vrf_name - if interface.physical: - interface.description += ' vrf %s' \ - % vrf_int.vrf_name - - if vrf_node and vrf_node.vrf_role in ('P', 'PE'): - - # Add PE -> P, PE -> PE interfaces to MPLS LDP - - node.mpls.ldp_interfaces = [] - for interface in node.physical_interfaces(): - mpls_ldp_int = self.anm['mpls_ldp'].interface(interface) - if mpls_ldp_int.is_bound: - node.mpls.ldp_interfaces.append(interface.id) - interface.use_mpls = True - - if vrf_node and vrf_node.vrf_role is 'P': - node.mpls.ldp_interfaces = [] - for interface in node.physical_interfaces(): - node.mpls.ldp_interfaces.append(interface.id) - - vrf_node = self.anm['vrf'].node(node) - - node.vrf.use_ipv4 = node.ip.use_ipv4 - node.vrf.use_ipv6 = node.ip.use_ipv6 - node.vrf.vrfs = sorted(node.vrf.vrfs, key=lambda x: x.vrf) - - if self.anm.has_overlay('mpls_ldp') and node \ - in self.anm['mpls_ldp']: - node.mpls.enabled = True - node.mpls.router_id = node.loopback_zero.id - - def rip(self, node): - #Inheriting from base compiler. Adding in interface stanza. - super(IosBaseCompiler, self).rip(node) - for interface in node.physical_interfaces(): - phy_int = self.anm['phy'].interface(interface) - - rip_int = phy_int['rip'] - if rip_int and rip_int.is_bound: - if interface.exclude_igp: - continue - - interface.rip = { - 'cost': rip_int.cost, - 'area': rip_int.area, - 'process_id': node.rip.process_id, - 'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6, - } - - def ospf(self, node): - super(IosBaseCompiler, self).ospf(node) - for interface in node.physical_interfaces(): - phy_int = self.anm['phy'].interface(interface) - - ospf_int = phy_int['ospf'] - if ospf_int and ospf_int.is_bound: - if interface.exclude_igp: - continue # don't configure IGP for this interface - - # TODO: use ConfigStanza here - interface.ospf = { - 'cost': ospf_int.cost, - 'area': ospf_int.area, - 'process_id': node.ospf.process_id, - 'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6, - 'multipoint': ospf_int.multipoint, - } - - # TODO: add wrapper for this - - def eigrp(self, node): - super(IosBaseCompiler, self).eigrp(node) - for interface in node.physical_interfaces(): - phy_int = self.anm['phy'].interface(interface) - - eigrp_int = phy_int['eigrp'] - if eigrp_int and eigrp_int.is_bound: - if interface.exclude_igp: - continue # don't configure IGP for this interface - - interface.eigrp = { - 'metric': eigrp_int.metric, - 'area': eigrp_int.area, - 'name': node.eigrp.name, - 'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6, - 'multipoint': eigrp_int.multipoint, - } - - # TODO: add wrapper for this - - def isis(self, node): - super(IosBaseCompiler, self).isis(node) - for interface in node.physical_interfaces(): - isis_int = self.anm['isis'].interface(interface) - edges = isis_int.edges() - if not isis_int.is_bound: - # Could occur for VRFs - log.debug("No ISIS connections for interface %s" % interface) - continue - - # TODO: change this to be is_bound and is_multipoint - if isis_int.multipoint: - log.warning('Extended IOS config support not valid for multipoint ISIS connections on %s' - % interface) - continue - - # TODO multipoint handling? - - edge = edges[0] - dst = edge.dst - if not dst.is_router(): - log.debug('Connection to non-router host not added to IGP' - ) - continue - - src_type = node.device_subtype - dst_type = dst['phy'].device_subtype - if src_type == 'IOS XRv': - if dst_type == 'IOSv': - interface.isis.hello_padding_disable = True - elif dst_type == 'CSR1000v': - interface.isis.hello_padding_disable = True - elif dst_type == 'NX-OSv': - interface.isis.hello_padding_disable = True - - if src_type == 'IOSv': - if dst_type == 'IOS XRv': - interface.isis.mtu = 1430 - - if src_type == 'CSR1000v': - if dst_type == 'IOS XRv': - interface.isis.mtu = 1430 - - if src_type == 'NX-OSv': - if dst_type == 'IOS XRv': - interface.mtu = 1430 # for all of interface - interface.isis.hello_padding_disable = True - elif dst_type == 'IOSv': - interface.isis.hello_padding_disable = True - elif dst_type == 'CSR1000v': - interface.isis.hello_padding_disable = True - - interface.isis_mtu = interface.isis.mtu - interface.hello_padding_disable = \ - interface.isis.hello_padding_disable - - -class IosClassicCompiler(IosBaseCompiler): - - def compile(self, node): - super(IosClassicCompiler, self).compile(node) - - self.mpls_te(node) - self.mpls_oam(node) - self.gre(node) - self.l2tp_v3(node) - - phy_node = self.anm['phy'].node(node) - if phy_node.device_subtype == 'IOSv': - - # only copy across for certain reference platforms - - node.use_onepk = phy_node.use_onepk - node.transport_input_ssh_telnet = True - node.no_service_config = True - node.ipv4_cef = True - node.ipv6_cef = True - - if phy_node.device_subtype == 'CSR1000v': - - # only copy across for certain reference platforms - - node.use_onepk = phy_node.use_onepk - node.transport_input_ssh_telnet = True - node.include_csr = True - - # Set secret password to "cisco" - - node.enable_secret = \ - 'tnhtc92DXBhelxjYk8LWJrPV36S2i4ntXrpb4RFmfqY' - node.exclude_phy_int_auto_speed_duplex = True - node.no_service_config = True - - def rip(self, node): - super(IosClassicCompiler, self).rip(node) - - def ospf(self, node): - super(IosClassicCompiler, self).ospf(node) - loopback_zero = node.loopback_zero - ospf_node = self.anm['ospf'].node(node) - loopback_zero.ospf = { - 'cost': 1, - 'area': ospf_node.area, - 'process_id': node.ospf.process_id, - 'use_ipv4': False, - 'use_ipv6': node.ip.use_ipv6, - 'multipoint': False, - } - - # TODO: add wrapper for this - def gre(self, node): - node.gre_tunnels = [] - if not self.anm.has_overlay('gre_tunnel'): - return - - g_gre_tunnel = self.anm['gre_tunnel'] - if node not in g_gre_tunnel: - return # no gre tunnel for node - - gre_node = g_gre_tunnel.node(node) - neighbors = gre_node.neighbors() - for index, neigh in enumerate(neighbors, start=1): - stanza = ConfigStanza(id=index, endpoint=neigh) - - # TODO: try/except here - # TODO: Explain logic here - src_int = g_gre_tunnel.edge(node, neigh).src_int - tunnel_source = node.interface(src_int).id - stanza.source = tunnel_source - stanza.destination = "0.0.0.0" # placeholder for user to replace - - if neigh.tunnel_enabled_ipv4: - ip_address = neigh.tunnel_ipv4_address - cidr = neigh.tunnel_ipv4_cidr - stanza.ipv4_address = ip_address - stanza.ipv4_subnet = cidr - stanza.use_ipv4 = True - - if neigh.tunnel_enabled_ipv6: - cidr = neigh.tunnel_ipv6_cidr - stanza.ipv4_subnet = cidr - stanza.use_ipv6 = True - - node.gre_tunnels.append(stanza) - - def l2tp_v3(self, node): - node.l2tp_classes = [] - node.pseudowire_classes = [] - if not self.anm.has_overlay('l2tp_v3'): - return - - g_l2tp_v3 = self.anm['l2tp_v3'] - g_phy = self.anm['phy'] - if node not in g_l2tp_v3: - return # no l2tp_v3 for node - - l2tp_v3_node = g_l2tp_v3.node(node) - if l2tp_v3_node.role != "tunnel": - return # nothing to configure - - node.l2tp_classes = list(l2tp_v3_node.l2tp_classes) - for l2tp_class in l2tp_v3_node.l2tp_classes: - node.l2tp_classes - - node.pseudowire_classes = [] - for pwc in l2tp_v3_node.pseudowire_classes: - stanza = ConfigStanza() - stanza.name = pwc['name'] - stanza.encapsulation = pwc['encapsulation'] - stanza.protocol = pwc['protocol'] - stanza.l2tp_class_name = pwc['l2tp_class_name'] - local_interface = pwc['local_interface'] - - # Lookup the interface ID allocated for this loopback by compiler - local_interface_id = node.interface(local_interface).id - stanza.local_interface = local_interface_id - - node.pseudowire_classes.append(stanza) - - for interface in node.physical_interfaces(): - phy_int = g_phy.interface(interface) - if phy_int.xconnect_encapsulation != "l2tpv3": - continue # no l2tpv3 encap, no need to do anything - - tunnel_int = l2tp_v3_node.interface(interface) - stanza = ConfigStanza() - stanza.remote_ip = tunnel_int.xconnect_remote_ip - stanza.vc_id = tunnel_int.xconnect_vc_id - stanza.encapsulation = "l2tpv3" - #TODO: need to be conscious of support for other xconnect types - # in templates since pw_class may not apply if not l2tpv3, et - stanza.pw_class = tunnel_int.xconnect_pw_class - - interface.xconnect = stanza - - def eigrp(self, node): - super(IosClassicCompiler, self).eigrp(node) - # Numeric process IDs use "old-style" non-ipv6 EIGRP stanzas - process_id = node.eigrp.process_id - if str(process_id).isdigit(): - process_id = "as%s" % process_id - node.eigrp.process_id = process_id - - def mpls_te(self, node): - super(IosClassicCompiler, self).mpls_te(node) - - g_mpls_te = self.anm['mpls_te'] - if node not in g_mpls_te: - return # no mpls te configured - - mpls_te_node = g_mpls_te.node(node) - - for interface in mpls_te_node.physical_interfaces(): - nidb_interface = self.nidb.interface(interface) - if not interface.is_bound: - log.debug('Not enable MPLS and RSVP for interface %s on %s ' - % (nidb_interface.id, node)) - continue - nidb_interface.te_tunnels = True - nidb_interface.rsvp_bandwidth_percent = 100 - - def bgp(self, node): - super(IosClassicCompiler, self).bgp(node) - - node.bgp.use_ipv4 = node.ip.use_ipv4 - node.bgp.use_ipv6 = node.ip.use_ipv6 - - # Seperate by address family - - ipv4_peers = [] - ipv6_peers = [] - - # Note cast to dict - #TODO revisit this requirement - # TODO: revisit and tidy up the logic here: split iBGP and eBGP - # TODO: sort the peer list by peer IP - - for peer in node.bgp.ibgp_neighbors: - peer = ConfigStanza(peer) - peer.remote_ip = peer.loopback - if peer.use_ipv4: - if node.is_ebgp_v4: - peer.next_hop_self = True - ipv4_peers.append(peer) - if peer.use_ipv6: - if node.is_ebgp_v6: - peer.next_hop_self = True - ipv6_peers.append(peer) - - for peer in node.bgp.ibgp_rr_parents: - peer = ConfigStanza(peer) - peer.remote_ip = peer.loopback - if peer.use_ipv4: - if node.is_ebgp_v4: - peer.next_hop_self = True - ipv4_peers.append(peer) - if peer.use_ipv6: - if node.is_ebgp_v6: - peer.next_hop_self = True - ipv6_peers.append(peer) - - for peer in node.bgp.ibgp_rr_clients: - peer = ConfigStanza(peer) - peer.rr_client = True - peer.remote_ip = peer.loopback - if peer.use_ipv4: - if node.is_ebgp_v4: - peer.next_hop_self = True - ipv4_peers.append(peer) - if peer.use_ipv6: - if node.is_ebgp_v6: - peer.next_hop_self = True - ipv6_peers.append(peer) - - for peer in node.bgp.ebgp_neighbors: - peer = ConfigStanza(peer) - peer.is_ebgp = True - peer.remote_ip = peer.dst_int_ip - if peer.use_ipv4: - peer.next_hop_self = True - ipv4_peers.append(peer) - if peer.use_ipv6: - peer.next_hop_self = True - ipv6_peers.append(peer) - - node.bgp.ipv4_peers = ipv4_peers - node.bgp.ipv6_peers = ipv6_peers - - vpnv4_neighbors = [] - if node.bgp.vpnv4: - for neigh in node.bgp.ibgp_neighbors: - if not neigh.use_ipv4: - continue - - neigh_data = ConfigStanza(neigh) - vpnv4_neighbors.append(neigh_data) - - for neigh in node.bgp.ibgp_rr_clients: - if not neigh.use_ipv4: - continue - neigh_data = ConfigStanza(neigh) - neigh_data.rr_client = True - vpnv4_neighbors.append(neigh_data) - - for neigh in node.bgp.ibgp_rr_parents: - if not neigh.use_ipv4: - continue - neigh_data = ConfigStanza(neigh) - vpnv4_neighbors.append(neigh_data) - - vpnv4_neighbors = sorted(vpnv4_neighbors, key=lambda x: - x['loopback']) - node.bgp.vpnv4_neighbors = vpnv4_neighbors - - -class IosXrCompiler(IosBaseCompiler): - - def compile(self, node): - super(IosXrCompiler, self).compile(node) - self.mpls_te(node) - self.mpls_oam(node) - - def mpls_te(self, node): - super(IosXrCompiler, self).mpls_te(node) - - g_mpls_te = self.anm['mpls_te'] - if node not in g_mpls_te: - return # no mpls te configured - - rsvp_interfaces = [] - mpls_te_interfaces = [] - mpls_te_node = g_mpls_te.node(node) - - for interface in mpls_te_node.physical_interfaces(): - nidb_interface = self.nidb.interface(interface) - stanza = ConfigStanza(id=nidb_interface.id, - bandwidth_percent=100) - rsvp_interfaces.append(stanza) - - mpls_te_interfaces.append(nidb_interface.id) - - node.add_stanza("rsvp") - node.rsvp.interfaces = rsvp_interfaces - node.mpls.te_interfaces = mpls_te_interfaces - - def rip(self, node): - super(IosXrCompiler, self).rip(node) - - g_rip = self.anm['rip'] - ipv4_interfaces = [] - - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # discontinue configuring IGP for this interface - - rip_int = g_rip.interface(interface) - if rip_int and rip_int.is_bound: - data = {'id': interface.id, 'passive': False} - stanza = ConfigStanza(**data) - if node.rip.use_ipv4: - ipv4_interfaces.append(stanza) - - loopback_zero = node.loopback_zero - data = {'id': node.loopback_zero.id, 'passive': True} - stanza = ConfigStanza(**data) - if node.rip.use_ipv4: - ipv4_interfaces.append(stanza) - - - node.rip.ipv4_interfaces = ipv4_interfaces - - def ospf(self, node): - super(IosXrCompiler, self).ospf(node) - - def eigrp(self, node): - super(IosXrCompiler, self).eigrp(node) - - g_eigrp = self.anm['eigrp'] - ipv4_interfaces = [] - ipv6_interfaces = [] - - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - - eigrp_int = g_eigrp.interface(interface) - if eigrp_int and eigrp_int.is_bound: - # TODO: for here and below use stanza directly - data = {'id': interface.id, 'passive': False} - stanza = ConfigStanza(**data) - if node.eigrp.use_ipv4: - ipv4_interfaces.append(stanza) - if node.eigrp.use_ipv6: - ipv6_interfaces.append(stanza) - - loopback_zero = node.loopback_zero - data = {'id': node.loopback_zero.id, 'passive': True} - stanza = ConfigStanza(**data) - if node.eigrp.use_ipv4: - ipv4_interfaces.append(stanza) - if node.eigrp.use_ipv6: - ipv6_interfaces.append(stanza) - - node.eigrp.ipv4_interfaces = ipv4_interfaces - node.eigrp.ipv6_interfaces = ipv6_interfaces - - def isis(self, node): - super(IosXrCompiler, self).isis(node) - node.isis.isis_links = [] - - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - - # print interface.isis.dump() - # copy across attributes from the IosBaseCompiler setting step - - isis_int = self.anm['isis'].interface(interface) - if isis_int and isis_int.is_bound: - data = {'id': interface.id, 'metric': isis_int.metric, - 'multipoint': isis_int.multipoint} - if interface.isis.hello_padding_disable is not None: - data['hello_padding_disable'] = \ - interface.isis.hello_padding_disable - if interface.isis.mtu is not None: - data['mtu'] = interface.isis.hello_padding_disable - - # TODO: make stanza - stanza = ConfigStanza(**data) - node.isis.isis_links.append(stanza) - - -class NxOsCompiler(IosBaseCompiler): - - def compile(self, node): - super(NxOsCompiler, self).compile(node) - self.mpls_te(node) - self.mpls_oam(node) - - def mpls_te(self, node): - g_mpls_te = self.anm['mpls_te'] - if node not in g_mpls_te: - return # no mpls te configured - - if node.supported_features.mpls_te is False: - node.log.warning("Feature MPLS TE is not supported for %s on the %s platform" % ( - node.device_subtype, node.platform)) - - def mpls_oam(self, node): - g_mpls_oam = self.anm['mpls_oam'] - if node not in g_mpls_oam: - return # no mpls oam configured - - if node.supported_features.mpls_oam is False: - node.log.warning("Feature MPLS OAM is not supported for %s the on %s platform" % ( - node.device_subtype, node.platform)) - - def vrf(self, node): - g_vrf = self.anm['vrf'] - if node not in g_vrf: - return # no mpls oam configured - if node.supported_features.vrf is False: - node.log.warning("Feature VRF is not supported for %s on the %s platform" % ( - node.device_subtype, node.platform)) - - def interfaces(self, node): - - # need to aggregate areas - - super(NxOsCompiler, self).interfaces(node) - - def rip(self, node): - super(NxOsCompiler, self).rip(node) - loopback_zero = node.loopback_zero - loopback_zero.rip = {'use_ipv4': node.ip.use_ipv4, - 'process_id': node.rip.process_id} - - - def ospf(self, node): - super(NxOsCompiler, self).ospf(node) - loopback_zero = node.loopback_zero - g_ospf = self.anm['ospf'] - ospf_node = g_ospf.node(node) - loopback_zero.ospf = { - 'cost': ospf_node.cost, - 'area': ospf_node.area, - 'process_id': node.ospf.process_id, - 'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6, - } - - # TODO: add wrapper for this - - def eigrp(self, node): - - # TODO: do we want to specify the name or hard-code (as currently)? - - super(NxOsCompiler, self).eigrp(node) - loopback_zero = node.loopback_zero - loopback_zero.eigrp = {'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6} - - -class StarOsCompiler(IosBaseCompiler): - - pass diff --git a/autonetkit/compilers/device/device_base.py b/autonetkit/compilers/device/device_base.py deleted file mode 100644 index 2467d2a4..00000000 --- a/autonetkit/compilers/device/device_base.py +++ /dev/null @@ -1,9 +0,0 @@ -class DeviceCompiler(object): - - def __init__(self, nidb, anm): - """Base Router compiler""" - self.nidb = nidb - self.anm = anm - - def compile(self, node): - pass diff --git a/autonetkit/compilers/device/quagga.py b/autonetkit/compilers/device/quagga.py deleted file mode 100644 index 85f4b245..00000000 --- a/autonetkit/compilers/device/quagga.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -from autonetkit.compilers.device.router_base import RouterCompiler -from autonetkit.nidb import ConfigStanza - - -class QuaggaCompiler(RouterCompiler): - - """Base Quagga compiler""" - - lo_interface = 'lo:1' - - def compile(self, node): - super(QuaggaCompiler, self).compile(node) - - def interfaces(self, node): - """Quagga interface compiler""" - # TODO: put this on the router base? - - ipv4_node = self.anm['ipv4'].node(node) - phy_node = self.anm['phy'].node(node) - - super(QuaggaCompiler, self).interfaces(node) - - # OSPF cost - - if phy_node.is_l3device(): - node.loopback_zero.id = self.lo_interface - node.loopback_zero.description = 'Loopback' - node.loopback_zero.ipv4_address = ipv4_node.loopback - node.loopback_zero.ipv4_subnet = node.loopback_subnet - - def ospf(self, node): - """Quagga ospf compiler""" - - super(QuaggaCompiler, self).ospf(node) - - # add eBGP link subnets - - node.ospf.passive_interfaces = [] - - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - - if self.anm.has_overlay('ebgp_v4'): - bgp_int = self.anm['ebgp_v4'].interface(interface) - if bgp_int.is_bound: # ebgp interface - node.ospf.passive_interfaces.append( - ConfigStanza(id=interface.id)) - subnet = bgp_int['ipv4'].subnet - default_ebgp_area = 0 - node.ospf.ospf_links.append( - ConfigStanza(network=subnet, - area=default_ebgp_area)) - - def isis(self, node): - """Sets ISIS links - """ - - g_isis = self.anm['isis'] - isis_node = g_isis.node(node) - node.isis.net = isis_node.net - node.isis.process_id = isis_node.process_id diff --git a/autonetkit/compilers/device/router_base.py b/autonetkit/compilers/device/router_base.py deleted file mode 100644 index 7ede352b..00000000 --- a/autonetkit/compilers/device/router_base.py +++ /dev/null @@ -1,519 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -from autonetkit.compiler import sort_sessions -from autonetkit.compilers.device.device_base import DeviceCompiler -from autonetkit.nidb import ConfigStanza - -import autonetkit.plugins.naming as naming -import autonetkit.log as log -import netaddr -from autonetkit.ank import sn_preflen_to_network - - -class RouterCompiler(DeviceCompiler): - - """Base router compiler""" - - lo_interface = 'lo0' - lo_interface_prefix = 'lo' - -# and set per platform - - def ibgp_session_data(self, session, ip_version): - - # Don't make staticmethod as may want to extend (DeviceCompiler.g. in - # IOS compiler for vpnv4) - - node = session.src - neigh = session.dst - - if ip_version == 4: - neigh_ip = neigh['ipv4'] - use_ipv4 = True - use_ipv6 = False - elif ip_version == 6: - neigh_ip = neigh['ipv6'] - use_ipv4 = False - use_ipv6 = True - - # TODO: return ConfigStanza rather than a dict - data = { # TODO: this is platform dependent??? - 'neighbor': neigh.label, - 'use_ipv4': use_ipv4, - 'use_ipv6': use_ipv6, - 'asn': neigh.asn, - 'loopback': neigh_ip.loopback, - 'update_source': node.loopback_zero.id, - } - return data - - def ebgp_session_data(self, session, ip_version): - node = session.src - neigh = session.dst - if ip_version == 4: - neigh_ip = neigh['ipv4'] - local_int_ip = session.src_int['ipv4'].ip_address - dst_int_ip = session.dst_int['ipv4'].ip_address - use_ipv4 = True - use_ipv6 = False - elif ip_version == 6: - neigh_ip = neigh['ipv6'] - local_int_ip = session.src_int['ipv6'].ip_address - dst_int_ip = session.dst_int['ipv6'].ip_address - use_ipv4 = False - use_ipv6 = True - - md5_password = session.md5_password - multihop = session.multihop - - # TODO: return ConfigStanza rather than a dict - data = { # TODO: change templates to access from node.bgp.lo_int - 'neighbor': neigh.label, - 'use_ipv4': use_ipv4, - 'use_ipv6': use_ipv6, - 'asn': neigh.asn, - 'loopback': neigh_ip.loopback, - 'local_int_ip': local_int_ip, - 'dst_int_ip': dst_int_ip, - 'update_source': node.loopback_zero.id, - 'md5_password': md5_password, - 'multihop': multihop, - } - return data - - def __init__(self, nidb, anm): - """Base Router compiler""" - - super(RouterCompiler, self).__init__(nidb, anm) - - def compile(self, node): - node.do_render = True # turn on rendering - - phy_node = self.anm['phy'].node(node) - ipv4_node = self.anm['ipv4'].node(node) - - node.global_custom_config = phy_node.custom_config - - node.add_stanza("ip") - node.ip.use_ipv4 = phy_node.use_ipv4 or False - node.ip.use_ipv6 = phy_node.use_ipv6 or False - if not (node.ip.use_ipv4 and node.ip.use_ipv6): - node.log.debug( - 'Neither IPv4 nor IPv6 specified: using default IPv4') - - self.static_routes(node) - - # node.ip.use_ipv4 = True - - node.label = naming.network_hostname(phy_node) - node.input_label = phy_node.id - if node.ip.use_ipv4: - node.loopback = ipv4_node.loopback - node.loopback_subnet = netaddr.IPNetwork(node.loopback) - node.loopback_subnet.prefixlen = 32 - - if self.anm['phy'].data.enable_routing: - # applies even if ipv4 disabled, used for eg eigrp, bgp, ... - node.router_id = ipv4_node.loopback - - self.interfaces(node) - if self.anm.has_overlay('ospf') and node in self.anm['ospf']: - self.ospf(node) - if self.anm.has_overlay('isis') and node in self.anm['isis']: - self.isis(node) - if self.anm.has_overlay('eigrp') and node in self.anm['eigrp']: - self.eigrp(node) - ###self.rip(node) - if self.anm.has_overlay('rip') and node in self.anm['rip']: - self.rip(node) - # TODO: drop bgp overlay - bgp_overlays = ["bgp", "ebgp_v4", "ibgp_v4", "ebgp_v6", "ibgp_v6"] - use_bgp = False - for overlay in bgp_overlays: - if (self.anm.has_overlay(overlay) - and node in self.anm[overlay] - and self.anm[overlay].node(node).degree() > 0 - ): - use_bgp = True - break - - if use_bgp: - self.bgp(node) - - def static_routes(self, node): - node.ipv4_static_routes = [] - if self.anm.has_overlay("ipv4"): - ipv4_node = self.anm['ipv4'].node(node) - if ipv4_node and ipv4_node.static_routes: - for route in ipv4_node.static_routes: - stanza = ConfigStanza( - prefix=route.prefix, - netmask=route.netmask, - nexthop=route.nexthop, - metric=route.metric, - ) - node.ipv4_static_routes.append(stanza) - - node.ipv6_static_routes = [] - if self.anm.has_overlay("ipv6"): - ipv6_node = self.anm['ipv6'].node(node) - if ipv6_node and ipv6_node.static_routes: - for route in ipv6_node.static_routes: - stanza = ConfigStanza( - prefix=route.prefix, - nexthop=route.nexthop, - metric=route.metric, - ) - node.ipv6_static_routes.append(stanza) - - def interfaces(self, node): - node.interfaces = [] - - node.loopback_zero.id = self.lo_interface - node.loopback_zero.description = 'Loopback' - phy_node = self.anm['phy'].node(node) - node.loopback_zero.custom_config = phy_node.loopback_zero.custom_config - - if node.ip.use_ipv4: - ipv4_node = self.anm['ipv4'].node(node) - node.loopback_zero.ipv4_address = ipv4_node.loopback - node.loopback_zero.ipv4_subnet = node.loopback_subnet - - # TODO: bne consistent wit hcidr name so can use in cisco ios xr templates - # if node.ip.use_ipv6: - #ipv6_node = self.anm['ipv6'].node(node) - #node.loopback_zero.ipv6_address = ipv6_node.loopback - #node.loopback_zero.ipv6_subnet = node.loopback_subnet - - for interface in node.physical_interfaces(): - phy_int = self.anm['phy'].interface(interface) - interface.physical = True - - # TODO: allocate ID in platform compiler - - if not phy_int: - # for instance if added as management interface to nidb in - # compile - continue - - interface.custom_config = phy_int.custom_config - - interface.description = phy_int.description - - continue - - def ospf(self, node): - """Returns OSPF links, also sets process_id - """ - - g_ospf = self.anm['ospf'] - g_ipv4 = self.anm['ipv4'] - ospf_node = g_ospf.node(node) - ospf_stanza = node.add_stanza("ospf") - - ospf_stanza.custom_config = ospf_node.custom_config - - # default, inherited enable if necessary - node.ospf.ipv4_mpls_te = False - - node.ospf.loopback_area = g_ospf.node(node).area or 0 - - node.ospf.process_id = ospf_node.process_id - node.ospf.lo_interface = self.lo_interface - - node.ospf.ospf_links = [] - - # aggregate by area - from collections import defaultdict - interfaces_by_area = defaultdict(list) - - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - - ospf_int = g_ospf.interface(interface) - if ospf_int and ospf_int.is_bound: - area = ospf_int.area - # TODO: can we remove the next line? - area = str(area) # can't serialize IPAddress object to JSON - # TODO: put in interface rather than interface.id for - # consistency - try: - cost = int(ospf_int.cost) - except TypeError: - #TODO: log warning here - cost = 1 - stanza = ConfigStanza(id=interface.id, - cost=cost, passive=False) - - if node.ip.use_ipv4: - stanza.ipv4_address = ospf_int['ipv4'].ip_address - stanza.ipv4_subnet = ospf_int['ipv4'].subnet - if node.ip.use_ipv6: - stanza.ipv6_address = ospf_int['ipv6'].ip_address - stanza.ipv6_subnet = ospf_int['ipv6'].subnet - - interfaces_by_area[area].append(stanza) - - loopback_zero = node.loopback_zero - ospf_loopback_zero = g_ospf.interface(loopback_zero) - router_area = ospf_loopback_zero.area # area assigned to router - # can't serialize IPAddress object to JSON - router_area = str(router_area) - stanza = ConfigStanza(id=node.loopback_zero.id, - cost=0, passive=True) - interfaces_by_area[router_area].append(stanza) - - node.ospf.interfaces_by_area = ConfigStanza(**interfaces_by_area) - - # TODO: split this into a generic IGP function - added_networks = set() - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - if not interface.use_ipv4: - continue - ipv4_int = g_ipv4.interface(interface) - ospf_int = g_ospf.interface(interface) - if not ospf_int.is_bound: - continue # not an OSPF interface - try: - ospf_cost = int(ospf_int.cost) - except TypeError: - try: - ospf_cost = netaddr.IPAddress(ospf_int.cost) - except (TypeError, netaddr.AddrFormatError): - log.debug('Using default OSPF cost of 1 for %s on %s', - ospf_int, node) - ospf_cost = 1 # default - interface.ospf_cost = ospf_cost - network = ipv4_int.subnet - - #TODO: refactor to allow injecting loopback IPs, etc into IGP - if ospf_int and ospf_int.is_bound and network \ - not in added_networks: # don't add more than once - added_networks.add(network) - link_stanza = ConfigStanza( - network=network, interface=interface, area=ospf_int.area) - node.ospf.ospf_links.append(link_stanza) - - for interface in node.loopback_interfaces(): - phy_int = self.anm['phy'].interface(interface) - ipv4_int = g_ipv4.interface(interface) - if not phy_int.inject_to_igp: - #TODO: need to confirm which area to use - continue - - network = ipv4_int.subnet - if network in added_networks: - #TODO: may want to warn here - continue # already advertised ("how? - warn if so!) - - # Use the same area as Loopback Zero - area = node.ospf.loopback_area - - if not network: - log.info("Not injecting unset network on loopback %s " - "to IGP", interface) - continue - - link_stanza = ConfigStanza( - network=network, interface=interface, area=area) - node.ospf.ospf_links.append(link_stanza) - - # also add networks for subnets to servers in the same AS - - def bgp(self, node): - phy_node = self.anm['phy'].node(node) - g_ipv4 = self.anm['ipv4'] - if node.ip.use_ipv6: - g_ipv6 = self.anm['ipv6'] - asn = phy_node.asn - node.asn = asn - bgp_stanza = node.add_stanza("bgp") - if self.anm.has_overlay("bgp"): - bgp_stanza.custom_config = phy_node['bgp'].custom_config - else: - bgp_stanza.custom_config = "" - - node.bgp.ipv4_advertise_subnets = [] - if node.ip.use_ipv4: - node.bgp.ipv4_advertise_subnets = \ - g_ipv4.data.infra_blocks.get( - asn) or [] # could be none (one-node AS) default empty list - node.bgp.ipv6_advertise_subnets = [] - if node.ip.use_ipv6: - g_ipv6 = self.anm['ipv6'] - node.bgp.ipv6_advertise_subnets = \ - g_ipv6.data.infra_blocks.get(asn) or [] - - ibgp_neighbors = [] - ibgp_rr_clients = [] - ibgp_rr_parents = [] - - g_ibgp_v4 = self.anm['ibgp_v4'] - for session in sort_sessions(g_ibgp_v4.edges(phy_node)): - if session.exclude: - log.debug('Skipping excluded ibgp session %s', session) - # exclude from regular ibgp config (eg VRF, VPLS, etc) - continue - - data = self.ibgp_session_data(session, ip_version=4) - bgp_stanza = ConfigStanza(**data) - - direction = session.direction - if direction == 'down': - ibgp_rr_clients.append(bgp_stanza) - elif direction == 'up': - ibgp_rr_parents.append(bgp_stanza) - else: - ibgp_neighbors.append(bgp_stanza) - - # TODO: check v6 hierarchy only created if node set to being v4 or v6 - - if node.ip.use_ipv6: - g_ibgp_v6 = self.anm['ibgp_v6'] - for session in sort_sessions(g_ibgp_v6.edges(phy_node)): - if session.exclude: - log.debug('Skipping excluded ibgp session %s', session) - # exclude from regular ibgp config (eg VRF, VPLS, etc) - continue - data = self.ibgp_session_data(session, ip_version=6) - bgp_stanza = ConfigStanza(**data) - - direction = session.direction - if direction == 'down': - ibgp_rr_clients.append(bgp_stanza) - elif direction == 'up': - ibgp_rr_parents.append(bgp_stanza) - else: - ibgp_neighbors.append(bgp_stanza) - - # TODO: update this to use ibgp_v4 and ibgp_v6 overlays - - node.bgp.ibgp_neighbors = ibgp_neighbors - node.bgp.ibgp_rr_clients = ibgp_rr_clients - node.bgp.ibgp_rr_parents = ibgp_rr_parents - - # ebgp - - ebgp_neighbors = [] - g_ebgp_v4 = self.anm['ebgp_v4'] - for session in sort_sessions(g_ebgp_v4.edges(phy_node)): - if session.exclude: - log.debug('Skipping excluded ebgp session %s', session) - # exclude from regular ibgp config (eg VRF, VPLS, etc) - continue - data = self.ebgp_session_data(session, ip_version=4) - bgp_stanza = ConfigStanza(**data) - ebgp_neighbors.append(bgp_stanza) - - if node.ip.use_ipv6: - g_ebgp_v6 = self.anm['ebgp_v6'] - for session in sort_sessions(g_ebgp_v6.edges(phy_node)): - if session.exclude: - log.debug('Skipping excluded ebgp session %s', session) - # exclude from regular ibgp config (eg VRF, VPLS, etc) - continue - data = self.ebgp_session_data(session, ip_version=6) - bgp_stanza = ConfigStanza(**data) - ebgp_neighbors.append(bgp_stanza) - - ebgp_neighbors = sorted(ebgp_neighbors, key=lambda x: x.asn) - node.bgp.ebgp_neighbors = ebgp_neighbors - - return - - def rip(self, node): - g_rip = self.anm['rip'] - g_ipv4 = self.anm['ipv4'] - rip_node = self.anm['rip'].node(node) - node.rip.process_id = rip_node.process_id - node.rip.custom_config = rip_node.custom_config - - ipv4_networks = set() - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # discontinue configuring IGP for this interface. - ipv4_int = g_ipv4.interface(interface) - rip_int = g_rip.interface(interface) - if not rip_int.is_bound: - continue # not an rip interface - network = ipv4_int.subnet - if rip_int and rip_int.is_bound and interface.use_ipv4: - ipv4_networks.add(network) - - ipv4_networks.add(node.loopback_zero.ipv4_cidr) - node.rip.ipv4_networks = sorted(list(ipv4_networks)) - - - def isis(self, node): - g_isis = self.anm['isis'] - isis_node = g_isis.node(node) - process_id = isis_node.process_id - node.isis.custom_config = isis_node.custom_config - - # default, inherited enable if necessary - node.isis.ipv4_mpls_te = False - - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - - phy_int = self.anm['phy'].interface(interface) - - isis_int = phy_int['isis'] - if isis_int and isis_int.is_bound: - isis_node = g_isis.node(node) - interface.isis = { - 'metric': isis_int.metric, - 'process_id': process_id, - 'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6, - 'multipoint': isis_int.multipoint, - } - - # TODO: add wrapper for this - - g_isis = self.anm['isis'] - isis_node = self.anm['isis'].node(node) - node.isis.net = isis_node.net - - # TODO: generalise loopbacks to allow more than one per device - - node.isis.process_id = process_id - node.isis.lo_interface = self.lo_interface - -# set isis on loopback_zero - - node.loopback_zero.isis = {'use_ipv4': node.ip.use_ipv4, - 'use_ipv6': node.ip.use_ipv6} - - # TODO: add wrapper for this - - def eigrp(self, node): - - g_eigrp = self.anm['eigrp'] - g_ipv4 = self.anm['ipv4'] - eigrp_node = self.anm['eigrp'].node(node) - node.eigrp.process_id = eigrp_node.process_id - node.eigrp.custom_config = eigrp_node.custom_config - - ipv4_networks = set() - for interface in node.physical_interfaces(): - if interface.exclude_igp: - continue # don't configure IGP for this interface - ipv4_int = g_ipv4.interface(interface) - eigrp_int = g_eigrp.interface(interface) - if not eigrp_int.is_bound: - continue # not an EIGRP interface - network = ipv4_int.subnet - if eigrp_int and eigrp_int.is_bound and interface.use_ipv4: - ipv4_networks.add(network) - - # Loopback zero subnet - - - ipv4_networks.add(node.loopback_zero.ipv4_cidr) - - node.eigrp.ipv4_networks = sorted(list(ipv4_networks)) diff --git a/autonetkit/compilers/device/server_base.py b/autonetkit/compilers/device/server_base.py deleted file mode 100644 index c84d18fd..00000000 --- a/autonetkit/compilers/device/server_base.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import autonetkit.log as log -from autonetkit.compilers.device.device_base import DeviceCompiler -from autonetkit.nidb import ConfigStanza - - -class ServerCompiler(DeviceCompiler): - - def compile(self, node): - node.do_render = True # turn on rendering - phy_node = self.anm['phy'].node(node) - ipv4_node = self.anm['ipv4'].node(node) - super(ServerCompiler, self).compile(node) diff --git a/autonetkit/compilers/device/ubuntu.py b/autonetkit/compilers/device/ubuntu.py deleted file mode 100644 index 00e64b42..00000000 --- a/autonetkit/compilers/device/ubuntu.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import autonetkit.log as log -from autonetkit.compilers.device.server_base import ServerCompiler -from autonetkit.nidb import ConfigStanza - - -class UbuntuCompiler(ServerCompiler): - - def compile(self, node): - super(UbuntuCompiler, self).compile(node) - - # up route add -net ${route.network} gw ${router.gw} dev - # ${route.interface} - - self.static_routes(node) - - def static_routes(self, node): - # initialise for case of no routes -> simplifies template logic - node.static_routes_v4 = [] - # initialise for case of no routes -> simplifies template logic - node.host_routes_v4 = [] - # initialise for case of no routes -> simplifies template logic - node.static_routes_v6 = [] - # initialise for case of no routes -> simplifies template logic - node.host_routes_v6 = [] - if not self.anm['phy'].data.enable_routing: - log.info('Routing disabled, not configuring static routes for Ubuntu server %s' - % node) - return - - if self.anm['phy'].node(node).dont_configure_static_routing: - log.info('Static routing disabled for server %s' % node) - return - - l3_node = self.anm['layer3'].node(node) - gateway_list = [n for n in l3_node.neighbors() - if n.is_router()] - if not len(gateway_list): - log.warning('Server %s is not directly connected to any routers' - % node) - return - elif len(gateway_list) > 1: - log.info('Server %s is multi-homed: using gateways %s' - % (node, sorted(gateway_list))) - - # TODO: warn if server has no neighbors in same ASN (either in design or verification steps) - # TODO: need to check that servers don't have any direct ebgp - # connections - - cloud_init_static_routes = [] - g_l3 = self.anm['layer3'] - - for gateway in sorted(gateway_list): - for gateway_edge_l3 in g_l3.edges(node, gateway): - server_interface = gateway_edge_l3.src_int - server_interface_id = self.nidb.interface(server_interface).id - - gateway_interface = gateway_edge_l3.dst_int - - gateway_ipv4 = gateway_ipv6 = None - node.add_stanza("ip") - if node.ip.use_ipv4: - gateway_ipv4 = gateway_interface['ipv4'].ip_address - if node.ip.use_ipv6: - gateway_ipv6 = gateway_interface['ipv6'].ip_address - - # TODO: look at aggregation - # TODO: catch case of ip addressing being disabled - - # TODO: handle both ipv4 and ipv6 - - # IGP advertised infrastructure pool from same AS - static_routes_v4 = [] - host_routes_v4 = [] - for (asn, asn_routes) in self.anm['ipv4'].data['infra_blocks'].items(): - - # host_routes_v4 - for infra_route in asn_routes: - route_entry = { - 'network': infra_route, - 'prefix': infra_route.network, - 'gw': gateway_ipv4, - 'interface': server_interface_id, - 'description': 'Route to infra subnet in AS %s via %s' - % (asn, gateway), - } - route_entry = ConfigStanza(**route_entry) - if infra_route.prefixlen == 32: - host_routes_v4.append(route_entry) - else: - static_routes_v4.append(route_entry) - - # eBGP advertised loopbacks in all (same + other) ASes - - for (asn, asn_routes) in self.anm['ipv4'].data['loopback_blocks' - ].items(): - for asn_route in asn_routes: - route_entry = { - 'network': asn_route, - 'prefix': asn_route.network, - 'gw': gateway_ipv4, - 'interface': server_interface_id, - 'description': 'Route to loopback subnet in AS %s via %s' - % (asn, gateway), - } - route_entry = ConfigStanza(**route_entry) - if asn_route.prefixlen == 32: - host_routes_v4.append(route_entry) - else: - static_routes_v4.append(route_entry) - - # TODO: combine the above logic into single step rather than - # creating dict then formatting with it - - for entry in static_routes_v4: - formatted = 'route add -net %s gw %s dev %s' \ - % (entry.network, entry.gw, entry.interface) - cloud_init_static_routes.append(formatted) - for entry in host_routes_v4: - formatted = 'route add -host %s gw %s dev %s' \ - % (entry.prefix, entry.gw, entry.interface) - cloud_init_static_routes.append(formatted) - - node.add_stanza("cloud_init") - node.cloud_init.static_routes = cloud_init_static_routes - - # Render inline for packaging into yaml - # TODO: no longer used, but keep as reference for later templates that require this format - # import autonetkit.render - # import os - # lookup = autonetkit.render.initialise_lookup() - # render_template = os.path.join("templates", "linux", "static_route.mako") - # render_output = autonetkit.render.render_inline(node, render_template) - # node.cloud_init.static_routes = render_output diff --git a/autonetkit/compilers/platform/__init__.py b/autonetkit/compilers/platform/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/compilers/platform/cisco.py b/autonetkit/compilers/platform/cisco.py deleted file mode 100644 index 5c0b5afb..00000000 --- a/autonetkit/compilers/platform/cisco.py +++ /dev/null @@ -1,402 +0,0 @@ -import itertools -import os -from datetime import datetime - -import autonetkit -import autonetkit.config -import autonetkit.log as log -import autonetkit.plugins.naming as naming -from autonetkit.ank import sn_preflen_to_network -from autonetkit.ank_utils import call_log -from autonetkit.compilers.device.cisco import (IosBaseCompiler, - IosClassicCompiler, - IosXrCompiler, NxOsCompiler, - StarOsCompiler) -from autonetkit.compilers.platform.platform_base import PlatformCompiler -from autonetkit.nidb import ConfigStanza - - -class CiscoCompiler(PlatformCompiler): - - """Platform compiler for Cisco""" - - @staticmethod - def numeric_to_interface_label_ios(x): - """Starts at GigabitEthernet0/1 """ - x = x + 1 - return "GigabitEthernet0/%s" % x - - @staticmethod - def numeric_to_interface_label_ra(x): - """Starts at Gi0/1 - #TODO: check""" - x = x + 1 - return "GigabitEthernet%s" % x - - @staticmethod - def numeric_to_interface_label_nxos(x): - return "Ethernet2/%s" % (x + 1) - - @staticmethod - def numeric_to_interface_label_ios_xr(x): - return "GigabitEthernet0/0/0/%s" % x - - @staticmethod - def numeric_to_interface_label_star_os(x): - return "ethernet 1/%s" % (x + 10) - - @staticmethod - def numeric_to_interface_label_linux(x): - return "eth%s" % x - - @staticmethod - def loopback_interface_ids(): - for x in itertools.count(100): # start at 100 for secondary - prefix = IosBaseCompiler.lo_interface_prefix - yield "%s%s" % (prefix, x) - - @staticmethod - def interface_ids_ios(): - # TODO: make this skip if in list of allocated ie [interface.name for - # interface in node] - for x in itertools.count(0): - yield "GigabitEthernet0/%s" % x - - @staticmethod - def interface_ids_csr1000v(): - # TODO: make this skip if in list of allocated ie [interface.name for - # interface in node] - for x in itertools.count(0): - yield "GigabitEthernet%s" % x - - @staticmethod - def interface_ids_nxos(): - for x in itertools.count(0): - yield "Ethernet2/%s" % x - - @staticmethod - def interface_ids_ios_xr(): - for x in itertools.count(0): - yield "GigabitEthernet0/0/0/%s" % x - - @staticmethod - def numeric_interface_ids(): - """#TODO: later skip interfaces already taken""" - for x in itertools.count(0): - yield x - - #@call_log - def compile(self): - self.copy_across_ip_addresses() - self.compile_devices() - self.assign_management_interfaces() - - def _parameters(self): - g_phy = self.anm['phy'] - settings = autonetkit.config.settings - to_memory = settings['Compiler']['Cisco']['to memory'] - use_mgmt_interfaces = g_phy.data.mgmt_interfaces_enabled - - now = datetime.now() - if settings['Compiler']['Cisco']['timestamp']: - timestamp = now.strftime("%Y%m%d_%H%M%S_%f") - dst_folder = os.path.join( - "rendered", self.host, timestamp, "cisco") - else: - dst_folder = os.path.join("rendered", self.host, "cisco") - - # TODO: use a namedtuple - return to_memory, use_mgmt_interfaces, dst_folder - - #@call_log - def compile_devices(self): - g_phy = self.anm['phy'] - - to_memory, use_mgmt_interfaces, dst_folder = self._parameters() - if use_mgmt_interfaces: - log.debug("Allocating VIRL management interfaces") - else: - log.debug("Not allocating VIRL management interfaces") -# TODO: need to copy across the interface name from edge to the interface - -# TODO: merge common router code, so end up with three loops: routers, ios -# routers, ios_xr routers - - # TODO: Split out each device compiler into own function - - # TODO: look for unused code paths here - especially for interface - # allocation - - # store autonetkit_cisco version - log.debug("Generating device configurations") - from pkg_resources import get_distribution - - # Copy across indices for external connectors (e.g may want to copy - # configs) - external_connectors = [n for n in g_phy - if n.host == self.host and n.device_type == "external_connector"] - for phy_node in external_connectors: - DmNode = self.nidb.node(phy_node) - DmNode.indices = phy_node.indices - - managed_switches = [n for n in g_phy.switches() - if n.host == self.host - and n.device_subtype == "managed"] - for phy_node in managed_switches: - DmNode = self.nidb.node(phy_node) - DmNode.indices = phy_node.indices - - for phy_node in g_phy.l3devices(host=self.host): - loopback_ids = self.loopback_interface_ids() - # allocate loopbacks to routes (same for all ios variants) - DmNode = self.nidb.node(phy_node) - DmNode.add_stanza("render") - DmNode.indices = phy_node.indices - - for interface in DmNode.loopback_interfaces(): - if interface != DmNode.loopback_zero: - interface.id = loopback_ids.next() - - # numeric ids - numeric_int_ids = self.numeric_interface_ids() - for interface in DmNode.physical_interfaces(): - phy_numeric_id = phy_node.interface(interface).numeric_id - if phy_numeric_id is None: - # TODO: remove numeric ID code - interface.numeric_id = numeric_int_ids.next() - else: - interface.numeric_id = int(phy_numeric_id) - - phy_specified_id = phy_node.interface(interface).specified_id - if phy_specified_id is not None: - interface.id = phy_specified_id - - #from autonetkit.compilers.device.ubuntu import UbuntuCompiler - from autonetkit_cisco.compilers.device.ubuntu import UbuntuCompiler - - ubuntu_compiler = UbuntuCompiler(self.nidb, self.anm) - for phy_node in g_phy.servers(host=self.host): - DmNode = self.nidb.node(phy_node) - DmNode.add_stanza("render") - DmNode.add_stanza("ip") - - # TODO: look at server syntax also, same as for routers - for interface in DmNode.physical_interfaces(): - phy_specified_id = phy_node.interface(interface).specified_id - if phy_specified_id is not None: - interface.id = phy_specified_id - - #interface.id = self.numeric_to_interface_label_linux(interface.numeric_id) - # print "numeric", interface.numeric_id, interface.id - DmNode.ip.use_ipv4 = phy_node.use_ipv4 - DmNode.ip.use_ipv6 = phy_node.use_ipv6 - - # TODO: clean up interface handling - numeric_int_ids = self.numeric_interface_ids() - for interface in DmNode.physical_interfaces(): - phy_int = phy_node.interface(interface) - phy_numeric_id = phy_node.interface(interface).numeric_id - if phy_numeric_id is None: - # TODO: remove numeric ID code - interface.numeric_id = numeric_int_ids.next() - else: - interface.numeric_id = int(phy_numeric_id) - - phy_specified_id = phy_node.interface(interface).specified_id - if phy_specified_id is not None: - interface.id = phy_specified_id - - # TODO: make this part of the base device compiler, which - # server/router inherits - - # not these are physical interfaces; configure after previous - # config steps - if use_mgmt_interfaces: - mgmt_int = DmNode.add_interface( - management=True, description="eth0") - mgmt_int_id = "eth0" - mgmt_int.id = mgmt_int_id - - # render route config - DmNode = self.nidb.node(phy_node) - ubuntu_compiler.compile(DmNode) - - if not phy_node.dont_configure_static_routing: - DmNode.render.template = os.path.join( - "templates", "linux", "static_route.mako") - if to_memory: - DmNode.render.to_memory = True - else: - DmNode.render.dst_folder = dst_folder - DmNode.render.dst_file = "%s.conf" % naming.network_hostname( - phy_node) - - # TODO: refactor out common logic - - ios_compiler = IosClassicCompiler(self.nidb, self.anm) - host_routers = g_phy.routers(host=self.host) - ios_nodes = (n for n in host_routers if n.syntax in ("ios", "ios_xe")) - for phy_node in ios_nodes: - DmNode = self.nidb.node(phy_node) - DmNode.add_stanza("render") - DmNode.render.template = os.path.join("templates", "ios.mako") - if to_memory: - DmNode.render.to_memory = True - else: - DmNode.render.dst_folder = dst_folder - DmNode.render.dst_file = "%s.conf" % naming.network_hostname( - phy_node) - - # TODO: write function that assigns interface number excluding - # those already taken - - # Assign interfaces - if phy_node.device_subtype == "IOSv": - int_ids = self.interface_ids_ios() - numeric_to_interface_label = self.numeric_to_interface_label_ios - elif phy_node.device_subtype == "CSR1000v": - int_ids = self.interface_ids_csr1000v() - numeric_to_interface_label = self.numeric_to_interface_label_ra - else: - # default if no subtype specified - # TODO: need to set default in the load module - log.warning("Unexpected subtype %s for %s" % - (phy_node.device_subtype, phy_node)) - int_ids = self.interface_ids_ios() - numeric_to_interface_label = self.numeric_to_interface_label_ios - - if use_mgmt_interfaces: - if phy_node.device_subtype == "IOSv": - # TODO: make these configured in the internal config file - # for platform/device_subtype keying - mgmt_int_id = "GigabitEthernet0/0" - if phy_node.device_subtype == "CSR1000v": - mgmt_int_id = "GigabitEthernet1" - - for interface in DmNode.physical_interfaces(): - # TODO: use this code block once for all routers - if not interface.id: - interface.id = numeric_to_interface_label( - interface.numeric_id) - - ios_compiler.compile(DmNode) - if use_mgmt_interfaces: - mgmt_int = DmNode.add_interface(management=True) - mgmt_int.id = mgmt_int_id - - try: - from autonetkit_cisco.compilers.device.cisco import IosXrCompiler - ios_xr_compiler = IosXrCompiler(self.nidb, self.anm) - except ImportError: - ios_xr_compiler = IosXrCompiler(self.nidb, self.anm) - - for phy_node in g_phy.routers(host=self.host, syntax='ios_xr'): - DmNode = self.nidb.node(phy_node) - DmNode.add_stanza("render") - DmNode.render.template = os.path.join( - "templates", "ios_xr", "router.conf.mako") - if to_memory: - DmNode.render.to_memory = True - else: - DmNode.render.dst_folder = dst_folder - DmNode.render.dst_file = "%s.conf" % naming.network_hostname( - phy_node) - - # Assign interfaces - int_ids = self.interface_ids_ios_xr() - for interface in DmNode.physical_interfaces(): - if not interface.id: - interface.id = self.numeric_to_interface_label_ios_xr( - interface.numeric_id) - - ios_xr_compiler.compile(DmNode) - - if use_mgmt_interfaces: - mgmt_int_id = "mgmteth0/0/CPU0/0" - mgmt_int = DmNode.add_interface(management=True) - mgmt_int.id = mgmt_int_id - - nxos_compiler = NxOsCompiler(self.nidb, self.anm) - for phy_node in g_phy.routers(host=self.host, syntax='nx_os'): - DmNode = self.nidb.node(phy_node) - DmNode.add_stanza("render") - DmNode.render.template = os.path.join("templates", "nx_os.mako") - if to_memory: - DmNode.render.to_memory = True - else: - DmNode.render.dst_folder = dst_folder - DmNode.render.dst_file = "%s.conf" % naming.network_hostname( - phy_node) - - # Assign interfaces - int_ids = self.interface_ids_nxos() - for interface in DmNode.physical_interfaces(): - if not interface.id: - interface.id = self.numeric_to_interface_label_nxos( - interface.numeric_id) - - DmNode.supported_features = ConfigStanza( - mpls_te=False, mpls_oam=False, vrf=False) - - nxos_compiler.compile(DmNode) - # TODO: make this work other way around - - if use_mgmt_interfaces: - mgmt_int_id = "mgmt0" - mgmt_int = DmNode.add_interface(management=True) - mgmt_int.id = mgmt_int_id - - staros_compiler = StarOsCompiler(self.nidb, self.anm) - for phy_node in g_phy.routers(host=self.host, syntax='StarOS'): - DmNode = self.nidb.node(phy_node) - DmNode.add_stanza("render") - DmNode.render.template = os.path.join("templates", "staros.mako") - if to_memory: - DmNode.render.to_memory = True - else: - DmNode.render.dst_folder = dst_folder - DmNode.render.dst_file = "%s.conf" % naming.network_hostname( - phy_node) - - # Assign interfaces - int_ids = self.interface_ids_nxos() - for interface in DmNode.physical_interfaces(): - if not interface.id: - interface.id = self.numeric_to_interface_label_star_os( - interface.numeric_id) - - staros_compiler.compile(DmNode) - # TODO: make this work other way around - - if use_mgmt_interfaces: - mgmt_int_id = "ethernet 1/1" - mgmt_int = DmNode.add_interface(management=True) - mgmt_int.id = mgmt_int_id - - def assign_management_interfaces(self): - g_phy = self.anm['phy'] - use_mgmt_interfaces = g_phy.data.mgmt_interfaces_enabled - if not use_mgmt_interfaces: - return - lab_topology = self.nidb.topology(self.host) - oob_management_ips = {} - - hosts_to_allocate = sorted(self.nidb.l3devices(host=self.host)) - dhcp_subtypes = {"vios"} - dhcp_hosts = [ - h for h in hosts_to_allocate if h.device_subtype in dhcp_subtypes] - - for DmNode in hosts_to_allocate: - for interface in DmNode.physical_interfaces(): - if interface.management: - interface.description = "OOB Management" - interface.physical = True - interface.mgmt = True - interface.comment = "Configured on launch" - if DmNode.ip.use_ipv4: - # want a "no ip address" stanza - interface.use_ipv4 = False - if DmNode.use_cdp: - interface.use_cdp = True # ensure CDP activated - if DmNode in dhcp_hosts: - interface.use_dhcp = True diff --git a/autonetkit/compilers/platform/netkit.py b/autonetkit/compilers/platform/netkit.py deleted file mode 100644 index 0531a62f..00000000 --- a/autonetkit/compilers/platform/netkit.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Compiler for Netkit""" -import os -import autonetkit -import autonetkit.config -import autonetkit.log as log -import autonetkit.plugins.naming as naming -from autonetkit.compilers.platform.platform_base import PlatformCompiler -import string -import itertools -from autonetkit.ank_utils import alphabetical_sort as alpha_sort -from autonetkit.compilers.device.quagga import QuaggaCompiler -from autonetkit.nidb import ConfigStanza -from autonetkit.render2 import NodeRender, PlatformRender - - -class NetkitCompiler(PlatformCompiler): - - """Netkit Platform Compiler""" - @staticmethod - def index_to_int_id(index): - """Maps interface index to ethx e.g. eth0, eth1, ...""" - return "eth%s" % index - - def compile(self): - self.copy_across_ip_addresses() - - log.info("Compiling Netkit for %s" % self.host) - g_phy = self.anm['phy'] - quagga_compiler = QuaggaCompiler(self.nidb, self.anm) - - # todo: set platform render - lab_topology = self.nidb.topology(self.host) - lab_topology.render2 = PlatformRender() - -# TODO: this should be all l3 devices not just routers - for phy_node in g_phy.l3devices(host=self.host, syntax='quagga'): - folder_name = naming.network_hostname(phy_node) - dm_node = self.nidb.node(phy_node) - dm_node.add_stanza("render") - # TODO: order by folder and file template src/dst - dm_node.render.base = os.path.join("templates", "quagga") - dm_node.render.template = os.path.join("templates", - "netkit_startup.mako") - dm_node.render.dst_folder = os.path.join("rendered", - self.host, "netkit") - dm_node.render.base_dst_folder = os.path.join("rendered", - self.host, "netkit", folder_name) - dm_node.render.dst_file = "%s.startup" % folder_name - - dm_node.render.custom = { - 'abc': 'def.txt' - } - - render2 = NodeRender() - # TODO: dest folder also needs to be able to accept a list - # TODO: document that use a list so can do native os.path.join on - # target platform - render2.add_folder(["templates", "quagga"], folder_name) - render2.add_file( - ("templates", "netkit_startup.mako"), "%s.startup" % folder_name) - dm_node.render2 = render2 - lab_topology.render2.add_node(dm_node) - # lab_topology.render2_hosts.append(phy_node) - -# allocate zebra information - dm_node.add_stanza("zebra") - if dm_node.is_router(): - dm_node.zebra.password = "1234" - hostname = folder_name - if hostname[0] in string.digits: - hostname = "r" + hostname - dm_node.hostname = hostname # can't have . in quagga hostnames - dm_node.add_stanza("ssh") - # TODO: make this set based on presence of key - dm_node.ssh.use_key = True - - # Note this could take external data - int_ids = itertools.count(0) - for interface in dm_node.physical_interfaces(): - numeric_id = int_ids.next() - interface.numeric_id = numeric_id - interface.id = self.index_to_int_id(numeric_id) - -# and allocate tap interface - dm_node.add_stanza("tap") - dm_node.tap.id = self.index_to_int_id(int_ids.next()) - - quagga_compiler.compile(dm_node) - - if dm_node.bgp: - dm_node.bgp.debug = True - static_routes = [] - dm_node.zebra.static_routes = static_routes - - # and lab.conf - self.allocate_tap_ips() - self.allocate_lab_topology() - - def allocate_tap_ips(self): - """Allocates TAP IPs""" - settings = autonetkit.config.settings - lab_topology = self.nidb.topology(self.host) - from netaddr import IPNetwork - address_block = IPNetwork(settings.get("tapsn") - or "172.16.0.0/16").iter_hosts() # added for backwards compatibility - lab_topology.tap_host = address_block.next() - lab_topology.tap_vm = address_block.next() # for tunnel host - for node in sorted(self.nidb.l3devices(host=self.host)): - node.tap.ip = address_block.next() - - def allocate_lab_topology(self): - # TODO: replace name/label and use attribute from subgraph - lab_topology = self.nidb.topology(self.host) - lab_topology.render_template = os.path.join("templates", - "netkit_lab_conf.mako") - lab_topology.render_dst_folder = os.path.join("rendered", - self.host, "netkit") - lab_topology.render_dst_file = "lab.conf" - lab_topology.description = "AutoNetkit Lab" - lab_topology.author = "AutoNetkit" - lab_topology.web = "www.autonetkit.org" - - render2 = lab_topology.render2 - # TODO: test with adding a folder - #render2.add_folder(["templates", "quagga"], folder_name) - render2.add_file(("templates", "netkit_lab_conf.mako"), "lab.conf") - render2.base_folder = [self.host, "netkit"] - render2.archive = "%s_%s" % (self.host, "netkit") - render2.template_data = { - "render_dst_file": "lab.conf", - "description": "AutoNetkit Lab", - "author": "AutoNetkit", - "web": "www.autonetkit.org", - } - - host_nodes = list( - self.nidb.nodes(host=self.host, platform="netkit")) - if not len(host_nodes): - log.debug("No Netkit hosts for %s" % self.host) -# also need collision domains for this host - cd_nodes = self.nidb.nodes("broadcast_domain", host=self.host) - host_nodes += cd_nodes - subgraph = self.nidb.subgraph(host_nodes, self.host) - - lab_topology.machines = " ".join(alpha_sort(naming.network_hostname(phy_node) - for phy_node in subgraph.l3devices())) - - lab_topology.config_items = [] - for node in sorted(subgraph.l3devices()): - for interface in node.physical_interfaces(): - broadcast_domain = str(interface.ipv4_subnet).replace("/", ".") - # netkit lab.conf uses 1 instead of eth1 - numeric_id = interface.numeric_id - stanza = ConfigStanza( - device=naming.network_hostname(node), - key=numeric_id, - value=broadcast_domain, - ) - lab_topology.config_items.append(stanza) - - lab_topology.tap_ips = [] - for node in subgraph: - if node.tap: - stanza = ConfigStanza( - device=naming.network_hostname(node), - id=node.tap.id.replace("eth", ""), # strip ethx -> x - ip=node.tap.ip, - ) - lab_topology.tap_ips.append(stanza) - - lab_topology.tap_ips = sorted(lab_topology.tap_ips, key=lambda x: x.ip) - lab_topology.config_items = sorted( - lab_topology.config_items, key=lambda x: x.device) diff --git a/autonetkit/compilers/platform/platform_base.py b/autonetkit/compilers/platform/platform_base.py deleted file mode 100644 index 46e03e45..00000000 --- a/autonetkit/compilers/platform/platform_base.py +++ /dev/null @@ -1,101 +0,0 @@ -import autonetkit.log as log - - -class PlatformCompiler(object): - - """Base Platform Compiler""" - - def __init__(self, nidb, anm, host): - self.nidb = nidb - self.anm = anm - self.host = host - - @property - def timestamp(self): - return self.nidb.timestamp - - def compile(self): - # TODO: make this abstract - pass - - def copy_across_ip_addresses(self): - # log.info("Copying IP addresses to device model") - # TODO: try/except and raise SystemError as fatal error if cant copy - from autonetkit.ank import sn_preflen_to_network - - # TODO: check if this will clobber with platform? - for node in self.nidb.l3devices(host=self.host): - phy_node = self.anm['phy'].node(node) - - node.add_stanza("ip") - node.ip.use_ipv4 = phy_node.use_ipv4 or False - node.ip.use_ipv6 = phy_node.use_ipv6 or False - - for interface in node.interfaces: - phy_int = phy_node.interface(interface) - if phy_int.exclude_igp is not None: - interface.exclude_igp = phy_int.exclude_igp - - if phy_node.use_ipv4: - ipv4_int = phy_int['ipv4'] - - """ - TODO: refactor this logic (and for ipv6) to only check if None - then compilers are more pythonic - test for IP is None - rather than bound etc - simplifies logic - make try to copy, and if fail then warn and set use_ipv4 to False - """ - if node.is_server() and interface.is_loopback: - continue - if interface.is_physical and not interface.is_bound: - continue - - # permit unbound ip interfaces (e.g. if skipped for l2 encap) - if interface.is_physical and not ipv4_int.is_bound: - interface.use_ipv4 = False - continue - - if ipv4_int.ip_address is None: - #TODO: put into dev log - log.debug("No IP address allocated on %s", interface) - interface.use_ipv4 = False - continue - - # TODO: also need to skip layer2 virtual interfaces - # interface is connected - try: - interface.ipv4_address = ipv4_int.ip_address - interface.ipv4_subnet = ipv4_int.subnet - interface.ipv4_cidr = sn_preflen_to_network(interface.ipv4_address, - interface.ipv4_subnet.prefixlen) - except AttributeError, error: - log.warning( - "Unable to copy across IPv4 for %s" % interface) - log.debug(error) - else: - interface.use_ipv4 = True - if phy_node.use_ipv6: - ipv6_int = phy_int['ipv6'] - if node.is_server() and interface.is_loopback: - continue - if interface.is_physical and not interface.is_bound: - continue - # permit unbound ip interfaces (e.g. if skipped for l2 encap) - if interface.is_physical and not ipv6_int.is_bound: - interface.use_ipv6 = False - continue - if ipv6_int.ip_address is None: - #TODO: put into dev log - log.debug("No IP address allocated on %s", interface) - interface.use_ipv6 = False - continue - try: - # TODO: copy ip address as well - interface.ipv6_subnet = ipv6_int.subnet - interface.ipv6_address = sn_preflen_to_network(ipv6_int.ip_address, - interface.ipv6_subnet.prefixlen) - except AttributeError: - log.warning( - "Unable to copy IPv6 subnet for %s" % interface) - else: - interface.use_ipv6 = True diff --git a/autonetkit/config.py b/autonetkit/config.py deleted file mode 100644 index 45c72ba2..00000000 --- a/autonetkit/config.py +++ /dev/null @@ -1,45 +0,0 @@ -import ConfigParser -import os -import os.path - -import pkg_resources -import validate -from autonetkit.exception import AutoNetkitException -from configobj import ConfigObj, flatten_errors - -validator = validate.Validator() - -# from http://stackoverflow.com/questions/4028904 -ank_user_dir = os.path.join(os.path.expanduser("~"), ".autonetkit") - -def load_config(): - retval = ConfigParser.RawConfigParser() - spec_file = pkg_resources.resource_filename(__name__,"/config/configspec.cfg") - retval = ConfigObj(configspec=spec_file, encoding='UTF8') -# User's ANK retval - user_config_file = os.path.join(ank_user_dir, "autonetkit.cfg") - retval.merge(ConfigObj(user_config_file)) -# ANK retval in current directory - retval.merge(ConfigObj("autonetkit.cfg")) -# ANK retval specified by environment variable - try: - ankcfg = os.environ['AUTONETKIT_CFG'] - retval.merge(ConfigObj(ankcfg)) - except KeyError: - pass - - results = retval.validate(validator) - if results != True: - for (section_list, key, _) in flatten_errors(retval, results): - if key is not None: - print "Error loading configuration file:" - print 'Invalid key "%s" in section "%s"' % (key, ', '.join(section_list)) - raise AutoNetkitException - else: -# ignore missing sections - use defaults - #print 'The following section was missing:%s ' % ', '.join(section_list) - pass - return retval - -#NOTE: this only gets loaded once package-wide if imported as import autonetkit.config -settings = load_config() diff --git a/autonetkit/config/configspec.cfg b/autonetkit/config/configspec.cfg deleted file mode 100644 index fdf78f8b..00000000 --- a/autonetkit/config/configspec.cfg +++ /dev/null @@ -1,109 +0,0 @@ -[Rabbitmq] -active = boolean(default=False) -server = string(default = "127.0.0.1") - -[Http Post] -active = boolean(default=True) -server = string(default = "127.0.0.1") -port = integer(default = 8000) -uuid = string(default = "singleuser") - -[Measurement] -host = string(default = "localhost") -port = integer(default = 5559) - -[Logging] -console = boolean(default=False) -file = boolean(default=False) - -[General] -archive = boolean(default=False) -build = boolean(default=True) -compile = boolean(default=True) -debug = boolean(default=False) -quiet = boolean(default=False) -deploy = boolean(default=False) -diff = boolean(default=False) -measure = boolean(default=False) -monitor = boolean(default=False) -render = boolean(default=True) -validate = boolean(default=True) -visualise = boolean(default=True) -stack_trace = boolean(default=False) - -[IP Addressing] -[[v4]] -infra_subnet = string(default = "10.0.0.0")] -infra_prefix = integer(default = 8) -loopback_subnet = string(default = "192.168.0.0") -loopback_prefix = integer(default = 22) -vrf_loopback_subnet = string(default = "172.16.0.0") -vrf_loopback_prefix = integer(default = 24) -[[v6]] -infra_subnet = string(default = "0:0:0:a::") -infra_prefix = integer(default = 64) -loopback_subnet = string(default = "0:0:0:b::") -loopback_prefix = integer(default = 64) -vrf_loopback_subnet = string(default = "0:0:0:c::") -vrf_loopback_prefix = integer(default = 64) - -[Compile Targets] -#TODO: merge Compiler options below to be specific to each target -[[Cisco]] -host = string(default ="internal") -platform = string(default ="VIRL") -[[Netkit]] -host = string(default ="localhost") -platform = string(default ="netkit") -[[__many__]] -host = string() -platform = string() - -[Compiler] -[[Cisco]] -timestamp = boolean(default=True) # if to include timestamp in folder name -to memory = boolean(default=True) # if to compile to memory instead of directory files - -[Deploy Hosts] - [[__many__]] # servername - [[[__many__]]] # platform - host = string() - username = string() - key file = string(default = "") - compile = boolean(default=False) # whether to compile for this host - deploy = boolean(default=False) # whether to deploy to this host - measure = boolean(default=False) # whether to measure from this host - measure commands = force_list(default=list('')) - -#TODO: inherit from base load settings, and then override for format, eg graphml, json etc. -[Graphml] - [[Graph Defaults]] - __many__ = string() - [[Node Defaults]] - device_type = string(default = "router") - asn = integer(default = 1) - platform = string(default = "netkit") - host = string(default = "localhost") - ospf_area = integer(default = 0) - igp = string(default = "ospf") - __many__ = string() - [[Edge Defaults]] - type = string(default = "physical") - ospf_cost = integer(default = 1) - __many__ = string() - -[JSON] - [[Graph Defaults]] - __many__ = string() - [[Node Defaults]] - device_type = string(default = "router") - asn = integer(default = 1) - platform = string(default = "netkit") - host = string(default = "localhost") - ospf_area = integer(default = 0) - igp = string(default = "ospf") - __many__ = string() - [[Edge Defaults]] - type = string(default = "physical") - ospf_cost = integer(default = 1) - __many__ = string() diff --git a/autonetkit/console_script.py b/autonetkit/console_script.py deleted file mode 100644 index 6a33d369..00000000 --- a/autonetkit/console_script.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Console script entry point for AutoNetkit""" - -import os -import sys -import time -import traceback -from datetime import datetime - -import autonetkit.config as config -import autonetkit.log as log -import autonetkit.workflow as workflow -import pkg_resources - -try: - ANK_VERSION = pkg_resources.get_distribution("autonetkit").version -except pkg_resources.DistributionNotFound: - ANK_VERSION = "dev" - -def parse_options(argument_string=None): - """Parse user-provided options""" - import argparse - usage = "autonetkit -f input.graphml" - version = "%(prog)s " + str(ANK_VERSION) - parser = argparse.ArgumentParser(description=usage, version=version) - - input_group = parser.add_mutually_exclusive_group() - input_group.add_argument( - '--file', '-f', default=None, help="Load topology from FILE") - input_group.add_argument('--stdin', action="store_true", default=False, - help="Load topology from STDIN") - - parser.add_argument( - '--monitor', '-m', action="store_true", default=False, - help="Monitor input file for changes") - parser.add_argument('--debug', action="store_true", - default=False, help="Debug mode") - parser.add_argument('--quiet', action="store_true", - default=False, help="Quiet mode (only display warnings and errors)") - parser.add_argument('--diff', action="store_true", default=False, - help="Diff DeviceModel") - parser.add_argument('--no_vis', dest="visualise", - action="store_false", default=True, - help="Visualise output") - parser.add_argument('--compile', action="store_true", - default=False, help="Compile") - parser.add_argument( - '--build', action="store_true", default=False, help="Build") - parser.add_argument( - '--render', action="store_true", default=False, help="Compile") - parser.add_argument( - '--validate', action="store_true", default=False, help="Validate") - parser.add_argument('--deploy', action="store_true", - default=False, help="Deploy") - parser.add_argument('--archive', action="store_true", default=False, - help="Archive ANM, DeviceModel, and IP allocations") - parser.add_argument('--measure', action="store_true", - default=False, help="Measure") - parser.add_argument( - '--webserver', action="store_true", default=False, help="Webserver") - parser.add_argument('--grid', type=int, help="Grid Size (n * n)") - parser.add_argument( - '--target', choices=['netkit', 'cisco'], default=None) - parser.add_argument( - '--vis_uuid', default=None, help="UUID for multi-user visualisation") - if argument_string: - arguments = parser.parse_args(argument_string.split()) - else: - # from command line arguments - arguments = parser.parse_args() - - return arguments - - -def main(options): - settings = config.settings - - if options.vis_uuid: - config.settings['Http Post']['uuid'] = options.vis_uuid - - try: - # test if can import, if not present will fail and not add to template - # path - import autonetkit_cisco - except ImportError: - pass - else: - import autonetkit_cisco.version - version_banner = autonetkit_cisco.version.banner() - log.info("%s" % version_banner) - - log.info("AutoNetkit %s" % ANK_VERSION) - - if options.target == "cisco": - # output target is Cisco - log.info("Setting output target as Cisco") - settings['Graphml']['Node Defaults']['platform'] = "VIRL" - settings['Graphml']['Node Defaults']['host'] = "internal" - settings['Graphml']['Node Defaults']['syntax'] = "ios_xr" - settings['Compiler']['Cisco']['to memory'] = 1 - settings['General']['deploy'] = 1 - settings['Deploy Hosts']['internal'] = {'VIRL': - {'deploy': 1}} - - if options.debug or settings['General']['debug']: - # TODO: fix this - import logging - logger = logging.getLogger("ANK") - logger.setLevel(logging.DEBUG) - - if options.quiet or settings['General']['quiet']: - import logging - logger = logging.getLogger("ANK") - logger.setLevel(logging.WARNING) - - build_options = { - 'compile': options.compile or settings['General']['compile'], - 'render': options.render or settings['General']['render'], - 'validate': options.validate or settings['General']['validate'], - 'build': options.build or settings['General']['build'], - 'deploy': options.deploy or settings['General']['deploy'], - 'measure': options.measure or settings['General']['measure'], - 'monitor': options.monitor or settings['General']['monitor'], - 'diff': options.diff or settings['General']['diff'], - 'archive': options.archive or settings['General']['archive'], - # use and for visualise as no_vis negates - 'visualise': options.visualise and settings['General']['visualise'], - } - - if options.webserver: - log.info("Webserver not yet supported, please run as seperate module") - - if options.file: - with open(options.file, "r") as fh: - input_string = fh.read() - timestamp = os.stat(options.file).st_mtime - elif options.stdin: - input_string = sys.stdin - now = datetime.now() - timestamp = now.strftime("%Y%m%d_%H%M%S_%f") - elif options.grid: - input_string = "" - now = datetime.now() - timestamp = now.strftime("%Y%m%d_%H%M%S_%f") - else: - log.info("No input file specified. Exiting") - return - - try: - workflow.manage_network(input_string, timestamp, - grid=options.grid, **build_options) - except Exception, err: - log.error( - "Error generating network configurations: %s" % err) - log.debug("Error generating network configurations", exc_info=True) - if settings['General']['stack_trace']: - print traceback.print_exc() - sys.exit("Unable to build configurations.") - -# TODO: work out why build_options is being clobbered for monitor mode - build_options['monitor'] = options.monitor or settings['General'][ - 'monitor'] - - if build_options['monitor']: - try: - log.info("Monitoring for updates...") - input_filemonitor = workflow.file_monitor(options.file) - #build_filemonitor = file_monitor("autonetkit/build_network.py") - while True: - time.sleep(1) - rebuild = False - if input_filemonitor.next(): - rebuild = True - - if rebuild: - try: - log.info("Input graph updated, recompiling network") - with open(options.file, "r") as fh: - input_string = fh.read() # read updates - workflow.manage_network(input_string, - timestamp, build_options) - log.info("Monitoring for updates...") - except Exception, e: - log.warning("Unable to build network %s" % e) - traceback.print_exc() - - except KeyboardInterrupt: - log.info("Exiting") - -def console_entry(): - """If come from console entry point""" - args = parse_options() - main(args) - -if __name__ == "__main__": - try: - args = parse_options() - main(args) - except KeyboardInterrupt: - pass diff --git a/autonetkit/deploy/__init__.py b/autonetkit/deploy/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/deploy/netkit.py b/autonetkit/deploy/netkit.py deleted file mode 100644 index e6bef230..00000000 --- a/autonetkit/deploy/netkit.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import re -import time - -import autonetkit.ank_messaging as ank_messaging -import autonetkit.config as config -import autonetkit.log as log - -try: - import Exscript -except ImportError: - log.warning('Deployment requires Exscript: pip install https://github.com/knipknap/exscript/tarball/master' - ) - - -def deploy(host, username, dst_folder, key_filename=None, - parallel_count=5): - tar_file = package(dst_folder) - transfer(host, username, tar_file, key_filename=key_filename) - extract( - host, - username, - tar_file, - dst_folder, - key_filename=key_filename, - parallel_count=parallel_count, - ) - - -def package(src_dir, target='netkit_lab'): - log.info('Packaging %s' % src_dir) - import tarfile - import os - tar_filename = '%s.tar.gz' % target - tar = tarfile.open(os.path.join(tar_filename), 'w:gz') - tar.add(src_dir) - tar.close() - return tar_filename - - -def transfer(host, username, local, remote=None, key_filename=None): - log.debug('Transferring lab to %s' % host) - log.info('Transferring Netkit lab') - if not remote: - remote = local # same filename - import paramiko - - # import logging - # logging.getLogger("paramiko").setLevel(logging.DEBUG) - - ssh = paramiko.SSHClient() - - # ssh.set_log_channel("ANK") - - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - if key_filename: - log.debug('Connecting to %s with %s and key %s' % (host, - username, key_filename)) - ssh.connect(host, username=username, key_filename=key_filename) - else: - log.info('Connecting to %s with %s' % (host, username)) - ssh.connect(host, username=username) - log.info('Opening SSH for SFTP') - ftp = ssh.open_sftp() - log.info('Putting file %s tspoto %s' % (local, remote)) - ftp.put(local, remote) - log.info('Put file %s to %s' % (local, remote)) - ftp.close() - - -def extract( - host, - username, - tar_file, - cd_dir, - timeout=45, - key_filename=None, - verbosity=0, - parallel_count=5, - ): - """Extract and start lab""" - - log.debug('Extracting and starting lab on %s' % host) - log.info('Extracting and starting Netkit lab') - from Exscript import Account - from Exscript.util.start import start - from Exscript.util.match import first_match - from Exscript import PrivateKey - from Exscript.protocols.Exception import InvalidCommandException - - messaging = ank_messaging - - def starting_host(protocol, index, data): - log.info('Starting %s' % data.group(1)) - - def lab_started(protocol, index, data): - log.info('Lab started on %s' % host) - - def make_not_found(protocol, index, data): - log.warning('Make not installed on remote host %s. Please install make and retry.' - % host) - return - - def process_vlist(response): - """Obtain VM to PID listing: required if terminating a numeric VM""" - - # TODO: could process using textfsm template - - vm_to_pid = {} - for line in response.splitlines(): - match = re.match(r'^\w+\s+(\w+)\s+(\d+)', line) - if match: - vm = match.group(1) - pid = match.group(2) - vm_to_pid[vm] = pid - - return vm_to_pid - - def start_lab(thread, host, conn): - conn.set_timeout(timeout) - conn.add_monitor(r'Starting "(\w+)"', starting_host) - conn.add_monitor(r'The lab has been started', lab_started) - lab_vlist = [] - - # conn.add_monitor(r'Virtual machine "((\S*_*)+)" is already running. Please', already_running_b) - - conn.add_monitor(r'make: not found', make_not_found) - - # conn.data_received_event.connect(data_received) - - conn.execute('cd %s' % cd_dir) - conn.execute('lcrash -k') - conn.execute('lclean') - conn.execute('cd') # back to home directory tar file copied to - conn.execute('tar -xzf %s' % tar_file) - conn.execute('cd %s' % cd_dir) - - conn.execute('linfo') - linfo_response = str(conn.response) - vm_list = [] - for line in linfo_response.splitlines(): - if 'The lab is made up of' in line: - open_bracket = line.index('(') - close_bracket = line.index(')') - vm_list = line[open_bracket + 1:close_bracket] - vm_list = vm_list.split() - log.info('The lab contains VMs %s' % ', '.join(vm_list)) - - # now check if any vms are still running - - conn.execute('vlist') - response = str(conn.response) - lab_vlist = process_vlist(response) - - for virtual_machine in lab_vlist: - if virtual_machine in vm_list: - if virtual_machine.isdigit: - - # convert to PID if numeric, as vcrash can't crash numeric ids (treats as PID) - - crash_id = lab_vlist.get(virtual_machine) - else: - crash_id = virtual_machine # use name - - if crash_id: - - # crash_id may not be set, if machine not present in initial vlist, if so then ignore - - log.info('Stopping running VM %s' % virtual_machine) - conn.execute('vcrash %s' % crash_id) - - conn.execute('vlist') - conn.execute('lclean') - start_command = 'lstart -p%s -o --con0=none' % parallel_count - lab_is_started = False - while lab_is_started == False: - try: - log.info('Starting lab') - conn.execute(start_command) - except InvalidCommandException, error: - error_string = str(error) - if 'already running' in error_string: - - conn.execute('vlist') - response = str(conn.response) - lab_vlist = process_vlist(response) - - running_vms = [] - for line in error_string.splitlines(): - if 'already running' in line: - running_vm = line.split('"')[1] - running_vms.append(running_vm) - - for virtual_machine in running_vms: - if virtual_machine.isdigit: - - # convert to PID if numeric, as vcrash can't crash numeric ids (treats as PID) - - crash_id = lab_vlist.get(virtual_machine) - else: - crash_id = virtual_machine # use name - - if crash_id: - - # crash_id may not be set, if machine not present in initial vlist, if so then ignore - - log.info('Stopping running VM %s' - % virtual_machine) - conn.execute('vcrash %s' % crash_id) - - time.sleep(1) - else: - - # conn.execute(start_command) - - lab_is_started = True - first_match(conn, r'^The lab has been started') - log.info('Lab started') # TODO: make this captured - need to debug capturing - conn.send('exit') - - if key_filename: - key = PrivateKey.from_file(key_filename) - log.debug('Connecting to %s with username %s and key %s' - % (host, username, key_filename)) - accounts = [Account(username, key=key)] - else: - log.debug('Connecting to %s with username %s' % (host, - username)) - accounts = [Account(username)] - - hosts = ['ssh://%s' % host] - verbosity = -1 - start(accounts, hosts, start_lab, verbose=verbosity) diff --git a/autonetkit/design/__init__.py b/autonetkit/design/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/design/bgp.py b/autonetkit/design/bgp.py deleted file mode 100644 index eb5586dc..00000000 --- a/autonetkit/design/bgp.py +++ /dev/null @@ -1,262 +0,0 @@ -import autonetkit.ank as ank_utils -import autonetkit.log as log -from autonetkit.ank_utils import call_log - -#@call_log - - -def build_ibgp_v4(anm): - # TODO: remove the bgp layer and have just ibgp and ebgp - # TODO: build from design rules, currently just builds from ibgp links in bgp layer - # TODO: base on generic ibgp graph, rather than bgp graph - g_bgp = anm['bgp'] - g_phy = anm['phy'] - g_ibgpv4 = anm.add_overlay("ibgp_v4", directed=True) - ipv4_nodes = set(g_phy.routers("use_ipv4")) - if len(ipv4_nodes) == 0: - return - g_ibgpv4.add_nodes_from((n for n in g_bgp if n in ipv4_nodes), - retain=["ibgp_role", "hrr_cluster", "rr_cluster"]) - g_ibgpv4.add_edges_from(g_bgp.edges(type="ibgp"), retain="direction") - -#@call_log - - -def build_ibgp_v6(anm): - # TODO: remove the bgp layer and have just ibgp and ebgp - # TODO: build from design rules, currently just builds from ibgp links in bgp layer - # TODO: base on generic ibgp graph, rather than bgp graph - g_bgp = anm['bgp'] - g_phy = anm['phy'] - g_ibgpv6 = anm.add_overlay("ibgp_v6", directed=True) - ipv6_nodes = set(g_phy.routers("use_ipv6")) - if len(ipv6_nodes) == 0: - return - g_ibgpv6.add_nodes_from((n for n in g_bgp if n in ipv6_nodes), - retain=["ibgp_role", "hrr_cluster", "rr_cluster"]) - g_ibgpv6.add_edges_from(g_bgp.edges(type="ibgp"), retain="direction") - -#@call_log - - -def build_ebgp_v4(anm): - # TODO: remove the bgp layer and have just ibgp and ebgp - # TODO: build from design rules, currently just builds from ibgp links in - # bgp layer - g_ebgp = anm['ebgp'] - g_phy = anm['phy'] - g_ebgpv4 = anm.add_overlay("ebgp_v4", directed=True) - ipv4_nodes = set(g_phy.routers("use_ipv4")) - if len(ipv4_nodes) == 0: - return - g_ebgpv4.add_nodes_from(n for n in g_ebgp if n in ipv4_nodes) - g_ebgpv4.add_edges_from(g_ebgp.edges(), retain="direction") - -#@call_log - - -def build_ebgp_v6(anm): - # TODO: remove the bgp layer and have just ibgp and ebgp - # TODO: build from design rules, currently just builds from ibgp links in - # bgp layer - g_ebgp = anm['ebgp'] - g_phy = anm['phy'] - g_ebgpv6 = anm.add_overlay("ebgp_v6", directed=True) - ipv6_nodes = set(g_phy.routers("use_ipv6")) - if len(ipv6_nodes) == 0: - return - g_ebgpv6.add_nodes_from(n for n in g_ebgp if n in ipv6_nodes) - g_ebgpv6.add_edges_from(g_ebgp.edges(), retain="direction") - -#@call_log - - -def build_ebgp(anm): - g_l3 = anm['layer3'] - - g_ebgp = anm.add_overlay("ebgp", directed=True) - - g_ebgp.add_nodes_from(g_l3.routers()) - ank_utils.copy_int_attr_from(g_l3, g_ebgp, "multipoint") - - ebgp_edges = [e for e in g_l3.edges() if e.src.asn != e.dst.asn] - g_ebgp.add_edges_from(ebgp_edges, bidirectional=True, type='ebgp') - -#@call_log - - -def build_ibgp(anm): - g_in = anm['input'] - g_bgp = anm['bgp'] - - # TODO: build direct to ibgp graph - can construct combined bgp for vis - #TODO: normalise input property - - ank_utils.copy_attr_from(g_in, g_bgp, "ibgp_role") - ank_utils.copy_attr_from( - g_in, g_bgp, "ibgp_l2_cluster", "hrr_cluster", default=None) - ank_utils.copy_attr_from( - g_in, g_bgp, "ibgp_l3_cluster", "rr_cluster", default=None) - - # TODO: add more detailed logging - - for n in g_bgp: - # Tag with label to make logic clearer - if n.ibgp_role is None: - n.ibgp_role = "Peer" - - # TODO: if top-level, then don't mark as RRC - - ibgp_nodes = [n for n in g_bgp if not n.ibgp_role is "Disabled"] - - # Notify user of non-ibgp nodes - non_ibgp_nodes = [n for n in g_bgp if n.ibgp_role is "Disabled"] - if 0 < len(non_ibgp_nodes) < 10: - log.info("Skipping iBGP for iBGP disabled nodes: %s", non_ibgp_nodes) - elif len(non_ibgp_nodes) >= 10: - log.info("Skipping iBGP for more than 10 iBGP disabled nodes:" - "refer to visualization for resulting topology.") - - # warn for any nodes that have RR set but no rr_cluster, or HRR set and no - # hrr_cluster - rr_mismatch = [ - n for n in ibgp_nodes if n.ibgp_role == "RR" and n.rr_cluster is None] - if len(rr_mismatch): - log.warning("Some routers are set as RR but have no rr_cluster: %s. Please specify an rr_cluster for peering." - % ", ".join(str(n) for n in rr_mismatch)) - - hrr_mismatch = [ - n for n in ibgp_nodes if n.ibgp_role == "HRR" and n.hrr_cluster is None] - if len(hrr_mismatch): - log.warning("Some routers are set as HRR but have no hrr_cluster: %s. Please specify an hrr_cluster for peering." - % ", ".join(str(n) for n in hrr_mismatch)) - - for _, asn_devices in ank_utils.groupby("asn", ibgp_nodes): - asn_devices = list(asn_devices) - - # iBGP peer peers with - peers = [n for n in asn_devices if n.ibgp_role == "Peer"] - rrs = [n for n in asn_devices if n.ibgp_role == "RR"] - hrrs = [n for n in asn_devices if n.ibgp_role == "HRR"] - rrcs = [n for n in asn_devices if n.ibgp_role == "RRC"] - - over_links = [] - up_links = [] - down_links = [] - - # 0. RRCs can only belong to either an rr_cluster or a hrr_cluster - invalid_rrcs = [r for r in rrcs if r.rr_cluster is not None - and r.hrr_cluster is not None] - if len(invalid_rrcs): - message = ", ".join(str(r) for r in invalid_rrcs) - log.warning("RRCs can only have either a rr_cluster or hrr_cluster set. " - "The following have both set, and only the rr_cluster will be used: %s", message) - - # TODO: do we also want to warn for RRCs with no cluster set? Do we also exclude these? - # TODO: do we also want to warn for HRRs and RRs with no cluster set? - # Do we also exclude these? - - # 1. Peers: - # 1a. Peers connect over to peers - over_links += [(s, t) for s in peers for t in peers] - # 1b. Peers connect over to RRs - over_links += [(s, t) for s in peers for t in rrs] - - # 2. RRs: - # 2a. RRs connect over to Peers - over_links += [(s, t) for s in rrs for t in peers] - # 2b. RRs connect over to RRs - over_links += [(s, t) for s in rrs for t in rrs] - # 2c. RRs connect down to RRCs in same rr_cluster - down_links += [(s, t) for s in rrs for t in rrcs - if s.rr_cluster == t.rr_cluster != None] - # 2d. RRs connect down to HRRs in the same rr_cluster - down_links += [(s, t) for s in rrs for t in hrrs - if s.rr_cluster == t.rr_cluster != None] - - # 3. HRRs - # 3a. HRRs connect up to RRs in the same rr_cluster - up_links += [(s, t) for s in hrrs for t in rrs - if s.rr_cluster == t.rr_cluster != None] - # 3b. HRRs connect down to RRCs in same hrr_cluster (providing RRC has - # no rr_cluster set) - down_links += [(s, t) for s in hrrs for t in rrcs - if s.hrr_cluster == t.hrr_cluster != None - and t.rr_cluster is None] - - # 4. RRCs - # 4a. RRCs connect up to RRs in the same rr_cluster (regardless if RRC - # has hrr_cluster set) - up_links += [(s, t) for s in rrcs for t in rrs - if s.rr_cluster == t.rr_cluster != None] - # 3b. RRCs connect up to HRRs in same hrr_cluster (providing RRC has no - # rr_cluster set) - up_links += [(s, t) for s in rrcs for t in hrrs - if s.hrr_cluster == t.hrr_cluster != None - and s.rr_cluster is None] - - # Remove self-links - over_links = [(s, t) for s, t in over_links if s != t] - up_links = [(s, t) for s, t in up_links if s != t] - down_links = [(s, t) for s, t in down_links if s != t] - - g_bgp.add_edges_from(over_links, type='ibgp', direction='over') - g_bgp.add_edges_from(up_links, type='ibgp', direction='up') - g_bgp.add_edges_from(down_links, type='ibgp', direction='down') - -#@call_log - - -def build_bgp(anm): - """Build iBGP end eBGP overlays""" - # eBGP - g_in = anm['input'] - g_l3 = anm['layer3'] - - if not anm['phy'].data.enable_routing: - log.info("Routing disabled, not configuring BGP") - return - - build_ebgp(anm) - build_ebgp_v4(anm) - build_ebgp_v6(anm) - - """TODO: remove from here once compiler updated""" - g_bgp = anm.add_overlay("bgp", directed=True) - g_bgp.add_nodes_from(g_l3.routers()) - edges_to_add = [e for e in g_l3.edges() - if e.src in g_bgp and e.dst in g_bgp] - g_bgp.add_edges_from(edges_to_add, bidirectional=True) - ank_utils.copy_int_attr_from(g_l3, g_bgp, "multipoint") - - # remove ibgp links - - """TODO: remove up to here once compiler updated""" - ank_utils.copy_attr_from( - g_in, g_bgp, "custom_config_bgp", dst_attr="custom_config") - - # log.info("Building eBGP") - ebgp_nodes = [d for d in g_bgp if any( - edge.type == 'ebgp' for edge in d.edges())] - g_bgp.update(ebgp_nodes, ebgp=True) - - for ebgp_edge in g_bgp.edges(type="ebgp"): - for interface in ebgp_edge.interfaces(): - interface.ebgp = True - - for edge in g_bgp.edges(type='ibgp'): - # TODO: need interface querying/selection. rather than hard-coded ids - # TODO: create a new port (once API allows) rarher than binding to - # loopback zero - edge.bind_interface(edge.src, 0) - - # TODO: need to initialise interface zero to be a loopback rather than physical type - # TODO: wat is this for? - for node in g_bgp: - for interface in node.interfaces(): - interface.multipoint = any(e.multipoint for e in interface.edges()) - - # log.info("Building iBGP") - build_ibgp(anm) - build_ibgp_v4(anm) - build_ibgp_v6(anm) diff --git a/autonetkit/design/igp.py b/autonetkit/design/igp.py deleted file mode 100644 index 27b8059b..00000000 --- a/autonetkit/design/igp.py +++ /dev/null @@ -1,324 +0,0 @@ -import autonetkit.log as log -import autonetkit.ank as ank_utils - -from autonetkit.ank_utils import call_log - -# TODO: extract the repeated code and use the layer2 and layer3 graphs - - -def build_igp(anm): - build_ospf(anm) - build_eigrp(anm) - build_isis(anm) - build_rip(anm) - - # Build a protocol summary graph - g_igp = anm.add_overlay("igp") - igp_protocols = ["ospf", "eigrp", "isis", "rip"] - for protocol in igp_protocols: - g_protocol = anm[protocol] - g_igp.add_nodes_from(g_protocol, igp=protocol) - g_igp.add_edges_from(g_protocol.edges(), igp=protocol) - -#@call_log - - -def build_ospf(anm): - """ - Build OSPF graph. - - Allowable area combinations: - 0 -> 0 - 0 -> x (x!= 0) - x -> 0 (x!= 0) - x -> x (x != 0) - - Not-allowed: - x -> x (x != y != 0) - - """ - import netaddr - g_in = anm['input'] - g_l3 = anm['layer3'] - g_phy = anm['phy'] - # add regardless, so allows quick check of node in anm['ospf'] in compilers - g_ospf = anm.add_overlay("ospf") - if not anm['phy'].data.enable_routing: - g_ospf.log.info("Routing disabled, not configuring OSPF") - return - - if not any(n.igp == "ospf" for n in g_phy): - g_ospf.log.debug("No OSPF nodes") - return - - ospf_nodes = [n for n in g_l3 if n['phy'].igp == "ospf"] - g_ospf.add_nodes_from(ospf_nodes) - g_ospf.add_edges_from(g_l3.edges(), warn=False) - ank_utils.copy_int_attr_from(g_l3, g_ospf, "multipoint") - - # TODO: work out why this doesnt work - #ank_utils.copy_int_attr_from(g_in, g_ospf, "ospf_cost", dst_attr="cost", type=int, default = 1) - for node in g_ospf: - for interface in node.physical_interfaces(): - interface.cost = 1 - - ank_utils.copy_attr_from(g_in, g_ospf, "ospf_area", dst_attr="area") - #ank_utils.copy_edge_attr_from(g_in, g_ospf, "ospf_cost", dst_attr="cost", type=int, default = 1) - ank_utils.copy_attr_from( - g_in, g_ospf, "custom_config_ospf", dst_attr="custom_config") - - g_ospf.remove_edges_from([link for link in g_ospf.edges( - ) if link.src.asn != link.dst.asn]) # remove inter-AS links - - area_zero_ip = netaddr.IPAddress("0.0.0.0") - area_zero_int = 0 - area_zero_ids = {area_zero_ip, area_zero_int} - default_area = area_zero_int - if any(router.area == "0.0.0.0" for router in g_ospf): - # string comparison as hasn't yet been cast to IPAddress - default_area = area_zero_ip - - for router in g_ospf: - if not router.area or router.area == "None": - router.area = default_area - # check if 0.0.0.0 used anywhere, if so then use 0.0.0.0 as format - else: - try: - router.area = int(router.area) - except ValueError: - try: - router.area = netaddr.IPAddress(router.area) - except netaddr.core.AddrFormatError: - router.log.warning("Invalid OSPF area %s. Using default" - " of %s" % (router.area, default_area)) - router.area = default_area - - # TODO: use interfaces throughout, rather than edges - for router in g_ospf: - # and set area on interface - for edge in router.edges(): - if edge.area: - continue # allocated (from other "direction", as undirected) - if router.area == edge.dst.area: - edge.area = router.area # intra-area - continue - - if router.area in area_zero_ids or edge.dst.area in area_zero_ids: - # backbone to other area - if router.area in area_zero_ids: - # router in backbone, use other area - edge.area = edge.dst.area - else: - # router not in backbone, use its area - edge.area = router.area - - for router in g_ospf: - areas = {edge.area for edge in router.edges()} - router.areas = list(areas) # edges router participates in - - if len(areas) in area_zero_ids: - router.type = "backbone" # no ospf edges (eg single node in AS) - elif len(areas) == 1: - # single area: either backbone (all 0) or internal (all nonzero) - if len(areas & area_zero_ids): - # intersection has at least one element -> router has area zero - router.type = "backbone" - else: - router.type = "internal" - - else: - # multiple areas - if len(areas & area_zero_ids): - # intersection has at least one element -> router has area zero - router.type = "backbone ABR" - elif router.area in area_zero_ids: - router.log.debug( - "Router belongs to area %s but has no area zero interfaces", - router.area) - router.type = "backbone ABR" - else: - router.log.warning( - "spans multiple areas but is not a member of area 0") - router.type = "INVALID" - - if (any(area_zero_int in router.areas for router in g_ospf) and - any(area_zero_ip in router.areas for router in g_ospf)): - router.log.warning("Using both area 0 and area 0.0.0.0") - - for link in g_ospf.edges(): - if not link.cost: - link.cost = 1 - - # map areas and costs onto interfaces - # TODO: later map them directly rather than with edges - part of - # the transition - for edge in g_ospf.edges(): - for interface in edge.interfaces(): - interface.cost = edge.cost - interface.area = edge.area - interface.multipoint = edge.multipoint - - for router in g_ospf: - router.loopback_zero.area = router.area - router.loopback_zero.cost = 0 - router.process_id = router.asn - -#@call_log - - -def ip_to_net_ent_title_ios(ip_addr): - """ Converts an IP address into an OSI Network Entity Title - suitable for use in IS-IS on IOS. - - >>> from netaddr import IPAddress - >>> ip_to_net_ent_title_ios(IPAddress("192.168.19.1")) - '49.1921.6801.9001.00' - """ - try: - ip_words = ip_addr.words - except AttributeError: - import netaddr # try to cast to IP Address - ip_addr = netaddr.IPAddress(ip_addr) - ip_words = ip_addr.words - - log.debug("Converting IP to OSI ENT format") - area_id = "49" - ip_octets = "".join("%03d" % int( - octet) for octet in ip_words) # single string, padded if needed - return ".".join([area_id, ip_octets[0:4], ip_octets[4:8], ip_octets[8:12], - "00"]) - -#@call_log - - -def build_eigrp(anm): - """Build eigrp overlay""" - g_in = anm['input'] - # add regardless, so allows quick check of node in anm['isis'] in compilers - g_l3 = anm['layer3'] - g_eigrp = anm.add_overlay("eigrp") - g_phy = anm['phy'] - - if not anm['phy'].data.enable_routing: - g_eigrp.log.info("Routing disabled, not configuring EIGRP") - return - - if not any(n.igp == "eigrp" for n in g_phy): - log.debug("No EIGRP nodes") - return - eigrp_nodes = [n for n in g_l3 if n['phy'].igp == "eigrp"] - g_eigrp.add_nodes_from(eigrp_nodes) - g_eigrp.add_edges_from(g_l3.edges(), warn=False) - ank_utils.copy_int_attr_from(g_l3, g_eigrp, "multipoint") - - ank_utils.copy_attr_from( - g_in, g_eigrp, "custom_config_eigrp", dst_attr="custom_config") - -# Merge and explode switches - ank_utils.aggregate_nodes(g_eigrp, g_eigrp.switches()) - exploded_edges = ank_utils.explode_nodes(g_eigrp, - g_eigrp.switches()) - for edge in exploded_edges: - edge.multipoint = True - - g_eigrp.remove_edges_from( - [link for link in g_eigrp.edges() if link.src.asn != link.dst.asn]) - - for node in g_eigrp: - node.process_id = node.asn - - for link in g_eigrp.edges(): - link.metric = 1 # default - - for edge in g_eigrp.edges(): - for interface in edge.interfaces(): - interface.metric = edge.metric - interface.multipoint = edge.multipoint - -#@call_log - - -def build_network_entity_title(anm): - g_isis = anm['isis'] - g_ipv4 = anm['ipv4'] - for node in g_isis.routers(): - ip_node = g_ipv4.node(node) - node.net = ip_to_net_ent_title_ios(ip_node.loopback) - - -def build_rip(anm): - """Build rip overlay""" - g_in = anm['input'] - g_l3 = anm['layer3'] - g_rip = anm.add_overlay("rip") - g_phy = anm['phy'] - - if not anm['phy'].data.enable_routing: - g_rip.log.info("Routing disabled, not configuring rip") - return - - if not any(n.igp == "rip" for n in g_phy): - log.debug("No rip nodes") - return - rip_nodes = [n for n in g_l3 if n['phy'].igp == "rip"] - g_rip.add_nodes_from(rip_nodes) - g_rip.add_edges_from(g_l3.edges(), warn=False) - ank_utils.copy_int_attr_from(g_l3, g_rip, "multipoint") - - ank_utils.copy_attr_from( - g_in, g_rip, "custom_config_rip", dst_attr="custom_config") - - g_rip.remove_edges_from( - [link for link in g_rip.edges() if link.src.asn != link.dst.asn]) - - for node in g_rip: - node.process_id = node.asn - - for link in g_rip.edges(): - link.metric = 1 # default - - for edge in g_rip.edges(): - for interface in edge.interfaces(): - interface.metric = edge.metric - interface.multipoint = edge.multipoint - - -def build_isis(anm): - """Build isis overlay""" - g_in = anm['input'] - # add regardless, so allows quick check of node in anm['isis'] in compilers - g_l3 = anm['layer3'] - g_phy = anm['phy'] - g_isis = anm.add_overlay("isis") - - if not anm['phy'].data.enable_routing: - g_isis.log.info("Routing disabled, not configuring ISIS") - return - - if not any(n.igp == "isis" for n in g_phy): - g_isis.log.debug("No ISIS nodes") - return - - isis_nodes = [n for n in g_l3 if n['phy'].igp == "isis"] - g_isis.add_nodes_from(isis_nodes) - g_isis.add_edges_from(g_l3.edges(), warn=False) - ank_utils.copy_int_attr_from(g_l3, g_isis, "multipoint") - - ank_utils.copy_attr_from( - g_in, g_isis, "custom_config_isis", dst_attr="custom_config") - - g_isis.remove_edges_from( - [link for link in g_isis.edges() if link.src.asn != link.dst.asn]) - - build_network_entity_title(anm) - - for node in g_isis.routers(): - node.process_id = node.asn - - for link in g_isis.edges(): - link.metric = 1 # default - - for edge in g_isis.edges(): - for interface in edge.interfaces(): - interface.metric = edge.metric - interface.multipoint = edge.multipoint diff --git a/autonetkit/design/ip.py b/autonetkit/design/ip.py deleted file mode 100644 index 433cf8fc..00000000 --- a/autonetkit/design/ip.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import autonetkit.ank as ank_utils -import autonetkit.config -import autonetkit.log as log - -SETTINGS = autonetkit.config.settings - - -# TODO: refactor to go in chronological workflow order - - - - -#@call_log -def build_ip(anm): - g_ip = anm.add_overlay('ip') - g_l2 = anm['layer2'] - g_phy = anm['phy'] - # Retain arbitrary ASN allocation for IP addressing - g_ip.add_nodes_from(g_l2, retain=["asn", "broadcast_domain"]) - g_ip.add_edges_from(g_l2.edges()) - - #TODO: - for bc in g_ip.nodes("broadcast_domain"): - bc.allocate = True - - for bc in g_ip.nodes("broadcast_domain"): - if bc.asn is None: - # arbitrary choice - asn = ank_utils.neigh_most_frequent( g_l2, bc, 'asn', g_phy) - bc.asn = asn - - for neigh in bc.neighbors(): - if (neigh.device_type == "external_connector" - and neigh.device_subtype in ("FLAT", "SNAT")): - bc.allocate = False - - for neigh_int in bc.neighbor_interfaces(): - neigh_int.allocate = False - - # Encapsulated if any neighbor interface has - for edge in bc.edges(): - if edge.dst_int['phy'].l2_encapsulated: - log.debug("Removing IP allocation for broadcast_domain %s " - "as neighbor %s is L2 encapsulated", bc, edge.dst) - - #g_ip.remove_node(bc) - bc.allocate = False - - # and mark on connected interfaces - for neigh_int in bc.neighbor_interfaces(): - neigh_int.allocate = False - - break - - # copy over skipped loopbacks - #TODO: check if loopbck copy attr - for node in g_ip.l3devices(): - for interface in node.loopback_interfaces(): - if interface['phy'].allocate is not None: - interface['ip'].allocate = interface['phy'].allocate - - -def build_ipv4(anm, infrastructure=True): - import autonetkit.design.ip_addressing.ipv4 - autonetkit.design.ip_addressing.ipv4.build_ipv4( - anm, infrastructure=infrastructure) - -def build_ipv6(anm): - #TODO: check why ipv6 doesn't take infrastructure - import autonetkit.design.ip_addressing.ipv6 - autonetkit.design.ip_addressing.ipv6.build_ipv6(anm) -#@call_log diff --git a/autonetkit/design/ip_addressing/__init__.py b/autonetkit/design/ip_addressing/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/design/ip_addressing/ipv4.py b/autonetkit/design/ip_addressing/ipv4.py deleted file mode 100644 index b914d485..00000000 --- a/autonetkit/design/ip_addressing/ipv4.py +++ /dev/null @@ -1,335 +0,0 @@ -import autonetkit.ank as ank_utils -import autonetkit.config -import autonetkit.log as log -from netaddr import IPAddress -from autonetkit.ank import sn_preflen_to_network - -SETTINGS = autonetkit.config.settings - - -# TODO: add unit tests for each function here and in ipv6 - -def extract_ipv4_blocks(anm): - - # TODO: set all these blocks globally in config file, rather than repeated - # in load, build_network, compile, etc - - from netaddr import IPNetwork - g_in = anm['input'] - ipv4_defaults = SETTINGS["IP Addressing"]["v4"] - - # TODO: wrap these in a common function - - try: - infra_subnet = g_in.data.ipv4_infra_subnet - infra_prefix = g_in.data.ipv4_infra_prefix - infra_block = sn_preflen_to_network(infra_subnet, infra_prefix) - except Exception, e: - infra_block = IPNetwork( - '%s/%s' % (ipv4_defaults["infra_subnet"], ipv4_defaults["infra_prefix"])) - if infra_subnet is None or infra_prefix is None: - log.debug('Using default IPv4 infra_subnet %s' % infra_block) - else: - log.warning('Unable to obtain IPv4 infra_subnet from input graph: %s, using default %s' % ( - e, infra_block)) - - try: - loopback_subnet = g_in.data.ipv4_loopback_subnet - loopback_prefix = g_in.data.ipv4_loopback_prefix - loopback_block = sn_preflen_to_network(loopback_subnet, - loopback_prefix) - except Exception, e: - loopback_block = IPNetwork( - '%s/%s' % (ipv4_defaults["loopback_subnet"], ipv4_defaults["loopback_prefix"])) - if loopback_subnet is None or loopback_prefix is None: - log.debug('Using default IPv4 loopback_subnet %s' % loopback_block) - else: - log.warning('Unable to obtain IPv4 loopback_subnet from input graph: %s, using default %s' % ( - e, loopback_block)) - - try: - vrf_loopback_subnet = g_in.data.ipv4_vrf_loopback_subnet - vrf_loopback_prefix = g_in.data.ipv4_vrf_loopback_prefix - vrf_loopback_block = sn_preflen_to_network(vrf_loopback_subnet, - vrf_loopback_prefix) - except Exception, e: - vrf_loopback_block = IPNetwork( - '%s/%s' % (ipv4_defaults["vrf_loopback_subnet"], ipv4_defaults["vrf_loopback_prefix"])) - if vrf_loopback_subnet is None or vrf_loopback_prefix is None: - log.debug('Using default IPv4 vrf_loopback_subnet %s' % - vrf_loopback_block) - else: - log.warning('Unable to obtain IPv4 vrf_loopback_subnet from input graph: %s, using default %s' % ( - e, vrf_loopback_block)) - - return (infra_block, loopback_block, vrf_loopback_block) - - -def manual_ipv4_infrastructure_allocation(anm): - """Applies manual IPv4 allocation""" - - import netaddr - g_ipv4 = anm['ipv4'] - g_in = anm['input'] - log.info('Using specified IPv4 infrastructure allocation') - - for node in g_ipv4.l3devices(): - for interface in node.physical_interfaces(): - if not interface['input'].is_bound: - continue # unbound interface - if not interface['ipv4'].is_bound: - continue - if interface['ip'].allocate is False: - # TODO: copy interface allocate attribute across - continue - - ip_address = netaddr.IPAddress(interface['input' - ].ipv4_address) - prefixlen = interface['input'].ipv4_prefixlen - interface.ip_address = ip_address - interface.prefixlen = prefixlen - cidr_string = '%s/%s' % (ip_address, prefixlen) - interface.subnet = netaddr.IPNetwork(cidr_string) - - broadcast_domains = [d for d in g_ipv4 if d.broadcast_domain] - - # TODO: allow this to work with specified ip_address/subnet as well as - # ip_address/prefixlen - - global_infra_block = None - try: - # Note this is only pickling up if explictly set in g_in - infra_subnet = g_in.data.ipv4_infra_subnet - infra_prefix = g_in.data.ipv4_infra_prefix - global_infra_block = sn_preflen_to_network(infra_subnet, infra_prefix) - except Exception, e: - log.info("Unable to parse specified ipv4 infra subnets %s/%s") - - mismatched_interfaces = [] - from netaddr import IPNetwork - for coll_dom in broadcast_domains: - if coll_dom.allocate is False: - continue - - # TODO: use neighbor_interfaces() - connected_interfaces = [edge.dst_int for edge in - coll_dom.edges()] - - connected_interfaces = [i for i in connected_interfaces - if i.node.is_l3device()] - - cd_subnets = [IPNetwork('%s/%s' % (i.subnet.network, - i.prefixlen)) for i in connected_interfaces - if i['ip'].allocate is not False] - - # mismatched_interfaces += [i for i in connected_interfaces - # if i. - if global_infra_block is not None: - mismatched_interfaces += [i for i in connected_interfaces - if i.ip_address not in global_infra_block] - - if len(cd_subnets) == 0: - log.warning( - "Collision domain %s is not connected to any nodes" % coll_dom) - continue - - try: - assert len(set(cd_subnets)) == 1 - except AssertionError: - mismatch_subnets = '; '.join('%s: %s/%s' % (i, - i.subnet.network, i.prefixlen) for i in - connected_interfaces) - log.warning('Non matching subnets from collision domain %s: %s' - % (coll_dom, mismatch_subnets)) - else: - coll_dom.subnet = cd_subnets[0] # take first entry - - # apply to remote interfaces - - for edge in coll_dom.edges(): - edge.dst_int.subnet = coll_dom.subnet - - # also need to form aggregated IP blocks (used for e.g. routing prefix - # advertisement) - # import autonetkit - # autonetkit.update_vis(anm) - if len(mismatched_interfaces): - log.warning("IPv4 Infrastructure IPs %s are not in global " - "loopback allocation block %s" - % (sorted(mismatched_interfaces), global_infra_block)) - - infra_blocks = {} - for (asn, devices) in g_ipv4.groupby('asn').items(): - broadcast_domains = [d for d in devices if d.broadcast_domain] - subnets = [cd.subnet for cd in broadcast_domains - if cd.subnet is not None] # only if subnet is set - infra_blocks[asn] = netaddr.cidr_merge(subnets) - - # formatted = {key: [str(v) for v in val] for key, val in infra_blocks.items()} - # log.info("Found infrastructure IP blocks %s", formatted) - g_ipv4.data.infra_blocks = infra_blocks - - -#@call_log -def manual_ipv4_loopback_allocation(anm): - """Applies manual IPv4 allocation""" - - import netaddr - g_ipv4 = anm['ipv4'] - g_in = anm['input'] - - for l3_device in g_ipv4.l3devices(): - try: - l3_device.loopback = IPAddress(l3_device['input'].loopback_v4) - except netaddr.AddrFormatError: - log.debug("Unable to parse IP address %s on %s", - l3_device['input'].loopback_v6, l3_device) - - try: - loopback_subnet = g_in.data.ipv4_loopback_subnet - loopback_prefix = g_in.data.ipv4_loopback_prefix - loopback_block = sn_preflen_to_network(loopback_subnet, - loopback_prefix) - except Exception, e: - log.info("Unable to parse specified ipv4 loopback subnets %s/%s") - else: - mismatched_nodes = [n for n in g_ipv4.l3devices() - if n.loopback and n.loopback not in loopback_block] - if len(mismatched_nodes): - log.warning("IPv4 loopbacks set on nodes %s are not in global " - "loopback allocation block %s" - % (sorted(mismatched_nodes), loopback_block)) - - # mismatch = [n for n in g_ipv4.l3devices() if n.loopback not in - - # also need to form aggregated IP blocks (used for e.g. routing prefix - # advertisement) - - loopback_blocks = {} - for (asn, devices) in g_ipv4.groupby('asn').items(): - routers = [d for d in devices if d.is_router()] - loopbacks = [r.loopback for r in routers] - loopback_blocks[asn] = netaddr.cidr_merge(loopbacks) - - g_ipv4.data.loopback_blocks = loopback_blocks - # formatted = {key: [str(v) for v in val] for key, val in loopback_blocks.items()} - #log.info("Found loopback IP blocks %s", formatted) - - -#@call_log -def build_ipv4(anm, infrastructure=True): - """Builds IPv4 graph""" - - import autonetkit.plugins.ipv4 as ipv4 - import netaddr - g_ipv4 = anm.add_overlay('ipv4') - g_ip = anm['ip'] - g_in = anm['input'] - # retain if collision domain or not - g_ipv4.add_nodes_from(g_ip, retain=['label', 'allocate', - 'broadcast_domain']) - - # Copy ASN attribute chosen for collision domains (used in alloc algorithm) - - ank_utils.copy_attr_from( - g_ip, g_ipv4, 'asn', nbunch=g_ipv4.nodes('broadcast_domain')) - # work around until fall-through implemented - vswitches = [n for n in g_ip.nodes() - if n['layer2'].device_type == "switch" - and n['layer2'].device_subtype == "virtual"] - ank_utils.copy_attr_from(g_ip, g_ipv4, 'asn', nbunch=vswitches) - g_ipv4.add_edges_from(g_ip.edges()) - - # check if ip ranges have been specified on g_in - - (infra_block, loopback_block, vrf_loopback_block) = \ - extract_ipv4_blocks(anm) - -# TODO: don't present if using manual allocation - if any(i for n in g_ip.nodes() for i in - n.loopback_interfaces() if not i.is_loopback_zero): - block_message = "IPv4 Secondary Loopbacks: %s" % vrf_loopback_block - log.info(block_message) - - # See if IP addresses specified on each interface - - # do we need this still? in ANM? - differnt because input graph.... but - # can map back to self overlay first then phy??? - l3_devices = [d for d in g_in if d.device_type in ('router', 'server')] - - # TODO: need to account for devices whose interfaces are in only e.g. vpns - - manual_alloc_devices = set() - for device in l3_devices: - physical_interfaces = list(device.physical_interfaces()) - allocated = list( - interface.ipv4_address for interface in physical_interfaces - if interface.is_bound and interface['ipv4'].allocate is not False - and interface['ipv4'].is_bound) - if all(interface.ipv4_address for interface in - physical_interfaces if interface.is_bound - and interface['ip'].allocate is not False - and interface['ip'].is_bound): - # add as a manual allocated device - manual_alloc_devices.add(device) - - if manual_alloc_devices == set(l3_devices): - manual_alloc_ipv4_infrastructure = True - else: - log.info("Allocating from IPv4 infrastructure block: %s" % infra_block) - manual_alloc_ipv4_infrastructure = False - # warn if any set - allocated = [] - unallocated = [] - for node in l3_devices: - # TODO: make these inverse sets - allocated += sorted([i for i in node.physical_interfaces() - if i.is_bound and i.ipv4_address]) - unallocated += sorted([i for i in node.physical_interfaces() - if i.is_bound and not i.ipv4_address - and i['ipv4'].is_bound]) - - # TODO: what if IP is set but not a prefix? - if len(allocated): - # TODO: if set is > 50% of nodes then list those that are NOT set - log.warning( - "Using automatic IPv4 interface allocation. IPv4 interface addresses specified on interfaces %s will be ignored." % allocated) - - # TODO: need to set allocate_ipv4 by default in the readers - - if manual_alloc_ipv4_infrastructure: - manual_ipv4_infrastructure_allocation(anm) - else: - ipv4.allocate_infra(g_ipv4, infra_block) - - if g_in.data.alloc_ipv4_loopbacks is False: - manual_ipv4_loopback_allocation(anm) - else: - log.info("Allocating from IPv4 loopback block: %s" % loopback_block) - # Check if some nodes are allocated - allocated = sorted([n for n in g_ip if n['input'].loopback_v4]) - unallocated = sorted([n for n in g_ip if not n['input'].loopback_v4]) - if len(allocated): - log.warning( - "Using automatic IPv4 loopback allocation. IPv4 loopback addresses specified on nodes %s will be ignored." % allocated) - # TODO: if set is > 50% of nodes then list those that are NOT set - ipv4.allocate_loopbacks(g_ipv4, loopback_block) - - # TODO: need to also support secondary_loopbacks for IPv6 - # TODO: only call if secondaries are set - - ipv4.allocate_secondary_loopbacks(g_ipv4, vrf_loopback_block) - - # TODO: replace this with direct allocation to interfaces in ip alloc plugin - # TODO: add option for nonzero interfaces on node - ie - # node.secondary_loopbacks - for node in g_ipv4: - node.static_routes = [] - - for node in g_ipv4.routers(): - node.loopback_zero.ip_address = node.loopback - node.loopback_zero.subnet = netaddr.IPNetwork("%s/32" % node.loopback) - for interface in node.loopback_interfaces(): - if not interface.is_loopback_zero: - # TODO: fix this inconsistency elsewhere - interface.ip_address = interface.loopback diff --git a/autonetkit/design/ip_addressing/ipv6.py b/autonetkit/design/ip_addressing/ipv6.py deleted file mode 100644 index 607c7d58..00000000 --- a/autonetkit/design/ip_addressing/ipv6.py +++ /dev/null @@ -1,329 +0,0 @@ -import autonetkit.ank as ank_utils -import autonetkit.config -import autonetkit.log as log -from autonetkit.ank import sn_preflen_to_network -from netaddr import IPAddress - -SETTINGS = autonetkit.config.settings - -#@call_log - - -def manual_ipv6_loopback_allocation(anm): - """Applies manual IPv6 allocation""" - - import netaddr - g_ipv6 = anm['ipv6'] - g_in = anm['input'] - - for l3_device in g_ipv6.l3devices(): - try: - l3_device.loopback = IPAddress(l3_device['input'].loopback_v6) - except netaddr.AddrFormatError: - log.debug("Unable to parse IP address %s on %s", - l3_device['input'].loopback_v6, l3_device) - - # also need to form aggregated IP blocks (used for e.g. routing prefix - # advertisement) - try: - loopback_subnet = g_in.data.ipv6_loopback_subnet - loopback_prefix = g_in.data.ipv6_loopback_prefix - loopback_block = sn_preflen_to_network(loopback_subnet, - loopback_prefix) - except Exception, e: - log.info("Unable to parse specified ipv4 loopback subnets %s/%s") - else: - mismatched_nodes = [n for n in g_ipv6.l3devices() - if n.loopback and - n.loopback not in loopback_block] - if len(mismatched_nodes): - log.warning("IPv6 loopbacks set on nodes %s are not in global " - "loopback allocation block %s" - % (sorted(mismatched_nodes), loopback_block)) - - loopback_blocks = {} - for (asn, devices) in g_ipv6.groupby('asn').items(): - routers = [d for d in devices if d.is_router()] - loopbacks = [r.loopback for r in routers] - loopback_blocks[asn] = netaddr.cidr_merge(loopbacks) - - g_ipv6.data.loopback_blocks = loopback_blocks - # formatted = {key: [str(v) for v in val] for key, val in loopback_blocks.items()} - # log.info("Found loopback IP blocks %s", formatted) - -#@call_log - - -def extract_ipv6_blocks(anm): - - # TODO: set all these blocks globally in config file, rather than repeated - # in load, build_network, compile, etc - - from autonetkit.ank import sn_preflen_to_network - from netaddr import IPNetwork - g_in = anm['input'] - - ipv6_defaults = SETTINGS["IP Addressing"]["v6"] - - try: - infra_subnet = g_in.data.ipv6_infra_subnet - infra_prefix = g_in.data.ipv6_infra_prefix - infra_block = sn_preflen_to_network(infra_subnet, infra_prefix) - except Exception, error: - infra_block = IPNetwork( - '%s/%s' % (ipv6_defaults["infra_subnet"], - ipv6_defaults["infra_prefix"])) - if infra_subnet is None or infra_prefix is None: - log.debug('Using default IPv6 infra_subnet %s', infra_block) - else: - log.warning('Unable to obtain IPv6 infra_subnet from input graph: %s, using default %s' % ( - error, infra_block)) - - try: - loopback_subnet = g_in.data.ipv6_loopback_subnet - loopback_prefix = g_in.data.ipv6_loopback_prefix - loopback_block = sn_preflen_to_network(loopback_subnet, - loopback_prefix) - except Exception, error: - loopback_block = IPNetwork( - '%s/%s' % (ipv6_defaults["loopback_subnet"], - ipv6_defaults["loopback_prefix"])) - if loopback_subnet is None or loopback_prefix is None: - log.debug('Using default IPv6 loopback_subnet %s', - loopback_block) - else: - log.warning('Unable to obtain IPv6 loopback_subnet from" input graph: %s, using default %s' % ( - error, loopback_block)) - - try: - vrf_loopback_subnet = g_in.data.ipv6_vrf_loopback_subnet - vrf_loopback_prefix = g_in.data.ipv6_vrf_loopback_prefix - vrf_loopback_block = sn_preflen_to_network(vrf_loopback_subnet, - vrf_loopback_prefix) - except Exception, e: - vrf_loopback_block = IPNetwork( - '%s/%s' % (ipv6_defaults["vrf_loopback_subnet"], ipv6_defaults["vrf_loopback_prefix"])) - if vrf_loopback_subnet is None or vrf_loopback_prefix is None: - log.debug('Using default IPv6 vrf_loopback_subnet %s' % - vrf_loopback_block) - else: - log.warning('Unable to obtain IPv6 vrf_loopback_subnet from input graph: %s, using default %s' % ( - e, vrf_loopback_block)) - - return (infra_block, loopback_block, vrf_loopback_block) - -#@call_log - - -def manual_ipv6_infrastructure_allocation(anm): - """Applies manual IPv6 allocation""" - - import netaddr - g_ipv6 = anm['ipv6'] - g_in = anm['input'] - log.info('Using specified IPv6 infrastructure allocation') - - for node in g_ipv6.l3devices(): - for interface in node.physical_interfaces(): - if not interface['input'].is_bound: - continue # unbound interface - if not interface['ipv6'].is_bound: - continue - ip_address = netaddr.IPAddress(interface['input' - ].ipv6_address) - prefixlen = interface['input'].ipv6_prefixlen - interface.ip_address = ip_address - interface.prefixlen = prefixlen - cidr_string = '%s/%s' % (ip_address, prefixlen) - interface.subnet = netaddr.IPNetwork(cidr_string) - - broadcast_domains = [d for d in g_ipv6 if d.broadcast_domain] - - # TODO: allow this to work with specified ip_address/subnet as well as - # ip_address/prefixlen - - global_infra_block = None - try: - # Note this is only pickling up if explictly set in g_in - infra_subnet = g_in.data.ipv6_infra_subnet - infra_prefix = g_in.data.ipv6_infra_prefix - global_infra_block = sn_preflen_to_network(infra_subnet, infra_prefix) - except Exception, e: - log.info("Unable to parse specified ipv4 infra subnets %s/%s") - - from netaddr import IPNetwork - mismatched_interfaces = [] - - for coll_dom in broadcast_domains: - connected_interfaces = [edge.dst_int for edge in - coll_dom.edges()] - cd_subnets = [IPNetwork('%s/%s' % (i.subnet.network, - i.prefixlen)) for i in connected_interfaces] - - if global_infra_block is not None: - mismatched_interfaces += [i for i in connected_interfaces - if i.ip_address not in global_infra_block] - - if len(cd_subnets) == 0: - log.warning("Collision domain %s is not connected to any nodes", - coll_dom) - continue - - try: - assert len(set(cd_subnets)) == 1 - except AssertionError: - mismatch_subnets = '; '.join('%s: %s/%s' % (i, - i.subnet.network, i.prefixlen) for i in - connected_interfaces) - log.warning('Non matching subnets from collision domain %s: %s', - coll_dom, mismatch_subnets) - else: - coll_dom.subnet = cd_subnets[0] # take first entry - - # apply to remote interfaces - - for edge in coll_dom.edges(): - edge.dst_int.subnet = coll_dom.subnet - - # also need to form aggregated IP blocks (used for e.g. routing prefix - # advertisement) - # import autonetkit - # autonetkit.update_vis(anm) - if len(mismatched_interfaces): - log.warning("IPv6 Infrastructure IPs %s are not in global " - "loopback allocation block %s" - % (sorted(mismatched_interfaces), global_infra_block)) - - infra_blocks = {} - for (asn, devices) in g_ipv6.groupby('asn').items(): - broadcast_domains = [d for d in devices if d.broadcast_domain] - subnets = [cd.subnet for cd in broadcast_domains - if cd.subnet is not None] # only if subnet is set - infra_blocks[asn] = netaddr.cidr_merge(subnets) - - g_ipv6.data.infra_blocks = infra_blocks - # formatted = {key: [str(v) for v in val] for key, val in infra_blocks.items()} - # log.info("Found loopback IP blocks %s", formatted) - - -#@call_log - - -def build_ipv6(anm): - """Builds IPv6 graph, using nodes and edges from IP graph""" - import netaddr - import autonetkit.plugins.ipv6 as ipv6 - - # uses the nodes and edges from ipv4 - - # TODO: do we also need to copy across the asn for broadcast domains? - - g_ipv6 = anm.add_overlay('ipv6') - g_ip = anm['ip'] - g_in = anm['input'] - # retain if collision domain or not - g_ipv6.add_nodes_from(g_ip, retain=['label', 'asn', 'allocate', - 'broadcast_domain']) - g_ipv6.add_edges_from(g_ip.edges()) - - # TODO: tidy up naming consitency of secondary_loopback_block and - # vrf_loopback_block - (infra_block, loopback_block, secondary_loopback_block) = \ - extract_ipv6_blocks(anm) - - if any(i for n in g_ip.nodes() for i in - n.loopback_interfaces() if not i.is_loopback_zero): - block_message = "IPv6 Secondary Loopbacks: %s" % secondary_loopback_block - log.info(block_message) - - # TODO: replace this with direct allocation to interfaces in ip alloc - # plugin - allocated = sorted([n for n in g_ip if n['input'].loopback_v6]) - if len(allocated) == len(g_ip.l3devices()): - # all allocated - # TODO: need to infer subnetomanual_ipv6_loopback_allocation - log.info("Using user-specified IPv6 loopback addresses") - manual_ipv6_loopback_allocation(anm) - else: - log.info("Allocating from IPv6 loopback block: %s" % loopback_block) - if len(allocated): - log.warning( - "Using automatic IPv6 loopback allocation. IPv6 loopback addresses specified on nodes %s will be ignored." % allocated) - else: - log.info("Automatically assigning IPv6 loopback addresses") - - ipv6.allocate_loopbacks(g_ipv6, loopback_block) - - l3_devices = [d for d in g_in if d.device_type in ('router', 'server')] - - manual_alloc_devices = set() - for device in l3_devices: - physical_interfaces = list(device.physical_interfaces()) - allocated = list( - interface.ipv6_address for interface in physical_interfaces - if interface.is_bound and interface['ipv6'].is_bound - and interface['ip'].allocate is not False) - - #TODO: check for repeated code - -#TODO: copy allocate from g_ip to g_ipv6 - if all(interface.ipv6_address for interface in - physical_interfaces if interface.is_bound - and interface['ipv6'].is_bound - and interface['ip'].allocate is not False): - # add as a manual allocated device - manual_alloc_devices.add(device) - - if manual_alloc_devices == set(l3_devices): - log.info("Using user-specified IPv6 infrastructure addresses") - manual_alloc_ipv6_infrastructure = True - else: - log.info("Allocating from IPv6 Infrastructure block: %s" % infra_block) - manual_alloc_ipv6_infrastructure = False - # warn if any set - allocated = [] - unallocated = [] - for node in l3_devices: - allocated += sorted([i for i in node.physical_interfaces() - if i.is_bound and i.ipv6_address]) - unallocated += sorted([i for i in node.physical_interfaces() - if i.is_bound and not i.ipv6_address - and i['ipv6'].is_bound]) - - # TODO: what if IP is set but not a prefix? - if len(allocated): - # TODO: if set is > 50% of nodes then list those that are NOT set - log.warning( - "Using automatic IPv6 interface allocation. IPv6 interface addresses specified on interfaces %s will be ignored." % allocated) - else: - log.info("Automatically assigning IPv6 infrastructure addresses") - - if manual_alloc_ipv6_infrastructure: - manual_ipv6_infrastructure_allocation(anm) - else: - ipv6.allocate_infra(g_ipv6, infra_block) - # TODO: see if this is still needed or if can allocate direct from the - # ipv6 allocation plugin - for node in g_ipv6.l3devices(): - for interface in node: - edges = list(interface.edges()) - if len(edges): - edge = edges[0] # first (only) edge - # TODO: make this consistent - interface.ip_address = edge.ip - interface.subnet = edge.dst.subnet # from collision domain - - ipv6.allocate_secondary_loopbacks(g_ipv6, secondary_loopback_block) - for node in g_ipv6: - node.static_routes = [] - - for node in g_ipv6.routers(): - # TODO: test this code - node.loopback_zero.ip_address = node.loopback - node.loopback_zero.subnet = netaddr.IPNetwork("%s/32" % node.loopback) - for interface in node.loopback_interfaces(): - if not interface.is_loopback_zero: - # TODO: fix this inconsistency elsewhere - interface.ip_address = interface.loopback - -#@call_log diff --git a/autonetkit/design/layer1.py b/autonetkit/design/layer1.py deleted file mode 100644 index 9add9fc2..00000000 --- a/autonetkit/design/layer1.py +++ /dev/null @@ -1,90 +0,0 @@ -import autonetkit.ank as ank_utils -import autonetkit.log as log - - -def build_layer1(anm): - g_l1 = anm.add_overlay('layer1') - g_phy = anm['phy'] - g_l1.add_nodes_from(g_phy) - g_l1.add_edges_from(g_phy.edges()) - - # aggregate collision domains - hubs = g_l1.nodes(device_type="hub") - ank_utils.aggregate_nodes(g_l1, hubs) - # TODO: remove aggregated and disconnected nodes - # refresh hubs list - hubs = g_l1.nodes(device_type="hub") - - for hub in hubs: - hub.collision_domain = True - - split_ptp(anm) - build_layer1_conn(anm) - -def build_layer1_conn(anm): - g_l1 = anm['layer1'] - g_l1_conn = anm.add_overlay('layer1_conn') - g_l1_conn.add_nodes_from(g_l1, retain="collision_domain") - g_l1_conn.add_edges_from(g_l1.edges()) - - collision_domains = g_l1_conn.nodes(collision_domain=True) - exploded_edges = ank_utils.explode_nodes(g_l1_conn, collision_domains) - - # explode each seperately? - for edge in exploded_edges: - edge.multipoint = True - edge.src_int.multipoint = True - edge.dst_int.multipoint = True - - # TODO: tidy up partial repetition of collision_domain attribute and - # device type - - -def split_ptp(anm): - g_l1 = anm['layer1'] - g_phy = anm['phy'] - - edges_to_split = [e for e in g_l1.edges()] - edges_to_split = [e for e in edges_to_split - if not (e.src.is_hub() or e.dst.is_hub())] - - # TODO: debug the edges to split - # print "edges to split", edges_to_split - for edge in edges_to_split: - edge.split = True # mark as split for use in building nidb - - split_created_nodes = list(ank_utils.split(g_l1, edges_to_split, - retain=['split'], - id_prepend='cd_')) - - for node in split_created_nodes: - node.device_type = "collision_domain" - node.collision_domain = True - - # TODO: if parallel nodes, offset - # TODO: remove graphics, assign directly - g_graphics = anm['graphics'] - - if len(g_graphics): - co_ords_overlay = g_graphics # source from graphics overlay - else: - co_ords_overlay = g_phy # source from phy overlay - - for node in split_created_nodes: - node['graphics'].x = ank_utils.neigh_average(g_l1, node, 'x', - co_ords_overlay) + 0.1 - - # temporary fix for gh-90 - - node['graphics'].y = ank_utils.neigh_average(g_l1, node, 'y', - co_ords_overlay) + 0.1 - - # temporary fix for gh-90 - - asn = ank_utils.neigh_most_frequent( - g_l1, node, 'asn', g_phy) # arbitrary choice - node['graphics'].asn = asn - node.asn = asn # need to use asn in IP overlay for aggregating subnets - - -# TODO: build layer 1 connectivity graph diff --git a/autonetkit/design/layer2.py b/autonetkit/design/layer2.py deleted file mode 100644 index a8740504..00000000 --- a/autonetkit/design/layer2.py +++ /dev/null @@ -1,350 +0,0 @@ -import autonetkit.ank as ank_utils -import autonetkit.log as log - - -def build_layer2(anm): - build_layer2_base(anm) - build_vlans(anm) - build_layer2_conn(anm) - - check_layer2(anm) - build_layer2_broadcast(anm) - - -def build_layer2_base(anm): - g_l2 = anm.add_overlay('layer2') - g_l1 = anm['layer1'] - - g_l2.add_nodes_from(g_l1) - g_l2.add_edges_from(g_l1.edges()) - # Don't aggregate managed switches - for node in g_l2: - if node['layer1'].collision_domain == True: - node.broadcast_domain = True - node.device_type = "broadcast_domain" - - try: - from autonetkit_cisco import build_network as cisco_build_network - except ImportError: - pass - else: - cisco_build_network.post_layer2(anm) - - # Note: layer 2 base is much simpler since most is inherited from layer 1 - # cds - - -def build_layer2_conn(anm): - g_l2 = anm['layer2'] - g_l2_conn = anm.add_overlay('layer2_conn') - g_l2_conn.add_nodes_from(g_l2, retain="broadcast_domain") - g_l2_conn.add_edges_from(g_l2.edges()) - - broadcast_domains = g_l2_conn.nodes(broadcast_domain=True) - exploded_edges = ank_utils.explode_nodes(g_l2_conn, broadcast_domains) - - # explode each seperately? - for edge in exploded_edges: - edge.multipoint = True - edge.src_int.multipoint = True - edge.dst_int.multipoint = True - - -def check_layer2(anm): - """Sanity checks on topology""" - from collections import defaultdict - g_l2 = anm['layer2'] - - # check for igp and ebgp on same switch - for switch in sorted(g_l2.switches()): - neigh_asns = defaultdict(int) - for neigh in switch.neighbors(): - if neigh.asn is None: - continue # don't add if not set - neigh_asns[neigh.asn] += 1 - - # IGP if two or more neighbors share the same ASN - is_igp = any(asns > 1 for asns in neigh_asns.values()) - # eBGP if more than one unique neigh ASN - is_ebgp = len(neigh_asns.keys()) > 1 - if is_igp and is_ebgp: - log.warning("Switch %s contains both IGP and eBGP neighbors", - switch) - - # check for multiple links from nodes to switch - for switch in sorted(g_l2.switches()): - for neighbor in sorted(switch.neighbors()): - edges = g_l2.edges(switch, neighbor) - if len(edges) > 1: - # more than one edge between the (src, dst) pair -> parallel - log.warning("Multiple edges (%s) between %s and device %s", - len(edges), switch, neighbor) - - -def build_layer2_broadcast(anm): - g_l2 = anm['layer2'] - g_phy = anm['phy'] - g_graphics = anm['graphics'] - g_l2_bc = anm.add_overlay('layer2_bc') - g_l2_bc.add_nodes_from(g_l2.l3devices()) - g_l2_bc.add_nodes_from(g_l2.switches()) - g_l2_bc.add_edges_from(g_l2.edges()) - - # remove external connectors - - edges_to_split = [edge for edge in g_l2_bc.edges() - if edge.src.is_l3device() and edge.dst.is_l3device()] - # TODO: debug the edges to split - for edge in edges_to_split: - edge.split = True # mark as split for use in building nidb - - split_created_nodes = list(ank_utils.split(g_l2_bc, edges_to_split, - retain=['split'], - id_prepend='cd_')) - - # TODO: if parallel nodes, offset - # TODO: remove graphics, assign directly - if len(g_graphics): - co_ords_overlay = g_graphics # source from graphics overlay - else: - co_ords_overlay = g_phy # source from phy overlay - - for node in split_created_nodes: - node['graphics'].x = ank_utils.neigh_average(g_l2_bc, node, 'x', - co_ords_overlay) + 0.1 - - # temporary fix for gh-90 - - node['graphics'].y = ank_utils.neigh_average(g_l2_bc, node, 'y', - co_ords_overlay) + 0.1 - - # temporary fix for gh-90 - - asn = ank_utils.neigh_most_frequent( - g_l2_bc, node, 'asn', g_phy) # arbitrary choice - node['graphics'].asn = asn - node.asn = asn # need to use asn in IP overlay for aggregating subnets - - # also allocate an ASN for virtual switches - vswitches = [n for n in g_l2_bc.nodes() - if n['layer2'].device_type == "switch" - and n['layer2'].device_subtype == "virtual"] - for node in vswitches: - # TODO: refactor neigh_most_frequent to allow fallthrough attributes - # asn = ank_utils.neigh_most_frequent(g_l2_bc, node, 'asn', g_l2) # - # arbitrary choice - asns = [n['layer2'].asn for n in node.neighbors()] - asns = [x for x in asns if x is not None] - asn = ank_utils.most_frequent(asns) - node.asn = asn # need to use asn in IP overlay for aggregating subnets - # also mark as broadcast domain - - from collections import defaultdict - coincident_nodes = defaultdict(list) - for node in split_created_nodes: - coincident_nodes[(node['graphics'].x, node['graphics'].y)].append(node) - - coincident_nodes = {k: v for k, v in coincident_nodes.items() - if len(v) > 1} # trim out single node co-ordinates - import math - for _, val in coincident_nodes.items(): - for index, item in enumerate(val): - index = index + 1 - x_offset = 25 * math.floor(index / 2) * math.pow(-1, index) - y_offset = -1 * 25 * math.floor(index / 2) * math.pow(-1, index) - item['graphics'].x = item['graphics'].x + x_offset - item['graphics'].y = item['graphics'].y + y_offset - - switch_nodes = g_l2_bc.switches() # regenerate due to aggregated - g_l2_bc.update(switch_nodes, broadcast_domain=True) - - # switches are part of collision domain - g_l2_bc.update(split_created_nodes, broadcast_domain=True) - - # Assign collision domain to a host if all neighbours from same host - - for node in split_created_nodes: - if ank_utils.neigh_equal(g_l2_bc, node, 'host', g_phy): - node.host = ank_utils.neigh_attr(g_l2_bc, node, 'host', - g_phy).next() # first attribute - - # set collision domain IPs - # TODO; work out why this throws a json exception - #autonetkit.ank.set_node_default(g_l2_bc, broadcast_domain=False) - - for node in g_l2_bc.nodes('broadcast_domain'): - graphics_node = g_graphics.node(node) - #graphics_node.device_type = 'broadcast_domain' - if node.is_switch(): - # TODO: check not virtual - node['phy'].broadcast_domain = True - if not node.is_switch(): - # use node sorting, as accomodates for numeric/string names - graphics_node.device_type = 'broadcast_domain' - neighbors = sorted(neigh for neigh in node.neighbors()) - label = '_'.join(neigh.label for neigh in neighbors) - cd_label = 'cd_%s' % label # switches keep their names - node.label = cd_label - graphics_node.label = cd_label - node.device_type = "broadcast_domain" - node.label = node.id - graphics_node.label = node.id - - for node in vswitches: - node.broadcast_domain = True - - -def set_default_vlans(anm, default_vlan=2): - #TODO: read default vlan from global input config (eg from .virl) - # TODO: rename to "mark_defaults_vlan" or similar - # checks all links to managed switches have vlans - g_vtp = anm['vtp'] - g_l2 = anm['layer2'] - g_l1_conn = anm['layer1_conn'] - managed_switches = [n for n in g_vtp.switches() - if n.device_subtype == "managed"] - - no_vlan_ints = [] - for switch in managed_switches: - for edge in switch.edges(): - neigh_int = edge.dst_int - local_int = edge.src_int - if neigh_int.node in managed_switches: - neigh_int.trunk = True - continue - - if neigh_int['input'].vlan is None: - vlan = default_vlan - no_vlan_ints.append(neigh_int) - else: - vlan = neigh_int['input'].vlan - - try: - vlan = int(vlan) - except TypeError: - log.warning("Non-integer vlan %s for %s. Using default %s", - vlan, neigh_int, default_vlan) - vlan = default_vlan - - neigh_int.vlan = vlan - local_int.vlan = vlan - - for interface in switch: - if interface.vlan and interface.trunk: - log.warning("Interface %s set to trunk and vlan", interface) - - # map to layer 2 interfaces - if len(no_vlan_ints): - log.info("Setting default VLAN %s to interfaces connected to a managed " - "switch with no VLAN: %s", default_vlan, no_vlan_ints) - - -def build_vlans(anm): - import itertools - from collections import defaultdict - g_l2 = anm['layer2'] - g_l1_conn = anm['layer1_conn'] - g_phy = anm['phy'] - - g_vtp = anm.add_overlay('vtp') - # g_vlan = anm.add_overlay('vlan') - managed_switches = [n for n in g_l2.switches() - if n.device_subtype == "managed"] - - g_vtp.add_nodes_from(g_l1_conn) - g_vtp.add_edges_from(g_l1_conn.edges()) - - # remove anything not a managed_switch or connected to a managed_switch - keep = set() - keep.update(managed_switches) - for switch in managed_switches: - keep.update(switch['vtp'].neighbors()) - - remove = set(g_vtp) - keep - g_vtp.remove_nodes_from(remove) - - edges_to_remove = [e for e in g_vtp.edges() - if not(e.src in managed_switches or e.dst in managed_switches)] - g_vtp.remove_edges_from(edges_to_remove) - - # import ipdb - # ipdb.set_trace() - set_default_vlans(anm) - - # copy across vlans from input graph - vswitch_id_counter = itertools.count(1) - - # TODO: aggregate managed switches - - bcs_to_trim = set() - - subs = ank_utils.connected_subgraphs(g_vtp, managed_switches) - for sub in subs: - # identify the VLANs on these switches - vlans = defaultdict(list) - sub_neigh_ints = set() - for switch in sub: - l2_switch = switch['layer2'] - bcs_to_trim.update(l2_switch.neighbors()) - neigh_ints = {iface for iface in switch.neighbor_interfaces() - if iface.node.is_l3device() - and iface.node not in sub} - - sub_neigh_ints.update(neigh_ints) - - for interface in sub_neigh_ints: - # store keyed by vlan id - vlan = interface['vtp'].vlan - vlans[vlan].append(interface) - - log.debug("Vlans for sub %s are %s", sub, vlans) - # create a virtual switch for each - # TODO: naming: if this is the only pair then name after these, else - # use the switch names too - #vswitch_prefix = "_".join(str(sw) for sw in sub) - vswitches = [] # store to connect trunks - for vlan, interfaces in vlans.items(): - # create a virtual switch - vswitch_id = "vswitch%s" % vswitch_id_counter.next() - # vswitch = g_vlan.add_node(vswitch_id) - vswitch = g_l2.add_node(vswitch_id) - # TODO: check of switch or just broadcast_domain for higher layer - # purposes - vswitch.device_type = "switch" - vswitch.device_subtype = "virtual" - vswitches.append(vswitch) - # TODO: layout based on midpoint of previous? - # or if same number as real switches, use their co-ordinates? - # and then check for coincident? - vswitch.x = sum( - i.node['phy'].x for i in interfaces) / len(interfaces) + 50 - vswitch.y = sum( - i.node['phy'].y for i in interfaces) / len(interfaces) + 50 - vswitch.vlan = vlan - - vswitch['layer2'].broadcast_domain = True - vswitch['layer2'].vlan = vlan - - # and connect from vswitch to the interfaces - edges_to_add = [(vswitch, iface) for iface in interfaces] - g_l2.add_edges_from(edges_to_add) - - # remove the physical switches - g_l2.remove_nodes_from(bcs_to_trim) - g_l2.remove_nodes_from(sub) - # TODO: also remove any broadcast domains no longer connected - - # g_l2.remove_nodes_from(disconnected_bcs) - - # Note: we don't store the interface names as ciuld clobber - # eg came from two physical switches, each on gige0 - # if need, work backwards from the router iface and its connectivity - - # and add the trunks - # TODO: these need annotations! - # create trunks - edges_to_add = list(itertools.combinations(vswitches, 2)) - # TODO: ensure only once - # TODO: filter so only one direction - # g_vlan.add_edges_from(edges_to_add, trunk=True) - g_vtp.add_edges_from(edges_to_add, trunk=True) diff --git a/autonetkit/design/mpls.py b/autonetkit/design/mpls.py deleted file mode 100644 index 73431bb1..00000000 --- a/autonetkit/design/mpls.py +++ /dev/null @@ -1,344 +0,0 @@ - #!/usr/bin/python -# -*- coding: utf-8 -*- -import autonetkit.log as log -import autonetkit.ank as ank_utils - -import itertools -from autonetkit.ank_utils import call_log - -#@call_log - - -def mpls_te(anm): - g_in = anm['input'] - g_phy = anm['phy'] - g_l3 = anm['layer3'] - - # add regardless, so allows quick check of node in anm['mpls_te'] in - # compilers - - g_mpls_te = anm.add_overlay('mpls_te') - if not any(True for n in g_in.routers() if n.mpls_te_enabled): - log.debug('No nodes with mpls_te_enabled set') - return - - # te head end set if here - - g_mpls_te.add_nodes_from(g_in.routers()) - - # build up edge list sequentially, to provide meaningful messages for - # multipoint links - - multipoint_edges = [e for e in g_l3.edges() if e.multipoint] - if len(multipoint_edges): - log.info('Excluding multi-point edges from MPLS TE topology: %s' - % ', '.join(str(e) for e in multipoint_edges)) - - edges_to_add = set(g_l3.edges()) - set(multipoint_edges) - g_mpls_te.add_edges_from(edges_to_add) - - -#@call_log -def mpls_oam(anm): - g_in = anm['input'] - - # create placeholder graph (may have been created in other steps) - - if anm.has_overlay('mpls_oam'): - g_mpls_oam = anm['mpls_oam'] - else: - g_mpls_oam = anm.add_overlay('mpls_oam') - - use_mpls_oam = g_in.data.use_mpls_oam - if use_mpls_oam: - g_mpls_oam.add_nodes_from(g_in.routers()) - -#@call_log - - -def vrf_pre_process(anm): - """Marks nodes in g_in as appropriate based on vrf roles. - CE nodes -> ibgp_role = Disabled, so not in iBGP (this is allocated later) - """ - log.debug("Applying VRF pre-processing") - g_vrf = anm['vrf'] - for node in g_vrf.nodes(vrf_role="CE"): - log.debug("Marking CE node %s as non-ibgp" % node) - node['input'].ibgp_role = "Disabled" - -#@call_log - - -def allocate_vrf_roles(g_vrf): - """Allocate VRF roles""" - g_phy = g_vrf.anm['phy'] - # TODO: might be clearer like ibgp with is_p is_pe etc booleans? - final - # step to translate to role for vis - for node in g_vrf.nodes(vrf_role="CE"): - if not node.vrf: - node.vrf = "default_vrf" - - ce_set_nodes = [] - for node in sorted(g_vrf.nodes('vrf')): - node.vrf_role = "CE" - ce_set_nodes.append(node) - if len(ce_set_nodes): - message = ", ".join(str(n) for n in sorted(ce_set_nodes)) - g_vrf.log.info("VRF role set to CE for %s" % message) - - non_ce_nodes = [node for node in g_vrf if node.vrf_role != "CE"] - - pe_set_nodes = [] - p_set_nodes = [] - for node in sorted(non_ce_nodes): - phy_neighbors = [ - n for n in g_phy.node(node).neighbors() if n.is_router()] - # neighbors from physical graph for connectivity - # TODO: does this do anything? - phy_neighbors = [neigh for neigh in phy_neighbors] - # filter to just this asn - if any(g_vrf.node(neigh).vrf_role == "CE" for neigh in phy_neighbors): - # phy neigh has vrf set in this graph - node.vrf_role = "PE" - pe_set_nodes.append(node) - else: - node.vrf_role = "P" # default role - p_set_nodes.append(node) - - if len(pe_set_nodes): - message = ", ".join(str(n) for n in sorted(pe_set_nodes)) - g_vrf.log.info("VRF role set to PE for %s" % message) - if len(p_set_nodes): - message = ", ".join(str(n) for n in sorted(p_set_nodes)) - g_vrf.log.info("VRF role set to P for %s" % message) - -#@call_log - - -def add_vrf_loopbacks(g_vrf): - """Adds loopbacks for VRFs, and stores VRFs connected to PE router""" - # autonetkit.update_vis(anm) - for node in g_vrf.nodes(vrf_role="PE"): - node_vrf_names = {n.vrf for n in node.neighbors(vrf_role="CE")} - node.node_vrf_names = node_vrf_names - node.rd_indices = {} - for index, vrf_name in enumerate(node_vrf_names, 1): - node.rd_indices[vrf_name] = index - node.add_loopback(vrf_name=vrf_name, - description="loopback for vrf %s" % vrf_name) - -#@call_log - - -def build_ibgp_vpn_v4(anm): - """Based on the ibgp_v4 hierarchy rules. - Exceptions: - 1. Remove links to (PE, RRC) nodes - - CE nodes are excluded from RR hierarchy ibgp creation through pre-process step - - """ - # TODO: remove the bgp layer and have just ibgp and ebgp - # TODO: build from design rules, currently just builds from ibgp links in - # bgp layer - g_bgp = anm['bgp'] - g_ibgp_v4 = anm['ibgp_v4'] - g_vrf = anm['vrf'] - g_ibgp_vpn_v4 = anm.add_overlay("ibgp_vpn_v4", directed=True) - - v6_vrf_nodes = [n for n in g_vrf - if n.vrf is not None and n['phy'].use_ipv6 is True] - if len(v6_vrf_nodes): - message = ", ".join(str(s) for s in v6_vrf_nodes) - log.warning("This version of AutoNetkit does not support IPv6 MPLS VPNs. " - "The following nodes have IPv6 enabled but will not have an associated IPv6 MPLS VPN topology created: %s" % message) - - ibgp_v4_nodes = list(g_ibgp_v4.nodes()) - pe_nodes = set(g_vrf.nodes(vrf_role="PE")) - pe_rrc_nodes = {n for n in ibgp_v4_nodes if - n in pe_nodes and n.ibgp_role == "RRC"} - # TODO: warn if pe_rrc_nodes? - ce_nodes = set(g_vrf.nodes(vrf_role="CE")) - - if len(pe_nodes) == len(ce_nodes) == len(pe_rrc_nodes) == 0: - # no vrf nodes to connect - return - - # TODO: extend this to only connect nodes which are connected in VRFs, so - # don't set to others - - - ibgp_vpn_v4_nodes = (n for n in ibgp_v4_nodes - if n not in ce_nodes) - g_ibgp_vpn_v4.add_nodes_from(ibgp_vpn_v4_nodes, retain=["ibgp_role"]) - g_ibgp_vpn_v4.add_edges_from(g_ibgp_v4.edges(), retain="direction") - - for node in g_ibgp_vpn_v4: - if node.ibgp_role in ("HRR", "RR"): - node.retain_route_target = True - - ce_edges = [e for e in g_ibgp_vpn_v4.edges() - if e.src in ce_nodes or e.dst in ce_nodes] - - # mark ibgp direction - ce_pe_edges = [] - pe_ce_edges = [] - for edge in g_ibgp_vpn_v4.edges(): - if (edge.src.vrf_role, edge.dst.vrf_role) == ("CE", "PE"): - edge.direction = "up" - edge.vrf = edge.src.vrf - ce_pe_edges.append(edge) - elif (edge.src.vrf_role, edge.dst.vrf_role) == ("PE", "CE"): - edge.direction = "down" - edge.vrf = edge.dst.vrf - pe_ce_edges.append(edge) - - # TODO: Document this - g_ibgpv4 = anm['ibgp_v4'] - g_ibgpv6 = anm['ibgp_v6'] - g_ibgpv4.remove_edges_from(ce_edges) - g_ibgpv6.remove_edges_from(ce_edges) - g_ibgpv4.add_edges_from(ce_pe_edges, retain=["direction", "vrf"]) - g_ibgpv4.add_edges_from(pe_ce_edges, retain=["direction", "vrf"]) - g_ibgpv6.add_edges_from(ce_pe_edges, retain=["direction", "vrf"]) - g_ibgpv6.add_edges_from(pe_ce_edges, retain=["direction", "vrf"]) - for edge in pe_ce_edges: - # mark as exclude so don't include in standard ibgp config stanzas - if g_ibgpv4.has_edge(edge): - edge['ibgp_v4'].exclude = True - if g_ibgpv6.has_edge(edge): - edge['ibgp_v6'].exclude = True - -# legacy - g_bgp = anm['bgp'] - g_bgp.remove_edges_from(ce_edges) - g_bgp.add_edges_from(ce_pe_edges, retain=["direction", "vrf", "type"]) - g_bgp.add_edges_from(pe_ce_edges, retain=["direction", "vrf", "type"]) - - # also need to modify the ibgp_v4 and ibgp_v6 graphs - -#@call_log - - -def build_mpls_ldp(anm): - """Builds MPLS LDP""" - g_in = anm['input'] - g_vrf = anm['vrf'] - g_layer3 = anm['layer3'] - g_mpls_ldp = anm.add_overlay("mpls_ldp") - nodes_to_add = [n for n in g_in.routers() - if n['vrf'].vrf_role in ("PE", "P")] - g_mpls_ldp.add_nodes_from(nodes_to_add, retain=["vrf_role", "vrf"]) - - # store as set for faster lookup - pe_nodes = set(g_vrf.nodes(vrf_role="PE")) - p_nodes = set(g_vrf.nodes(vrf_role="P")) - - pe_to_pe_edges = (e for e in g_layer3.edges() - if e.src in pe_nodes and e.dst in pe_nodes) - g_mpls_ldp.add_edges_from(pe_to_pe_edges) - - pe_to_p_edges = (e for e in g_layer3.edges() - if e.src in pe_nodes and e.dst in p_nodes - or e.src in p_nodes and e.dst in pe_nodes) - g_mpls_ldp.add_edges_from(pe_to_p_edges) - - p_to_p_edges = (e for e in g_layer3.edges() - if e.src in p_nodes and e.dst in p_nodes) - g_mpls_ldp.add_edges_from(p_to_p_edges) - -#@call_log - - -def mark_ebgp_vrf(anm): - g_vrf = anm['vrf'] - g_ebgpv4 = anm['ebgp_v4'] - g_ebgpv6 = anm['ebgp_v6'] - pe_nodes = set(g_vrf.nodes(vrf_role="PE")) - ce_nodes = set(g_vrf.nodes(vrf_role="CE")) - for edge in g_ebgpv4.edges(): - if edge.src in pe_nodes and edge.dst in ce_nodes: - # exclude from "regular" ebgp (as put into vrf stanza) - edge.exclude = True - edge.vrf = edge.dst['vrf'].vrf - - for edge in g_ebgpv6.edges(): - if edge.src in pe_nodes and edge.dst in ce_nodes: - # exclude from "regular" ebgp (as put into vrf stanza) - edge.exclude = True - edge.vrf = edge.dst['vrf'].vrf - -#@call_log - - -def build_vrf(anm): - """Build VRF Overlay""" - g_in = anm['input'] - g_layer3 = anm['layer3'] - g_vrf = anm.add_overlay("vrf") - - import autonetkit - autonetkit.ank.set_node_default(g_in, vrf=None) - - if not any(True for n in g_in.routers() if n.vrf): - log.debug("No VRFs set") - return - - g_vrf.add_nodes_from(g_in.routers(), retain=["vrf_role", "vrf"]) - - allocate_vrf_roles(g_vrf) - vrf_pre_process(anm) - - def is_pe_ce_edge(edge): - if not(edge.src in g_vrf and edge.dst in g_vrf): - return False - - src_vrf_role = g_vrf.node(edge.src).vrf_role - dst_vrf_role = g_vrf.node(edge.dst).vrf_role - return (src_vrf_role, dst_vrf_role) in (("PE", "CE"), ("CE", "PE")) - - vrf_add_edges = (e for e in g_layer3.edges() - if is_pe_ce_edge(e)) - # TODO: should mark as being towards PE or CE - g_vrf.add_edges_from(vrf_add_edges) - - def is_pe_p_edge(edge): - if not(edge.src in g_vrf and edge.dst in g_vrf): - return False - src_vrf_role = g_vrf.node(edge.src).vrf_role - dst_vrf_role = g_vrf.node(edge.dst).vrf_role - return (src_vrf_role, dst_vrf_role) in (("PE", "P"), ("P", "PE")) - vrf_add_edges = (e for e in g_layer3.edges() - if is_pe_p_edge(e)) - g_vrf.add_edges_from(vrf_add_edges) - - build_mpls_ldp(anm) - # add PE to P edges - - add_vrf_loopbacks(g_vrf) - # allocate route-targets per AS - # This could later look at connected components for each ASN - route_targets = {} - for asn, devices in ank_utils.groupby("asn", g_vrf.nodes(vrf_role="PE")): - asn_vrfs = [d.node_vrf_names for d in devices] - # flatten list to unique set - asn_vrfs = set(itertools.chain.from_iterable(asn_vrfs)) - route_targets[asn] = {vrf: "%s:%s" % (asn, index) - for index, vrf in enumerate(sorted(asn_vrfs), 1)} - - g_vrf.data.route_targets = route_targets - - for node in g_vrf: - vrf_loopbacks = node.interfaces("is_loopback", "vrf_name") - for index, interface in enumerate(vrf_loopbacks, start=101): - interface.index = index - - for edge in g_vrf.edges(): - # Set the vrf of the edge to be that of the CE device (either src or - # dst) - edge.vrf = edge.src.vrf if edge.src.vrf_role is "CE" else edge.dst.vrf - - # map attributes to interfaces - for edge in g_vrf.edges(): - for interface in edge.interfaces(): - interface.vrf_name = edge.vrf diff --git a/autonetkit/design/osi_layers.py b/autonetkit/design/osi_layers.py deleted file mode 100644 index a57dee3a..00000000 --- a/autonetkit/design/osi_layers.py +++ /dev/null @@ -1,36 +0,0 @@ -import autonetkit.log as log -import autonetkit.ank as ank_utils - -def build_layer1(anm): - import autonetkit.design.layer1 - autonetkit.design.layer1.build_layer1(anm) - -def build_layer2(anm): - import autonetkit.design.layer2 - autonetkit.design.layer2.build_layer2(anm) - -def build_layer3(anm): - """ l3_connectivity graph: switch nodes aggregated and exploded""" - g_in = anm['input'] - gl2_conn = anm['layer2_conn'] - g_l3 = anm.add_overlay("layer3") - g_l3.add_nodes_from(gl2_conn, retain=['label']) - g_l3.add_nodes_from(g_in.switches(), retain=['asn']) - g_l3.add_edges_from(gl2_conn.edges()) - - switches = g_l3.switches() - - ank_utils.aggregate_nodes(g_l3, switches) - exploded_edges = ank_utils.explode_nodes(g_l3, - switches) - - # also explode virtual switches - vswitches = [n for n in g_l3.nodes() - if n['layer2'].device_type == "switch" - and n['layer2'].device_subtype == "virtual"] - - # explode each seperately? - for edge in exploded_edges: - edge.multipoint = True - edge.src_int.multipoint = True - edge.dst_int.multipoint = True \ No newline at end of file diff --git a/autonetkit/design/tests/test_igp.py b/autonetkit/design/tests/test_igp.py deleted file mode 100644 index 54aa8ce7..00000000 --- a/autonetkit/design/tests/test_igp.py +++ /dev/null @@ -1,130 +0,0 @@ -import autonetkit -import autonetkit.design.igp -from mock import patch - -def build_layer3(): - anm = autonetkit.topos.house() - from autonetkit.design.osi_layers import build_layer2, build_layer3 - build_layer2(anm) - build_layer3(anm) - - - return anm - - -def test_ospf(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - - for node in anm['phy']: - node.igp = "ospf" - - autonetkit.design.igp.build_ospf(anm) - - g_ospf = anm['ospf'] - assert(len(g_ospf) == 5) - edges = {(e.src, e.dst) for e in g_ospf.edges()} - expected = {("r4", "r5"), ("r1", "r2"), ("r1", "r3"), ("r2", "r3")} - assert edges == expected - - -def test_ospf_no_routing(): - anm = build_layer3() - autonetkit.design.igp.build_ospf(anm) - -def test_ospf_no_isis_set(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - autonetkit.design.igp.build_ospf(anm) - -def test_eigrp(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - - for node in anm['phy']: - node.igp = "eigrp" - - autonetkit.design.igp.build_eigrp(anm) - - g_eigrp = anm['eigrp'] - assert(len(g_eigrp) == 5) - edges = {(e.src, e.dst) for e in g_eigrp.edges()} - expected = {("r4", "r5"), ("r1", "r2"), ("r1", "r3"), ("r2", "r3")} - assert edges == expected - -def test_eigrp_no_routing(): - anm = build_layer3() - autonetkit.design.igp.build_eigrp(anm) - -def test_eigrp_no_isis_set(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - autonetkit.design.igp.build_eigrp(anm) - -def net_side_effect(anm): - import netaddr - g_isis = anm['isis'] - #Note: this only mocks up to 255 - import itertools - # fake the NET addresses - nets = ("49.1921.6801.%s.00" % x for x in itertools.count(start=9001)) - for node in g_isis: - node.net =nets.next() - -def test_isis(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - - # isis also needs ipv4 to allocate the OSI addresses - - for node in anm['phy']: - node.igp = "isis" - -#@patch("", side_effect=net_side_effect) - with patch("autonetkit.design.igp.build_network_entity_title", - side_effect = net_side_effect): - autonetkit.design.igp.build_isis(anm) - - g_isis = anm['isis'] - assert(len(g_isis) == 5) - edges = {(e.src, e.dst) for e in g_isis.edges()} - expected = {("r4", "r5"), ("r1", "r2"), ("r1", "r3"), ("r2", "r3")} - assert edges == expected - -def test_isis_no_routing(): - anm = build_layer3() - autonetkit.design.igp.build_isis(anm) - -def test_isis_no_isis_set(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - autonetkit.design.igp.build_isis(anm) - -def test_multi_igp(): - anm = build_layer3() - anm['phy'].data.enable_routing = True - g_phy = anm['phy'] - - igps = {"r1": "ospf", "r2": "ospf", "r3": "ospf", "r4": "isis", "r5": "isis"} - for label, igp in igps.items(): - g_phy.node(label).igp = igp - - with patch("autonetkit.design.igp.build_network_entity_title", - side_effect = net_side_effect): - autonetkit.design.igp.build_igp(anm) - - g_ospf = anm['ospf'] - g_isis = anm['isis'] - assert(len(g_ospf) == 3) - assert(len(g_isis) == 2) - edges = {(e.src, e.dst) for e in g_ospf.edges()} - expected = {("r1", "r2"), ("r1", "r3"), ("r2", "r3")} - assert edges == expected - - edges = {(e.src, e.dst) for e in g_isis.edges()} - expected = {("r4", "r5")} - assert edges == expected - - #TODO: check labels in the merged g_igp graph - -test_multi_igp() \ No newline at end of file diff --git a/autonetkit/diff.py b/autonetkit/diff.py deleted file mode 100644 index 10d31aed..00000000 --- a/autonetkit/diff.py +++ /dev/null @@ -1,151 +0,0 @@ -import glob -import os - -import nidb - -#TODO: make this generalise to two graphs, rather than DeviceModel specifically - -def nidb_diff(directory = None, length = 1): - if not directory: - directory = os.path.join("versions", "nidb") - glob_dir = os.path.join(directory, "*.json.gz") - pickle_files = glob.glob(glob_dir) - pickle_files = sorted(pickle_files) - pairs = [(a, b) for (a, b) in zip(pickle_files, pickle_files[1:])] - pairs = pairs[-1*length:] - diffs = [] - for file_a, file_b in pairs: - nidb_a = nidb.DeviceModel() - nidb_a.restore(file_a) - graph_a = nidb_a._graph - nidb_b = nidb.DeviceModel() - nidb_b.restore(file_b) - graph_b = nidb_b._graph - diff = compare(graph_a, graph_b) - # remove render folder which is timestamps - diffs.append(diff) - - return diffs - -def elem_diff(elem_a, elem_b): - if type(elem_a) != type(elem_b): - #TODO: fix this - string_types = (str, unicode) - if type(elem_a) in string_types and type(elem_b) in string_types: - pass - else: - return "different types" - - if isinstance(elem_a, dict): - retval = {} - keys_a = set(elem_a.keys()) - keys_b = set(elem_b.keys()) - added_keys = keys_b - keys_a - if len(added_keys): - retval['a'] = list(added_keys) - removed_keys = keys_a - keys_b - if len(removed_keys): - retval['r'] = list(removed_keys) - common_keys = keys_a & keys_b - for key in common_keys: - res = elem_diff(elem_a[key], elem_b[key]) - if res: - retval[key] = res - - if len(retval): - return retval - - if isinstance(elem_a, list): - len_a = len(elem_a) - len_b = len(elem_b) -#TODO: handle if different lengths - retval = [] - min_len = min(len_a, len_b) - for index in range(min_len): - res = elem_diff(elem_a[index], elem_b[index]) - if res: - retval.append(res) - - if any(retval): - return retval - - if elem_a != elem_b: - return {1: elem_a, 2: elem_b } - -def compare_nidb(nidb_a, nidb_b): - graph_a = nidb_a._graph - graph_b = nidb_b._graph - diff = compare(graph_a, graph_b) - return diff - -def compare(graph_a, graph_b): - diff = {} - - diff = { - 'graph': {}, - 'nodes': {}, - 'edges': {}, - } - - diff['graph'] = elem_diff(graph_a.graph, graph_b.graph) - - diff['nodes'] = { - 'm': {}, - } - - nodes_a = set(graph_a.nodes()) - nodes_b = set(graph_b.nodes()) - common_nodes = nodes_a & nodes_b - added_nodes = nodes_b - nodes_a - if added_nodes: - diff['nodes']['a'] = list(added_nodes) - removed_nodes = nodes_a - nodes_b - if removed_nodes: - diff['nodes']['r'] = list(removed_nodes) - - for node in common_nodes: - dict_a = graph_a.node[node] - dict_b = graph_b.node[node] - node_diff = elem_diff(dict_a, dict_b) - if node_diff: - diff['nodes']['m'][node] = node_diff - - # remove empty if no changes - if not len(diff['nodes']['m']): - del diff['nodes']['m'] - - #TODO: do this backwards, and append at end - if not len(diff['nodes']): - del diff['nodes'] - - diff['edges'] = { - 'm': {}, - } - edges_a = set(graph_a.edges()) - edges_b = set(graph_b.edges()) - added_edges = edges_b - edges_a - if added_edges: - diff['edges']['a'] = list(added_edges) - removed_edges = edges_a - edges_b - if removed_edges: - diff['edges']['r'] = list(removed_edges) - - common_edges = edges_a & edges_b - for (src, dst) in common_edges: - dictA = graph_a[src][dst] - dictB = graph_b[src][dst] - edge_diff = elem_diff(dictA, dictB) - if edge_diff: - name = "%s_%s" % (src, dst) - diff['edges']['m'][name] = edge_diff - - # remove empty if no changes - if not len(diff['edges']['m']): - del diff['edges']['m'] - - #TODO: do this backwards, and append at end - if not len(diff['edges']): - del diff['edges'] - - - return diff diff --git a/autonetkit/example.py b/autonetkit/example.py deleted file mode 100644 index 2d2b7579..00000000 --- a/autonetkit/example.py +++ /dev/null @@ -1,87 +0,0 @@ -def house(): - """Returns anm with input and physical as house graph""" - from networkx.readwrite import json_graph - import networkx as nx - import autonetkit - # returns a house graph - data = {'directed': False, - 'graph': [], - 'links': [{'_ports': {'r4': 2, 'r5': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 1}, - {'_ports': {'r2': 3, 'r4': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 3}, - {'_ports': {'r3': 3, 'r5': 2}, - 'raw_interfaces': {}, - 'source': 1, - 'target': 4}, - {'_ports': {'r1': 1, 'r2': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 3}, - {'_ports': {'r1': 2, 'r3': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 4}, - {'_ports': {'r2': 2, 'r3': 2}, - 'raw_interfaces': {}, - 'source': 3, - 'target': 4}], - 'multigraph': False, - 'nodes': [{'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r4 to r2', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r4 to r5', 'id': 'eth1'}}, - 'asn': 2, - 'device_type': 'router', - 'id': 'r4', - 'label': 'r4', - 'x': 675, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r5 to r4', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r5 to r3', 'id': 'eth1'}}, - 'asn': 2, - 'device_type': 'router', - 'id': 'r5', - 'label': 'r5', - 'x': 675, - 'y': 500}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r1 to r2', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r1 to r3', 'id': 'eth1'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r1', - 'label': 'r1', - 'x': 350, - 'y': 400}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r2 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r2 to r3', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r2 to r4', 'id': 'eth2'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r2', - 'label': 'r2', - 'x': 500, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r3 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r3 to r2', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r3 to r5', 'id': 'eth2'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r3', - 'label': 'r3', - 'x': 500, - 'y': 500}]} - graph = json_graph.node_link_graph(data) - anm = autonetkit.anm.NetworkModel() - g_in = anm.add_overlay("input") - g_in._replace_graph(nx.Graph(graph)) - g_phy = anm["phy"] - g_phy._replace_graph(graph) - return anm \ No newline at end of file diff --git a/autonetkit/exception.py b/autonetkit/exception.py deleted file mode 100644 index 52431c55..00000000 --- a/autonetkit/exception.py +++ /dev/null @@ -1,17 +0,0 @@ -class AutoNetkitException(Exception): - - """Base class for AutoNetkit Exceptions""" - - -class AnkIncorrectFileFormat(AutoNetkitException): - - """Wrong file format""" - - -class OverlayNotFound(AutoNetkitException): - - def __init__(self, errors): - self.Errors = errors - - def __str__(self): - return 'Overlay %s not found' % self.Errors diff --git a/autonetkit/load/__init__.py b/autonetkit/load/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/load/graphml.py b/autonetkit/load/graphml.py deleted file mode 100644 index a766fd4c..00000000 --- a/autonetkit/load/graphml.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import itertools -import math -import string -from collections import defaultdict -from cStringIO import StringIO - -import autonetkit.config -import autonetkit.exception -import autonetkit.log as log -import networkx as nx - -settings = autonetkit.config.settings - - -# TODO: fallback to python if cStringIO version is not available - -def load_graphml(input_data, defaults = True): - - - # TODO: allow default properties to be passed in as dicts - - try: - graph = nx.read_graphml(input_data) - except IOError, e: - acceptable_errors = set([2, 36, 63]) # no such file or directory - # input string too long for filename - # input string too long for filename - if e.errno in acceptable_errors: - from xml.etree.cElementTree import ParseError - - # try as data string rather than filename string - - try: - input_pseduo_fh = StringIO(input_data) # load into filehandle to networkx - graph = nx.read_graphml(input_pseduo_fh) - except IOError: - raise autonetkit.exception.AnkIncorrectFileFormat - except IndexError: - raise autonetkit.exception.AnkIncorrectFileFormat - except ParseError: - raise autonetkit.exception.AnkIncorrectFileFormat - except ParseError: - raise autonetkit.exception.AnkIncorrectFileFormat - else: - raise e - - if graph.is_multigraph(): - log.info('Input graph is multigraph. Converting to single-edge graph' - ) - if graph.is_directed(): - graph = nx.DiGraph(graph) - else: - graph = nx.Graph(graph) - - # TODO: need to support edge index keying for multi graphs - graph.remove_edges_from(edge for edge in graph.selfloop_edges()) - -# TODO: if selfloops then log that are removing - - letters_single = (c for c in string.lowercase) # a, b, c, ... z - letters_double = ('%s%s' % (a, b) for (a, b) in - itertools.product(string.lowercase, string.lowercase)) # aa, ab, ... zz - letters = itertools.chain(letters_single, letters_double) # a, b, c, .. z, aa, ab, ac, ... zz - -# TODO: need to get set of current labels, and only return if not in this set - - # TODO: add cloud, host, etc - # prefixes for unlabelled devices, ie router -> r_a - - label_prefixes = {'router': 'r', 'switch': 'sw', 'server': 'se'} - - current_labels = set(graph.node[node].get('label') for node in - graph.nodes_iter()) - unique_label = (letter for letter in letters if letter - not in current_labels) - -# TODO: make sure device label set - - ank_graph_defaults = settings['Graphml']['Graph Defaults'] - for (key, val) in ank_graph_defaults.items(): - if key not in graph.graph: - graph.graph[key] = val - - # handle yEd exported booleans: if a boolean is set, then only the nodes marked true have the attribute. need to map the remainder to be false to allow ANK logic - # for node in graph.nodes(data=True): - # print node - - all_labels = dict((n, d.get('label')) for (n, d) in - graph.nodes(data=True)) - label_counts = defaultdict(list) - for (node, label) in all_labels.items(): - label_counts[label].append(node) - - # set default name for blank labels to ensure unique - - try: - blank_labels = [v for (k, v) in label_counts.items() - if not k].pop() # strip outer list - except IndexError: - blank_labels = [] # no blank labels - for (index, node) in enumerate(blank_labels): - - # TODO: log message that no label set, so setting default - - graph.node[node]['label'] = 'none___%s' % index - - duplicates = [(k, v) for (k, v) in label_counts.items() if k - and len(v) > 1] - for (label, nodes) in duplicates: - for node in nodes: - - # TODO: need to check they don't all have same ASN... if so then warn - - try: - graph.node[node]['label'] = '%s_%s' \ - % (graph.node[node]['label'], graph.node[node]['asn' - ]) - except KeyError: - log.warning('Unable to set new label for duplicate node %s: %s' - % (node, graph.node[node].get('label'))) - - boolean_attributes = set(k for (n, d) in graph.nodes(data=True) - for (k, v) in d.items() if isinstance(v, - bool)) - - for node in graph: - for attr in boolean_attributes: - if attr not in graph.node[node]: - graph.node[node][attr] = False - - boolean_attributes = set(k for (n1, d1) in graph.edge.items() - for (n2, d2) in d1.items() for (k, v) in - d2.items() if isinstance(v, bool)) - for (n1, d1) in graph.edge.items(): - for (n2, d2) in d1.items(): - for attr in boolean_attributes: - if attr not in graph.edge[n1][n2]: - graph.edge[n1][n2][attr] = False - -# TODO: store these in config file - - if defaults: - ank_node_defaults = settings['Graphml']['Node Defaults'] - node_defaults = graph.graph['node_default'] # update with defaults from graphml - for (key, val) in node_defaults.items(): - if val == 'False': - node_defaults[key] = False - - # TODO: do a dict update before applying so only need to iterate nodes once - - for (key, val) in ank_node_defaults.items(): - if key not in node_defaults or node_defaults[key] == 'None': - node_defaults[key] = val - - for node in graph: - for (key, val) in node_defaults.items(): - if key not in graph.node[node]: - graph.node[node][key] = val - - # set address family - - graph.graph['address_family'] = 'v4' - graph.graph['enable_routing'] = True - - # map lat/lon from zoo to crude x/y approximation - - if graph.graph.get('Creator') == 'Topology Zoo Toolset': - all_lat = [graph.node[n].get('Latitude') for n in graph - if graph.node[n].get('Latitude')] - all_lon = [graph.node[n].get('Longitude') for n in graph - if graph.node[n].get('Longitude')] - - lat_min = min(all_lat) - lon_min = min(all_lon) - lat_max = max(all_lat) - lon_max = max(all_lon) - lat_mean = (lat_max - lat_min) / 2 - lon_mean = (lon_max - lon_min) / 2 - lat_scale = 500 / (lat_max - lat_min) - lon_scale = 500 / (lon_max - lon_min) - for node in graph: - lat = graph.node[node].get('Latitude') or lat_mean # set default to be mean of min/max - lon = graph.node[node].get('Longitude') or lon_mean # set default to be mean of min/max - graph.node[node]['y'] = -1 * lat * lat_scale - graph.node[node]['x'] = lon * lon_scale - - if not (any(graph.node[n].get('x') for n in graph) - and any(graph.node[n].get('y') for n in graph)): - -# No x, y set, layout in a grid - - grid_length = int(math.ceil(math.sqrt(len(graph)))) - co_ords = [(x * 100, y * 100) for y in range(grid_length) - for x in range(grid_length)] - - # (0,0), (100, 0), (200, 0), (0, 100), (100, 100) .... - - for node in sorted(graph): - (x, y) = co_ords.pop(0) - graph.node[node]['x'] = x - graph.node[node]['y'] = y - - # and ensure asn is integer, x and y are floats - - for node in sorted(graph): - graph.node[node]['asn'] = int(graph.node[node]['asn']) - if graph.node[node]['asn'] == 0: - log.debug('Node %s has ASN set to 0. Setting to 1' - % graph.node[node]['label']) - graph.node[node]['asn'] = 1 - try: - x = float(graph.node[node]['x']) - except KeyError: - x = 0 - graph.node[node]['x'] = x - try: - y = float(graph.node[node]['y']) - except KeyError: - y = 0 - graph.node[node]['y'] = y - try: - graph.node[node]['label'] - except KeyError: - device_type = graph.node[node]['device_type'] - graph.node[node]['label'] = '%s_%s' \ - % (label_prefixes[device_type], unique_label.next()) - - if defaults: - ank_edge_defaults = settings['Graphml']['Edge Defaults'] - edge_defaults = graph.graph['edge_default'] - for (key, val) in ank_edge_defaults.items(): - if key not in edge_defaults or edge_defaults[key] == 'None': - edge_defaults[key] = val - - for (src, dst) in graph.edges(): - for (key, val) in edge_defaults.items(): - if key not in graph[src][dst]: - graph[src][dst][key] = val - -# apply defaults -# relabel nodes -# other handling... split this into seperate module! -# relabel based on label: assume unique by now! - # if graph.graph.get("Network") == "European NRENs": - # TODO: test if non-unique labels, if so then warn and proceed with this logic - # we need to map node ids to contain network to ensure unique labels - # mapping = dict( (n, "%s__%s" % (d['label'], d['asn'])) for n, d in graph.nodes(data=True)) - - - mapping = dict((n, d['label']) for (n, d) in graph.nodes(data=True)) # TODO: use dict comprehension - if not all(key == val for (key, val) in mapping.items()): - nx.relabel_nodes(graph, mapping, copy=False) # Networkx wipes data if remap with same labels - - graph.graph['file_type'] = 'graphml' - - selfloop_count = graph.number_of_selfloops() - if selfloop_count > 0: - log.warning("Self loops present: do multiple nodes have the same label?") - selfloops = ", ".join(str(e) for e in graph.selfloop_edges()) - log.warning("Removing selfloops: %s" % selfloops) - graph.remove_edges_from(edge for edge in graph.selfloop_edges()) - - - return graph diff --git a/autonetkit/load/initialize.py b/autonetkit/load/initialize.py deleted file mode 100644 index 0861ddec..00000000 --- a/autonetkit/load/initialize.py +++ /dev/null @@ -1,8 +0,0 @@ -def initialize(filename, defaults = True): - from autonetkit.build_network import load, initialise - with open(filename) as fh: - data = fh.read() - - graph = load(data, defaults=defaults) - anm = initialise(graph) - return anm \ No newline at end of file diff --git a/autonetkit/load/load_json.py b/autonetkit/load/load_json.py deleted file mode 100644 index 941dfcd9..00000000 --- a/autonetkit/load/load_json.py +++ /dev/null @@ -1,169 +0,0 @@ -import autonetkit -from autonetkit import example -from collections import defaultdict -from networkx.readwrite import json_graph -settings = autonetkit.config.settings - - -def nx_to_simple(graph): - j_data = json_graph.node_link_data(graph) - - # map ports to their named list - port_index_mapping = defaultdict(dict) - for node in j_data['nodes']: - node_id = node['id'] - _ports = node['_ports'] - ports = [] - for index, port in _ports.items(): - try: - port_id = port["id"] - except KeyError: - # no port id specified - if index == 0: - port_id = "Loopback0" - else: - port_id = "_port%s" % index - - port_data = dict(port) - port_data['id'] = port_id - ports.append(port_data) - # record the mapping of index to port_id - port_index_mapping[node_id][index] = port_id - - node['ports'] = sorted(ports, key=lambda x: x['id']) - del node['_ports'] - - nodes = j_data['nodes'] - mapped_links = [] - for link in j_data['links']: - src_node = nodes[link['source']]['id'] - dst_node = nodes[link['target']]['id'] - src_int_index = link['_ports'][src_node] - dst_int_index = link['_ports'][dst_node] - src_int = port_index_mapping[src_node][src_int_index] - dst_int = port_index_mapping[dst_node][dst_int_index] - # link['ports'] = {'src': src_node, 'src_port': src_int, - # 'dst': dst_node, 'dst_port': dst_int} - link['src'] = src_node - link['src_port'] = src_int - link['dst'] = dst_node - link['dst_port'] = dst_int - del link['_ports'] - try: - del link['raw_interfaces'] - except KeyError: - pass - del link['source'] - del link['target'] - - j_data['nodes'] = sorted(j_data['nodes'], key=lambda x: x['id']) - j_data['links'] = sorted( - j_data['links'], key=lambda x: (x['src'], x['dst'])) - - return j_data - - -def simple_to_nx(j_data): - port_to_index_mapping = defaultdict(dict) - for node in j_data['nodes']: - if not "ports" in node: - continue - node_id = node['id'] - # first check for loopback zero - ports = node['ports'] - _ports = {} # output format - try: - lo_zero = [p for p in ports if p['id'] == "Loopback0"].pop() - except IndexError: - # can't pop -> no loopback zero, append - lo_zero = {'category': 'loopback', - 'description': "Loopback Zero"} - else: - ports.remove(lo_zero) - finally: - _ports[0] = lo_zero - - for index, port in enumerate(ports, start=1): - _ports[index] = port - port_to_index_mapping[node_id][port['id']] = index - - del node['ports'] - node['_ports'] = _ports - - nodes_by_id = {n['id']: i for i, n - in enumerate(j_data['nodes'])} - - unmapped_links = [] - - if "links" in j_data: - mapped_links = j_data['links'] - - for link in mapped_links: - src = link['src'] - dst = link['dst'] - src_pos = nodes_by_id[src] - dst_pos = nodes_by_id[dst] - src_port_id = port_to_index_mapping[src][link['src_port']] - dst_port_id = port_to_index_mapping[dst][link['dst_port']] - - interfaces = {src: src_port_id, - dst: dst_port_id} - - unmapped_links.append({'source': src_pos, - 'target': dst_pos, - '_ports': interfaces - }) - - j_data['links'] = unmapped_links - return json_graph.node_link_graph(j_data) - - -def load_json(input_data, defaults = True): - import json - data = json.loads(input_data) - graph = simple_to_nx(data) - # TODO: any required pre-processing goes here - - if defaults: - ank_graph_defaults = settings['JSON']['Graph Defaults'] - for (key, val) in ank_graph_defaults.items(): - if key not in graph.graph: - graph.graph[key] = val - - ank_node_defaults = settings['JSON']['Node Defaults'] - node_defaults = graph.graph.get("node_default", {}) - for (key, val) in node_defaults.items(): - if val == 'False': - node_defaults[key] = False - - for (key, val) in ank_node_defaults.items(): - if key not in node_defaults or node_defaults[key] == 'None': - node_defaults[key] = val - - for node in graph: - for (key, val) in node_defaults.items(): - if key not in graph.node[node]: - graph.node[node][key] = val - - ank_edge_defaults = settings['Graphml']['Edge Defaults'] - edge_defaults = graph.graph.get('edge_default', {}) - for (key, val) in ank_edge_defaults.items(): - if key not in edge_defaults or edge_defaults[key] == 'None': - edge_defaults[key] = val - - for src, dst, data in graph.edges(data=True): - for key, val in edge_defaults.items(): - if key not in data: - data[key] = val - - graph.graph['address_family'] = 'v4' - graph.graph['enable_routing'] = True - - #TODO: move out of defaults boolean, and try/catch - for node in sorted(graph): - graph.node[node]['asn'] = int(graph.node[node]['asn']) - - # TODO: set default x/y if not set - or else json export will do it - graph.graph['file_type'] = 'json' - - return graph diff --git a/autonetkit/log.py b/autonetkit/log.py deleted file mode 100644 index e0199eaa..00000000 --- a/autonetkit/log.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -import logging.handlers -import autonetkit.config as config - -class CustomAdapter(logging.LoggerAdapter): - def process(self, msg, kwargs): - return '[%s]: %s' % (self.extra['item'], msg), kwargs - - -ank_logger = logging.getLogger("ANK") -if not ank_logger.handlers: - console_formatter = logging.Formatter("%(levelname)-1s %(message)s") - ch = logging.StreamHandler() - #ch.setLevel(logging.INFO) - ch.setFormatter(console_formatter) - ch.setLevel(logging.DEBUG) - ank_logger.addHandler(ch) - - file_logging = config.settings['Logging']['file'] - if file_logging: - LOG_FILENAME = "autonetkit.log" - #fh = logging.FileHandler(LOG_FILENAME) - LOG_SIZE = 2097152 # 2 MB - fh = logging.handlers.RotatingFileHandler( - LOG_FILENAME, maxBytes=LOG_SIZE, backupCount=5) - fh.setLevel(logging.DEBUG) - formatter = logging.Formatter("%(asctime)s %(levelname)s " - "%(funcName)s %(message)s") - fh.setFormatter(formatter) - ank_logger.addHandler(fh) - -ank_logger.setLevel(logging.INFO) -# Reference for external access -logger = ank_logger -# Use approach of Pika, allows for autonetkit.log.debug("message") -debug = logger.debug -error = logger.error -info = logger.info -warning = logger.warning -exception = logger.exception -critical = logger.critical diff --git a/autonetkit/log2.py b/autonetkit/log2.py deleted file mode 100644 index c741871e..00000000 --- a/autonetkit/log2.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging -import logging.handlers -import autonetkit.config as config - -ank_logger = logging.getLogger("ANK2") -if not ank_logger.handlers: - console_formatter = logging.Formatter("%(levelname)-1s %(type)s %(id)s %(message)s") - ch = logging.StreamHandler() - #ch.setLevel(logging.INFO) - ch.setFormatter(console_formatter) - ch.setLevel(logging.INFO) - ank_logger.addHandler(ch) - - - -ank_logger.setLevel(logging.INFO) -# Reference for external access -logger = ank_logger -# Use approach of Pika, allows for autonetkit.log.debug("message") -debug = logger.debug -error = logger.error -info = logger.info -warning = logger.warning -exception = logger.exception -critical = logger.critical diff --git a/autonetkit/nidb/__init__.py b/autonetkit/nidb/__init__.py deleted file mode 100644 index 7841dbfc..00000000 --- a/autonetkit/nidb/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from autonetkit.nidb.device_model import DeviceModel as DeviceModel -from autonetkit.nidb.node import DmNode as DmNode -from autonetkit.nidb.edge import DmEdge as DmEdge -from autonetkit.nidb.interface import DmInterface as DmInterface -from autonetkit.nidb.config_stanza import ConfigStanza as ConfigStanza \ No newline at end of file diff --git a/autonetkit/nidb/base.py b/autonetkit/nidb/base.py deleted file mode 100644 index 29165811..00000000 --- a/autonetkit/nidb/base.py +++ /dev/null @@ -1,339 +0,0 @@ -import autonetkit.log as log - -from autonetkit.nidb.interface import DmInterface -from autonetkit.nidb.edge import DmEdge -from autonetkit.nidb.node import DmNode -from autonetkit import ank_json - -# TODO: add to doc we don't llow bidirectional nidb - - -class DmBase(object): - # TODO: inherit common methods from same base as overlay - - def __init__(self): - # TODO: make optional for restore serialized file on init - self._graph = None - pass - - def __getstate__(self): - return self._graph - - def __setstate__(self, state): - self._graph = state - - def __repr__(self): - return "nidb" - - def is_multigraph(self): - return self._graph.is_multigraph() - - # Model-level functions - - def save(self, timestamp=True, use_gzip=True): - import os - import gzip - archive_dir = os.path.join("versions", "nidb") - if not os.path.isdir(archive_dir): - os.makedirs(archive_dir) - - data = ank_json.ank_json_dumps(self._graph) - if timestamp: - json_file = "nidb_%s.json.gz" % self.timestamp - else: - json_file = "nidb.json" - json_path = os.path.join(archive_dir, json_file) - log.debug("Saving to %s" % json_path) - if use_gzip: - with gzip.open(json_path, "wb") as json_fh: - json_fh.write(data) - else: - with open(json_path, "wb") as json_fh: - json_fh.write(data) - - def interface(self, interface): - return DmInterface(self, - interface.node_id, interface.interface_id) - - def restore_latest(self, directory=None): - import os - import glob - if not directory: - # TODO: make directory loaded from config - directory = os.path.join("versions", "nidb") - - glob_dir = os.path.join(directory, "*.json.gz") - pickle_files = glob.glob(glob_dir) - pickle_files = sorted(pickle_files) - try: - latest_file = pickle_files[-1] - except IndexError: - # No files loaded - log.warning( - "No previous DeviceModel saved. Please compile new DeviceModel") - return - self.restore(latest_file) - ank_json.rebind_nidb_interfaces(self) - - def restore(self, pickle_file): - import gzip - log.debug("Restoring %s" % pickle_file) - with gzip.open(pickle_file, "r") as fh: - #data = json.load(fh) - data = fh.read() - self._graph = ank_json.ank_json_loads(data) - - ank_json.rebind_nidb_interfaces(self) - - @property - def name(self): - return self.__repr__() - - def raw_graph(self): - """Returns the underlying NetworkX graph""" - return self._graph - - def copy_graphics(self, network_model): - """Transfers graphics data from anm to nidb""" - for node in self: - node.add_stanza("graphics") - #TODO: deprecate g_graphics overlay, store on phy - if (network_model.has_overlay("graphics") - and node in network_model['graphics']): - source_node = network_model['graphics'].node(node) - else: - source_node = network_model['phy'].node(node) - node.graphics.x = source_node.x - node.graphics.y = source_node.y - node.graphics.device_type = source_node.device_type - node.graphics.device_subtype = source_node.device_subtype - node.device_type = source_node.device_type - node.device_subtype = source_node.device_subtype - - def __len__(self): - return len(self._graph) - - @property - def data(self): - from autonetkit.nidb.device_model import DmGraphData - return DmGraphData(self) - - # Nodes - - def __iter__(self): - return iter(DmNode(self, node) - for node in self._graph) - - def node(self, key): - """Returns node based on name - This is currently O(N). Could use a lookup table""" - try: - if key.node_id in self._graph: - return DmNode(self, key.node_id) - except AttributeError: - # doesn't have node_id, likely a label string, search on label - for node in self: - if str(node) == key: - return node - elif node.id == key: - # label could be "a b" -> "a_b" (ie folder safe, etc) - # TODO: need to fix this discrepancy - return node - print "Unable to find node", key, "in", self - return None - - def update(self, nbunch, **kwargs): - for node in nbunch: - for (_, key), value in kwargs.items(): - node.category.set(key, value) - - def nodes(self, *args, **kwargs): - result = self.__iter__() - if len(args) or len(kwargs): - result = self.filter(result, *args, **kwargs) - return result - - def routers(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be router""" - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_router()] - - def switches(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be switch""" - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_switch()] - - def servers(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be server""" - - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_server()] - - def l3devices(self, *args, **kwargs): - """Shortcut for nodes(), sets device_type to be server""" - result = self.nodes(*args, **kwargs) - return [r for r in result if r.is_l3device()] - - def filter(self, nbunch=None, *args, **kwargs): - # TODO: also allow nbunch to be passed in to subfilter on...? - """TODO: expand this to allow args also, ie to test if value evaluates to True""" - # need to allow filter_func to access these args - if not nbunch: - nbunch = self.nodes() - - def filter_func(node): - return ( - all(getattr(node, key) for key in args) and - all(getattr(node, key) == val for key, val in kwargs.items()) - ) - - return (n for n in nbunch if filter_func(n)) - - def add_nodes_from(self, nbunch, retain=None, **kwargs): - if retain is None: - retain = [] - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - nbunch = list(nbunch) - nodes_to_add = nbunch # retain for interface copying - - if len(retain): - add_nodes = [] - for n in nbunch: - data = dict((key, n.get(key)) for key in retain) - add_nodes.append((n.node_id, data)) - nbunch = add_nodes - else: - log.warning( - "Cannot add node ids directly to DeviceModel: must add overlay nodes") - self._graph.add_nodes_from(nbunch, **kwargs) - - for node in nodes_to_add: - # TODO: add an interface_retain for attributes also - int_dict = {i.interface_id: {'category': i.category, - 'description': i.description, - 'layer': i.overlay_id} for i in node.interfaces()} - int_dict = {i.interface_id: {'category': i.category, - 'description': i.description, - } for i in node.interfaces()} - self._graph.node[node.node_id]["_ports"] = int_dict - - # Edges - - def edges(self, nbunch=None, *args, **kwargs): - # nbunch may be single node - # TODO: Apply edge filters - if nbunch: - try: - nbunch = nbunch.node_id - except AttributeError: - # only store the id in overlay - nbunch = (n.node_id for n in nbunch) - - def filter_func(edge): - return ( - all(getattr(edge, key) for key in args) and - all(getattr(edge, key) == val for key, val in kwargs.items()) - ) - - # TODO: See if more efficient way to access underlying data structure - # rather than create overlay to throw away - if self.is_multigraph(): - valid_edges = list((src, dst, key) for (src, dst, key) in - self._graph.edges(nbunch, keys=True)) - else: - default_key = 0 - valid_edges = list((src, dst, default_key) for (src, dst) in - self._graph.edges(nbunch)) - - all_edges = [DmEdge(self, src, dst, key) - for src, dst, key in valid_edges] - return (edge for edge in all_edges if filter_func(edge)) - - def edge(self, edge_to_find, key=0): - """returns edge in this graph with same src and dst""" - # TODO: check if this even needed - will be if searching nidb specifically - # but that's so rare (that's a design stage if anywhere) - src_id = edge_to_find.src - dst_id = edge_to_find.dst - - if self.is_multigraph(): - for (src, dst, rkey) in self._graph.edges(src_id, keys=True): - if dst == dst_id and rkey == search_key: - return DmEdge(self._anm, src, dst, search_key) - - for (src, dst) in self._graph.edges(src_id): - if dst == dst_id: - return DmEdge(self._anm, src, dst) - - def add_edge(self, src, dst, retain=None, **kwargs): - # TODO: support multigraph - if retain is None: - retain = [] - self.add_edges_from([(src, dst)], retain, **kwargs) - - def add_edges_from(self, ebunch, retain=None, **kwargs): - """Used to copy edges from ANM -> NIDB - Note: won't support (yet) copying from one NIDB to another - #TODO: allow copying from one NIDB to another - (check for DmNode as well as NmNode) - - To keep congruency, only allow copying edges from ANM - can't add NIDB edges directly (node, node) oor (port, port) - workflow: if need this, create a new overlay and copy from there - """ - from autonetkit.anm import NmEdge - if not retain: - retain = [] - try: - retain.lower() - retain = [retain] # was a string, put into list - except AttributeError: - pass # already a list - - # TODO: this needs to support parallel links - for in_edge in ebunch: - """Edge could be one of: - - NmEdge - copied into be returned as a DmEdge - """ - # This is less efficient than nx add_edges_from, but cleaner logic - # TODO: could put the interface data into retain? - data = {'_ports': {}} # to retain - ekey = 0 - - # convert input to a NmEdge - if isinstance(in_edge, NmEdge): - edge = in_edge # simple case - ekey = edge.ekey - src = edge.src.node_id - dst = edge.dst.node_id - - # and copy retain data - retain.append('_ports') - # TODO: explicity copy ports as raw_interfaces? - data = dict((key, edge.get(key)) for key in retain) - - # this is the only case where copy across data - # but want to copy attributes for all cases - - # TODO: add check that edge.src and edge.dst exist - if not(src in self and dst in self): - log.warning("Not adding edge, %s to %s, " - "src and/or dst not in overlay %s" % (src, dst, self)) - continue - - # TODO: warn if not multigraph and edge already exists - don't - # add/clobber - data.update(**kwargs) - - if self.is_multigraph(): - self._graph.add_edge(src, dst, key=ekey, - attr_dict=dict(data)) - else: - self._graph.add_edge(src, dst, attr_dict=dict(data)) diff --git a/autonetkit/nidb/config_stanza.py b/autonetkit/nidb/config_stanza.py deleted file mode 100644 index 24dc0d3f..00000000 --- a/autonetkit/nidb/config_stanza.py +++ /dev/null @@ -1,81 +0,0 @@ -from collections import OrderedDict - -import autonetkit.log as log - -# based on docs.python.org/2.7/library/collections -# and http://stackoverflow.com/q/455059 - - -class ConfigStanza(object): - - def __init__(self, *args, **kwargs): - if len(args) == 1 and isinstance(args[0], ConfigStanza): - # Clone the data (shallow copy) - # TODO: check how this relates to calling dict() on a dict - same? - in_dict = args[0]._odict - object.__setattr__(self, '_odict', OrderedDict(in_dict)) - return - - if len(args) == 1 and isinstance(args[0], dict): - # Clone the data (shallow copy) - in_dict = args[0] - object.__setattr__(self, '_odict', OrderedDict(in_dict)) - return - - object.__setattr__(self, '_odict', OrderedDict(kwargs)) - - def __repr__(self): - return str(self._odict.items()) - - def to_json(self): - retval = OrderedDict(self._odict) # clone to append to - retval['_ConfigStanza'] = True - return retval - - def add_stanza(self, name, **kwargs): - """Adds a sub-stanza to this stanza""" - stanza = ConfigStanza(**kwargs) - self[name] = stanza - return stanza - - def __getitem__(self, key): - return self._odict[key] - - def __delitem__(self, key): - del self._odict[key] - - def __setitem__(self, key, value): - if isinstance(value, dict): - log.warning( - "Adding dictionary %s: did you mean ", - "to add a ConfigStanza?" % key) - self._odict[key] = value - - def __setattr__(self, key, value): - self._odict[key] = value - - def __getattr__(self, key): - # TODO: decide how to return misses - try: - return self._odict[key] - except KeyError: - # TODO: implement warning here - # TODO: log a key miss, and if strict turned on, give warning - return - - def items(self): - # TODO map this to proper dict inherit to - # support these methods, keys, etc - return self._odict.items() - - def __iter__(self): - return iter(self._odict.items()) - - def __len__(self): - return len(self._odict) - - # TODO: add __iter__, __keys etc - - #self.__dict__['_odict'][key] = value - - # TODO: add a sort function that sorts the OrderedDict diff --git a/autonetkit/nidb/device_model.py b/autonetkit/nidb/device_model.py deleted file mode 100644 index d458682f..00000000 --- a/autonetkit/nidb/device_model.py +++ /dev/null @@ -1,137 +0,0 @@ -import collections - -import time - -import networkx as nx - -from autonetkit.nidb.edge import DmEdge -from autonetkit.nidb.node import DmNode -from autonetkit.nidb.base import DmBase - - -class DmGraphData(object): - - """API to access overlay graph data in network""" - - def __init__(self, nidb): - # Set using this method to bypass __setattr__ - object.__setattr__(self, 'nidb', nidb) - - def __repr__(self): - return "DeviceModel data: %s" % self.nidb.raw_graph().graph - - def __getattr__(self, key): - """Returns edge property""" - return self.nidb.raw_graph().graph.get(key) - - def __setattr__(self, key, val): - """Sets edge property""" - self.nidb.raw_graph().graph[key] = val - -# TODO: make this inherit same overlay base as overlay_graph for add nodes etc properties -# but not the degree etc - - -class DmLabTopology(object): - - """API to access lab topology in network""" - # TODO: replace this with ConfigStanza - - def __init__(self, nidb, topology_id): - # Set using this method to bypass __setattr__ - object.__setattr__(self, 'nidb', nidb) - object.__setattr__(self, 'topology_id', topology_id) - - def __repr__(self): - return "Lab Topology: %s" % self.topology_id - - @property - def _topology_data(self): - return self.nidb.raw_graph().graph['topologies'][self.topology_id] - - def dump(self): - return str(self._topology_data) - - def __getattr__(self, key): - """Returns topology property""" - data = self._topology_data.get(key) - return data - - def __setattr__(self, key, val): - """Sets topology property""" - self._topology_data[key] = val - - def set(self, key, val): - """For consistency, topology.set(key, value) is neater - than setattr(topology, key, value)""" - return self.__setattr__(key, val) - - -class DeviceModel(DmBase): - - def __init__(self, network_model=None): - super(DeviceModel, self).__init__() - # only for connectivity, any other information stored on node - if network_model and network_model['phy'].is_multigraph: - self._graph = nx.MultiGraph() - else: - self._graph = nx.Graph() - - self._graph.graph['topologies'] = collections.defaultdict(dict) - self._graph.graph['timestamp'] = time.strftime( - "%Y%m%d_%H%M%S", time.localtime()) - - if network_model: - self._build_from_anm(network_model) - - def _build_from_anm(self, network_model): - # TODO: Allow to specify which attributes to copy across - # TODO: provide another function to copy across attributes - # post-creation - g_phy = network_model['phy'] - g_graphics = network_model['graphics'] - self.add_nodes_from(g_phy, retain=['label', 'host', 'platform', - "syntax", 'Network', 'update', 'asn', ]) - - self.add_edges_from(g_phy.edges()) - self.copy_graphics(network_model) - - def topology(self, key): - return DmLabTopology(self, key) - - def topologies(self): - return iter(DmLabTopology(self, key) - for key in self._graph.graph['topologies'].keys()) - - @property - def timestamp(self): - return self._graph.graph['timestamp'] - - def subgraph(self, nbunch, name=None): - nbunch = (n.node_id for n in nbunch) # only store the id in overlay - return DmSubgraph(self._graph.subgraph(nbunch), name) - - def boundary_nodes(self, nbunch, nbunch2=None): - nbunch = (n.node_id for n in nbunch) # only store the id in overlay - return iter(DmNode(self, node) - for node in nx.node_boundary(self._graph, - nbunch, nbunch2)) - - def boundary_edges(self, nbunch, nbunch2=None): - nbunch = (n.node_id for n in nbunch) # only store the id in overlay - return iter(DmEdge(self, src, dst) - for (src, dst) in nx.edge_boundary(self._graph, - nbunch, nbunch2)) - - -class DmSubgraph(DmBase): - - def __init__(self, graph, name=None): - super(DmSubgraph, self).__init__() - # TODO: need to refer back to the source nidb - # only for connectivity, any other information stored on node - self._graph = graph - self._name = name - - def __repr__(self): - return "nidb: %s" % self._name diff --git a/autonetkit/nidb/edge.py b/autonetkit/nidb/edge.py deleted file mode 100644 index 3ae9cbe4..00000000 --- a/autonetkit/nidb/edge.py +++ /dev/null @@ -1,144 +0,0 @@ -import logging -from autonetkit.nidb.node import DmNode -from autonetkit.nidb.interface import DmInterface -from autonetkit.log import CustomAdapter -import autonetkit.log as log - - -class DmEdge(object): - - """API to access edge in nidb""" - - def __init__(self, nidb, src_id, dst_id, ekey=0): - # Set using this method to bypass __setattr__ - object.__setattr__(self, 'nidb', nidb) - object.__setattr__(self, 'src_id', src_id) - object.__setattr__(self, 'dst_id', dst_id) - object.__setattr__(self, 'ekey', ekey) # for multigraphs - #logger = logging.getLogger("ANK") - #logstring = "Edge: %s" % str(self) - #self.log = CustomAdapter(logger, {'item': logstring}) - logger = log - object.__setattr__(self, 'log', logger) - - def __repr__(self): - if self.is_multigraph(): - return '(%s, %s, %s)' % (self.src, - self.dst, self.ekey) - - return '(%s, %s)' % (self.src, self.dst) - - @property - def raw_interfaces(self): - """Direct access to the interfaces dictionary, used by ANK modules""" - return self._ports - - @raw_interfaces.setter - def raw_interfaces(self, value): - self._ports = value - - def is_multigraph(self): - return self._graph.is_multigraph() - - @property - def src(self): - return DmNode(self.nidb, self.src_id) - - @property - def src_int(self): - src_int_id = self._ports[self.src_id] - return DmInterface(self.nidb, self.src_id, src_int_id) - - @property - def dst_int(self): - dst_int_id = self._ports[self.dst_id] - return DmInterface(self.nidb, self.dst_id, dst_int_id) - - def __eq__(self, other): - """""" - if self.is_multigraph(): - try: - if other.is_multigraph(): - return (self.src_id, self.dst_id, self.ekey) == (other.src_id, - other.dst_id, other.ekey) - else: - # multi, single - return (self.src_id, self.dst_id) == (other.src_id, - other.dst_id) - - except AttributeError: - if len(other) == 2: - # (src, dst) - return (self.src_id, self.dst_id) == other - elif len(other) == 3: - # (src, dst, key) - return (self.src_id, self.dst_id, self.ekey) == other - - try: - # self is single, other is single or multi -> only compare (src, - # dst) - return (self.src_id, self.dst_id) == (other.src_id, other.dst_id) - except AttributeError: - # compare to strings - return (self.src_id, self.dst_id) == other - - def __lt__(self, other): - """""" - if self.is_multigraph() and other.is_multigraph(): - return (self.src.node_id, self.dst.node_id, self.ekey) \ - < (other.src.node_id, other.dst.node_id, other.ekey) - - return (self.src.node_id, self.dst.node_id) \ - < (other.src.node_id, other.dst.node_id) - - # Internal properties - def __nonzero__(self): - """Allows for checking if edge exists - """ - if self.is_multigraph(): - return self._graph.has_edge(self.src_id, self.dst_id, - key=self.ekey) - - return self._graph.has_edge(self.src_id, self.dst_id) - - @property - def dst(self): - return DmNode(self.nidb, self.dst_id) - - def dump(self): - return str(self._graph[self.src_id][self.dst_id]) - - def __nonzero__(self): - """Allows for checking if edge exists - """ - try: - # TODO: refactor to be _graph.has_edge(src, dst) - _ = self._graph[self.src_id][self.dst_id] - return True - except KeyError: - return False - - @property - def _data(self): - """Return data the node belongs to""" - if self.is_multigraph(): - return self._graph[self.src_id][self.dst_id][self.ekey] - - return self._graph[self.src_id][self.dst_id] - - @property - def _graph(self): - """Return graph the node belongs to""" - return self.nidb.raw_graph() - - def get(self, key): - """For consistency, edge.get(key) is neater than getattr(edge, key)""" - return self.__getattr__(key) - - def __getattr__(self, key): - """Returns edge property""" - return self._data.get(key) - - def __setattr__(self, key, val): - """Sets edge property""" - self._data[key] = val diff --git a/autonetkit/nidb/interface.py b/autonetkit/nidb/interface.py deleted file mode 100644 index 1dc36302..00000000 --- a/autonetkit/nidb/interface.py +++ /dev/null @@ -1,190 +0,0 @@ -import collections -import logging -from autonetkit.log import CustomAdapter -import autonetkit.log as log - - -class DmInterface(object): - - def __init__(self, nidb, node_id, interface_id): - object.__setattr__(self, 'nidb', nidb) - object.__setattr__(self, 'node_id', node_id) - object.__setattr__(self, 'interface_id', interface_id) - #logger = logging.getLogger("ANK") - #logstring = "Interface: %s" % str(self) - #self.log = CustomAdapter(logger, {'item': logstring}) - logger = log - object.__setattr__(self, 'log', logger) - - def __key(self): - # based on http://stackoverflow.com/q/2909106 - return (self.interface_id, self.node_id) - - def __hash__(self): - """""" - return hash(self.__key()) - - def __eq__(self, other): - return self.__key() == other.__key() - - @property - def is_bound(self): - """Returns if this interface is bound to an edge on this layer""" - return len(self.edges()) > 0 - - def __repr__(self): - description = self.description or self.interface_id - return "%s.%s" % (self.node, description) - - def __nonzero__(self): - """Allows for checking if node exists - """ - return len(self._port) > 0 # if interface data set - - def __str__(self): - return self.__repr__() - - @property - def _graph(self): - """Return graph the node belongs to""" - return self.nidb.raw_graph() - - @property - def _node(self): - """Return node the node belongs to""" - return self._graph.node[self.node_id] - - @property - def _port(self): - """Return graph the node belongs to""" - return self._node["_ports"][self.interface_id] - - @property - def is_loopback(self): - """""" - return self.category == "loopback" or self.phy.category == "loopback" - - @property - def is_physical(self): - """""" - return self.category == "physical" or self.phy.category == "physical" - - @property - def description(self): - """""" - return self._port.get("description") - - @property - def is_loopback_zero(self): - # by convention, loopback_zero is at id 0 - return self.interface_id == 0 and self.is_loopback - - @property - def node(self): - """Returns parent node of this interface""" - from autonetkit.nidb import DmNode - return DmNode(self.nidb, self.node_id) - - def dump(self): - return str(self._port.items()) - - def dict(self): - """Returns shallow copy of dictionary used. - Note not a deep copy: modifying values may have impact""" - return dict(self._port.items()) - - def __getattr__(self, key): - """Returns interface property""" - try: - data = self._port.get(key) - except KeyError: - return - - if isinstance(data, dict): - # TODO: use config stanza instead? - return InterfaceDataDict(data) - - return data - - def get(self, key): - """For consistency, node.get(key) is neater - than getattr(interface, key)""" - return getattr(self, key) - - def __setattr__(self, key, val): - """Sets interface property""" - try: - self._port[key] = val - except KeyError: - self.set(key, val) - - def set(self, key, val): - """For consistency, node.set(key, value) is neater - than setattr(interface, key, value)""" - return self.__setattr__(key, val) - - def edges(self): - """Returns all edges from node that have this interface ID - This is the convention for binding an edge to an interface""" - # edges have raw_interfaces stored as a dict of {node_id: interface_id, - # } - valid_edges = [e for e in self.node.edges() - if self.node_id in e.raw_interfaces - and e.raw_interfaces[self.node_id] == self.interface_id] - return valid_edges - - def neighbors(self): - """Returns interfaces on nodes that are linked to this interface - Can get nodes using [i.node for i in interface.neighbors()] - """ - edges = self.edges() - return [e.dst_int for e in edges] - - -class InterfaceDataDict(collections.MutableMapping): - # TODO: replace with config stanza - - """A dictionary which allows access as dict.key as well as dict['key'] - Based on http://stackoverflow.com/questions/3387691 - only allows read only acess - """ - - def __repr__(self): - return ", ".join(self.store.keys()) - - def __init__(self, data): - # Note this won't allow updates in place - self.store = data - #self.data = parent[index] - # self.update(dict(*args, **kwargs)) # use the free update to set keys -# TODO: remove duplicate of self.store and parent - - def __getitem__(self, key): - return self.store[self.__keytransform__(key)] - - def __setitem__(self, key, value): - self.store[key] = value # store locally - - def __delitem__(self, key): - del self.store[self.__keytransform__(key)] - - def __iter__(self): - return iter(self.store) - - def __len__(self): - return len(self.store) - - def __keytransform__(self, key): - return key - - def __getattr__(self, key): - return self.store.get(key) - - def __setattr__(self, key, value): - if key == "store": - object.__setattr__(self, 'store', value) - else: - self.store[key] = value # store locally - - def dump(self): - return self.store diff --git a/autonetkit/nidb/node.py b/autonetkit/nidb/node.py deleted file mode 100644 index 8262bf06..00000000 --- a/autonetkit/nidb/node.py +++ /dev/null @@ -1,284 +0,0 @@ -import functools -import logging -import string - -import autonetkit.log as log -from autonetkit.log import CustomAdapter -from autonetkit.nidb.config_stanza import ConfigStanza -from autonetkit.nidb.interface import DmInterface -import autonetkit.log as log - - -@functools.total_ordering -class DmNode(object): - - """API to access overlay graph node in network""" - - def __init__(self, nidb, node_id): - # Set using this method to bypass __setattr__ - object.__setattr__(self, 'nidb', nidb) - object.__setattr__(self, 'node_id', node_id) - #logger = logging.getLogger("ANK") - # TODO: also pass the node object to the logger for building custom output lists - # ie create a special handler that just outputs the specific node/link/interface errors - #logstring = "Node: %s" % str(self) - #logger = CustomAdapter(logger, {'item': logstring}) - logger = log - object.__setattr__(self, 'log', logger) - - # TODO: make a json objct that returns keys that aren't logs, etc - filter - # out - - def __repr__(self): - return self._node_data['label'] - - # TODO: add a dump method - needed with str()? - - def add_stanza(self, name, **kwargs): - # TODO: decide if want shortcut for *args to set to True - if self.get(name): - value = self.get(name) - if isinstance(value, ConfigStanza): - # Don't recreate - self.log.debug("Stanza %s already exists" % name) - return value - else: - # TODO: remove? - as shouldn't reach here now? GH-186 - log.warning( - "Creating stanza: %s already set as %s for %s" % (name, type(value), self)) - - stanza = ConfigStanza(**kwargs) - self.__setattr__(name, stanza) - return stanza - - def __hash__(self): - return hash(self.node_id) - - def __eq__(self, other): - try: - return self.node_id == other.node_id - except AttributeError: - # TODO: check why comparing against strings - if in overlay - # graph... - return self.node_id == other - - def interface(self, key): - # TODO: also need to allow access interface for nidb and search on - # (node, interface id) tuple - try: - interface_id = key.interface_id # eg extract from interface - except AttributeError: - interface_id = key # eg string - - return DmInterface(self.nidb, self.node_id, interface_id) - - @property - def _ports(self): - """Returns underlying interface dict""" - try: - return self._graph.node[self.node_id]["_ports"] - except KeyError: - log.debug("No interfaces initialised for %s" % self) - return - - @property - def _next_int_id(self): - """""" -# returns next free interface ID - import itertools - for int_id in itertools.count(1): # start at 1 as 0 is loopback - if int_id not in self._ports: - return int_id - - def add_interface(self, description=None, category="physical", *args, **kwargs): - """Public function to add interface""" - data = dict(kwargs) - interface_id = self._next_int_id - data['category'] = category # store category on node - data['description'] = description - self._ports[interface_id] = data - - return DmInterface(self.nidb, self.node_id, interface_id) - - @property - def _interface_ids(self): - return self._ports.keys() - - @property - def interfaces(self): - # TODO: make not a property - """Called by templates, sorts by ID""" - int_list = self.get_interfaces() - - # Put loopbacks before physical interfaces - type_index = {"loopback": 0, "physical": 1} - # TODO: extend this based on medium category, etc - - int_list = sorted(int_list, key=lambda x: x.id) - int_list = sorted(int_list, key=lambda x: type_index[x.category]) - return int_list - - def physical_interfaces(self): - return self.get_interfaces(category="physical") - - def loopback_interfaces(self): - return self.get_interfaces(category="loopback") - - def get_interfaces(self, *args, **kwargs): - """Public function to view interfaces - - Temporary function name until Compiler/DeviceModel/Templates - move to using "proper" interfaces""" - def filter_func(interface): - """Filter based on args and kwargs""" - return ( - all(getattr(interface, key) for key in args) and - all(getattr( - interface, key) == val for key, val in kwargs.items()) - ) - - all_interfaces = iter(DmInterface(self.nidb, - self.node_id, interface_id) - for interface_id in self._interface_ids) - retval = (i for i in all_interfaces if filter_func(i)) - return retval - - @property - def loopback_zero(self): - return (i for i in self.interfaces if i.is_loopback_zero).next() - - @property - def raw_interfaces(self): - """Direct access to the interfaces dictionary, used by ANK modules""" - return self._ports - - @raw_interfaces.setter - def raw_interfaces(self, value): - self._ports = value - - @property - def _graph(self): - return self.nidb.raw_graph() - - def degree(self): - return self._graph.degree(self.node_id) - - def neighbors(self): - return iter(DmNode(self.nidb, node) - for node in self._graph.neighbors(self.node_id)) - - def __setstate__(self, state): - (nidb, node_id) = state - object.__setattr__(self, 'nidb', nidb) - object.__setattr__(self, 'node_id', node_id) - - def __lt__(self, other): - # TODO: use human sort from StackOverflow - - # sort on label if available - if self.label is not None: - self_node_id = self.label - else: - self_node_id = self.node_id - - if other.label is not None: - other_node_id = other.label - else: - other_node_id = other_node_id - - try: - self_node_string = [x for x in self_node_id if x - not in string.digits] - other_node_string = [x for x in self_node_id if x - not in string.digits] - except TypeError: - - # e.g. non-iterable type, such as an int node_id - - pass - else: - if self_node_string == other_node_string: - self_node_id = """""".join([x for x in self_node_id - if x in string.digits]) - other_node_id = """""".join([x for x in other_node_id - if x in string.digits]) - try: - self_node_id = int(self_node_id) - except ValueError: - pass # not a number - try: - other_node_id = int(other_node_id) - except ValueError: - pass # not a number - - return (self.asn, self_node_id) < (other.asn, other_node_id) - - @property - def _node_data(self): - return self.nidb.raw_graph().node[self.node_id] - - def dump(self): - # return str(self._node_data) - import pprint - pprint.pprint(self._node_data) - - def __nonzero__(self): - return self.node_id in self.nidb.raw_graph() - - def is_router(self): - return self.device_type == "router" - - def is_device_type(self, device_type): - """Generic user-defined cross-overlay search for device_type for consistency with ANM""" - return self.device_type == device_type - - def is_switch(self): - return self.device_type == "switch" - - def is_server(self): - return self.device_type == "server" - - def is_l3device(self): - """Layer 3 devices: router, server, cloud, host - ie not switch - """ - # TODO: need to check for cloud, host - return self.is_router() or self.is_server() - - def edges(self, *args, **kwargs): - # TODO: want to add filter for *args and **kwargs here too - return self.nidb.edges(self, *args, **kwargs) - - @property - def id(self): - return self.node_id - - @property - def label(self): - return self.__repr__() - - def get(self, key): - return getattr(self, key) - - def __getattr__(self, key): - """Returns edge property""" - data = self._node_data.get(key) - - # TODO: remove once deprecated DmNode_category - if isinstance(data, ConfigStanza): - return data - - return data - - def __setattr__(self, key, val): - """Sets edge property""" - self._node_data[key] = val - # return DmNode_category(self.nidb, self.node_id, key) - - def __iter__(self): - return iter(self._node_data) - - def set(self, key, val): - """For consistency, node.set(key, value) is neater - than setattr(node, key, value)""" - return self.__setattr__(key, val) diff --git a/autonetkit/plugins/__init__.py b/autonetkit/plugins/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/plugins/ipv4.py b/autonetkit/plugins/ipv4.py deleted file mode 100644 index 7fbb7eca..00000000 --- a/autonetkit/plugins/ipv4.py +++ /dev/null @@ -1,687 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import itertools -import math -import time -from collections import defaultdict - -import autonetkit.ank as ank_utils -import autonetkit.ank_json -import autonetkit.ank_messaging -import autonetkit.log as log -import netaddr -import networkx as nx -from autonetkit.exception import AutoNetkitException - -# TODO: allow slack in allocations: both for ASN (group level), and for -# collision domains to allow new nodes to be easily added - -try: - import cPickle as pickle -except ImportError: - import pickle - - -def subnet_size(host_count): - """Returns subnet size""" - - host_count += 2 # network and broadcast - return int(math.ceil(math.log(host_count, 2))) - - -class TreeNode(object): - - def __init__(self, graph, node): - object.__setattr__(self, 'graph', graph) - object.__setattr__(self, 'node', node) - -# TODO: make thise fixed attributes, as only certain number needed here - - def __getattr__(self, attr): - return self.graph.node[self.node].get(attr) - - def __setattr__(self, key, val): - self.graph.node[self.node][key] = val - - def __lt__(self, other): - if self.host and other.host: - return self.host < other.host - return self.node < other.node - -# TODO: restore function that truncated subnets - - def __repr__(self): - if self.host: - return '%s %s' % (self.subnet, self.host) - if self.loopback_group: - return 'Lo Gr %s: %s' % (self.group_attr, self.subnet) - if self.group_attr: - return '%s: %s' % (self.group_attr, self.subnet) - if self.subnet: - return '%s' % self.subnet - return 'TreeNode: %s' % self.node - - def is_broadcast_domain(self): - return self.host and self.host.broadcast_domain - - def is_loopback_group(self): - return self.loopback_group - - def is_interface(self): - return isinstance(self.host, autonetkit.anm.NmPort) - - def is_host(self): - return bool(self.host) - - def children(self): - return [TreeNode(self.graph, child) for child in - self.graph.successors(self.node)] - - -class IpTree(object): - - def __init__(self, root_ip_block): - self.unallocated_nodes = [] - self.graph = nx.DiGraph() - self.root_node = None - self.timestamp = time.strftime('%Y%m%d_%H%M%S', - time.localtime()) - -# taken_nodes -> these are nodes manually specified, eg in graphml - - self.node_id_counter = (i for i in itertools.count(0) if i - not in self.graph) - self.root_ip_block = root_ip_block - - def __len__(self): - return len(self.graph) - - def __iter__(self): - return iter(TreeNode(self.graph, node) for node in self.graph) - - @property - def next_node_id(self): - return self.node_id_counter.next() - - def add_parent_nodes(self, subgraph, level_counts): - for level in range(32, 0, -1): - try: - # float so do floating point division - current_count = float(level_counts[level]) - except KeyError: - continue # key not present - likely higher up tree - parent_count = int(math.ceil(current_count / 2)) - parent_level = level - 1 - level_counts[parent_level] += parent_count - subgraph.add_nodes_from((self.next_node_id, - {'prefixlen': parent_level}) - for n in range(parent_count)) - - if level_counts[parent_level] == 1: - if parent_level == min(level_counts.keys()): - children = [n for n in subgraph - if subgraph.node[n]['prefixlen'] - == parent_level + 1] - if all('host' in subgraph.node[n] for n in - children): - subgraph.add_node(self.next_node_id, - prefixlen=parent_level - 1) - - break # Reached top of tree - - def build_tree( - self, - subgraph, - level_counts, - nodes_by_level, - ): - smallest_prefix = min(level_counts.keys()) - for prefixlen in range(smallest_prefix, 32): - - # TODO: fix sorting here - - unallocated_children = set(nodes_by_level[prefixlen + 1]) - for node in sorted(nodes_by_level[prefixlen]): - is_not_subnet = not ('host' in subgraph.node[node] - or 'group_attr' in subgraph.node[node]) - if is_not_subnet: - child_a = unallocated_children.pop() - subgraph.add_edge(node, child_a) - try: - child_b = unallocated_children.pop() - subgraph.add_edge(node, child_b) - except KeyError: - pass # single child, just attach - - root_node = nodes_by_level[smallest_prefix][0] - return root_node - - def save(self): - import os - archive_dir = os.path.join('versions', 'ip') - if not os.path.isdir(archive_dir): - os.makedirs(archive_dir) - - self.graph.graph['timestamp'] = self.timestamp - data = autonetkit.ank_json.ank_json_dumps(self.graph) - -# TODO: should this use the ank_json.jsonify_nidb() ? - - json_file = 'ip_%s.json.gz' % self.timestamp - json_path = os.path.join(archive_dir, json_file) - log.debug('Saving to %s' % json_path) - - # with gzip.open(json_path, "wb") as json_fh: - - with open(json_path, 'wb') as json_fh: - json_fh.write(data) - - def build(self, group_attr='asn'): - """Builds tree from unallocated_nodes, - groupby is the attribute to build subtrees from""" - - subgraphs = [] - -# if network final octet is .0 eg 10.0.0.0 or 192.168.0.0, then add extra "dummy" node, so don't have a loopback of 10.0.0.0 -# Change strategy: if just hosts (ie loopbacks), then allocate as a large -# collision domain - - if not len(self.unallocated_nodes): - - # no nodes to allocate - eg could be no collision domains - - return - - unallocated_nodes = self.unallocated_nodes - key_func = lambda x: x.get(group_attr) - if all(isinstance(item, autonetkit.anm.NmPort) - and item.is_loopback for item in unallocated_nodes): - # interface, map key function to be the interface's node - key_func = lambda x: x.node.get(group_attr) - - unallocated_nodes = sorted(unallocated_nodes, key=key_func) - groupings = itertools.groupby(unallocated_nodes, key=key_func) - prefixes_by_attr = {} - - for (attr_value, items) in groupings: - - # make subtree for each attr - - items = sorted(list(items)) - subgraph = nx.DiGraph() - - if all(isinstance(item, autonetkit.anm.NmPort) - for item in items): - - # interface - - if all(item.is_loopback for item in items): - parent_id = self.next_node_id - # group all loopbacks into single subnet - prefixlen = 32 - subnet_size(len(items)) - subgraph.add_node(parent_id, prefixlen=prefixlen, - loopback_group=True) - for item in sorted(items): - - # subgraph.add_edge(node, child_a) - - item_id = self.next_node_id - subgraph.add_node(item_id, prefixlen=32, - host=item) - subgraph.add_edge(parent_id, item_id) - - root_node = parent_id - subgraphs.append(subgraph) - subgraph.graph['root'] = root_node - subgraph.node[root_node]['group_attr'] = attr_value - subgraph.node[root_node]['prefixlen'] = 24 - # finished for loopbacks, continue only for collision - # domains - continue - - if all(item.is_l3device() for item in items): - - # Note: only l3 devices are added for loopbacks: cds allocate - # to edges not devices (for now) - will be fixed when move to - # proper interface model - - parent_id = self.next_node_id - # group all loopbacks into single subnet - prefixlen = 32 - subnet_size(len(items)) - subgraph.add_node(parent_id, prefixlen=prefixlen, - loopback_group=True) - for item in sorted(items): - - # subgraph.add_edge(node, child_a) - - item_id = self.next_node_id - subgraph.add_node(item_id, prefixlen=32, host=item) - subgraph.add_edge(parent_id, item_id) - - root_node = parent_id - subgraphs.append(subgraph) - subgraph.graph['root'] = root_node - subgraph.node[root_node]['group_attr'] = attr_value - # finished for loopbacks, continue only for collision domains - continue - - for item in sorted(items): - if item.broadcast_domain: - subgraph.add_node(self.next_node_id, prefixlen=32 - - subnet_size(item.degree()), host=item) - if item.is_l3device(): - subgraph.add_node(self.next_node_id, prefixlen=32, - host=item) - - # now group by levels - - level_counts = defaultdict(int) - - nodes_by_level = defaultdict(list) - for node in subgraph.nodes(): - prefixlen = subgraph.node[node]['prefixlen'] - nodes_by_level[prefixlen].append(node) - - log.debug('Building IP subtree for %s %s' % (group_attr, - attr_value)) - - for (level, nodes) in nodes_by_level.items(): - level_counts[level] = len(nodes) - - self.add_parent_nodes(subgraph, level_counts) - -# test if min_level node is bound, if so then add a parent, so root for AS -# isn't a cd - - min_level = min(level_counts) - min_level_nodes = [n for n in subgraph - if subgraph.node[n]['prefixlen'] - == min_level] - - # test if bound - - if len(min_level_nodes) == 2: - subgraph.add_node(self.next_node_id, - {'prefixlen': min_level - 2}) - subgraph.add_node(self.next_node_id, - {'prefixlen': min_level - 2}) - subgraph.add_node(self.next_node_id, - {'prefixlen': min_level - 1}) - if len(min_level_nodes) == 1: - subgraph.add_node(self.next_node_id, - {'prefixlen': min_level - 1}) - - # rebuild with parent nodes - - nodes_by_level = defaultdict(list) - for node in sorted(subgraph.nodes()): - prefixlen = subgraph.node[node]['prefixlen'] - nodes_by_level[prefixlen].append(node) - - root_node = self.build_tree(subgraph, level_counts, - nodes_by_level) - subgraphs.append(subgraph) - - subgraph.graph['root'] = root_node - -# FOrce to be a /16 block -# TODO: document this - - subgraph.node[root_node]['prefixlen'] = 16 - subgraph.node[root_node]['group_attr'] = attr_value - prefixes_by_attr[attr_value] = subgraph.node[ - root_node]['prefixlen'] - - global_graph = nx.DiGraph() - subgraphs = sorted(subgraphs, key=lambda x: - subgraph.node[subgraph.graph['root' - ]]['group_attr']) - root_nodes = [subgraph.graph['root'] for subgraph in subgraphs] - root_nodes = [] - for subgraph in subgraphs: - root_node = subgraph.graph['root'] - root_nodes.append(root_node) - global_graph.add_node(root_node, subgraph.node[root_node]) - - nodes_by_level = defaultdict(list) - for node in root_nodes: - prefixlen = global_graph.node[node]['prefixlen'] - nodes_by_level[prefixlen].append(node) - - level_counts = defaultdict(int) - for (level, nodes) in nodes_by_level.items(): - level_counts[level] = len(nodes) - - self.add_parent_nodes(global_graph, level_counts) - -# rebuild nodes by level -# TODO: make this a function - - nodes_by_level = defaultdict(list) - for node in global_graph: - prefixlen = global_graph.node[node]['prefixlen'] - nodes_by_level[prefixlen].append(node) - - global_root = self.build_tree(global_graph, level_counts, - nodes_by_level) - global_root = TreeNode(global_graph, global_root) - - for subgraph in subgraphs: - global_graph = nx.compose(global_graph, subgraph) - - # now allocate the IPs - - global_prefix_len = global_root.prefixlen - - # TODO: try/catch if the block is too small for prefix - - try: - global_ip_block = \ - self.root_ip_block.subnet(global_prefix_len).next() - except StopIteration: - #message = ("Unable to allocate IPv4 subnets. ") - formatted_prefixes = ", ".join( - "AS%s: /%s" % (k, v) for k, v in sorted(prefixes_by_attr.items())) - message = ("Cannot create requested number of /%s subnets from root block %s. Please specify a larger root IP block. (Requested subnet allocations are: %s)" - % (global_prefix_len, self.root_ip_block, formatted_prefixes)) - log.error(message) - # TODO: throw ANK specific exception here - raise AutoNetkitException(message) - self.graph = global_graph - -# add children of collision domains - - cd_nodes = [n for n in self if n.is_broadcast_domain()] - for cd in sorted(cd_nodes): - for edge in sorted(cd.host.edges()): - - # TODO: sort these - - child_id = self.next_node_id - cd_id = cd.node - global_graph.add_node(child_id, prefixlen=32, - host=edge.dst_int) - # cd -> neigh (cd is parent) - global_graph.add_edge(cd_id, child_id) - -# TODO: make allocate seperate step - - def allocate(node): - - # children = graph.successors(node) - - children = sorted(node.children()) - prefixlen = node.prefixlen + 1 - - # workaround for clobbering attr subgraph root node with /16 if was - # a /28 - - subnet = node.subnet.subnet(prefixlen) - -# handle case where children subnet - - # special case of single AS -> root is loopback_group - if node.is_loopback_group() or node.is_broadcast_domain(): - - # TODO: generalise this rather than repeated code with below - # node.subnet = subnet.next() # Note: don't break into smaller - # subnets if single-AS - - # ensures start at .1 rather than .0 - iterhosts = node.subnet.iter_hosts() - sub_children = node.children() - for sub_child in sorted(sub_children): - - # TODO: tidy up this allocation to always record the subnet - - if sub_child.is_interface() \ - and sub_child.host.is_loopback: - if sub_child.host.is_loopback_zero: - - # loopback zero, just store the ip address - - sub_child.ip_address = iterhosts.next() - else: - - # secondary loopback - - sub_child.ip_address = iterhosts.next() - sub_child.subnet = node.subnet - elif sub_child.is_interface() \ - and sub_child.host.is_physical: - - # physical interface - - sub_child.ip_address = iterhosts.next() - sub_child.subnet = node.subnet - else: - sub_child.subnet = iterhosts.next() - - return - - for child in sorted(children): - - # traverse the tree - - if child.is_broadcast_domain(): - subnet = subnet.next() - child.subnet = subnet - # ensures start at .1 rather than .0 - iterhosts = child.subnet.iter_hosts() - sub_children = child.children() - for sub_child in sorted(sub_children): - if sub_child.is_interface(): - interface = sub_child.host - if interface.is_physical: - - # physical interface - - sub_child.ip_address = iterhosts.next() - sub_child.subnet = subnet - elif interface.is_loopback \ - and not interface.is_loopback_zero: - - # secondary loopback interface - - sub_child.ip_address = iterhosts.next() - sub_child.subnet = subnet - else: - sub_child.subnet = iterhosts.next() - - #log.debug('Allocate sub_child to %s %s'% (sub_child, sub_child.subnet)) - elif child.is_host(): - child.subnet = subnet.next() - elif child.is_loopback_group(): - child.subnet = subnet.next() - # ensures start at .1 rather than .0 - iterhosts = child.subnet.iter_hosts() - sub_children = child.children() - for sub_child in sorted(sub_children): - if sub_child.is_interface() \ - and not sub_child.host.is_loopback_zero: - - # secondary loopback - - sub_child.ip_address = iterhosts.next() - sub_child.subnet = child.subnet - else: - sub_child.subnet = iterhosts.next() - else: - child.subnet = subnet.next() - allocate(child) # continue down the tree - - global_root.subnet = global_ip_block - -# TODO: fix this workaround where referring to the wrong graph - - global_root_id = global_root.node - global_root = TreeNode(global_graph, global_root_id) - allocate(global_root) - -# check for parentless nodes - - self.graph = global_graph - self.root_node = global_root - - def group_allocations(self): - allocs = {} - for node in self: - if node.group_attr: - - # TODO: Also need to store the type - - allocs[node.group_attr] = [node.subnet] - - return allocs - - def add_nodes(self, nodes): - self.unallocated_nodes += list(nodes) - - def walk(self): - - def list_successors(node): - successors = self.graph.successors(node) - if successors: - children = [list_successors(n) for n in successors] - return {node: children} - return node - - return list_successors(self.root_node) - - def json(self): - - def list_successors(node): - children = node.children() - if children: - children = [list_successors(n) for n in children] - return {'name': node, 'subnet': node.subnet, - 'children': children} - return {'name': node, 'subnet': node.subnet} - - if not self.root_node: - log.debug('No root node set') - return {'name': str(self.root_ip_block), - 'subnet': str(self.root_ip_block), 'children': []} - return - return list_successors(self.root_node) - - def assign(self): - - # assigns allocated addresses back to hosts - # don't look at host nodes now - use loopback_groups - # TODO: make check for interface and loopback zero now - host_tree_nodes = [n for n in self if n.is_host() - and isinstance(n.host, autonetkit.anm.NmNode) - and n.host.is_l3device()] - - # for host_tree_node in host_tree_nodes: - # print host_tree_node, host_tree_node.subnet - - for host_tree_node in host_tree_nodes: - host_tree_node.host.loopback = host_tree_node.subnet - - cds = [n for n in self if n.is_broadcast_domain()] - for cd in cds: - cd.host.subnet = cd.subnet - - interfaces = [n for n in self if n.is_interface()] - for n in interfaces: - interface = n.host - if interface.is_loopback and interface.is_loopback_zero: - - # primary loopback - - interface.loopback = n.ip_address - elif interface.is_loopback \ - and not interface.is_loopback_zero: - - # secondary loopback - - interface.loopback = n.ip_address - loopback_255 = netaddr.IPNetwork("%s/32" % n.ip_address) - interface.subnet = loopback_255 - elif interface.is_physical: - interface.ip_address = n.ip_address - interface.subnet = n.subnet - - -def assign_asn_to_interasn_cds(g_ip, address_block=None): - G_phy = g_ip.overlay('phy') - for broadcast_domain in g_ip.nodes('broadcast_domain'): - neigh_asn = list(ank_utils.neigh_attr(g_ip, broadcast_domain, - 'asn', G_phy)) # asn of neighbors - if len(set(neigh_asn)) == 1: - asn = set(neigh_asn).pop() # asn of any neigh, as all same - else: - # allocate cd to asn with most neighbors in it - asn = ank_utils.most_frequent(neigh_asn) - broadcast_domain.asn = asn - - return - - -def allocate_infra(g_ip, address_block=None): - if not address_block: - address_block = netaddr.IPNetwork('10.0.0.0/8') - log.debug('Allocating v4 Infrastructure IPs') - ip_tree = IpTree(address_block) - assign_asn_to_interasn_cds(g_ip) - nodes_to_allocate = sorted(n for n in g_ip.nodes('broadcast_domain') - if n.allocate) - ip_tree.add_nodes(nodes_to_allocate) - ip_tree.build() - - # cd_tree = ip_tree.json() - - ip_tree.assign() - - g_ip.data.infra_blocks = ip_tree.group_allocations() - - # total_tree = { 'name': "ip", 'children': [cd_tree], } - # jsontree = json.dumps(total_tree, cls=autonetkit.ank_json.AnkEncoder, indent = 4) - - g_ip.data.infra_blocks = ip_tree.group_allocations() - - -# TODO: apply directly here - -def allocate_loopbacks(g_ip, address_block=None): - if not address_block: - address_block = netaddr.IPNetwork('192.168.0.0/22') - log.debug('Allocating v4 Primary Host loopback IPs') - ip_tree = IpTree(address_block) - ip_tree.add_nodes(sorted(g_ip.l3devices())) - ip_tree.build() - - # loopback_tree = ip_tree.json() - - ip_tree.assign() - g_ip.data.loopback_blocks = ip_tree.group_allocations() - - -def allocate_secondary_loopbacks(g_ip, address_block=None): - if not address_block: - address_block = netaddr.IPNetwork('172.16.0.0/24') - - secondary_loopbacks = [i for n in g_ip.l3devices() for i in - n.loopback_interfaces() - if not i.is_loopback_zero and i['ip'].allocate is not False] - - if not len(secondary_loopbacks): - return # nothing to set - log.debug('Allocating v4 Secondary Host loopback IPs') - log.debug('Allocating v4 Secondary Host loopback IPs to %s', - secondary_loopbacks) - ip_tree = IpTree(address_block) - - #vrf_loopbacks = [i for i in secondary_loopbacks if i['vrf'].vrf_name] - - ip_tree.add_nodes(sorted(secondary_loopbacks)) - - ip_tree.build() - - # secondary_loopback_tree = ip_tree.json() - - ip_tree.assign() - - # TODO: store vrf block to g_ip.data diff --git a/autonetkit/plugins/ipv6.py b/autonetkit/plugins/ipv6.py deleted file mode 100644 index 32549dbd..00000000 --- a/autonetkit/plugins/ipv6.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import json - -import autonetkit.ank as ank_utils -import autonetkit.ank_json -import autonetkit.ank_messaging -import autonetkit.log as log -import netaddr - -# TODO: allow slack in allocations: both for ASN (group level), and for -# collision domains to allow new nodes to be easily added - -try: - import cPickle as pickle -except ImportError: - import pickle - - -def assign_asn_to_interasn_cds(G_ip): - # TODO: remove this if no longer needed - - # TODO: rename to assign_asn_to_cds as also does intra-asn cds - # TODO: make this a common function to ip4 and ip6 - - G_phy = G_ip.overlay('phy') - for broadcast_domain in G_ip.nodes('broadcast_domain'): - neigh_asn = list(ank_utils.neigh_attr(G_ip, broadcast_domain, - 'asn', G_phy)) # asn of neighbors - if len(set(neigh_asn)) == 1: - asn = set(neigh_asn).pop() # asn of any neigh, as all same - else: - # allocate bc to asn with most neighbors in it - asn = ank_utils.most_frequent(neigh_asn) - broadcast_domain.asn = asn - - return - - -def allocate_loopbacks(g_ip, address_block=None): - # TODO: handle no block specified - loopback_blocks = {} - loopback_pool = address_block.subnet(80) - - # consume the first address as it is the network address - - _ = loopback_pool.next() # network address - - unique_asns = set(n.asn for n in g_ip) - for asn in sorted(unique_asns): - loopback_blocks[asn] = loopback_pool.next() - - for (asn, devices) in g_ip.groupby('asn').items(): - loopback_hosts = loopback_blocks[asn].iter_hosts() - # drop .0 as a host address (valid but can be confusing) - loopback_hosts.next() - l3hosts = set(d for d in devices if d.is_l3device()) - for host in sorted(l3hosts, key=lambda x: x.label): - host.loopback = loopback_hosts.next() - - g_ip.data.loopback_blocks = dict((asn, [subnet]) for (asn, - subnet) in loopback_blocks.items()) - - -def allocate_infra(g_ip, address_block=None): - infra_blocks = {} - -# TODO: check if need to do network address... possibly only for -# loopback_pool and infra_pool so maps to asn - - infra_pool = address_block.subnet(80) - - # consume the first address as it is the network address - - _ = infra_pool.next() # network address - - unique_asns = set(n.asn for n in g_ip) - for asn in sorted(unique_asns): - infra_blocks[asn] = infra_pool.next() - - for (asn, devices) in sorted(g_ip.groupby('asn').items()): - subnets = infra_blocks[asn].subnet(96) - subnets.next() # network address - ptp_subnet = subnets.next().subnet(126) - ptp_subnet.next() # network address - all_bcs = set(d for d in devices if d.broadcast_domain - and d.allocate) - ptp_bcs = [bc for bc in all_bcs if bc.degree() == 2] - - for bc in sorted(ptp_bcs): - subnet = ptp_subnet.next() - hosts = subnet.iter_hosts() - # drop .0 as a host address (valid but can be confusing) - hosts.next() - bc.subnet = subnet - # TODO: check: should sort by default on dst as tie-breaker - for edge in sorted(bc.edges(), key=lambda x: x.dst.label): - edge.ip = hosts.next() - - non_ptp_cds = all_bcs - set(ptp_bcs) - - # break into /96 subnets - - for bc in sorted(non_ptp_cds): - subnet = subnets.next() - hosts = subnet.iter_hosts() - # drop .0 as a host address (valid but can be confusing) - hosts.next() - bc.subnet = subnet - for edge in sorted(bc.edges(), key=lambda x: x.dst.label): - edge.ip = hosts.next() - - g_ip.data.infra_blocks = dict((asn, [subnet]) for (asn, subnet) in - infra_blocks.items()) - - -def allocate_secondary_loopbacks(g_ip, address_block=None): - secondary_loopback_blocks = {} - secondary_loopback_pool = address_block.subnet(80) - # consume the first address as it is the network address - # network address - secondary_loopback_network = secondary_loopback_pool.next() - - unique_asns = set(n.asn for n in g_ip) - for asn in sorted(unique_asns): - secondary_loopback_blocks[asn] = \ - secondary_loopback_network.next() - - for (asn, devices) in g_ip.groupby('asn').items(): - l3hosts = set(d for d in devices if d.is_l3device()) - routers = [n for n in l3hosts if n.is_router()] # filter - secondary_loopbacks = [i for n in routers for i in - n.loopback_interfaces() - if not i.is_loopback_zero] - - secondary_loopback_hosts = \ - secondary_loopback_blocks[asn].iter_hosts() - # drop .0 as a host address (valid but can be confusing) - secondary_loopback_hosts.next() - for interface in sorted(secondary_loopbacks): - interface.loopback = secondary_loopback_hosts.next() - interface.subnet = netaddr.IPNetwork("%s/128" % interface.loopback) - - -def allocate_ips(G_ip, infra_block=None, loopback_block=None, secondary_loopback_block=None): - log.info('Allocating Host loopback IPs') - # TODO: move the following step to the l3 graph - assign_asn_to_interasn_cds(G_ip) - - allocate_loopbacks(sorted(G_ip), loopback_block) - allocate_infra(sorted(G_ip), infra_block) - allocate_secondary_loopbacks(sorted(G_ip), secondary_loopback_block) diff --git a/autonetkit/plugins/naming.py b/autonetkit/plugins/naming.py deleted file mode 100644 index 92aedf1a..00000000 --- a/autonetkit/plugins/naming.py +++ /dev/null @@ -1,9 +0,0 @@ -import autonetkit.ank as ank - -def network_hostname(node): - network = node.Network - if network: - return "%s_%s" % (ank.name_folder_safe(network), - ank.name_folder_safe(node.label)) - else: - return ank.name_folder_safe(node.label) diff --git a/autonetkit/render.py b/autonetkit/render.py deleted file mode 100644 index 069d595d..00000000 --- a/autonetkit/render.py +++ /dev/null @@ -1,301 +0,0 @@ -import fnmatch -import os -import shutil -import time - -import autonetkit.log as log -import mako -import pkg_resources -from mako.exceptions import SyntaxException -from mako.lookup import TemplateLookup - -# TODO: have same error handling block for each template render call - -# TODO: clean up cache enable/disable - - -def resource_path(relative): - """Makes relative to package""" - return pkg_resources.resource_filename(__name__, relative) - -# TODO: fix support here for template lookups, internal, user provided -#template_cache_dir = config.template_cache_dir -template_cache_dir = "cache" - -# disable cache for cleaner folder structure - - -def initialise_lookup(): - retval = TemplateLookup(directories=[resource_path("")], - #module_directory= template_cache_dir, - cache_type='memory', - cache_enabled=True, - ) - - try: - cisco_templates = pkg_resources.resource_filename( - "autonetkit_cisco", "") - retval.directories.append(cisco_templates) - except ImportError: - pass # Cisco ANK not present - - # and cwd - retval.directories.append(os.getcwd()) - - return retval - -# TODO: make lookup initialised once rather than global for module import -# and allow users to append to the lookup -TEMPLATE_LOOKUP = initialise_lookup() - -try: - from autonetkit_cisco.template_wrapper import inject_templates - inject_templates(TEMPLATE_LOOKUP) -except ImportError: - pass - - -def format_version_banner(): - version_banner = "autonetkit_dev" - try: - # test if can import, if not present will fail and not add to template - # path - import autonetkit_cisco - except ImportError: - pass - else: - import autonetkit_cisco.version - version_banner = autonetkit_cisco.version.banner() - return version_banner - - try: - version_banner = ("autonetkit_%s" % - pkg_resources.get_distribution("autonetkit").version) - # TODO: pick up name automatically - except pkg_resources.DistributionNotFound: - version_banner = "autonetkit_dev" - - return version_banner - -# TODO: make a render class, that caches traversed folders for speed - - -def render_inline(node, render_template_file, to_memory=True, - render_dst_file=None): - """Generic rendering of a node attribute rather than the standard location. - Needs to be called by render_node. - Doesn't support base folders - only single attributes. - Note: supports rendering to memory (ie back to nidb rather than file) - """ - - node.log.debug("Rendering template %s" % (render_template_file)) - version_banner = format_version_banner() - - date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) - - if render_template_file: - try: - render_template = TEMPLATE_LOOKUP.get_template( - render_template_file) - except SyntaxException, error: - log.warning("Unable to render %s: " - "Syntax error in template: %s" % (node, error)) - return - - if to_memory: - # Render directly to DeviceModel - render_output = render_template.render( - node=node, - version_banner=version_banner, - date=date, - ) - - return render_output - -# TODO: Add support for both src template and src folder (eg for quagga, -# servers) - - -def render_node(node): - if not node.do_render: - node.log.debug("Rendering disabled for node") - return - - try: - render_output_dir = node.render.dst_folder - # TODO: could check if base is set, so don't put error into debug log - render_base = node.render.base - render_base_output_dir = node.render.base_dst_folder - render_template_file = node.render.template - render_custom = node.render.custom - except KeyError, error: - # TODO: make sure allows case of just custom render - return - - version_banner = format_version_banner() - - date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) - if render_custom: - # print render_custom - pass - -# TODO: make sure is an abspath here so don't wipe user directory!!! - if render_output_dir and not os.path.isdir(render_output_dir): - try: - os.makedirs(render_output_dir) - except OSError, e: - if e.strerror == "File exists": - pass # created by another process, safe to ignore - else: - raise e - - if render_template_file: - try: - render_template = TEMPLATE_LOOKUP.get_template( - render_template_file) - except SyntaxException, error: - log.warning("Unable to render %s: " - "Syntax error in template: %s" % (node, error)) - return - - if node.render.dst_file: - dst_file = os.path.join(render_output_dir, node.render.dst_file) - with open(dst_file, 'wb') as dst_fh: - try: - dst_fh.write(render_template.render( - node=node, - version_banner=version_banner, - date=date, - )) - except KeyError, error: - log.warning("Unable to render %s:" - " %s not set" % (node, error)) - from mako import exceptions - log.debug(exceptions.text_error_template().render()) - except AttributeError, error: - log.warning("Unable to render %s: %s " % (node, error)) - from mako import exceptions - log.debug(exceptions.text_error_template().render()) - except NameError, error: - log.warning("Unable to render %s: %s. " - "Check all variables used are defined" % (node, error)) - except TypeError, error: - log.warning("Unable to render %s: %s." % (node, error)) - from mako import exceptions - log.debug(exceptions.text_error_template().render()) - - if node.render.to_memory: - # Render directly to DeviceModel - node.render.render_output = render_template.render( - node=node, - version_banner=version_banner, - date=date, - ) - - if render_base: - # TODO: revert to shutil copy - if render_base: - render_base = resource_path(render_base) - fs_mako_templates = [] - for root, _, filenames in os.walk(render_base): - for filename in fnmatch.filter(filenames, '*.mako'): - # relative to fs root - rel_root = os.path.relpath(root, render_base) - fs_mako_templates.append(os.path.join(rel_root, filename)) - - try: - shutil.rmtree(render_base_output_dir) - except OSError: - pass # doesn't exist - shutil.copytree(render_base, render_base_output_dir, - ignore=shutil.ignore_patterns('*.mako')) - for template_file in fs_mako_templates: - template_file_path = os.path.normpath( - os.path.join(render_base, template_file)) - mytemplate = mako.template.Template(filename=template_file_path, - ) - dst_file = os.path.normpath( - (os.path.join(render_base_output_dir, template_file))) - dst_file, _ = os.path.splitext(dst_file) # remove .mako suffix - with open(dst_file, 'wb') as dst_fh: - dst_fh.write(mytemplate.render( - node=node, - version_banner=version_banner, - date=date, - )) - return - - -def render(nidb): - # TODO: config option for single or multi threaded - log.debug("Rendering Configuration Files") - render_single(nidb) - render_topologies(nidb) - - -def render_single(nidb): - for node in sorted(nidb): - render_node(node) - - -def render_topologies(nidb): - for topology in nidb.topologies(): - render_topology(topology) - - -def render_topology(topology): - version_banner = format_version_banner() - - date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) - try: - render_output_dir = topology.render_dst_folder - render_template_file = topology.render_template - except KeyError, error: - return - - if not render_template_file: - log.debug("No render template specified for topology %s, skipping" - % topology) - return - - try: - render_template = TEMPLATE_LOOKUP.get_template(render_template_file) - except SyntaxException, error: - log.warning( - "Unable to render %s: Syntax error in template: %s" % (topology, error)) - return - - if not os.path.isdir(render_output_dir): - try: - os.makedirs(render_output_dir) - except OSError, e: - # TODO: replace with e.errno - if e.strerror == "File exists": - pass # created by another process, safe to ignore - else: - raise e - dst_file = os.path.join(render_output_dir, topology.render_dst_file) - -# TODO: may need to iterate if multiple parts of the directory need to be -# created - - # TODO: capture mako errors better - - with open(dst_file, 'wb') as dst_fh: - try: - dst_fh.write(render_template.render( - topology=topology, - version_banner=version_banner, - date=date, - )) - except KeyError, error: - log.warning("Unable to render %s: %s not set" % (topology, error)) - except AttributeError, error: - log.warning("Unable to render %s: %s " % (topology, error)) - except NameError, error: - log.warning("Unable to render %s: %s. Check all variables used are defined" % ( - topology, error)) - except TypeError, error: - log.warning("Unable to render topology: %s." % (error)) - from mako import exceptions - log.warning(exceptions.text_error_template().render()) diff --git a/autonetkit/render2.py b/autonetkit/render2.py deleted file mode 100644 index b5f1f227..00000000 --- a/autonetkit/render2.py +++ /dev/null @@ -1,416 +0,0 @@ -from collections import defaultdict -import os -import autonetkit.log as log - - -# TODO: need to work out clean way to handle template searchspaces -# and to allow users to specify their own templates from disk, or own -# searchspaces? - -# TODO: need to implement caching for performance - -from collections import namedtuple -src_dst = namedtuple("src_dst", ['src', 'dst']) - - -class NodeRender(object): - - def __init__(self, files=None, folders=None): - self._files = defaultdict(list) - self._folders = defaultdict(list) - # TODO: need a new dict for each pass_id to store if memory/file... or just do all to memory? - # and then platform decides the output - for filename in files or []: - self.add_file(filename) - for foldername in files or []: - self.add_folder(foldername) - - def __iter__(self): - """Returns each render pass as a namedtuple of structure: - - with the folder structure flattened out to a list - to_disk is default of False (render to memory by default) - """ - pass - - def add_template_location(self, location): - """TODO: allow user to specify a location to search - """ - pass - - # TODO:implement __add__ with defaults for add_file - - # TODO: take in location (by default is autonetkit templates dir) - def add_file(self, src, dst=None, pass_id=0): - """Adds a file to pass "pass" - dst is the target filename, if not set the src is used - """ - if dst is None: - dst = src - self._files[pass_id].append(src_dst(src, dst)) - - def add_folder(self, src, dst=None, pass_id=0): - """Pass in folder as a list, will automatically apply os.path.sep""" - if dst is None: - dst = src - self._folders[pass_id].append(src_dst(src, dst)) - - def to_json(self): - # TODO: return folders then files - return {'files': self._files, 'folders': self._folders} - - def flatten(self): - # flattens nested dicts out - pass - - def get_files(self, pass_id=0): - return self._files.get(pass_id) - - def get_file_dst(self, src, pass_id=0): - return self._files.get(pass_id).get(src) - - def get_folders(self, pass_id=0): - return self._folders.get(pass_id) - - -class PlatformRender(NodeRender): - - def __init__(self): - super(PlatformRender, self).__init__() - self.nodes = [] - self.base_folder = "" - self.archive = None - self.template_data = {} - - def add_node(self, node): - self.nodes.append(node) - - def to_json(self): - return self.nodes - - def __iter__(self): - return iter(self.nodes) - - -def setup(): - # any setup steps for renderers - pass - - -def render_node(node): - pass - - -def render_topology_basic(topology): - """Simple renderer, no caching""" - for node in topology.render2: - print node - - -# TODO: Put inside class once created -# TODO: inherit from common base - -class MakoRenderer(object): - - def __init__(self): - # if archive is none then render to memory - # Note: this could also include setting lookup directories (unlikely to - # be required though) - pass - - def prepare(self, template_data): - from mako.template import Template - return Template(template_data) - - def render(self, template, node, version_banner, date, template_data=None): - if template_data is None: - template_data = {} - - try: - return template.render( - node=node, - version_banner=version_banner, - date=date, - **template_data - ) - except KeyError, error: - log.warning("Unable to render %s:" - " %s not set" % (node, error)) - from mako import exceptions - log.debug(exceptions.text_error_template().render()) - except AttributeError, error: - log.warning("Unable to render %s: %s " % (node, error)) - from mako import exceptions - log.debug(exceptions.text_error_template().render()) - except NameError, error: - log.warning("Unable to render %s: %s. " - "Check all variables used are defined" % (node, error)) - except TypeError, error: - log.warning("Unable to render %s: %s." % (node, error)) - from mako import exceptions - log.debug(exceptions.text_error_template().render()) - - import ipdb - ipdb.set_trace() - - -def get_folder_contents(folder): - retval = [] - - for base, _, filenames in os.walk(folder): - for name in filenames: - # TODO: take in a skiplist - if name == ".DS_Store": - continue - # TODO: see if can iterate folder? - file_base = base.replace(folder, "") - file_base = file_base[1:] # strip leading / - retval.append(os.path.join(file_base, name)) - - return retval - -extension_renderers = {'.mako': MakoRenderer()} - - -def render_nodes(nodes, base_folder="", - archive=None): - - if archive is None: - to_memory = True - else: - to_memory = False - - # extracts common paths from the nodes - # first build up list of all paths - common_files = defaultdict(list) - common_folders = defaultdict(list) - - if isinstance(base_folder, basestring): - pass - else: - base_folder = os.path.join(*base_folder) - - # store destinations - file_dsts = defaultdict(dict) - folder_dsts = defaultdict(dict) - - for node in nodes: - # print node.render2.get_pass() - # TODO: need to iterate over all passes - for path in node.render2.get_files(): - src = tuple(path.src) - common_files[src].append(node) - file_dsts[node][src] = path.dst - # TODO: need to skip templates..... defined programatically in mapping - # TODO: probably need to make a render class for this... - for path in node.render2.get_folders(): - src = tuple(path.src) - common_folders[src].append(node) - folder_dsts[node][src] = path.dst - - # print common_files - # print common_folders - - # load common files into cache - # TODO: no cache now do file-by-file - # TODO: do folders first so more specific precedence over less specific - # in case of a clash (like in routing) - - # Prepare static classes for extensions - - # TODO: fix up naming - for src_base, nodes in common_folders.items(): - src_folder = os.path.join(*src_base) # "splat" list for os.path.join - import pkg_resources - src_path = pkg_resources.resource_filename(__name__, src_folder) - # get structure of each folder - contents = get_folder_contents(src_path) - # TODO: treat per-file the same as for files below - # TODO: could optimise so that if clobber, don't render previous - # (unlikely to occur though) - for filename in contents: - out_filename = filename - abs_filename = os.path.join(src_folder, filename) - abs_filename = pkg_resources.resource_filename( - __name__, abs_filename) - with open(abs_filename) as fh: - file_data = fh.read() - - template_renderer = None - extension = os.path.splitext(filename)[1] - if extension in extension_renderers: - template_renderer = extension_renderers[extension] - - # and strip extension from output file - out_filename = out_filename[:-len(extension)] - - import time - version_banner = ("autonetkit_%s" % - pkg_resources.get_distribution("autonetkit").version) - date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) - render_template = template_renderer.prepare(file_data) - - for node in nodes: - # node_data = template_renderer.render(render_template, - # node, version_banner, date) - if template_renderer: - node_data = template_renderer.render(render_template, - node, version_banner, date) - else: - node_data = file_data - - # to memory -> store - - dst = folder_dsts[node][src_base] - if isinstance(dst, basestring): - pass - else: - dst = os.path.join(*dst) - dst = os.path.join(dst, out_filename) - dst = os.path.join(base_folder, dst) - - # else to file - if to_memory: - node.set(dst, node_data) - else: - archive.writestr(dst, node_data) - - for src, nodes in common_files.items(): - # TODO: write this to a separate function - filename = os.path.join(*src) # "splat" list for os.path.join - import pkg_resources - # TODO: specify the full file path (or provide wrapper for this to - # specify the package) - abs_filename = pkg_resources.resource_filename(__name__, filename) - with open(abs_filename) as fh: - file_data = fh.read() - - # TODO: Only do the following for .mako - extension = os.path.splitext(filename)[1] - template_renderer = extension_renderers[extension] - - import time - version_banner = ("autonetkit_%s" % - pkg_resources.get_distribution("autonetkit").version) - date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) - render_template = template_renderer.prepare(file_data) - - # get extension - - for node in nodes: - node_data = template_renderer.render(render_template, - node, version_banner, date) - - # to memory -> store - dst = file_dsts[node][src] - dst = os.path.join(base_folder, dst) - - # else to file - if to_memory: - node.set(dst, node_data) - else: - archive.writestr(dst, node_data) - - # TODO: to save memory could process for each node that has this file, etc? - # then write that to the archive, and continue on - # eg file by file rather than node by node? - - # TODO: if a file is greater than a certain size dont cache in memory - - # TODO: alternative would be to cache on the fly as seen, but this could s - # another alternative is to cache if seen for second time? - -# note: may want "platform render data" and a "platform renderer" - - -# TODO: also create a render_topology(topology) -# note that we don't have as much need to do caching as much less -# topologies than node - -def render_topology(topology): - """Pre-caches""" - # TODO: also need to render the topology template eg lab.conf - - render_data = topology.render2 - base_folder = render_data.base_folder - - archive_filename = render_data.archive - archive = None - to_memory = True - if archive_filename: - to_memory = False - import zipfile - archive_filename = "%s.zip" % archive_filename - # TODO: make rendered folder set in config, and check exists - archive_path = os.path.join("rendered", archive_filename) - # TODO: look at compression levels - archive = zipfile.ZipFile(archive_path, mode='w') - - nodes = render_data.nodes - render_nodes(nodes, - archive=archive, - base_folder=base_folder) - - if isinstance(base_folder, basestring): - pass - else: - base_folder = os.path.join(*base_folder) - - for entry in render_data.get_files(): - src = entry.src - dst = entry.dst - # TODO: write this to a separate function - filename = os.path.join(*src) # "splat" list for os.path.join - print filename - import pkg_resources - # TODO: specify the full file path (or provide wrapper for this to - # specify the package) - abs_filename = pkg_resources.resource_filename(__name__, filename) - with open(abs_filename) as fh: - file_data = fh.read() - - # TODO: Only do the following for .mako - extension = os.path.splitext(filename)[1] - template_renderer = extension_renderers[extension] - - import time - version_banner = ("autonetkit_%s" % - pkg_resources.get_distribution("autonetkit").version) - date = time.strftime("%Y-%m-%d %H:%M", time.localtime()) - render_template = template_renderer.prepare(file_data) - - # get extension - # TODO: clean up placeholder for node renderer - node = None - template_data = {'topology': topology} - - topology_data = template_renderer.render(render_template, - node, date, version_banner, template_data) - - # to memory -> store - dst = os.path.join(base_folder, dst) - if isinstance(dst, basestring): - pass - else: - dst = os.path.join(*dst) - - # else to file - if to_memory: - topology.set(dst, topology_data) - else: - archive.writestr(dst, topology_data) - - # render files - - if archive: - archive.close() - - # warn if any folders that not supported yet for topologies - - -def render(nidb): - log.info("Rendering v2") - # TODO: should allow render to take list of nodes in case different namespace - # perhaps allow render to take list of topologies/nodes to render? - for topology in nidb.topologies(): - render_topology(topology) diff --git a/autonetkit/templates/dynagen.mako b/autonetkit/templates/dynagen.mako deleted file mode 100644 index 000f620c..00000000 --- a/autonetkit/templates/dynagen.mako +++ /dev/null @@ -1,30 +0,0 @@ -autostart = False -version = 0.8.3.1 -[${topology.hypervisor_server}:${topology.hypervisor_port}] - workingdir = /tmp - udp = 10000 - [[7200]] - image = ${topology.image} - idlepc = ${topology.idlepc} - ghostios = True - - %for router in topology.routers: - [[ROUTER ${router.hostname}]] - model = ${router.model} - console = ${router.console} - aux = ${router.aux} - % for index, card in router.slots: - slot${index} = PA-2FE-TX - %endfor - % for interface in router.interfaces: - ${interface['src_port']} = ${interface['dst']} ${interface['dst_port']} - %endfor - x = ${router.x} - y = ${router.y} - z = 1.0 - cnfg = ${router.cnfg} - %endfor - -[GNS3-DATA] - configs = ${topology.config_dir} - diff --git a/autonetkit/templates/ios.mako b/autonetkit/templates/ios.mako deleted file mode 100644 index 43eb3e20..00000000 --- a/autonetkit/templates/ios.mako +++ /dev/null @@ -1,476 +0,0 @@ -! IOS Config generated on ${date} -! by ${version_banner} -! -hostname ${node} -boot-start-marker -boot-end-marker -! -% if node.include_csr: -license accept end user agreement -license boot level premium -! -! -% endif -% if node.global_custom_config: -! -${node.global_custom_config} -! -% endif -no aaa new-model -! -! -% if node.ipv4_cef: -ip cef -%endif -ipv6 unicast-routing -% if node.ipv6_cef: -ipv6 cef -%endif -% if node.mpls_te: -mpls traffic-eng tunnels -%endif -! -! -service timestamps debug datetime msec -service timestamps log datetime msec -no service password-encryption -% if node.no_service_config: -no service config -%endif -enable password cisco -% if node.enable_secret: -enable secret 4 ${node.enable_secret} -%endif -ip classless -ip subnet-zero -no ip domain lookup -line vty 0 4 -% if node.transport_input_ssh_telnet: - transport input ssh telnet -%endif - exec-timeout 720 0 - password cisco - login -line con 0 - password cisco -! -% if node.use_cdp: -! -cdp run -! -%endif -% if node.use_onepk: -! -username cisco privilege 15 password 0 cisco - ! - onep - transport type tls disable-remotecert-validation - start - service set vty -! -%endif -## VRF -% for vrf in node.vrf.vrfs: -vrf definition ${vrf.vrf} -rd ${vrf.rd} -! -% if node.vrf.use_ipv4: -address-family ipv4 - route-target export ${vrf.route_target} - route-target import ${vrf.route_target} -exit-address-family -%endif -%endfor -## L2TP Classes -% for l2tp_class in node.l2tp_classes: -% if loop.first: -! -% endif -l2tp-class ${l2tp_class} -%endfor -## PseudoWire Classes -% for pwc in node.pseudowire_classes: - % if pwc.encapsulation == "l2tpv3": - % if loop.first: - ! - % endif -pseudowire-class ${pwc.name} - encapsulation ${pwc.encapsulation} - protocol l2tpv3 ${pwc.l2tp_class_name} - ip local interface ${pwc.local_interface} - % endif -! -%endfor -! -## Physical Interfaces -% for interface in node.interfaces: -interface ${interface.id} - description ${interface.description} - % if interface.comment: - ! ${interface.comment} - %endif - % if interface.mtu: - mtu ${interface.mtu} - %endif - % if interface.custom_config: - ${interface.custom_config} - %endif - % if interface.vrf: - vrf forwarding ${interface.vrf} - %endif - % if interface.use_ipv4: - %if interface.use_dhcp: - ip address dhcp - %else: - ip address ${interface.ipv4_address} ${interface.ipv4_subnet.netmask} - %endif - %else: - no ip address - %endif - % if interface.use_ipv6: - ipv6 address ${interface.ipv6_address} - %endif - % if interface.use_cdp: - cdp enable - %endif - % if interface.rip: - %if interface.rip.use_ipv6: - ipv6 rip {node.rip.process_id} enable - %endif - %endif - % if interface.ospf: - %if interface.ospf.use_ipv4: - %if not interface.ospf.multipoint: - ###ip ospf network point-to-point - %endif - ip ospf cost ${interface.ospf.cost} - %endif - %if interface.ospf.use_ipv6: - %if not interface.ospf.multipoint: - ###ipv6 ospf network point-to-point - %endif - ipv6 ospf cost ${interface.ospf.cost} - ipv6 ospf ${interface.ospf.process_id} area ${interface.ospf.area} - %endif - %endif - % if interface.isis: - % if interface.isis.use_ipv4: - ip router isis ${node.isis.process_id} - % if interface.physical: - isis circuit-type level-2-only - %if not interface.isis.multipoint: - isis network point-to-point - % endif - isis metric ${interface.isis.metric} - % endif - % endif - % if interface.isis.use_ipv6: - ipv6 router isis ${node.isis.process_id} - % if interface.physical: - isis ipv6 metric ${interface.isis.metric} - % endif - % endif - % if interface.isis.mtu: - clns mtu ${interface.isis.mtu} - %endif - % endif - % if interface.physical: - % if not node.exclude_phy_int_auto_speed_duplex: - ## don't include auto duplex and speed on platforms eg CSR1000v - ## include by default - duplex auto - speed auto - % endif - no shutdown - %endif - % if interface.use_mpls: - mpls ip - %endif - % if interface.te_tunnels: - mpls traffic-eng tunnels - %endif - % for sub_int in interface.sub_ints or []: - interface ${interface.id}.${sub_int.id} - ipv4 address ${sub_int.ipv4_address} ${sub_int.ipv4_subnet.netmask} - encapsulation dot1q ${sub_int.dot1q} - ! - %endfor - % if interface.rsvp_bandwidth_percent: - ip rsvp bandwidth percent ${interface.rsvp_bandwidth_percent} - %endif - % if interface.xconnect: - % if interface.xconnect.encapsulation == "l2tpv3": - xconnect ${interface.xconnect.remote_ip} ${interface.xconnect.vc_id} encapsulation l2tpv3 pw-class ${interface.xconnect.pw_class} - %endif - %endif -! -% endfor -! -% for tunnel in node.gre_tunnels: -interface Tunnel${tunnel.id} - % if tunnel.use_ipv4: - ip address ${tunnel.ipv4_address} ${tunnel.ipv4_subnet.netmask} - %else: - no ip address - %endif - % if tunnel.use_ipv6: - ipv6 address ${tunnel.ipv6_address} - %endif - tunnel source ${tunnel.source} - tunnel destination ${tunnel.destination} -%endfor -! -## OSPF -% if node.ospf: -% if node.ospf.use_ipv4: -router ospf ${node.ospf.process_id} - % if node.ospf.custom_config: - ${node.ospf.custom_config} - % endif - %if node.ospf.ipv4_mpls_te: - mpls traffic-eng router-id ${node.ospf.mpls_te_router_id} - mpls traffic-eng area ${node.ospf.loopback_area} - % endif -## Loopback - network ${node.loopback} 0.0.0.0 area ${node.ospf.loopback_area} - log-adjacency-changes - passive-interface ${node.ospf.lo_interface} -% for ospf_link in node.ospf.ospf_links: - network ${ospf_link.network.network} ${ospf_link.network.hostmask} area ${ospf_link.area} -% endfor -% endif -% if node.ospf.use_ipv6: -router ospfv3 ${node.ospf.process_id} - router-id ${node.loopback} - ! - address-family ipv6 unicast - exit-address-family -% endif -% endif -## RIP -% if node.rip: -router rip -version 2 -no auto-summary - % if node.rip.custom_config: - ${node.rip.custom_config} - % endif -% if node.rip.use_ipv4: - % for subnet in node.rip.ipv4_networks: - network ${subnet.network} - %endfor - %endif -%endif -## ISIS -% if node.isis: -router isis ${node.isis.process_id} - % if node.isis.custom_config: - ${node.isis.custom_config} - % endif - %if node.isis.ipv4_mpls_te: - mpls traffic-eng router-id ${node.isis.mpls_te_router_id} - mpls traffic-eng level-2 - % endif - net ${node.isis.net} - metric-style wide -% if node.isis.use_ipv6: - ! - address-family ipv6 - multi-topology - exit-address-family -% endif -% endif -% if node.eigrp: -router eigrp ${node.eigrp.process_id} - ! - % if node.eigrp.custom_config: - ${node.eigrp.custom_config} - % endif -% if node.eigrp.use_ipv4: - address-family ipv4 unicast autonomous-system ${node.asn} - ! - topology base - exit-af-topology - % for subnet in node.eigrp.ipv4_networks: - network ${subnet.network} ${subnet.hostmask} - % endfor - exit-address-family - ! -% endif -% if node.eigrp.use_ipv6: - address-family ipv6 unicast autonomous-system ${node.asn} - ! - topology base - exit-af-topology - exit-address-family -! -% endif -% endif -! -% if node.mpls.enabled: -mpls ldp router-id ${node.mpls.router_id} -%endif -! -## BGP -% if node.bgp: -router bgp ${node.asn} - bgp router-id ${node.router_id} - no synchronization - % if node.bgp.custom_config: - ${node.bgp.custom_config} - % endif -! ibgp -## iBGP Route Reflector Clients -% for client in node.bgp.ibgp_rr_clients: -% if loop.first: - ! ibgp clients -% endif - ! - neighbor ${client.loopback} remote-as ${client.asn} - neighbor ${client.loopback} description rr client ${client.neighbor} - neighbor ${client.loopback} update-source ${node.bgp.lo_interface} -% endfor -## iBGP Route Reflectors (Parents) -% for parent in node.bgp.ibgp_rr_parents: -% if loop.first: - ! ibgp route reflector servers -% endif - ! - neighbor ${parent.loopback} remote-as ${parent.asn} - neighbor ${parent.loopback} description rr parent ${parent.neighbor} - neighbor ${parent.loopback} update-source ${node.bgp.lo_interface} -% endfor -## iBGP peers -% for neigh in node.bgp.ibgp_neighbors: -% if loop.first: - ! ibgp peers -% endif - ! - neighbor ${neigh.loopback} remote-as ${neigh.asn} - neighbor ${neigh.loopback} description iBGP peer ${neigh.neighbor} - neighbor ${neigh.loopback} update-source ${node.bgp.lo_interface} -% endfor -## vpnv4 peers -% for neigh in node.bgp.vpnv4_neighbors: -% if loop.first: - ! vpnv4 peers - address-family vpnv4 -% endif - neighbor ${neigh.loopback} activate - % if neigh.rr_client: - neighbor ${neigh.loopback} route-reflector-client - % endif - neighbor ${neigh.loopback} send-community extended - % if node.bgp.ebgp_neighbors: - neighbor ${neigh.loopback} next-hop-self - % endif - % if loop.last: - exit-address-family - %else: - % endif -% endfor -! -## eBGP peers -% for neigh in node.bgp.ebgp_neighbors: -% if loop.first: -! ebgp -% endif - ! - neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn} - neighbor ${neigh.dst_int_ip} description eBGP to ${neigh.neighbor} - % if neigh.multihop: - neighbor ${neigh.dst_int_ip} ebgp-multihop ${neigh.multihop} - %endif -% if loop.last: -! -% endif -% endfor -! -## ******** -% if node.bgp.use_ipv4: - ! - address-family ipv4 -% for subnet in node.bgp.ipv4_advertise_subnets: - network ${subnet.network} mask ${subnet.netmask} -% endfor -%for peer in node.bgp.ipv4_peers: - neighbor ${peer.remote_ip} activate - % if peer.is_ebgp: - neighbor ${peer.remote_ip} send-community - %endif - % if peer.next_hop_self: - ## iBGP on an eBGP-speaker - neighbor ${peer.remote_ip} next-hop-self - % endif - % if peer.rr_client: - neighbor ${peer.remote_ip} route-reflector-client - % endif -%endfor - exit-address-family -% endif -% if node.bgp.use_ipv6: - ! - address-family ipv6 -% for subnet in node.bgp.ipv6_advertise_subnets: - network ${subnet} -% endfor -%for peer in node.bgp.ipv6_peers: - neighbor ${peer.remote_ip} activate - % if peer.is_ebgp: - neighbor ${peer.remote_ip} send-community - %endif - % if peer.next_hop_self: - ## iBGP on an eBGP-speaker - neighbor ${peer.remote_ip} next-hop-self - % endif -%endfor - exit-address-family -% endif -! -## VRFs -% for vrf in node.bgp.vrfs: -% if loop.first: -! vrfs -% endif - address-family ipv4 vrf ${vrf.vrf} -% for neigh in vrf.vrf_ibgp_neighbors: - % if loop.first: - % endif - % if neigh.use_ipv4: - neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn} - neighbor ${neigh.dst_int_ip} activate - neighbor ${neigh.dst_int_ip} as-override - ! - %endif -% endfor -% for neigh in vrf.vrf_ebgp_neighbors: - % if loop.first: - % endif - % if neigh.use_ipv4: - neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn} - neighbor ${neigh.dst_int_ip} activate - neighbor ${neigh.dst_int_ip} as-override - ! - %endif -% endfor - exit-address-family - ! -%endfor -! -% for route in node.ipv4_static_routes: -% if route.metric: -ip route ${route.prefix} ${route.netmask} ${route.nexthop} ${route.metric} -% else: -ip route ${route.prefix} ${route.netmask} ${route.nexthop} -%endif -% endfor -% for route in node.ipv6_static_routes: -% if route.metric: -ipv6 route ${route.prefix} ${route.nexthop} ${route.metric} -% else: -ipv6 route ${route.prefix} ${route.nexthop} -%endif -% endfor -! -end -% endif diff --git a/autonetkit/templates/linux/static_route.mako b/autonetkit/templates/linux/static_route.mako deleted file mode 100644 index 82100748..00000000 --- a/autonetkit/templates/linux/static_route.mako +++ /dev/null @@ -1,8 +0,0 @@ -# Static route Config generated on ${date} -! by ${version_banner} -% for route in node.static_routes_v4: -route add -net ${route.network} gw ${route.gw} dev ${route.interface} -%endfor -% for route in node.host_routes_v4: -route add -host ${route.prefix} gw ${route.gw} dev ${route.interface} -%endfor \ No newline at end of file diff --git a/autonetkit/templates/netkit_lab_conf.mako b/autonetkit/templates/netkit_lab_conf.mako deleted file mode 100644 index d822503f..00000000 --- a/autonetkit/templates/netkit_lab_conf.mako +++ /dev/null @@ -1,15 +0,0 @@ -LAB_DESCRIPTION="${topology.description}" -LAB_VERSION="${version_banner}" -LAB_AUTHOR="${topology.author}" -LAB_EMAIL="${topology.email}" -LAB_WEB="${topology.web}" - -machines="${topology.machines}" - -% for config_item in topology.config_items: -${config_item.device}[${config_item.key}]=${config_item.value} -%endfor - -% for tap in topology.tap_ips: -${tap.device}[${tap.id}]=tap,${topology.tap_host},${tap.ip} -%endfor diff --git a/autonetkit/templates/netkit_startup.mako b/autonetkit/templates/netkit_startup.mako deleted file mode 100644 index 8a966d17..00000000 --- a/autonetkit/templates/netkit_startup.mako +++ /dev/null @@ -1,25 +0,0 @@ -% for i in node.interfaces: -/sbin/ifconfig ${i.id} ${i.ipv4_address} netmask ${i.ipv4_subnet.netmask} broadcast ${i.ipv4_subnet.broadcast} up -% endfor -route del default -/sbin/ifconfig lo 127.0.0.1 up -/etc/init.d/ssh start -/etc/init.d/hostname.sh -% if node.zebra: -/etc/init.d/zebra start -% endif -% if node.ssh.use_key: -chown -R root:root /root -chmod 755 /root -chmod 755 /root/.ssh -chmod 644 /root/.ssh/authorized_keys -% endif -##TODO: make this toggle-able for telnet login -/etc/init.d/inetd restart -echo pts/0 >> /etc/securetty -echo pts/1 >> /etc/securetty -echo pts/2 >> /etc/securetty -echo pts/3 >> /etc/securetty -echo pts/4 >> /etc/securetty -echo pts/5 >> /etc/securetty -echo pts/6 >> /etc/securetty diff --git a/autonetkit/templates/quagga.conf.mako b/autonetkit/templates/quagga.conf.mako deleted file mode 100644 index edbf3298..00000000 --- a/autonetkit/templates/quagga.conf.mako +++ /dev/null @@ -1,116 +0,0 @@ -##Merged by AutoNetkit merge_quagga_conf on 20140115_162237 -% if node.zebra: -hostname ${node.hostname} -password ${node.zebra.password} -enable password ${node.zebra.password} -banner motd file /etc/quagga/motd.txt -% for static_route in node.zebra.static_routes: -! ${static_route.description} -ip route ${static_route.loopback} ${static_route.next_hop} -%endfor -! -% for interface in node.interfaces: -interface ${interface.id} - %if interface.ospf_cost: - #Link to ${interface.description} - ip ospf cost ${interface.ospf_cost} - ! - %endif - % if interface.isis: - ip router isis ${node.isis.process_id} - % if interface.physical: - ## level-2-only - why would I want only level 2 network wide? - isis circuit-type level-2-only - ## isis metric ${interface.isis_metric} - % if interface.isis_hello: - isis hello-interval ${interface.isis_hello} - % endif - % if interface.isis_metric: - isis metric ${interface.isis_metric} - % endif - % endif - % endif -% endfor -## Loopback -interface lo - description local loopback - ip address 127.0.0.1/8 - ip address ${node.loopback}/32 -! -%endif -% if node.bgp: -router bgp ${node.asn} - bgp router-id ${node.loopback} - no synchronization -% for subnet in node.bgp.ipv4_advertise_subnets: - network ${subnet.cidr} -% endfor -! ibgp -% for client in node.bgp.ibgp_rr_clients: -% if loop.first: - ! ibgp clients -% endif - ! ${client.neighbor} - neighbor ${client.loopback} remote-as ${node.asn} - neighbor ${client.loopback} update-source ${node.loopback} - neighbor ${client.loopback} route-reflector-client - neighbor ${client.loopback} send-community -% endfor -% for parent in node.bgp.ibgp_rr_parents: -% if loop.first: - ! ibgp route reflector servers -% endif - ! ${parent.neighbor} - neighbor ${parent.loopback} remote-as ${parent.asn} - neighbor ${parent.loopback} update-source ${node.loopback} - neighbor ${parent.loopback} send-community -% endfor -% for neigh in node.bgp.ibgp_neighbors: -% if loop.first: - ! ibgp peers -% endif - ! ${neigh.neighbor} - neighbor ${neigh.loopback} remote-as ${neigh.asn} - neighbor ${neigh.loopback} update-source ${node.loopback} - neighbor ${neigh.loopback} send-community - neighbor ${neigh.loopback} next-hop-self -% endfor -! ebgp -% for neigh in node.bgp.ebgp_neighbors: - ! ${neigh.neighbor} - neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn} - neighbor ${neigh.dst_int_ip} update-source ${neigh.local_int_ip} - neighbor ${neigh.dst_int_ip} send-community -% endfor -% endif - -% if node.bgp.debug: -debug bgp -debug bgp events -debug bgp filters -debug bgp fsm -debug bgp keepalives -debug bgp updates -log file /var/log/zebra/bgpd.log -% endif -! -% if node.ospf: -router ospf -% for ospf_link in node.ospf.ospf_links: - network ${ospf_link.network.cidr} area ${ospf_link.area} -% endfor - ! -% for passive_interface in node.ospf.passive_interfaces: - passive-interface ${passive_interface.id} -% endfor - ! - network ${node.loopback_subnet} area 0 -% endif -! -% if node.isis: -router isis ${node.isis.process_id} - net ${node.isis.net} - metric-style wide -% endif -! -log file /var/log/zebra/zebra.log diff --git a/autonetkit/templates/quagga/etc/hostname.mako b/autonetkit/templates/quagga/etc/hostname.mako deleted file mode 100644 index edee2ee6..00000000 --- a/autonetkit/templates/quagga/etc/hostname.mako +++ /dev/null @@ -1 +0,0 @@ -${node.hostname} diff --git a/autonetkit/templates/quagga/etc/shadow b/autonetkit/templates/quagga/etc/shadow deleted file mode 100644 index bb606a40..00000000 --- a/autonetkit/templates/quagga/etc/shadow +++ /dev/null @@ -1,36 +0,0 @@ -root:$1$LB0MUyNC$4y8CpiP0PjymXi.p.M8fg/:14558:0:99999:7::: -daemon:*:14219:0:99999:7::: -bin:*:14219:0:99999:7::: -sys:*:14219:0:99999:7::: -sync:*:14219:0:99999:7::: -games:*:14219:0:99999:7::: -man:*:14219:0:99999:7::: -lp:*:14219:0:99999:7::: -mail:*:14219:0:99999:7::: -news:*:14219:0:99999:7::: -uucp:*:14219:0:99999:7::: -proxy:*:14219:0:99999:7::: -www-data:*:14219:0:99999:7::: -backup:*:14219:0:99999:7::: -list:*:14219:0:99999:7::: -irc:*:14219:0:99999:7::: -gnats:*:14219:0:99999:7::: -nobody:*:14219:0:99999:7::: -libuuid:!:14219:0:99999:7::: -bind:*:14219:0:99999:7::: -messagebus:*:14219:0:99999:7::: -dnsmasq:*:14219:0:99999:7::: -Debian-exim:!:14219:0:99999:7::: -freerad:*:14219:0:99999:7::: -statd:*:14219:0:99999:7::: -sshd:*:14219:0:99999:7::: -pdns:!:14219:0:99999:7::: -proftpd:!:14219:0:99999:7::: -ftp:*:14219:0:99999:7::: -quagga:*:14219:0:99999:7::: -snmp:*:14219:0:99999:7::: -snort:*:14219:0:99999:7::: -telnetd:*:14219:0:99999:7::: -uml-net:*:14219:0:99999:7::: -xorp:*:14219:0:99999:7::: -guest:nlbMnI.VQBS3s:14219:0:99999:7::: \ No newline at end of file diff --git a/autonetkit/templates/quagga/etc/ssh/sshd_config b/autonetkit/templates/quagga/etc/ssh/sshd_config deleted file mode 100644 index 633b381a..00000000 --- a/autonetkit/templates/quagga/etc/ssh/sshd_config +++ /dev/null @@ -1,2 +0,0 @@ -UseDNS no -LogLevel DEBUG \ No newline at end of file diff --git a/autonetkit/templates/quagga/etc/zebra/bgpd.conf.mako b/autonetkit/templates/quagga/etc/zebra/bgpd.conf.mako deleted file mode 100644 index 8dac86c6..00000000 --- a/autonetkit/templates/quagga/etc/zebra/bgpd.conf.mako +++ /dev/null @@ -1,61 +0,0 @@ -! -hostname ${node.hostname} -password ${node.zebra.password} -banner motd file /etc/quagga/motd.txt -!enable password ${node.zebra.password} -! -% if node.bgp: - router bgp ${node.asn} - bgp router-id ${node.loopback} - no synchronization -% for subnet in node.bgp.ipv4_advertise_subnets: - network ${subnet.cidr} -% endfor -! ibgp -% for client in node.bgp.ibgp_rr_clients: -% if loop.first: - ! ibgp clients -% endif - ! ${client.neighbor} - neighbor ${client.loopback} remote-as ${node.asn} - neighbor ${client.loopback} update-source ${node.loopback} - neighbor ${client.loopback} route-reflector-client - neighbor ${client.loopback} send-community -% endfor -% for parent in node.bgp.ibgp_rr_parents: -% if loop.first: - ! ibgp route reflector servers -% endif - ! ${parent.neighbor} - neighbor ${parent.loopback} remote-as ${parent.asn} - neighbor ${parent.loopback} update-source ${node.loopback} - neighbor ${parent.loopback} send-community -% endfor -% for neigh in node.bgp.ibgp_neighbors: -% if loop.first: - ! ibgp peers -% endif - ! ${neigh.neighbor} - neighbor ${neigh.loopback} remote-as ${neigh.asn} - neighbor ${neigh.loopback} update-source ${node.loopback} - neighbor ${neigh.loopback} send-community - neighbor ${neigh.loopback} next-hop-self -% endfor -! ebgp -% for neigh in node.bgp.ebgp_neighbors: - ! ${neigh.neighbor} - neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn} - neighbor ${neigh.dst_int_ip} update-source ${neigh.local_int_ip} - neighbor ${neigh.dst_int_ip} send-community -% endfor -% endif - -% if node.bgp.debug: -debug bgp -debug bgp events -debug bgp filters -debug bgp fsm -debug bgp keepalives -debug bgp updates -log file /var/log/zebra/bgpd.log -% endif diff --git a/autonetkit/templates/quagga/etc/zebra/daemons.mako b/autonetkit/templates/quagga/etc/zebra/daemons.mako deleted file mode 100644 index 51b24685..00000000 --- a/autonetkit/templates/quagga/etc/zebra/daemons.mako +++ /dev/null @@ -1,42 +0,0 @@ -# daemons file -% if node.zebra: -zebra=yes -%else: -zebra=no -%endif -## -% if node.bgp: -bgpd=yes -%else: -bgpd=no -%endif -## -% if node.rip: -ripd=yes -%else: -ripd=no -%endif -## -% if node.ospf6: -ospf6d=yes -%else: -ospf6d=no -%endif -## -% if node.ospf: -ospfd=yes -%else: -ospfd=no -%endif -## -% if node.ripngd: -ripngd=yes -%else: -ripngd=no -%endif -## -% if node.isis: -isisd=yes -%else: -isisd=no -%endif diff --git a/autonetkit/templates/quagga/etc/zebra/isisd.conf.mako b/autonetkit/templates/quagga/etc/zebra/isisd.conf.mako deleted file mode 100644 index 79757153..00000000 --- a/autonetkit/templates/quagga/etc/zebra/isisd.conf.mako +++ /dev/null @@ -1,27 +0,0 @@ -hostname ${node.hostname} -password ${node.zebra.password} -!log stdout -% for interface in node.interfaces: - % if interface.isis: -interface ${interface.id} - ip router isis ${node.isis.process_id} - % if interface.physical: - ## level-2-only - why would I want only level 2 network wide? - isis circuit-type level-2-only - ## isis metric ${interface.isis_metric} - % if interface.isis_hello: - isis hello-interval ${interface.isis_hello} - % endif - % if interface.isis_metric: - isis metric ${interface.isis_metric} - % endif - % endif - % endif -% endfor -! -% if node.isis: -router isis ${node.isis.process_id} - net ${node.isis.net} - metric-style wide -% endif -! diff --git a/autonetkit/templates/quagga/etc/zebra/motd.txt.mako b/autonetkit/templates/quagga/etc/zebra/motd.txt.mako deleted file mode 100644 index e1117146..00000000 --- a/autonetkit/templates/quagga/etc/zebra/motd.txt.mako +++ /dev/null @@ -1,3 +0,0 @@ - -Welcome to Quagga configured on ${date} by ${version_banner} -Password is ${node.zebra.password} diff --git a/autonetkit/templates/quagga/etc/zebra/ospfd.conf.mako b/autonetkit/templates/quagga/etc/zebra/ospfd.conf.mako deleted file mode 100644 index 46830394..00000000 --- a/autonetkit/templates/quagga/etc/zebra/ospfd.conf.mako +++ /dev/null @@ -1,26 +0,0 @@ -hostname ${node.hostname} -password ${node.zebra.password} -banner motd file /etc/quagga/motd.txt -! -% for interface in node.interfaces: - %if interface.ospf_cost: - interface ${interface.id} - #Link to ${interface.description} - ip ospf cost ${interface.ospf_cost} - ! - %endif -%endfor -! -% if node.ospf: -router ospf -% for ospf_link in node.ospf.ospf_links: - network ${ospf_link.network.cidr} area ${ospf_link.area} -% endfor - ! -% for passive_interface in node.ospf.passive_interfaces: - passive-interface ${passive_interface.id} -% endfor - ! - network ${node.loopback_subnet} area 0 -% endif -! diff --git a/autonetkit/templates/quagga/etc/zebra/zebra.conf.mako b/autonetkit/templates/quagga/etc/zebra/zebra.conf.mako deleted file mode 100644 index 6bf69497..00000000 --- a/autonetkit/templates/quagga/etc/zebra/zebra.conf.mako +++ /dev/null @@ -1,20 +0,0 @@ -% if node.zebra: -hostname ${node.hostname} -password ${node.zebra.password} -enable password ${node.zebra.password} -banner motd file /etc/quagga/motd.txt -% for static_route in node.zebra.static_routes: -! ${static_route.description} -ip route ${static_route.loopback} ${static_route.next_hop} -%endfor -! -## Loopback -interface lo -description local loopback -ip address 127.0.0.1/8 -ip address ${node.loopback}/32 -! - - -log file /var/log/zebra/zebra.log -%endif \ No newline at end of file diff --git a/autonetkit/templates/quagga/root/.ssh/authorized_keys b/autonetkit/templates/quagga/root/.ssh/authorized_keys deleted file mode 100644 index 56ec3114..00000000 --- a/autonetkit/templates/quagga/root/.ssh/authorized_keys +++ /dev/null @@ -1,2 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAqzv6+xZTfiHsijRVfHzomLWzASiF8RwdQTJAlLJ/pTkNtT+vgZPG5ZIK6dN/EJQMHS+uawV6CaG1MUrR9cTGVrAitdr1twI6ppPSVPyUGqJZjvgwa17O4eHGrzQWZz5t1I2aTFfEvYlLVhyJzaDJg0gvB0mwlYBLbubDmQZjDXA87JtGwW7Lus+ohC8NatA1Focm6Hl1ST0tQwaTRAsBTsnXrVlfm8PN5DdK/LU6U6YJ16Z5syCTHrCZkJZq+BfWcxZHnt+Ump9Ut1l7uYjSwdlJtpjpqRbBkeBKFSsXBHMKaqHHozte/qNMs2TZrnEI5w4pzYwpMzpzW587F6uWNw== ubuntu@netkit2 -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSbuZkHaYXweHzpRrXwBAZZTO8xMrIECyap3kBrVP0U/rsbIeaTmvwqaXxFsTCmU1GJXBzaSLlc5RVU8NxFJjKpK9KwQjAnr+4j0DtShSn+AVjS2/SfgCf5niplkhj+p7dLMQZNE4nzWvCYibwWxbDBBMHRpQ7BT9Ykd5rnsebEEoLblGQHbNlynRVIBFmWX+sIIsX9dmPVCUBx6QCCe2AIuTuwQS0ceKLWbNfvpP/k+kQ6Z3jcDX+QHFZYipPP3vdKVbpg1VRFu7ErUHP2zsuff9JmIm/kfHL8T++KkQl6C8wlkOhiIk3VU1lm8oPQRCkUrQxeP1IFz0xqHqCcy9v sk2@ubuntu diff --git a/autonetkit/topologies/.ipynb_checkpoints/Untitled0-checkpoint.ipynb b/autonetkit/topologies/.ipynb_checkpoints/Untitled0-checkpoint.ipynb deleted file mode 100644 index 4e523fd5..00000000 --- a/autonetkit/topologies/.ipynb_checkpoints/Untitled0-checkpoint.ipynb +++ /dev/null @@ -1,9 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:a4e3dcc9061b2ee4501b5221065fe951ffb3baeeea9e39226350d8dbf1f5f905" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [] -} \ No newline at end of file diff --git a/autonetkit/topologies/__init__.py b/autonetkit/topologies/__init__.py deleted file mode 100644 index 48c5860c..00000000 --- a/autonetkit/topologies/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from autonetkit.topologies.mixed_topology import mixed as mixed -from autonetkit.topologies.multi_edge_topology import multi_edge as multi_edge -from autonetkit.topologies.multi_as_topology import multi_as as multi_as -from autonetkit.topologies.four_chain_topology import four_chain as four_chain -from autonetkit.topologies.house_topology import house as house \ No newline at end of file diff --git a/autonetkit/topologies/four_chain_topology.py b/autonetkit/topologies/four_chain_topology.py deleted file mode 100644 index aca6b9cb..00000000 --- a/autonetkit/topologies/four_chain_topology.py +++ /dev/null @@ -1,30 +0,0 @@ -def four_chain(): - """Returns anm with input and physical as house graph""" - import autonetkit - anm = autonetkit.NetworkModel() - - g_in = anm.add_overlay("input") - - router_ids = ["r1", "r2", "r3", "r4"] - g_in.add_nodes_from(router_ids) - - g_in.update(device_type = "router") - g_in.update(asn = 1) - - positions = {'r1': (101, 250), 'r2': (100, 500), 'r3': (100, 750), 'r4': (100, 1000)} - for node in g_in: - node.x = positions[node][0] - node.y = positions[node][1] - eth0 = node.add_interface("eth0") - eth0.speed = 100 - - # node->node edges - input_edges = [("r1", "r2"), ("r2", "r4"), ("r3", "r4")] - input_interface_edges = [(g_in.node(src).interface(1), g_in.node(dst).interface(1)) for src, dst in input_edges] - g_in.add_edges_from(input_interface_edges) - - g_phy = anm['phy'] - g_phy.add_nodes_from(g_in, retain=["device_type", "x", "y", "asn"]) - g_phy.add_edges_from(g_in.edges()) - - return anm diff --git a/autonetkit/topologies/graphml2json.py b/autonetkit/topologies/graphml2json.py deleted file mode 100644 index 83e2ea37..00000000 --- a/autonetkit/topologies/graphml2json.py +++ /dev/null @@ -1,20 +0,0 @@ -import sys - - -def convert(): - filename = sys.argv[1] - with open(filename, "r") as fh: - data = fh.read() - - from autonetkit.build_network import build, load - graph = load(data) - anm = build(graph) - - - import autonetkit - g_in_nx = anm['input']._graph - - output = autonetkit.load.load_json.nx_to_simple(g_in_nx) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/autonetkit/topologies/house_topology.py b/autonetkit/topologies/house_topology.py deleted file mode 100644 index cb2b372e..00000000 --- a/autonetkit/topologies/house_topology.py +++ /dev/null @@ -1,88 +0,0 @@ -def house(): - """Returns anm with input and physical as house graph""" - from networkx.readwrite import json_graph - import networkx as nx - import autonetkit - # returns a house graph - data = {'directed': False, - 'graph': [], - 'links': [{'_ports': {'r4': 2, 'r5': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 1}, - {'_ports': {'r2': 3, 'r4': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 3}, - {'_ports': {'r3': 3, 'r5': 2}, - 'raw_interfaces': {}, - 'source': 1, - 'target': 4}, - {'_ports': {'r1': 1, 'r2': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 3}, - {'_ports': {'r1': 2, 'r3': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 4}, - {'_ports': {'r2': 2, 'r3': 2}, - 'raw_interfaces': {}, - 'source': 3, - 'target': 4}], - 'multigraph': False, - 'nodes': [{'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r4 to r2', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r4 to r5', 'id': 'eth1'}}, - 'asn': 2, - 'device_type': 'router', - 'id': 'r4', - 'label': 'r4', - 'x': 675, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r5 to r4', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r5 to r3', 'id': 'eth1'}}, - 'asn': 2, - 'device_type': 'router', - 'id': 'r5', - 'label': 'r5', - 'x': 675, - 'y': 500}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r1 to r2', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r1 to r3', 'id': 'eth1'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r1', - 'label': 'r1', - 'x': 350, - 'y': 400}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r2 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r2 to r3', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r2 to r4', 'id': 'eth2'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r2', - 'label': 'r2', - 'x': 500, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r3 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r3 to r2', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r3 to r5', 'id': 'eth2'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r3', - 'label': 'r3', - 'x': 500, - 'y': 500}]} - graph = json_graph.node_link_graph(data) - anm = autonetkit.anm.NetworkModel() - g_in = anm.add_overlay("input") - g_in._replace_graph(nx.Graph(graph)) - #TODO: check if should build overlays here rather than clone in? - g_phy = anm["phy"] - g_phy._replace_graph(graph) - return anm \ No newline at end of file diff --git a/autonetkit/topologies/mixed_topology.py b/autonetkit/topologies/mixed_topology.py deleted file mode 100644 index d0baa6e2..00000000 --- a/autonetkit/topologies/mixed_topology.py +++ /dev/null @@ -1,85 +0,0 @@ -def mixed(): - """Returns anm with input and physical as house graph""" - from networkx.readwrite import json_graph - import networkx as nx - import autonetkit - # returns a house graph - data = {'directed': False, - 'graph': [], - 'links': [ - {'_ports': {'r1': 3, 'r3': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 3}, - {'_ports': {'r2': 3, 's1': 2}, - 'raw_interfaces': {}, - 'source': 1, - 'target': 4}, - {'_ports': {'sw1': 1, 'r1': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 3}, - {'_ports': {'sw1': 2, 'r2': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 4}, - {'_ports': {'r1': 2, 'r2': 2}, - 'raw_interfaces': {}, - 'source': 3, - 'target': 4}], - 'multigraph': False, - 'nodes': [{'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r3 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r3 to s1', 'id': 'eth1'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r3', - 'label': 'r3', - 'x': 675, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 's1 to r3', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 's1 to r2', 'id': 'eth1'}}, - 'asn': 1, - 'device_type': 'server', - 'id': 's1', - 'label': 's1', - 'x': 675, - 'y': 500}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'sw1 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'sw1 to r2', 'id': 'eth1'}}, - 'asn': 1, - 'device_type': 'switch', - 'id': 'sw1', - 'label': 'sw1', - 'x': 350, - 'y': 400}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r1 to sw1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r1 to r2', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r1 to r3', 'id': 'eth2'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r1', - 'label': 'r1', - 'x': 500, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r2 to sw1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r2 to r1', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r2 to s1', 'id': 'eth2'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r2', - 'label': 'r2', - 'x': 500, - 'y': 500}]} - graph = json_graph.node_link_graph(data) - anm = autonetkit.anm.NetworkModel() - g_in = anm.add_overlay("input") - g_in._replace_graph(nx.Graph(graph)) - #TODO: check if should build overlays here rather than clone in? - g_phy = anm["phy"] - g_phy._replace_graph(graph) - return anm \ No newline at end of file diff --git a/autonetkit/topologies/multi_as.json b/autonetkit/topologies/multi_as.json deleted file mode 100644 index e69de29b..00000000 diff --git a/autonetkit/topologies/multi_as_topology.py b/autonetkit/topologies/multi_as_topology.py deleted file mode 100644 index 0296c4e1..00000000 --- a/autonetkit/topologies/multi_as_topology.py +++ /dev/null @@ -1,378 +0,0 @@ -def multi_as(): - """Returns anm with input and physical as house graph""" - import autonetkit - import networkx as nx - from autonetkit.load.load_json import simple_to_nx - # returns a house graph - data = {'directed': False, - 'graph': [('node_default', - {'asn': 1, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'None', - 'ibgp_role': u'None', - 'igp': 'ospf', - 'ospf_area': 0, - 'platform': 'netkit'}), - ('address_family', 'v4'), - ('file_type', 'graphml'), - ('enable_routing', True), - ('edge_default', {'ospf_cost': 1, 'type': 'physical'}), - ('specified_int_names', False)], - 'links': [{'dst': 'r2', - 'dst_port': '_port1', - 'id': 'e5', - 'src': 'r1', - 'src_port': '_port1', - 'type': 'physical'}, - {'dst': 'r8', - 'dst_port': '_port1', - 'id': 'e10', - 'src': 'r10', - 'src_port': '_port1', - 'type': 'physical'}, - {'dst': 'r9', - 'dst_port': '_port1', - 'id': 'e11', - 'src': 'r10', - 'src_port': '_port2', - 'type': 'physical'}, - {'dst': 'r2', - 'dst_port': '_port2', - 'id': 'e4', - 'src': 'r3', - 'src_port': '_port1', - 'type': 'physical'}, - {'dst': 'r1', - 'dst_port': '_port2', - 'id': 'e6', - 'src': 'r4', - 'src_port': '_port1', - 'type': 'physical'}, - {'dst': 'r2', - 'dst_port': '_port3', - 'id': 'e13', - 'src': 'r4', - 'src_port': '_port2', - 'type': 'physical'}, - {'dst': 'r5', - 'dst_port': '_port1', - 'id': 'e7', - 'src': 'r4', - 'src_port': '_port3', - 'type': 'physical'}, - {'dst': 'r7', - 'dst_port': '_port1', - 'id': 'e2', - 'src': 'r4', - 'src_port': '_port4', - 'type': 'physical'}, - {'dst': 'r3', - 'dst_port': '_port2', - 'id': 'e3', - 'src': 'r5', - 'src_port': '_port2', - 'type': 'physical'}, - {'dst': 'r6', - 'dst_port': '_port1', - 'id': 'e8', - 'src': 'r5', - 'src_port': '_port3', - 'type': 'physical'}, - {'dst': 'r8', - 'dst_port': '_port2', - 'id': 'e12', - 'src': 'r5', - 'src_port': '_port4', - 'type': 'physical'}, - {'dst': 'r8', - 'dst_port': '_port3', - 'id': 'e9', - 'src': 'r6', - 'src_port': '_port2', - 'type': 'physical'}, - {'dst': 'r8', - 'dst_port': '_port4', - 'id': 'e0', - 'src': 'r7', - 'src_port': '_port2', - 'type': 'physical'}, - {'dst': 'r9', - 'dst_port': '_port2', - 'id': 'e1', - 'src': 'r7', - 'src_port': '_port3', - 'type': 'physical'}], - 'multigraph': False, - 'nodes': [{'asn': 1, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'A', - 'ibgp_role': u'RRC', - 'id': 'r1', - 'igp': 'ospf', - 'label': 'r1', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r1 to r2', 'id': '_port1'}, - {'category': 'physical', 'description': 'r1 to r4', 'id': '_port2'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r1 to r2'}, - 2: {'category': 'physical', 'description': 'r1 to r4'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 555.0, - 'y': 285.0}, - {'asn': 3, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'None', - 'ibgp_role': u'Peer', - 'id': 'r10', - 'igp': 'ospf', - 'label': 'r10', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r10 to r8', 'id': '_port1'}, - {'category': 'physical', 'description': 'r10 to r9', 'id': '_port2'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r10 to r8'}, - 2: {'category': 'physical', 'description': 'r10 to r9'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 765.0, - 'y': 500.52000000000004}, - {'asn': 1, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'A', - 'ibgp_role': u'RRC', - 'id': 'r2', - 'igp': 'ospf', - 'label': 'r2', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r2 to r1', 'id': '_port1'}, - {'category': 'physical', - 'description': 'r2 to r3', 'id': '_port2'}, - {'category': 'physical', 'description': 'r2 to r4', 'id': '_port3'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r2 to r1'}, - 2: {'category': 'physical', 'description': 'r2 to r3'}, - 3: {'category': 'physical', 'description': 'r2 to r4'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 645.0, - 'y': 255.0}, - {'asn': 1, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'A', - 'ibgp_role': u'RRC', - 'id': 'r3', - 'igp': 'ospf', - 'label': 'r3', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r3 to r2', 'id': '_port1'}, - {'category': 'physical', 'description': 'r3 to r5', 'id': '_port2'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r3 to r2'}, - 2: {'category': 'physical', 'description': 'r3 to r5'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 765.0, - 'y': 255.0}, - {'asn': 1, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'A', - 'ibgp_role': u'RR', - 'id': 'r4', - 'igp': 'ospf', - 'label': 'r4', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r4 to r1', 'id': '_port1'}, - {'category': 'physical', - 'description': 'r4 to r2', 'id': '_port2'}, - {'category': 'physical', - 'description': 'r4 to r5', 'id': '_port3'}, - {'category': 'physical', 'description': 'r4 to r7', 'id': '_port4'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r4 to r1'}, - 2: {'category': 'physical', 'description': 'r4 to r2'}, - 3: {'category': 'physical', 'description': 'r4 to r5'}, - 4: {'category': 'physical', 'description': 'r4 to r7'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 645.0, - 'y': 345.0}, - {'asn': 1, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'A', - 'ibgp_role': u'RR', - 'id': 'r5', - 'igp': 'ospf', - 'label': 'r5', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r5 to r4', 'id': '_port1'}, - {'category': 'physical', - 'description': 'r5 to r3', 'id': '_port2'}, - {'category': 'physical', - 'description': 'r5 to r6', 'id': '_port3'}, - {'category': 'physical', 'description': 'r5 to r8', 'id': '_port4'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r5 to r4'}, - 2: {'category': 'physical', 'description': 'r5 to r3'}, - 3: {'category': 'physical', 'description': 'r5 to r6'}, - 4: {'category': 'physical', 'description': 'r5 to r8'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 765.0, - 'y': 345.0}, - {'asn': 2, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'None', - 'ibgp_role': u'None', - 'id': 'r6', - 'igp': 'ospf', - 'label': 'r6', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r6 to r5', 'id': '_port1'}, - {'category': 'physical', 'description': 'r6 to r8', 'id': '_port2'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r6 to r5'}, - 2: {'category': 'physical', 'description': 'r6 to r8'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 894.616, - 'y': 345.0}, - {'asn': 3, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'None', - 'ibgp_role': u'Peer', - 'id': 'r7', - 'igp': 'ospf', - 'label': 'r7', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r7 to r4', 'id': '_port1'}, - {'category': 'physical', - 'description': 'r7 to r8', 'id': '_port2'}, - {'category': 'physical', 'description': 'r7 to r9', 'id': '_port3'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r7 to r4'}, - 2: {'category': 'physical', 'description': 'r7 to r8'}, - 3: {'category': 'physical', 'description': 'r7 to r9'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 645.0, - 'y': 435.0}, - {'asn': 3, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'None', - 'ibgp_role': u'Peer', - 'id': 'r8', - 'igp': 'ospf', - 'label': 'r8', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r8 to r10', 'id': '_port1'}, - {'category': 'physical', - 'description': 'r8 to r5', 'id': '_port2'}, - {'category': 'physical', - 'description': 'r8 to r6', 'id': '_port3'}, - {'category': 'physical', 'description': 'r8 to r7', 'id': '_port4'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r8 to r10'}, - 2: {'category': 'physical', 'description': 'r8 to r5'}, - 3: {'category': 'physical', 'description': 'r8 to r6'}, - 4: {'category': 'physical', 'description': 'r8 to r7'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 765.0, - 'y': 435.0}, - {'asn': 3, - 'device_type': 'router', - 'host': 'localhost', - 'ibgp_l3_cluster': u'None', - 'ibgp_role': u'Peer', - 'id': 'r9', - 'igp': 'ospf', - 'label': 'r9', - 'ospf_area': 0, - 'platform': 'netkit', - 'ports': [{'category': 'loopback', - 'description': 'loopback', - 'id': 'Loopback0'}, - {'category': 'physical', - 'description': 'r9 to r10', 'id': '_port1'}, - {'category': 'physical', 'description': 'r9 to r7', 'id': '_port2'}], - 'raw_interfaces': {0: {'category': 'loopback', 'description': 'loopback'}, - 1: {'category': 'physical', 'description': 'r9 to r10'}, - 2: {'category': 'physical', 'description': 'r9 to r7'}}, - 'specified_int_names': None, - 'syntax': 'quagga', - 'vrf': None, - 'x': 645.0, - 'y': 500.52}]} - graph = simple_to_nx(data) - anm = autonetkit.anm.NetworkModel() - g_in = anm.add_overlay("input") - g_in._replace_graph(nx.Graph(graph)) - # TODO: check if should build overlays here rather than clone in? - g_phy = anm["phy"] - g_phy._replace_graph(graph) - return anm \ No newline at end of file diff --git a/autonetkit/topologies/multi_edge.json b/autonetkit/topologies/multi_edge.json deleted file mode 100644 index 3fa63cc8..00000000 --- a/autonetkit/topologies/multi_edge.json +++ /dev/null @@ -1,244 +0,0 @@ -{ - "directed": false, - "graph": [], - "nodes": [ - { - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r1 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r1 to r3", - "id": "eth1" - }, - { - "category": "physical", - "description": "r1 to r2", - "id": "eth2" - }, - { - "category": "physical", - "description": "r1 to r3", - "id": "eth3" - }, - { - "category": "physical", - "description": "r1 to r3", - "id": "eth4" - } - ], - "label": "r1", - "device_type": "router", - "y": 400, - "x": 350, - "id": "r1", - "asn": 1 - }, - { - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r2 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r2 to r3", - "id": "eth1" - }, - { - "category": "physical", - "description": "r2 to r4", - "id": "eth2" - }, - { - "category": "physical", - "description": "r2 to r4", - "id": "eth3" - }, - { - "category": "physical", - "description": "r2 to r1", - "id": "eth4" - } - ], - "label": "r2", - "device_type": "router", - "y": 300, - "x": 500, - "id": "r2", - "asn": 1 - }, - { - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r3 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r3 to r2", - "id": "eth1" - }, - { - "category": "physical", - "description": "r3 to r5", - "id": "eth2" - }, - { - "category": "physical", - "description": "r3 to r1", - "id": "eth3" - }, - { - "category": "physical", - "description": "r3 to r1", - "id": "eth4" - } - ], - "label": "r3", - "device_type": "router", - "y": 500, - "x": 500, - "id": "r3", - "asn": 1 - }, - { - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r4 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r4 to r5", - "id": "eth1" - }, - { - "category": "physical", - "description": "r4 to r2", - "id": "eth2" - } - ], - "label": "r4", - "device_type": "router", - "y": 300, - "x": 675, - "id": "r4", - "asn": 2 - }, - { - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r5 to r4", - "id": "eth0" - }, - { - "category": "physical", - "description": "r5 to r3", - "id": "eth1" - } - ], - "label": "r5", - "device_type": "router", - "y": 500, - "x": 675, - "id": "r5", - "asn": 2 - } - ], - "links": [ - { - "src": "r1", - "src_port": "eth0", - "dst": "r2", - "dst_port": "eth0" - }, - { - "src": "r1", - "src_port": "eth2", - "dst": "r2", - "dst_port": "eth4" - }, - { - "src": "r1", - "src_port": "eth1", - "dst": "r3", - "dst_port": "eth0" - }, - { - "src": "r1", - "src_port": "eth3", - "dst": "r3", - "dst_port": "eth3" - }, - { - "src": "r1", - "src_port": "eth4", - "dst": "r3", - "dst_port": "eth4" - }, - { - "src": "r2", - "src_port": "eth1", - "dst": "r3", - "dst_port": "eth1" - }, - { - "src": "r4", - "src_port": "eth0", - "dst": "r2", - "dst_port": "eth2" - }, - { - "src": "r4", - "src_port": "eth2", - "dst": "r2", - "dst_port": "eth3" - }, - { - "src": "r4", - "src_port": "eth1", - "dst": "r5", - "dst_port": "eth0" - }, - { - "src": "r5", - "src_port": "eth1", - "dst": "r3", - "dst_port": "eth2" - } - ], - "multigraph": true -} \ No newline at end of file diff --git a/autonetkit/topologies/multi_edge_topology.py b/autonetkit/topologies/multi_edge_topology.py deleted file mode 100644 index c571362c..00000000 --- a/autonetkit/topologies/multi_edge_topology.py +++ /dev/null @@ -1,112 +0,0 @@ -def multi_edge(): - """Returns anm with input and physical as house graph""" - from networkx.readwrite import json_graph - import networkx as nx - import autonetkit - # returns a house graph - data = {'directed': False, - 'graph': [], - 'links': [{'_ports': {'r4': 2, 'r5': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 1}, - {'_ports': {'r2': 3, 'r4': 1}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 3}, - {'_ports': {'r2': 4, 'r4': 3}, - 'raw_interfaces': {}, - 'source': 0, - 'target': 3}, - {'_ports': {'r3': 3, 'r5': 2}, - 'raw_interfaces': {}, - 'source': 1, - 'target': 4}, - {'_ports': {'r1': 1, 'r2': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 3}, - {'_ports': {'r1': 3, 'r2': 5}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 3}, - {'_ports': {'r1': 2, 'r3': 1}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 4}, - {'_ports': {'r1': 4, 'r3': 4}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 4}, - {'_ports': {'r1': 5, 'r3': 5}, - 'raw_interfaces': {}, - 'source': 2, - 'target': 4}, - {'_ports': {'r2': 2, 'r3': 2}, - 'raw_interfaces': {}, - 'source': 3, - 'target': 4}], - 'multigraph': True, - 'nodes': [{'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r4 to r2', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r4 to r5', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r4 to r2', 'id': 'eth2'}}, - 'asn': 2, - 'device_type': 'router', - 'id': 'r4', - 'label': 'r4', - 'x': 675, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r5 to r4', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r5 to r3', 'id': 'eth1'}}, - 'asn': 2, - 'device_type': 'router', - 'id': 'r5', - 'label': 'r5', - 'x': 675, - 'y': 500}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r1 to r2', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r1 to r3', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r1 to r2', 'id': 'eth2'}, - 4: {'category': 'physical', 'description': 'r1 to r3', 'id': 'eth3'}, - 5: {'category': 'physical', 'description': 'r1 to r3', 'id': 'eth4'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r1', - 'label': 'r1', - 'x': 350, - 'y': 400}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r2 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r2 to r3', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r2 to r4', 'id': 'eth2'}, - 4: {'category': 'physical', 'description': 'r2 to r4', 'id': 'eth3'}, - 5: {'category': 'physical', 'description': 'r2 to r1', 'id': 'eth4'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r2', - 'label': 'r2', - 'x': 500, - 'y': 300}, - {'_ports': {0: {'category': 'physical', 'description': None}, - 1: {'category': 'physical', 'description': 'r3 to r1', 'id': 'eth0'}, - 2: {'category': 'physical', 'description': 'r3 to r2', 'id': 'eth1'}, - 3: {'category': 'physical', 'description': 'r3 to r5', 'id': 'eth2'}, - 4: {'category': 'physical', 'description': 'r3 to r1', 'id': 'eth3'}, - 5: {'category': 'physical', 'description': 'r3 to r1', 'id': 'eth4'}}, - 'asn': 1, - 'device_type': 'router', - 'id': 'r3', - 'label': 'r3', - 'x': 500, - 'y': 500}]} - graph = json_graph.node_link_graph(data) - anm = autonetkit.anm.NetworkModel() - g_in = anm.add_overlay("input") - g_in._replace_graph(nx.MultiGraph(graph)) - # TODO: check if should build overlays here rather than clone in? - g_phy = anm["phy"] - g_phy._replace_graph(graph) - return anm diff --git a/autonetkit/untitled b/autonetkit/untitled deleted file mode 100644 index 73c4e93c..00000000 --- a/autonetkit/untitled +++ /dev/null @@ -1,238 +0,0 @@ -class AbstractNetworkModel(object): - - """""" - - def __init__(self): - """""" - - self._overlays = {} - self.add_overlay('phy') - self.add_overlay('graphics') - - self.label_seperator = '_' - self.label_attrs = ['label'] - self._build_node_label() - self.timestamp = time.strftime('%Y%m%d_%H%M%S', - time.localtime()) - - def __repr__(self): - """""" - - return 'ANM %s' % self.timestamp - - @property - def overlay_nx_graphs(self): - """""" - - return self._overlays - - def has_overlay(self, overlay_id): - """""" - - return overlay_id in self._overlays - - def dump(self): - import autonetkit.ank_json as ank_json - data = ank_json.jsonify_anm(self) - - # data = data.replace("\\n", "\n") - # data = data.replace('\\"', '\"') - - return data - - def save(self): - """""" - - # TODO: take optional filename as parameter - - import autonetkit.ank_json as ank_json - import os - import gzip - archive_dir = os.path.join('versions', 'anm') - if not os.path.isdir(archive_dir): - os.makedirs(archive_dir) - - data = ank_json.jsonify_anm(self) - json_file = 'anm_%s.json.gz' % self.timestamp - json_path = os.path.join(archive_dir, json_file) - log.debug('Saving to %s' % json_path) - with gzip.open(json_path, 'wb') as json_fh: - json_fh.write(data) - - def restore_latest(self, directory=None): - """Restores latest saved ANM""" - - import os - import glob - if not directory: - directory = os.path.join('versions', 'anm') - - glob_dir = os.path.join(directory, '*.json.gz') - pickle_files = glob.glob(glob_dir) - pickle_files = sorted(pickle_files) - try: - latest_file = pickle_files[-1] - except IndexError: - -# No files loaded - - log.warning('No previous ANM saved. Please compile new ANM') - return - self.restore(latest_file) - - def restore(self, pickle_file): - """""" - - import json - import gzip - import autonetkit.ank_json as ank_json - log.debug('Restoring %s' % pickle_file) - with gzip.open(pickle_file, 'r') as fh: - data = json.load(fh) - for (overlay_id, graph_data) in data.items(): - - self._overlays[overlay_id] = \ - ank_json.ank_json_loads(graph_data) - - ank_json.rebind_interfaces(self) - - @property - def _phy(self): - """""" - - return NmGraph(self, 'phy') - - def initialise_graph(self, graph): - """Sets input graph. Converts to undirected. - Initialises graphics overlay.""" - - # TODO: check why this isn't used in build_network - - graph = nx.Graph(graph) - g_graphics = self['graphics'] - g_in = self.add_overlay('input', graph=graph, directed=False) - g_graphics.add_nodes_from(g_in, retain=[ - 'x', - 'y', - 'device_type', - 'device_subtype', - 'pop', - 'label', - 'asn', - ]) - return g_in - - def add_overlay( - self, - name, - nodes=None, - graph=None, - directed=False, - multi_edge=False, - retain=None, - ): - """Adds overlay graph of name name""" - - if graph: - if not directed and graph.is_directed(): - log.info('Converting graph %s to undirected' % name) - graph = nx.Graph(graph) - elif directed: - - if multi_edge: - graph = nx.MultiDiGraph() - else: - graph = nx.DiGraph() - else: - if multi_edge: - graph = nx.MultiGraph() - else: - graph = nx.Graph() - - self._overlays[name] = graph - overlay = NmGraph(self, name) - overlay.allocate_interfaces() - if nodes: - retain = retain or [] # default is an empty list - overlay.add_nodes_from(nodes, retain) - - return overlay - - def overlays(self): - """""" - - return self._overlays.keys() - - def devices(self, *args, **kwargs): - """""" - - return self._phy.filter(*args, **kwargs) - - def __getitem__(self, key): - """""" - - return NmGraph(self, key) - - def node_label(self, node): - """Returns node label from physical graph""" - - return self.default_node_label(node) - - def _build_node_label(self): - """""" - - def custom_label(node): - return self.label_seperator.join(str(self._overlays['phy' - ].node[node.node_id].get(val)) for val in - self.label_attrs if self._overlays['phy' - ].node[node.node_id].get(val) is not None) - - self.node_label = custom_label - - def set_node_label(self, seperator, label_attrs): - """""" - - try: - label_attrs.lower() - label_attrs = [label_attrs] # was a string, put into list - except AttributeError: - pass # already a list - - self.label_seperator = seperator - self.label_attrs = label_attrs - - def dump_graph(self, graph): - """""" - - print '----Graph %s----' % graph - print 'Graph' - print self.dump_graph_data(graph) - print 'Nodes' - print self.dump_nodes(graph) - print 'Edges' - print self.dump_edges(graph) - - @staticmethod - def dump_graph_data(graph): - """""" - - debug_data = dict((key, val) for (key, val) in - sorted(graph._graph.graph.items())) - return pprint.pformat(debug_data) - - @staticmethod - def dump_nodes(graph): - """""" - - debug_data = dict((graph.node_label(node), data) for (node, - data) in graph._graph.nodes(data=True)) - return pprint.pformat(debug_data) - - @staticmethod - def dump_edges(graph): - """""" - - debug_data = dict(((graph.node_label(src), - graph.node_label(dst)), data) for (src, dst, - data) in graph._graph.edges(data=True)) - return pprint.pformat(debug_data) \ No newline at end of file diff --git a/autonetkit/webserver.py b/autonetkit/webserver.py deleted file mode 100644 index fd9e2e55..00000000 --- a/autonetkit/webserver.py +++ /dev/null @@ -1,424 +0,0 @@ -# based on -# http://reminiscential.wordpress.com/2012/04/07/realtime-notification-delivery-using-rabbitmq-tornado-and-websocket/ -import json -import logging -import os -import socket - -import autonetkit.config as config -import pkg_resources -import tornado -import tornado.websocket as websocket - - -class MyWebHandler(tornado.web.RequestHandler): - - def initialize(self, ank_accessor, singleuser_mode=False): - self.ank_accessor = ank_accessor - self.singleuser_mode = singleuser_mode - - def get(self): - # TODO: make thie echo the ank version, and suggest how to post - # topologies - self.write("Hello, world") - - def post(self): - data = self.get_argument('body', 'No data received') - data_type = self.get_argument('type', 'No data received') - if self.singleuser_mode: - uuid = "singleuser" - else: - uuid = self.get_argument('uuid', 'singleuser') - - # get listeners for this uuid - uuid_socket_listeners = self.application.socket_listeners[uuid] - - if data_type == "anm": - body_parsed = json.loads(data) - - # TODO: if single user mode then fall back to single user - - if False: # use to save the default.json - import gzip - vis_content = pkg_resources.resource_filename( - "autonetkit_vis", "web_content") - with gzip.open(os.path.join(vis_content, "default.json.gz"), "w") as fh: - json.dump(body_parsed, fh) - - self.ank_accessor.store_overlay(uuid, body_parsed) - logging.info("Updating listeners") - for listener in uuid_socket_listeners: - listener.update_overlay() - - elif data_type == "highlight": - body_parsed = json.loads(data) - for listener in uuid_socket_listeners: - listener.write_message({'highlight': body_parsed}) - else: - pass - - -class MyWebSocketHandler(websocket.WebSocketHandler): - - def initialize(self, ank_accessor, overlay_id, singleuser_mode=False): - """ Store the overlay_id this listener is currently viewing. - Used when updating.""" - self.ank_accessor = ank_accessor - self.overlay_id = overlay_id - self.uuid = None # set by the client - self.uuid_socket_listeners = set() - self.singleuser_mode = singleuser_mode - - def allow_draft76(self): - # for iOS 5.0 Safari - return True - - def open(self, *args, **kwargs): - # Tornado needs default (here is None) or throws exception that - # required argument not provided - if self.singleuser_mode: - uuid = "singleuser" - else: - uuid = self.get_argument("uuid", "singleuser") - - self.uuid = uuid - - self.uuid_socket_listeners = self.application.socket_listeners[uuid] - - logging.info("Client connected from %s" % self.request.remote_ip) - self.uuid_socket_listeners.add(self) - - def on_close(self): - self.uuid_socket_listeners.remove(self) - logging.info("Client disconnected from %s" % self.request.remote_ip) - try: - self.application.pc.remove_event_listener(self) - except AttributeError: - pass # no RabbitMQ server - try: - self.application.echo_server.remove_event_listener(self) - except AttributeError: - pass # no echo_server - - def on_message(self, message): - logging.info("Received message %s from websocket client" % message) - if "overlay_id" in message: - # TODO: form JSON on client side, use loads here - _, overlay_id = message.split("=") - self.overlay_id = overlay_id - self.update_overlay() - elif "overlay_list" in message: - body = json.dumps( - {'overlay_list': self.ank_accessor.overlay_list(self.uuid)}) - self.write_message(body) - elif "ip_allocations" in message: - pass - - def update_overlay(self): - body = self.ank_accessor.get_overlay(self.uuid, self.overlay_id) - self.write_message(body) - body = json.dumps( - {'overlay_list': self.ank_accessor.overlay_list(self.uuid)}) - self.write_message(body) - - -class AnkAccessor(): - - """ Used to store published topologies""" - - def __init__(self, maxlen=25, simplified_overlays=False): - from collections import deque - self.anm_index = {} - self.uuid_list = deque(maxlen=maxlen) # use for circular buffer - self.anm = {} - self.ip_allocation = {} - self.simplified_overlays = simplified_overlays -# try loading from vis directory - try: - import autonetkit_cisco_webui - default_file = pkg_resources.resource_filename( - "autonetkit_cisco_webui", "cisco.json.gz") - except ImportError: - vis_content = pkg_resources.resource_filename( - "autonetkit_vis", "web_content") - default_file = os.path.join(vis_content, "default.json.gz") - - try: - import gzip - fh = gzip.open(default_file, "r") - data = json.load(fh) - #data = json.loads(loaded) - self.anm_index['singleuser'] = data - except IOError, e: - logging.warning(e) - pass # use default blank anm - - def store_overlay(self, uuid, overlay_input): - logging.info("Storing overlay_input with UUID %s" % uuid) - - if self.simplified_overlays: - overlays_tidied = {} - - overlay_keys = [ - index for index, data in overlay_input.items() if len(data.get("nodes"))] - - keys_to_exclude = {"input", - "bgp", "ibgp", "ebgp", - "graphics", "ip", "nidb"} - overlay_keys = [ - k for k in overlay_keys if k not in keys_to_exclude] - - labels = { - "ibgp_v6": "iBGP v6", - "ibgp_v4": "iBGP v4", - "ibgp_vpn_v4": "iBGP VPN v4", - "ebgp_v6": "eBGP v6", - "mpls_te": "MPLS TE", - "mpls_ldp": "MPLS LDP", - "ebgp_v4": "eBGP v4", - "mpls_oam": "MPLS OAM", - "ipv4": "IP v4", - "ipv6": "IP v6", - "segment_routing": "Segment Routing", - "pce": "PCE", - "bgp_ls": "BGP LS", - "phy": "Physical", - "vrf": "VRF", - "isis": "IS-IS", - "bgp": "BGP", - "eigrp": "EIGRP", - "ebgp": "eBGP", - "ospf": "OSPF", - } - - # DIsable until all web engines are verified to support format (eg - # ank_cisco_webui) - labels = {} - - # Check if new uuid or updating previous uuid - for key in overlay_keys: - # use from labels if present - store_key = labels.get(key) or key - overlays_tidied[store_key] = overlay_input[key] - - else: - overlays_tidied = overlay_input - - # New uuid - if len(self.uuid_list) == self.uuid_list.maxlen: - # list is full - oldest_uuid = self.uuid_list.popleft() - logging.info("UUID list full, removing UUID %s" % oldest_uuid) - - try: - del self.anm_index[oldest_uuid] - except KeyError: - logging.warning("Unable to remove UUID %s" % oldest_uuid) - - # If uuid already present, then remove from the queue, and then add to the end - # This avoids erroneously removing recently updated (i.e. non-stale - # uuids) - if uuid in self.uuid_list: - logging.info("Removing UUID %s to add to end of queue" % uuid) - self.uuid_list.remove(uuid) - - self.uuid_list.append(uuid) - logging.info("Stored overlay with UUID %s" % uuid) - self.anm_index[uuid] = overlays_tidied - - def get_overlay(self, uuid, overlay_id): - logging.info("Getting overlay %s with UUID %s" % (overlay_id, uuid)) - try: - anm = self.anm_index[uuid] - except KeyError: - logging.warning("Unable to find topology with UUID %s" % uuid) - return "" - else: - try: - if overlay_id == "*": - return anm - # elif self.simplified_overlays and overlay_id == "phy": - # print "Returning physical for phy" - # return anm["Physical"] - else: - return anm[overlay_id] - except KeyError: - logging.warning( - "Unable to find overlay %s in topoplogy with UUID %s" % (overlay_id, uuid)) - - def overlay_list(self, uuid): - logging.info("Trying for anm list with UUID %s" % uuid) - try: - anm = self.anm_index[uuid] - except KeyError: - logging.warning("Unable to find topology with UUID %s" % uuid) - return [""] - - if not len(anm): - return [""] - - return sorted(anm.keys(), key=lambda x: str(x[0]).lower()) - - def __getitem__(self, key): - try: - return self.anm[key] - except KeyError: - return json.dumps(["No ANM loaded"]) - - def ip_allocations(self): - return self.ip_allocation - - -class IndexHandler(tornado.web.RequestHandler): - - """Used to treat index.html as a template and substitute the uuid parameter for the websocket call - """ - - def initialize(self, path): - self.content_path = path - - def get(self): - # if not set, use default uuid of "singleuser" - uuid = self.get_argument("uuid", "singleuser") - # Note agent detection is usually *not* the solution to the problem, see: - # https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent - user_agent = self.request.headers.get("User-Agent") - formatted_url = "http://%s/?uuid=%s" % (self.request.host, uuid) - logging.info(user_agent) - supported_browsers = ", ".join(["Google Chrome", "Mozilla Firefox"]) - unsupported_browsers = ["MSIE 5.0", "MSIE 6.0", "MSIE 7.0", - # "Chrome", # testing - #"Mozilla", - "MSIE 8.0", "MSIE 9.0", "MSIE 10.0", "MSIE 11.0"] - if any(x in user_agent for x in unsupported_browsers): - # bad browser - fallback_template = ("""Your browser is not supported.
- Please try the following link in a supported browser:
- %s
-

- Supported browsers are %s. -

-
- """ % (formatted_url, formatted_url, supported_browsers)) - - template = os.path.join( - self.content_path, "unsupported_browser.html") - self.render(template, formatted_url=formatted_url, - supported_browsers=supported_browsers) - return - - logging.info("Rendering template with uuid %s" % uuid) - template = os.path.join(self.content_path, "index.html") - self.render(template, uuid=uuid) - - -def main(): - - try: - ANK_VERSION = pkg_resources.get_distribution("autonetkit").version - except pkg_resources.DistributionNotFound: - ANK_VERSION = "dev" - - import argparse - usage = "ank_webserver" - version = "%(prog)s using AutoNetkit " + str(ANK_VERSION) - parser = argparse.ArgumentParser(description=usage, version=version) - parser.add_argument( - '--port', type=int, help="Port to run webserver on (default 8000)") - parser.add_argument( - '--multi_user', action="store_true", default=False, help="Multi-User mode") - parser.add_argument('--ank_vis', action="store_true", - default=False, help="Force AutoNetkit visualisation system") - arguments = parser.parse_args() - -# check if most recent outdates current most recent - content_path = None - - try: - import autonetkit_vis - except ImportError: - pass # #TODO: logging no vis - else: - # use web content from autonetkit_cisco module - content_path = pkg_resources.resource_filename( - "autonetkit_vis", "web_content") - - # arguments.ank_vis = False # manually force for now - - simplified_overlays = False - if not arguments.ank_vis: - try: - import autonetkit_cisco_webui - simplified_overlays = True - except ImportError: - pass # use AutoNetkit internal web content - else: - # use web content from autonetkit_cisco module - content_path = pkg_resources.resource_filename( - "autonetkit_cisco_webui", "web_content") - - if not content_path: - logging.warning( - "No visualisation pages found: did you mean to install autonetkit_vis? Exiting...") - raise SystemExit - - settings = { - "static_path": content_path, - 'debug': False, - # otherwise content with folder /static won't get mapped - "static_url_prefix": "unused", - } - - singleuser_mode = False # default for now - if arguments.multi_user: - singleuser_mode = False - - if singleuser_mode: - logging.info("Running webserver in single-user mode") - - ank_accessor = AnkAccessor(simplified_overlays=simplified_overlays) - - # TODO: inherit the IndexHandler to switch based on browser version - application = tornado.web.Application([ - (r'/ws', MyWebSocketHandler, {"ank_accessor": ank_accessor, - 'singleuser_mode': singleuser_mode, - "overlay_id": "phy"}), - (r'/publish', MyWebHandler, {"ank_accessor": ank_accessor, - 'singleuser_mode': singleuser_mode, - }), - - # TODO: merge the two below into a single handler that captures both - # cases - (r'/', IndexHandler, {"path": settings['static_path']}), - (r'/index.html', IndexHandler, {"path": settings['static_path']}), - ("/(.*)", tornado.web.StaticFileHandler, - {"path": settings['static_path']}) - ], **settings) - - logging.getLogger().setLevel(logging.INFO) - - from collections import defaultdict - application.socket_listeners = defaultdict(set) # Indexed by uuid - - io_loop = tornado.ioloop.IOLoop.instance() - - port = config.settings['Http Post']['port'] - if arguments.port: - port = arguments.port # explicitly set on command line - - import time - timestamp = time.strftime("%Y %m %d_%H:%M:%S", time.localtime()) - logging.info("Starting on port %s at %s" % (port, timestamp)) - - try: - application.listen(port) - except socket.error, e: - if e.errno is 48: # socket in use - logging.warning( - "Unable to start webserver: socket in use for port %s" % port) - raise SystemExit - - io_loop.start() - -if __name__ == '__main__': - main() diff --git a/autonetkit/workflow.py b/autonetkit/workflow.py deleted file mode 100644 index dea1d1cb..00000000 --- a/autonetkit/workflow.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import os - -import autonetkit -import autonetkit.ank_json as ank_json -import autonetkit.config as config -import autonetkit.log as log -import autonetkit.render as render -from autonetkit.nidb import DeviceModel - - -import cProfile - -# from https://zapier.com/engineering/profiling-python-boss/ - - -def do_cprofile(func): - def profiled_func(*args, **kwargs): - profile = cProfile.Profile() - try: - profile.enable() - result = func(*args, **kwargs) - profile.disable() - return result - finally: - # profile.print_stats() - profile.dump_stats("profile") - return profiled_func - - -def file_monitor(filename): - """Generator based function to check if a file has changed""" - - last_timestamp = os.stat(filename).st_mtime - - while True: - timestamp = os.stat(filename).st_mtime - if timestamp > last_timestamp: - last_timestamp = timestamp - yield True - yield False - - -#@do_cprofile -def manage_network(input_graph_string, timestamp, build=True, - visualise=True, compile=True, validate=True, render=True, - monitor=False, deploy=False, measure=False, diff=False, - archive=False, grid=None, ): - """Build, compile, render network as appropriate""" - - # import build_network_simple as build_network - - import autonetkit.build_network as build_network - - if build: - if input_graph_string: - graph = build_network.load(input_graph_string) - elif grid: - graph = build_network.grid_2d(grid) - - # TODO: integrate the code to visualise on error (enable in config) - anm = None - try: - anm = build_network.build(graph) - except Exception, e: - # Send the visualisation to help debugging - try: - # if visualise: - # import autonetkit - # autonetkit.update_vis(anm) - #TODO: refactor so only update if config or compile not both - pass - except Exception, e: - # problem with vis -> could be coupled with original exception - - # raise original - log.warning("Unable to visualise: %s" % e) - raise # raise the original exception - else: - if visualise: - # log.info("Visualising network") - # import autonetkit - # autonetkit.update_vis(anm) - pass - - if not compile and visualise: - autonetkit.update_vis(anm) - pass - - if validate: - import autonetkit.ank_validate - try: - autonetkit.ank_validate.validate(anm) - except Exception, e: - log.warning('Unable to validate topologies: %s' % e) - log.debug('Unable to validate topologies', - exc_info=True) - - if compile: - if archive: - anm.save() - nidb = compile_network(anm) - autonetkit.update_vis(anm, nidb) - - #autonetkit.update_vis(anm, nidb) - log.debug('Sent ANM to web server') - if archive: - nidb.save() - - # render.remove_dirs(["rendered"]) - - if render: - import time - #start = time.clock() - autonetkit.render.render(nidb) - # print time.clock() - start - #import autonetkit.render2 - #start = time.clock() - # autonetkit.render2.render(nidb) - # print time.clock() - start - - if not (build or compile): - - # Load from last run - - import autonetkit.anm - anm = autonetkit.anm.NetworkModel() - anm.restore_latest() - nidb = DeviceModel() - nidb.restore_latest() - #autonetkit.update_vis(anm, nidb) - - if diff: - import autonetkit.diff - nidb_diff = autonetkit.diff.nidb_diff() - import json - data = json.dumps(nidb_diff, cls=ank_json.AnkEncoder, indent=4) - # log.info('Wrote diff to diff.json') - - # TODO: make file specified in config - - with open('diff.json', 'w') as fh: - fh.write(data) - - if deploy: - deploy_network(anm, nidb, input_graph_string) - - log.info('Configuration engine completed') # TODO: finished what? - - -#@do_cprofile -def compile_network(anm): - # log.info("Creating base network model") - nidb = create_nidb(anm) - g_phy = anm['phy'] - # log.info("Compiling to targets") - - for target_data in config.settings['Compile Targets'].values(): - host = target_data['host'] - platform = target_data['platform'] - if platform == 'netkit': - import autonetkit.compilers.platform.netkit as pl_netkit - platform_compiler = pl_netkit.NetkitCompiler(nidb, anm, - host) - elif platform == 'VIRL': - try: - import autonetkit_cisco.compilers.platform.cisco as pl_cisco - platform_compiler = pl_cisco.CiscoCompiler(nidb, anm, - host) - except ImportError: - log.debug('Unable to load VIRL platform compiler') - elif platform == 'dynagen': - import autonetkit.compilers.platform.dynagen as pl_dynagen - platform_compiler = pl_dynagen.DynagenCompiler(nidb, anm, - host) - elif platform == 'junosphere': - import autonetkit.compilers.platform.junosphere as pl_junosphere - platform_compiler = pl_junosphere.JunosphereCompiler(nidb, - anm, host) - - if any(g_phy.nodes(host=host, platform=platform)): - # log.info('Compiling configurations for %s on %s' - # % (platform, host)) - platform_compiler.compile() # only compile if hosts set - else: - log.debug('No devices set for %s on %s' % (platform, host)) - - return nidb - - -def create_nidb(anm): - - # todo: refactor this now with the layer2/layer2_bc graphs - what does nidb need? - # probably just layer2, and then allow compiled to access layer2_bc if - # need (eg netkit?) - - nidb = DeviceModel(anm) - - - return nidb - - -def deploy_network(anm, nidb, input_graph_string=None): - - # log.info('Deploying Network') - - deploy_hosts = config.settings['Deploy Hosts'] - for (hostname, host_data) in deploy_hosts.items(): - for (platform, platform_data) in host_data.items(): - if not any(nidb.nodes(host=hostname, platform=platform)): - log.debug('No hosts for (host, platform) (%s, %s), skipping deployment' - % (hostname, platform)) - continue - - if not platform_data['deploy']: - log.debug('Not deploying to %s on %s' % (platform, - hostname)) - continue - - config_path = os.path.join('rendered', hostname, platform) - - if hostname == 'internal': - try: - from autonetkit_cisco import deploy as cisco_deploy - except ImportError: - pass # development module, may not be available - if platform == 'VIRL': - create_new_xml = False - if not input_graph_string: - create_new_xml = True # no input, eg if came from grid - elif anm['input'].data['file_type'] == 'graphml': - create_new_xml = True # input from graphml, create XML - - if create_new_xml: - cisco_deploy.create_xml(anm, nidb, - input_graph_string) - else: - cisco_deploy.package(nidb, config_path, - input_graph_string) - continue - - username = platform_data['username'] - key_file = platform_data['key_file'] - host = platform_data['host'] - - if platform == 'netkit': - import autonetkit.deploy.netkit as netkit_deploy - tar_file = netkit_deploy.package(config_path, 'nklab') - netkit_deploy.transfer(host, username, tar_file, - tar_file, key_file) - netkit_deploy.extract( - host, - username, - tar_file, - config_path, - timeout=60, - key_filename=key_file, - parallel_count=10, - ) - if platform == 'VIRL': - - # TODO: check why using nklab here - - cisco_deploy.package(config_path, 'nklab') diff --git a/autonetkit/yaml_utils.py b/autonetkit/yaml_utils.py deleted file mode 100644 index ea42311b..00000000 --- a/autonetkit/yaml_utils.py +++ /dev/null @@ -1,27 +0,0 @@ -# YAML helpers from http://stackoverflow.com/questions/8640959 - -try: - import yaml -except ImportError: - import autonetkit.log as log - log.warning('Yaml Parsing requires pyyaml installed') - -from collections import OrderedDict - -class quoted(str): pass - -def quoted_presenter(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"') - -class literal(str): pass - -def literal_presenter(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') - -def ordered_dict_presenter(dumper, data): - return dumper.represent_dict(data.items()) - -def add_representers(): - yaml.add_representer(quoted, quoted_presenter) - yaml.add_representer(literal, literal_presenter) - yaml.add_representer(OrderedDict, ordered_dict_presenter) diff --git a/changelog.html b/changelog.html deleted file mode 100644 index 0bc04468..00000000 --- a/changelog.html +++ /dev/null @@ -1,4399 +0,0 @@ - - - - - - -Changelog - - - -
-

Changelog

- -
-

Testing release

-
    -
  • Merge pull request #242 from sk2/ACLs. [Simon Knight]

    -

    Ac ls

    -
  • -
  • Add tox. [Simon Knight]

    -
  • -
  • Merge pull request #241 from sk2/ACLs. [Simon Knight]

    -

    Ac ls

    -
  • -
-
-
-

v0.9.2 (2014-06-27)

-
-

Changes

-
    -
  • >= rather than == for deps. [Simon Knight]
  • -
  • Update info on ip blocks. [Simon Knight]
  • -
-
-
-
-

v0.9.1 (2014-05-13)

-
-

New

-
    -
  • User: record overlay dependencies to new overlay. [Simon Knight]
  • -
-
-
-

Changes

-
    -
  • Minor tweaks. [Simon Knight]

    -
  • -
  • Condense example. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Update docs for json example. [Simon Knight]

    -
  • -
  • Add Json to doc for gh-222. [Simon Knight]

    -
  • -
  • User: Initial commit of JSON load code for gh-222. [Simon Knight]

    -

    Example in tests/house.json if using dev build of ank: ` $ -autonetkit -f tests/house.json INFO VIRL Configuration Engine 0.9.0 -INFO AutoNetkit 0.9.0 INFO IPv4 allocations: Infrastructure: -10.0.0.0/8, Loopback: 192.168.0.0/22 INFO Allocating v4 Infrastructure -IPs INFO Allocating v4 Primary Host loopback IPs INFO Topology not -created for VIRL, skipping Cisco PCE design rules INFO Skipping iBGP -for iBGP disabled nodes: [] INFO All validation tests passed. INFO -Rendering Configuration Files INFO Finished ` if run in monitor -mode, then changes will be reflected live: ` $ ank_webserver ` -and then ` $ autonetkit -f tests/house.json —monitor `

    -
  • -
  • Updates. [Simon Knight]

    -
  • -
  • Rebuild doc. [Simon Knight]

    -
  • -
  • Increment year. [Simon Knight]

    -
  • -
  • Run sphinx-apidoc. [Simon Knight]

    -
  • -
  • Adding doctests. [Simon Knight]

    -
  • -
  • User: update_http -> update_vis. [Simon Knight]

    -
  • -
  • User: Initial support for multi-homed servers for VIRL-425. [Simon -Knight]

    -
  • -
  • Add initial support to auto-build dependency diagrams. [Simon Knight]

    -
  • -
-
-
-

Other

-
    -
  • Merge pull request #237 from oliviertilmans/patch-1. [Simon Knight]

    -

    The network might not run BGP

    -
  • -
  • The network might not run BGP. [oliviertilmans]

    -

    This result in a Key Error

    -
  • -
  • Merge pull request #236 from sk2/parallel. [Simon Knight]

    -

    Parallel

    -
  • -
  • Merge pull request #235 from sk2/ACLs. [Simon Knight]

    -

    Ac ls

    -
  • -
  • Revert "chg: dev: add ACL placeholder for gh-234" [Simon Knight]

    -

    This reverts commit 4941b893daf279e5bb24a0b54c10ed490db27716.

    -
  • -
  • Merge pull request #232 from sk2/render2.0. [Simon Knight]

    -

    Render2.0

    -
  • -
  • Add: dev: initial work to support vlans for gh-229. [Simon Knight]

    -
  • -
  • Update README.md. [Simon Knight]

    -
  • -
  • Merge pull request #228 from sk2/parallel. [Simon Knight]

    -

    chg: doc: minor tweaks

    -
  • -
  • Merge pull request #227 from sk2/parallel. [Simon Knight]

    -

    Parallel

    -
  • -
  • Update README.md. [Simon Knight]

    -
  • -
  • Update README.md. [Simon Knight]

    -
  • -
  • Merge pull request #226 from sk2/parallel. [Simon Knight]

    -

    Chg: doc: add Json to doc for gh-222

    -
  • -
  • Merge pull request #225 from sk2/parallel. [Simon Knight]

    -

    Chg: User: Initial commit of JSON load code for gh-222

    -
  • -
  • Merge pull request #224 from sk2/parallel. [Simon Knight]

    -

    Parallel

    -
  • -
  • Merge pull request #223 from sk2/parallel. [Simon Knight]

    -

    Parallel

    -
  • -
  • Merge pull request #221 from sk2/parallel. [Simon Knight]

    -

    Parallel

    -
  • -
-
-
-
-

v0.9.0 (2014-03-24)

-
-

New

-
    -
  • User: Support layer2 and layer3 overlays for gh-206. [Simon Knight]
  • -
-
-
-

Changes

-
    -
  • User: Sort nodes before allocation, closes gh-208. [Simon Knight]
  • -
  • User: label-based sort if present closes gh-207. [Simon Knight]
  • -
  • User: add ability to search for interfaces by their "id" string e.g. -"GigabitEthernet0/1" [Simon Knight]
  • -
-
-
-
-

v0.8.39 (2014-03-14)

-
-

Changes

-
    -
  • User: Allow custom config injection for VIRL-122. [Simon Knight]
  • -
-
-
-
-

v0.8.38 (2014-03-13)

-
-

Changes

-
    -
  • User: Handle IP addresses on L3 devices for VIRL-372. [Simon Knight]
  • -
  • User: support custom unsupported server templates for VIRL-378. [Simon -Knight]
  • -
  • User: add "external_connector" device_type for VIRL-672. [Simon -Knight]
  • -
  • User: only validate IP on Layer 3 devices for VIRL-672. [Simon Knight]
  • -
  • Allow specifying edge properties (color/width) [Simon Knight]
  • -
-
-
-
-

v0.8.37 (2014-03-11)

-
-

Fix

-
    -
  • User: Corrects bug with non-routers in EIGRP fixes VIRL-659. [Simon -Knight]

    -

    Had extra ISIS NET address line

    -
  • -
-
-
-
-

v0.8.35 (2014-03-10)

-
-

Changes

-
    -
  • Update warnings to not warn if no subnet/prefix set. [Simon Knight]
  • -
-
-
-
-

v0.8.33 (2014-03-05)

-
-

Changes

-
    -
  • User: Use ASN as process-id for IGP for VIRL-602. [Simon Knight]

    -

    Causes problems if multiple ASes connected across a multi-point -network.

    -
  • -
-
-
-

Other

-
    -
  • Add overview video. [Simon Knight]
  • -
-
-
-
-

v0.8.32 (2014-02-14)

-
-

New

-
    -
  • User: Allow manually specified IPv6 blocks for VIRL-481. [Simon -Knight]
  • -
-
-
-

Changes

-
    -
  • User: Config driven IP defaults, better logging of problems with -manually specified IPs. [Simon Knight]
  • -
  • User: Swap default IPv6 blocks for infra/loopback to be sequential. -[Simon Knight]
  • -
-
-
-
-

v0.8.31 (2014-02-14)

-
-

Changes

-
    -
  • User: Tidied up VRF role notification logic to aggregate by role. -VIRL-368. [Simon Knight]
  • -
  • User: Exclude BGP block if no iBGP/eBGP sessions. VIRL-564. [Simon -Knight]
  • -
-
-
-
-

v0.8.29 (2014-02-14)

-
-

New

-
    -
  • User: Warn that IPv6 MPLS VPNs not currently supported for VIRL-56. -[Simon Knight]
  • -
-
-
-

Changes

-
    -
  • User: update iBGP design rules for VIRL-558. [Simon Knight]
  • -
-
-
-

Fix

-
    -
  • User: Allow PE RRC nodes to participate in ibgp_vpn_v4. [Simon Knight]
  • -
-
-
-
-

v0.8.26 (2014-02-13)

-
-

Changes

-
    -
  • User: Add ibgp "peer" type for VIRL-558. [Simon Knight]
  • -
  • User: Clarify IPv4 allocation warning message for VIRL-550. [Simon -Knight]
  • -
  • User: list Interfaces as GigabitEthernet0/1.RR_2 instead of -(GigabitEthernet0/1, RR_2) [Simon Knight]
  • -
-
-
-
-

v0.8.17 (2014-01-31)

-
-

New

-
    -
  • User: Allow user-defined IPv6 IPs (infra + loopback) [Simon Knight]
  • -
-
-
-

Changes

-
    -
  • User: More descriptive logs for user-defined IPv6 addresses. [Simon -Knight]
  • -
-
-
-

Fix

-
    -
  • User: Bugfix for EIGRP IPv6 for VIRL-493. [Simon Knight]
  • -
-
-
-
-

v0.8.14 (2014-01-24)

-
-

New

-
    -
  • User: Warn if partial IPs set for VIRL-456. [Simon Knight]
  • -
  • User: Display human-readable ibgp_role for VIRL-469. [Simon Knight]
  • -
-
-
-
-

v0.8.12 (2014-01-22)

-
-

Changes

-
    -
  • User: Update logging. [Simon Knight]
  • -
-
-
-
-

v0.8.11 (2014-01-22)

-
-

Changes

-
    -
  • User: Tidy logging. [Simon Knight]
  • -
  • User: Warn for unsupported device features. [Simon Knight]
  • -
  • User: Use VIRL platform identifier instead of Cisco. [Simon Knight]
  • -
  • User: Tidy logging messages. [Simon Knight]
  • -
-
-
-
-

v0.8.10 (2014-01-21)

-
-

Fix

-
    -
  • Sort values neigh_most_frequent so tie-break chooses lowest. [Simon -Knight]

    -

    Addresses issue with stability in IP addressing: inter-asn links had a -collision domain that was arbitrarily being allocated to one or the -other ASN depending on the arbitarty position. This ensures the lowest -is always returned in a tie-break leading to repeatable addressing -(especially important for automated tests)

    -
  • -
-
-
-
-

v0.8.9 (2014-01-20)

-
    -
  • Merge branch 'master' of github.com:sk2/autonetkit. [Simon Knight]

    -

    Conflicts: .bumpversion.cfg autonetkit/render.py -setup.py

    -
  • -
  • Merge cleanup. [Simon Knight]

    -
  • -
-
-
-

v0.8.7 (2014-01-20)

-
    -
  • @chg: dev: renaming. [Simon Knight]
  • -
  • @chg: dev: renaming. [Simon Knight]
  • -
-
-
-

v0.8.6 (2014-01-20)

-
-

New

-
    -
  • User: Display address blocks to use for VIRL-350. [Simon Knight]
  • -
  • User: Display address blocks to use for VIRL-350. [Simon Knight]
  • -
-
-
-
-

v0.8.4 (2014-01-16)

-
-

New

-
    -
  • User: add per-element logging for GH-190. [Simon Knight]
  • -
  • User: add per-element logging for GH-190. [Simon Knight]
  • -
  • User: add single config for gh-189. [Simon Knight]
  • -
  • User: add single config for gh-189. [Simon Knight]
  • -
  • User: Screenshot capture for GH-188. [Simon Knight]
  • -
  • User: Screenshot capture for GH-188. [Simon Knight]
  • -
-
-
-

Other

-
    -
  • Change: dev: remove superseded config. [Simon Knight]
  • -
  • Change: dev: remove superseded config. [Simon Knight]
  • -
  • Change: dev: refactor XR OSPF by interfaces to common router_base. -[Simon Knight]
  • -
  • Change: dev: refactor XR OSPF by interfaces to common router_base. -[Simon Knight]
  • -
  • Update changelog. [Simon Knight]
  • -
  • Update changelog. [Simon Knight]
  • -
-
-
-
-

v0.8.3 (2014-01-13)

-
    -
  • Add gitchangelog support. [sk2]
  • -
  • Add gitchangelog support. [sk2]
  • -
  • Move indent correctly inside loop. [sk2]
  • -
  • Move indent correctly inside loop. [sk2]
  • -
  • Typo fix for ebgp not ibgp. [sk2]
  • -
  • Typo fix for ebgp not ibgp. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Add read me. [sk2]
  • -
  • Add read me. [sk2]
  • -
  • Update. [sk2]
  • -
  • Update. [sk2]
  • -
  • Add img. [sk2]
  • -
  • Add img. [sk2]
  • -
  • Examples. [sk2]
  • -
  • Examples. [sk2]
  • -
  • Add cwd to templates search dir. [sk2]
  • -
  • Add cwd to templates search dir. [sk2]
  • -
  • Add ordering. [sk2]
  • -
  • Add ordering. [sk2]
  • -
  • Add note. [sk2]
  • -
  • Add note. [sk2]
  • -
  • Support v6 bgp. [sk2]
  • -
  • Support v6 bgp. [sk2]
  • -
  • Add note. [sk2]
  • -
  • Add note. [sk2]
  • -
  • Add __ne__ and ordering. [sk2]
  • -
  • Add __ne__ and ordering. [sk2]
  • -
  • Support nidb nodes. [sk2]
  • -
  • Support nidb nodes. [sk2]
  • -
  • Add example. [sk2]
  • -
  • Add example. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Handle new overlays. [sk2]
  • -
  • Handle new overlays. [sk2]
  • -
  • New overlay. [sk2]
  • -
  • New overlay. [sk2]
  • -
  • Better json. [sk2]
  • -
  • Better json. [sk2]
  • -
  • Tutorial notebook. [sk2]
  • -
  • Tutorial notebook. [sk2]
  • -
  • Add new test case notes. [sk2]
  • -
  • Add new test case notes. [sk2]
  • -
  • Better debugging for templates. [sk2]
  • -
  • Better debugging for templates. [sk2]
  • -
  • More support for gh-186. [sk2]
  • -
  • More support for gh-186. [sk2]
  • -
  • Move mpls code to separate module. [sk2]
  • -
  • Move mpls code to separate module. [sk2]
  • -
  • More support for gh-186. [sk2]
  • -
  • More support for gh-186. [sk2]
  • -
  • Pep8. [sk2]
  • -
  • Pep8. [sk2]
  • -
  • Improvements to setting defaults on bunches. [sk2]
  • -
  • Improvements to setting defaults on bunches. [sk2]
  • -
  • Remove old demos. [sk2]
  • -
  • Remove old demos. [sk2]
  • -
  • Add config_stanza class for gh-186. [sk2]
  • -
  • Add config_stanza class for gh-186. [sk2]
  • -
  • Add option for stack_trace (useful for dev) [sk2]
  • -
  • Add option for stack_trace (useful for dev) [sk2]
  • -
  • Add browser test. [sk2]
  • -
  • Add browser test. [sk2]
  • -
  • Connectors. [sk2]
  • -
  • Connectors. [sk2]
  • -
-
-
-

v0.8.2 (2014-01-10)

-
    -
  • Remove old call to publish data. [sk2]
  • -
  • Remove old call to publish data. [sk2]
  • -
-
-
-

v0.8.1 (2014-01-10)

-
    -
  • Add note. [sk2]
  • -
  • Add note. [sk2]
  • -
  • Remove unused messaging functions. [sk2]
  • -
  • Remove unused messaging functions. [sk2]
  • -
  • Add log message. [sk2]
  • -
  • Add log message. [sk2]
  • -
  • Update anm tests. [sk2]
  • -
  • Update anm tests. [sk2]
  • -
  • Testing for anm. [sk2]
  • -
  • Testing for anm. [sk2]
  • -
  • Remove directed. [sk2]
  • -
  • Remove directed. [sk2]
  • -
  • Improving edge-case handling. [sk2]
  • -
  • Improving edge-case handling. [sk2]
  • -
  • Remove state for pickling - use json. [sk2]
  • -
  • Remove state for pickling - use json. [sk2]
  • -
  • Improving handling for custom overlays. [sk2]
  • -
  • Improving handling for custom overlays. [sk2]
  • -
  • Remove unused code. [sk2]
  • -
  • Remove unused code. [sk2]
  • -
-
-
-

v0.8.0 (2014-01-08)

-
    -
  • Remove old ibgp code. [sk2]
  • -
  • Remove old ibgp code. [sk2]
  • -
  • More removal of edge_id for gh-184. [sk2]
  • -
  • More removal of edge_id for gh-184. [sk2]
  • -
  • Closes gh-184. [sk2]
  • -
  • Closes gh-184. [sk2]
  • -
  • Pylint. [sk2]
  • -
  • Pylint. [sk2]
  • -
-
-
-

v0.7.33 (2014-01-08)

-
    -
  • Update warnings. [sk2]
  • -
  • Update warnings. [sk2]
  • -
-
-
-

v0.7.32 (2014-01-08)

-
    -
  • Add other AS subnets. [sk2]
  • -
  • Add other AS subnets. [sk2]
  • -
-
-
-

v0.7.31 (2014-01-07)

-
    -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Handle non-subnet collision domains (i.e. no nodes) [sk2]
  • -
  • Handle non-subnet collision domains (i.e. no nodes) [sk2]
  • -
  • Rename subtype. [sk2]
  • -
  • Rename subtype. [sk2]
  • -
  • Pass exception up. [sk2]
  • -
  • Pass exception up. [sk2]
  • -
-
-
-

v0.7.29 (2014-01-06)

-
    -
  • Use official subtypes. [sk2]
  • -
  • Use official subtypes. [sk2]
  • -
-
-
-

v0.7.27 (2014-01-04)

-
    -
  • Pep8. [sk2]
  • -
  • Pep8. [sk2]
  • -
-
-
-

v0.7.26 (2014-01-03)

-
    -
  • Add logging. [sk2]
  • -
  • Add logging. [sk2]
  • -
-
-
-

v0.7.25 (2014-01-03)

-
    -
  • Fix issue with uuids being re-used but discarded, update logging. -[sk2]
  • -
  • Fix issue with uuids being re-used but discarded, update logging. -[sk2]
  • -
-
-
-

v0.7.24 (2014-01-02)

-
    -
  • Catching overlay uuid deletion errors. [sk2]
  • -
  • Catching overlay uuid deletion errors. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Handling of interfaces in adding edges. [sk2]
  • -
  • Handling of interfaces in adding edges. [sk2]
  • -
  • Allow uuid to be specified in call. [sk2]
  • -
  • Allow uuid to be specified in call. [sk2]
  • -
  • Update connectors. [sk2]
  • -
  • Update connectors. [sk2]
  • -
  • Add comment. [sk2]
  • -
  • Add comment. [sk2]
  • -
  • Add script to build wheel. [sk2]
  • -
  • Add script to build wheel. [sk2]
  • -
  • Isort imports. [sk2]
  • -
  • Isort imports. [sk2]
  • -
-
-
-

v0.7.23 (2013-12-27)

-
    -
  • Enable telnet and ssh over vty. [sk2]
  • -
  • Enable telnet and ssh over vty. [sk2]
  • -
  • Tidying. [sk2]
  • -
  • Tidying. [sk2]
  • -
-
-
-

v0.7.20 (2013-12-23)

-
    -
  • Correct bug in writing static instead of host routes. [sk2]
  • -
  • Correct bug in writing static instead of host routes. [sk2]
  • -
-
-
-

v0.7.19 (2013-12-23)

-
    -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
-
-
-

v0.7.17 (2013-12-23)

-
    -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy logging. [sk2]
  • -
  • Tidy logging. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add tests. [sk2]
  • -
  • Add tests. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test. [sk2]
  • -
  • Add test topology. [sk2]
  • -
  • Add test topology. [sk2]
  • -
-
-
-

v0.7.16 (2013-12-20)

-
    -
  • Add extra onepk line. [sk2]
  • -
  • Add extra onepk line. [sk2]
  • -
-
-
-

v0.7.15 (2013-12-20)

-
    -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
-
-
-

v0.7.14 (2013-12-20)

-
    -
  • Return the node label rendered rather than node_id for repr of -interfaces. [sk2]
  • -
  • Return the node label rendered rather than node_id for repr of -interfaces. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
-
-
-

v0.7.13 (2013-12-19)

-
    -
  • If exception, send the visualisation that was constructed to help -debug. [sk2]
  • -
  • If exception, send the visualisation that was constructed to help -debug. [sk2]
  • -
  • Return nonzero if error. [sk2]
  • -
  • Return nonzero if error. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Add top-level exception capturing. [sk2]
  • -
  • Add top-level exception capturing. [sk2]
  • -
-
-
-

v0.7.12 (2013-12-19)

-
    -
  • Revert out. [sk2]
  • -
  • Revert out. [sk2]
  • -
  • Onepk syntax change. [sk2]
  • -
  • Onepk syntax change. [sk2]
  • -
  • Remove todo. [sk2]
  • -
  • Remove todo. [sk2]
  • -
  • More descriptive error message for mismatched subnets. [sk2]
  • -
  • More descriptive error message for mismatched subnets. [sk2]
  • -
-
-
-

v0.7.10 (2013-12-18)

-
    -
  • Copy label across to ipv4 and v6 graphs for display. [sk2]
  • -
  • Copy label across to ipv4 and v6 graphs for display. [sk2]
  • -
-
-
-

v0.7.9 (2013-12-18)

-
    -
  • Add yaml helpers for multiline strings. [sk2]
  • -
  • Add yaml helpers for multiline strings. [sk2]
  • -
  • Default handler. [sk2]
  • -
  • Default handler. [sk2]
  • -
  • Add validate catch. [sk2]
  • -
  • Add validate catch. [sk2]
  • -
  • Handle no routing. [sk2]
  • -
  • Handle no routing. [sk2]
  • -
-
-
-

v0.7.8 (2013-12-12)

-
    -
  • Closes gh-183. [sk2]

    -
  • -
  • Closes gh-183. [sk2]

    -
  • -
  • Use new vars, tidy. [sk2]

    -
  • -
  • Use new vars, tidy. [sk2]

    -
  • -
  • Info -> debug. [sk2]

    -
  • -
  • Info -> debug. [sk2]

    -
  • -
  • Lists instead of generators. [sk2]

    -
  • -
  • Lists instead of generators. [sk2]

    -
  • -
  • Ignores. [sk2]

    -
  • -
  • Ignores. [sk2]

    -
  • -
  • Merge pull request #182 from iainwp/master. [Simon Knight]

    -

    modification to accept a configuration file from an environment -variable

    -
  • -
  • Merge pull request #182 from iainwp/master. [Simon Knight]

    -

    modification to accept a configuration file from an environment -variable

    -
  • -
  • Comment out custom code. [sk2]

    -
  • -
  • Comment out custom code. [sk2]

    -
  • -
  • Revert labels. [sk2]

    -
  • -
  • Revert labels. [sk2]

    -
  • -
  • 254 on static route. [sk2]

    -
  • -
  • 254 on static route. [sk2]

    -
  • -
  • Modification to accept a configuration file from an environment -variable. [iainwp]

    -
  • -
  • Modification to accept a configuration file from an environment -variable. [iainwp]

    -
  • -
  • Merge pull request #125 from oliviertilmans/loopback_ids. [Simon -Knight]

    -

    Loopback interface needs to have an associated id with them

    -
  • -
  • Merge pull request #125 from oliviertilmans/loopback_ids. [Simon -Knight]

    -

    Loopback interface needs to have an associated id with them

    -
  • -
-
-
-

v0.7.4 (2013-12-02)

-
    -
  • Tidy overlay names. [sk2]
  • -
  • Tidy overlay names. [sk2]
  • -
  • Handle vis corner case if just input. [sk2]
  • -
  • Handle vis corner case if just input. [sk2]
  • -
-
-
-

v0.7.3 (2013-11-29)

-
    -
  • Correct version that bumpversion clobbered. [sk2]
  • -
  • Correct version that bumpversion clobbered. [sk2]
  • -
  • Add helper function to return neighbors of an interface. [sk2]
  • -
  • Add helper function to return neighbors of an interface. [sk2]
  • -
  • Add is_bound property for nidb interfaces for parity with anm. [sk2]
  • -
  • Add is_bound property for nidb interfaces for parity with anm. [sk2]
  • -
  • Set mgmt interface name correctly. [sk2]
  • -
  • Set mgmt interface name correctly. [sk2]
  • -
  • Remove extra http postings. [sk2]
  • -
  • Remove extra http postings. [sk2]
  • -
  • Add helper function to return neighbors of an interface. [sk2]
  • -
  • Add helper function to return neighbors of an interface. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
-
-
-

v0.7.2 (2013-11-27)

-
    -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidying version string. [sk2]
  • -
  • Tidying version string. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Fix extra syntax. [sk2]
  • -
  • Fix extra syntax. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
-
-
-

v0.7.1 (2013-11-26)

-
    -
  • More work for cloud-init support. [sk2]
  • -
  • More work for cloud-init support. [sk2]
  • -
  • Ignore .yaml. [sk2]
  • -
  • Ignore .yaml. [sk2]
  • -
  • Improving render for cloud init output. [sk2]
  • -
  • Improving render for cloud init output. [sk2]
  • -
  • Cloud init. [sk2]
  • -
  • Cloud init. [sk2]
  • -
  • Move out vis. [sk2]
  • -
  • Move out vis. [sk2]
  • -
  • Top-level behaviour. [sk2]
  • -
  • Top-level behaviour. [sk2]
  • -
  • Change top level peering behaviour. [sk2]
  • -
  • Change top level peering behaviour. [sk2]
  • -
  • Lock deps. [sk2]
  • -
  • Lock deps. [sk2]
  • -
  • Tidying. [sk2]
  • -
  • Tidying. [sk2]
  • -
-
-
-

v0.6.8 (2013-11-19)

-
    -
  • First iteration of simplified RR/HRR iBGP. [sk2]
  • -
  • First iteration of simplified RR/HRR iBGP. [sk2]
  • -
  • Refactor out ibgp. [sk2]
  • -
  • Refactor out ibgp. [sk2]
  • -
  • Remove extra node. [sk2]
  • -
  • Remove extra node. [sk2]
  • -
  • Path colours. [sk2]
  • -
  • Path colours. [sk2]
  • -
  • Handle base topo. [sk2]
  • -
  • Handle base topo. [sk2]
  • -
  • Update colours. [sk2]
  • -
  • Update colours. [sk2]
  • -
  • Error handling. [sk2]
  • -
  • Error handling. [sk2]
  • -
  • Logging. [sk2]
  • -
  • Logging. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Basic ibgp check. [sk2]
  • -
  • Basic ibgp check. [sk2]
  • -
-
-
-

v0.6.7 (2013-10-30)

-
    -
  • Fix looping issue not assigning server ips. [sk2]
  • -
  • Fix looping issue not assigning server ips. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
-
-
-

v0.6.6 (2013-10-29)

-
    -
  • Write IPs onto all server interfaces. [sk2]
  • -
  • Write IPs onto all server interfaces. [sk2]
  • -
-
-
-

v0.6.5 (2013-10-28)

-
    -
  • Redo bgp peering for ios. [sk2]
  • -
  • Redo bgp peering for ios. [sk2]
  • -
  • Revisiting bgp peering. [sk2]
  • -
  • Revisiting bgp peering. [sk2]
  • -
  • Adding nailed up routes for eBGP. [sk2]
  • -
  • Adding nailed up routes for eBGP. [sk2]
  • -
-
-
-

v0.6.4 (2013-10-22)

-
    -
  • Use -host for /32. [sk2]
  • -
  • Use -host for /32. [sk2]
  • -
-
-
-

v0.6.3 (2013-10-22)

-
    -
  • Tidying. [sk2]
  • -
  • Tidying. [sk2]
  • -
  • Add config-driven webserver port. [sk2]
  • -
  • Add config-driven webserver port. [sk2]
  • -
-
-
-

v0.6.2 (2013-10-21)

-
    -
  • Fix server issue. [sk2]
  • -
  • Fix server issue. [sk2]
  • -
-
-
-

v0.6.1 (2013-10-18)

-
    -
  • Toggle routing config. [sk2]
  • -
  • Toggle routing config. [sk2]
  • -
-
-
-

v0.6.0 (2013-10-18)

-
    -
  • Add mpls oam. [sk2]
  • -
  • Add mpls oam. [sk2]
  • -
  • Call mpls oam module. [sk2]
  • -
  • Call mpls oam module. [sk2]
  • -
  • Add mpls oam. [sk2]
  • -
  • Add mpls oam. [sk2]
  • -
  • Don't auto-correct explicitly set ASNs. [sk2]
  • -
  • Don't auto-correct explicitly set ASNs. [sk2]
  • -
  • Fix typo in comment. [sk2]
  • -
  • Fix typo in comment. [sk2]
  • -
  • Exclude multipoint edges from mpls te and rsvp. [sk2]
  • -
  • Exclude multipoint edges from mpls te and rsvp. [sk2]
  • -
  • Mark multipoint edges. [sk2]
  • -
  • Mark multipoint edges. [sk2]
  • -
  • Fallback to category20b colours if > 10 groups. [sk2]
  • -
  • Fallback to category20b colours if > 10 groups. [sk2]
  • -
  • Restore cef for ios. [sk2]
  • -
  • Restore cef for ios. [sk2]
  • -
  • Update doc. [sk2]
  • -
  • Update doc. [sk2]
  • -
  • Interface handling if specified name for servers. [sk2]
  • -
  • Interface handling if specified name for servers. [sk2]
  • -
  • Add lo routes. [sk2]
  • -
  • Add lo routes. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Remove debug, tidy. [sk2]
  • -
  • Remove debug, tidy. [sk2]
  • -
  • Include linux in manifest. [sk2]
  • -
  • Include linux in manifest. [sk2]
  • -
  • Add linux static routes. [sk2]
  • -
  • Add linux static routes. [sk2]
  • -
  • Add mpls te rules. [sk2]
  • -
  • Add mpls te rules. [sk2]
  • -
  • Ubuntu server class for static routes. [sk2]
  • -
  • Ubuntu server class for static routes. [sk2]
  • -
  • Server base class. [sk2]
  • -
  • Server base class. [sk2]
  • -
  • Base device class. [sk2]
  • -
  • Base device class. [sk2]
  • -
  • Tidying, add mpls to ios. [sk2]
  • -
  • Tidying, add mpls to ios. [sk2]
  • -
  • Fix ebgp session created on switch that has both ebgp and ibgp -sessions. [sk2]
  • -
  • Fix ebgp session created on switch that has both ebgp and ibgp -sessions. [sk2]
  • -
  • Adding route config rendering. [sk2]
  • -
  • Adding route config rendering. [sk2]
  • -
  • Tidying oo. [sk2]
  • -
  • Tidying oo. [sk2]
  • -
  • Add mpls code. [sk2]
  • -
  • Add mpls code. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Add fn to check server asn. [sk2]
  • -
  • Add fn to check server asn. [sk2]
  • -
  • Add mpls callout. [sk2]
  • -
  • Add mpls callout. [sk2]
  • -
  • Update asn setting. [sk2]
  • -
  • Update asn setting. [sk2]
  • -
  • Update asn handling: copy from phy if present. [sk2]
  • -
  • Update asn handling: copy from phy if present. [sk2]
  • -
-
-
-

v0.5.21 (2013-09-06)

-
    -
  • Set ipv4 and routing enabled defaults. [sk2]
  • -
  • Set ipv4 and routing enabled defaults. [sk2]
  • -
  • Post-collect processing. [sk2]
  • -
  • Post-collect processing. [sk2]
  • -
  • Remove uuid from test. [sk2]
  • -
  • Remove uuid from test. [sk2]
  • -
  • Reverse map for single ip. [sk2]
  • -
  • Reverse map for single ip. [sk2]
  • -
  • Multi-user uuid support. [sk2]
  • -
  • Multi-user uuid support. [sk2]
  • -
-
-
-

v0.5.20 (2013-08-29)

-
    -
  • Tidying, adding in vrfs. [sk2]
  • -
  • Tidying, adding in vrfs. [sk2]
  • -
-
-
-

v0.5.19 (2013-08-28)

-
    -
  • More collect. [sk2]
  • -
  • More collect. [sk2]
  • -
-
-
-

v0.5.18 (2013-08-27)

-
    -
  • Rename collect server. [sk2]
  • -
  • Rename collect server. [sk2]
  • -
  • Z ordering. [sk2]
  • -
  • Z ordering. [sk2]
  • -
  • Node data mapping. [sk2]
  • -
  • Node data mapping. [sk2]
  • -
  • Inc default threads to 5. [sk2]
  • -
  • Inc default threads to 5. [sk2]
  • -
  • Remove interfaces from node data dump. [sk2]
  • -
  • Remove interfaces from node data dump. [sk2]
  • -
  • Reverse mapping ips. [sk2]
  • -
  • Reverse mapping ips. [sk2]
  • -
  • Pep8. [sk2]
  • -
  • Pep8. [sk2]
  • -
-
-
-

v0.5.17 (2013-08-23)

-
    -
  • Allow no ip allocs. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
  • Split out functions from build_network. [sk2]
  • -
  • Split out functions from build_network. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
  • Allow no ip allocs. [sk2]
  • -
-
-
-

v0.5.16 (2013-08-22)

-
    -
  • Move endif to end of bgp block to enable bgp to be disabled. [sk2]
  • -
  • Move endif to end of bgp block to enable bgp to be disabled. [sk2]
  • -
  • Add todo. [sk2]
  • -
  • Add todo. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Option to disable routing protocols. [sk2]
  • -
  • Option to disable routing protocols. [sk2]
  • -
  • Nonzero function. [sk2]
  • -
  • Nonzero function. [sk2]
  • -
  • Include eigrp overlay. [sk2]
  • -
  • Include eigrp overlay. [sk2]
  • -
-
-
-

v0.5.14 (2013-08-22)

-
    -
  • Remvoe debug. [sk2]
  • -
  • Remvoe debug. [sk2]
  • -
-
-
-

v0.5.13 (2013-08-22)

-
    -
  • Only require specified ip for bound interfaces. [sk2]
  • -
  • Only require specified ip for bound interfaces. [sk2]
  • -
-
-
-

v0.5.12 (2013-08-21)

-
    -
  • Updates. [sk2]
  • -
  • Updates. [sk2]
  • -
  • Add lo to eigrp v6. [sk2]
  • -
  • Add lo to eigrp v6. [sk2]
  • -
  • Try seperate packages if possible. [sk2]
  • -
  • Try seperate packages if possible. [sk2]
  • -
  • Add eigrp. [sk2]
  • -
  • Add eigrp. [sk2]
  • -
-
-
-

v0.5.9 (2013-08-16)

-
    -
  • Remove debug. [sk2]
  • -
  • Remove debug. [sk2]
  • -
-
-
-

v0.5.8 (2013-08-16)

-
    -
  • Update. [sk2]
  • -
  • Update. [sk2]
  • -
-
-
-

v0.5.7 (2013-08-13)

-
    -
  • Misc bugfixes. [sk2]
  • -
  • Latest. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Measure updates. [sk2]
  • -
  • More collection. [sk2]
  • -
  • Measurement -> collection. [sk2]
  • -
  • More measure. [sk2]
  • -
  • Update docs. [sk2]
  • -
  • Remove unused measuremetn. [sk2]
  • -
  • Measure. [sk2]
  • -
-
-
-

v0.5.6 (2013-08-02)

-
    -
  • More measure. [sk2]
  • -
-
-
-

v0.5.5 (2013-08-02)

-
    -
  • Add colorbrewer. [sk2]
  • -
  • Add colorbrewer. [sk2]
  • -
  • Tidying colours. [sk2]
  • -
  • Tidying colours. [sk2]
  • -
  • Add enable secret. [sk2]
  • -
  • Measurement improvements. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Reorganise, ultra -> csr1000v, add hash. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Add server. [sk2]
  • -
-
-
-

v0.5.4 (2013-08-01)

-
    -
  • Rename icon. [sk2]
  • -
  • Ultra -> CSR1000v. [sk2]
  • -
  • Change mgmt interface handling. [sk2]
  • -
  • More measure. [sk2]
  • -
  • Update measure. [sk2]
  • -
  • Tidying measurement. [sk2]
  • -
  • Update user. [sk2]
  • -
  • Regen autodoc. [sk2]
  • -
  • Remove old measure code. [sk2]
  • -
  • Working traceroute measurement. [sk2]
  • -
  • Rebuild docs. [sk2]
  • -
  • Change docs theme. [sk2]
  • -
  • Doc -> docs. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Update ignore. [sk2]
  • -
  • Tidying. [sk2]
  • -
-
-
-

v0.5.3 (2013-07-31)

-
    -
  • Tidying setup.py. [sk2]
  • -
  • Add new platform. [sk2]
  • -
  • Tidying tests. [sk2]
  • -
  • Restore. [sk2]
  • -
  • Add comment. [sk2]
  • -
  • Zmq measurement working (needs deserialization) [sk2]
  • -
  • Zmq measure. [sk2]
  • -
  • Testing, deployment. [sk2]
  • -
  • Pep8, fix ibgp 2 layer issues. [sk2]
  • -
  • Pep8. [sk2]
  • -
  • Pep8. [sk2]
  • -
  • Pep8. [sk2]
  • -
  • Diff testing. [sk2]
  • -
  • Remove unused code. [sk2]
  • -
  • Add bgp pol tests. [sk2]
  • -
  • More testing. [sk2]
  • -
  • Change lo_interface to a valid linux/netkit name. [Olivier Tilmans]
  • -
  • Split single compiler into modular platform and device compilers. -[sk2]
  • -
  • Tidying. [sk2]
  • -
  • Loosen path tension. [sk2]
  • -
  • Add testing to setup.py. [sk2]
  • -
  • More cleanup. [sk2]
  • -
  • Update tests. [sk2]
  • -
-
-
-

v0.5.2 (2013-07-24)

-
    -
  • Sorting on ipv6 for stability. [sk2]
  • -
-
-
-

v0.5.1 (2013-07-24)

-
    -
  • Sort for stability. [sk2]
  • -
  • Natural sorting for bgp sessions. [sk2]
  • -
  • Debug. [sk2]
  • -
  • Sort for repeatability. [sk2]
  • -
  • Merge onepk. [sk2]
  • -
  • Allocate interfaces if not allocated on input. closes gh-180. [sk2]
  • -
  • Apply correct subnet to interfaces. [sk2]
  • -
  • Report node label rather than node id for string representation of -interface. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Improvements. [sk2]
  • -
  • Remove debug. [sk2]
  • -
  • Fix issue with secondary loopbacks. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Store label on json. [sk2]
  • -
  • More 3d. [sk2]
  • -
  • More 3d. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • 3d prototype. [sk2]
  • -
  • More 3d dev. [sk2]
  • -
  • More 3d dev. [sk2]
  • -
  • Three js dev. [sk2]
  • -
  • Tidied. [sk2]
  • -
  • Ignore dev project. [sk2]
  • -
  • New icon. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Dont load ip allocs, labels by default. [sk2]
  • -
  • Tidy logic. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Tidy, rename, add servers. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Add todo. [sk2]
  • -
  • Dev. [sk2]
  • -
-
-
-

v0.5.0 (2013-07-02)

-
    -
  • Tidy. [sk2]

    -
  • -
  • Split out ui. [sk2]

    -
  • -
  • Isis handling. [sk2]

    -
  • -
  • Setting with setattr for interface dict. [sk2]

    -
  • -
  • Isis combinations. [sk2]

    -
  • -
  • Better handling of ips. [sk2]

    -
  • -
  • Notes. [sk2]

    -
  • -
  • Split out webui. [sk2]

    -
  • -
  • More work to work with interfaces directly. [sk2]

    -
  • -
  • Tidying, check if bound interfaces. [sk2]

    -
  • -
  • Work on ip addressing if already set. [sk2]

    -
  • -
  • Ignore unbound interfaces. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Fixing ordering. [sk2]

    -
  • -
  • Tidying interface if set externally. [sk2]

    -
  • -
  • Clean up interface handling. [sk2]

    -
  • -
  • Less cryptic names, tidying. [sk2]

    -
  • -
  • Remap icons. [sk2]

    -
  • -
  • Add todos, better remote interface desc. [sk2]

    -
  • -
  • Copying attributes. [sk2]

    -
  • -
  • Better labelling, testing of interfaces. [sk2]

    -
  • -
  • Flip. [sk2]

    -
  • -
  • Handling of interfaces if allocated in physical. [sk2]

    -
  • -
  • Improve tension on paths. [sk2]

    -
  • -
  • Dev. [sk2]

    -
  • -
  • Able to search for edge by edge, used for cross-layer edge searches. -[sk2]

    -
  • -
  • String function ensures string. [sk2]

    -
  • -
  • Interface errorr handling. [sk2]

    -
  • -
  • Handle numeric node ids. [sk2]

    -
  • -
  • More work on paths. [sk2]

    -
  • -
  • Dev. [sk2]

    -
  • -
  • Compress anm to send over wire. [sk2]

    -
  • -
  • Cdp on mgmt eth. [sk2]

    -
  • -
  • Add measure support. [sk2]

    -
  • -
  • Add path annotations. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Fixing interface access. [sk2]

    -
  • -
  • Fixing serialising. [sk2]

    -
  • -
  • Fix corner-case with building trees. [sk2]

    -
  • -
  • Add logging message. [sk2]

    -
  • -
  • Merge pull request #179 from sk2/custom-folders. [Simon Knight]

    -

    Custom folders

    -
  • -
  • Work on new measure framework. [sk2]

    -
  • -
  • Initial work. [sk2]

    -
  • -
  • Toggle. [sk2]

    -
  • -
  • Remap the interfaces back to nodes, and integers. [sk2]

    -
  • -
  • Hash on edges. [sk2]

    -
  • -
-
-
-

v0.4.9 (2013-06-14)

-
    -
  • Fix bug with nx-os. [sk2]
  • -
  • Better hashing for cross-layer and cross-anm/nidb interface -comparison. [sk2]
  • -
  • Add quiet (non verbose) option. [sk2]
  • -
  • Test for presence in vrf graph. [sk2]
  • -
  • Only add vrfs if at least one node has been tagged with vrf tag. [sk2]
  • -
  • Turn web json stream back to anm/nidb. [sk2]
  • -
-
-
-

v0.4.8 (2013-06-12)

-
    -
  • Ospfv3 on loopback zero. [sk2]
  • -
-
-
-

v0.4.7 (2013-06-12)

-
    -
  • Tidy. [sk2]
  • -
  • Add servers to igp. [sk2]
  • -
-
-
-

v0.4.6 (2013-06-11)

-
    -
  • Disable bundled vis. [sk2]

    -
  • -
  • Update demo notebook. [sk2]

    -
  • -
  • Support for specific packages. [sk2]

    -
  • -
  • Update template. [sk2]

    -
  • -
  • Ignore ds store. [sk2]

    -
  • -
  • Add key filename support. [sk2]

    -
  • -
  • Split out args so can call programatically. [sk2]

    -

    arg_string = "-f %s --deploy" % input_file args = -console_script.parse_options(arg_string) console_script.main(args)

    -
  • -
  • Mark ipv4/ipv6 per interface, numeric ids. [sk2]

    -
  • -
  • Add l3 conn graph, use for vrfs. [sk2]

    -
  • -
  • Add dump. [sk2]

    -
  • -
  • Update entry point. [sk2]

    -
  • -
  • Update ignore. [sk2]

    -
  • -
  • Add tests. [sk2]

    -
  • -
  • Tidying compiler for interfaces. [sk2]

    -
  • -
  • Tidying, add option to force ank vis, add info message if single user -mode activated. [sk2]

    -
  • -
  • Update to command line argument parsing. [sk2]

    -
  • -
  • Remove testing uuid. [sk2]

    -
  • -
  • Remove unused imports. [sk2]

    -
  • -
  • More multi-user support. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Use shorter uuid - less unique, but more usable. still unlikely to -collide for our purposes. [sk2]

    -
  • -
  • Send uuid with highlight. [sk2]

    -
  • -
  • Tidy, add support for muti user. [sk2]

    -
  • -
  • Multi-user vis support. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Dont monitor build_network (won't work if using as module) [sk2]

    -
  • -
  • Add uuid support. [sk2]

    -
  • -
  • Support uuid. [sk2]

    -
  • -
  • Remove messaging call. [sk2]

    -
  • -
  • Remove highlight call. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
-
-
-

v0.4.5 (2013-05-29)

-
    -
  • Use green for verified paths. [sk2]
  • -
  • Use green for verified paths. [sk2]
  • -
  • Use autonetkit_cisco web content if present. [sk2]
  • -
  • Exception handling. [sk2]
  • -
  • Add logging message. [sk2]
  • -
  • Add logging message. [sk2]
  • -
  • Fix logging. [sk2]
  • -
  • Update demos. [sk2]
  • -
  • Markdown extension of md not mmd. [sk2]
  • -
  • Readme from .txt -> mmd. [sk2]
  • -
  • Retry as markdown. [sk2]
  • -
  • Add badge count using. [sk2]
  • -
  • Demo updates. [sk2]
  • -
  • More work on demo. [sk2]
  • -
  • Further improved numeric vm id shutdown. [sk2]
  • -
  • New demo notebook. [sk2]
  • -
  • Clean paths on redraw. [sk2]
  • -
  • Handle numeric vm ids. [sk2]
  • -
-
-
-

v0.4.4 (2013-05-15)

-
    -
  • Dhcp management. [sk2]
  • -
  • Add output target. [sk2]
  • -
  • Fix global settings. [sk2]
  • -
  • Add todo. [sk2]
  • -
  • Updates to notebook. [sk2]
  • -
  • Restore ui elements. [sk2]
  • -
  • Link highlights behind nodes. [sk2]
  • -
  • Add onepk stanza. [sk2]
  • -
  • Updates. [sk2]
  • -
  • Updates. [sk2]
  • -
  • Demo notebook. [sk2]
  • -
  • Highlight path colour. [sk2]
  • -
  • Logging, highlight path colour. [sk2]
  • -
  • Bugfix for highlights. [sk2]
  • -
  • Bugfix. [sk2]
  • -
  • Measure. [sk2]
  • -
  • Add function to map edge attr to interfaces. [sk2]
  • -
-
-
-

v0.4.2 (2013-05-13)

-
    -
  • Add code to switch on input extension. [sk2]
  • -
-
-
-

v0.4.1 (2013-05-10)

-
    -
  • Don't put clns mtu on loopbacks. [sk2]
  • -
-
-
-

v0.3.14 (2013-05-10)

-
    -
  • Enable clns mtu 1400 on isis interfaces. [sk2]
  • -
  • Enable cdp per interface. [sk2]
  • -
  • Enable cdp on all interfaces, rename mgmt interface. [sk2]
  • -
  • Add ank_cisco to version. [sk2]
  • -
-
-
-

v0.3.13 (2013-05-10)

-
    -
  • Mpls lite support for ios. [sk2]
  • -
  • Only add PE, P to mpls_ldp. [sk2]
  • -
-
-
-

v0.3.12 (2013-05-10)

-
    -
  • Use specified subnet. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Return interface on creation. [sk2]
  • -
  • Updating ip allocations. [sk2]
  • -
  • Refactored ip allocation. [sk2]
  • -
  • Add comment. [sk2]
  • -
-
-
-

v0.3.11 (2013-05-09)

-
    -
  • Mgmt + cdp. [sk2]
  • -
  • Management toggle. [sk2]
  • -
  • Tidying. [sk2]
  • -
  • Rename function. [sk2]
  • -
  • Support to copy across management info. [sk2]
  • -
  • Allow [] notation to set/get overlay data. [sk2]
  • -
  • Fix capitalisation. [sk2]
  • -
  • Tidy. [sk2]
  • -
  • Fix imports. [sk2]
  • -
  • Fix import errors. [sk2]
  • -
  • Don't over-write infrastructure blocks, closes gh-176. [sk2]
  • -
  • Add comment. [sk2]
  • -
  • Ensure allocation is imported. [sk2]
  • -
-
-
-

v0.3.10 (2013-05-04)

-
    -
  • Catch value errors. [sk2]
  • -
  • Fallback. [sk2]
  • -
-
-
-

v0.3.9 (2013-05-03)

-
    -
  • Tidy management ips. [sk2]

    -
  • -
  • Explicitly set mgmt interface label for xr and nx-os. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Different ids based on ios derivative. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Nx-os interface labels. [sk2]

    -
  • -
  • Copy management subnet info if relevant. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Use "use" with icon defs, rather than redefining each time. [sk2]

    -

    based on -https://groups.google.com/forum/?fromgroups=#!topic/d3-js/EtEwgOYnY6U -better performance avoids the chrome caching issues

    -
  • -
  • Merge pull request #124 from oliviertilmans/http_vis. [Simon Knight]

    -

    Fix a small log.info error

    -
  • -
  • Fix the following error: [Olivier Tilmans]

    -

    > File "autonetkit/ank_messaging.py", line 107, in publish_data > -log.info("Unable to connect to HTTP Server %s: e" % (http_url, e)) > -TypeError: not all arguments converted during string formatting When -trying to generate cfg's without having the visualisation server -running

    -
  • -
  • Treat specified interface labels per node rather than globally. [sk2]

    -
  • -
  • Make labels on top of links and nodes. [sk2]

    -
  • -
  • Add note. [sk2]

    -
  • -
  • Dont spuriously warn on unset. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Fix error with interface names if not allocated, eg on a lan segment. -[sk2]

    -
  • -
  • Remove unused code. [sk2]

    -
  • -
  • Ignore html coverage output. [sk2]

    -
  • -
  • Ignore coverage. [sk2]

    -
  • -
  • Rename validate to ank_validate to avoid conflict with configobj and -paths. [sk2]

    -
  • -
  • Add IGP overlays even if not used - allows quicker test in compiler. -[sk2]

    -
  • -
  • Include cluster attribute for rendering. [sk2]

    -
  • -
  • Show grouping for ibgp_v4 and ibgp_v6. [sk2]

    -
  • -
  • Resolve merge conflicts. [sk2]

    -
  • -
  • Tidy ignore. [sk2]

    -
  • -
  • Fix single-node hulls: make slightly bigger so don't get printing -artifacts with gap in middle. [sk2]

    -
  • -
  • Merge pull request #116 from oliviertilmans/cleanup. [Simon Knight]

    -

    Minor cleanup & usage of os.path.join

    -
  • -
  • Merge pull request #119 from oliviertilmans/device_type_server. [Simon -Knight]

    -

    (Fix Issue #117) Using Any other non router l3device node (i.e. -server) crashes ANK

    -
  • -
  • Merge pull request #118 from sdefauw/master. [Simon Knight]

    -

    Bug of boolean fields in graphml solved.

    -
  • -
  • Hostname is now independent from zebra. [Olivier Tilmans]

    -
  • -
  • Merge branch 'device_type_server' into anycast_dns_resolver. [Olivier -Tilmans]

    -
  • -
  • Start zebra only if the node needs it (is a router at the moment) -[Olivier Tilmans]

    -
  • -
  • Added anycast ip attribute. [Olivier Tilmans]

    -
  • -
  • Add anycast dns resolver support on ANK side, anycast ip's have yet to -handled. [Olivier Tilmans]

    -
  • -
    • -
    • Allow the server nodes (and by extension all l3devices) to be real
    • -
    -
    -

    System Message: WARNING/2 (changelog.rst, line 2118)

    -

    Bullet list ends without a blank line; unexpected unindent.

    -
    -

    netkit VM's * Make the start of the zebra daemon optional (only if one -of its component is in use) * Made sure that the debug flag for BGP -was only set if BGP was enabled in the node. [Olivier Tilmans]

    -
  • -
  • Ensure that copy_edge_attr_from will only copy attributes from edges -which are common to the two graphs. [Olivier Tilmans]

    -
  • -
  • Bug of boolean fields in graphml solved. [Sébastien De Fauw]

    -
  • -
  • Enforced usage of os.path.join in compiler. [Olivier Tilmans]

    -
  • -
  • Remove redundant overlay creations. [Olivier Tilmans]

    -
  • -
  • Clean out last of pika references. [sk2]

    -
  • -
  • Tidying messaging. [sk2]

    -
  • -
  • Use new format messaging. [sk2]

    -
  • -
  • Using url params for routing, stripping out rabbitmq and telnet. [sk2]

    -
  • -
  • Tidying up json format. [sk2]

    -
  • -
  • Uncompress notebooks for easier access. [sk2]

    -
  • -
  • Compress ipython notebooks. [sk2]

    -
  • -
  • Remove symlink. [sk2]

    -
  • -
  • Use gzip for default (smaller file size) [sk2]

    -
  • -
  • Use gzip for default json. [sk2]

    -
  • -
  • Remove unused data. [sk2]

    -
  • -
  • Only apply ospf to interfaces bound in ospf graph. [sk2]

    -
  • -
  • Remove images from tutorial. [sk2]

    -
  • -
-
-
-

v0.3.7 (2013-04-15)

-
    -
  • Update packages to latest version. [sk2]
  • -
  • Remove message pipe using telnet, support tornado 3.0.1. [sk2]
  • -
-
-
-

v0.3.6 (2013-04-15)

-
    -
  • Add images. [sk2]

    -
  • -
  • New module to push changes. [sk2]

    -
  • -
  • Split out functions. [sk2]

    -
  • -
  • Allow search on node id as well as label. [sk2]

    -
  • -
  • Convert multi -> single edge graph. [sk2]

    -
  • -
  • Split out functions. [sk2]

    -
  • -
  • Split out functions. [sk2]

    -
  • -
  • Allow select edge by nodes. [sk2]

    -
  • -
  • Example notebook on OSPF cost experiments. [sk2]

    -
  • -
  • Inc version. [sk2]

    -
  • -
  • Split the boolean to render to_memory, and the rendered output. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Split out initialise into new function. [sk2]

    -
  • -
  • New diff script to monitor and update network. [sk2]

    -
  • -
  • Update. [sk2]

    -
  • -
  • Modify example input. [sk2]

    -
  • -
  • Add support for trace colours. [sk2]

    -
  • -
  • Updates to traces. [sk2]

    -
  • -
  • Index edges by src/dst pair. [sk2]

    -
  • -
  • Add note. [sk2]

    -
  • -
  • Comment out highlight. [sk2]

    -
  • -
  • Allow access interface by numeric value (eg if from diff output) [sk2]

    -
  • -
  • Add support for show ip ospf and conf t. [sk2]

    -
  • -
  • Add function to diff two nidbs. [sk2]

    -
  • -
  • Add basic processing (this needs to be moved to a process module) -[sk2]

    -
  • -
  • Increase management subnet pool for testing (this needs to be modified -later) [sk2]

    -
  • -
  • Don't set ibgp for grid. [sk2]

    -
  • -
  • Remove extra update. [sk2]

    -
  • -
  • Allow path data. [sk2]

    -
  • -
  • More work on path animations. [sk2]

    -
  • -
  • Animated path plotting. [sk2]

    -
  • -
  • Change marker colour. [sk2]

    -
  • -
  • Mapping from node id to id, ensures unique. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Improve path plotting, add markers (arrows) [sk2]

    -
  • -
  • Groupings for nodes, edges, etc: can control ordering. [sk2]

    -
  • -
  • Notify when receive highlight. [sk2]

    -
  • -
  • Storing measured data to json. [sk2]

    -
  • -
  • Improvements to automated measurement: use iteration rather than -callbacks. [sk2]

    -
  • -
  • Tidying, show verification results. [sk2]

    -
  • -
  • Set ospf for quagga. [sk2]

    -
  • -
  • Sort cd ids. [sk2]

    -
  • -
  • Basic shell script to run measure periodically. [sk2]

    -

    will later be replaced with pure python script run as part of -autonetkit (or autonetkit_measure) command

    -
  • -
  • Data and script to replay measurements. [sk2]

    -
  • -
  • New verify module. [sk2]

    -
  • -
  • Sort names for split. [sk2]

    -
  • -
  • Better trace highlight support. [sk2]

    -
  • -
  • Add sh ip route support. [sk2]

    -
  • -
  • Bugfix: only validate if anm loaded. [sk2]

    -
  • -
  • Remove old code. [sk2]

    -
  • -
  • Add support for parsing sh ip route from quagga. [sk2]

    -
  • -
  • Support for highlight paths [node, node, ... node] [sk2]

    -
  • -
  • Support for highlight paths. [sk2]

    -
  • -
  • Asn 0 -> 1. [sk2]

    -
  • -
  • Remove trailing comma which made loopback ip a tuple. [sk2]

    -
  • -
  • Support topology data used to store data without a template to render. -[sk2]

    -
  • -
  • More work on oob ips. [sk2]

    -
  • -
  • Better handling for non existent interfaces - eg oob added to nidb. -[sk2]

    -
  • -
  • Allow interfaces to be added to nidb. [sk2]

    -
  • -
  • Adding oob support. [sk2]

    -
  • -
  • More work on vrfs. [sk2]

    -
  • -
  • Tidy .gitignore. [sk2]

    -
  • -
  • Ignore .graphml files. [sk2]

    -
  • -
  • New collision domain icon. [sk2]

    -
  • -
  • Remove symlink that crept in. [sk2]

    -
  • -
  • Remove point-to-point config statement for ospf. [sk2]

    -
  • -
  • Use same variable name for vpnv4. [sk2]

    -
  • -
  • Tidying vrf pre-process for ibgp. [sk2]

    -
  • -
  • Enforce specific packages. [sk2]

    -
  • -
  • Change default edge color. [sk2]

    -
  • -
  • Send ipv4 infra as json. [sk2]

    -
  • -
  • Convert areas to strings for serializing keys. [sk2]

    -
  • -
  • Add docstrings. [sk2]

    -
  • -
  • Sort returned json keys. [sk2]

    -
  • -
  • Continued vrfs. [sk2]

    -
  • -
  • Ibgp vrf. [sk2]

    -
  • -
  • Work on vrfs and bgp sessions, tidied up bgp sessions. [sk2]

    -
  • -
  • More work on bgp vrfs. [sk2]

    -
  • -
  • More work on vrfs. [sk2]

    -
  • -
  • Remove debugging. [sk2]

    -
  • -
  • Add to mpls ldp if bound in that overlay. [sk2]

    -
  • -
  • Copy description as well as type from anm. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Smaller interface labels. [sk2]

    -
  • -
  • Allow access to interface from nidb. [sk2]

    -
  • -
  • Remove testing code. [sk2]

    -
  • -
  • Work on vrfs, mpls ldp. [sk2]

    -
  • -
  • Work on mpls, vrfs, mpls ldp. [sk2]

    -
  • -
  • Fix issue with interface descriptions for secondary loopbacks. [sk2]

    -
  • -
  • Copy interface ids back from nidb to anm overlays, condense to brief -for brevity. [sk2]

    -
  • -
  • Update doc, work on json tree for nidb. [sk2]

    -
  • -
  • Merge pull request #115 from sk2/master. [Simon Knight]

    -

    merge back to interfaces

    -
  • -
  • Merge pull request #114 from sk2/validate. [Simon Knight]

    -

    add validation tests for ipv4

    -
  • -
  • Merge pull request #113 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • Add validation tests for ipv4. [sk2]

    -
  • -
  • Initial commit of validate. [sk2]

    -
  • -
  • Remove specific code, works under generic interface attributes. [sk2]

    -
  • -
  • Add hooks for validate enable/disable. [sk2]

    -
  • -
  • Workaround to import validate from python system, namespace clash with -using validate inside ank. [sk2]

    -
  • -
  • Shortcut to check if interface is physical. [sk2]

    -
  • -
  • Interface font size. [sk2]

    -
  • -
  • Simpler cd icon. [sk2]

    -
  • -
  • More work on vrfs. [sk2]

    -
  • -
  • Generic interface overlay groupings (to support vrfs and ospf in -consistent format, will auto adapt) [sk2]

    -
  • -
  • V6 secondary loopback alloc. [sk2]

    -
  • -
  • Add shortcuts to interface iteration by type. [sk2]

    -
  • -
  • Fix comment. [sk2]

    -
  • -
  • Define lt for interface comparisons. [sk2]

    -
  • -
  • Optional handling of secondary loopbacks. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Copying v4 and v6 ips for secondary loopbacks. [sk2]

    -
  • -
  • Tidying vrf interfaces. [sk2]

    -
  • -
  • Merge pull request #112 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • Ipv4/v6 switches. [sk2]

    -
  • -
  • More work on tidying v4, v6, interfaces, testing. [sk2]

    -
  • -
  • More interface hulls. [sk2]

    -
  • -
  • Tidy icons. [sk2]

    -
  • -
  • Debug. [sk2]

    -
  • -
  • Inc ver. [sk2]

    -
  • -
  • Update. [sk2]

    -
  • -
  • New icon. [sk2]

    -
  • -
  • Interface hulls. [sk2]

    -
  • -
  • Bugfixes. [sk2]

    -
  • -
  • Add accessors for physical and loopback access. [sk2]

    -
  • -
  • More work on interfaces. [sk2]

    -
  • -
  • Fix ibgp layering. [sk2]

    -
  • -
  • More interface work. [sk2]

    -
  • -
  • More interfaces. [sk2]

    -
  • -
  • More interface work. [sk2]

    -
  • -
  • More work on interfaces. [sk2]

    -
  • -
  • More work on interfaces: datastructures, api, build, compile. [sk2]

    -
  • -
  • Fixes for interfaces. [sk2]

    -
  • -
  • Partial code for interface groupings eg for ospf areas. [sk2]

    -
  • -
  • Working interface mappings in nidb. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Copying across interface type to nidb. [sk2]

    -
  • -
  • Interface dev. [sk2]

    -
  • -
  • Adding notes. [sk2]

    -
  • -
  • Fix order: first param if using args eg ("description") is desc not -type. [sk2]

    -
  • -
  • Fix bug: need to test overlay_id is phy, not node_id is phy. [sk2]

    -
  • -
  • Fix docstring. [sk2]

    -
  • -
  • Add note. [sk2]

    -
  • -
  • Return type. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Remove unneeded check (as fixed bug in ank split) [sk2]

    -
  • -
  • Fix bug: was copying interface id from src rather than dst. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Expand out _interfaces for edges. [sk2]

    -
  • -
  • More dev work on interfaces. [sk2]

    -
  • -
  • Looking up interfaces in nidb. [sk2]

    -
  • -
  • Better adding edges to nidb if from cd vs switch. [sk2]

    -
  • -
  • Better adding edges to nidb if from cd vs switch. [sk2]

    -
  • -
  • Edge comparisons. [sk2]

    -
  • -
  • Debug. [sk2]

    -
  • -
  • Workarounds for multipoint ospf. [sk2]

    -
  • -
  • Workarounds for multipoint ospf. [sk2]

    -
  • -
  • Merge pull request #111 from sk2/multipoint. [Simon Knight]

    -

    Multipoint

    -
  • -
  • Make single-node groups less bubble-y. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Update ebgp to handle switches. [sk2]

    -
  • -
  • Fix bugs in explode. [sk2]

    -
  • -
  • Fix multipoint ebgp session handling to obtain ips. [sk2]

    -
  • -
  • Switch support for isis, ospf, ebgp. [sk2]

    -
  • -
  • Handle connected components. [sk2]

    -
  • -
  • Concat rather than nested lists. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Fix support for wrapping exploded edges. [sk2]

    -
  • -
  • Look at neighbouring routers. [sk2]

    -
  • -
  • Only look at neighbouring routers for vrf (handles switches, other -devices) [sk2]

    -
  • -
  • Fix bug where passing in empty list would fall back to all nodes in -graph. [sk2]

    -

    now check if nbunch is None rather than evaluating to False (which was -case for empty list)

    -
  • -
  • Merge pull request #110 from sk2/master. [Simon Knight]

    -

    merge updates back to vrf branch

    -
  • -
  • Fix issue with ibgp levels. [sk2]

    -
  • -
  • More work on interfaces. [sk2]

    -
  • -
  • Updating interface support. [sk2]

    -
  • -
  • Testing code for interfaces. [sk2]

    -
  • -
  • Testing code for interfaces. [sk2]

    -
  • -
  • Correct returning edges to use new interface binding format of -{node_id: interface_id} [sk2]

    -
  • -
  • Access corresponding interface across overlays (if exists) [sk2]

    -
  • -
  • String repr of anm. [sk2]

    -
  • -
  • New function for testing if overlay present in anm. [sk2]

    -
  • -
  • Retain relevant interface bindings when splitting edges. [sk2]

    -
  • -
  • Merge pull request #108 from sk2/multi-edge. [Simon Knight]

    -

    Multi edge

    -
  • -
  • Inc ver. [sk2]

    -
  • -
  • Fix problem with one or two collision domain ASes. [sk2]

    -
  • -
  • Handle case of AS with no iBGP nodes (all set to ibgp_role of None) -[sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • Fix correct image. [sk2]

    -
  • -
  • Fix right version. [sk2]

    -
  • -
  • Update. [sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • More updates. [sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • Revert change. [sk2]

    -
  • -
  • Move to work with online notebook viewer. [sk2]

    -
  • -
  • Update images, add images to tutorial. [sk2]

    -
  • -
  • Update tutorial. [sk2]

    -
  • -
  • Increase timeout. [sk2]

    -
  • -
  • Add tutorial graphml. [sk2]

    -
  • -
  • Add tutorial images. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Update tutorial. [sk2]

    -
  • -
  • Inc ver. [sk2]

    -
  • -
  • Updates. [sk2]

    -
  • -
  • Use ipv4 not ip. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • New path colours. [sk2]

    -
  • -
  • Handling starting and lab started. [sk2]

    -
  • -
  • Better debug. [sk2]

    -
  • -
  • Use ipv4 overlay. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Tidying, add option for grid. [sk2]

    -
  • -
  • Default ospf cost. [sk2]

    -
  • -
  • Ensure ospf cost is int. [sk2]

    -
  • -
  • Add 2d grid. [sk2]

    -
  • -
  • Bugfix. [sk2]

    -
  • -
  • More work on vrfs. [sk2]

    -
  • -
  • Remove website (has been moved to gh-pages branch) [sk2]

    -
  • -
  • Inc ver. [sk2]

    -
  • -
  • Closes gh-91. [sk2]

    -
  • -
  • Extra send option. [sk2]

    -
  • -
  • More explicit boolean. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Workaround for gh-90. [sk2]

    -
  • -
  • Auto list contributors from github api. [sk2]

    -
  • -
  • Set default igp. [sk2]

    -
  • -
  • Bugfix: dont set if node not in graph. [sk2]

    -
  • -
  • Extend tutorial examples. [sk2]

    -
  • -
  • Allow type casting in copy edge and node attribute functions. [sk2]

    -
  • -
  • Update tutorial. [sk2]

    -
  • -
  • Add tutorial. [sk2]

    -
  • -
  • Move to gist. [sk2]

    -
  • -
  • More notebook updates. [sk2]

    -
  • -
  • Update workbook. [sk2]

    -
  • -
  • Example ipython notebook. [sk2]

    -
  • -
  • Highlights for nodes and edges. [sk2]

    -
  • -
  • Inc ver. [sk2]

    -
  • -
  • White body for printing. [sk2]

    -
  • -
  • Merge ospf areas back into general function. [sk2]

    -
  • -
  • Search for edges based on src/dst string ids. [sk2]

    -
  • -
  • Simplified access to update http. [sk2]

    -
  • -
  • Add shortcuts to common classes/functions. [sk2]

    -
  • -
  • Merge pull request #88 from metaswirl/master. [Simon Knight]

    -

    First pull request :)

    -
  • -
  • Merge pull request #89 from bhesmans/fixCache. [Simon Knight]

    -

    fixe cache issue.

    -
  • -
  • Fixe cache issue. [Hesmans Benjamin]

    -

    Won't render otherwise the two path joined were both absolute. Now, -use relative "base" isntead of full_base to build the base_cache_dir

    -
  • -
  • Cleaned comments. [Niklas Semmler]

    -
  • -
  • Added isis support to quagga, fixed a bug in the renderer. [Niklas -Semmler]

    -
  • -
  • Tidying code. [sk2]

    -
  • -
  • Add offset to fix truncating of curved edges to boxes in 2 node group -plots. [sk2]

    -
  • -
  • Fix ordering of functions. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Pep8, tidying. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Tidying vrfs. [sk2]

    -
  • -
  • Merge pull request #86 from sk2/vrf. [Simon Knight]

    -

    Vrf support, misc bugfixes + improvements

    -
  • -
  • Fix merge. [sk2]

    -
  • -
  • Auto set ce. [sk2]

    -
  • -
  • Remove todo. [sk2]

    -
  • -
  • Merge pull request #85 from bhesmans/fixRRClientAS. [Simon Knight]

    -

    quick fix for RR: no remote as.

    -
  • -
  • Remove offset. [sk2]

    -
  • -
  • Vrfs. [sk2]

    -
  • -
  • Bugfix. [sk2]

    -
  • -
  • Handle socket in use. [sk2]

    -
  • -
  • Quick fix for RR: no remote as. [Hesmans Benjamin]

    -
  • -
  • Work on caching. [sk2]

    -
  • -
  • Use set comprehensions. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Move utility function. [sk2]

    -
  • -
  • Code tidy. [sk2]

    -
  • -
  • Pep8. [sk2]

    -
  • -
  • Merge pull request #65 from oliviertilmans/master. [Simon Knight]

    -

    Clear out .svn subdir from doc/source/reference/

    -
  • -
  • Updated gitignore to avoid further accidental tracking of .svn -subdirs. [Olivier Tilmans]

    -
  • -
  • Removed svn subdir. [Olivier Tilmans]

    -
  • -
  • Ios v6 isis. [sk2]

    -
  • -
  • Add template error rendering. [sk2]

    -
  • -
  • Ospfv3 on ios. [sk2]

    -
  • -
  • Tidy status output. [sk2]

    -
  • -
  • Marking for ospf v3. [sk2]

    -
  • -
  • Attempts to tidy zoom. [sk2]

    -
  • -
  • Increment version. [sk2]

    -
  • -
  • Fix indent, add process id for isis. [sk2]

    -
  • -
  • Bugfix: 126 ->128 bit v6 loopbacks. [sk2]

    -
  • -
  • More work on interfaces, secondary loopbacks, vrfs. [sk2]

    -
  • -
  • More interface support. [sk2]

    -
  • -
  • Allocate to secondary loopbacks. [sk2]

    -
  • -
  • Initial vrf block. [sk2]

    -
  • -
  • More vrf. [sk2]

    -
  • -
  • Improved interface handling. [sk2]

    -
  • -
  • Update github link ank_v3_dev -> autonetkit. [sk2]

    -
  • -
  • More work on interfaces: store on physical graph if node exists in it. -allows consistent interfaces across layers. [sk2]

    -
  • -
  • Toggle filter. [sk2]

    -
  • -
  • Neater filter. [sk2]

    -
  • -
  • Inc ver. [sk2]

    -
  • -
  • Toggle filter. [sk2]

    -
  • -
  • Add extra log message. [sk2]

    -
  • -
  • Load opacity on enter. [sk2]

    -
  • -
  • Filter long attribute lists. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Node filtering. [sk2]

    -
  • -
  • Work on filtering opacity. [sk2]

    -
  • -
  • Increment version. [sk2]

    -
  • -
  • Check l3 cluster for ibgp, tidy syntax. [sk2]

    -
  • -
  • Fix quagga. [sk2]

    -
  • -
  • Work on interfaces. [sk2]

    -
  • -
  • Attribute filtering for neighbors. [sk2]

    -
  • -
  • Take icon size into account for auto scaling. [sk2]

    -
  • -
  • Add grouping for vrf. [sk2]

    -
  • -
  • Interfaces: adding with attributes, filtering on attributes, -iteration. [sk2]

    -
  • -
  • Error handling. [sk2]

    -
  • -
  • Adding vrf config. [sk2]

    -
  • -
  • Tidy v6 access, format for consistency. [sk2]

    -
  • -
  • Renaming ip -> ipv4, ip6 -> ipv6. [sk2]

    -
  • -
  • Only configure v4 or v6 address blocks if v4 or v6 respectively is -enabled. [sk2]

    -
  • -
  • Add note. [sk2]

    -
  • -
  • Fix ipv4 var. [sk2]

    -
  • -
  • Tidy debug. [sk2]

    -
  • -
  • More work on nx_os. [sk2]

    -
  • -
  • Initial work for nxos. [sk2]

    -
  • -
  • Updates to allow dual-stack for cisco. [sk2]

    -
  • -
  • Update scale for resized initial. [sk2]

    -
  • -
  • Inc version. [sk2]

    -
  • -
  • Tidy syntax. [sk2]

    -
  • -
  • Tidying example access syntax. [sk2]

    -
  • -
  • Better default scale for large topologies. [sk2]

    -
  • -
  • Rename icon to descriptive label. [sk2]

    -
  • -
  • Fix var names. [sk2]

    -
  • -
  • Fix order of description. [sk2]

    -
  • -
  • Set config dir, fix chassis. [sk2]

    -
  • -
  • Add todo note. [sk2]

    -
  • -
  • Fix error handling. [sk2]

    -
  • -
  • Default to memory. [sk2]

    -
  • -
  • Fix quagga ip format. [sk2]

    -
  • -
  • Set dynagen config directory. [sk2]

    -
  • -
  • Tidy dynagen. [sk2]

    -
  • -
  • Toggle off v6. [sk2]

    -
  • -
  • Use 7200 image. [sk2]

    -
  • -
  • Add functions to nidb to be closer to anm. [sk2]

    -
  • -
  • Ospf cost support. [sk2]

    -
  • -
  • Enable v6. [sk2]

    -
  • -
  • Fix level support for ibgp from yed. [sk2]

    -
  • -
  • Update add_edge attr. [sk2]

    -
  • -
  • Update. [sk2]

    -
  • -
  • Initial commit of dynagen code for gh-46. [sk2]

    -
  • -
  • Handle no ip6 graph. [sk2]

    -
  • -
  • Remove overlay_accessor: use either anm['overlay_id'] or -G_a.overlay("overlay_id") [sk2]

    -
  • -
  • Access overlay directly. [sk2]

    -
  • -
  • Support v6. [sk2]

    -
  • -
  • Support v6. [sk2]

    -
  • -
  • Add groupby independent of subgraph. [sk2]

    -
  • -
  • Add library for # [sk2]

    -
  • -
  • Info -> debug. [sk2]

    -
  • -
  • Increment version. [sk2]

    -
  • -
  • Add # library. [sk2]

    -
  • -
  • Tidy logic, add l3 to ibgp clustering. [sk2]

    -
  • -
  • Look for correct package name. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Tidying, adding from HRR->RR if same RR group. [sk2]

    -
  • -
  • Add extra logging information. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Change interface allocations. [sk2]

    -
  • -
  • Simplifying. [sk2]

    -
  • -
  • Tidy to use routers. [sk2]

    -
  • -
  • Exclude _interfaces from edge tooltip. [sk2]

    -
  • -
  • Fix websocket tooltip. [sk2]

    -
  • -
  • Add deploy wrapped, tidy. [sk2]

    -
  • -
  • Tidy syntax. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Add routers shortcut. [sk2]

    -
  • -
  • Support ibgp l1->l3 if not l2 in ibgp_l3_cluster. [sk2]

    -
  • -
  • Add ignores. [sk2]

    -
  • -
  • Update ignore. [sk2]

    -
  • -
  • Add fonts to manifest. [Simon Knight]

    -
  • -
  • Remove other deps. [Simon Knight]

    -
  • -
  • Update setup. [Simon Knight]

    -
  • -
  • Update version. [Simon Knight]

    -
  • -
  • Update icons folder. [Simon Knight]

    -
  • -
  • Merge pull request #64 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Handle pika. [sk2]

    -
  • -
  • Merge pull request #63 from sk2/master. [Simon Knight]

    -

    push

    -
  • -
  • Merge pull request #62 from sk2/Stable. [Simon Knight]

    -

    improvements to measurement and traceroute plotting

    -
  • -
  • Improvements to measurement and traceroute plotting. [sk2]

    -
  • -
  • Merge pull request #61 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Disable measure by default. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Add bootup circles. [sk2]

    -
  • -
  • Show websocket state as icon. [sk2]

    -
  • -
  • Merge pull request #60 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Add example. [sk2]

    -
  • -
  • Merge pull request #59 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • More features. [sk2]

    -
  • -
  • Exit for paths. [sk2]

    -
  • -
  • Bugfix. [sk2]

    -
  • -
  • Allow direct messaging using messaging rather than manual rabbitmq -construction. [sk2]

    -
  • -
  • Merge pull request #58 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Example updates. [sk2]

    -
  • -
  • Bugfix. [sk2]

    -
  • -
  • Measure client updates. [sk2]

    -
  • -
  • Change import order. [sk2]

    -
  • -
  • More updates. [sk2]

    -
  • -
  • Take rmq as argument. [sk2]

    -
  • -
  • Add measure client. [sk2]

    -
  • -
  • Merge pull request #57 from sk2/development. [Simon Knight]

    -

    tidy

    -
  • -
  • Ignore rendered. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Merge pull request #56 from sk2/development. [Simon Knight]

    -

    move example to base dir

    -
  • -
  • Move example to base dir. [sk2]

    -
  • -
  • Merge pull request #55 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Work on example. [sk2]

    -
  • -
  • Update default log. [sk2]

    -
  • -
  • More icon. [sk2]

    -
  • -
  • Example. [sk2]

    -
  • -
  • More icon. [sk2]

    -
  • -
  • More icon. [sk2]

    -
  • -
  • Merge pull request #54 from sk2/development. [Simon Knight]

    -

    Development

    -
  • -
  • Remove egg info. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Update icon. [sk2]

    -
  • -
  • Move vis folder. [sk2]

    -
  • -
  • Update packaging dependencies. [sk2]

    -
  • -
  • Update doc, setup config. [sk2]

    -
  • -
  • Merge pull request #53 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • Add dependencies. [sk2]

    -
  • -
  • Add icons to ui. [sk2]

    -
  • -
  • Merge pull request #52 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • Update icon. [sk2]

    -
  • -
  • Remove old messaging package. [sk2]

    -
  • -
  • Merge pull request #51 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • Move to examples directory. [sk2]

    -
  • -
  • Add zoom fit button. [sk2]

    -
  • -
  • Update vis layout. [sk2]

    -
  • -
  • Update year, add favico to website. [sk2]

    -
  • -
  • Icon data. [sk2]

    -
  • -
  • Update icon. [sk2]

    -
  • -
  • Auto zoom, remove interfaces and labels. [sk2]

    -
  • -
  • Dont hide labels. [sk2]

    -
  • -
  • Add icon. [sk2]

    -
  • -
  • Remove unused messaging. [sk2]

    -
  • -
  • Ui tidy. [sk2]

    -
  • -
  • Revert. [sk2]

    -
  • -
  • Auto hide revisions, tidy general ui, remove interfaces with toggle. -[sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Merge pull request #50 from sk2/interfaces. [Simon Knight]

    -

    Merge

    -
  • -
  • Add docs to repo. [sk2]

    -
  • -
  • Remove unused python package. [sk2]

    -
  • -
  • Add note. [sk2]

    -
  • -
  • Add icon. [sk2]

    -
  • -
  • Add todo. [sk2]

    -
  • -
  • Better node handling. [sk2]

    -
  • -
  • Remove debug. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Simpler add edges wrapper. [sk2]

    -
  • -
  • Tidy manifest. [sk2]

    -
  • -
  • Set default for blank labels, better handling of non-unique labels: if -so then set with asn. [sk2]

    -
  • -
  • Handle multi-as from zoo. [sk2]

    -
  • -
  • Tidying. [sk2]

    -
  • -
  • Add ip. [sk2]

    -
  • -
  • Processing for nren 1400. [sk2]

    -
  • -
  • Simple example. [sk2]

    -
  • -
  • More example. [sk2]

    -
  • -
  • More examples. [sk2]

    -
  • -
  • Set False for yEd exported booleans (by default not present on a node) -[sk2]

    -
  • -
  • Tidy simple. [sk2]

    -
  • -
  • Add retain to adding nodes through add_overlay. [sk2]

    -
  • -
  • Add build option. [sk2]

    -
  • -
  • Tidy simple example. [sk2]

    -
  • -
  • Add examples. [sk2]

    -
  • -
  • Tidy logic. [sk2]

    -
  • -
  • New simplified example. [sk2]

    -
  • -
  • Tidy, toggle out publishing v6 topology. [sk2]

    -
  • -
  • Use new add overlay format. [sk2]

    -
  • -
  • Add ability to add nodes at overlay creation. [sk2]

    -
  • -
  • Merge pull request #43 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • V6 overlay support and allocation done. [sk2]

    -
  • -
  • Optional server param for messaging: not required if using http post, -as picked up from settings. [sk2]

    -
  • -
  • Adding ipv6 support. [sk2]

    -
  • -
  • Increment version. [sk2]

    -
  • -
  • Support import of cisco templates. [sk2]

    -
  • -
  • Move more cisco specific code out. [sk2]

    -
  • -
  • Move cisco specific load and deploy to autonetkit_cisco module. [sk2]

    -
  • -
  • Update doc. [sk2]

    -
  • -
  • Increment. [sk2]

    -
  • -
  • Ensure area is string. [sk2]

    -
  • -
  • Initial work on highlighting shared interfaces (eg loopback0) [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Remove unused allocate_hardware. [sk2]

    -
  • -
  • Add interface labels. [sk2]

    -
  • -
  • Tidying debug. [sk2]

    -
  • -
  • More interfaces. [sk2]

    -
  • -
  • More improvements to interfaces. [sk2]

    -
  • -
  • More work on interfaces - work in progress. [sk2]

    -
  • -
  • More work on vis. [sk2]

    -
  • -
  • Display interfaces for directed edges. [sk2]

    -
  • -
  • Bigger font for edges. [sk2]

    -
  • -
  • Much improved directed edges, now with labels on the edge. [sk2]

    -
  • -
  • Redoing directed edges. [sk2]

    -
  • -
  • Dev. [sk2]

    -
  • -
  • Dev. [sk2]

    -
  • -
  • Disable zoom. [sk2]

    -
  • -
  • Initial work on dynamic zooming. [sk2]

    -
  • -
  • Remote message pipe from setup guide. [sk2]

    -
  • -
  • Merge pull request #40 from sk2/interfaces. [Simon Knight]

    -

    tidy

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Merge pull request #39 from sk2/interfaces. [Simon Knight]

    -

    Interfaces

    -
  • -
  • Disable full hostnames. [sk2]

    -
  • -
  • Increment version. [sk2]

    -
  • -
  • Lower node labels. [sk2]

    -
  • -
  • Lower node labels. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Better option to disable edge labels. [sk2]

    -
  • -
  • Don't display interfaces. [sk2]

    -
  • -
  • Better handling of interfaces in tooltip. [sk2]

    -
  • -
  • Add option to disable edge labels. [sk2]

    -
  • -
  • Tidy area zero handling. [sk2]

    -
  • -
  • Handle ip address format for ospf areas. [sk2]

    -
  • -
  • Tidy, todos. [sk2]

    -
  • -
  • Correct rendering of arrays in tooltips. [sk2]

    -
  • -
  • Interface toggle. [sk2]

    -
  • -
  • More improvements for interfaces. [sk2]

    -
  • -
  • Better interface vis. [sk2]

    -
  • -
  • Add interfaces to anm, render interfaces on vis. [sk2]

    -
  • -
  • Store ospf areas on node. [sk2]

    -
  • -
  • Upgrade d3 from v2 to v3. [sk2]

    -
  • -
  • Use v3 of d3, hide history buttons. [sk2]

    -
  • -
  • Alignment, grouping for ospf areas. [sk2]

    -
  • -
  • Tidy. [sk2]

    -
  • -
  • Correct docstring. [sk2]

    -
  • -
  • Merge pull request #38 from sk2/dev. [Simon Knight]

    -

    default netkit render

    -
  • -
  • Default netkit render. [sk2]

    -
  • -
  • Merge pull request #37 from sk2/dev. [Simon Knight]

    -

    better web message

    -
  • -
  • Better web message. [sk2]

    -
  • -
  • Merge pull request #36 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Ignore. [sk2]

    -
  • -
  • Add tornado to base dependencies. [sk2]

    -
  • -
  • Error handling. [sk2]

    -
  • -
  • Error handling if no input file. [sk2]

    -
  • -
  • Better desc string. [sk2]

    -
  • -
  • Disable pika requirement for base install. [sk2]

    -
  • -
  • Enable http post by default. [sk2]

    -
  • -
  • Merge pull request #35 from sk2/dev. [Simon Knight]

    -

    Merge latest updates

    -
  • -
  • Set input label for other device types, used in post-processing -module. [Simon Knight]

    -
  • -
  • Support manually specified interface names. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Support manually specified interface names. [Simon Knight]

    -
  • -
  • New messaging module. [Simon Knight]

    -
  • -
  • Better error handling for invalid category ids. [Simon Knight]

    -
  • -
  • Fix syntax error in logging. [Simon Knight]

    -
  • -
  • Add end statement. [Simon Knight]

    -
  • -
  • Handle extra attribute. [Simon Knight]

    -
  • -
  • Copy across extra attribute. [Simon Knight]

    -
  • -
  • Support for specified interface names. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Update look and feel. [Simon Knight]

    -
  • -
  • Rename ank_pika to more generic messaging module. [Simon Knight]

    -
  • -
  • Increment version. [Simon Knight]

    -
  • -
  • Add new icons. [Simon Knight]

    -
  • -
  • Tidy http post, support manually specified IPs. [Simon Knight]

    -
  • -
  • Updates to logging. [Simon Knight]

    -
  • -
  • Merge pull request #34 from sk2/dev. [Simon Knight]

    -

    iBGP hierarchies, HTTP Post to update web ui

    -
  • -
  • Tweak line offsets. [Simon Knight]

    -
  • -
  • Support HTTP POST for updating topologies. [Simon Knight]

    -
  • -
  • Fix indent. [Simon Knight]

    -
  • -
  • Group by l3 cluster. [Simon Knight]

    -
  • -
  • Tweaks to vis. [Simon Knight]

    -
  • -
  • Update taper. [Simon Knight]

    -
  • -
  • More tapered edges. [Simon Knight]

    -
  • -
  • Update ignore. [Simon Knight]

    -
  • -
  • Tidy neighbors. [Simon Knight]

    -
  • -
  • Ibgp hierarchies. [Simon Knight]

    -
  • -
  • Tapered edges. [Simon Knight]

    -
  • -
  • Merge pull request #33 from sk2/dev. [Simon Knight]

    -

    route reflectors

    -
  • -
  • Corrections to iBGP. [Simon Knight]

    -
  • -
  • Seperate out address classes. [Simon Knight]

    -
  • -
  • Seperate out address classes. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Route reflectors. [Simon Knight]

    -
  • -
  • Merge pull request #32 from sk2/dev. [Simon Knight]

    -

    IOS IGP, bugfix for single-AS loopbacks

    -
  • -
  • Fix single-AS loopbacks. [Simon Knight]

    -
  • -
  • Hover for ip address nodes. [Simon Knight]

    -
  • -
  • Interface type. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Fix network format. [Simon Knight]

    -
  • -
  • Merge pull request #31 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Add isis support to ios. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Merge pull request #30 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Issue with configspec, update areas. [Simon Knight]

    -
  • -
  • Set syntax from config defaults. [Simon Knight]

    -
  • -
  • Merge pull request #29 from sk2/dev. [Simon Knight]

    -

    fixes to ios config, interface naming, separate loopback IP groups: -don't allocate 10.0.0.0 etc as a loopback

    -
  • -
  • Seperate loopback groups: don't allocate 10.0.0.0 as a loopback. -[Simon Knight]

    -
  • -
  • Allocated loopbacks in a group: don't want 10.0.0.0 as a loopback ip. -[Simon Knight]

    -
  • -
  • Add point-to-point to networks. [Simon Knight]

    -
  • -
  • Id format Ethernet x/0. [Simon Knight]

    -
  • -
  • Option to toggle timestamp in rendered output. [Simon Knight]

    -
  • -
  • Handle socket error with warning. [Simon Knight]

    -
  • -
  • Merge pull request #28 from sk2/dev. [Simon Knight]

    -

    website updates, add readme

    -
  • -
  • Update website, add readme. [Simon Knight]

    -
  • -
  • Fix github link. [Simon Knight]

    -
  • -
  • Update css page references, title in using. [Simon Knight]

    -
  • -
  • New website. [Simon Knight]

    -
  • -
  • Merge pull request #27 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Tidy loading. [Simon Knight]

    -
  • -
  • Default ospf area for graphml. [Simon Knight]

    -
  • -
  • Update defaut topology for vis. [Simon Knight]

    -
  • -
  • Merge pull request #26 from sk2/dev. [Simon Knight]

    -

    add edge labels, restore print css, hierarchical ospf for IOS

    -
  • -
  • More hierarchical ospf config. [Simon Knight]

    -
  • -
  • Ospf hierarchy. [Simon Knight]

    -
  • -
  • Fix print css. [Simon Knight]

    -
  • -
  • Edge labels. [Simon Knight]

    -
  • -
  • Merge pull request #24 from sk2/dev. [Simon Knight]

    -

    remove debug, update github link to dev alpha

    -
  • -
  • Update github link to dev alpha. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Merge pull request #23 from sk2/dev. [Simon Knight]

    -

    Optional render, adding node label and edge group dropdowns, add ospf -areas, tipsy for tooltips

    -
  • -
  • Default area of 0. [Simon Knight]

    -
  • -
  • Redrawing changed edge_group_id. [Simon Knight]

    -
  • -
  • Hide infobar, larger font for yapsy. [Simon Knight]

    -
  • -
  • Use tipsy for tooltips. [Simon Knight]

    -
  • -
  • Add OSPF router type. [Simon Knight]

    -
  • -
  • Add title on hover, tidying. [Simon Knight]

    -
  • -
  • Work on ospf areas. [Simon Knight]

    -
  • -
  • Support for node label and edge grouping. [Simon Knight]

    -
  • -
  • Add comments. [Simon Knight]

    -
  • -
  • Add dropdowns for node label and edge grouping. [Simon Knight]

    -
  • -
  • Add underscore js library. [Simon Knight]

    -
  • -
  • Add render option. [Simon Knight]

    -
  • -
  • Merge pull request #22 from sk2/dev. [Simon Knight]

    -

    bugfix for ip if no links, add support for nested grouping in vis -(used for ospf attributes)

    -
  • -
  • Fix ip crash if no links. [Simon Knight]

    -
  • -
  • Remove debug, copy ospf_area into "area" in ospf graph. [Simon Knight]

    -
  • -
  • Support ospf areas, nested groupings. [Simon Knight]

    -
  • -
  • More parameters for copy_attr_from. [Simon Knight]

    -
  • -
  • Merge pull request #16 from sk2/dev. [Simon Knight]

    -

    Improvements to packaging for textfsm templates, add demo video to -website, fix passive interface for quagga IGP

    -
  • -
  • Add demo video. [Simon Knight]

    -
  • -
  • Correct path. [Simon Knight]

    -
  • -
  • Open traceroute template from package. [Simon Knight]

    -
  • -
  • Use package template file. [Simon Knight]

    -
  • -
  • Update textfsm include. [Simon Knight]

    -
  • -
  • Include textfsm templates. [Simon Knight]

    -
  • -
  • Non numeric first character for zebra hostname too. [Simon Knight]

    -
  • -
  • Make sure quagga hostnames start with letter. [Simon Knight]

    -
  • -
  • Passive interfaces for ebgp. [Simon Knight]

    -
  • -
  • Add loopback to interfaces. [Simon Knight]

    -
  • -
  • Handle empty key string. [Simon Knight]

    -
  • -
  • Merge pull request #15 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Make screencasts more visible. [Simon Knight]

    -
  • -
  • Change ip ranges. [Simon Knight]

    -
  • -
  • Add alpha sorting for machines. [Simon Knight]

    -
  • -
  • Use interface id. [Simon Knight]

    -
  • -
  • Add sorting. [Simon Knight]

    -
  • -
  • Add text sorting. [Simon Knight]

    -
  • -
  • Start loopbacks at 172.16.127 so don't interfere with taps. [Simon -Knight]

    -
  • -
  • Don't clobber measure. [Simon Knight]

    -
  • -
  • Less verbose messages. [Simon Knight]

    -
  • -
  • Better output. [Simon Knight]

    -
  • -
  • Add note. [Simon Knight]

    -
  • -
  • Merge pull request #13 from sk2/dev. [Simon Knight]

    -

    drive compilation from config file

    -
  • -
  • Drive compilation from config file. [Simon Knight]

    -
  • -
  • Merge pull request #12 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Add publications. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Neater maximise display. [Simon Knight]

    -
  • -
  • Handle case of no infrastructure ips to advertise. [Simon Knight]

    -
  • -
  • Allocate loopbacks seperately to infra, closes gh-10. [Simon Knight]

    -
  • -
  • Add css maximise option. [Simon Knight]

    -
  • -
  • Merge pull request #11 from sk2/dev. [Simon Knight]

    -

    Cleaner updating to web interface

    -
  • -
  • Read updates. [Simon Knight]

    -
  • -
  • Remove extra sending pika. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Merge pull request #9 from sk2/dev. [Simon Knight]

    -

    Basic lat/lon to x/y from zoo, grid layout if no x/y set

    -
  • -
  • Add comment. [Simon Knight]

    -
  • -
  • Merge pull request #8 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Support for reading from stdin, writing single-file templates into -memory. [Simon Knight]

    -
  • -
  • Use argparse instead of deprecated optparse. [Simon Knight]

    -
  • -
  • Support html in status: lists rather than block of text. [Simon -Knight]

    -
  • -
  • Merge pull request #7 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Update instructions. [Simon Knight]

    -
  • -
  • Inc. [Simon Knight]

    -
  • -
  • Update isis format. [Simon Knight]

    -
  • -
  • Default isis metric. [Simon Knight]

    -
  • -
  • Merge pull request #6 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Merge pull request #5 from sk2/master. [Simon Knight]

    -

    Dev

    -
  • -
  • Dont warn if no rmq. [Simon Knight]

    -
  • -
  • Merge pull request #4 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Update tutorial. [Simon Knight]

    -
  • -
  • Inc. [Simon Knight]

    -
  • -
  • Better handling of timestamp. [Simon Knight]

    -
  • -
  • Include quagga templates. [Simon Knight]

    -
  • -
  • Disable file logging for now. [Simon Knight]

    -
  • -
  • Default to compile. [Simon Knight]

    -
  • -
  • Don't write overlays as graphml. [Simon Knight]

    -
  • -
  • Merge pull request #3 from sk2/master. [Simon Knight]

    -

    Merge website and templates

    -
  • -
  • Add tutorial. [Simon Knight]

    -
  • -
  • Inc ver. [Simon Knight]

    -
  • -
  • Include quagga templates. [Simon Knight]

    -
  • -
  • Merge pull request #2 from sk2/dev. [Simon Knight]

    -

    Better defaults and compilation

    -
  • -
  • Better compilation depending on presence of platform/host. [Simon -Knight]

    -
  • -
  • Update graphml default. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Merge pull request #1 from sk2/dev. [Simon Knight]

    -

    Dev

    -
  • -
  • Add development install guide. [Simon Knight]

    -
  • -
  • Increment version. [Simon Knight]

    -
  • -
  • Update interface names. [Simon Knight]

    -
  • -
  • Add youtube link for screencasts. [Simon Knight]

    -
  • -
  • Increment version, include html data in package. [Simon Knight]

    -
  • -
  • Support telnet sockets. [Simon Knight]

    -
  • -
  • Use new load module. [Simon Knight]

    -
  • -
  • More visible trace data. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Sorting edges. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • More visible traceroutes. [Simon Knight]

    -
  • -
  • Add support for telnet. [Simon Knight]

    -
  • -
  • Move vis inside distro. [Simon Knight]

    -
  • -
  • Updates. [Simon Knight]

    -
  • -
  • Use ank vis in distro. [Simon Knight]

    -
  • -
  • Increment version. [Simon Knight]

    -
  • -
  • Include vis in distro. [Simon Knight]

    -
  • -
  • Increment version. [Simon Knight]

    -
  • -
  • Fix naming. [Simon Knight]

    -
  • -
  • Comment out dev. [Simon Knight]

    -
  • -
  • Increment version. [Simon Knight]

    -
  • -
  • Use correct ip for update-source. [Simon Knight]

    -
  • -
  • Tidy anm sending over rabbit. [Simon Knight]

    -
  • -
  • Dev. [Simon Knight]

    -
  • -
  • Error handling alpha only name sorting. [Simon Knight]

    -
  • -
  • More visible traces. [Simon Knight]

    -
  • -
  • Updates for netkit deploy. [Simon Knight]

    -
  • -
  • Use rabbitmq server from config. [Simon Knight]

    -
  • -
  • Better log message. [Simon Knight]

    -
  • -
  • Fix int ids. [Simon Knight]

    -
  • -
  • Dev. [Simon Knight]

    -
  • -
  • Update version. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Use package name. [Simon Knight]

    -
  • -
  • Get correct package name for version. [Simon Knight]

    -
  • -
  • Store original node label. [Simon Knight]

    -
  • -
  • Update ignore for package. [Simon Knight]

    -
  • -
  • Rename package version. [Simon Knight]

    -
  • -
  • Add console help, version. [Simon Knight]

    -
  • -
  • Remove dev. [Simon Knight]

    -
  • -
  • Adding directed input graph support - useful for edge attributes. -[Simon Knight]

    -
  • -
  • Sort interfaces on id. [Simon Knight]

    -
  • -
  • Copy edge attributes. [Simon Knight]

    -
  • -
  • Edge comparisons for sorting. [Simon Knight]

    -
  • -
  • Improve bgp, isis. [Simon Knight]

    -
  • -
  • Better version string. [Simon Knight]

    -
  • -
  • Notes. [Simon Knight]

    -
  • -
  • Better ios support, isis. [Simon Knight]

    -
  • -
  • Default isis process id. [Simon Knight]

    -
  • -
  • Hide nav for printing. [Simon Knight]

    -
  • -
  • Longer delay for monitor, optional archiving, neater json writing for -diff. [Simon Knight]

    -
  • -
  • Select between IGPs. [Simon Knight]

    -
  • -
  • Diffing support. [Simon Knight]

    -
  • -
  • Greatly improved differ. [Simon Knight]

    -
  • -
  • Ignore diff. [Simon Knight]

    -
  • -
  • Error message if no rabbitmq. [Simon Knight]

    -
  • -
  • Disable hardware alloc for now. [Simon Knight]

    -
  • -
  • Default dir. [Simon Knight]

    -
  • -
  • Testing diff. [Simon Knight]

    -
  • -
  • Handle sets. [Simon Knight]

    -
  • -
  • More on interfaces. [Simon Knight]

    -
  • -
  • Add graphics data. [Simon Knight]

    -
  • -
  • Setting group attr. [Simon Knight]

    -
  • -
  • Better error handling, retain of node id. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • More dev. [Simon Knight]

    -
  • -
  • Basic interface icon. [Simon Knight]

    -
  • -
  • More testing for hw. [Simon Knight]

    -
  • -
  • Add remove_node fn. [Simon Knight]

    -
  • -
  • More hw alloc. [Simon Knight]

    -
  • -
  • Handle empty overlays. [Simon Knight]

    -
  • -
  • Adding conn graph. [Simon Knight]

    -
  • -
  • Graph based hardware. [Simon Knight]

    -
  • -
  • Change zoom. [Simon Knight]

    -
  • -
  • Group by device for conn graph. [Simon Knight]

    -
  • -
  • Add hash for set comparison, accessor for anm, option to add node, -option to not clobber adding nodes, [Simon Knight]

    -
  • -
  • Before switching to graph-based interface representation. [Simon -Knight]

    -
  • -
  • Hardware profiles. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Adding hardware profiles. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Adding standalone actions. [Simon Knight]

    -
  • -
  • Policy parsing implemented, including nested if/then/else. [Simon -Knight]

    -
  • -
  • More policy. [Simon Knight]

    -
  • -
  • More pol. [Simon Knight]

    -
  • -
  • Initial policy parsing. [Simon Knight]

    -
  • -
  • Fix packaging. [Simon Knight]

    -
  • -
  • Handle imports better. [Simon Knight]

    -
  • -
  • Remove unused dep. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Update. [Simon Knight]

    -
  • -
  • Updates. [Simon Knight]

    -
  • -
  • Disable sockets for now. [Simon Knight]

    -
  • -
  • Adding messaging. [Simon Knight]

    -
  • -
  • Update ignore. [Simon Knight]

    -
  • -
  • Add cisco internal support, tidy up, update build options if updated, -relay update node parameter. [Simon Knight]

    -
  • -
  • Default disabled pika. [Simon Knight]

    -
  • -
  • Add node label, tidy. [Simon Knight]

    -
  • -
  • Relay update, support cisco internal host. [Simon Knight]

    -
  • -
  • Handle disabled pika. [Simon Knight]

    -
  • -
  • Restoring ios2. [Simon Knight]

    -
  • -
  • Update ignore. [Simon Knight]

    -
  • -
  • Exceptions class. [Simon Knight]

    -
  • -
  • Add sorting. [Simon Knight]

    -
  • -
  • Better exception handling. [Simon Knight]

    -
  • -
  • Add internal host. [Simon Knight]

    -
  • -
  • Handle non .graphml. [Simon Knight]

    -
  • -
  • Todo. [Simon Knight]

    -
  • -
  • Change sizes. [Simon Knight]

    -
  • -
  • Ignore. [Simon Knight]

    -
  • -
  • Remove. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Moved ip to be native networkx graph based. [Simon Knight]

    -
  • -
  • More tidying compiler. [Simon Knight]

    -
  • -
  • Keeping track of parent to update dicts. [Simon Knight]

    -
  • -
  • Nidb access methods needing work - can't modify dictionary accessors. -[Simon Knight]

    -
  • -
  • Before updating interface format. [Simon Knight]

    -
  • -
  • Search by edge id. [Simon Knight]

    -
  • -
  • Append as kwargs. [Simon Knight]

    -
  • -
  • Moving to integrated lists. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Put in list (expected format) [Simon Knight]

    -
  • -
  • Don't clobber host attribute from switches bugfix. [Simon Knight]

    -
  • -
  • Adding sorting to tidy up compiler. [Simon Knight]

    -
  • -
  • Allocate ip not subnet to loopback. [Simon Knight]

    -
  • -
  • Add area back. [Simon Knight]

    -
  • -
  • Better vis, moving towards twitter bootstrap, scalable with resizing. -[Simon Knight]

    -
  • -
  • Tidying formatting. [Simon Knight]

    -
  • -
  • Minimum of one rr per AS. [Simon Knight]

    -
  • -
  • Better transition from overlay -> ip allocs. [Simon Knight]

    -
  • -
  • Add node sorting for anm, placeholder for nidb. [Simon Knight]

    -
  • -
  • More ip progress. [Simon Knight]

    -
  • -
  • Ip addressing working. [Simon Knight]

    -
  • -
  • More ip. [Simon Knight]

    -
  • -
  • Add option to search for edge by node pair. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Use neighbors from overlay. [Simon Knight]

    -
  • -
  • Better ip vis. [Simon Knight]

    -
  • -
  • Testing radial layout. [Simon Knight]

    -
  • -
  • More addressing. [Simon Knight]

    -
  • -
  • More ip addressing. [Simon Knight]

    -
  • -
  • More ip addressing. [Simon Knight]

    -
  • -
  • Redoing IP allocation to be digraph (DAG) based. [Simon Knight]

    -
  • -
  • Better plotting. [Simon Knight]

    -
  • -
  • Transitions for updated ip data. [Simon Knight]

    -
  • -
  • Tidying, adding support for ip allocation plotting. [Simon Knight]

    -
  • -
  • Add demo to website. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • More config driven. [Simon Knight]

    -
  • -
  • Use standard folder format. [Simon Knight]

    -
  • -
  • Turn down base logging (fixes verbose paramiko) [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Use config instead of hard-coded settings. [Simon Knight]

    -
  • -
  • Default boolean for general configs, add deploy hosts. [Simon Knight]

    -
  • -
  • Update title with revison number. [Simon Knight]

    -
  • -
  • Tidying igp. [Simon Knight]

    -
  • -
  • Move deploy to directory. [Simon Knight]

    -
  • -
  • Measure directory. [Simon Knight]

    -
  • -
  • More isis support. [Simon Knight]

    -
  • -
  • Ready for sorted. [Simon Knight]

    -
  • -
  • Debug on key miss. [Simon Knight]

    -
  • -
  • Retain data. [Simon Knight]

    -
  • -
  • Isis support. [Simon Knight]

    -
  • -
  • Ignore extra rendered. [Simon Knight]

    -
  • -
  • Subgraph only for netkit nodes. [Simon Knight]

    -
  • -
  • Remove defaults (use config) [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Also load cisco compiler. [Simon Knight]

    -
  • -
  • Defaults from config. [Simon Knight]

    -
  • -
  • Defaults from config. [Simon Knight]

    -
  • -
  • More concise edge adding syntax. [Simon Knight]

    -
  • -
  • Debug. [Simon Knight]

    -
  • -
  • Tidy ip to network entity function. [Simon Knight]

    -
  • -
  • Use new graphml reader. [Simon Knight]

    -
  • -
  • Better most frequent algorithm. [Simon Knight]

    -
  • -
  • Split out graphml reader. [Simon Knight]

    -
  • -
  • Split out graphml reader, better most frequent algorithm. [Simon -Knight]

    -
  • -
  • Create load module directory. [Simon Knight]

    -
  • -
  • Move to load module directory, remove old caching code. [Simon Knight]

    -
  • -
  • Add most frequent function, use instead. [Simon Knight]

    -
  • -
  • Use loopback to create network_entity_title. [Simon Knight]

    -
  • -
  • Bugfix: take most frequent ASN for inter-asn collision domains, rather -than mean. [Simon Knight]

    -

    (otherwise cd between ASN 2 and 4 gets put in 3)

    -
  • -
  • Add setup for pypi. [Simon Knight]

    -
  • -
  • Turn down log verbosity. [Simon Knight]

    -
  • -
  • Tidied, adding IS-IS support. [Simon Knight]

    -
  • -
  • Better file checking, tidied up building, better monitor mode, -checking if build has changed, better stack trace. [Simon Knight]

    -
  • -
  • Ignore egg builds. [Simon Knight]

    -
  • -
  • Default overlay of phy not ospf. [Simon Knight]

    -
  • -
  • Bugfix: asn of parent not neighbor. [Simon Knight]

    -
  • -
  • Render for single run mode. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • More descriptive queue name. [Simon Knight]

    -
  • -
  • Use server from config. [Simon Knight]

    -
  • -
  • Better formatting,zoom. [Simon Knight]

    -
  • -
  • Add zoom. [Simon Knight]

    -
  • -
  • Ignore local config, crash dump. [Simon Knight]

    -
  • -
  • Read from config. [Simon Knight]

    -
  • -
  • Default localhost. [Simon Knight]

    -
  • -
  • Default localhost. [Simon Knight]

    -
  • -
  • Hiding better layout for printing. [Simon Knight]

    -
  • -
  • Better layout. [Simon Knight]

    -
  • -
  • Fix fast-forward, add hidden option for printing each history -revision. [Simon Knight]

    -
  • -
  • Update title with revision id. [Simon Knight]

    -
  • -
  • Added clarity for comparison. [Simon Knight]

    -
  • -
  • Add arrows, full history support. [Simon Knight]

    -
  • -
  • Use global config settings. [Simon Knight]

    -
  • -
  • Support for pika to update web ui. [Simon Knight]

    -
  • -
  • Add note. [Simon Knight]

    -
  • -
  • Revision back and forward. [Simon Knight]

    -
  • -
  • Also turn off infobar. [Simon Knight]

    -
  • -
  • Preselect correct dropdown based on overlay_id at init, add history -support. [Simon Knight]

    -
  • -
  • Tidy print. [Simon Knight]

    -
  • -
  • Remove redundant code. [Simon Knight]

    -
  • -
  • Add print css to disable nav. [Simon Knight]

    -
  • -
  • Extra notes. [Simon Knight]

    -
  • -
  • Tidying js in vis. [Simon Knight]

    -
  • -
  • More tidying. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Add config file. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Serialize ip using json. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Default not to compile. [Simon Knight]

    -
  • -
  • Tidy json support. [Simon Knight]

    -
  • -
  • Use json instead of pickle for serializing anm. [Simon Knight]

    -
  • -
  • Function to copy graphics across from anm. [Simon Knight]

    -
  • -
  • Tidying compression, moving network construction to seperate module. -[Simon Knight]

    -
  • -
  • Move network construction into seperate module. [Simon Knight]

    -
  • -
  • Abstract out pika messaging. [Simon Knight]

    -
  • -
  • Restore diffing (disabled for now awaiting stable IP addressing) -[Simon Knight]

    -
  • -
  • Better error handling, initial support for diffs. [Simon Knight]

    -
  • -
  • Work with attributes rather than anm nodes directly. [Simon Knight]

    -
  • -
  • Handling nidb. [Simon Knight]

    -
  • -
  • Better serialization, de-serialize ip address/ip networks in lists -properly. [Simon Knight]

    -
  • -
  • Don't use object references to anm nodes in nidb, use attributes eg -asn, label, etc. [Simon Knight]

    -
  • -
  • Save/restore nidb using pickle. [Simon Knight]

    -
  • -
  • Directed edge arcs, working on arrow alignment. [Simon Knight]

    -
  • -
  • Trace colour. [Simon Knight]

    -
  • -
  • Plotting all traceroutes (bugfix) [Simon Knight]

    -
  • -
  • Loading saved json. [Simon Knight]

    -
  • -
  • Saving json. [Simon Knight]

    -
  • -
  • Testing cloud (doesn't scale width well) [Simon Knight]

    -
  • -
  • Add cloud icon. [Simon Knight]

    -
  • -
  • Set new default. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Tweaking grouping. [Simon Knight]

    -
  • -
  • Grouping for single nodes, tidy. [Simon Knight]

    -
  • -
  • Add hull for groups of 2 nodes. [Simon Knight]

    -
  • -
  • Add compression for large anm that exceed rabbitmq max frame size. -[Simon Knight]

    -
  • -
  • Add compression support. [Simon Knight]

    -
  • -
  • Add queue watching for debugging. [Simon Knight]

    -
  • -
  • Filter out incomplete traceroutes. [Simon Knight]

    -
  • -
  • Default not to compile. [Simon Knight]

    -
  • -
  • Increase dimensions. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Json handling IP address serialization and deserialization. [Simon -Knight]

    -
  • -
  • Use json properly. [Simon Knight]

    -
  • -
  • Example to load on webserver. [Simon Knight]

    -
  • -
  • Send json not pickle to webserver. [Simon Knight]

    -
  • -
  • Update overlay dropdown, remove poll code. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Pass json over rabbitmq rather than pickled anm - much more flexible. -[Simon Knight]

    -
  • -
  • Initial work to pass json anm rather than pickled anm across network -to webserver. [Simon Knight]

    -
  • -
  • Create dir if needed for topology (bugfix) [Simon Knight]

    -
  • -
  • Extra todo note. [Simon Knight]

    -
  • -
  • Don't relabel if same label (saves clobbering node data) [Simon -Knight]

    -
  • -
  • Visual tweaks, reload images -> stay vector. [Simon Knight]

    -
  • -
  • Placeholder. [Simon Knight]

    -
  • -
  • Ignore logs. [Simon Knight]

    -
  • -
  • Script to update website. [Simon Knight]

    -
  • -
  • Demo website redirect. [Simon Knight]

    -
  • -
  • Add pop to graphics. [Simon Knight]

    -
  • -
  • Smoother updates and transitions. [Simon Knight]

    -
  • -
  • Tidy debug. [Simon Knight]

    -
  • -
  • Visual tweaks. [Simon Knight]

    -
  • -
  • Handle if no version number. [Simon Knight]

    -
  • -
  • Problems with pika sending anm. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Better parsing and rmq messages on starting and launched. [Simon -Knight]

    -
  • -
  • Nicer trace colours. [Simon Knight]

    -
  • -
  • Turn off compile by default. [Simon Knight]

    -
  • -
  • Handle paths. [Simon Knight]

    -
  • -
  • Add path for traceroutes. [Simon Knight]

    -
  • -
  • Fix traceroutes. [Simon Knight]

    -
  • -
  • Placeholder for webserver. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Change status font, don't list id in attributes. [Simon Knight]

    -
  • -
  • Remove need for full ank install (better for remote servers) [Simon -Knight]

    -
  • -
  • Tidy debug, better info messages. [Simon Knight]

    -
  • -
  • Add more icons. [Simon Knight]

    -
  • -
  • Websocket live updates working. [Simon Knight]

    -
  • -
  • Add rmq support. [Simon Knight]

    -
  • -
  • Debugging. [Simon Knight]

    -
  • -
  • Extra note. [Simon Knight]

    -
  • -
  • Moving to entirely websocket, no polling. [Simon Knight]

    -
  • -
  • Removing outdated webserver. [Simon Knight]

    -
  • -
  • Fix bug in monitoring. [Simon Knight]

    -
  • -
  • Better hull updates, status labels, general tidy, [Simon Knight]

    -
  • -
  • Better handling of no template attribute set. [Simon Knight]

    -
  • -
  • Fix grouping hulls. [Simon Knight]

    -
  • -
  • Update monitor mode. [Simon Knight]

    -
  • -
  • Better log output. [Simon Knight]

    -
  • -
  • Exception handling. [Simon Knight]

    -
  • -
  • Dynamic websocket url. [Simon Knight]

    -
  • -
  • Update label, select node from available nodes rather than hard-coded. -[Simon Knight]

    -
  • -
  • Basic process error handling. [Simon Knight]

    -
  • -
  • Add support for http. [Simon Knight]

    -
  • -
  • Support tornado based web. [Simon Knight]

    -
  • -
  • Fix label naming, [Simon Knight]

    -
  • -
  • Moving webapp to single script, entirely in tornado. [Simon Knight]

    -
  • -
  • Don't run graph products if no template set. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Graph products working. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • More graph products. [Simon Knight]

    -
  • -
  • More graph products. [Simon Knight]

    -
  • -
  • Plotting edges from graph products. [Simon Knight]

    -
  • -
  • Bug with hull in d3. [Simon Knight]

    -
  • -
  • Initial commit of graph products. [Simon Knight]

    -
  • -
  • Don't try overlays on first call. [Simon Knight]

    -
  • -
  • Adding graph product support. [Simon Knight]

    -
  • -
  • Bugfix. [Simon Knight]

    -
  • -
  • Fn to replace graph. [Simon Knight]

    -
  • -
  • Move icons. [Simon Knight]

    -
  • -
  • Tidying. [Simon Knight]

    -
  • -
  • Tidy icons. [Simon Knight]

    -
  • -
  • Tidying viz. [Simon Knight]

    -
  • -
  • More trace route parsing. [Simon Knight]

    -
  • -
  • Better trace route vis. [Simon Knight]

    -
  • -
  • Neater save/restore. [Simon Knight]

    -
  • -
  • Turn down debug. [Simon Knight]

    -
  • -
  • Nidb save/restore. [Simon Knight]

    -
  • -
  • Visualize ip allocs. [Simon Knight]

    -
  • -
  • Tidy. [Simon Knight]

    -
  • -
  • Neater pickle. [Simon Knight]

    -
  • -
  • Path plotting in d3. [Simon Knight]

    -
  • -
  • More d3. [Simon Knight]

    -
  • -
  • More rabbitmq/json/websockets/d3. [Simon Knight]

    -
  • -
  • Bgp working, adding rmq sending of trace routes to d3. [Simon Knight]

    -
  • -
  • List machines in lab - don't just boot all folders. [Simon Knight]

    -
  • -
  • Log booting machines. [Simon Knight]

    -
  • -
  • Connect to interface not loopback ip for ebgp. [Simon Knight]

    -
  • -
  • Options for compile, deploy, measure. [Simon Knight]

    -
  • -
  • Add file logging. [Simon Knight]

    -
  • -
  • Fix issue where multiple threads create folder at same time. [Simon -Knight]

    -
  • -
  • Use ethernet address for next-hop as workaround to DENIED due to: non- -connected next-hop; [Simon Knight]

    -
  • -
  • Ebgp DENIED due to: non-connected next-hop. [Simon Knight]

    -
  • -
  • Add static loopback routes for bgp. [Simon Knight]

    -
  • -
  • Fix indent. [Simon Knight]

    -
  • -
  • Fix warning. [Simon Knight]

    -
  • -
  • Fix bgp network advertisement. [Simon Knight]

    -
  • -
  • Fixing ebgp for traceroutes. [Simon Knight]

    -
  • -
  • Adding extra measurement functions. [Simon Knight]

    -
  • -
  • End-to-end deployment using exscript, measurement using exscript/rmq, -and parsing using textfsm. [Simon Knight]

    -
  • -
  • More rmq measurement. [Simon Knight]

    -
  • -
  • Adding rmq remote measurement. [Simon Knight]

    -
  • -
  • More textfsm. [Simon Knight]

    -
  • -
  • With initial textfsm processing of sh ip route. [Simon Knight]

    -
  • -
  • Trying to capture routing output using exscript templates. [Simon -Knight]

    -
  • -
  • Collecting data from hosts. [Simon Knight]

    -
  • -
  • Fixing bgp config. [Simon Knight]

    -
  • -
  • Fixing netkit routing. [Simon Knight]

    -
  • -
  • Use remote interface ip for eBGP not loopback. [Simon Knight]

    -
  • -
  • Fix issue with single dst node being treated as string for set. [Simon -Knight]

    -
  • -
  • More netkit deploy improvements. [Simon Knight]

    -
  • -
  • Fixing netkit deployment. [Simon Knight]

    -
  • -
  • Remove previous lab dirs. [Simon Knight]

    -
  • -
  • Testing deployment. [Simon Knight]

    -
  • -
  • Testing. [Simon Knight]

    -
  • -
  • Better deployment. [Simon Knight]

    -
  • -
  • Don't save ip allocs -> speed. [Simon Knight]

    -
  • -
  • Fix up naming. [Simon Knight]

    -
  • -
  • Recommit. [Simon Knight]

    -
  • -
  • Update ignore. [Simon Knight]

    -
  • -
  • Better formatting. [Simon Knight]

    -
  • -
  • Support host keys. [Simon Knight]

    -
  • -
  • Use nklab not netkit as folder, support for host keys. [Simon Knight]

    -
  • -
  • Add nonzero for nodes, fix subtle issue with evaluation nonzero for -None values in nidb_node_category. [Simon Knight]

    -
  • -
  • Update template names. [Simon Knight]

    -
  • -
  • Pass Network name to nidb. [Simon Knight]

    -
  • -
  • Sort rendering. [Simon Knight]

    -
  • -
  • Default name handling. [Simon Knight]

    -
  • -
  • Ignore graphmls. [Simon Knight]

    -
  • -
  • Tidying, add ssh. [Simon Knight]

    -
  • -
  • Copy Network for zoo graphs. [Simon Knight]

    -
  • -
  • New function to copy attributes from one overlay to another. [Simon -Knight]

    -
  • -
  • Remove debug. [Simon Knight]

    -
  • -
  • Different folder naming structure. [Simon Knight]

    -
  • -
  • Dump graph attributes. [Simon Knight]

    -
  • -
  • New naming functions. [Simon Knight]

    -
  • -
  • Fixes. [Simon Knight]

    -
  • -
  • Writing collision domains. [Simon Knight]

    -
  • -
  • Update ignore. [Simon Knight]

    -
  • -
  • Remove built docs. [Simon Knight]

    -
  • -
-
-
- - diff --git a/changelog.rst b/changelog.rst deleted file mode 100644 index d571927f..00000000 --- a/changelog.rst +++ /dev/null @@ -1,4574 +0,0 @@ -Changelog -========= - -Testing release ---------------- - -- Merge pull request #242 from sk2/ACLs. [Simon Knight] - - Ac ls - -- Add tox. [Simon Knight] - -- Merge pull request #241 from sk2/ACLs. [Simon Knight] - - Ac ls - -v0.9.2 (2014-06-27) -------------------- - -Changes -~~~~~~~ - -- >= rather than == for deps. [Simon Knight] - -- Update info on ip blocks. [Simon Knight] - -v0.9.1 (2014-05-13) -------------------- - -New -~~~ - -- User: record overlay dependencies to new overlay. [Simon Knight] - -Changes -~~~~~~~ - -- Minor tweaks. [Simon Knight] - -- Condense example. [Simon Knight] - -- Tidy. [Simon Knight] - -- Tidy. [Simon Knight] - -- Update docs for json example. [Simon Knight] - -- Add Json to doc for gh-222. [Simon Knight] - -- User: Initial commit of JSON load code for gh-222. [Simon Knight] - - Example in tests/house.json if using dev build of ank: ``` $ - autonetkit -f tests/house.json INFO VIRL Configuration Engine 0.9.0 - INFO AutoNetkit 0.9.0 INFO IPv4 allocations: Infrastructure: - 10.0.0.0/8, Loopback: 192.168.0.0/22 INFO Allocating v4 Infrastructure - IPs INFO Allocating v4 Primary Host loopback IPs INFO Topology not - created for VIRL, skipping Cisco PCE design rules INFO Skipping iBGP - for iBGP disabled nodes: [] INFO All validation tests passed. INFO - Rendering Configuration Files INFO Finished ``` if run in monitor - mode, then changes will be reflected live: ``` $ ank_webserver ``` - and then ``` $ autonetkit -f tests/house.json —monitor ``` - -- Updates. [Simon Knight] - -- Rebuild doc. [Simon Knight] - -- Increment year. [Simon Knight] - -- Run sphinx-apidoc. [Simon Knight] - -- Adding doctests. [Simon Knight] - -- User: update_http -> update_vis. [Simon Knight] - -- User: Initial support for multi-homed servers for VIRL-425. [Simon - Knight] - -- Add initial support to auto-build dependency diagrams. [Simon Knight] - -Other -~~~~~ - -- Merge pull request #237 from oliviertilmans/patch-1. [Simon Knight] - - The network might not run BGP - -- The network might not run BGP. [oliviertilmans] - - This result in a Key Error - -- Merge pull request #236 from sk2/parallel. [Simon Knight] - - Parallel - -- Merge pull request #235 from sk2/ACLs. [Simon Knight] - - Ac ls - -- Revert "chg: dev: add ACL placeholder for gh-234" [Simon Knight] - - This reverts commit 4941b893daf279e5bb24a0b54c10ed490db27716. - -- Merge pull request #232 from sk2/render2.0. [Simon Knight] - - Render2.0 - -- Add: dev: initial work to support vlans for gh-229. [Simon Knight] - -- Update README.md. [Simon Knight] - -- Merge pull request #228 from sk2/parallel. [Simon Knight] - - chg: doc: minor tweaks - -- Merge pull request #227 from sk2/parallel. [Simon Knight] - - Parallel - -- Update README.md. [Simon Knight] - -- Update README.md. [Simon Knight] - -- Merge pull request #226 from sk2/parallel. [Simon Knight] - - Chg: doc: add Json to doc for gh-222 - -- Merge pull request #225 from sk2/parallel. [Simon Knight] - - Chg: User: Initial commit of JSON load code for gh-222 - -- Merge pull request #224 from sk2/parallel. [Simon Knight] - - Parallel - -- Merge pull request #223 from sk2/parallel. [Simon Knight] - - Parallel - -- Merge pull request #221 from sk2/parallel. [Simon Knight] - - Parallel - -v0.9.0 (2014-03-24) -------------------- - -New -~~~ - -- User: Support layer2 and layer3 overlays for gh-206. [Simon Knight] - -Changes -~~~~~~~ - -- User: Sort nodes before allocation, closes gh-208. [Simon Knight] - -- User: label-based sort if present closes gh-207. [Simon Knight] - -- User: add ability to search for interfaces by their "id" string e.g. - "GigabitEthernet0/1" [Simon Knight] - -v0.8.39 (2014-03-14) --------------------- - -Changes -~~~~~~~ - -- User: Allow custom config injection for VIRL-122. [Simon Knight] - -v0.8.38 (2014-03-13) --------------------- - -Changes -~~~~~~~ - -- User: Handle IP addresses on L3 devices for VIRL-372. [Simon Knight] - -- User: support custom unsupported server templates for VIRL-378. [Simon - Knight] - -- User: add "external_connector" device_type for VIRL-672. [Simon - Knight] - -- User: only validate IP on Layer 3 devices for VIRL-672. [Simon Knight] - -- Allow specifying edge properties (color/width) [Simon Knight] - -v0.8.37 (2014-03-11) --------------------- - -Fix -~~~ - -- User: Corrects bug with non-routers in EIGRP fixes VIRL-659. [Simon - Knight] - - Had extra ISIS NET address line - -v0.8.35 (2014-03-10) --------------------- - -Changes -~~~~~~~ - -- Update warnings to not warn if no subnet/prefix set. [Simon Knight] - -v0.8.33 (2014-03-05) --------------------- - -Changes -~~~~~~~ - -- User: Use ASN as process-id for IGP for VIRL-602. [Simon Knight] - - Causes problems if multiple ASes connected across a multi-point - network. - -Other -~~~~~ - -- Add overview video. [Simon Knight] - -v0.8.32 (2014-02-14) --------------------- - -New -~~~ - -- User: Allow manually specified IPv6 blocks for VIRL-481. [Simon - Knight] - -Changes -~~~~~~~ - -- User: Config driven IP defaults, better logging of problems with - manually specified IPs. [Simon Knight] - -- User: Swap default IPv6 blocks for infra/loopback to be sequential. - [Simon Knight] - -v0.8.31 (2014-02-14) --------------------- - -Changes -~~~~~~~ - -- User: Tidied up VRF role notification logic to aggregate by role. - VIRL-368. [Simon Knight] - -- User: Exclude BGP block if no iBGP/eBGP sessions. VIRL-564. [Simon - Knight] - -v0.8.29 (2014-02-14) --------------------- - -New -~~~ - -- User: Warn that IPv6 MPLS VPNs not currently supported for VIRL-56. - [Simon Knight] - -Changes -~~~~~~~ - -- User: update iBGP design rules for VIRL-558. [Simon Knight] - -Fix -~~~ - -- User: Allow PE RRC nodes to participate in ibgp_vpn_v4. [Simon Knight] - -v0.8.26 (2014-02-13) --------------------- - -Changes -~~~~~~~ - -- User: Add ibgp "peer" type for VIRL-558. [Simon Knight] - -- User: Clarify IPv4 allocation warning message for VIRL-550. [Simon - Knight] - -- User: list Interfaces as GigabitEthernet0/1.RR_2 instead of - (GigabitEthernet0/1, RR_2) [Simon Knight] - -v0.8.17 (2014-01-31) --------------------- - -New -~~~ - -- User: Allow user-defined IPv6 IPs (infra + loopback) [Simon Knight] - -Changes -~~~~~~~ - -- User: More descriptive logs for user-defined IPv6 addresses. [Simon - Knight] - -Fix -~~~ - -- User: Bugfix for EIGRP IPv6 for VIRL-493. [Simon Knight] - -v0.8.14 (2014-01-24) --------------------- - -New -~~~ - -- User: Warn if partial IPs set for VIRL-456. [Simon Knight] - -- User: Display human-readable ibgp_role for VIRL-469. [Simon Knight] - -v0.8.12 (2014-01-22) --------------------- - -Changes -~~~~~~~ - -- User: Update logging. [Simon Knight] - -v0.8.11 (2014-01-22) --------------------- - -Changes -~~~~~~~ - -- User: Tidy logging. [Simon Knight] - -- User: Warn for unsupported device features. [Simon Knight] - -- User: Use VIRL platform identifier instead of Cisco. [Simon Knight] - -- User: Tidy logging messages. [Simon Knight] - -v0.8.10 (2014-01-21) --------------------- - -Fix -~~~ - -- Sort values neigh_most_frequent so tie-break chooses lowest. [Simon - Knight] - - Addresses issue with stability in IP addressing: inter-asn links had a - collision domain that was arbitrarily being allocated to one or the - other ASN depending on the arbitarty position. This ensures the lowest - is always returned in a tie-break leading to repeatable addressing - (especially important for automated tests) - -v0.8.9 (2014-01-20) -------------------- - -- Merge branch 'master' of github.com:sk2/autonetkit. [Simon Knight] - - Conflicts: .bumpversion.cfg autonetkit/render.py - setup.py - -- Merge cleanup. [Simon Knight] - -v0.8.7 (2014-01-20) -------------------- - -- @chg: dev: renaming. [Simon Knight] - -- @chg: dev: renaming. [Simon Knight] - -v0.8.6 (2014-01-20) -------------------- - -New -~~~ - -- User: Display address blocks to use for VIRL-350. [Simon Knight] - -- User: Display address blocks to use for VIRL-350. [Simon Knight] - -v0.8.4 (2014-01-16) -------------------- - -New -~~~ - -- User: add per-element logging for GH-190. [Simon Knight] - -- User: add per-element logging for GH-190. [Simon Knight] - -- User: add single config for gh-189. [Simon Knight] - -- User: add single config for gh-189. [Simon Knight] - -- User: Screenshot capture for GH-188. [Simon Knight] - -- User: Screenshot capture for GH-188. [Simon Knight] - -Other -~~~~~ - -- Change: dev: remove superseded config. [Simon Knight] - -- Change: dev: remove superseded config. [Simon Knight] - -- Change: dev: refactor XR OSPF by interfaces to common router_base. - [Simon Knight] - -- Change: dev: refactor XR OSPF by interfaces to common router_base. - [Simon Knight] - -- Update changelog. [Simon Knight] - -- Update changelog. [Simon Knight] - -v0.8.3 (2014-01-13) -------------------- - -- Add gitchangelog support. [sk2] - -- Add gitchangelog support. [sk2] - -- Move indent correctly inside loop. [sk2] - -- Move indent correctly inside loop. [sk2] - -- Typo fix for ebgp not ibgp. [sk2] - -- Typo fix for ebgp not ibgp. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Add read me. [sk2] - -- Add read me. [sk2] - -- Update. [sk2] - -- Update. [sk2] - -- Add img. [sk2] - -- Add img. [sk2] - -- Examples. [sk2] - -- Examples. [sk2] - -- Add cwd to templates search dir. [sk2] - -- Add cwd to templates search dir. [sk2] - -- Add ordering. [sk2] - -- Add ordering. [sk2] - -- Add note. [sk2] - -- Add note. [sk2] - -- Support v6 bgp. [sk2] - -- Support v6 bgp. [sk2] - -- Add note. [sk2] - -- Add note. [sk2] - -- Add __ne__ and ordering. [sk2] - -- Add __ne__ and ordering. [sk2] - -- Support nidb nodes. [sk2] - -- Support nidb nodes. [sk2] - -- Add example. [sk2] - -- Add example. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Handle new overlays. [sk2] - -- Handle new overlays. [sk2] - -- New overlay. [sk2] - -- New overlay. [sk2] - -- Better json. [sk2] - -- Better json. [sk2] - -- Tutorial notebook. [sk2] - -- Tutorial notebook. [sk2] - -- Add new test case notes. [sk2] - -- Add new test case notes. [sk2] - -- Better debugging for templates. [sk2] - -- Better debugging for templates. [sk2] - -- More support for gh-186. [sk2] - -- More support for gh-186. [sk2] - -- Move mpls code to separate module. [sk2] - -- Move mpls code to separate module. [sk2] - -- More support for gh-186. [sk2] - -- More support for gh-186. [sk2] - -- Pep8. [sk2] - -- Pep8. [sk2] - -- Improvements to setting defaults on bunches. [sk2] - -- Improvements to setting defaults on bunches. [sk2] - -- Remove old demos. [sk2] - -- Remove old demos. [sk2] - -- Add config_stanza class for gh-186. [sk2] - -- Add config_stanza class for gh-186. [sk2] - -- Add option for stack_trace (useful for dev) [sk2] - -- Add option for stack_trace (useful for dev) [sk2] - -- Add browser test. [sk2] - -- Add browser test. [sk2] - -- Connectors. [sk2] - -- Connectors. [sk2] - -v0.8.2 (2014-01-10) -------------------- - -- Remove old call to publish data. [sk2] - -- Remove old call to publish data. [sk2] - -v0.8.1 (2014-01-10) -------------------- - -- Add note. [sk2] - -- Add note. [sk2] - -- Remove unused messaging functions. [sk2] - -- Remove unused messaging functions. [sk2] - -- Add log message. [sk2] - -- Add log message. [sk2] - -- Update anm tests. [sk2] - -- Update anm tests. [sk2] - -- Testing for anm. [sk2] - -- Testing for anm. [sk2] - -- Remove directed. [sk2] - -- Remove directed. [sk2] - -- Improving edge-case handling. [sk2] - -- Improving edge-case handling. [sk2] - -- Remove state for pickling - use json. [sk2] - -- Remove state for pickling - use json. [sk2] - -- Improving handling for custom overlays. [sk2] - -- Improving handling for custom overlays. [sk2] - -- Remove unused code. [sk2] - -- Remove unused code. [sk2] - -v0.8.0 (2014-01-08) -------------------- - -- Remove old ibgp code. [sk2] - -- Remove old ibgp code. [sk2] - -- More removal of edge_id for gh-184. [sk2] - -- More removal of edge_id for gh-184. [sk2] - -- Closes gh-184. [sk2] - -- Closes gh-184. [sk2] - -- Pylint. [sk2] - -- Pylint. [sk2] - -v0.7.33 (2014-01-08) --------------------- - -- Update warnings. [sk2] - -- Update warnings. [sk2] - -v0.7.32 (2014-01-08) --------------------- - -- Add other AS subnets. [sk2] - -- Add other AS subnets. [sk2] - -v0.7.31 (2014-01-07) --------------------- - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Handle non-subnet collision domains (i.e. no nodes) [sk2] - -- Handle non-subnet collision domains (i.e. no nodes) [sk2] - -- Rename subtype. [sk2] - -- Rename subtype. [sk2] - -- Pass exception up. [sk2] - -- Pass exception up. [sk2] - -v0.7.29 (2014-01-06) --------------------- - -- Use official subtypes. [sk2] - -- Use official subtypes. [sk2] - -v0.7.27 (2014-01-04) --------------------- - -- Pep8. [sk2] - -- Pep8. [sk2] - -v0.7.26 (2014-01-03) --------------------- - -- Add logging. [sk2] - -- Add logging. [sk2] - -v0.7.25 (2014-01-03) --------------------- - -- Fix issue with uuids being re-used but discarded, update logging. - [sk2] - -- Fix issue with uuids being re-used but discarded, update logging. - [sk2] - -v0.7.24 (2014-01-02) --------------------- - -- Catching overlay uuid deletion errors. [sk2] - -- Catching overlay uuid deletion errors. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Handling of interfaces in adding edges. [sk2] - -- Handling of interfaces in adding edges. [sk2] - -- Allow uuid to be specified in call. [sk2] - -- Allow uuid to be specified in call. [sk2] - -- Update connectors. [sk2] - -- Update connectors. [sk2] - -- Add comment. [sk2] - -- Add comment. [sk2] - -- Add script to build wheel. [sk2] - -- Add script to build wheel. [sk2] - -- Isort imports. [sk2] - -- Isort imports. [sk2] - -v0.7.23 (2013-12-27) --------------------- - -- Enable telnet and ssh over vty. [sk2] - -- Enable telnet and ssh over vty. [sk2] - -- Tidying. [sk2] - -- Tidying. [sk2] - -v0.7.20 (2013-12-23) --------------------- - -- Correct bug in writing static instead of host routes. [sk2] - -- Correct bug in writing static instead of host routes. [sk2] - -v0.7.19 (2013-12-23) --------------------- - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.7.17 (2013-12-23) --------------------- - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidy logging. [sk2] - -- Tidy logging. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Add test. [sk2] - -- Add test. [sk2] - -- Add tests. [sk2] - -- Add tests. [sk2] - -- Add test. [sk2] - -- Add test. [sk2] - -- Add test. [sk2] - -- Add test. [sk2] - -- Add test. [sk2] - -- Add test. [sk2] - -- Add test topology. [sk2] - -- Add test topology. [sk2] - -v0.7.16 (2013-12-20) --------------------- - -- Add extra onepk line. [sk2] - -- Add extra onepk line. [sk2] - -v0.7.15 (2013-12-20) --------------------- - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.7.14 (2013-12-20) --------------------- - -- Return the node label rendered rather than node_id for repr of - interfaces. [sk2] - -- Return the node label rendered rather than node_id for repr of - interfaces. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.7.13 (2013-12-19) --------------------- - -- If exception, send the visualisation that was constructed to help - debug. [sk2] - -- If exception, send the visualisation that was constructed to help - debug. [sk2] - -- Return nonzero if error. [sk2] - -- Return nonzero if error. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Add top-level exception capturing. [sk2] - -- Add top-level exception capturing. [sk2] - -v0.7.12 (2013-12-19) --------------------- - -- Revert out. [sk2] - -- Revert out. [sk2] - -- Onepk syntax change. [sk2] - -- Onepk syntax change. [sk2] - -- Remove todo. [sk2] - -- Remove todo. [sk2] - -- More descriptive error message for mismatched subnets. [sk2] - -- More descriptive error message for mismatched subnets. [sk2] - -v0.7.10 (2013-12-18) --------------------- - -- Copy label across to ipv4 and v6 graphs for display. [sk2] - -- Copy label across to ipv4 and v6 graphs for display. [sk2] - -v0.7.9 (2013-12-18) -------------------- - -- Add yaml helpers for multiline strings. [sk2] - -- Add yaml helpers for multiline strings. [sk2] - -- Default handler. [sk2] - -- Default handler. [sk2] - -- Add validate catch. [sk2] - -- Add validate catch. [sk2] - -- Handle no routing. [sk2] - -- Handle no routing. [sk2] - -v0.7.8 (2013-12-12) -------------------- - -- Closes gh-183. [sk2] - -- Closes gh-183. [sk2] - -- Use new vars, tidy. [sk2] - -- Use new vars, tidy. [sk2] - -- Info -> debug. [sk2] - -- Info -> debug. [sk2] - -- Lists instead of generators. [sk2] - -- Lists instead of generators. [sk2] - -- Ignores. [sk2] - -- Ignores. [sk2] - -- Merge pull request #182 from iainwp/master. [Simon Knight] - - modification to accept a configuration file from an environment - variable - -- Merge pull request #182 from iainwp/master. [Simon Knight] - - modification to accept a configuration file from an environment - variable - -- Comment out custom code. [sk2] - -- Comment out custom code. [sk2] - -- Revert labels. [sk2] - -- Revert labels. [sk2] - -- 254 on static route. [sk2] - -- 254 on static route. [sk2] - -- Modification to accept a configuration file from an environment - variable. [iainwp] - -- Modification to accept a configuration file from an environment - variable. [iainwp] - -- Merge pull request #125 from oliviertilmans/loopback_ids. [Simon - Knight] - - Loopback interface needs to have an associated id with them - -- Merge pull request #125 from oliviertilmans/loopback_ids. [Simon - Knight] - - Loopback interface needs to have an associated id with them - -v0.7.4 (2013-12-02) -------------------- - -- Tidy overlay names. [sk2] - -- Tidy overlay names. [sk2] - -- Handle vis corner case if just input. [sk2] - -- Handle vis corner case if just input. [sk2] - -v0.7.3 (2013-11-29) -------------------- - -- Correct version that bumpversion clobbered. [sk2] - -- Correct version that bumpversion clobbered. [sk2] - -- Add helper function to return neighbors of an interface. [sk2] - -- Add helper function to return neighbors of an interface. [sk2] - -- Add is_bound property for nidb interfaces for parity with anm. [sk2] - -- Add is_bound property for nidb interfaces for parity with anm. [sk2] - -- Set mgmt interface name correctly. [sk2] - -- Set mgmt interface name correctly. [sk2] - -- Remove extra http postings. [sk2] - -- Remove extra http postings. [sk2] - -- Add helper function to return neighbors of an interface. [sk2] - -- Add helper function to return neighbors of an interface. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.7.2 (2013-11-27) -------------------- - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidying version string. [sk2] - -- Tidying version string. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Fix extra syntax. [sk2] - -- Fix extra syntax. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.7.1 (2013-11-26) -------------------- - -- More work for cloud-init support. [sk2] - -- More work for cloud-init support. [sk2] - -- Ignore .yaml. [sk2] - -- Ignore .yaml. [sk2] - -- Improving render for cloud init output. [sk2] - -- Improving render for cloud init output. [sk2] - -- Cloud init. [sk2] - -- Cloud init. [sk2] - -- Move out vis. [sk2] - -- Move out vis. [sk2] - -- Top-level behaviour. [sk2] - -- Top-level behaviour. [sk2] - -- Change top level peering behaviour. [sk2] - -- Change top level peering behaviour. [sk2] - -- Lock deps. [sk2] - -- Lock deps. [sk2] - -- Tidying. [sk2] - -- Tidying. [sk2] - -v0.6.8 (2013-11-19) -------------------- - -- First iteration of simplified RR/HRR iBGP. [sk2] - -- First iteration of simplified RR/HRR iBGP. [sk2] - -- Refactor out ibgp. [sk2] - -- Refactor out ibgp. [sk2] - -- Remove extra node. [sk2] - -- Remove extra node. [sk2] - -- Path colours. [sk2] - -- Path colours. [sk2] - -- Handle base topo. [sk2] - -- Handle base topo. [sk2] - -- Update colours. [sk2] - -- Update colours. [sk2] - -- Error handling. [sk2] - -- Error handling. [sk2] - -- Logging. [sk2] - -- Logging. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Basic ibgp check. [sk2] - -- Basic ibgp check. [sk2] - -v0.6.7 (2013-10-30) -------------------- - -- Fix looping issue not assigning server ips. [sk2] - -- Fix looping issue not assigning server ips. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.6.6 (2013-10-29) -------------------- - -- Write IPs onto all server interfaces. [sk2] - -- Write IPs onto all server interfaces. [sk2] - -v0.6.5 (2013-10-28) -------------------- - -- Redo bgp peering for ios. [sk2] - -- Redo bgp peering for ios. [sk2] - -- Revisiting bgp peering. [sk2] - -- Revisiting bgp peering. [sk2] - -- Adding nailed up routes for eBGP. [sk2] - -- Adding nailed up routes for eBGP. [sk2] - -v0.6.4 (2013-10-22) -------------------- - -- Use -host for /32. [sk2] - -- Use -host for /32. [sk2] - -v0.6.3 (2013-10-22) -------------------- - -- Tidying. [sk2] - -- Tidying. [sk2] - -- Add config-driven webserver port. [sk2] - -- Add config-driven webserver port. [sk2] - -v0.6.2 (2013-10-21) -------------------- - -- Fix server issue. [sk2] - -- Fix server issue. [sk2] - -v0.6.1 (2013-10-18) -------------------- - -- Toggle routing config. [sk2] - -- Toggle routing config. [sk2] - -v0.6.0 (2013-10-18) -------------------- - -- Add mpls oam. [sk2] - -- Add mpls oam. [sk2] - -- Call mpls oam module. [sk2] - -- Call mpls oam module. [sk2] - -- Add mpls oam. [sk2] - -- Add mpls oam. [sk2] - -- Don't auto-correct explicitly set ASNs. [sk2] - -- Don't auto-correct explicitly set ASNs. [sk2] - -- Fix typo in comment. [sk2] - -- Fix typo in comment. [sk2] - -- Exclude multipoint edges from mpls te and rsvp. [sk2] - -- Exclude multipoint edges from mpls te and rsvp. [sk2] - -- Mark multipoint edges. [sk2] - -- Mark multipoint edges. [sk2] - -- Fallback to category20b colours if > 10 groups. [sk2] - -- Fallback to category20b colours if > 10 groups. [sk2] - -- Restore cef for ios. [sk2] - -- Restore cef for ios. [sk2] - -- Update doc. [sk2] - -- Update doc. [sk2] - -- Interface handling if specified name for servers. [sk2] - -- Interface handling if specified name for servers. [sk2] - -- Add lo routes. [sk2] - -- Add lo routes. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Remove debug, tidy. [sk2] - -- Remove debug, tidy. [sk2] - -- Include linux in manifest. [sk2] - -- Include linux in manifest. [sk2] - -- Add linux static routes. [sk2] - -- Add linux static routes. [sk2] - -- Add mpls te rules. [sk2] - -- Add mpls te rules. [sk2] - -- Ubuntu server class for static routes. [sk2] - -- Ubuntu server class for static routes. [sk2] - -- Server base class. [sk2] - -- Server base class. [sk2] - -- Base device class. [sk2] - -- Base device class. [sk2] - -- Tidying, add mpls to ios. [sk2] - -- Tidying, add mpls to ios. [sk2] - -- Fix ebgp session created on switch that has both ebgp and ibgp - sessions. [sk2] - -- Fix ebgp session created on switch that has both ebgp and ibgp - sessions. [sk2] - -- Adding route config rendering. [sk2] - -- Adding route config rendering. [sk2] - -- Tidying oo. [sk2] - -- Tidying oo. [sk2] - -- Add mpls code. [sk2] - -- Add mpls code. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Add fn to check server asn. [sk2] - -- Add fn to check server asn. [sk2] - -- Add mpls callout. [sk2] - -- Add mpls callout. [sk2] - -- Update asn setting. [sk2] - -- Update asn setting. [sk2] - -- Update asn handling: copy from phy if present. [sk2] - -- Update asn handling: copy from phy if present. [sk2] - -v0.5.21 (2013-09-06) --------------------- - -- Set ipv4 and routing enabled defaults. [sk2] - -- Set ipv4 and routing enabled defaults. [sk2] - -- Post-collect processing. [sk2] - -- Post-collect processing. [sk2] - -- Remove uuid from test. [sk2] - -- Remove uuid from test. [sk2] - -- Reverse map for single ip. [sk2] - -- Reverse map for single ip. [sk2] - -- Multi-user uuid support. [sk2] - -- Multi-user uuid support. [sk2] - -v0.5.20 (2013-08-29) --------------------- - -- Tidying, adding in vrfs. [sk2] - -- Tidying, adding in vrfs. [sk2] - -v0.5.19 (2013-08-28) --------------------- - -- More collect. [sk2] - -- More collect. [sk2] - -v0.5.18 (2013-08-27) --------------------- - -- Rename collect server. [sk2] - -- Rename collect server. [sk2] - -- Z ordering. [sk2] - -- Z ordering. [sk2] - -- Node data mapping. [sk2] - -- Node data mapping. [sk2] - -- Inc default threads to 5. [sk2] - -- Inc default threads to 5. [sk2] - -- Remove interfaces from node data dump. [sk2] - -- Remove interfaces from node data dump. [sk2] - -- Reverse mapping ips. [sk2] - -- Reverse mapping ips. [sk2] - -- Pep8. [sk2] - -- Pep8. [sk2] - -v0.5.17 (2013-08-23) --------------------- - -- Allow no ip allocs. [sk2] - -- Allow no ip allocs. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Allow no ip allocs. [sk2] - -- Allow no ip allocs. [sk2] - -- Split out functions from build_network. [sk2] - -- Split out functions from build_network. [sk2] - -- Allow no ip allocs. [sk2] - -- Allow no ip allocs. [sk2] - -- Allow no ip allocs. [sk2] - -- Allow no ip allocs. [sk2] - -v0.5.16 (2013-08-22) --------------------- - -- Move endif to end of bgp block to enable bgp to be disabled. [sk2] - -- Move endif to end of bgp block to enable bgp to be disabled. [sk2] - -- Add todo. [sk2] - -- Add todo. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Option to disable routing protocols. [sk2] - -- Option to disable routing protocols. [sk2] - -- Nonzero function. [sk2] - -- Nonzero function. [sk2] - -- Include eigrp overlay. [sk2] - -- Include eigrp overlay. [sk2] - -v0.5.14 (2013-08-22) --------------------- - -- Remvoe debug. [sk2] - -- Remvoe debug. [sk2] - -v0.5.13 (2013-08-22) --------------------- - -- Only require specified ip for bound interfaces. [sk2] - -- Only require specified ip for bound interfaces. [sk2] - -v0.5.12 (2013-08-21) --------------------- - -- Updates. [sk2] - -- Updates. [sk2] - -- Add lo to eigrp v6. [sk2] - -- Add lo to eigrp v6. [sk2] - -- Try seperate packages if possible. [sk2] - -- Try seperate packages if possible. [sk2] - -- Add eigrp. [sk2] - -- Add eigrp. [sk2] - -v0.5.9 (2013-08-16) -------------------- - -- Remove debug. [sk2] - -- Remove debug. [sk2] - -v0.5.8 (2013-08-16) -------------------- - -- Update. [sk2] - -- Update. [sk2] - -v0.5.7 (2013-08-13) -------------------- - -- Misc bugfixes. [sk2] - -- Latest. [sk2] - -- Tidy. [sk2] - -- Measure updates. [sk2] - -- More collection. [sk2] - -- Measurement -> collection. [sk2] - -- More measure. [sk2] - -- Update docs. [sk2] - -- Remove unused measuremetn. [sk2] - -- Measure. [sk2] - -v0.5.6 (2013-08-02) -------------------- - -- More measure. [sk2] - -v0.5.5 (2013-08-02) -------------------- - -- Add colorbrewer. [sk2] - -- Add colorbrewer. [sk2] - -- Tidying colours. [sk2] - -- Tidying colours. [sk2] - -- Add enable secret. [sk2] - -- Measurement improvements. [sk2] - -- Tidy. [sk2] - -- Reorganise, ultra -> csr1000v, add hash. [sk2] - -- Tidy. [sk2] - -- Add server. [sk2] - -v0.5.4 (2013-08-01) -------------------- - -- Rename icon. [sk2] - -- Ultra -> CSR1000v. [sk2] - -- Change mgmt interface handling. [sk2] - -- More measure. [sk2] - -- Update measure. [sk2] - -- Tidying measurement. [sk2] - -- Update user. [sk2] - -- Regen autodoc. [sk2] - -- Remove old measure code. [sk2] - -- Working traceroute measurement. [sk2] - -- Rebuild docs. [sk2] - -- Change docs theme. [sk2] - -- Doc -> docs. [sk2] - -- Tidy. [sk2] - -- Update ignore. [sk2] - -- Tidying. [sk2] - -v0.5.3 (2013-07-31) -------------------- - -- Tidying setup.py. [sk2] - -- Add new platform. [sk2] - -- Tidying tests. [sk2] - -- Restore. [sk2] - -- Add comment. [sk2] - -- Zmq measurement working (needs deserialization) [sk2] - -- Zmq measure. [sk2] - -- Testing, deployment. [sk2] - -- Pep8, fix ibgp 2 layer issues. [sk2] - -- Pep8. [sk2] - -- Pep8. [sk2] - -- Pep8. [sk2] - -- Diff testing. [sk2] - -- Remove unused code. [sk2] - -- Add bgp pol tests. [sk2] - -- More testing. [sk2] - -- Change lo_interface to a valid linux/netkit name. [Olivier Tilmans] - -- Split single compiler into modular platform and device compilers. - [sk2] - -- Tidying. [sk2] - -- Loosen path tension. [sk2] - -- Add testing to setup.py. [sk2] - -- More cleanup. [sk2] - -- Update tests. [sk2] - -v0.5.2 (2013-07-24) -------------------- - -- Sorting on ipv6 for stability. [sk2] - -v0.5.1 (2013-07-24) -------------------- - -- Sort for stability. [sk2] - -- Natural sorting for bgp sessions. [sk2] - -- Debug. [sk2] - -- Sort for repeatability. [sk2] - -- Merge onepk. [sk2] - -- Allocate interfaces if not allocated on input. closes gh-180. [sk2] - -- Apply correct subnet to interfaces. [sk2] - -- Report node label rather than node id for string representation of - interface. [sk2] - -- Tidy. [sk2] - -- Improvements. [sk2] - -- Remove debug. [sk2] - -- Fix issue with secondary loopbacks. [sk2] - -- Tidy. [sk2] - -- Store label on json. [sk2] - -- More 3d. [sk2] - -- More 3d. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- 3d prototype. [sk2] - -- More 3d dev. [sk2] - -- More 3d dev. [sk2] - -- Three js dev. [sk2] - -- Tidied. [sk2] - -- Ignore dev project. [sk2] - -- New icon. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Dont load ip allocs, labels by default. [sk2] - -- Tidy logic. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Tidy, rename, add servers. [sk2] - -- Tidy. [sk2] - -- Add todo. [sk2] - -- Dev. [sk2] - -v0.5.0 (2013-07-02) -------------------- - -- Tidy. [sk2] - -- Split out ui. [sk2] - -- Isis handling. [sk2] - -- Setting with setattr for interface dict. [sk2] - -- Isis combinations. [sk2] - -- Better handling of ips. [sk2] - -- Notes. [sk2] - -- Split out webui. [sk2] - -- More work to work with interfaces directly. [sk2] - -- Tidying, check if bound interfaces. [sk2] - -- Work on ip addressing if already set. [sk2] - -- Ignore unbound interfaces. [sk2] - -- Tidying. [sk2] - -- Fixing ordering. [sk2] - -- Tidying interface if set externally. [sk2] - -- Clean up interface handling. [sk2] - -- Less cryptic names, tidying. [sk2] - -- Remap icons. [sk2] - -- Add todos, better remote interface desc. [sk2] - -- Copying attributes. [sk2] - -- Better labelling, testing of interfaces. [sk2] - -- Flip. [sk2] - -- Handling of interfaces if allocated in physical. [sk2] - -- Improve tension on paths. [sk2] - -- Dev. [sk2] - -- Able to search for edge by edge, used for cross-layer edge searches. - [sk2] - -- String function ensures string. [sk2] - -- Interface errorr handling. [sk2] - -- Handle numeric node ids. [sk2] - -- More work on paths. [sk2] - -- Dev. [sk2] - -- Compress anm to send over wire. [sk2] - -- Cdp on mgmt eth. [sk2] - -- Add measure support. [sk2] - -- Add path annotations. [sk2] - -- Tidy. [sk2] - -- Fixing interface access. [sk2] - -- Fixing serialising. [sk2] - -- Fix corner-case with building trees. [sk2] - -- Add logging message. [sk2] - -- Merge pull request #179 from sk2/custom-folders. [Simon Knight] - - Custom folders - -- Work on new measure framework. [sk2] - -- Initial work. [sk2] - -- Toggle. [sk2] - -- Remap the interfaces back to nodes, and integers. [sk2] - -- Hash on edges. [sk2] - -v0.4.9 (2013-06-14) -------------------- - -- Fix bug with nx-os. [sk2] - -- Better hashing for cross-layer and cross-anm/nidb interface - comparison. [sk2] - -- Add quiet (non verbose) option. [sk2] - -- Test for presence in vrf graph. [sk2] - -- Only add vrfs if at least one node has been tagged with vrf tag. [sk2] - -- Turn web json stream back to anm/nidb. [sk2] - -v0.4.8 (2013-06-12) -------------------- - -- Ospfv3 on loopback zero. [sk2] - -v0.4.7 (2013-06-12) -------------------- - -- Tidy. [sk2] - -- Add servers to igp. [sk2] - -v0.4.6 (2013-06-11) -------------------- - -- Disable bundled vis. [sk2] - -- Update demo notebook. [sk2] - -- Support for specific packages. [sk2] - -- Update template. [sk2] - -- Ignore ds store. [sk2] - -- Add key filename support. [sk2] - -- Split out args so can call programatically. [sk2] - - arg_string = "-f %s --deploy" % input_file args = - console_script.parse_options(arg_string) console_script.main(args) - -- Mark ipv4/ipv6 per interface, numeric ids. [sk2] - -- Add l3 conn graph, use for vrfs. [sk2] - -- Add dump. [sk2] - -- Update entry point. [sk2] - -- Update ignore. [sk2] - -- Add tests. [sk2] - -- Tidying compiler for interfaces. [sk2] - -- Tidying, add option to force ank vis, add info message if single user - mode activated. [sk2] - -- Update to command line argument parsing. [sk2] - -- Remove testing uuid. [sk2] - -- Remove unused imports. [sk2] - -- More multi-user support. [sk2] - -- Tidy. [sk2] - -- Use shorter uuid - less unique, but more usable. still unlikely to - collide for our purposes. [sk2] - -- Send uuid with highlight. [sk2] - -- Tidy, add support for muti user. [sk2] - -- Multi-user vis support. [sk2] - -- Add todo. [sk2] - -- Dont monitor build_network (won't work if using as module) [sk2] - -- Add uuid support. [sk2] - -- Support uuid. [sk2] - -- Remove messaging call. [sk2] - -- Remove highlight call. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -v0.4.5 (2013-05-29) -------------------- - -- Use green for verified paths. [sk2] - -- Use green for verified paths. [sk2] - -- Use autonetkit_cisco web content if present. [sk2] - -- Exception handling. [sk2] - -- Add logging message. [sk2] - -- Add logging message. [sk2] - -- Fix logging. [sk2] - -- Update demos. [sk2] - -- Markdown extension of md not mmd. [sk2] - -- Readme from .txt -> mmd. [sk2] - -- Retry as markdown. [sk2] - -- Add badge count using. [sk2] - -- Demo updates. [sk2] - -- More work on demo. [sk2] - -- Further improved numeric vm id shutdown. [sk2] - -- New demo notebook. [sk2] - -- Clean paths on redraw. [sk2] - -- Handle numeric vm ids. [sk2] - -v0.4.4 (2013-05-15) -------------------- - -- Dhcp management. [sk2] - -- Add output target. [sk2] - -- Fix global settings. [sk2] - -- Add todo. [sk2] - -- Updates to notebook. [sk2] - -- Restore ui elements. [sk2] - -- Link highlights behind nodes. [sk2] - -- Add onepk stanza. [sk2] - -- Updates. [sk2] - -- Updates. [sk2] - -- Demo notebook. [sk2] - -- Highlight path colour. [sk2] - -- Logging, highlight path colour. [sk2] - -- Bugfix for highlights. [sk2] - -- Bugfix. [sk2] - -- Measure. [sk2] - -- Add function to map edge attr to interfaces. [sk2] - -v0.4.2 (2013-05-13) -------------------- - -- Add code to switch on input extension. [sk2] - -v0.4.1 (2013-05-10) -------------------- - -- Don't put clns mtu on loopbacks. [sk2] - -v0.3.14 (2013-05-10) --------------------- - -- Enable clns mtu 1400 on isis interfaces. [sk2] - -- Enable cdp per interface. [sk2] - -- Enable cdp on all interfaces, rename mgmt interface. [sk2] - -- Add ank_cisco to version. [sk2] - -v0.3.13 (2013-05-10) --------------------- - -- Mpls lite support for ios. [sk2] - -- Only add PE, P to mpls_ldp. [sk2] - -v0.3.12 (2013-05-10) --------------------- - -- Use specified subnet. [sk2] - -- Tidy. [sk2] - -- Return interface on creation. [sk2] - -- Updating ip allocations. [sk2] - -- Refactored ip allocation. [sk2] - -- Add comment. [sk2] - -v0.3.11 (2013-05-09) --------------------- - -- Mgmt + cdp. [sk2] - -- Management toggle. [sk2] - -- Tidying. [sk2] - -- Rename function. [sk2] - -- Support to copy across management info. [sk2] - -- Allow [] notation to set/get overlay data. [sk2] - -- Fix capitalisation. [sk2] - -- Tidy. [sk2] - -- Fix imports. [sk2] - -- Fix import errors. [sk2] - -- Don't over-write infrastructure blocks, closes gh-176. [sk2] - -- Add comment. [sk2] - -- Ensure allocation is imported. [sk2] - -v0.3.10 (2013-05-04) --------------------- - -- Catch value errors. [sk2] - -- Fallback. [sk2] - -v0.3.9 (2013-05-03) -------------------- - -- Tidy management ips. [sk2] - -- Explicitly set mgmt interface label for xr and nx-os. [sk2] - -- Tidy. [sk2] - -- Different ids based on ios derivative. [sk2] - -- Tidy. [sk2] - -- Nx-os interface labels. [sk2] - -- Copy management subnet info if relevant. [sk2] - -- Tidying. [sk2] - -- Remove debug. [sk2] - -- Use "use" with icon defs, rather than redefining each time. [sk2] - - based on - https://groups.google.com/forum/?fromgroups=#!topic/d3-js/EtEwgOYnY6U - better performance avoids the chrome caching issues - -- Merge pull request #124 from oliviertilmans/http_vis. [Simon Knight] - - Fix a small log.info error - -- Fix the following error: [Olivier Tilmans] - - > File "autonetkit/ank_messaging.py", line 107, in publish_data > - log.info("Unable to connect to HTTP Server %s: e" % (http_url, e)) > - TypeError: not all arguments converted during string formatting When - trying to generate cfg's without having the visualisation server - running - -- Treat specified interface labels per node rather than globally. [sk2] - -- Make labels on top of links and nodes. [sk2] - -- Add note. [sk2] - -- Dont spuriously warn on unset. [sk2] - -- Remove debug. [sk2] - -- Fix error with interface names if not allocated, eg on a lan segment. - [sk2] - -- Remove unused code. [sk2] - -- Ignore html coverage output. [sk2] - -- Ignore coverage. [sk2] - -- Rename validate to ank_validate to avoid conflict with configobj and - paths. [sk2] - -- Add IGP overlays even if not used - allows quicker test in compiler. - [sk2] - -- Include cluster attribute for rendering. [sk2] - -- Show grouping for ibgp_v4 and ibgp_v6. [sk2] - -- Resolve merge conflicts. [sk2] - -- Tidy ignore. [sk2] - -- Fix single-node hulls: make slightly bigger so don't get printing - artifacts with gap in middle. [sk2] - -- Merge pull request #116 from oliviertilmans/cleanup. [Simon Knight] - - Minor cleanup & usage of os.path.join - -- Merge pull request #119 from oliviertilmans/device_type_server. [Simon - Knight] - - (Fix Issue #117) Using Any other non router l3device node (i.e. - server) crashes ANK - -- Merge pull request #118 from sdefauw/master. [Simon Knight] - - Bug of boolean fields in graphml solved. - -- Hostname is now independent from zebra. [Olivier Tilmans] - -- Merge branch 'device_type_server' into anycast_dns_resolver. [Olivier - Tilmans] - -- Start zebra only if the node needs it (is a router at the moment) - [Olivier Tilmans] - -- Added anycast ip attribute. [Olivier Tilmans] - -- Add anycast dns resolver support on ANK side, anycast ip's have yet to - handled. [Olivier Tilmans] - -- * Allow the server nodes (and by extension all l3devices) to be real - netkit VM's * Make the start of the zebra daemon optional (only if one - of its component is in use) * Made sure that the debug flag for BGP - was only set if BGP was enabled in the node. [Olivier Tilmans] - -- Ensure that copy_edge_attr_from will only copy attributes from edges - which are common to the two graphs. [Olivier Tilmans] - -- Bug of boolean fields in graphml solved. [Sébastien De Fauw] - -- Enforced usage of os.path.join in compiler. [Olivier Tilmans] - -- Remove redundant overlay creations. [Olivier Tilmans] - -- Clean out last of pika references. [sk2] - -- Tidying messaging. [sk2] - -- Use new format messaging. [sk2] - -- Using url params for routing, stripping out rabbitmq and telnet. [sk2] - -- Tidying up json format. [sk2] - -- Uncompress notebooks for easier access. [sk2] - -- Compress ipython notebooks. [sk2] - -- Remove symlink. [sk2] - -- Use gzip for default (smaller file size) [sk2] - -- Use gzip for default json. [sk2] - -- Remove unused data. [sk2] - -- Only apply ospf to interfaces bound in ospf graph. [sk2] - -- Remove images from tutorial. [sk2] - -v0.3.7 (2013-04-15) -------------------- - -- Update packages to latest version. [sk2] - -- Remove message pipe using telnet, support tornado 3.0.1. [sk2] - -v0.3.6 (2013-04-15) -------------------- - -- Add images. [sk2] - -- New module to push changes. [sk2] - -- Split out functions. [sk2] - -- Allow search on node id as well as label. [sk2] - -- Convert multi -> single edge graph. [sk2] - -- Split out functions. [sk2] - -- Split out functions. [sk2] - -- Allow select edge by nodes. [sk2] - -- Example notebook on OSPF cost experiments. [sk2] - -- Inc version. [sk2] - -- Split the boolean to render to_memory, and the rendered output. [sk2] - -- Tidying. [sk2] - -- Split out initialise into new function. [sk2] - -- New diff script to monitor and update network. [sk2] - -- Update. [sk2] - -- Modify example input. [sk2] - -- Add support for trace colours. [sk2] - -- Updates to traces. [sk2] - -- Index edges by src/dst pair. [sk2] - -- Add note. [sk2] - -- Comment out highlight. [sk2] - -- Allow access interface by numeric value (eg if from diff output) [sk2] - -- Add support for show ip ospf and conf t. [sk2] - -- Add function to diff two nidbs. [sk2] - -- Add basic processing (this needs to be moved to a process module) - [sk2] - -- Increase management subnet pool for testing (this needs to be modified - later) [sk2] - -- Don't set ibgp for grid. [sk2] - -- Remove extra update. [sk2] - -- Allow path data. [sk2] - -- More work on path animations. [sk2] - -- Animated path plotting. [sk2] - -- Change marker colour. [sk2] - -- Mapping from node id to id, ensures unique. [sk2] - -- Tidying. [sk2] - -- Improve path plotting, add markers (arrows) [sk2] - -- Groupings for nodes, edges, etc: can control ordering. [sk2] - -- Notify when receive highlight. [sk2] - -- Storing measured data to json. [sk2] - -- Improvements to automated measurement: use iteration rather than - callbacks. [sk2] - -- Tidying, show verification results. [sk2] - -- Set ospf for quagga. [sk2] - -- Sort cd ids. [sk2] - -- Basic shell script to run measure periodically. [sk2] - - will later be replaced with pure python script run as part of - autonetkit (or autonetkit_measure) command - -- Data and script to replay measurements. [sk2] - -- New verify module. [sk2] - -- Sort names for split. [sk2] - -- Better trace highlight support. [sk2] - -- Add sh ip route support. [sk2] - -- Bugfix: only validate if anm loaded. [sk2] - -- Remove old code. [sk2] - -- Add support for parsing sh ip route from quagga. [sk2] - -- Support for highlight paths [node, node, ... node] [sk2] - -- Support for highlight paths. [sk2] - -- Asn 0 -> 1. [sk2] - -- Remove trailing comma which made loopback ip a tuple. [sk2] - -- Support topology data used to store data without a template to render. - [sk2] - -- More work on oob ips. [sk2] - -- Better handling for non existent interfaces - eg oob added to nidb. - [sk2] - -- Allow interfaces to be added to nidb. [sk2] - -- Adding oob support. [sk2] - -- More work on vrfs. [sk2] - -- Tidy .gitignore. [sk2] - -- Ignore *.graphml* files. [sk2] - -- New collision domain icon. [sk2] - -- Remove symlink that crept in. [sk2] - -- Remove point-to-point config statement for ospf. [sk2] - -- Use same variable name for vpnv4. [sk2] - -- Tidying vrf pre-process for ibgp. [sk2] - -- Enforce specific packages. [sk2] - -- Change default edge color. [sk2] - -- Send ipv4 infra as json. [sk2] - -- Convert areas to strings for serializing keys. [sk2] - -- Add docstrings. [sk2] - -- Sort returned json keys. [sk2] - -- Continued vrfs. [sk2] - -- Ibgp vrf. [sk2] - -- Work on vrfs and bgp sessions, tidied up bgp sessions. [sk2] - -- More work on bgp vrfs. [sk2] - -- More work on vrfs. [sk2] - -- Remove debugging. [sk2] - -- Add to mpls ldp if bound in that overlay. [sk2] - -- Copy description as well as type from anm. [sk2] - -- Add todo. [sk2] - -- Smaller interface labels. [sk2] - -- Allow access to interface from nidb. [sk2] - -- Remove testing code. [sk2] - -- Work on vrfs, mpls ldp. [sk2] - -- Work on mpls, vrfs, mpls ldp. [sk2] - -- Fix issue with interface descriptions for secondary loopbacks. [sk2] - -- Copy interface ids back from nidb to anm overlays, condense to brief - for brevity. [sk2] - -- Update doc, work on json tree for nidb. [sk2] - -- Merge pull request #115 from sk2/master. [Simon Knight] - - merge back to interfaces - -- Merge pull request #114 from sk2/validate. [Simon Knight] - - add validation tests for ipv4 - -- Merge pull request #113 from sk2/interfaces. [Simon Knight] - - Interfaces - -- Add validation tests for ipv4. [sk2] - -- Initial commit of validate. [sk2] - -- Remove specific code, works under generic interface attributes. [sk2] - -- Add hooks for validate enable/disable. [sk2] - -- Workaround to import validate from python system, namespace clash with - using validate inside ank. [sk2] - -- Shortcut to check if interface is physical. [sk2] - -- Interface font size. [sk2] - -- Simpler cd icon. [sk2] - -- More work on vrfs. [sk2] - -- Generic interface overlay groupings (to support vrfs and ospf in - consistent format, will auto adapt) [sk2] - -- V6 secondary loopback alloc. [sk2] - -- Add shortcuts to interface iteration by type. [sk2] - -- Fix comment. [sk2] - -- Define lt for interface comparisons. [sk2] - -- Optional handling of secondary loopbacks. [sk2] - -- Tidy. [sk2] - -- Copying v4 and v6 ips for secondary loopbacks. [sk2] - -- Tidying vrf interfaces. [sk2] - -- Merge pull request #112 from sk2/interfaces. [Simon Knight] - - Interfaces - -- Ipv4/v6 switches. [sk2] - -- More work on tidying v4, v6, interfaces, testing. [sk2] - -- More interface hulls. [sk2] - -- Tidy icons. [sk2] - -- Debug. [sk2] - -- Inc ver. [sk2] - -- Update. [sk2] - -- New icon. [sk2] - -- Interface hulls. [sk2] - -- Bugfixes. [sk2] - -- Add accessors for physical and loopback access. [sk2] - -- More work on interfaces. [sk2] - -- Fix ibgp layering. [sk2] - -- More interface work. [sk2] - -- More interfaces. [sk2] - -- More interface work. [sk2] - -- More work on interfaces. [sk2] - -- More work on interfaces: datastructures, api, build, compile. [sk2] - -- Fixes for interfaces. [sk2] - -- Partial code for interface groupings eg for ospf areas. [sk2] - -- Working interface mappings in nidb. [sk2] - -- Remove debug. [sk2] - -- Copying across interface type to nidb. [sk2] - -- Interface dev. [sk2] - -- Adding notes. [sk2] - -- Fix order: first param if using args eg ("description") is desc not - type. [sk2] - -- Fix bug: need to test overlay_id is phy, not node_id is phy. [sk2] - -- Fix docstring. [sk2] - -- Add note. [sk2] - -- Return type. [sk2] - -- Tidy. [sk2] - -- Remove unneeded check (as fixed bug in ank split) [sk2] - -- Fix bug: was copying interface id from src rather than dst. [sk2] - -- Add todo. [sk2] - -- Add todo. [sk2] - -- Expand out _interfaces for edges. [sk2] - -- More dev work on interfaces. [sk2] - -- Looking up interfaces in nidb. [sk2] - -- Better adding edges to nidb if from cd vs switch. [sk2] - -- Better adding edges to nidb if from cd vs switch. [sk2] - -- Edge comparisons. [sk2] - -- Debug. [sk2] - -- Workarounds for multipoint ospf. [sk2] - -- Workarounds for multipoint ospf. [sk2] - -- Merge pull request #111 from sk2/multipoint. [Simon Knight] - - Multipoint - -- Make single-node groups less bubble-y. [sk2] - -- Tidy. [sk2] - -- Update ebgp to handle switches. [sk2] - -- Fix bugs in explode. [sk2] - -- Fix multipoint ebgp session handling to obtain ips. [sk2] - -- Switch support for isis, ospf, ebgp. [sk2] - -- Handle connected components. [sk2] - -- Concat rather than nested lists. [sk2] - -- Add todo. [sk2] - -- Fix support for wrapping exploded edges. [sk2] - -- Look at neighbouring routers. [sk2] - -- Only look at neighbouring routers for vrf (handles switches, other - devices) [sk2] - -- Fix bug where passing in empty list would fall back to all nodes in - graph. [sk2] - - now check if nbunch is None rather than evaluating to False (which was - case for empty list) - -- Merge pull request #110 from sk2/master. [Simon Knight] - - merge updates back to vrf branch - -- Fix issue with ibgp levels. [sk2] - -- More work on interfaces. [sk2] - -- Updating interface support. [sk2] - -- Testing code for interfaces. [sk2] - -- Testing code for interfaces. [sk2] - -- Correct returning edges to use new interface binding format of - {node_id: interface_id} [sk2] - -- Access corresponding interface across overlays (if exists) [sk2] - -- String repr of anm. [sk2] - -- New function for testing if overlay present in anm. [sk2] - -- Retain relevant interface bindings when splitting edges. [sk2] - -- Merge pull request #108 from sk2/multi-edge. [Simon Knight] - - Multi edge - -- Inc ver. [sk2] - -- Fix problem with one or two collision domain ASes. [sk2] - -- Handle case of AS with no iBGP nodes (all set to ibgp_role of None) - [sk2] - -- Updates. [sk2] - -- Fix correct image. [sk2] - -- Fix right version. [sk2] - -- Update. [sk2] - -- Updates. [sk2] - -- Updates. [sk2] - -- More updates. [sk2] - -- Updates. [sk2] - -- Updates. [sk2] - -- Updates. [sk2] - -- Revert change. [sk2] - -- Move to work with online notebook viewer. [sk2] - -- Update images, add images to tutorial. [sk2] - -- Update tutorial. [sk2] - -- Increase timeout. [sk2] - -- Add tutorial graphml. [sk2] - -- Add tutorial images. [sk2] - -- Remove debug. [sk2] - -- Update tutorial. [sk2] - -- Inc ver. [sk2] - -- Updates. [sk2] - -- Use ipv4 not ip. [sk2] - -- Tidy. [sk2] - -- New path colours. [sk2] - -- Handling starting and lab started. [sk2] - -- Better debug. [sk2] - -- Use ipv4 overlay. [sk2] - -- Add todo. [sk2] - -- Tidying, add option for grid. [sk2] - -- Default ospf cost. [sk2] - -- Ensure ospf cost is int. [sk2] - -- Add 2d grid. [sk2] - -- Bugfix. [sk2] - -- More work on vrfs. [sk2] - -- Remove website (has been moved to gh-pages branch) [sk2] - -- Inc ver. [sk2] - -- Closes gh-91. [sk2] - -- Extra send option. [sk2] - -- More explicit boolean. [sk2] - -- Remove debug. [sk2] - -- Workaround for gh-90. [sk2] - -- Auto list contributors from github api. [sk2] - -- Set default igp. [sk2] - -- Bugfix: dont set if node not in graph. [sk2] - -- Extend tutorial examples. [sk2] - -- Allow type casting in copy edge and node attribute functions. [sk2] - -- Update tutorial. [sk2] - -- Add tutorial. [sk2] - -- Move to gist. [sk2] - -- More notebook updates. [sk2] - -- Update workbook. [sk2] - -- Example ipython notebook. [sk2] - -- Highlights for nodes and edges. [sk2] - -- Inc ver. [sk2] - -- White body for printing. [sk2] - -- Merge ospf areas back into general function. [sk2] - -- Search for edges based on src/dst string ids. [sk2] - -- Simplified access to update http. [sk2] - -- Add shortcuts to common classes/functions. [sk2] - -- Merge pull request #88 from metaswirl/master. [Simon Knight] - - First pull request :) - -- Merge pull request #89 from bhesmans/fixCache. [Simon Knight] - - fixe cache issue. - -- Fixe cache issue. [Hesmans Benjamin] - - Won't render otherwise the two path joined were both absolute. Now, - use relative "base" isntead of full_base to build the base_cache_dir - -- Cleaned comments. [Niklas Semmler] - -- Added isis support to quagga, fixed a bug in the renderer. [Niklas - Semmler] - -- Tidying code. [sk2] - -- Add offset to fix truncating of curved edges to boxes in 2 node group - plots. [sk2] - -- Fix ordering of functions. [sk2] - -- Tidy. [sk2] - -- Pep8, tidying. [sk2] - -- Tidying. [sk2] - -- Tidying vrfs. [sk2] - -- Merge pull request #86 from sk2/vrf. [Simon Knight] - - Vrf support, misc bugfixes + improvements - -- Fix merge. [sk2] - -- Auto set ce. [sk2] - -- Remove todo. [sk2] - -- Merge pull request #85 from bhesmans/fixRRClientAS. [Simon Knight] - - quick fix for RR: no remote as. - -- Remove offset. [sk2] - -- Vrfs. [sk2] - -- Bugfix. [sk2] - -- Handle socket in use. [sk2] - -- Quick fix for RR: no remote as. [Hesmans Benjamin] - -- Work on caching. [sk2] - -- Use set comprehensions. [sk2] - -- Tidy. [sk2] - -- Move utility function. [sk2] - -- Code tidy. [sk2] - -- Pep8. [sk2] - -- Merge pull request #65 from oliviertilmans/master. [Simon Knight] - - Clear out .svn subdir from doc/source/reference/ - -- Updated gitignore to avoid further accidental tracking of .svn - subdirs. [Olivier Tilmans] - -- Removed svn subdir. [Olivier Tilmans] - -- Ios v6 isis. [sk2] - -- Add template error rendering. [sk2] - -- Ospfv3 on ios. [sk2] - -- Tidy status output. [sk2] - -- Marking for ospf v3. [sk2] - -- Attempts to tidy zoom. [sk2] - -- Increment version. [sk2] - -- Fix indent, add process id for isis. [sk2] - -- Bugfix: 126 ->128 bit v6 loopbacks. [sk2] - -- More work on interfaces, secondary loopbacks, vrfs. [sk2] - -- More interface support. [sk2] - -- Allocate to secondary loopbacks. [sk2] - -- Initial vrf block. [sk2] - -- More vrf. [sk2] - -- Improved interface handling. [sk2] - -- Update github link ank_v3_dev -> autonetkit. [sk2] - -- More work on interfaces: store on physical graph if node exists in it. - allows consistent interfaces across layers. [sk2] - -- Toggle filter. [sk2] - -- Neater filter. [sk2] - -- Inc ver. [sk2] - -- Toggle filter. [sk2] - -- Add extra log message. [sk2] - -- Load opacity on enter. [sk2] - -- Filter long attribute lists. [sk2] - -- Remove debug. [sk2] - -- Node filtering. [sk2] - -- Work on filtering opacity. [sk2] - -- Increment version. [sk2] - -- Check l3 cluster for ibgp, tidy syntax. [sk2] - -- Fix quagga. [sk2] - -- Work on interfaces. [sk2] - -- Attribute filtering for neighbors. [sk2] - -- Take icon size into account for auto scaling. [sk2] - -- Add grouping for vrf. [sk2] - -- Interfaces: adding with attributes, filtering on attributes, - iteration. [sk2] - -- Error handling. [sk2] - -- Adding vrf config. [sk2] - -- Tidy v6 access, format for consistency. [sk2] - -- Renaming ip -> ipv4, ip6 -> ipv6. [sk2] - -- Only configure v4 or v6 address blocks if v4 or v6 respectively is - enabled. [sk2] - -- Add note. [sk2] - -- Fix ipv4 var. [sk2] - -- Tidy debug. [sk2] - -- More work on nx_os. [sk2] - -- Initial work for nxos. [sk2] - -- Updates to allow dual-stack for cisco. [sk2] - -- Update scale for resized initial. [sk2] - -- Inc version. [sk2] - -- Tidy syntax. [sk2] - -- Tidying example access syntax. [sk2] - -- Better default scale for large topologies. [sk2] - -- Rename icon to descriptive label. [sk2] - -- Fix var names. [sk2] - -- Fix order of description. [sk2] - -- Set config dir, fix chassis. [sk2] - -- Add todo note. [sk2] - -- Fix error handling. [sk2] - -- Default to memory. [sk2] - -- Fix quagga ip format. [sk2] - -- Set dynagen config directory. [sk2] - -- Tidy dynagen. [sk2] - -- Toggle off v6. [sk2] - -- Use 7200 image. [sk2] - -- Add functions to nidb to be closer to anm. [sk2] - -- Ospf cost support. [sk2] - -- Enable v6. [sk2] - -- Fix level support for ibgp from yed. [sk2] - -- Update add_edge attr. [sk2] - -- Update. [sk2] - -- Initial commit of dynagen code for gh-46. [sk2] - -- Handle no ip6 graph. [sk2] - -- Remove overlay_accessor: use either anm['overlay_id'] or - G_a.overlay("overlay_id") [sk2] - -- Access overlay directly. [sk2] - -- Support v6. [sk2] - -- Support v6. [sk2] - -- Add groupby independent of subgraph. [sk2] - -- Add library for # [sk2] - -- Info -> debug. [sk2] - -- Increment version. [sk2] - -- Add # library. [sk2] - -- Tidy logic, add l3 to ibgp clustering. [sk2] - -- Look for correct package name. [sk2] - -- Tidy. [sk2] - -- Tidying, adding from HRR->RR if same RR group. [sk2] - -- Add extra logging information. [sk2] - -- Remove debug. [sk2] - -- Change interface allocations. [sk2] - -- Simplifying. [sk2] - -- Tidy to use routers. [sk2] - -- Exclude _interfaces from edge tooltip. [sk2] - -- Fix websocket tooltip. [sk2] - -- Add deploy wrapped, tidy. [sk2] - -- Tidy syntax. [sk2] - -- Tidy. [sk2] - -- Add routers shortcut. [sk2] - -- Support ibgp l1->l3 if not l2 in ibgp_l3_cluster. [sk2] - -- Add ignores. [sk2] - -- Update ignore. [sk2] - -- Add fonts to manifest. [Simon Knight] - -- Remove other deps. [Simon Knight] - -- Update setup. [Simon Knight] - -- Update version. [Simon Knight] - -- Update icons folder. [Simon Knight] - -- Merge pull request #64 from sk2/development. [Simon Knight] - - Development - -- Handle pika. [sk2] - -- Merge pull request #63 from sk2/master. [Simon Knight] - - push - -- Merge pull request #62 from sk2/Stable. [Simon Knight] - - improvements to measurement and traceroute plotting - -- Improvements to measurement and traceroute plotting. [sk2] - -- Merge pull request #61 from sk2/development. [Simon Knight] - - Development - -- Disable measure by default. [sk2] - -- Remove debug. [sk2] - -- Add bootup circles. [sk2] - -- Show websocket state as icon. [sk2] - -- Merge pull request #60 from sk2/development. [Simon Knight] - - Development - -- Add example. [sk2] - -- Merge pull request #59 from sk2/development. [Simon Knight] - - Development - -- Remove debug. [sk2] - -- More features. [sk2] - -- Exit for paths. [sk2] - -- Bugfix. [sk2] - -- Allow direct messaging using messaging rather than manual rabbitmq - construction. [sk2] - -- Merge pull request #58 from sk2/development. [Simon Knight] - - Development - -- Tidy. [sk2] - -- Example updates. [sk2] - -- Bugfix. [sk2] - -- Measure client updates. [sk2] - -- Change import order. [sk2] - -- More updates. [sk2] - -- Take rmq as argument. [sk2] - -- Add measure client. [sk2] - -- Merge pull request #57 from sk2/development. [Simon Knight] - - tidy - -- Ignore rendered. [sk2] - -- Tidy. [sk2] - -- Merge pull request #56 from sk2/development. [Simon Knight] - - move example to base dir - -- Move example to base dir. [sk2] - -- Merge pull request #55 from sk2/development. [Simon Knight] - - Development - -- Work on example. [sk2] - -- Update default log. [sk2] - -- More icon. [sk2] - -- Example. [sk2] - -- More icon. [sk2] - -- More icon. [sk2] - -- Merge pull request #54 from sk2/development. [Simon Knight] - - Development - -- Remove egg info. [sk2] - -- Tidy. [sk2] - -- Update icon. [sk2] - -- Move vis folder. [sk2] - -- Update packaging dependencies. [sk2] - -- Update doc, setup config. [sk2] - -- Merge pull request #53 from sk2/interfaces. [Simon Knight] - - Interfaces - -- Add dependencies. [sk2] - -- Add icons to ui. [sk2] - -- Merge pull request #52 from sk2/interfaces. [Simon Knight] - - Interfaces - -- Update icon. [sk2] - -- Remove old messaging package. [sk2] - -- Merge pull request #51 from sk2/interfaces. [Simon Knight] - - Interfaces - -- Move to examples directory. [sk2] - -- Add zoom fit button. [sk2] - -- Update vis layout. [sk2] - -- Update year, add favico to website. [sk2] - -- Icon data. [sk2] - -- Update icon. [sk2] - -- Auto zoom, remove interfaces and labels. [sk2] - -- Dont hide labels. [sk2] - -- Add icon. [sk2] - -- Remove unused messaging. [sk2] - -- Ui tidy. [sk2] - -- Revert. [sk2] - -- Auto hide revisions, tidy general ui, remove interfaces with toggle. - [sk2] - -- Tidying. [sk2] - -- Merge pull request #50 from sk2/interfaces. [Simon Knight] - - Merge - -- Add docs to repo. [sk2] - -- Remove unused python package. [sk2] - -- Add note. [sk2] - -- Add icon. [sk2] - -- Add todo. [sk2] - -- Better node handling. [sk2] - -- Remove debug. [sk2] - -- Tidying. [sk2] - -- Simpler add edges wrapper. [sk2] - -- Tidy manifest. [sk2] - -- Set default for blank labels, better handling of non-unique labels: if - so then set with asn. [sk2] - -- Handle multi-as from zoo. [sk2] - -- Tidying. [sk2] - -- Add ip. [sk2] - -- Processing for nren 1400. [sk2] - -- Simple example. [sk2] - -- More example. [sk2] - -- More examples. [sk2] - -- Set False for yEd exported booleans (by default not present on a node) - [sk2] - -- Tidy simple. [sk2] - -- Add retain to adding nodes through add_overlay. [sk2] - -- Add build option. [sk2] - -- Tidy simple example. [sk2] - -- Add examples. [sk2] - -- Tidy logic. [sk2] - -- New simplified example. [sk2] - -- Tidy, toggle out publishing v6 topology. [sk2] - -- Use new add overlay format. [sk2] - -- Add ability to add nodes at overlay creation. [sk2] - -- Merge pull request #43 from sk2/interfaces. [Simon Knight] - - Interfaces - -- V6 overlay support and allocation done. [sk2] - -- Optional server param for messaging: not required if using http post, - as picked up from settings. [sk2] - -- Adding ipv6 support. [sk2] - -- Increment version. [sk2] - -- Support import of cisco templates. [sk2] - -- Move more cisco specific code out. [sk2] - -- Move cisco specific load and deploy to autonetkit_cisco module. [sk2] - -- Update doc. [sk2] - -- Increment. [sk2] - -- Ensure area is string. [sk2] - -- Initial work on highlighting shared interfaces (eg loopback0) [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Remove unused allocate_hardware. [sk2] - -- Add interface labels. [sk2] - -- Tidying debug. [sk2] - -- More interfaces. [sk2] - -- More improvements to interfaces. [sk2] - -- More work on interfaces - work in progress. [sk2] - -- More work on vis. [sk2] - -- Display interfaces for directed edges. [sk2] - -- Bigger font for edges. [sk2] - -- Much improved directed edges, now with labels on the edge. [sk2] - -- Redoing directed edges. [sk2] - -- Dev. [sk2] - -- Dev. [sk2] - -- Disable zoom. [sk2] - -- Initial work on dynamic zooming. [sk2] - -- Remote message pipe from setup guide. [sk2] - -- Merge pull request #40 from sk2/interfaces. [Simon Knight] - - tidy - -- Tidy. [sk2] - -- Merge pull request #39 from sk2/interfaces. [Simon Knight] - - Interfaces - -- Disable full hostnames. [sk2] - -- Increment version. [sk2] - -- Lower node labels. [sk2] - -- Lower node labels. [sk2] - -- Tidy. [sk2] - -- Tidy. [sk2] - -- Better option to disable edge labels. [sk2] - -- Don't display interfaces. [sk2] - -- Better handling of interfaces in tooltip. [sk2] - -- Add option to disable edge labels. [sk2] - -- Tidy area zero handling. [sk2] - -- Handle ip address format for ospf areas. [sk2] - -- Tidy, todos. [sk2] - -- Correct rendering of arrays in tooltips. [sk2] - -- Interface toggle. [sk2] - -- More improvements for interfaces. [sk2] - -- Better interface vis. [sk2] - -- Add interfaces to anm, render interfaces on vis. [sk2] - -- Store ospf areas on node. [sk2] - -- Upgrade d3 from v2 to v3. [sk2] - -- Use v3 of d3, hide history buttons. [sk2] - -- Alignment, grouping for ospf areas. [sk2] - -- Tidy. [sk2] - -- Correct docstring. [sk2] - -- Merge pull request #38 from sk2/dev. [Simon Knight] - - default netkit render - -- Default netkit render. [sk2] - -- Merge pull request #37 from sk2/dev. [Simon Knight] - - better web message - -- Better web message. [sk2] - -- Merge pull request #36 from sk2/dev. [Simon Knight] - - Dev - -- Ignore. [sk2] - -- Add tornado to base dependencies. [sk2] - -- Error handling. [sk2] - -- Error handling if no input file. [sk2] - -- Better desc string. [sk2] - -- Disable pika requirement for base install. [sk2] - -- Enable http post by default. [sk2] - -- Merge pull request #35 from sk2/dev. [Simon Knight] - - Merge latest updates - -- Set input label for other device types, used in post-processing - module. [Simon Knight] - -- Support manually specified interface names. [Simon Knight] - -- Tidy. [Simon Knight] - -- Support manually specified interface names. [Simon Knight] - -- New messaging module. [Simon Knight] - -- Better error handling for invalid category ids. [Simon Knight] - -- Fix syntax error in logging. [Simon Knight] - -- Add end statement. [Simon Knight] - -- Handle extra attribute. [Simon Knight] - -- Copy across extra attribute. [Simon Knight] - -- Support for specified interface names. [Simon Knight] - -- Tidying. [Simon Knight] - -- Update look and feel. [Simon Knight] - -- Rename ank_pika to more generic messaging module. [Simon Knight] - -- Increment version. [Simon Knight] - -- Add new icons. [Simon Knight] - -- Tidy http post, support manually specified IPs. [Simon Knight] - -- Updates to logging. [Simon Knight] - -- Merge pull request #34 from sk2/dev. [Simon Knight] - - iBGP hierarchies, HTTP Post to update web ui - -- Tweak line offsets. [Simon Knight] - -- Support HTTP POST for updating topologies. [Simon Knight] - -- Fix indent. [Simon Knight] - -- Group by l3 cluster. [Simon Knight] - -- Tweaks to vis. [Simon Knight] - -- Update taper. [Simon Knight] - -- More tapered edges. [Simon Knight] - -- Update ignore. [Simon Knight] - -- Tidy neighbors. [Simon Knight] - -- Ibgp hierarchies. [Simon Knight] - -- Tapered edges. [Simon Knight] - -- Merge pull request #33 from sk2/dev. [Simon Knight] - - route reflectors - -- Corrections to iBGP. [Simon Knight] - -- Seperate out address classes. [Simon Knight] - -- Seperate out address classes. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Route reflectors. [Simon Knight] - -- Merge pull request #32 from sk2/dev. [Simon Knight] - - IOS IGP, bugfix for single-AS loopbacks - -- Fix single-AS loopbacks. [Simon Knight] - -- Hover for ip address nodes. [Simon Knight] - -- Interface type. [Simon Knight] - -- Tidy. [Simon Knight] - -- Fix network format. [Simon Knight] - -- Merge pull request #31 from sk2/dev. [Simon Knight] - - Dev - -- Add isis support to ios. [Simon Knight] - -- Tidy. [Simon Knight] - -- Merge pull request #30 from sk2/dev. [Simon Knight] - - Dev - -- Issue with configspec, update areas. [Simon Knight] - -- Set syntax from config defaults. [Simon Knight] - -- Merge pull request #29 from sk2/dev. [Simon Knight] - - fixes to ios config, interface naming, separate loopback IP groups: - don't allocate 10.0.0.0 etc as a loopback - -- Seperate loopback groups: don't allocate 10.0.0.0 as a loopback. - [Simon Knight] - -- Allocated loopbacks in a group: don't want 10.0.0.0 as a loopback ip. - [Simon Knight] - -- Add point-to-point to networks. [Simon Knight] - -- Id format Ethernet x/0. [Simon Knight] - -- Option to toggle timestamp in rendered output. [Simon Knight] - -- Handle socket error with warning. [Simon Knight] - -- Merge pull request #28 from sk2/dev. [Simon Knight] - - website updates, add readme - -- Update website, add readme. [Simon Knight] - -- Fix github link. [Simon Knight] - -- Update css page references, title in using. [Simon Knight] - -- New website. [Simon Knight] - -- Merge pull request #27 from sk2/dev. [Simon Knight] - - Dev - -- Tidy loading. [Simon Knight] - -- Default ospf area for graphml. [Simon Knight] - -- Update defaut topology for vis. [Simon Knight] - -- Merge pull request #26 from sk2/dev. [Simon Knight] - - add edge labels, restore print css, hierarchical ospf for IOS - -- More hierarchical ospf config. [Simon Knight] - -- Ospf hierarchy. [Simon Knight] - -- Fix print css. [Simon Knight] - -- Edge labels. [Simon Knight] - -- Merge pull request #24 from sk2/dev. [Simon Knight] - - remove debug, update github link to dev alpha - -- Update github link to dev alpha. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Merge pull request #23 from sk2/dev. [Simon Knight] - - Optional render, adding node label and edge group dropdowns, add ospf - areas, tipsy for tooltips - -- Default area of 0. [Simon Knight] - -- Redrawing changed edge_group_id. [Simon Knight] - -- Hide infobar, larger font for yapsy. [Simon Knight] - -- Use tipsy for tooltips. [Simon Knight] - -- Add OSPF router type. [Simon Knight] - -- Add title on hover, tidying. [Simon Knight] - -- Work on ospf areas. [Simon Knight] - -- Support for node label and edge grouping. [Simon Knight] - -- Add comments. [Simon Knight] - -- Add dropdowns for node label and edge grouping. [Simon Knight] - -- Add underscore js library. [Simon Knight] - -- Add render option. [Simon Knight] - -- Merge pull request #22 from sk2/dev. [Simon Knight] - - bugfix for ip if no links, add support for nested grouping in vis - (used for ospf attributes) - -- Fix ip crash if no links. [Simon Knight] - -- Remove debug, copy ospf_area into "area" in ospf graph. [Simon Knight] - -- Support ospf areas, nested groupings. [Simon Knight] - -- More parameters for copy_attr_from. [Simon Knight] - -- Merge pull request #16 from sk2/dev. [Simon Knight] - - Improvements to packaging for textfsm templates, add demo video to - website, fix passive interface for quagga IGP - -- Add demo video. [Simon Knight] - -- Correct path. [Simon Knight] - -- Open traceroute template from package. [Simon Knight] - -- Use package template file. [Simon Knight] - -- Update textfsm include. [Simon Knight] - -- Include textfsm templates. [Simon Knight] - -- Non numeric first character for zebra hostname too. [Simon Knight] - -- Make sure quagga hostnames start with letter. [Simon Knight] - -- Passive interfaces for ebgp. [Simon Knight] - -- Add loopback to interfaces. [Simon Knight] - -- Handle empty key string. [Simon Knight] - -- Merge pull request #15 from sk2/dev. [Simon Knight] - - Dev - -- Make screencasts more visible. [Simon Knight] - -- Change ip ranges. [Simon Knight] - -- Add alpha sorting for machines. [Simon Knight] - -- Use interface id. [Simon Knight] - -- Add sorting. [Simon Knight] - -- Add text sorting. [Simon Knight] - -- Start loopbacks at 172.16.127 so don't interfere with taps. [Simon - Knight] - -- Don't clobber measure. [Simon Knight] - -- Less verbose messages. [Simon Knight] - -- Better output. [Simon Knight] - -- Add note. [Simon Knight] - -- Merge pull request #13 from sk2/dev. [Simon Knight] - - drive compilation from config file - -- Drive compilation from config file. [Simon Knight] - -- Merge pull request #12 from sk2/dev. [Simon Knight] - - Dev - -- Add publications. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Neater maximise display. [Simon Knight] - -- Handle case of no infrastructure ips to advertise. [Simon Knight] - -- Allocate loopbacks seperately to infra, closes gh-10. [Simon Knight] - -- Add css maximise option. [Simon Knight] - -- Merge pull request #11 from sk2/dev. [Simon Knight] - - Cleaner updating to web interface - -- Read updates. [Simon Knight] - -- Remove extra sending pika. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Merge pull request #9 from sk2/dev. [Simon Knight] - - Basic lat/lon to x/y from zoo, grid layout if no x/y set - -- Add comment. [Simon Knight] - -- Merge pull request #8 from sk2/dev. [Simon Knight] - - Dev - -- Support for reading from stdin, writing single-file templates into - memory. [Simon Knight] - -- Use argparse instead of deprecated optparse. [Simon Knight] - -- Support html in status: lists rather than block of text. [Simon - Knight] - -- Merge pull request #7 from sk2/dev. [Simon Knight] - - Dev - -- Update instructions. [Simon Knight] - -- Inc. [Simon Knight] - -- Update isis format. [Simon Knight] - -- Default isis metric. [Simon Knight] - -- Merge pull request #6 from sk2/dev. [Simon Knight] - - Dev - -- Merge pull request #5 from sk2/master. [Simon Knight] - - Dev - -- Dont warn if no rmq. [Simon Knight] - -- Merge pull request #4 from sk2/dev. [Simon Knight] - - Dev - -- Update tutorial. [Simon Knight] - -- Inc. [Simon Knight] - -- Better handling of timestamp. [Simon Knight] - -- Include quagga templates. [Simon Knight] - -- Disable file logging for now. [Simon Knight] - -- Default to compile. [Simon Knight] - -- Don't write overlays as graphml. [Simon Knight] - -- Merge pull request #3 from sk2/master. [Simon Knight] - - Merge website and templates - -- Add tutorial. [Simon Knight] - -- Inc ver. [Simon Knight] - -- Include quagga templates. [Simon Knight] - -- Merge pull request #2 from sk2/dev. [Simon Knight] - - Better defaults and compilation - -- Better compilation depending on presence of platform/host. [Simon - Knight] - -- Update graphml default. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Merge pull request #1 from sk2/dev. [Simon Knight] - - Dev - -- Add development install guide. [Simon Knight] - -- Increment version. [Simon Knight] - -- Update interface names. [Simon Knight] - -- Add youtube link for screencasts. [Simon Knight] - -- Increment version, include html data in package. [Simon Knight] - -- Support telnet sockets. [Simon Knight] - -- Use new load module. [Simon Knight] - -- More visible trace data. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Sorting edges. [Simon Knight] - -- Tidying. [Simon Knight] - -- More visible traceroutes. [Simon Knight] - -- Add support for telnet. [Simon Knight] - -- Move vis inside distro. [Simon Knight] - -- Updates. [Simon Knight] - -- Use ank vis in distro. [Simon Knight] - -- Increment version. [Simon Knight] - -- Include vis in distro. [Simon Knight] - -- Increment version. [Simon Knight] - -- Fix naming. [Simon Knight] - -- Comment out dev. [Simon Knight] - -- Increment version. [Simon Knight] - -- Use correct ip for update-source. [Simon Knight] - -- Tidy anm sending over rabbit. [Simon Knight] - -- Dev. [Simon Knight] - -- Error handling alpha only name sorting. [Simon Knight] - -- More visible traces. [Simon Knight] - -- Updates for netkit deploy. [Simon Knight] - -- Use rabbitmq server from config. [Simon Knight] - -- Better log message. [Simon Knight] - -- Fix int ids. [Simon Knight] - -- Dev. [Simon Knight] - -- Update version. [Simon Knight] - -- Tidying. [Simon Knight] - -- Tidying. [Simon Knight] - -- Use package name. [Simon Knight] - -- Get correct package name for version. [Simon Knight] - -- Store original node label. [Simon Knight] - -- Update ignore for package. [Simon Knight] - -- Rename package version. [Simon Knight] - -- Add console help, version. [Simon Knight] - -- Remove dev. [Simon Knight] - -- Adding directed input graph support - useful for edge attributes. - [Simon Knight] - -- Sort interfaces on id. [Simon Knight] - -- Copy edge attributes. [Simon Knight] - -- Edge comparisons for sorting. [Simon Knight] - -- Improve bgp, isis. [Simon Knight] - -- Better version string. [Simon Knight] - -- Notes. [Simon Knight] - -- Better ios support, isis. [Simon Knight] - -- Default isis process id. [Simon Knight] - -- Hide nav for printing. [Simon Knight] - -- Longer delay for monitor, optional archiving, neater json writing for - diff. [Simon Knight] - -- Select between IGPs. [Simon Knight] - -- Diffing support. [Simon Knight] - -- Greatly improved differ. [Simon Knight] - -- Ignore diff. [Simon Knight] - -- Error message if no rabbitmq. [Simon Knight] - -- Disable hardware alloc for now. [Simon Knight] - -- Default dir. [Simon Knight] - -- Testing diff. [Simon Knight] - -- Handle sets. [Simon Knight] - -- More on interfaces. [Simon Knight] - -- Add graphics data. [Simon Knight] - -- Setting group attr. [Simon Knight] - -- Better error handling, retain of node id. [Simon Knight] - -- Tidy. [Simon Knight] - -- More dev. [Simon Knight] - -- Basic interface icon. [Simon Knight] - -- More testing for hw. [Simon Knight] - -- Add remove_node fn. [Simon Knight] - -- More hw alloc. [Simon Knight] - -- Handle empty overlays. [Simon Knight] - -- Adding conn graph. [Simon Knight] - -- Graph based hardware. [Simon Knight] - -- Change zoom. [Simon Knight] - -- Group by device for conn graph. [Simon Knight] - -- Add hash for set comparison, accessor for anm, option to add node, - option to not clobber adding nodes, [Simon Knight] - -- Before switching to graph-based interface representation. [Simon - Knight] - -- Hardware profiles. [Simon Knight] - -- Tidy. [Simon Knight] - -- Adding hardware profiles. [Simon Knight] - -- Tidying. [Simon Knight] - -- Adding standalone actions. [Simon Knight] - -- Policy parsing implemented, including nested if/then/else. [Simon - Knight] - -- More policy. [Simon Knight] - -- More pol. [Simon Knight] - -- Initial policy parsing. [Simon Knight] - -- Fix packaging. [Simon Knight] - -- Handle imports better. [Simon Knight] - -- Remove unused dep. [Simon Knight] - -- Tidy. [Simon Knight] - -- Tidy. [Simon Knight] - -- Update. [Simon Knight] - -- Updates. [Simon Knight] - -- Disable sockets for now. [Simon Knight] - -- Adding messaging. [Simon Knight] - -- Update ignore. [Simon Knight] - -- Add cisco internal support, tidy up, update build options if updated, - relay update node parameter. [Simon Knight] - -- Default disabled pika. [Simon Knight] - -- Add node label, tidy. [Simon Knight] - -- Relay update, support cisco internal host. [Simon Knight] - -- Handle disabled pika. [Simon Knight] - -- Restoring ios2. [Simon Knight] - -- Update ignore. [Simon Knight] - -- Exceptions class. [Simon Knight] - -- Add sorting. [Simon Knight] - -- Better exception handling. [Simon Knight] - -- Add internal host. [Simon Knight] - -- Handle non .graphml. [Simon Knight] - -- Todo. [Simon Knight] - -- Change sizes. [Simon Knight] - -- Ignore. [Simon Knight] - -- Remove. [Simon Knight] - -- Tidying. [Simon Knight] - -- Moved ip to be native networkx graph based. [Simon Knight] - -- More tidying compiler. [Simon Knight] - -- Keeping track of parent to update dicts. [Simon Knight] - -- Nidb access methods needing work - can't modify dictionary accessors. - [Simon Knight] - -- Before updating interface format. [Simon Knight] - -- Search by edge id. [Simon Knight] - -- Append as kwargs. [Simon Knight] - -- Moving to integrated lists. [Simon Knight] - -- Tidy. [Simon Knight] - -- Put in list (expected format) [Simon Knight] - -- Don't clobber host attribute from switches bugfix. [Simon Knight] - -- Adding sorting to tidy up compiler. [Simon Knight] - -- Allocate ip not subnet to loopback. [Simon Knight] - -- Add area back. [Simon Knight] - -- Better vis, moving towards twitter bootstrap, scalable with resizing. - [Simon Knight] - -- Tidying formatting. [Simon Knight] - -- Minimum of one rr per AS. [Simon Knight] - -- Better transition from overlay -> ip allocs. [Simon Knight] - -- Add node sorting for anm, placeholder for nidb. [Simon Knight] - -- More ip progress. [Simon Knight] - -- Ip addressing working. [Simon Knight] - -- More ip. [Simon Knight] - -- Add option to search for edge by node pair. [Simon Knight] - -- Tidy. [Simon Knight] - -- Use neighbors from overlay. [Simon Knight] - -- Better ip vis. [Simon Knight] - -- Testing radial layout. [Simon Knight] - -- More addressing. [Simon Knight] - -- More ip addressing. [Simon Knight] - -- More ip addressing. [Simon Knight] - -- Redoing IP allocation to be digraph (DAG) based. [Simon Knight] - -- Better plotting. [Simon Knight] - -- Transitions for updated ip data. [Simon Knight] - -- Tidying, adding support for ip allocation plotting. [Simon Knight] - -- Add demo to website. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Remove debug. [Simon Knight] - -- More config driven. [Simon Knight] - -- Use standard folder format. [Simon Knight] - -- Turn down base logging (fixes verbose paramiko) [Simon Knight] - -- Tidy. [Simon Knight] - -- Use config instead of hard-coded settings. [Simon Knight] - -- Default boolean for general configs, add deploy hosts. [Simon Knight] - -- Update title with revison number. [Simon Knight] - -- Tidying igp. [Simon Knight] - -- Move deploy to directory. [Simon Knight] - -- Measure directory. [Simon Knight] - -- More isis support. [Simon Knight] - -- Ready for sorted. [Simon Knight] - -- Debug on key miss. [Simon Knight] - -- Retain data. [Simon Knight] - -- Isis support. [Simon Knight] - -- Ignore extra rendered. [Simon Knight] - -- Subgraph only for netkit nodes. [Simon Knight] - -- Remove defaults (use config) [Simon Knight] - -- Tidy. [Simon Knight] - -- Also load cisco compiler. [Simon Knight] - -- Defaults from config. [Simon Knight] - -- Defaults from config. [Simon Knight] - -- More concise edge adding syntax. [Simon Knight] - -- Debug. [Simon Knight] - -- Tidy ip to network entity function. [Simon Knight] - -- Use new graphml reader. [Simon Knight] - -- Better most frequent algorithm. [Simon Knight] - -- Split out graphml reader. [Simon Knight] - -- Split out graphml reader, better most frequent algorithm. [Simon - Knight] - -- Create load module directory. [Simon Knight] - -- Move to load module directory, remove old caching code. [Simon Knight] - -- Add most frequent function, use instead. [Simon Knight] - -- Use loopback to create network_entity_title. [Simon Knight] - -- Bugfix: take most frequent ASN for inter-asn collision domains, rather - than mean. [Simon Knight] - - (otherwise cd between ASN 2 and 4 gets put in 3) - -- Add setup for pypi. [Simon Knight] - -- Turn down log verbosity. [Simon Knight] - -- Tidied, adding IS-IS support. [Simon Knight] - -- Better file checking, tidied up building, better monitor mode, - checking if build has changed, better stack trace. [Simon Knight] - -- Ignore egg builds. [Simon Knight] - -- Default overlay of phy not ospf. [Simon Knight] - -- Bugfix: asn of parent not neighbor. [Simon Knight] - -- Render for single run mode. [Simon Knight] - -- Tidying. [Simon Knight] - -- More descriptive queue name. [Simon Knight] - -- Use server from config. [Simon Knight] - -- Better formatting,zoom. [Simon Knight] - -- Add zoom. [Simon Knight] - -- Ignore local config, crash dump. [Simon Knight] - -- Read from config. [Simon Knight] - -- Default localhost. [Simon Knight] - -- Default localhost. [Simon Knight] - -- Hiding better layout for printing. [Simon Knight] - -- Better layout. [Simon Knight] - -- Fix fast-forward, add hidden option for printing each history - revision. [Simon Knight] - -- Update title with revision id. [Simon Knight] - -- Added clarity for comparison. [Simon Knight] - -- Add arrows, full history support. [Simon Knight] - -- Use global config settings. [Simon Knight] - -- Support for pika to update web ui. [Simon Knight] - -- Add note. [Simon Knight] - -- Revision back and forward. [Simon Knight] - -- Also turn off infobar. [Simon Knight] - -- Preselect correct dropdown based on overlay_id at init, add history - support. [Simon Knight] - -- Tidy print. [Simon Knight] - -- Remove redundant code. [Simon Knight] - -- Add print css to disable nav. [Simon Knight] - -- Extra notes. [Simon Knight] - -- Tidying js in vis. [Simon Knight] - -- More tidying. [Simon Knight] - -- Tidy. [Simon Knight] - -- Add config file. [Simon Knight] - -- Tidy. [Simon Knight] - -- Serialize ip using json. [Simon Knight] - -- Tidying. [Simon Knight] - -- Default not to compile. [Simon Knight] - -- Tidy json support. [Simon Knight] - -- Use json instead of pickle for serializing anm. [Simon Knight] - -- Function to copy graphics across from anm. [Simon Knight] - -- Tidying compression, moving network construction to seperate module. - [Simon Knight] - -- Move network construction into seperate module. [Simon Knight] - -- Abstract out pika messaging. [Simon Knight] - -- Restore diffing (disabled for now awaiting stable IP addressing) - [Simon Knight] - -- Better error handling, initial support for diffs. [Simon Knight] - -- Work with attributes rather than anm nodes directly. [Simon Knight] - -- Handling nidb. [Simon Knight] - -- Better serialization, de-serialize ip address/ip networks in lists - properly. [Simon Knight] - -- Don't use object references to anm nodes in nidb, use attributes eg - asn, label, etc. [Simon Knight] - -- Save/restore nidb using pickle. [Simon Knight] - -- Directed edge arcs, working on arrow alignment. [Simon Knight] - -- Trace colour. [Simon Knight] - -- Plotting all traceroutes (bugfix) [Simon Knight] - -- Loading saved json. [Simon Knight] - -- Saving json. [Simon Knight] - -- Testing cloud (doesn't scale width well) [Simon Knight] - -- Add cloud icon. [Simon Knight] - -- Set new default. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Tweaking grouping. [Simon Knight] - -- Grouping for single nodes, tidy. [Simon Knight] - -- Add hull for groups of 2 nodes. [Simon Knight] - -- Add compression for large anm that exceed rabbitmq max frame size. - [Simon Knight] - -- Add compression support. [Simon Knight] - -- Add queue watching for debugging. [Simon Knight] - -- Filter out incomplete traceroutes. [Simon Knight] - -- Default not to compile. [Simon Knight] - -- Increase dimensions. [Simon Knight] - -- Tidy. [Simon Knight] - -- Json handling IP address serialization and deserialization. [Simon - Knight] - -- Use json properly. [Simon Knight] - -- Example to load on webserver. [Simon Knight] - -- Send json not pickle to webserver. [Simon Knight] - -- Update overlay dropdown, remove poll code. [Simon Knight] - -- Tidy. [Simon Knight] - -- Pass json over rabbitmq rather than pickled anm - much more flexible. - [Simon Knight] - -- Initial work to pass json anm rather than pickled anm across network - to webserver. [Simon Knight] - -- Create dir if needed for topology (bugfix) [Simon Knight] - -- Extra todo note. [Simon Knight] - -- Don't relabel if same label (saves clobbering node data) [Simon - Knight] - -- Visual tweaks, reload images -> stay vector. [Simon Knight] - -- Placeholder. [Simon Knight] - -- Ignore logs. [Simon Knight] - -- Script to update website. [Simon Knight] - -- Demo website redirect. [Simon Knight] - -- Add pop to graphics. [Simon Knight] - -- Smoother updates and transitions. [Simon Knight] - -- Tidy debug. [Simon Knight] - -- Visual tweaks. [Simon Knight] - -- Handle if no version number. [Simon Knight] - -- Problems with pika sending anm. [Simon Knight] - -- Tidy. [Simon Knight] - -- Better parsing and rmq messages on starting and launched. [Simon - Knight] - -- Nicer trace colours. [Simon Knight] - -- Turn off compile by default. [Simon Knight] - -- Handle paths. [Simon Knight] - -- Add path for traceroutes. [Simon Knight] - -- Fix traceroutes. [Simon Knight] - -- Placeholder for webserver. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Change status font, don't list id in attributes. [Simon Knight] - -- Remove need for full ank install (better for remote servers) [Simon - Knight] - -- Tidy debug, better info messages. [Simon Knight] - -- Add more icons. [Simon Knight] - -- Websocket live updates working. [Simon Knight] - -- Add rmq support. [Simon Knight] - -- Debugging. [Simon Knight] - -- Extra note. [Simon Knight] - -- Moving to entirely websocket, no polling. [Simon Knight] - -- Removing outdated webserver. [Simon Knight] - -- Fix bug in monitoring. [Simon Knight] - -- Better hull updates, status labels, general tidy, [Simon Knight] - -- Better handling of no template attribute set. [Simon Knight] - -- Fix grouping hulls. [Simon Knight] - -- Update monitor mode. [Simon Knight] - -- Better log output. [Simon Knight] - -- Exception handling. [Simon Knight] - -- Dynamic websocket url. [Simon Knight] - -- Update label, select node from available nodes rather than hard-coded. - [Simon Knight] - -- Basic process error handling. [Simon Knight] - -- Add support for http. [Simon Knight] - -- Support tornado based web. [Simon Knight] - -- Fix label naming, [Simon Knight] - -- Moving webapp to single script, entirely in tornado. [Simon Knight] - -- Don't run graph products if no template set. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Graph products working. [Simon Knight] - -- Remove debug. [Simon Knight] - -- Remove debug. [Simon Knight] - -- More graph products. [Simon Knight] - -- More graph products. [Simon Knight] - -- Plotting edges from graph products. [Simon Knight] - -- Bug with hull in d3. [Simon Knight] - -- Initial commit of graph products. [Simon Knight] - -- Don't try overlays on first call. [Simon Knight] - -- Adding graph product support. [Simon Knight] - -- Bugfix. [Simon Knight] - -- Fn to replace graph. [Simon Knight] - -- Move icons. [Simon Knight] - -- Tidying. [Simon Knight] - -- Tidy icons. [Simon Knight] - -- Tidying viz. [Simon Knight] - -- More trace route parsing. [Simon Knight] - -- Better trace route vis. [Simon Knight] - -- Neater save/restore. [Simon Knight] - -- Turn down debug. [Simon Knight] - -- Nidb save/restore. [Simon Knight] - -- Visualize ip allocs. [Simon Knight] - -- Tidy. [Simon Knight] - -- Neater pickle. [Simon Knight] - -- Path plotting in d3. [Simon Knight] - -- More d3. [Simon Knight] - -- More rabbitmq/json/websockets/d3. [Simon Knight] - -- Bgp working, adding rmq sending of trace routes to d3. [Simon Knight] - -- List machines in lab - don't just boot all folders. [Simon Knight] - -- Log booting machines. [Simon Knight] - -- Connect to interface not loopback ip for ebgp. [Simon Knight] - -- Options for compile, deploy, measure. [Simon Knight] - -- Add file logging. [Simon Knight] - -- Fix issue where multiple threads create folder at same time. [Simon - Knight] - -- Use ethernet address for next-hop as workaround to DENIED due to: non- - connected next-hop; [Simon Knight] - -- Ebgp DENIED due to: non-connected next-hop. [Simon Knight] - -- Add static loopback routes for bgp. [Simon Knight] - -- Fix indent. [Simon Knight] - -- Fix warning. [Simon Knight] - -- Fix bgp network advertisement. [Simon Knight] - -- Fixing ebgp for traceroutes. [Simon Knight] - -- Adding extra measurement functions. [Simon Knight] - -- End-to-end deployment using exscript, measurement using exscript/rmq, - and parsing using textfsm. [Simon Knight] - -- More rmq measurement. [Simon Knight] - -- Adding rmq remote measurement. [Simon Knight] - -- More textfsm. [Simon Knight] - -- With initial textfsm processing of sh ip route. [Simon Knight] - -- Trying to capture routing output using exscript templates. [Simon - Knight] - -- Collecting data from hosts. [Simon Knight] - -- Fixing bgp config. [Simon Knight] - -- Fixing netkit routing. [Simon Knight] - -- Use remote interface ip for eBGP not loopback. [Simon Knight] - -- Fix issue with single dst node being treated as string for set. [Simon - Knight] - -- More netkit deploy improvements. [Simon Knight] - -- Fixing netkit deployment. [Simon Knight] - -- Remove previous lab dirs. [Simon Knight] - -- Testing deployment. [Simon Knight] - -- Testing. [Simon Knight] - -- Better deployment. [Simon Knight] - -- Don't save ip allocs -> speed. [Simon Knight] - -- Fix up naming. [Simon Knight] - -- Recommit. [Simon Knight] - -- Update ignore. [Simon Knight] - -- Better formatting. [Simon Knight] - -- Support host keys. [Simon Knight] - -- Use nklab not netkit as folder, support for host keys. [Simon Knight] - -- Add nonzero for nodes, fix subtle issue with evaluation nonzero for - None values in nidb_node_category. [Simon Knight] - -- Update template names. [Simon Knight] - -- Pass Network name to nidb. [Simon Knight] - -- Sort rendering. [Simon Knight] - -- Default name handling. [Simon Knight] - -- Ignore graphmls. [Simon Knight] - -- Tidying, add ssh. [Simon Knight] - -- Copy Network for zoo graphs. [Simon Knight] - -- New function to copy attributes from one overlay to another. [Simon - Knight] - -- Remove debug. [Simon Knight] - -- Different folder naming structure. [Simon Knight] - -- Dump graph attributes. [Simon Knight] - -- New naming functions. [Simon Knight] - -- Fixes. [Simon Knight] - -- Writing collision domains. [Simon Knight] - -- Update ignore. [Simon Knight] - -- Remove built docs. [Simon Knight] - - diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index e5bcf2f1..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,177 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) -$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/AutoNetkit.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/AutoNetkit.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/AutoNetkit" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/AutoNetkit" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/autonetkit.anm.rst b/docs/autonetkit.anm.rst deleted file mode 100644 index 86f7a130..00000000 --- a/docs/autonetkit.anm.rst +++ /dev/null @@ -1,78 +0,0 @@ -autonetkit.anm package -====================== - -Submodules ----------- - -autonetkit.anm.base module --------------------------- - -.. automodule:: autonetkit.anm.base - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.edge module --------------------------- - -.. automodule:: autonetkit.anm.edge - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.graph module ---------------------------- - -.. automodule:: autonetkit.anm.graph - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.graph_data module --------------------------------- - -.. automodule:: autonetkit.anm.graph_data - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.interface module -------------------------------- - -.. automodule:: autonetkit.anm.interface - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.network_model module ------------------------------------ - -.. automodule:: autonetkit.anm.network_model - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.node module --------------------------- - -.. automodule:: autonetkit.anm.node - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm.subgraph module ------------------------------- - -.. automodule:: autonetkit.anm.subgraph - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.anm - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.collection.rst b/docs/autonetkit.collection.rst deleted file mode 100644 index c0b41cfd..00000000 --- a/docs/autonetkit.collection.rst +++ /dev/null @@ -1,46 +0,0 @@ -autonetkit.collection package -============================= - -Submodules ----------- - -autonetkit.collection.process module ------------------------------------- - -.. automodule:: autonetkit.collection.process - :members: - :undoc-members: - :show-inheritance: - -autonetkit.collection.server module ------------------------------------ - -.. automodule:: autonetkit.collection.server - :members: - :undoc-members: - :show-inheritance: - -autonetkit.collection.utils module ----------------------------------- - -.. automodule:: autonetkit.collection.utils - :members: - :undoc-members: - :show-inheritance: - -autonetkit.collection.verify module ------------------------------------ - -.. automodule:: autonetkit.collection.verify - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.collection - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.compilers.device.rst b/docs/autonetkit.compilers.device.rst deleted file mode 100644 index aa54dd77..00000000 --- a/docs/autonetkit.compilers.device.rst +++ /dev/null @@ -1,62 +0,0 @@ -autonetkit.compilers.device package -=================================== - -Submodules ----------- - -autonetkit.compilers.device.cisco module ----------------------------------------- - -.. automodule:: autonetkit.compilers.device.cisco - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.device.device_base module ----------------------------------------------- - -.. automodule:: autonetkit.compilers.device.device_base - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.device.quagga module ------------------------------------------ - -.. automodule:: autonetkit.compilers.device.quagga - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.device.router_base module ----------------------------------------------- - -.. automodule:: autonetkit.compilers.device.router_base - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.device.server_base module ----------------------------------------------- - -.. automodule:: autonetkit.compilers.device.server_base - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.device.ubuntu module ------------------------------------------ - -.. automodule:: autonetkit.compilers.device.ubuntu - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.compilers.device - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.compilers.platform.rst b/docs/autonetkit.compilers.platform.rst deleted file mode 100644 index 66d273db..00000000 --- a/docs/autonetkit.compilers.platform.rst +++ /dev/null @@ -1,54 +0,0 @@ -autonetkit.compilers.platform package -===================================== - -Submodules ----------- - -autonetkit.compilers.platform.cisco module ------------------------------------------- - -.. automodule:: autonetkit.compilers.platform.cisco - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.platform.dynagen module --------------------------------------------- - -.. automodule:: autonetkit.compilers.platform.dynagen - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.platform.junosphere module ------------------------------------------------ - -.. automodule:: autonetkit.compilers.platform.junosphere - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.platform.netkit module -------------------------------------------- - -.. automodule:: autonetkit.compilers.platform.netkit - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compilers.platform.platform_base module --------------------------------------------------- - -.. automodule:: autonetkit.compilers.platform.platform_base - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.compilers.platform - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.compilers.rst b/docs/autonetkit.compilers.rst deleted file mode 100644 index b538f507..00000000 --- a/docs/autonetkit.compilers.rst +++ /dev/null @@ -1,18 +0,0 @@ -autonetkit.compilers package -============================ - -Subpackages ------------ - -.. toctree:: - - autonetkit.compilers.device - autonetkit.compilers.platform - -Module contents ---------------- - -.. automodule:: autonetkit.compilers - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.deploy.rst b/docs/autonetkit.deploy.rst deleted file mode 100644 index 360a29f8..00000000 --- a/docs/autonetkit.deploy.rst +++ /dev/null @@ -1,22 +0,0 @@ -autonetkit.deploy package -========================= - -Submodules ----------- - -autonetkit.deploy.netkit module -------------------------------- - -.. automodule:: autonetkit.deploy.netkit - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.deploy - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.design.rst b/docs/autonetkit.design.rst deleted file mode 100644 index 14b70efa..00000000 --- a/docs/autonetkit.design.rst +++ /dev/null @@ -1,54 +0,0 @@ -autonetkit.design package -========================= - -Submodules ----------- - -autonetkit.design.bgp module ----------------------------- - -.. automodule:: autonetkit.design.bgp - :members: - :undoc-members: - :show-inheritance: - -autonetkit.design.igp module ----------------------------- - -.. automodule:: autonetkit.design.igp - :members: - :undoc-members: - :show-inheritance: - -autonetkit.design.ip module ---------------------------- - -.. automodule:: autonetkit.design.ip - :members: - :undoc-members: - :show-inheritance: - -autonetkit.design.mpls module ------------------------------ - -.. automodule:: autonetkit.design.mpls - :members: - :undoc-members: - :show-inheritance: - -autonetkit.design.osi_layers module ------------------------------------ - -.. automodule:: autonetkit.design.osi_layers - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.design - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.load.rst b/docs/autonetkit.load.rst deleted file mode 100644 index 3bf1e286..00000000 --- a/docs/autonetkit.load.rst +++ /dev/null @@ -1,30 +0,0 @@ -autonetkit.load package -======================= - -Submodules ----------- - -autonetkit.load.graphml module ------------------------------- - -.. automodule:: autonetkit.load.graphml - :members: - :undoc-members: - :show-inheritance: - -autonetkit.load.load_json module --------------------------------- - -.. automodule:: autonetkit.load.load_json - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.load - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.nidb.rst b/docs/autonetkit.nidb.rst deleted file mode 100644 index a90eafba..00000000 --- a/docs/autonetkit.nidb.rst +++ /dev/null @@ -1,62 +0,0 @@ -autonetkit.nidb package -======================= - -Submodules ----------- - -autonetkit.nidb.base module ---------------------------- - -.. automodule:: autonetkit.nidb.base - :members: - :undoc-members: - :show-inheritance: - -autonetkit.nidb.config_stanza module ------------------------------------- - -.. automodule:: autonetkit.nidb.config_stanza - :members: - :undoc-members: - :show-inheritance: - -autonetkit.nidb.device_model module ------------------------------------ - -.. automodule:: autonetkit.nidb.device_model - :members: - :undoc-members: - :show-inheritance: - -autonetkit.nidb.edge module ---------------------------- - -.. automodule:: autonetkit.nidb.edge - :members: - :undoc-members: - :show-inheritance: - -autonetkit.nidb.interface module --------------------------------- - -.. automodule:: autonetkit.nidb.interface - :members: - :undoc-members: - :show-inheritance: - -autonetkit.nidb.node module ---------------------------- - -.. automodule:: autonetkit.nidb.node - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.nidb - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.plugins.rst b/docs/autonetkit.plugins.rst deleted file mode 100644 index 97e193e8..00000000 --- a/docs/autonetkit.plugins.rst +++ /dev/null @@ -1,54 +0,0 @@ -autonetkit.plugins package -========================== - -Submodules ----------- - -autonetkit.plugins.graph_product module ---------------------------------------- - -.. automodule:: autonetkit.plugins.graph_product - :members: - :undoc-members: - :show-inheritance: - -autonetkit.plugins.ipv4 module ------------------------------- - -.. automodule:: autonetkit.plugins.ipv4 - :members: - :undoc-members: - :show-inheritance: - -autonetkit.plugins.ipv4_new module ----------------------------------- - -.. automodule:: autonetkit.plugins.ipv4_new - :members: - :undoc-members: - :show-inheritance: - -autonetkit.plugins.ipv6 module ------------------------------- - -.. automodule:: autonetkit.plugins.ipv6 - :members: - :undoc-members: - :show-inheritance: - -autonetkit.plugins.naming module --------------------------------- - -.. automodule:: autonetkit.plugins.naming - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit.plugins - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/autonetkit.rst b/docs/autonetkit.rst deleted file mode 100644 index db089cc9..00000000 --- a/docs/autonetkit.rst +++ /dev/null @@ -1,164 +0,0 @@ -autonetkit package -================== - -Subpackages ------------ - -.. toctree:: - - autonetkit.anm - autonetkit.collection - autonetkit.compilers - autonetkit.deploy - autonetkit.design - autonetkit.load - autonetkit.nidb - autonetkit.plugins - -Submodules ----------- - -autonetkit.ank module ---------------------- - -.. automodule:: autonetkit.ank - :members: - :undoc-members: - :show-inheritance: - -autonetkit.ank_json module --------------------------- - -.. automodule:: autonetkit.ank_json - :members: - :undoc-members: - :show-inheritance: - -autonetkit.ank_messaging module -------------------------------- - -.. automodule:: autonetkit.ank_messaging - :members: - :undoc-members: - :show-inheritance: - -autonetkit.ank_utils module ---------------------------- - -.. automodule:: autonetkit.ank_utils - :members: - :undoc-members: - :show-inheritance: - -autonetkit.ank_validate module ------------------------------- - -.. automodule:: autonetkit.ank_validate - :members: - :undoc-members: - :show-inheritance: - -autonetkit.anm module ---------------------- - -.. automodule:: autonetkit.anm - :members: - :undoc-members: - :show-inheritance: - -autonetkit.build_network module -------------------------------- - -.. automodule:: autonetkit.build_network - :members: - :undoc-members: - :show-inheritance: - -autonetkit.compiler module --------------------------- - -.. automodule:: autonetkit.compiler - :members: - :undoc-members: - :show-inheritance: - -autonetkit.config module ------------------------- - -.. automodule:: autonetkit.config - :members: - :undoc-members: - :show-inheritance: - -autonetkit.console_script module --------------------------------- - -.. automodule:: autonetkit.console_script - :members: - :undoc-members: - :show-inheritance: - -autonetkit.diff module ----------------------- - -.. automodule:: autonetkit.diff - :members: - :undoc-members: - :show-inheritance: - -autonetkit.example module -------------------------- - -.. automodule:: autonetkit.example - :members: - :undoc-members: - :show-inheritance: - -autonetkit.exception module ---------------------------- - -.. automodule:: autonetkit.exception - :members: - :undoc-members: - :show-inheritance: - -autonetkit.log module ---------------------- - -.. automodule:: autonetkit.log - :members: - :undoc-members: - :show-inheritance: - -autonetkit.render module ------------------------- - -.. automodule:: autonetkit.render - :members: - :undoc-members: - :show-inheritance: - -autonetkit.webserver module ---------------------------- - -.. automodule:: autonetkit.webserver - :members: - :undoc-members: - :show-inheritance: - -autonetkit.yaml_utils module ----------------------------- - -.. automodule:: autonetkit.yaml_utils - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: autonetkit - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 71a9d868..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,265 +0,0 @@ -# -*- coding: utf-8 -*- -# -# AutoNetkit documentation build configuration file, created by -# sphinx-quickstart on Sat Mar 29 21:39:55 2014. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys -import os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.coverage', - 'sphinx.ext.pngmath', - 'sphinx.ext.mathjax', - 'sphinx.ext.ifconfig', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'AutoNetkit' -copyright = u'2014, AutoNetkit' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.9' -# The full version, including alpha/beta/rc tags. -release = '0.9' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'AutoNetkitdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ('index', 'AutoNetkit.tex', u'AutoNetkit Documentation', - u'AutoNetkit', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'autonetkit', u'AutoNetkit Documentation', - [u'AutoNetkit'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'AutoNetkit', u'AutoNetkit Documentation', - u'AutoNetkit', 'AutoNetkit', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False diff --git a/docs/extending.rst b/docs/extending.rst deleted file mode 100644 index 2125ab1b..00000000 --- a/docs/extending.rst +++ /dev/null @@ -1,405 +0,0 @@ - -AutoNetkit API tutorial -======================= - -Written for AutoNetkit 0.9 - -Create Network - -.. code:: python - - import autonetkit - anm = autonetkit.NetworkModel() -.. code:: python - - g_in = anm.add_overlay("input") - nodes = ['r1', 'r2', 'r3', 'r4', 'r5'] - - g_in.add_nodes_from(nodes) - g_in.update(device_type = "router", asn=1) - g_in.update("r5", asn = 2) -.. code:: python - - positions = {'r1': (10, 79), - 'r2': (226, 25), - 'r3': (172, 295), - 'r4': (334, 187), - 'r5': (496, 349)} - - for n in g_in: - n.x, n.y = positions[n] - - autonetkit.update_http(anm) - -.. code:: python - - edges = [("r1", "r2"), ("r2", "r4"), ("r1", "r3"), - ("r3", "r4"), ("r3", "r5"), ("r4", "r5")] - g_in.add_edges_from(edges) - autonetkit.update_http(anm) - -.. code:: python - - g_in.allocate_input_interfaces() - autonetkit.update_http(anm) -.. code:: python - - - - - for node in sorted(g_in): - print node - for index, interface in enumerate(node.physical_interfaces()): - print interface - interface.name = "eth%s" % index - - print node - autonetkit.update_http(anm) -.. code:: python - - g_phy = anm['phy'] - g_phy.add_nodes_from(g_in, retain=["asn", "device_type", "x", "y"]) - g_phy.update(use_ipv4 = True, host = "localhost", platform = "netkit", syntax = "quagga") - - g_phy.add_edges_from(g_in.edges()) - autonetkit.update_http(anm) - -.. code:: python - - g_ospf = anm.add_overlay("ospf") - g_ospf.add_nodes_from(g_in.routers()) - g_ospf.add_edges_from(e for e in g_in.edges() - if e.src.asn == e.dst.asn) - autonetkit.update_http(anm) -.. code:: python - - for node in g_ospf: - for interface in node.physical_interfaces(): - interface.cost = 10 - autonetkit.update_http(anm) -.. code:: python - - g_ebgp = anm.add_overlay("ebgp_v4", directed = True) - g_ebgp.add_nodes_from(g_in.routers()) - edges = [e for e in g_in.edges() - if e.src.asn != e.dst.asn] - # Add in both directions - g_ebgp.add_edges_from(edges, bidirectional = True) - autonetkit.update_http(anm) - -.. code:: python - - g_ibgp = anm.add_overlay("ibgp_v4", directed = True) - g_ibgp.add_nodes_from(g_in.routers()) - edges = [(s,t) for s in g_ibgp for t in g_ibgp - # belong to same ASN, but not self-loops - if s != t and s.asn == t.asn] - - # Add in both directions - g_ibgp.add_edges_from(edges, bidirectional = True) - autonetkit.update_http(anm) - -.. code:: python - - import autonetkit.ank as ank_utils - g_ipv4 = anm.add_overlay("ipv4") - g_ipv4.add_nodes_from(g_in) - g_ipv4.add_edges_from(g_in.edges()) - - # Split the point-to-point edges to add a collision domain - edges_to_split = [edge for edge in g_ipv4.edges() - if edge.src.is_l3device() and edge.dst.is_l3device()] - for edge in edges_to_split: - edge.split = True # mark as split for use in building nidb - - split_created_nodes = list(ank_utils.split(g_ipv4, edges_to_split, - retain=['split'], id_prepend='bc')) - - for node in split_created_nodes: - # Set the co-ordinates using 'x', 'y' of g_in - # based on neighbors in g_ipv4 - node.x = ank_utils.neigh_average(g_ipv4, node, 'x', g_in) - node.y = ank_utils.neigh_average(g_ipv4, node, 'y', g_in) - # Set most frequent of asn property in g_phy - # ASN is used to allocate IPs - node.asn = ank_utils.neigh_most_frequent(g_ipv4, node, - 'asn', g_phy) - node.broadcast_domain = True - node.device_type = "broadcast_domain" - - autonetkit.update_http(anm) -.. code:: python - - # Now use allocation plugin - import autonetkit.plugins.ipv4 as ipv4 - ipv4.allocate_infra(g_ipv4) - ipv4.allocate_loopbacks(g_ipv4) - - autonetkit.update_http(anm) -.. code:: python - - # Now construct NIDB - nidb = autonetkit.DeviceModel() - # NIDB is separate to the ANM -> copy over more properties - retain = ['label', 'host', 'platform', 'x', 'y', 'asn', 'device_type'] - nidb.add_nodes_from(g_phy, retain=retain) - - # Usually have a base g_ip which has structure - # allocate to g_ipv4, g_ipv6 - retain.append("subnet") # also copy across subnet - nidb.add_nodes_from(g_ipv4.nodes("broadcast_domain"), retain=retain) - nidb.add_edges_from(g_ipv4.edges()) - - # Also need to copy across the collision domains - - autonetkit.update_http(anm, nidb) -.. code:: python - - anm.add_overlay("ipv6") - anm.add_overlay("bgp") - autonetkit.update_http(anm) -.. code:: python - - import autonetkit.compilers.platform.netkit as pl_netkit - host = "localhost" - - platform_compiler = pl_netkit.NetkitCompiler(nidb, anm, host) - platform_compiler.compile() -.. code:: python - - import autonetkit.render - autonetkit.render.render(nidb) - -The output files are put - -:: - - into rendered/localhost/netkit - -For instance: - -:: - - ├── lab.conf - ├── r1 - │   ├── etc - │   │   ├── hostname - │   │   ├── shadow - │   │   ├── ssh - │   │   │   └── sshd_config - │   │   └── zebra - │   │   ├── bgpd.conf - │   │   ├── daemons - │   │   ├── isisd.conf - │   │   ├── motd.txt - │   │   ├── ospfd.conf - │   │   └── zebra.conf - │   └── root - ├── r1.startup - ├── r2 - │   ├── etc - │   │   ├── hostname - │   │   ├── shadow - │   │   ├── ssh - │   │   │   └── sshd_config - │   │   └── zebra - │   │   ├── bgpd.conf - │   │   ├── daemons - │   │   ├── isisd.conf - │   │   ├── motd.txt - │   │   ├── ospfd.conf - │   │   └── zebra.conf - │   └── root - ├── r2.startup - -Can also write our own compiler and templates: - -.. code:: python - - # AutoNetkit renderer expects filenames for templates - # uses the Mako template format - router_template_str = """Router |||rendered on ${date} by ${version_banner} - % for interface in node.interfaces: - interface ${interface.id} - description ${interface.description} - ip address ${interface.ipv4_address} netmask ${interface.ipv4_netmask} - % endfor - ! - router ospf ${node.ospf.process_id} - % for link in node.ospf.ospf_links: - network ${link.network.cidr} area ${link.area} - % endfor - ! - router bgp ${node.asn} - % for neigh in node.bgp.ibgp_neighbors: - ! ${neigh.neighbor} - neighbor ${neigh.loopback} remote-as ${neigh.asn} - neighbor ${neigh.loopback} update-source ${node.loopback_zero.ipv4_address} - neighbor ${neigh.loopback} next-hop-self - % endfor - ! - % for neigh in node.bgp.ebgp_neighbors: - ! ${neigh.neighbor} - neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn} - neighbor ${neigh.dst_int_ip} update-source ${neigh.local_int_ip} - % endfor - ! - """ - - router_template = "router.mako" - with open(router_template, "w") as fh: - fh.write(router_template_str) -.. code:: python - - from autonetkit.compilers.device import router_base - - from autonetkit.nidb.config_stanza import ConfigStanza as ConfigStanza - - class simple_router_compiler(router_base.RouterCompiler): - lo_interface = 'lo:1' - - def compile(self, node): - self.interfaces(node) - self.ospf(node) - self.bgp(node) - - def interfaces(self, node): - # Append attributes to the interface, rather than add a stanza - ipv4_node = self.anm['ipv4'].node(node) - if node.is_l3device: - node.loopback_zero.id = self.lo_interface - node.loopback_zero.description = 'Loopback' - node.loopback_zero.ipv4_address = ipv4_node.loopback - node.loopback_zero.ipv4_netmask = "255.255.255.255" - #interface_list.append(stanza) - - for interface in node.physical_interfaces(): - ipv4_int = ipv4_node.interface(interface) - interface.ipv4_address = ipv4_int.ip_address - interface.ipv4_netmask = ipv4_int.subnet.netmask - - def ospf(self, node): - node.add_stanza("ospf", process_id = 1) - ospf_links = [] - for interface in node.physical_interfaces(): - ipv4_int = self.anm['ipv4'].interface(interface) - ospf_links.append(ConfigStanza(network=ipv4_int.subnet, - area=0)) - node.ospf.ospf_links = ospf_links - - def bgp(self, node): - node.add_stanza("bgp") - g_ebgp = self.anm["ebgp_v4"] - ebgp_neighbors = [] - ibgp_neighbors = [] - for session in g_ebgp.edges(node): - neighbor = session.dst # remote node - stanza = ConfigStanza(neighbor = neighbor, - asn = neighbor.asn) - # Can obtain the dst int, as created bgp session - # from physical links - stanza.local_int_ip = session.src_int['ipv4'].ip_address - stanza.dst_int_ip = session.dst_int['ipv4'].ip_address - ebgp_neighbors.append(stanza) - - for session in g_ibgp.edges(node): - neighbor = session.dst # remote node - stanza = ConfigStanza(neighbor = neighbor, - asn = neighbor.asn) - stanza.loopback = neighbor['ipv4'].loopback - ibgp_neighbors.append(stanza) - - node.bgp.ebgp_neighbors = sorted(ebgp_neighbors, key = lambda x: x.neighbor) - node.bgp.ibgp_neighbors = sorted(ibgp_neighbors, key = lambda x: x.neighbor) - -.. code:: python - - - topology_template_str = """Topology rendered on ${date} by ${version_banner} - % for host in topology.hosts: - host: ${host} - % endfor - """ - - topology_template = "topology.mako" - with open(topology_template, "w") as fh: - fh.write(topology_template_str) -.. code:: python - - from autonetkit.compilers.platform import platform_base - import netaddr - from autonetkit.nidb import config_stanza - - class simple_platform_compiler(platform_base.PlatformCompiler): - def compile(self): - rtr_comp = simple_router_compiler(self.nidb, self.anm) - - for node in nidb.routers(host=host): - for index, interface in enumerate(node.physical_interfaces()): - interface.id = "eth%s" % index - - # specify router template - node.add_stanza("render") - node.render.template = router_template - node.render.dst_folder = "rendered" - node.render.dst_file = "%s.conf" % node - - # enable rendering for node - node.do_render = True - # and compile - rtr_comp.compile(node) - - # and the topology - lab_topology = self.nidb.topology(self.host) - # template settings for the renderer - lab_topology.render_template = topology_template - lab_topology.render_dst_folder = "rendered" - lab_topology.render_dst_file = "lab.conf" - - lab_topology.hosts = [] - for node in nidb.routers(host=host): - lab_topology.hosts.append(node) - -.. code:: python - - # Now construct NIDB - nidb = autonetkit.DeviceModel() - # NIDB is separate to the ANM -> copy over more properties - retain = ['label', 'host', 'platform', 'x', 'y', 'asn', 'device_type'] - nidb.add_nodes_from(g_phy, retain=retain) - - # Usually have a base g_ip which has structure - # allocate to g_ipv4, g_ipv6 - retain.append("subnet") # also copy across subnet - nidb.add_nodes_from(g_ipv4.nodes("broadcast_domain"), retain=retain) - nidb.add_edges_from(g_ipv4.edges()) - - # Also need to copy across the collision domains - - autonetkit.update_http(anm, nidb) -.. code:: python - - sim_plat = simple_platform_compiler(nidb, anm, "localhost") - sim_plat.compile() - autonetkit.update_http(anm, nidb) -.. code:: python - - for node in nidb: - print node.dump() -.. code:: python - - import autonetkit.render - autonetkit.render.render(nidb) -.. code:: python - - with open("rendered/lab.conf") as fh: - print fh.read() -.. code:: python - - with open("rendered/r1.conf") as fh: - print fh.read() -.. code:: python - - with open("rendered/r5.conf") as fh: - print fh.read() - diff --git a/docs/images/four_chain.png b/docs/images/four_chain.png deleted file mode 100644 index 77361a6258589ae58914462d8cd23b4e89011b8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32052 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

a--kMqL3L?e^q6%=j3_8QZz$I>Nf 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 diff --git a/docs/images/mixed.png b/docs/images/mixed.png deleted file mode 100644 index b396ea8cc2cb55f5db446442b5a405d6f038542c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35553 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 Ao&W#< diff --git a/docs/images/multi_as.png b/docs/images/multi_as.png deleted file mode 100644 index e0db0033837eea5f615c0d0c04cfda81d4e565ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63487 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

  • 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 diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 445dfd73..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. AutoNetkit documentation master file, created by - sphinx-quickstart on Sat Mar 29 21:39:55 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to AutoNetkit's documentation! -====================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - modules - -Extending tutorial: - - :doc:`extending` - -Topologies: - - :doc:`topologies` - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/modules.rst b/docs/modules.rst deleted file mode 100644 index 754fe1ed..00000000 --- a/docs/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -autonetkit -========== - -.. toctree:: - :maxdepth: 4 - - autonetkit diff --git a/docs/topologies.rst b/docs/topologies.rst deleted file mode 100644 index 1a29b140..00000000 --- a/docs/topologies.rst +++ /dev/null @@ -1,23 +0,0 @@ - -Topologies -======================= - -House - - .. image:: images/house.png - -Four Chain - - .. image:: images/four_chain.png - -Mixed - - .. image:: images/mixed.png - -Multi AS - - .. image:: images/multi_as.png - -Multi Edge - - .. image:: images/multi_edge.png \ No newline at end of file diff --git a/example/extending.html b/example/extending.html deleted file mode 100644 index 4dbf081f..00000000 --- a/example/extending.html +++ /dev/null @@ -1,2835 +0,0 @@ - - - - -[] - - - - - - - - - - - - - - - - -
    -

    Written for AutoNetkit 0.8

    -
    -
    -

    Create Network

    -
    -
    -
    -
    -In [1]: -
    -
    -
    import autonetkit
    -anm = autonetkit.ANM()
    -
    - -
    -
    - -
    -
    -
    -
    -In [2]: -
    -
    -
    g_in = anm.add_overlay("input")
    -nodes = ['r1', 'r2', 'r3', 'r4', 'r5']
    -
    -g_in.add_nodes_from(nodes)
    -g_in.update(device_type = "router", asn=1)
    -g_in.update("r5", asn = 2)
    -
    - -
    -
    - -
    -
    -
    -
    -In [3]: -
    -
    -
    positions = {'r1': (10, 79),
    - 'r2': (226, 25),
    - 'r3': (172, 295),
    - 'r4': (334, 187),
    - 'r5': (496, 349)}
    -
    -for n in g_in:
    -    n.x, n.y = positions[n]
    -
    -autonetkit.update_http(anm)
    -
    - -
    -
    - -
    -
    -
    -
    -In [4]: -
    -
    -
    edges = [("r1", "r2"), ("r2", "r4"), ("r1", "r3"), 
    -         ("r3", "r4"), ("r3", "r5"), ("r4", "r5")]
    -g_in.add_edges_from(edges)
    -g_in.allocate_interfaces()
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -INFO Automatically assigning input interfaces
    -INFO:ANK:Automatically assigning input interfaces
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [5]: -
    -
    -
    autonetkit.update_http(anm)
    -
    - -
    -
    - -
    -
    -
    -
    -In [6]: -
    -
    -
    g_phy = anm['phy']
    -g_phy.add_nodes_from(g_in, retain=["asn", "device_type", "x", "y"])
    -g_phy.update(use_ipv4 = True, host = "localhost", platform = "netkit", syntax = "quagga")
    -
    -g_phy.add_edges_from(g_in.edges())
    -autonetkit.update_http(anm)
    -
    - -
    -
    - -
    -
    -
    -
    -In [7]: -
    -
    -
    g_ospf = anm.add_overlay("ospf")
    -g_ospf.add_nodes_from(g_in.routers())
    -g_ospf.add_edges_from(e for e in g_in.edges()
    -                      if e.src.asn == e.dst.asn)
    -autonetkit.update_http(anm)                      
    -
    - -
    -
    - -
    -
    -
    -
    -In [8]: -
    -
    -
    g_ebgp = anm.add_overlay("ebgp_v4", directed = True)
    -g_ebgp.add_nodes_from(g_in.routers())
    -edges = [e for e in g_in.edges()
    -         if e.src.asn != e.dst.asn]
    -# Add in both directions
    -g_ebgp.add_edges_from(edges, bidirectional = True)
    -autonetkit.update_http(anm)              
    -
    - -
    -
    - -
    -
    -
    -
    -In [10]: -
    -
    -
    g_ibgp = anm.add_overlay("ibgp_v4", directed = True)
    -g_ibgp.add_nodes_from(g_in.routers())
    -edges = [(s,t) for s in g_ibgp for t in g_ibgp
    -         # belong to same ASN, but not self-loops
    -         if s != t and s.asn == t.asn]
    -
    -# Add in both directions
    -g_ibgp.add_edges_from(edges, bidirectional = True)
    -autonetkit.update_http(anm)
    -
    - -
    -
    - -
    -
    -
    -
    -In [11]: -
    -
    -
    import autonetkit.ank as ank_utils
    -g_ipv4 = anm.add_overlay("ipv4")
    -g_ipv4.add_nodes_from(g_in)
    -g_ipv4.add_edges_from(g_in.edges())
    -
    -# Split the point-to-point edges to add a collision domain
    -edges_to_split = [edge for edge in g_ipv4.edges()
    -                  if edge.attr_both('is_l3device')]
    -for edge in edges_to_split:
    -    edge.split = True  # mark as split for use in building nidb
    -    
    -split_created_nodes = list(ank_utils.split(g_ipv4, edges_to_split,
    -    retain=['split'], id_prepend='cd'))
    -
    -for node in split_created_nodes:
    -    # Set the co-ordinates using 'x', 'y' of g_in
    -    # based on neighbors in g_ipv4
    -    node.x = ank_utils.neigh_average(g_ipv4, node, 'x', g_in)
    -    node.y = ank_utils.neigh_average(g_ipv4, node, 'y', g_in)
    -    # Set most frequent of asn property in g_phy
    -    # ASN is used to allocate IPs
    -    node.asn = ank_utils.neigh_most_frequent(g_ipv4, node,
    -                                             'asn', g_phy)
    -    node.collision_domain = True
    -    
    -# Now use allocation plugin
    -import autonetkit.plugins.ipv4 as ipv4
    -ipv4.allocate_infra(g_ipv4)
    -ipv4.allocate_loopbacks(g_ipv4)
    -
    -autonetkit.update_http(anm)
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -INFO Allocating v4 Infrastructure IPs
    -INFO:ANK:Allocating v4 Infrastructure IPs
    -INFO Allocating v4 Primary Host loopback IPs
    -INFO:ANK:Allocating v4 Primary Host loopback IPs
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [12]: -
    -
    -
    # Now construct NIDB
    -nidb = autonetkit.NIDB()
    -# NIDB is separate to the ANM -> copy over more properties
    -retain = ['label', 'host', 'platform', 'x', 'y', 'asn', 'device_type']
    -nidb.add_nodes_from(g_phy, retain=retain)
    -
    -# Usually have a base g_ip which has structure
    -# allocate to g_ipv4, g_ipv6
    -nidb.add_nodes_from(g_phy, retain=retain)
    -retain.append("subnet") # also copy across subnet
    -nidb.add_nodes_from(g_ipv4.nodes("collision_domain"), retain=retain)
    -nidb.add_edges_from(g_ipv4.edges())
    -
    -# Also need to copy across the collision domains
    -
    -autonetkit.update_http(anm, nidb)
    -
    - -
    -
    - -
    -
    -
    -
    -In [13]: -
    -
    -
    import autonetkit.compilers.platform.netkit as pl_netkit
    -host = "localhost"
    -
    -platform_compiler = pl_netkit.NetkitCompiler(nidb, anm, host)
    -platform_compiler.compile() 
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -INFO Compiling Netkit for localhost
    -INFO:ANK:Compiling Netkit for localhost
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [14]: -
    -
    -
    import autonetkit.render
    -autonetkit.render.render(nidb)
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -INFO Rendering Network
    -INFO:ANK:Rendering Network
    -
    -
    -
    -
    - -
    -
    - -
    -
    -

    The output files are put

    -
    into rendered/localhost/netkit
    -

    For instance:

    -
    ├── lab.conf
    -├── r1
    -│   ├── etc
    -│   │   ├── hostname
    -│   │   ├── shadow
    -│   │   ├── ssh
    -│   │   │   └── sshd_config
    -│   │   └── zebra
    -│   │       ├── bgpd.conf
    -│   │       ├── daemons
    -│   │       ├── isisd.conf
    -│   │       ├── motd.txt
    -│   │       ├── ospfd.conf
    -│   │       └── zebra.conf
    -│   └── root
    -├── r1.startup
    -├── r2
    -│   ├── etc
    -│   │   ├── hostname
    -│   │   ├── shadow
    -│   │   ├── ssh
    -│   │   │   └── sshd_config
    -│   │   └── zebra
    -│   │       ├── bgpd.conf
    -│   │       ├── daemons
    -│   │       ├── isisd.conf
    -│   │       ├── motd.txt
    -│   │       ├── ospfd.conf
    -│   │       └── zebra.conf
    -│   └── root
    -├── r2.startup
    -
    -
    -

    Can also write our own compiler and templates:

    -
    -
    -
    -
    -In [15]: -
    -
    -
    # AutoNetkit renderer expects filenames for templates
    -# uses the Mako template format
    -router_template_str = """Router rendered on ${date} by ${version_banner}
    -% for interface in node.interfaces:
    -interface ${interface.id}
    -    description ${interface.description}
    -    ip address ${interface.ipv4_address} netmask ${interface.ipv4_subnet.netmask}
    -% endfor
    -!
    -router ospf ${node.ospf.process_id}
    -    % for link in node.ospf.ospf_links:
    -    network ${link.network.cidr} area ${link.area}
    -    % endfor
    -!
    -router bgp ${node.asn}
    -% for neigh in node.bgp.ibgp_neighbors:
    -  ! ${neigh.neighbor}
    -  neighbor ${neigh.loopback} remote-as ${neigh.asn}
    -  neighbor ${neigh.loopback} update-source ${node.loopback}
    -  neighbor ${neigh.loopback} next-hop-self
    -% endfor
    -!
    -% for neigh in node.bgp.ebgp_neighbors:
    -  ! ${neigh.neighbor}
    -  neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn}
    -  neighbor ${neigh.dst_int_ip} update-source ${neigh.local_int_ip}
    -% endfor
    -!    
    -"""
    -
    -router_template = "router.mako"
    -with open(router_template, "w") as fh:
    -    fh.write(router_template_str)
    -
    - -
    -
    - -
    -
    -
    -
    -In [16]: -
    -
    -
    from autonetkit.compilers.device import router_base
    -class simple_router_compiler(router_base.RouterCompiler):
    -    lo_interface = 'lo:1'
    -
    -    def interfaces(self, node):
    -        ipv4_node = self.anm['ipv4'].node(node)
    -        phy_node = self.anm['phy'].node(node)
    -
    -        super(simple_router_compiler, self).interfaces(node)
    -        if node.is_l3device:
    -            node.loopback_zero.id = self.lo_interface
    -            node.loopback_zero.description = 'Loopback'
    -            node.loopback_zero.ipv4_address = ipv4_node.loopback
    -            node.loopback_zero.ipv4_subnet = node.loopback_subnet 
    -
    - -
    -
    - -
    -
    -
    -
    -In [17]: -
    -
    -
    topology_template_str = """Topology rendered on ${date} by ${version_banner}
    -% for host in topology.hosts:
    -host: ${host}
    -% endfor
    -"""
    -
    -topology_template = "topology.mako"
    -with open(topology_template, "w") as fh:
    -    fh.write(topology_template_str)
    -
    - -
    -
    - -
    -
    -
    -
    -In [18]: -
    -
    -
    from autonetkit.compilers.platform import platform_base
    -import netaddr
    -from autonetkit.nidb import config_stanza
    -
    -class simple_platform_compiler(platform_base.PlatformCompiler):
    -    def compile(self):
    -        mgmt_address_block = netaddr.IPNetwork("172.16.0.0/16").iter_hosts()
    -        
    -        rtr_comp = simple_router_compiler(self.nidb, self.anm)
    -        
    -        for node in nidb.routers(host=host):
    -            node.mgmt_ip = mgmt_address_block.next()
    -            for index, interface in enumerate(node.physical_interfaces):
    -                interface.id = "eth%s" % index
    -            
    -            # specify router template
    -            node.render.template = router_template
    -            node.render.dst_folder = "rendered"
    -            node.render.dst_file = "%s.conf" % node
    -            # and compile
    -            rtr_comp.compile(node)
    -            node.dump()
    -            
    -                
    -        # and the topology
    -        lab_topology = self.nidb.topology[self.host]
    -        # template settings for the rendered
    -        lab_topology.render_template = topology_template
    -        lab_topology.render_dst_folder = "rendered"
    -        lab_topology.render_dst_file = "lab.conf"
    -        
    -        lab_topology.hosts = []
    -        for node in nidb.routers(host=host):
    -            lab_topology.hosts.append(node)
    -            
    -        # Can also deduce links (by condensing the CDs)
    -
    - -
    -
    - -
    -
    -
    -
    -In [19]: -
    -
    -
    sim_plat = simple_platform_compiler(nidb, anm, "localhost")
    -sim_plat.compile()
    -#autonetkit.update_http(anm, nidb)
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -{'_interfaces': {0: {'description': 'Loopback',
    -                     'id': 'lo:1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('192.168.0.4'),
    -                     'ipv4_subnet': IPNetwork('192.168.0.4/32'),
    -                     'type': 'loopback'},
    -                 1: {'description': 'to r2',
    -                     'id': 'eth0',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.10'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.10/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.8/30'),
    -                     'numeric_id': 0,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 2: {'description': 'to r3',
    -                     'id': 'eth1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.14'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.14/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.12/30'),
    -                     'numeric_id': 1,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 3: {'description': 'to r5',
    -                     'id': 'eth2',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.128.1'),
    -                     'ipv4_cidr': IPNetwork('10.0.128.1/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.128.0/30'),
    -                     'numeric_id': 2,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True}},
    - 'asn': 1,
    - 'bgp': {'debug': True,
    -         'ebgp_neighbors': [{'asn': 2,
    -                             'dst_int_ip': IPAddress('10.0.128.2'),
    -                             'local_int_ip': IPAddress('10.0.128.1'),
    -                             'loopback': IPAddress('192.168.0.9'),
    -                             'neighbor': 'r5',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_neighbors': [{'asn': 1,
    -                             'loopback': IPAddress('192.168.0.1'),
    -                             'neighbor': 'r1',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.2'),
    -                             'neighbor': 'r2',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.3'),
    -                             'neighbor': 'r3',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_rr_clients': [],
    -         'ibgp_rr_parents': [],
    -         'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],
    -         'ipv6_advertise_subnets': []},
    - 'device_type': 'router',
    - 'host': 'localhost',
    - 'hostname': 'r4',
    - 'input_label': 'r4',
    - 'interfaces': [],
    - 'ip': {'use_ipv4': True, 'use_ipv6': False},
    - 'label': 'r4',
    - 'loopback': IPAddress('192.168.0.4'),
    - 'loopback_subnet': IPNetwork('192.168.0.4/32'),
    - 'mgmt_ip': IPAddress('172.16.0.1'),
    - 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.8/30')), ('area', None)], [('network', IPNetwork('10.0.0.12/30')), ('area', None)]]), ('passive_interfaces', [[('id', 'eth2')]])],
    - 'platform': 'netkit',
    - 'render': {'base': 'templates/quagga',
    -            'base_dst_folder': 'rendered/localhost/netkit/r4',
    -            'custom': {'abc': 'def.txt'},
    -            'dst_file': 'r4.conf',
    -            'dst_folder': 'rendered',
    -            'template': 'router.mako'},
    - 'ssh': {'use_key': True},
    - 'tap': {'id': 'eth3', 'ip': IPAddress('172.16.0.6')},
    - 'x': 324,
    - 'y': 162,
    - 'zebra': {'password': '1234', 'static_routes': []}}
    -{'_interfaces': {0: {'description': 'Loopback',
    -                     'id': 'lo:1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('192.168.0.9'),
    -                     'ipv4_subnet': IPNetwork('192.168.0.9/32'),
    -                     'type': 'loopback'},
    -                 1: {'description': 'to r4',
    -                     'id': 'eth0',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.128.2'),
    -                     'ipv4_cidr': IPNetwork('10.0.128.2/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.128.0/30'),
    -                     'numeric_id': 0,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 2: {'description': 'to r3',
    -                     'id': 'eth1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.1.0.2'),
    -                     'ipv4_cidr': IPNetwork('10.1.0.2/16'),
    -                     'ipv4_subnet': IPNetwork('10.1.0.0/16'),
    -                     'numeric_id': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True}},
    - 'asn': 2,
    - 'bgp': {'debug': True,
    -         'ebgp_neighbors': [{'asn': 1,
    -                             'dst_int_ip': IPAddress('10.1.0.1'),
    -                             'local_int_ip': IPAddress('10.1.0.2'),
    -                             'loopback': IPAddress('192.168.0.3'),
    -                             'neighbor': 'r3',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'dst_int_ip': IPAddress('10.0.128.1'),
    -                             'local_int_ip': IPAddress('10.0.128.2'),
    -                             'loopback': IPAddress('192.168.0.4'),
    -                             'neighbor': 'r4',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_neighbors': [],
    -         'ibgp_rr_clients': [],
    -         'ibgp_rr_parents': [],
    -         'ipv4_advertise_subnets': [IPNetwork('10.1.0.0/16')],
    -         'ipv6_advertise_subnets': []},
    - 'device_type': 'router',
    - 'host': 'localhost',
    - 'hostname': 'r5',
    - 'input_label': 'r5',
    - 'interfaces': [],
    - 'ip': {'use_ipv4': True, 'use_ipv6': False},
    - 'label': 'r5',
    - 'loopback': IPAddress('192.168.0.9'),
    - 'loopback_subnet': IPNetwork('192.168.0.9/32'),
    - 'mgmt_ip': IPAddress('172.16.0.2'),
    - 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', []), ('passive_interfaces', [[('id', 'eth0')], [('id', 'eth1')]])],
    - 'platform': 'netkit',
    - 'render': {'base': 'templates/quagga',
    -            'base_dst_folder': 'rendered/localhost/netkit/r5',
    -            'custom': {'abc': 'def.txt'},
    -            'dst_file': 'r5.conf',
    -            'dst_folder': 'rendered',
    -            'template': 'router.mako'},
    - 'ssh': {'use_key': True},
    - 'tap': {'id': 'eth2', 'ip': IPAddress('172.16.0.7')},
    - 'x': 486,
    - 'y': 324,
    - 'zebra': {'password': '1234', 'static_routes': []}}
    -{'_interfaces': {0: {'description': 'Loopback',
    -                     'id': 'lo:1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('192.168.0.1'),
    -                     'ipv4_subnet': IPNetwork('192.168.0.1/32'),
    -                     'type': 'loopback'},
    -                 1: {'description': 'to r2',
    -                     'id': 'eth0',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.1'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.1/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.0/30'),
    -                     'numeric_id': 0,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 2: {'description': 'to r3',
    -                     'id': 'eth1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.5'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.5/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.4/30'),
    -                     'numeric_id': 1,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True}},
    - 'asn': 1,
    - 'bgp': {'debug': True,
    -         'ebgp_neighbors': [],
    -         'ibgp_neighbors': [{'asn': 1,
    -                             'loopback': IPAddress('192.168.0.2'),
    -                             'neighbor': 'r2',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.3'),
    -                             'neighbor': 'r3',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.4'),
    -                             'neighbor': 'r4',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_rr_clients': [],
    -         'ibgp_rr_parents': [],
    -         'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],
    -         'ipv6_advertise_subnets': []},
    - 'device_type': 'router',
    - 'host': 'localhost',
    - 'hostname': 'r1',
    - 'input_label': 'r1',
    - 'interfaces': [],
    - 'ip': {'use_ipv4': True, 'use_ipv6': False},
    - 'label': 'r1',
    - 'loopback': IPAddress('192.168.0.1'),
    - 'loopback_subnet': IPNetwork('192.168.0.1/32'),
    - 'mgmt_ip': IPAddress('172.16.0.3'),
    - 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.0/30')), ('area', None)], [('network', IPNetwork('10.0.0.4/30')), ('area', None)]]), ('passive_interfaces', [])],
    - 'platform': 'netkit',
    - 'render': {'base': 'templates/quagga',
    -            'base_dst_folder': 'rendered/localhost/netkit/r1',
    -            'custom': {'abc': 'def.txt'},
    -            'dst_file': 'r1.conf',
    -            'dst_folder': 'rendered',
    -            'template': 'router.mako'},
    - 'ssh': {'use_key': True},
    - 'tap': {'id': 'eth2', 'ip': IPAddress('172.16.0.3')},
    - 'x': 0,
    - 'y': 54,
    - 'zebra': {'password': '1234', 'static_routes': []}}
    -{'_interfaces': {0: {'description': 'Loopback',
    -                     'id': 'lo:1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('192.168.0.2'),
    -                     'ipv4_subnet': IPNetwork('192.168.0.2/32'),
    -                     'type': 'loopback'},
    -                 1: {'description': 'to r1',
    -                     'id': 'eth0',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.2'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.2/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.0/30'),
    -                     'numeric_id': 0,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 2: {'description': 'to r4',
    -                     'id': 'eth1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.9'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.9/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.8/30'),
    -                     'numeric_id': 1,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True}},
    - 'asn': 1,
    - 'bgp': {'debug': True,
    -         'ebgp_neighbors': [],
    -         'ibgp_neighbors': [{'asn': 1,
    -                             'loopback': IPAddress('192.168.0.1'),
    -                             'neighbor': 'r1',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.3'),
    -                             'neighbor': 'r3',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.4'),
    -                             'neighbor': 'r4',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_rr_clients': [],
    -         'ibgp_rr_parents': [],
    -         'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],
    -         'ipv6_advertise_subnets': []},
    - 'device_type': 'router',
    - 'host': 'localhost',
    - 'hostname': 'r2',
    - 'input_label': 'r2',
    - 'interfaces': [],
    - 'ip': {'use_ipv4': True, 'use_ipv6': False},
    - 'label': 'r2',
    - 'loopback': IPAddress('192.168.0.2'),
    - 'loopback_subnet': IPNetwork('192.168.0.2/32'),
    - 'mgmt_ip': IPAddress('172.16.0.4'),
    - 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.0/30')), ('area', None)], [('network', IPNetwork('10.0.0.8/30')), ('area', None)]]), ('passive_interfaces', [])],
    - 'platform': 'netkit',
    - 'render': {'base': 'templates/quagga',
    -            'base_dst_folder': 'rendered/localhost/netkit/r2',
    -            'custom': {'abc': 'def.txt'},
    -            'dst_file': 'r2.conf',
    -            'dst_folder': 'rendered',
    -            'template': 'router.mako'},
    - 'ssh': {'use_key': True},
    - 'tap': {'id': 'eth2', 'ip': IPAddress('172.16.0.4')},
    - 'x': 216,
    - 'y': 0,
    - 'zebra': {'password': '1234', 'static_routes': []}}
    -{'_interfaces': {0: {'description': 'Loopback',
    -                     'id': 'lo:1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('192.168.0.3'),
    -                     'ipv4_subnet': IPNetwork('192.168.0.3/32'),
    -                     'type': 'loopback'},
    -                 1: {'description': 'to r1',
    -                     'id': 'eth0',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.6'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.6/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.4/30'),
    -                     'numeric_id': 0,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 2: {'description': 'to r4',
    -                     'id': 'eth1',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.0.0.13'),
    -                     'ipv4_cidr': IPNetwork('10.0.0.13/30'),
    -                     'ipv4_subnet': IPNetwork('10.0.0.12/30'),
    -                     'numeric_id': 1,
    -                     'ospf_cost': 1,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True},
    -                 3: {'description': 'to r5',
    -                     'id': 'eth2',
    -                     'id_brief': '',
    -                     'ipv4_address': IPAddress('10.1.0.1'),
    -                     'ipv4_cidr': IPNetwork('10.1.0.1/16'),
    -                     'ipv4_subnet': IPNetwork('10.1.0.0/16'),
    -                     'numeric_id': 2,
    -                     'physical': True,
    -                     'type': 'physical',
    -                     'use_ipv4': True}},
    - 'asn': 1,
    - 'bgp': {'debug': True,
    -         'ebgp_neighbors': [{'asn': 2,
    -                             'dst_int_ip': IPAddress('10.1.0.2'),
    -                             'local_int_ip': IPAddress('10.1.0.1'),
    -                             'loopback': IPAddress('192.168.0.9'),
    -                             'neighbor': 'r5',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_neighbors': [{'asn': 1,
    -                             'loopback': IPAddress('192.168.0.1'),
    -                             'neighbor': 'r1',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.2'),
    -                             'neighbor': 'r2',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False},
    -                            {'asn': 1,
    -                             'loopback': IPAddress('192.168.0.4'),
    -                             'neighbor': 'r4',
    -                             'update_source': None,
    -                             'use_ipv4': True,
    -                             'use_ipv6': False}],
    -         'ibgp_rr_clients': [],
    -         'ibgp_rr_parents': [],
    -         'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],
    -         'ipv6_advertise_subnets': []},
    - 'device_type': 'router',
    - 'host': 'localhost',
    - 'hostname': 'r3',
    - 'input_label': 'r3',
    - 'interfaces': [],
    - 'ip': {'use_ipv4': True, 'use_ipv6': False},
    - 'label': 'r3',
    - 'loopback': IPAddress('192.168.0.3'),
    - 'loopback_subnet': IPNetwork('192.168.0.3/32'),
    - 'mgmt_ip': IPAddress('172.16.0.5'),
    - 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.4/30')), ('area', None)], [('network', IPNetwork('10.0.0.12/30')), ('area', None)]]), ('passive_interfaces', [[('id', 'eth2')]])],
    - 'platform': 'netkit',
    - 'render': {'base': 'templates/quagga',
    -            'base_dst_folder': 'rendered/localhost/netkit/r3',
    -            'custom': {'abc': 'def.txt'},
    -            'dst_file': 'r3.conf',
    -            'dst_folder': 'rendered',
    -            'template': 'router.mako'},
    - 'ssh': {'use_key': True},
    - 'tap': {'id': 'eth3', 'ip': IPAddress('172.16.0.5')},
    - 'x': 162,
    - 'y': 270,
    - 'zebra': {'password': '1234', 'static_routes': []}}
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [20]: -
    -
    -
    import autonetkit.render
    -autonetkit.render.render(nidb)
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -INFO Rendering Network
    -INFO:ANK:Rendering Network
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [21]: -
    -
    -
    with open("rendered/lab.conf") as fh:
    -    print fh.read()
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -Topology rendered on 2014-01-13 00:58 by autonetkit_0.8.2
    -host: r4
    -host: r5
    -host: r1
    -host: r2
    -host: r3
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [22]: -
    -
    -
    with open("rendered/r1.conf") as fh:
    -    print fh.read()
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -Router rendered on 2014-01-13 00:58 by autonetkit_0.8.2
    -interface lo:1
    -    description Loopback
    -    ip address 192.168.0.1 netmask 255.255.255.255
    -interface eth0
    -    description to r2
    -    ip address 10.0.0.1 netmask 255.255.255.252
    -interface eth1
    -    description to r3
    -    ip address 10.0.0.5 netmask 255.255.255.252
    -!
    -router ospf 1
    -    network 10.0.0.0/30 area None
    -    network 10.0.0.4/30 area None
    -!
    -router bgp 1
    -  ! r2
    -  neighbor 192.168.0.2 remote-as 1
    -  neighbor 192.168.0.2 update-source 192.168.0.1
    -  neighbor 192.168.0.2 next-hop-self
    -  ! r3
    -  neighbor 192.168.0.3 remote-as 1
    -  neighbor 192.168.0.3 update-source 192.168.0.1
    -  neighbor 192.168.0.3 next-hop-self
    -  ! r4
    -  neighbor 192.168.0.4 remote-as 1
    -  neighbor 192.168.0.4 update-source 192.168.0.1
    -  neighbor 192.168.0.4 next-hop-self
    -!
    -!    
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -In [23]: -
    -
    -
    with open("rendered/r5.conf") as fh:
    -    print fh.read()
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    -Router rendered on 2014-01-13 00:58 by autonetkit_0.8.2
    -interface lo:1
    -    description Loopback
    -    ip address 192.168.0.9 netmask 255.255.255.255
    -interface eth0
    -    description to r4
    -    ip address 10.0.128.2 netmask 255.255.255.252
    -interface eth1
    -    description to r3
    -    ip address 10.1.0.2 netmask 255.255.0.0
    -!
    -router ospf 1
    -!
    -router bgp 2
    -!
    -  ! r3
    -  neighbor 10.1.0.1 remote-as 1
    -  neighbor 10.1.0.1 update-source 10.1.0.2
    -  ! r4
    -  neighbor 10.0.128.1 remote-as 1
    -  neighbor 10.0.128.1 update-source 10.0.128.2
    -!    
    -
    -
    -
    -
    -
    - -
    -
    - -
    - - diff --git a/example/extending.ipynb b/example/extending.ipynb deleted file mode 100644 index 4d1f4d8f..00000000 --- a/example/extending.ipynb +++ /dev/null @@ -1,1109 +0,0 @@ -{ - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "AutoNetkit API tutorial" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Written for AutoNetkit 0.8" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create Network" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import autonetkit\n", - "anm = autonetkit.ANM()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "g_in = anm.add_overlay(\"input\")\n", - "nodes = ['r1', 'r2', 'r3', 'r4', 'r5']\n", - "\n", - "g_in.add_nodes_from(nodes)\n", - "g_in.update(device_type = \"router\", asn=1)\n", - "g_in.update(\"r5\", asn = 2)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "positions = {'r1': (10, 79),\n", - " 'r2': (226, 25),\n", - " 'r3': (172, 295),\n", - " 'r4': (334, 187),\n", - " 'r5': (496, 349)}\n", - "\n", - "for n in g_in:\n", - " n.x, n.y = positions[n]\n", - "\n", - "autonetkit.update_http(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "edges = [(\"r1\", \"r2\"), (\"r2\", \"r4\"), (\"r1\", \"r3\"), \n", - " (\"r3\", \"r4\"), (\"r3\", \"r5\"), (\"r4\", \"r5\")]\n", - "g_in.add_edges_from(edges)\n", - "g_in.allocate_interfaces()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Automatically assigning input interfaces\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Automatically assigning input interfaces\n" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "autonetkit.update_http(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 5 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "g_phy = anm['phy']\n", - "g_phy.add_nodes_from(g_in, retain=[\"asn\", \"device_type\", \"x\", \"y\"])\n", - "g_phy.update(use_ipv4 = True, host = \"localhost\", platform = \"netkit\", syntax = \"quagga\")\n", - "\n", - "g_phy.add_edges_from(g_in.edges())\n", - "autonetkit.update_http(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 6 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "g_ospf = anm.add_overlay(\"ospf\")\n", - "g_ospf.add_nodes_from(g_in.routers())\n", - "g_ospf.add_edges_from(e for e in g_in.edges()\n", - " if e.src.asn == e.dst.asn)\n", - "autonetkit.update_http(anm) " - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 7 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "g_ebgp = anm.add_overlay(\"ebgp_v4\", directed = True)\n", - "g_ebgp.add_nodes_from(g_in.routers())\n", - "edges = [e for e in g_in.edges()\n", - " if e.src.asn != e.dst.asn]\n", - "# Add in both directions\n", - "g_ebgp.add_edges_from(edges, bidirectional = True)\n", - "autonetkit.update_http(anm) " - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 8 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "g_ibgp = anm.add_overlay(\"ibgp_v4\", directed = True)\n", - "g_ibgp.add_nodes_from(g_in.routers())\n", - "edges = [(s,t) for s in g_ibgp for t in g_ibgp\n", - " # belong to same ASN, but not self-loops\n", - " if s != t and s.asn == t.asn]\n", - "\n", - "# Add in both directions\n", - "g_ibgp.add_edges_from(edges, bidirectional = True)\n", - "autonetkit.update_http(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 10 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import autonetkit.ank as ank_utils\n", - "g_ipv4 = anm.add_overlay(\"ipv4\")\n", - "g_ipv4.add_nodes_from(g_in)\n", - "g_ipv4.add_edges_from(g_in.edges())\n", - "\n", - "# Split the point-to-point edges to add a collision domain\n", - "edges_to_split = [edge for edge in g_ipv4.edges()\n", - " if edge.attr_both('is_l3device')]\n", - "for edge in edges_to_split:\n", - " edge.split = True # mark as split for use in building nidb\n", - " \n", - "split_created_nodes = list(ank_utils.split(g_ipv4, edges_to_split,\n", - " retain=['split'], id_prepend='cd'))\n", - "\n", - "for node in split_created_nodes:\n", - " # Set the co-ordinates using 'x', 'y' of g_in\n", - " # based on neighbors in g_ipv4\n", - " node.x = ank_utils.neigh_average(g_ipv4, node, 'x', g_in)\n", - " node.y = ank_utils.neigh_average(g_ipv4, node, 'y', g_in)\n", - " # Set most frequent of asn property in g_phy\n", - " # ASN is used to allocate IPs\n", - " node.asn = ank_utils.neigh_most_frequent(g_ipv4, node,\n", - " 'asn', g_phy)\n", - " node.collision_domain = True\n", - " \n", - "# Now use allocation plugin\n", - "import autonetkit.plugins.ipv4 as ipv4\n", - "ipv4.allocate_infra(g_ipv4)\n", - "ipv4.allocate_loopbacks(g_ipv4)\n", - "\n", - "autonetkit.update_http(anm)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Allocating v4 Infrastructure IPs\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Allocating v4 Infrastructure IPs\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Allocating v4 Primary Host loopback IPs\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Allocating v4 Primary Host loopback IPs\n" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# Now construct NIDB\n", - "nidb = autonetkit.NIDB()\n", - "# NIDB is separate to the ANM -> copy over more properties\n", - "retain = ['label', 'host', 'platform', 'x', 'y', 'asn', 'device_type']\n", - "nidb.add_nodes_from(g_phy, retain=retain)\n", - "\n", - "# Usually have a base g_ip which has structure\n", - "# allocate to g_ipv4, g_ipv6\n", - "nidb.add_nodes_from(g_phy, retain=retain)\n", - "retain.append(\"subnet\") # also copy across subnet\n", - "nidb.add_nodes_from(g_ipv4.nodes(\"collision_domain\"), retain=retain)\n", - "nidb.add_edges_from(g_ipv4.edges())\n", - "\n", - "# Also need to copy across the collision domains\n", - "\n", - "autonetkit.update_http(anm, nidb)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import autonetkit.compilers.platform.netkit as pl_netkit\n", - "host = \"localhost\"\n", - "\n", - "platform_compiler = pl_netkit.NetkitCompiler(nidb, anm, host)\n", - "platform_compiler.compile() " - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Compiling Netkit for localhost\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Compiling Netkit for localhost\n" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import autonetkit.render\n", - "autonetkit.render.render(nidb)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Rendering Network\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Rendering Network\n" - ] - } - ], - "prompt_number": 14 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The output files are put \n", - "\n", - " into rendered/localhost/netkit\n", - " \n", - "For instance:\n", - " \n", - " \u251c\u2500\u2500 lab.conf\n", - " \u251c\u2500\u2500 r1\n", - " \u2502\u00a0\u00a0 \u251c\u2500\u2500 etc\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 hostname\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 shadow\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 ssh\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 sshd_config\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 zebra\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 bgpd.conf\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 daemons\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 isisd.conf\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 motd.txt\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 ospfd.conf\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 zebra.conf\n", - " \u2502\u00a0\u00a0 \u2514\u2500\u2500 root\n", - " \u251c\u2500\u2500 r1.startup\n", - " \u251c\u2500\u2500 r2\n", - " \u2502\u00a0\u00a0 \u251c\u2500\u2500 etc\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 hostname\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 shadow\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 ssh\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 sshd_config\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 zebra\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 bgpd.conf\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 daemons\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 isisd.conf\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 motd.txt\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 ospfd.conf\n", - " \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 zebra.conf\n", - " \u2502\u00a0\u00a0 \u2514\u2500\u2500 root\n", - " \u251c\u2500\u2500 r2.startup\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Can also write our own compiler and templates:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# AutoNetkit renderer expects filenames for templates\n", - "# uses the Mako template format\n", - "router_template_str = \"\"\"Router rendered on ${date} by ${version_banner}\n", - "% for interface in node.interfaces:\n", - "interface ${interface.id}\n", - " description ${interface.description}\n", - " ip address ${interface.ipv4_address} netmask ${interface.ipv4_subnet.netmask}\n", - "% endfor\n", - "!\n", - "router ospf ${node.ospf.process_id}\n", - " % for link in node.ospf.ospf_links:\n", - " network ${link.network.cidr} area ${link.area}\n", - " % endfor\n", - "!\n", - "router bgp ${node.asn}\n", - "% for neigh in node.bgp.ibgp_neighbors:\n", - " ! ${neigh.neighbor}\n", - " neighbor ${neigh.loopback} remote-as ${neigh.asn}\n", - " neighbor ${neigh.loopback} update-source ${node.loopback}\n", - " neighbor ${neigh.loopback} next-hop-self\n", - "% endfor\n", - "!\n", - "% for neigh in node.bgp.ebgp_neighbors:\n", - " ! ${neigh.neighbor}\n", - " neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn}\n", - " neighbor ${neigh.dst_int_ip} update-source ${neigh.local_int_ip}\n", - "% endfor\n", - "! \n", - "\"\"\"\n", - "\n", - "router_template = \"router.mako\"\n", - "with open(router_template, \"w\") as fh:\n", - " fh.write(router_template_str)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 15 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from autonetkit.compilers.device import router_base\n", - "class simple_router_compiler(router_base.RouterCompiler):\n", - " lo_interface = 'lo:1'\n", - "\n", - " def interfaces(self, node):\n", - " ipv4_node = self.anm['ipv4'].node(node)\n", - " phy_node = self.anm['phy'].node(node)\n", - "\n", - " super(simple_router_compiler, self).interfaces(node)\n", - " if node.is_l3device:\n", - " node.loopback_zero.id = self.lo_interface\n", - " node.loopback_zero.description = 'Loopback'\n", - " node.loopback_zero.ipv4_address = ipv4_node.loopback\n", - " node.loopback_zero.ipv4_subnet = node.loopback_subnet " - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 16 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "\n", - "topology_template_str = \"\"\"Topology rendered on ${date} by ${version_banner}\n", - "% for host in topology.hosts:\n", - "host: ${host}\n", - "% endfor\n", - "\"\"\"\n", - "\n", - "topology_template = \"topology.mako\"\n", - "with open(topology_template, \"w\") as fh:\n", - " fh.write(topology_template_str)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 17 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from autonetkit.compilers.platform import platform_base\n", - "import netaddr\n", - "from autonetkit.nidb import config_stanza\n", - "\n", - "class simple_platform_compiler(platform_base.PlatformCompiler):\n", - " def compile(self):\n", - " mgmt_address_block = netaddr.IPNetwork(\"172.16.0.0/16\").iter_hosts()\n", - " \n", - " rtr_comp = simple_router_compiler(self.nidb, self.anm)\n", - " \n", - " for node in nidb.routers(host=host):\n", - " node.mgmt_ip = mgmt_address_block.next()\n", - " for index, interface in enumerate(node.physical_interfaces):\n", - " interface.id = \"eth%s\" % index\n", - " \n", - " # specify router template\n", - " node.render.template = router_template\n", - " node.render.dst_folder = \"rendered\"\n", - " node.render.dst_file = \"%s.conf\" % node\n", - " # and compile\n", - " rtr_comp.compile(node)\n", - " node.dump()\n", - " \n", - " \n", - " # and the topology\n", - " lab_topology = self.nidb.topology[self.host]\n", - " # template settings for the rendered\n", - " lab_topology.render_template = topology_template\n", - " lab_topology.render_dst_folder = \"rendered\"\n", - " lab_topology.render_dst_file = \"lab.conf\"\n", - " \n", - " lab_topology.hosts = []\n", - " for node in nidb.routers(host=host):\n", - " lab_topology.hosts.append(node)\n", - " \n", - " # Can also deduce links (by condensing the CDs)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 18 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "sim_plat = simple_platform_compiler(nidb, anm, \"localhost\")\n", - "sim_plat.compile()\n", - "#autonetkit.update_http(anm, nidb)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "{'_interfaces': {0: {'description': 'Loopback',\n", - " 'id': 'lo:1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('192.168.0.4'),\n", - " 'ipv4_subnet': IPNetwork('192.168.0.4/32'),\n", - " 'type': 'loopback'},\n", - " 1: {'description': 'to r2',\n", - " 'id': 'eth0',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.10'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.10/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.8/30'),\n", - " 'numeric_id': 0,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 2: {'description': 'to r3',\n", - " 'id': 'eth1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.14'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.14/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.12/30'),\n", - " 'numeric_id': 1,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 3: {'description': 'to r5',\n", - " 'id': 'eth2',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.128.1'),\n", - " 'ipv4_cidr': IPNetwork('10.0.128.1/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.128.0/30'),\n", - " 'numeric_id': 2,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True}},\n", - " 'asn': 1,\n", - " 'bgp': {'debug': True,\n", - " 'ebgp_neighbors': [{'asn': 2,\n", - " 'dst_int_ip': IPAddress('10.0.128.2'),\n", - " 'local_int_ip': IPAddress('10.0.128.1'),\n", - " 'loopback': IPAddress('192.168.0.9'),\n", - " 'neighbor': 'r5',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_neighbors': [{'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.1'),\n", - " 'neighbor': 'r1',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.2'),\n", - " 'neighbor': 'r2',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.3'),\n", - " 'neighbor': 'r3',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_rr_clients': [],\n", - " 'ibgp_rr_parents': [],\n", - " 'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],\n", - " 'ipv6_advertise_subnets': []},\n", - " 'device_type': 'router',\n", - " 'host': 'localhost',\n", - " 'hostname': 'r4',\n", - " 'input_label': 'r4',\n", - " 'interfaces': [],\n", - " 'ip': {'use_ipv4': True, 'use_ipv6': False},\n", - " 'label': 'r4',\n", - " 'loopback': IPAddress('192.168.0.4'),\n", - " 'loopback_subnet': IPNetwork('192.168.0.4/32'),\n", - " 'mgmt_ip': IPAddress('172.16.0.1'),\n", - " 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.8/30')), ('area', None)], [('network', IPNetwork('10.0.0.12/30')), ('area', None)]]), ('passive_interfaces', [[('id', 'eth2')]])],\n", - " 'platform': 'netkit',\n", - " 'render': {'base': 'templates/quagga',\n", - " 'base_dst_folder': 'rendered/localhost/netkit/r4',\n", - " 'custom': {'abc': 'def.txt'},\n", - " 'dst_file': 'r4.conf',\n", - " 'dst_folder': 'rendered',\n", - " 'template': 'router.mako'},\n", - " 'ssh': {'use_key': True},\n", - " 'tap': {'id': 'eth3', 'ip': IPAddress('172.16.0.6')},\n", - " 'x': 324,\n", - " 'y': 162,\n", - " 'zebra': {'password': '1234', 'static_routes': []}}\n", - "{'_interfaces': {0: {'description': 'Loopback',\n", - " 'id': 'lo:1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('192.168.0.9'),\n", - " 'ipv4_subnet': IPNetwork('192.168.0.9/32'),\n", - " 'type': 'loopback'},\n", - " 1: {'description': 'to r4',\n", - " 'id': 'eth0',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.128.2'),\n", - " 'ipv4_cidr': IPNetwork('10.0.128.2/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.128.0/30'),\n", - " 'numeric_id': 0,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 2: {'description': 'to r3',\n", - " 'id': 'eth1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.1.0.2'),\n", - " 'ipv4_cidr': IPNetwork('10.1.0.2/16'),\n", - " 'ipv4_subnet': IPNetwork('10.1.0.0/16'),\n", - " 'numeric_id': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True}},\n", - " 'asn': 2,\n", - " 'bgp': {'debug': True,\n", - " 'ebgp_neighbors': [{'asn': 1,\n", - " 'dst_int_ip': IPAddress('10.1.0.1'),\n", - " 'local_int_ip': IPAddress('10.1.0.2'),\n", - " 'loopback': IPAddress('192.168.0.3'),\n", - " 'neighbor': 'r3',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'dst_int_ip': IPAddress('10.0.128.1'),\n", - " 'local_int_ip': IPAddress('10.0.128.2'),\n", - " 'loopback': IPAddress('192.168.0.4'),\n", - " 'neighbor': 'r4',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_neighbors': [],\n", - " 'ibgp_rr_clients': [],\n", - " 'ibgp_rr_parents': [],\n", - " 'ipv4_advertise_subnets': [IPNetwork('10.1.0.0/16')],\n", - " 'ipv6_advertise_subnets': []},\n", - " 'device_type': 'router',\n", - " 'host': 'localhost',\n", - " 'hostname': 'r5',\n", - " 'input_label': 'r5',\n", - " 'interfaces': [],\n", - " 'ip': {'use_ipv4': True, 'use_ipv6': False},\n", - " 'label': 'r5',\n", - " 'loopback': IPAddress('192.168.0.9'),\n", - " 'loopback_subnet': IPNetwork('192.168.0.9/32'),\n", - " 'mgmt_ip': IPAddress('172.16.0.2'),\n", - " 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', []), ('passive_interfaces', [[('id', 'eth0')], [('id', 'eth1')]])],\n", - " 'platform': 'netkit',\n", - " 'render': {'base': 'templates/quagga',\n", - " 'base_dst_folder': 'rendered/localhost/netkit/r5',\n", - " 'custom': {'abc': 'def.txt'},\n", - " 'dst_file': 'r5.conf',\n", - " 'dst_folder': 'rendered',\n", - " 'template': 'router.mako'},\n", - " 'ssh': {'use_key': True},\n", - " 'tap': {'id': 'eth2', 'ip': IPAddress('172.16.0.7')},\n", - " 'x': 486,\n", - " 'y': 324,\n", - " 'zebra': {'password': '1234', 'static_routes': []}}\n", - "{'_interfaces': {0: {'description': 'Loopback',\n", - " 'id': 'lo:1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('192.168.0.1'),\n", - " 'ipv4_subnet': IPNetwork('192.168.0.1/32'),\n", - " 'type': 'loopback'},\n", - " 1: {'description': 'to r2',\n", - " 'id': 'eth0',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.1'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.1/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.0/30'),\n", - " 'numeric_id': 0,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 2: {'description': 'to r3',\n", - " 'id': 'eth1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.5'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.5/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.4/30'),\n", - " 'numeric_id': 1,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True}},\n", - " 'asn': 1,\n", - " 'bgp': {'debug': True,\n", - " 'ebgp_neighbors': [],\n", - " 'ibgp_neighbors': [{'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.2'),\n", - " 'neighbor': 'r2',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.3'),\n", - " 'neighbor': 'r3',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.4'),\n", - " 'neighbor': 'r4',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_rr_clients': [],\n", - " 'ibgp_rr_parents': [],\n", - " 'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],\n", - " 'ipv6_advertise_subnets': []},\n", - " 'device_type': 'router',\n", - " 'host': 'localhost',\n", - " 'hostname': 'r1',\n", - " 'input_label': 'r1',\n", - " 'interfaces': [],\n", - " 'ip': {'use_ipv4': True, 'use_ipv6': False},\n", - " 'label': 'r1',\n", - " 'loopback': IPAddress('192.168.0.1'),\n", - " 'loopback_subnet': IPNetwork('192.168.0.1/32'),\n", - " 'mgmt_ip': IPAddress('172.16.0.3'),\n", - " 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.0/30')), ('area', None)], [('network', IPNetwork('10.0.0.4/30')), ('area', None)]]), ('passive_interfaces', [])],\n", - " 'platform': 'netkit',\n", - " 'render': {'base': 'templates/quagga',\n", - " 'base_dst_folder': 'rendered/localhost/netkit/r1',\n", - " 'custom': {'abc': 'def.txt'},\n", - " 'dst_file': 'r1.conf',\n", - " 'dst_folder': 'rendered',\n", - " 'template': 'router.mako'},\n", - " 'ssh': {'use_key': True},\n", - " 'tap': {'id': 'eth2', 'ip': IPAddress('172.16.0.3')},\n", - " 'x': 0,\n", - " 'y': 54,\n", - " 'zebra': {'password': '1234', 'static_routes': []}}\n", - "{'_interfaces': {0: {'description': 'Loopback',\n", - " 'id': 'lo:1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('192.168.0.2'),\n", - " 'ipv4_subnet': IPNetwork('192.168.0.2/32'),\n", - " 'type': 'loopback'},\n", - " 1: {'description': 'to r1',\n", - " 'id': 'eth0',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.2'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.2/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.0/30'),\n", - " 'numeric_id': 0,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 2: {'description': 'to r4',\n", - " 'id': 'eth1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.9'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.9/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.8/30'),\n", - " 'numeric_id': 1,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True}},\n", - " 'asn': 1,\n", - " 'bgp': {'debug': True,\n", - " 'ebgp_neighbors': [],\n", - " 'ibgp_neighbors': [{'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.1'),\n", - " 'neighbor': 'r1',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.3'),\n", - " 'neighbor': 'r3',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.4'),\n", - " 'neighbor': 'r4',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_rr_clients': [],\n", - " 'ibgp_rr_parents': [],\n", - " 'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],\n", - " 'ipv6_advertise_subnets': []},\n", - " 'device_type': 'router',\n", - " 'host': 'localhost',\n", - " 'hostname': 'r2',\n", - " 'input_label': 'r2',\n", - " 'interfaces': [],\n", - " 'ip': {'use_ipv4': True, 'use_ipv6': False},\n", - " 'label': 'r2',\n", - " 'loopback': IPAddress('192.168.0.2'),\n", - " 'loopback_subnet': IPNetwork('192.168.0.2/32'),\n", - " 'mgmt_ip': IPAddress('172.16.0.4'),\n", - " 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.0/30')), ('area', None)], [('network', IPNetwork('10.0.0.8/30')), ('area', None)]]), ('passive_interfaces', [])],\n", - " 'platform': 'netkit',\n", - " 'render': {'base': 'templates/quagga',\n", - " 'base_dst_folder': 'rendered/localhost/netkit/r2',\n", - " 'custom': {'abc': 'def.txt'},\n", - " 'dst_file': 'r2.conf',\n", - " 'dst_folder': 'rendered',\n", - " 'template': 'router.mako'},\n", - " 'ssh': {'use_key': True},\n", - " 'tap': {'id': 'eth2', 'ip': IPAddress('172.16.0.4')},\n", - " 'x': 216,\n", - " 'y': 0,\n", - " 'zebra': {'password': '1234', 'static_routes': []}}\n", - "{" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "'_interfaces': {0: {'description': 'Loopback',\n", - " 'id': 'lo:1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('192.168.0.3'),\n", - " 'ipv4_subnet': IPNetwork('192.168.0.3/32'),\n", - " 'type': 'loopback'},\n", - " 1: {'description': 'to r1',\n", - " 'id': 'eth0',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.6'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.6/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.4/30'),\n", - " 'numeric_id': 0,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 2: {'description': 'to r4',\n", - " 'id': 'eth1',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.0.0.13'),\n", - " 'ipv4_cidr': IPNetwork('10.0.0.13/30'),\n", - " 'ipv4_subnet': IPNetwork('10.0.0.12/30'),\n", - " 'numeric_id': 1,\n", - " 'ospf_cost': 1,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True},\n", - " 3: {'description': 'to r5',\n", - " 'id': 'eth2',\n", - " 'id_brief': '',\n", - " 'ipv4_address': IPAddress('10.1.0.1'),\n", - " 'ipv4_cidr': IPNetwork('10.1.0.1/16'),\n", - " 'ipv4_subnet': IPNetwork('10.1.0.0/16'),\n", - " 'numeric_id': 2,\n", - " 'physical': True,\n", - " 'type': 'physical',\n", - " 'use_ipv4': True}},\n", - " 'asn': 1,\n", - " 'bgp': {'debug': True,\n", - " 'ebgp_neighbors': [{'asn': 2,\n", - " 'dst_int_ip': IPAddress('10.1.0.2'),\n", - " 'local_int_ip': IPAddress('10.1.0.1'),\n", - " 'loopback': IPAddress('192.168.0.9'),\n", - " 'neighbor': 'r5',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_neighbors': [{'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.1'),\n", - " 'neighbor': 'r1',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.2'),\n", - " 'neighbor': 'r2',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False},\n", - " {'asn': 1,\n", - " 'loopback': IPAddress('192.168.0.4'),\n", - " 'neighbor': 'r4',\n", - " 'update_source': None,\n", - " 'use_ipv4': True,\n", - " 'use_ipv6': False}],\n", - " 'ibgp_rr_clients': [],\n", - " 'ibgp_rr_parents': [],\n", - " 'ipv4_advertise_subnets': [IPNetwork('10.0.0.0/16')],\n", - " 'ipv6_advertise_subnets': []},\n", - " 'device_type': 'router',\n", - " 'host': 'localhost',\n", - " 'hostname': 'r3',\n", - " 'input_label': 'r3',\n", - " 'interfaces': [],\n", - " 'ip': {'use_ipv4': True, 'use_ipv6': False},\n", - " 'label': 'r3',\n", - " 'loopback': IPAddress('192.168.0.3'),\n", - " 'loopback_subnet': IPNetwork('192.168.0.3/32'),\n", - " 'mgmt_ip': IPAddress('172.16.0.5'),\n", - " 'ospf': [('ipv4_mpls_te', False), ('loopback_area', 0), ('process_id', 1), ('lo_interface', 'lo:1'), ('ospf_links', [[('network', IPNetwork('10.0.0.4/30')), ('area', None)], [('network', IPNetwork('10.0.0.12/30')), ('area', None)]]), ('passive_interfaces', [[('id', 'eth2')]])],\n", - " 'platform': 'netkit',\n", - " 'render': {'base': 'templates/quagga',\n", - " 'base_dst_folder': 'rendered/localhost/netkit/r3',\n", - " 'custom': {'abc': 'def.txt'},\n", - " 'dst_file': 'r3.conf',\n", - " 'dst_folder': 'rendered',\n", - " 'template': 'router.mako'},\n", - " 'ssh': {'use_key': True},\n", - " 'tap': {'id': 'eth3', 'ip': IPAddress('172.16.0.5')},\n", - " 'x': 162,\n", - " 'y': 270,\n", - " 'zebra': {'password': '1234', 'static_routes': []}}\n" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import autonetkit.render\n", - "autonetkit.render.render(nidb)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO Rendering Network\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "INFO:ANK:Rendering Network\n" - ] - } - ], - "prompt_number": 20 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "with open(\"rendered/lab.conf\") as fh:\n", - " print fh.read()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Topology rendered on 2014-01-13 00:58 by autonetkit_0.8.2\n", - "host: r4\n", - "host: r5\n", - "host: r1\n", - "host: r2\n", - "host: r3\n", - "\n" - ] - } - ], - "prompt_number": 21 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "with open(\"rendered/r1.conf\") as fh:\n", - " print fh.read()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Router rendered on 2014-01-13 00:58 by autonetkit_0.8.2\n", - "interface lo:1\n", - " description Loopback\n", - " ip address 192.168.0.1 netmask 255.255.255.255\n", - "interface eth0\n", - " description to r2\n", - " ip address 10.0.0.1 netmask 255.255.255.252\n", - "interface eth1\n", - " description to r3\n", - " ip address 10.0.0.5 netmask 255.255.255.252\n", - "!\n", - "router ospf 1\n", - " network 10.0.0.0/30 area None\n", - " network 10.0.0.4/30 area None\n", - "!\n", - "router bgp 1\n", - " ! r2\n", - " neighbor 192.168.0.2 remote-as 1\n", - " neighbor 192.168.0.2 update-source 192.168.0.1\n", - " neighbor 192.168.0.2 next-hop-self\n", - " ! r3\n", - " neighbor 192.168.0.3 remote-as 1\n", - " neighbor 192.168.0.3 update-source 192.168.0.1\n", - " neighbor 192.168.0.3 next-hop-self\n", - " ! r4\n", - " neighbor 192.168.0.4 remote-as 1\n", - " neighbor 192.168.0.4 update-source 192.168.0.1\n", - " neighbor 192.168.0.4 next-hop-self\n", - "!\n", - "! \n", - "\n" - ] - } - ], - "prompt_number": 22 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "with open(\"rendered/r5.conf\") as fh:\n", - " print fh.read()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Router rendered on 2014-01-13 00:58 by autonetkit_0.8.2\n", - "interface lo:1\n", - " description Loopback\n", - " ip address 192.168.0.9 netmask 255.255.255.255\n", - "interface eth0\n", - " description to r4\n", - " ip address 10.0.128.2 netmask 255.255.255.252\n", - "interface eth1\n", - " description to r3\n", - " ip address 10.1.0.2 netmask 255.255.0.0\n", - "!\n", - "router ospf 1\n", - "!\n", - "router bgp 2\n", - "!\n", - " ! r3\n", - " neighbor 10.1.0.1 remote-as 1\n", - " neighbor 10.1.0.1 update-source 10.1.0.2\n", - " ! r4\n", - " neighbor 10.0.128.1 remote-as 1\n", - " neighbor 10.0.128.1 update-source 10.0.128.2\n", - "! \n", - "\n" - ] - } - ], - "prompt_number": 23 - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/example/house.json b/example/house.json deleted file mode 100644 index 12058a89..00000000 --- a/example/house.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "directed": false, - "graph": [], - "links": [ - { - "dst": "r2", "dst_port": "eth0", - "src": "r1", "src_port": "eth0" - }, - { - "dst": "r3", "dst_port": "eth0", - "src": "r1", "src_port": "eth1" - }, - { - "dst": "r3", "dst_port": "eth1", - "src": "r2", "src_port": "eth1" - }, - { - "dst": "r2", "dst_port": "eth2", - "src": "r4", "src_port": "eth0" - }, - { - "dst": "r5", "dst_port": "eth0", - "src": "r4", "src_port": "eth1" - }, - { - "dst": "r3", "dst_port": "eth2", - "src": "r5", "src_port": "eth1" - } - ], - "multigraph": false, - "nodes": [ - { - "asn": 1, - "device_type": "router", - "id": "r1", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r1 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r1 to r3", - "id": "eth1" - } - ], - "x": 350, - "y": 400 - }, - { - "asn": 1, - "device_type": "router", - "id": "r2", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r2 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r2 to r3", - "id": "eth1" - }, - { - "category": "physical", - "description": "r2 to r4", - "id": "eth2" - } - ], - "x": 500, - "y": 300 - }, - { - "asn": 1, - "device_type": "router", - "id": "r3", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r3 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r3 to r2", - "id": "eth1" - }, - { - "category": "physical", - "description": "r3 to r5", - "id": "eth2" - } - ], - "x": 500, - "y": 500 - }, - { - "asn": 2, - "device_type": "router", - "id": "r4", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r4 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r4 to r5", - "id": "eth1" - } - ], - "x": 675, - "y": 300 - }, - { - "asn": 2, - "device_type": "router", - "id": "r5", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r5 to r4", - "id": "eth0" - }, - { - "category": "physical", - "description": "r5 to r3", - "id": "eth1" - } - ], - "x": 675, - "y": 500 - } - ] -} \ No newline at end of file diff --git a/example/small_internet.graphml b/example/small_internet.graphml deleted file mode 100644 index 45b756be..00000000 --- a/example/small_internet.graphml +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - 100 - - - - - - - as100r2 - - - - - - - - - 100 - - - - - - - as100r3 - - - - - - - - - 100 - - - - - - - as100r1 - - - - - - - - - 20 - - - - - - - as20r2 - - - - - - - - - 20 - - - - - - - as20r3 - - - - - - - - - 20 - - - - - - - as20r1 - - - - - - - - - 1 - - - - - - - as1r1 - - - - - - - - - 40 - - - - - - - as40r1 - - - - - - - - - 30 - - - - - - - as30r1 - - - - - - - - - 300 - - - - - - - as300r1 - - - - - - - - - 300 - - - - - - - as300r3 - - - - - - - - - 300 - - - - - - - as300r2 - - - - - - - - - 200 - - - - - - - as200r1 - - - - - - - - - 300 - - - - - - - as300r4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - version="1.1" - width="0.5in" - height="0.40000001in" - viewBox="-9 12772 637 437" - id="svg2" - inkscape:version="0.48.2 r9819" - sodipodi:docname="router_small.svg"> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1032" - id="namedview14" - showgrid="false" - inkscape:zoom="6.5555556" - inkscape:cx="22.5" - inkscape:cy="18" - inkscape:window-x="1876" - inkscape:window-y="68" - inkscape:window-maximized="0" - inkscape:current-layer="svg2" /> - <metadata - id="metadata26"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs24" /> - <g - transform="matrix(1.0485581,0,0,1.0485581,14.41976,-671.91828)" - id="g4" - style="fill:none;stroke-width:0.025in"> - <polyline - points="616,12903 616,12904 613,12910 608,12922 604,12934 601,12940 601,12941 600,12942 598,12944 592,12950 584,12956 573,12964 557,12973 544,12980 535,12984 530,12986 527,12987 525,12988 519,12990 508,12994 491,12999 473,13004 461,13007 455,13008 452,13009 448,13010 441,13011 428,13013 407,13016 385,13018 366,13020 350,13021 335,13022 323,13022 315,13022 311,13022 310,13022 309,13022 301,13021 284,13020 262,13019 239,13018 222,13017 214,13016 213,13016 212,13016 209,13015 201,13014 190,13013 178,13010 164,13008 148,13004 129,12999 112,12994 101,12990 95,12988 92,12987 90,12986 85,12984 75,12980 62,12973 47,12964 39,12960 36,12958 34,12956 29,12951 19,12941 11,12930 7,12921 4,12913 3,12907 3,12904 3,12903 3,12904 3,12908 3,12919 3,12937 3,12962 3,12990 3,13018 3,13043 3,13061 3,13072 3,13076 3,13077 3,13078 6,13084 11,13096 16,13108 19,13114 19,13115 19,13116 22,13119 28,13124 35,13131 46,13139 62,13148 75,13155 85,13159 90,13161 92,13162 95,13163 101,13165 112,13169 129,13174 147,13179 159,13182 165,13183 169,13184 172,13185 179,13186 192,13188 213,13191 235,13193 254,13195 270,13196 285,13197 297,13197 305,13197 309,13197 310,13197 311,13197 319,13196 336,13195 359,13194 381,13193 398,13192 406,13191 407,13191 408,13191 411,13190 419,13189 430,13188 442,13185 456,13183 473,13179 491,13174 508,13169 519,13165 525,13163 527,13162 530,13161 535,13159 544,13155 557,13148 572,13139 580,13134 583,13132 586,13131 591,13125 601,13115 609,13104 613,13095 615,13087 616,13081 616,13078 616,13077 616,13076 616,13072 616,13061 616,13043 616,13018 616,12990 616,12962 616,12937 616,12919 616,12908 616,12904 616,12903 " - style="fill:#026c9b;stroke:#026c9b;stroke-width:0;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline6" /> - <polyline - points="616,12903 616,12904 613,12910 608,12922 604,12934 601,12940 601,12941 600,12942 598,12944 592,12950 584,12956 573,12964 557,12973 544,12980 535,12984 530,12986 527,12987 525,12988 519,12990 508,12994 491,12999 473,13004 461,13007 455,13008 452,13009 448,13010 441,13011 428,13013 407,13016 385,13018 366,13020 350,13021 335,13022 323,13022 315,13022 311,13022 310,13022 309,13022 301,13021 284,13020 262,13019 239,13018 222,13017 214,13016 213,13016 212,13016 209,13015 201,13014 190,13013 178,13010 164,13008 148,13004 129,12999 112,12994 101,12990 95,12988 92,12987 90,12986 85,12984 75,12980 62,12973 47,12964 39,12960 36,12958 34,12956 29,12951 19,12941 11,12930 7,12921 4,12913 3,12907 3,12904 3,12903 3,12904 3,12908 3,12919 3,12937 3,12962 3,12990 3,13018 3,13043 3,13061 3,13072 3,13076 3,13077 3,13078 6,13084 11,13096 16,13108 19,13114 19,13115 19,13116 22,13119 28,13124 35,13131 46,13139 62,13148 75,13155 85,13159 90,13161 92,13162 95,13163 101,13165 112,13169 129,13174 147,13179 159,13182 165,13183 169,13184 172,13185 179,13186 192,13188 213,13191 235,13193 254,13195 270,13196 285,13197 297,13197 305,13197 309,13197 310,13197 311,13197 319,13196 336,13195 359,13194 381,13193 398,13192 406,13191 407,13191 408,13191 411,13190 419,13189 430,13188 442,13185 456,13183 473,13179 491,13174 508,13169 519,13165 525,13163 527,13162 530,13161 535,13159 544,13155 557,13148 572,13139 580,13134 583,13132 586,13131 591,13125 601,13115 609,13104 613,13095 615,13087 616,13081 616,13078 616,13077 616,13076 616,13072 616,13061 616,13043 616,13018 616,12990 616,12962 616,12937 616,12919 616,12908 616,12904 616,12903 " - style="stroke:#ffffff;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline8" /> - <polyline - points="310,13022 311,13022 319,13021 336,13020 359,13019 381,13018 398,13017 406,13016 407,13016 408,13016 411,13015 419,13014 430,13013 442,13010 456,13008 473,13004 491,12999 508,12994 519,12990 525,12988 527,12987 530,12986 535,12984 544,12980 557,12973 572,12964 580,12960 583,12958 586,12956 591,12951 601,12941 609,12930 613,12921 615,12913 616,12907 616,12904 616,12903 616,12902 613,12896 608,12884 604,12872 601,12866 601,12865 600,12864 598,12861 592,12856 584,12849 573,12841 557,12832 544,12825 535,12821 530,12819 527,12818 525,12817 519,12816 508,12812 491,12807 473,12802 461,12799 455,12798 452,12797 448,12796 441,12795 428,12793 407,12790 385,12788 366,12786 350,12785 335,12784 323,12784 315,12784 311,12784 310,12784 309,12784 301,12785 284,12786 262,12787 239,12788 222,12789 214,12790 213,12790 212,12790 209,12791 201,12792 190,12794 178,12796 164,12799 148,12802 129,12807 112,12812 101,12816 95,12817 92,12818 90,12819 85,12821 75,12825 62,12832 47,12841 39,12846 36,12847 34,12849 29,12855 19,12865 11,12876 7,12885 4,12893 3,12899 3,12902 3,12903 3,12904 6,12910 11,12922 16,12934 19,12940 19,12941 19,12942 22,12944 28,12950 35,12956 46,12964 62,12973 75,12980 85,12984 90,12986 92,12987 95,12988 101,12990 112,12994 129,12999 147,13004 159,13007 165,13008 169,13009 172,13010 179,13011 192,13013 213,13016 235,13018 254,13020 270,13021 285,13022 297,13022 305,13022 309,13022 310,13022 " - style="fill:#026c9b;stroke:#026c9b;stroke-width:0;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline10" /> - <polyline - points="310,13022 311,13022 319,13021 336,13020 359,13019 381,13018 398,13017 406,13016 407,13016 408,13016 411,13015 419,13014 430,13013 442,13010 456,13008 473,13004 491,12999 508,12994 519,12990 525,12988 527,12987 530,12986 535,12984 544,12980 557,12973 572,12964 580,12960 583,12958 586,12956 591,12951 601,12941 609,12930 613,12921 615,12913 616,12907 616,12904 616,12903 616,12902 613,12896 608,12884 604,12872 601,12866 601,12865 600,12864 598,12861 592,12856 584,12849 573,12841 557,12832 544,12825 535,12821 530,12819 527,12818 525,12817 519,12816 508,12812 491,12807 473,12802 461,12799 455,12798 452,12797 448,12796 441,12795 428,12793 407,12790 385,12788 366,12786 350,12785 335,12784 323,12784 315,12784 311,12784 310,12784 309,12784 301,12785 284,12786 262,12787 239,12788 222,12789 214,12790 213,12790 212,12790 209,12791 201,12792 190,12794 178,12796 164,12799 148,12802 129,12807 112,12812 101,12816 95,12817 92,12818 90,12819 85,12821 75,12825 62,12832 47,12841 39,12846 36,12847 34,12849 29,12855 19,12865 11,12876 7,12885 4,12893 3,12899 3,12902 3,12903 3,12904 6,12910 11,12922 16,12934 19,12940 19,12941 19,12942 22,12944 28,12950 35,12956 46,12964 62,12973 75,12980 85,12984 90,12986 92,12987 95,12988 101,12990 112,12994 129,12999 147,13004 159,13007 165,13008 169,13009 172,13010 179,13011 192,13013 213,13016 235,13018 254,13020 270,13021 285,13022 297,13022 305,13022 309,13022 310,13022 " - style="stroke:#ffffff;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline12" /> - <polyline - points="240,12856 265,12894 169,12916 190,12899 43,12874 80,12846 222,12870 240,12856 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline14" /> - <polyline - points="375,12948 357,12909 444,12892 429,12905 573,12930 538,12958 395,12931 375,12948 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline16" /> - <polyline - points="327,12836 424,12809 425,12851 401,12846 354,12885 309,12878 357,12840 327,12836 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline18" /> - <polyline - points="286,12984 194,13001 190,12959 217,12964 268,12921 312,12929 258,12976 286,12984 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline20" /> - </g> -</svg> - - - - diff --git a/house.json b/house.json deleted file mode 100644 index e92b1534..00000000 --- a/house.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "directed": false, - "graph": [], - "links": [ - { - "dst": "r2", - "dst_port": "eth0", - "src": "r1", - "src_port": "eth0" - }, - { - "dst": "r3", - "dst_port": "eth0", - "src": "r1", - "src_port": "eth1" - }, - { - "dst": "r3", - "dst_port": "eth1", - "src": "r2", - "src_port": "eth1" - }, - { - "dst": "r2", - "dst_port": "eth2", - "src": "r4", - "src_port": "eth0" - }, - { - "dst": "r5", - "dst_port": "eth0", - "src": "r4", - "src_port": "eth1" - }, - { - "dst": "r3", - "dst_port": "eth2", - "src": "r5", - "src_port": "eth1" - } - ], - "multigraph": false, - "nodes": [ - { - "asn": 1, - "device_type": "router", - "id": "r1", - "label": "r1", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r1 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r1 to r3", - "id": "eth1" - } - ], - "x": 350, - "y": 400 - }, - { - "asn": 1, - "device_type": "router", - "id": "r2", - "label": "r2", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r2 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r2 to r3", - "id": "eth1" - }, - { - "category": "physical", - "description": "r2 to r4", - "id": "eth2" - } - ], - "x": 500, - "y": 300 - }, - { - "asn": 1, - "device_type": "router", - "id": "r3", - "label": "r3", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r3 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r3 to r2", - "id": "eth1" - }, - { - "category": "physical", - "description": "r3 to r5", - "id": "eth2" - } - ], - "x": 500, - "y": 500 - }, - { - "asn": 2, - "device_type": "router", - "id": "r4", - "label": "r4", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r4 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r4 to r5", - "id": "eth1" - } - ], - "x": 675, - "y": 300 - }, - { - "asn": 2, - "device_type": "router", - "id": "r5", - "label": "r5", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r5 to r4", - "id": "eth0" - }, - { - "category": "physical", - "description": "r5 to r3", - "id": "eth1" - } - ], - "x": 675, - "y": 500 - } - ] -} \ No newline at end of file diff --git a/junit-py27.xml b/junit-py27.xml deleted file mode 100644 index ec49b4c2..00000000 --- a/junit-py27.xml +++ /dev/null @@ -1,28 +0,0 @@ - \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..6a7d1f6a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +setuptools~=53.0.0 +networkx~=2.5 +pip~=21.0.1 +Jinja2~=2.11.3 +aiohttp~=3.7.3 +requests~=2.25.1 \ No newline at end of file diff --git a/setup.py b/setup.py index b173e82c..29cf02af 100644 --- a/setup.py +++ b/setup.py @@ -1,59 +1,47 @@ #!/usr/bin/env python from setuptools import setup, find_packages -import sys - -setup ( - name = "autonetkit", - version = "0.11.0", - description = 'Automatic configuration generation for emulated networks', - long_description = 'Automatic configuration generation for emulated networks', - - entry_points = { - 'console_scripts': [ - 'autonetkit = autonetkit.console_script:console_entry', - 'ank_webserver = autonetkit.webserver:main', - 'ank_collect_server = autonetkit.collection.server:main', - ], - }, - - author = 'Simon Knight', - author_email = "simon.knight@gmail.com", - url = "http://www.autonetkit.org", - #packages = ['autonetkit', 'autonetkit.deploy', 'autonetkit.load', 'autonetkit.plugins'], - - packages=find_packages(exclude=('tests', 'docs')), - - include_package_data = True, # include data from MANIFEST.in - - #package_data = {'': ['settings.cfg', 'config/configspec.cfg', ]}, - download_url = ("http://pypi.python.org/pypi/autonetkit"), - - install_requires = [ - 'netaddr>=0.7.10', - 'mako==0.8', - 'networkx==1.7', - 'configobj==4.7.1', - 'tornado>=3.0.1', - #'textfsm', 'pika', - # 'exscript==0.0.1' - ], - - #Note: exscript disabled in default install: requires pycrypto which requires compilation (can cause installation issues) - #dependency_links = [ 'https://github.com/knipknap/exscript/tarball/master#egg=exscript-0.0.1',], - - classifiers = [ - "Programming Language :: Python", - "Development Status :: 3 - Alpha", - "Intended Audience :: Science/Research", - "Intended Audience :: System Administrators", - "Intended Audience :: Telecommunications Industry", - "License :: OSI Approved :: BSD License", - "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX :: Linux", - "Topic :: System :: Networking", - "Topic :: System :: Software Distribution", - "Topic :: Scientific/Engineering :: Mathematics", - ], +setup( + name="autonetkit", + version="1.0.0", + description='Automatic configuration generation', + long_description='Automatic configuration generation', + + entry_points={ + 'console_scripts': [ + 'autonetkit = autonetkit.console_script:console_entry', + 'ank_webserver = autonetkit.webserver:main', + 'ank_collect_server = autonetkit.collection.server:main', + ], + }, + + author='Simon Knight', + author_email="simon.knight@gmail.com", + url="http://www.autonetkit.org", + + packages=find_packages(exclude=('tests', 'docs')), + + include_package_data=True, # include data from MANIFEST.in + + download_url=("http://pypi.python.org/pypi/autonetkit"), + + install_requires=[ + 'netaddr', + 'networkx', + ], + + classifiers=[ + "Programming Language :: Python", + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: BSD License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX :: Linux", + "Topic :: System :: Networking", + "Topic :: System :: Software Distribution", + "Topic :: Scientific/Engineering :: Mathematics", + ], ) diff --git a/tests/Aarnet.graphml b/tests/Aarnet.graphml deleted file mode 100644 index eae22a33..00000000 --- a/tests/Aarnet.graphml +++ /dev/null @@ -1,317 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2/12/10 - Australia - Country - AARNET - Primary - 0 - http://www.aarnet.edu.au/aarnet3.aspx - 1.0 - REN - Current - 1 - 0 - Aarnet - 0.3.34dev-20120328 - 0 - 0 - e278b1b - = - 08 - 3/08/10 - IP - Topology Zoo Toolset - 1 - 0 - 2010_08 - 2010 - 2011_09_01 - 0 - - 1 - -33.86785 - Australia - 0 - 151.20732 - Sydney1 - - - 1 - -27.46794 - Australia - 1 - 153.02809 - Brisbane2 - - - 1 - -35.28346 - Australia - 2 - 149.12807 - Canberra1 - - - 1 - -33.86785 - Australia - 3 - 151.20732 - Sydney2 - - - 1 - -19.25 - Australia - 4 - 146.8 - Townsville - - - 1 - -16.91667 - Australia - 5 - 145.76667 - Cairns - - - 1 - -27.46794 - Australia - 6 - 153.02809 - Brisbane1 - - - 1 - -23.38333 - Australia - 7 - 150.5 - Rockhampton - - - 1 - -30.51667 - Australia - 8 - 151.65 - Armidale - - - 1 - -42.87936 - Australia - 9 - 147.32941 - Hobart - - - 1 - -35.28346 - Australia - 10 - 149.12807 - Canberra2 - - - 1 - -31.93333 - Australia - 11 - 115.83333 - Perth1 - - - 1 - -31.93333 - Australia - 12 - 115.83333 - Perth2 - - - 1 - -34.93333 - Australia - 13 - 138.6 - Adelaide1 - - - 1 - -34.93333 - Australia - 14 - 138.6 - Adelaide2 - - - 1 - -37.814 - Australia - 15 - 144.96332 - Melbourne1 - - - 1 - -37.814 - Australia - 16 - 144.96332 - Melbourne2 - - - 1 - -23.7 - Australia - 17 - 133.88333 - Alice Springs - - - 1 - -12.46113 - Australia - 18 - 130.84185 - Darwin - - - < 10 Gbps - 0 - - - < 10 Gbps - 0 - - - < 10 Gbps - 0 - - - < 10 Gbps - 0 - - - < 10Gbps - 0 - - - < 10 Gbps - 0 - - - < 10 Gbps - 0 - - - < 1 Gbps - 0 - - - < 10 Gbps - 0 - - - < 2.5 Gbps - 0 - - - < 2.5 Gbps - 0 - - - < 2.5 Gbps - 0 - - - < 155 Mbps - 0 - - - < 155 Mbps - 0 - - - e0 - 0 - - - < 10 Gbps - 0 - - - < 2.5 Gbps - 0 - - - < 155 Mbps - 0 - - - e3 - 0 - - - < 10 Gbps - 0 - - - < 2.5 Gbps - 0 - - - < 155 Mbps - 0 - - - < 10 Gbps - 0 - - - < 155 Mbps - 0 - - - diff --git a/tests/asn_zero.graphml b/tests/asn_zero.graphml deleted file mode 100644 index fb9bb719..00000000 --- a/tests/asn_zero.graphml +++ /dev/null @@ -1,826 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - r1 - - - - - - - - - - - - - - r2 - - - - - - - - - - - - - - r3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" -"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Creator: fig2dev Version 3.2 Patchlevel 5d --> -<!-- CreationDate: Thu Jul 19 14:02:38 2012 --> -<!-- Magnification: 1.000 --> -<svg xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - width="0.5in" height="0.4in" - viewBox="-9 12772 637 437"> -<g style="stroke-width:.025in; fill:none"> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="240,12856 -265,12894 -169,12916 -190,12899 -43,12874 -80,12846 -222,12870 -240,12856 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="375,12948 -357,12909 -444,12892 -429,12905 -573,12930 -538,12958 -395,12931 -375,12948 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="327,12836 -424,12809 -425,12851 -401,12846 -354,12885 -309,12878 -357,12840 -327,12836 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="286,12984 -194,13001 -190,12959 -217,12964 -268,12921 -312,12929 -258,12976 -286,12984 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -</g> -</svg> - - - - diff --git a/tests/bgp_pol/policy_0.txt b/tests/bgp_pol/policy_0.txt deleted file mode 100755 index 1406c445..00000000 --- a/tests/bgp_pol/policy_0.txt +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "then": [ - "reject", - "setLP 100" - ], - "match": [ - "tags contain aaa", - "prefix_list zyx", - "tags contain bbb" - ], - "else": null - } -] \ No newline at end of file diff --git a/tests/bgp_pol/policy_1.txt b/tests/bgp_pol/policy_1.txt deleted file mode 100755 index 4e217010..00000000 --- a/tests/bgp_pol/policy_1.txt +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "then": [ - "reject", - "setLP 100" - ], - "match": [ - "tags contain aaa", - "prefix_list zyx", - "tags contain bbb" - ], - "else": [ - "setLP 200" - ] - } -] \ No newline at end of file diff --git a/tests/bgp_pol/policy_2.txt b/tests/bgp_pol/policy_2.txt deleted file mode 100755 index f7a450fd..00000000 --- a/tests/bgp_pol/policy_2.txt +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "then": [ - "reject", - "setLP 100" - ], - "match": [ - "prefix_list ccc" - ], - "else": [ - "reject", - "setMED 240", - "setLP 210" - ] - } -] \ No newline at end of file diff --git a/tests/bgp_pol/policy_3.txt b/tests/bgp_pol/policy_3.txt deleted file mode 100755 index 2ee58382..00000000 --- a/tests/bgp_pol/policy_3.txt +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "then": [ - "reject", - "setLP 100" - ], - "match": [ - "prefix_list ccc" - ], - "else": [ - { - "then": [ - "setLP 120" - ], - "match": [ - "prefix_list ccc" - ], - "else": [ - "setMED 230" - ] - } - ] - } -] \ No newline at end of file diff --git a/tests/bgp_pol/policy_4.txt b/tests/bgp_pol/policy_4.txt deleted file mode 100755 index 32037882..00000000 --- a/tests/bgp_pol/policy_4.txt +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "then": [ - "setLP 120" - ], - "match": [ - "prefix_list ccc" - ], - "else": [ - "setMED 230" - ] - } -] \ No newline at end of file diff --git a/tests/bgp_pol/policy_5.txt b/tests/bgp_pol/policy_5.txt deleted file mode 100755 index e9986375..00000000 --- a/tests/bgp_pol/policy_5.txt +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "then": [ - "reject", - "setLP 200" - ], - "match": null, - "else": null - } -] \ No newline at end of file diff --git a/tests/bgp_pol/test_bgp_pol.py b/tests/bgp_pol/test_bgp_pol.py deleted file mode 100755 index 1ecf4898..00000000 --- a/tests/bgp_pol/test_bgp_pol.py +++ /dev/null @@ -1,22 +0,0 @@ -from autonetkit.plugins.bgp_pol import pol_to_json -import os - -policies = [ -"if (tags contain aaa and prefix_list is zyx and tags contain bbb) then (reject and setLP 100)", -"if (tags contain aaa and prefix_list is zyx and tags contain bbb) then (reject and setLP 100) else (setLP 200)", -"if (prefix_list is ccc) then (reject and setLP 100) else (reject and setMED 240 and setLP 210)", -("if (prefix_list is ccc) then (reject and setLP 100) else " - "(if (prefix_list is ccc) then (setLP 120) else (setMED 230))"), -"if (prefix_list is ccc) then (setLP 120) else (setMED 230))", -"reject and setLP 200", -] - -for index, policy in enumerate(policies): - result = pol_to_json(policy) - dirname, filename = os.path.split(os.path.abspath(__file__)) - policy_filename = os.path.join(dirname, "policy_%s.txt" % index ) - - with open(policy_filename, "r") as fh: - expected = fh.read() - - assert(result == expected) diff --git a/tests/big.graphml b/tests/big.graphml deleted file mode 100644 index 7abda270..00000000 --- a/tests/big.graphml +++ /dev/null @@ -1,2648 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - 3 - - - - - - - - - - - - - - - - - 4 - - - - - - - - - - - - - - - - - 5 - - - - - - - - - - - - - - - - - 6 - - - - - - - - - - - - - - - - - 7 - - - - - - - - - - - - 4 - - - - - - 8 - - - - - - - - - - - - - - - - - 9 - - - - - - - - - - - - 4 - - - - - - 10 - - - - - - - - - - - - 4 - - - - - - 11 - - - - - - - - - - - - 4 - - - - - - 12 - - - - - - - - - - - - 5 - - - - - - 13 - - - - - - - - - - - - - - - - - 14 - - - - - - - - - - - - 4 - - - - - - 15 - - - - - - - - - - - - 4 - - - - - - 16 - - - - - - - - - - - - 4 - - - - - - 17 - - - - - - - - - - - - 5 - - - - - - 18 - - - - - - - - - - - - - - - - - 19 - - - - - - - - - - - - - - - - - 20 - - - - - - - - - - - - 2 - - - - - - 21 - - - - - - - - - - - - 4 - - - - - - 22 - - - - - - - - - - - - 3 - - - - - - 23 - - - - - - - - - - - - 2 - - - - - - 24 - - - - - - - - - - - - 5 - - - - - - 25 - - - - - - - - - - - - 4 - - - - - - 26 - - - - - - - - - - - - 2 - - - - - - 27 - - - - - - - - - - - - - - - - - 28 - - - - - - - - - - - - 5 - - - - - - 29 - - - - - - - - - - - - - - - - - 30 - - - - - - - - - - - - 4 - - - - - - 31 - - - - - - - - - - - - 3 - - - - - - 32 - - - - - - - - - - - - 3 - - - - - - 33 - - - - - - - - - - - - 2 - - - - - - 34 - - - - - - - - - - - - 5 - - - - - - 35 - - - - - - - - - - - - - - - - - 36 - - - - - - - - - - - - 5 - - - - - - 37 - - - - - - - - - - - - - - - - - 38 - - - - - - - - - - - - - - - - - 39 - - - - - - - - - - - - - - - - - 40 - - - - - - - - - - - - - - - - - 41 - - - - - - - - - - - - 3 - - - - - - 42 - - - - - - - - - - - - 4 - - - - - - 43 - - - - - - - - - - - - 3 - - - - - - 44 - - - - - - - - - - - - 3 - - - - - - 45 - - - - - - - - - - - - 2 - - - - - - 46 - - - - - - - - - - - - 2 - - - - - - 47 - - - - - - - - - - - - 4 - - - - - - 48 - - - - - - - - - - - - 4 - - - - - - 49 - - - - - - - - - - - - 5 - - - - - - 50 - - - - - - - - - - - - 2 - - - - - - 51 - - - - - - - - - - - - 2 - - - - - - 52 - - - - - - - - - - - - 4 - - - - - - 53 - - - - - - - - - - - - - - - - - 54 - - - - - - - - - - - - - - - - - 55 - - - - - - - - - - - - 5 - - - - - - 56 - - - - - - - - - - - - 2 - - - - - - 57 - - - - - - - - - - - - - - - - - 58 - - - - - - - - - - - - 5 - - - - - - 59 - - - - - - - - - - - - 5 - - - - - - 60 - - - - - - - - - - - - 4 - - - - - - 61 - - - - - - - - - - - - 5 - - - - - - 62 - - - - - - - - - - - - 2 - - - - - - 63 - - - - - - - - - - - - 3 - - - - - - 64 - - - - - - - - - - - - 5 - - - - - - 65 - - - - - - - - - - - - - - - - - 66 - - - - - - - - - - - - - - - - - 67 - - - - - - - - - - - - 4 - - - - - - 68 - - - - - - - - - - - - 4 - - - - - - 69 - - - - - - - - - - - - 5 - - - - - - 70 - - - - - - - - - - - - 3 - - - - - - 71 - - - - - - - - - - - - 2 - - - - - - 72 - - - - - - - - - - - - 3 - - - - - - 73 - - - - - - - - - - - - 5 - - - - - - 74 - - - - - - - - - - - - 4 - - - - - - 75 - - - - - - - - - - - - - - - - - 76 - - - - - - - - - - - - 2 - - - - - - 77 - - - - - - - - - - - - 3 - - - - - - 78 - - - - - - - - - - - - 4 - - - - - - 79 - - - - - - - - - - - - - - - - - 80 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/bigger.graphml b/tests/bigger.graphml deleted file mode 100644 index 898dbb58..00000000 --- a/tests/bigger.graphml +++ /dev/null @@ -1,28925 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - 12 - - - - - - - 1 - - - - - - - - - - - - 4 - - - - - - - 2 - - - - - - - - - - - - 9 - - - - - - - 3 - - - - - - - - - - - - 7 - - - - - - - 4 - - - - - - - - - - - - 2 - - - - - - - 5 - - - - - - - - - - - - 2 - - - - - - - 6 - - - - - - - - - - - - 2 - - - - - - - 7 - - - - - - - - - - - - - - - - - - 8 - - - - - - - - - - - - 2 - - - - - - - 9 - - - - - - - - - - - - 11 - - - - - - - 10 - - - - - - - - - - - - 6 - - - - - - - 11 - - - - - - - - - - - - 14 - - - - - - - 12 - - - - - - - - - - - - 3 - - - - - - - 13 - - - - - - - - - - - - 6 - - - - - - - 14 - - - - - - - - - - - - 2 - - - - - - - 15 - - - - - - - - - - - - 11 - - - - - - - 16 - - - - - - - - - - - - 14 - - - - - - - 17 - - - - - - - - - - - - 6 - - - - - - - 18 - - - - - - - - - - - - 3 - - - - - - - 19 - - - - - - - - - - - - 2 - - - - - - - 20 - - - - - - - - - - - - 10 - - - - - - - 21 - - - - - - - - - - - - - - - - - - 22 - - - - - - - - - - - - - - - - - - 23 - - - - - - - - - - - - 2 - - - - - - - 24 - - - - - - - - - - - - 14 - - - - - - - 25 - - - - - - - - - - - - 3 - - - - - - - 26 - - - - - - - - - - - - 6 - - - - - - - 27 - - - - - - - - - - - - 3 - - - - - - - 28 - - - - - - - - - - - - 2 - - - - - - - 29 - - - - - - - - - - - - 3 - - - - - - - 30 - - - - - - - - - - - - 9 - - - - - - - 31 - - - - - - - - - - - - - - - - - - 32 - - - - - - - - - - - - 11 - - - - - - - 33 - - - - - - - - - - - - 13 - - - - - - - 34 - - - - - - - - - - - - 9 - - - - - - - 35 - - - - - - - - - - - - 3 - - - - - - - 36 - - - - - - - - - - - - 10 - - - - - - - 37 - - - - - - - - - - - - 2 - - - - - - - 38 - - - - - - - - - - - - - - - - - - 39 - - - - - - - - - - - - 12 - - - - - - - 40 - - - - - - - - - - - - 2 - - - - - - - 41 - - - - - - - - - - - - 14 - - - - - - - 42 - - - - - - - - - - - - 10 - - - - - - - 43 - - - - - - - - - - - - 9 - - - - - - - 44 - - - - - - - - - - - - 10 - - - - - - - 45 - - - - - - - - - - - - 11 - - - - - - - 46 - - - - - - - - - - - - - - - - - - 47 - - - - - - - - - - - - 10 - - - - - - - 48 - - - - - - - - - - - - 7 - - - - - - - 49 - - - - - - - - - - - - 10 - - - - - - - 50 - - - - - - - - - - - - 6 - - - - - - - 51 - - - - - - - - - - - - 8 - - - - - - - 52 - - - - - - - - - - - - 13 - - - - - - - 53 - - - - - - - - - - - - 2 - - - - - - - 54 - - - - - - - - - - - - 2 - - - - - - - 55 - - - - - - - - - - - - 2 - - - - - - - 56 - - - - - - - - - - - - 9 - - - - - - - 57 - - - - - - - - - - - - 9 - - - - - - - 58 - - - - - - - - - - - - 2 - - - - - - - 59 - - - - - - - - - - - - 7 - - - - - - - 60 - - - - - - - - - - - - 14 - - - - - - - 61 - - - - - - - - - - - - 6 - - - - - - - 62 - - - - - - - - - - - - 10 - - - - - - - 63 - - - - - - - - - - - - 6 - - - - - - - 64 - - - - - - - - - - - - 11 - - - - - - - 65 - - - - - - - - - - - - 10 - - - - - - - 66 - - - - - - - - - - - - 11 - - - - - - - 67 - - - - - - - - - - - - 5 - - - - - - - 68 - - - - - - - - - - - - 5 - - - - - - - 69 - - - - - - - - - - - - 4 - - - - - - - 70 - - - - - - - - - - - - 8 - - - - - - - 71 - - - - - - - - - - - - 9 - - - - - - - 72 - - - - - - - - - - - - 3 - - - - - - - 73 - - - - - - - - - - - - - - - - - - 74 - - - - - - - - - - - - 3 - - - - - - - 75 - - - - - - - - - - - - 10 - - - - - - - 76 - - - - - - - - - - - - 9 - - - - - - - 77 - - - - - - - - - - - - 2 - - - - - - - 78 - - - - - - - - - - - - 10 - - - - - - - 79 - - - - - - - - - - - - 9 - - - - - - - 80 - - - - - - - - - - - - - - - - - - 81 - - - - - - - - - - - - 13 - - - - - - - 82 - - - - - - - - - - - - 5 - - - - - - - 83 - - - - - - - - - - - - 9 - - - - - - - 84 - - - - - - - - - - - - 11 - - - - - - - 85 - - - - - - - - - - - - 2 - - - - - - - 86 - - - - - - - - - - - - 12 - - - - - - - 87 - - - - - - - - - - - - 5 - - - - - - - 88 - - - - - - - - - - - - 13 - - - - - - - 89 - - - - - - - - - - - - 4 - - - - - - - 90 - - - - - - - - - - - - - - - - - - 91 - - - - - - - - - - - - 7 - - - - - - - 92 - - - - - - - - - - - - 2 - - - - - - - 93 - - - - - - - - - - - - 8 - - - - - - - 94 - - - - - - - - - - - - 13 - - - - - - - 95 - - - - - - - - - - - - 7 - - - - - - - 96 - - - - - - - - - - - - 3 - - - - - - - 97 - - - - - - - - - - - - 2 - - - - - - - 98 - - - - - - - - - - - - 12 - - - - - - - 99 - - - - - - - - - - - - 5 - - - - - - - 100 - - - - - - - - - - - - 10 - - - - - - - 101 - - - - - - - - - - - - - - - - - - 102 - - - - - - - - - - - - 10 - - - - - - - 103 - - - - - - - - - - - - - - - - - - 104 - - - - - - - - - - - - 6 - - - - - - - 105 - - - - - - - - - - - - 2 - - - - - - - 106 - - - - - - - - - - - - 5 - - - - - - - 107 - - - - - - - - - - - - 8 - - - - - - - 108 - - - - - - - - - - - - 11 - - - - - - - 109 - - - - - - - - - - - - 6 - - - - - - - 110 - - - - - - - - - - - - 11 - - - - - - - 111 - - - - - - - - - - - - 7 - - - - - - - 112 - - - - - - - - - - - - 14 - - - - - - - 113 - - - - - - - - - - - - - - - - - - 114 - - - - - - - - - - - - 6 - - - - - - - 115 - - - - - - - - - - - - 6 - - - - - - - 116 - - - - - - - - - - - - 2 - - - - - - - 117 - - - - - - - - - - - - 5 - - - - - - - 118 - - - - - - - - - - - - 2 - - - - - - - 119 - - - - - - - - - - - - 10 - - - - - - - 120 - - - - - - - - - - - - 8 - - - - - - - 121 - - - - - - - - - - - - 9 - - - - - - - 122 - - - - - - - - - - - - 7 - - - - - - - 123 - - - - - - - - - - - - 3 - - - - - - - 124 - - - - - - - - - - - - 13 - - - - - - - 125 - - - - - - - - - - - - 12 - - - - - - - 126 - - - - - - - - - - - - 11 - - - - - - - 127 - - - - - - - - - - - - 10 - - - - - - - 128 - - - - - - - - - - - - 11 - - - - - - - 129 - - - - - - - - - - - - 11 - - - - - - - 130 - - - - - - - - - - - - 2 - - - - - - - 131 - - - - - - - - - - - - 3 - - - - - - - 132 - - - - - - - - - - - - 13 - - - - - - - 133 - - - - - - - - - - - - 8 - - - - - - - 134 - - - - - - - - - - - - 6 - - - - - - - 135 - - - - - - - - - - - - 2 - - - - - - - 136 - - - - - - - - - - - - 12 - - - - - - - 137 - - - - - - - - - - - - 2 - - - - - - - 138 - - - - - - - - - - - - 2 - - - - - - - 139 - - - - - - - - - - - - 4 - - - - - - - 140 - - - - - - - - - - - - 5 - - - - - - - 141 - - - - - - - - - - - - 2 - - - - - - - 142 - - - - - - - - - - - - 10 - - - - - - - 143 - - - - - - - - - - - - 8 - - - - - - - 144 - - - - - - - - - - - - 7 - - - - - - - 145 - - - - - - - - - - - - 14 - - - - - - - 146 - - - - - - - - - - - - - - - - - - 147 - - - - - - - - - - - - 7 - - - - - - - 148 - - - - - - - - - - - - 7 - - - - - - - 149 - - - - - - - - - - - - 11 - - - - - - - 150 - - - - - - - - - - - - 2 - - - - - - - 151 - - - - - - - - - - - - 2 - - - - - - - 152 - - - - - - - - - - - - 11 - - - - - - - 153 - - - - - - - - - - - - 7 - - - - - - - 154 - - - - - - - - - - - - 5 - - - - - - - 155 - - - - - - - - - - - - 11 - - - - - - - 156 - - - - - - - - - - - - 6 - - - - - - - 157 - - - - - - - - - - - - 9 - - - - - - - 158 - - - - - - - - - - - - 9 - - - - - - - 159 - - - - - - - - - - - - 2 - - - - - - - 160 - - - - - - - - - - - - 7 - - - - - - - 161 - - - - - - - - - - - - 5 - - - - - - - 162 - - - - - - - - - - - - 14 - - - - - - - 163 - - - - - - - - - - - - 8 - - - - - - - 164 - - - - - - - - - - - - 11 - - - - - - - 165 - - - - - - - - - - - - 2 - - - - - - - 166 - - - - - - - - - - - - 3 - - - - - - - 167 - - - - - - - - - - - - 10 - - - - - - - 168 - - - - - - - - - - - - 6 - - - - - - - 169 - - - - - - - - - - - - 2 - - - - - - - 170 - - - - - - - - - - - - 11 - - - - - - - 171 - - - - - - - - - - - - 10 - - - - - - - 172 - - - - - - - - - - - - 9 - - - - - - - 173 - - - - - - - - - - - - 6 - - - - - - - 174 - - - - - - - - - - - - 13 - - - - - - - 175 - - - - - - - - - - - - 5 - - - - - - - 176 - - - - - - - - - - - - 2 - - - - - - - 177 - - - - - - - - - - - - 6 - - - - - - - 178 - - - - - - - - - - - - 14 - - - - - - - 179 - - - - - - - - - - - - 12 - - - - - - - 180 - - - - - - - - - - - - - - - - - - 181 - - - - - - - - - - - - 7 - - - - - - - 182 - - - - - - - - - - - - 3 - - - - - - - 183 - - - - - - - - - - - - 14 - - - - - - - 184 - - - - - - - - - - - - 3 - - - - - - - 185 - - - - - - - - - - - - - - - - - - 186 - - - - - - - - - - - - - - - - - - 187 - - - - - - - - - - - - 12 - - - - - - - 188 - - - - - - - - - - - - 10 - - - - - - - 189 - - - - - - - - - - - - 4 - - - - - - - 190 - - - - - - - - - - - - 10 - - - - - - - 191 - - - - - - - - - - - - 12 - - - - - - - 192 - - - - - - - - - - - - 6 - - - - - - - 193 - - - - - - - - - - - - 11 - - - - - - - 194 - - - - - - - - - - - - 2 - - - - - - - 195 - - - - - - - - - - - - 8 - - - - - - - 196 - - - - - - - - - - - - 10 - - - - - - - 197 - - - - - - - - - - - - 12 - - - - - - - 198 - - - - - - - - - - - - 4 - - - - - - - 199 - - - - - - - - - - - - 8 - - - - - - - 200 - - - - - - - - - - - - 8 - - - - - - - 201 - - - - - - - - - - - - 5 - - - - - - - 202 - - - - - - - - - - - - 7 - - - - - - - 203 - - - - - - - - - - - - 10 - - - - - - - 204 - - - - - - - - - - - - 2 - - - - - - - 205 - - - - - - - - - - - - - - - - - - 206 - - - - - - - - - - - - - - - - - - 207 - - - - - - - - - - - - 12 - - - - - - - 208 - - - - - - - - - - - - 12 - - - - - - - 209 - - - - - - - - - - - - 10 - - - - - - - 210 - - - - - - - - - - - - 11 - - - - - - - 211 - - - - - - - - - - - - 7 - - - - - - - 212 - - - - - - - - - - - - 2 - - - - - - - 213 - - - - - - - - - - - - 3 - - - - - - - 214 - - - - - - - - - - - - 2 - - - - - - - 215 - - - - - - - - - - - - 10 - - - - - - - 216 - - - - - - - - - - - - 2 - - - - - - - 217 - - - - - - - - - - - - 7 - - - - - - - 218 - - - - - - - - - - - - 2 - - - - - - - 219 - - - - - - - - - - - - 3 - - - - - - - 220 - - - - - - - - - - - - 3 - - - - - - - 221 - - - - - - - - - - - - 5 - - - - - - - 222 - - - - - - - - - - - - - - - - - - 223 - - - - - - - - - - - - 4 - - - - - - - 224 - - - - - - - - - - - - 8 - - - - - - - 225 - - - - - - - - - - - - 7 - - - - - - - 226 - - - - - - - - - - - - 13 - - - - - - - 227 - - - - - - - - - - - - 10 - - - - - - - 228 - - - - - - - - - - - - 2 - - - - - - - 229 - - - - - - - - - - - - 4 - - - - - - - 230 - - - - - - - - - - - - 12 - - - - - - - 231 - - - - - - - - - - - - 10 - - - - - - - 232 - - - - - - - - - - - - 3 - - - - - - - 233 - - - - - - - - - - - - 2 - - - - - - - 234 - - - - - - - - - - - - 2 - - - - - - - 235 - - - - - - - - - - - - 14 - - - - - - - 236 - - - - - - - - - - - - 7 - - - - - - - 237 - - - - - - - - - - - - 13 - - - - - - - 238 - - - - - - - - - - - - 9 - - - - - - - 239 - - - - - - - - - - - - 8 - - - - - - - 240 - - - - - - - - - - - - 10 - - - - - - - 241 - - - - - - - - - - - - 11 - - - - - - - 242 - - - - - - - - - - - - 9 - - - - - - - 243 - - - - - - - - - - - - 13 - - - - - - - 244 - - - - - - - - - - - - 10 - - - - - - - 245 - - - - - - - - - - - - 11 - - - - - - - 246 - - - - - - - - - - - - 8 - - - - - - - 247 - - - - - - - - - - - - 12 - - - - - - - 248 - - - - - - - - - - - - 10 - - - - - - - 249 - - - - - - - - - - - - 10 - - - - - - - 250 - - - - - - - - - - - - 13 - - - - - - - 251 - - - - - - - - - - - - 11 - - - - - - - 252 - - - - - - - - - - - - 10 - - - - - - - 253 - - - - - - - - - - - - 11 - - - - - - - 254 - - - - - - - - - - - - 7 - - - - - - - 255 - - - - - - - - - - - - 7 - - - - - - - 256 - - - - - - - - - - - - 10 - - - - - - - 257 - - - - - - - - - - - - 7 - - - - - - - 258 - - - - - - - - - - - - 10 - - - - - - - 259 - - - - - - - - - - - - 13 - - - - - - - 260 - - - - - - - - - - - - 3 - - - - - - - 261 - - - - - - - - - - - - 3 - - - - - - - 262 - - - - - - - - - - - - 6 - - - - - - - 263 - - - - - - - - - - - - 13 - - - - - - - 264 - - - - - - - - - - - - 10 - - - - - - - 265 - - - - - - - - - - - - 10 - - - - - - - 266 - - - - - - - - - - - - - - - - - - 267 - - - - - - - - - - - - 8 - - - - - - - 268 - - - - - - - - - - - - 13 - - - - - - - 269 - - - - - - - - - - - - 9 - - - - - - - 270 - - - - - - - - - - - - 2 - - - - - - - 271 - - - - - - - - - - - - 5 - - - - - - - 272 - - - - - - - - - - - - 7 - - - - - - - 273 - - - - - - - - - - - - - - - - - - 274 - - - - - - - - - - - - 7 - - - - - - - 275 - - - - - - - - - - - - 13 - - - - - - - 276 - - - - - - - - - - - - 9 - - - - - - - 277 - - - - - - - - - - - - 12 - - - - - - - 278 - - - - - - - - - - - - 9 - - - - - - - 279 - - - - - - - - - - - - 7 - - - - - - - 280 - - - - - - - - - - - - 2 - - - - - - - 281 - - - - - - - - - - - - 4 - - - - - - - 282 - - - - - - - - - - - - 2 - - - - - - - 283 - - - - - - - - - - - - 12 - - - - - - - 284 - - - - - - - - - - - - - - - - - - 285 - - - - - - - - - - - - 10 - - - - - - - 286 - - - - - - - - - - - - 6 - - - - - - - 287 - - - - - - - - - - - - 2 - - - - - - - 288 - - - - - - - - - - - - 2 - - - - - - - 289 - - - - - - - - - - - - 2 - - - - - - - 290 - - - - - - - - - - - - 8 - - - - - - - 291 - - - - - - - - - - - - 2 - - - - - - - 292 - - - - - - - - - - - - 9 - - - - - - - 293 - - - - - - - - - - - - 2 - - - - - - - 294 - - - - - - - - - - - - 3 - - - - - - - 295 - - - - - - - - - - - - 12 - - - - - - - 296 - - - - - - - - - - - - 5 - - - - - - - 297 - - - - - - - - - - - - 9 - - - - - - - 298 - - - - - - - - - - - - 10 - - - - - - - 299 - - - - - - - - - - - - 11 - - - - - - - 300 - - - - - - - - - - - - 10 - - - - - - - 301 - - - - - - - - - - - - 7 - - - - - - - 302 - - - - - - - - - - - - 10 - - - - - - - 303 - - - - - - - - - - - - 6 - - - - - - - 304 - - - - - - - - - - - - 5 - - - - - - - 305 - - - - - - - - - - - - 3 - - - - - - - 306 - - - - - - - - - - - - 6 - - - - - - - 307 - - - - - - - - - - - - 6 - - - - - - - 308 - - - - - - - - - - - - 10 - - - - - - - 309 - - - - - - - - - - - - 10 - - - - - - - 310 - - - - - - - - - - - - 10 - - - - - - - 311 - - - - - - - - - - - - 4 - - - - - - - 312 - - - - - - - - - - - - 3 - - - - - - - 313 - - - - - - - - - - - - 5 - - - - - - - 314 - - - - - - - - - - - - 2 - - - - - - - 315 - - - - - - - - - - - - 3 - - - - - - - 316 - - - - - - - - - - - - 10 - - - - - - - 317 - - - - - - - - - - - - 2 - - - - - - - 318 - - - - - - - - - - - - 2 - - - - - - - 319 - - - - - - - - - - - - 10 - - - - - - - 320 - - - - - - - - - - - - 12 - - - - - - - 321 - - - - - - - - - - - - - - - - - - 322 - - - - - - - - - - - - 12 - - - - - - - 323 - - - - - - - - - - - - 2 - - - - - - - 324 - - - - - - - - - - - - 10 - - - - - - - 325 - - - - - - - - - - - - 11 - - - - - - - 326 - - - - - - - - - - - - 10 - - - - - - - 327 - - - - - - - - - - - - 10 - - - - - - - 328 - - - - - - - - - - - - 13 - - - - - - - 329 - - - - - - - - - - - - 10 - - - - - - - 330 - - - - - - - - - - - - 10 - - - - - - - 331 - - - - - - - - - - - - 2 - - - - - - - 332 - - - - - - - - - - - - 10 - - - - - - - 333 - - - - - - - - - - - - 3 - - - - - - - 334 - - - - - - - - - - - - 10 - - - - - - - 335 - - - - - - - - - - - - 2 - - - - - - - 336 - - - - - - - - - - - - 10 - - - - - - - 337 - - - - - - - - - - - - 9 - - - - - - - 338 - - - - - - - - - - - - 8 - - - - - - - 339 - - - - - - - - - - - - 14 - - - - - - - 340 - - - - - - - - - - - - 2 - - - - - - - 341 - - - - - - - - - - - - 10 - - - - - - - 342 - - - - - - - - - - - - 2 - - - - - - - 343 - - - - - - - - - - - - 2 - - - - - - - 344 - - - - - - - - - - - - 9 - - - - - - - 345 - - - - - - - - - - - - - - - - - - 346 - - - - - - - - - - - - 2 - - - - - - - 347 - - - - - - - - - - - - 13 - - - - - - - 348 - - - - - - - - - - - - 2 - - - - - - - 349 - - - - - - - - - - - - 11 - - - - - - - 350 - - - - - - - - - - - - 9 - - - - - - - 351 - - - - - - - - - - - - - - - - - - 352 - - - - - - - - - - - - 14 - - - - - - - 353 - - - - - - - - - - - - 13 - - - - - - - 354 - - - - - - - - - - - - 3 - - - - - - - 355 - - - - - - - - - - - - 12 - - - - - - - 356 - - - - - - - - - - - - 10 - - - - - - - 357 - - - - - - - - - - - - 5 - - - - - - - 358 - - - - - - - - - - - - 6 - - - - - - - 359 - - - - - - - - - - - - 9 - - - - - - - 360 - - - - - - - - - - - - 2 - - - - - - - 361 - - - - - - - - - - - - 6 - - - - - - - 362 - - - - - - - - - - - - 10 - - - - - - - 363 - - - - - - - - - - - - 10 - - - - - - - 364 - - - - - - - - - - - - 4 - - - - - - - 365 - - - - - - - - - - - - 12 - - - - - - - 366 - - - - - - - - - - - - 14 - - - - - - - 367 - - - - - - - - - - - - 10 - - - - - - - 368 - - - - - - - - - - - - 14 - - - - - - - 369 - - - - - - - - - - - - 9 - - - - - - - 370 - - - - - - - - - - - - - - - - - - 371 - - - - - - - - - - - - 13 - - - - - - - 372 - - - - - - - - - - - - 14 - - - - - - - 373 - - - - - - - - - - - - 2 - - - - - - - 374 - - - - - - - - - - - - 4 - - - - - - - 375 - - - - - - - - - - - - 10 - - - - - - - 376 - - - - - - - - - - - - 12 - - - - - - - 377 - - - - - - - - - - - - - - - - - - 378 - - - - - - - - - - - - 4 - - - - - - - 379 - - - - - - - - - - - - 7 - - - - - - - 380 - - - - - - - - - - - - 3 - - - - - - - 381 - - - - - - - - - - - - 2 - - - - - - - 382 - - - - - - - - - - - - 7 - - - - - - - 383 - - - - - - - - - - - - 5 - - - - - - - 384 - - - - - - - - - - - - 2 - - - - - - - 385 - - - - - - - - - - - - 4 - - - - - - - 386 - - - - - - - - - - - - 8 - - - - - - - 387 - - - - - - - - - - - - 2 - - - - - - - 388 - - - - - - - - - - - - 5 - - - - - - - 389 - - - - - - - - - - - - 4 - - - - - - - 390 - - - - - - - - - - - - 2 - - - - - - - 391 - - - - - - - - - - - - 10 - - - - - - - 392 - - - - - - - - - - - - 4 - - - - - - - 393 - - - - - - - - - - - - 10 - - - - - - - 394 - - - - - - - - - - - - 11 - - - - - - - 395 - - - - - - - - - - - - 10 - - - - - - - 396 - - - - - - - - - - - - 7 - - - - - - - 397 - - - - - - - - - - - - 10 - - - - - - - 398 - - - - - - - - - - - - 2 - - - - - - - 399 - - - - - - - - - - - - 4 - - - - - - - 400 - - - - - - - - - - - - 10 - - - - - - - 401 - - - - - - - - - - - - 4 - - - - - - - 402 - - - - - - - - - - - - 11 - - - - - - - 403 - - - - - - - - - - - - 5 - - - - - - - 404 - - - - - - - - - - - - 2 - - - - - - - 405 - - - - - - - - - - - - 13 - - - - - - - 406 - - - - - - - - - - - - 14 - - - - - - - 407 - - - - - - - - - - - - 7 - - - - - - - 408 - - - - - - - - - - - - 14 - - - - - - - 409 - - - - - - - - - - - - 7 - - - - - - - 410 - - - - - - - - - - - - 10 - - - - - - - 411 - - - - - - - - - - - - 10 - - - - - - - 412 - - - - - - - - - - - - - - - - - - 413 - - - - - - - - - - - - 7 - - - - - - - 414 - - - - - - - - - - - - 10 - - - - - - - 415 - - - - - - - - - - - - 11 - - - - - - - 416 - - - - - - - - - - - - 2 - - - - - - - 417 - - - - - - - - - - - - 2 - - - - - - - 418 - - - - - - - - - - - - 7 - - - - - - - 419 - - - - - - - - - - - - 2 - - - - - - - 420 - - - - - - - - - - - - 4 - - - - - - - 421 - - - - - - - - - - - - 2 - - - - - - - 422 - - - - - - - - - - - - 10 - - - - - - - 423 - - - - - - - - - - - - 6 - - - - - - - 424 - - - - - - - - - - - - 10 - - - - - - - 425 - - - - - - - - - - - - 9 - - - - - - - 426 - - - - - - - - - - - - 6 - - - - - - - 427 - - - - - - - - - - - - 10 - - - - - - - 428 - - - - - - - - - - - - 9 - - - - - - - 429 - - - - - - - - - - - - 8 - - - - - - - 430 - - - - - - - - - - - - 10 - - - - - - - 431 - - - - - - - - - - - - 13 - - - - - - - 432 - - - - - - - - - - - - 4 - - - - - - - 433 - - - - - - - - - - - - 14 - - - - - - - 434 - - - - - - - - - - - - 10 - - - - - - - 435 - - - - - - - - - - - - 11 - - - - - - - 436 - - - - - - - - - - - - 6 - - - - - - - 437 - - - - - - - - - - - - 4 - - - - - - - 438 - - - - - - - - - - - - 2 - - - - - - - 439 - - - - - - - - - - - - 14 - - - - - - - 440 - - - - - - - - - - - - 7 - - - - - - - 441 - - - - - - - - - - - - 7 - - - - - - - 442 - - - - - - - - - - - - 2 - - - - - - - 443 - - - - - - - - - - - - 13 - - - - - - - 444 - - - - - - - - - - - - - - - - - - 445 - - - - - - - - - - - - 10 - - - - - - - 446 - - - - - - - - - - - - 10 - - - - - - - 447 - - - - - - - - - - - - 12 - - - - - - - 448 - - - - - - - - - - - - 2 - - - - - - - 449 - - - - - - - - - - - - 12 - - - - - - - 450 - - - - - - - - - - - - 3 - - - - - - - 451 - - - - - - - - - - - - 5 - - - - - - - 452 - - - - - - - - - - - - 10 - - - - - - - 453 - - - - - - - - - - - - 3 - - - - - - - 454 - - - - - - - - - - - - 6 - - - - - - - 455 - - - - - - - - - - - - 9 - - - - - - - 456 - - - - - - - - - - - - 13 - - - - - - - 457 - - - - - - - - - - - - 11 - - - - - - - 458 - - - - - - - - - - - - 2 - - - - - - - 459 - - - - - - - - - - - - 12 - - - - - - - 460 - - - - - - - - - - - - - - - - - - 461 - - - - - - - - - - - - 6 - - - - - - - 462 - - - - - - - - - - - - 2 - - - - - - - 463 - - - - - - - - - - - - 6 - - - - - - - 464 - - - - - - - - - - - - - - - - - - 465 - - - - - - - - - - - - 9 - - - - - - - 466 - - - - - - - - - - - - 2 - - - - - - - 467 - - - - - - - - - - - - 3 - - - - - - - 468 - - - - - - - - - - - - 5 - - - - - - - 469 - - - - - - - - - - - - 2 - - - - - - - 470 - - - - - - - - - - - - 12 - - - - - - - 471 - - - - - - - - - - - - 2 - - - - - - - 472 - - - - - - - - - - - - 9 - - - - - - - 473 - - - - - - - - - - - - 6 - - - - - - - 474 - - - - - - - - - - - - 3 - - - - - - - 475 - - - - - - - - - - - - 14 - - - - - - - 476 - - - - - - - - - - - - 12 - - - - - - - 477 - - - - - - - - - - - - - - - - - - 478 - - - - - - - - - - - - 5 - - - - - - - 479 - - - - - - - - - - - - 7 - - - - - - - 480 - - - - - - - - - - - - 7 - - - - - - - 481 - - - - - - - - - - - - 7 - - - - - - - 482 - - - - - - - - - - - - 7 - - - - - - - 483 - - - - - - - - - - - - 11 - - - - - - - 484 - - - - - - - - - - - - 9 - - - - - - - 485 - - - - - - - - - - - - 12 - - - - - - - 486 - - - - - - - - - - - - 12 - - - - - - - 487 - - - - - - - - - - - - 3 - - - - - - - 488 - - - - - - - - - - - - 10 - - - - - - - 489 - - - - - - - - - - - - 13 - - - - - - - 490 - - - - - - - - - - - - 3 - - - - - - - 491 - - - - - - - - - - - - 6 - - - - - - - 492 - - - - - - - - - - - - 3 - - - - - - - 493 - - - - - - - - - - - - 8 - - - - - - - 494 - - - - - - - - - - - - - - - - - - 495 - - - - - - - - - - - - - - - - - - 496 - - - - - - - - - - - - 5 - - - - - - - 497 - - - - - - - - - - - - 2 - - - - - - - 498 - - - - - - - - - - - - 2 - - - - - - - 499 - - - - - - - - - - - - 11 - - - - - - - 500 - - - - - - - - - - - - 10 - - - - - - - 501 - - - - - - - - - - - - 2 - - - - - - - 502 - - - - - - - - - - - - 2 - - - - - - - 503 - - - - - - - - - - - - 2 - - - - - - - 504 - - - - - - - - - - - - 7 - - - - - - - 505 - - - - - - - - - - - - 10 - - - - - - - 506 - - - - - - - - - - - - 9 - - - - - - - 507 - - - - - - - - - - - - - - - - - - 508 - - - - - - - - - - - - 11 - - - - - - - 509 - - - - - - - - - - - - 12 - - - - - - - 510 - - - - - - - - - - - - 12 - - - - - - - 511 - - - - - - - - - - - - 11 - - - - - - - 512 - - - - - - - - - - - - 2 - - - - - - - 513 - - - - - - - - - - - - 2 - - - - - - - 514 - - - - - - - - - - - - 7 - - - - - - - 515 - - - - - - - - - - - - 11 - - - - - - - 516 - - - - - - - - - - - - 12 - - - - - - - 517 - - - - - - - - - - - - 13 - - - - - - - 518 - - - - - - - - - - - - 12 - - - - - - - 519 - - - - - - - - - - - - 8 - - - - - - - 520 - - - - - - - - - - - - 10 - - - - - - - 521 - - - - - - - - - - - - 10 - - - - - - - 522 - - - - - - - - - - - - 2 - - - - - - - 523 - - - - - - - - - - - - 6 - - - - - - - 524 - - - - - - - - - - - - 10 - - - - - - - 525 - - - - - - - - - - - - 3 - - - - - - - 526 - - - - - - - - - - - - 10 - - - - - - - 527 - - - - - - - - - - - - 9 - - - - - - - 528 - - - - - - - - - - - - 2 - - - - - - - 529 - - - - - - - - - - - - 11 - - - - - - - 530 - - - - - - - - - - - - 9 - - - - - - - 531 - - - - - - - - - - - - 10 - - - - - - - 532 - - - - - - - - - - - - 11 - - - - - - - 533 - - - - - - - - - - - - 12 - - - - - - - 534 - - - - - - - - - - - - 14 - - - - - - - 535 - - - - - - - - - - - - - - - - - - 536 - - - - - - - - - - - - 10 - - - - - - - 537 - - - - - - - - - - - - 8 - - - - - - - 538 - - - - - - - - - - - - 2 - - - - - - - 539 - - - - - - - - - - - - 12 - - - - - - - 540 - - - - - - - - - - - - 10 - - - - - - - 541 - - - - - - - - - - - - 2 - - - - - - - 542 - - - - - - - - - - - - 9 - - - - - - - 543 - - - - - - - - - - - - 3 - - - - - - - 544 - - - - - - - - - - - - 7 - - - - - - - 545 - - - - - - - - - - - - 13 - - - - - - - 546 - - - - - - - - - - - - 8 - - - - - - - 547 - - - - - - - - - - - - 10 - - - - - - - 548 - - - - - - - - - - - - 12 - - - - - - - 549 - - - - - - - - - - - - - - - - - - 550 - - - - - - - - - - - - 8 - - - - - - - 551 - - - - - - - - - - - - 2 - - - - - - - 552 - - - - - - - - - - - - 6 - - - - - - - 553 - - - - - - - - - - - - 2 - - - - - - - 554 - - - - - - - - - - - - 13 - - - - - - - 555 - - - - - - - - - - - - 10 - - - - - - - 556 - - - - - - - - - - - - 9 - - - - - - - 557 - - - - - - - - - - - - 3 - - - - - - - 558 - - - - - - - - - - - - 2 - - - - - - - 559 - - - - - - - - - - - - 10 - - - - - - - 560 - - - - - - - - - - - - 8 - - - - - - - 561 - - - - - - - - - - - - 2 - - - - - - - 562 - - - - - - - - - - - - 4 - - - - - - - 563 - - - - - - - - - - - - 12 - - - - - - - 564 - - - - - - - - - - - - 7 - - - - - - - 565 - - - - - - - - - - - - 10 - - - - - - - 566 - - - - - - - - - - - - 9 - - - - - - - 567 - - - - - - - - - - - - - - - - - - 568 - - - - - - - - - - - - 12 - - - - - - - 569 - - - - - - - - - - - - 9 - - - - - - - 570 - - - - - - - - - - - - 3 - - - - - - - 571 - - - - - - - - - - - - 10 - - - - - - - 572 - - - - - - - - - - - - 11 - - - - - - - 573 - - - - - - - - - - - - 12 - - - - - - - 574 - - - - - - - - - - - - 7 - - - - - - - 575 - - - - - - - - - - - - 2 - - - - - - - 576 - - - - - - - - - - - - 4 - - - - - - - 577 - - - - - - - - - - - - 12 - - - - - - - 578 - - - - - - - - - - - - 11 - - - - - - - 579 - - - - - - - - - - - - 9 - - - - - - - 580 - - - - - - - - - - - - 10 - - - - - - - 581 - - - - - - - - - - - - 7 - - - - - - - 582 - - - - - - - - - - - - 6 - - - - - - - 583 - - - - - - - - - - - - 13 - - - - - - - 584 - - - - - - - - - - - - 11 - - - - - - - 585 - - - - - - - - - - - - 2 - - - - - - - 586 - - - - - - - - - - - - 13 - - - - - - - 587 - - - - - - - - - - - - 10 - - - - - - - 588 - - - - - - - - - - - - 6 - - - - - - - 589 - - - - - - - - - - - - 12 - - - - - - - 590 - - - - - - - - - - - - 2 - - - - - - - 591 - - - - - - - - - - - - 5 - - - - - - - 592 - - - - - - - - - - - - 7 - - - - - - - 593 - - - - - - - - - - - - 2 - - - - - - - 594 - - - - - - - - - - - - 7 - - - - - - - 595 - - - - - - - - - - - - 4 - - - - - - - 596 - - - - - - - - - - - - 2 - - - - - - - 597 - - - - - - - - - - - - 2 - - - - - - - 598 - - - - - - - - - - - - - - - - - - 599 - - - - - - - - - - - - 2 - - - - - - - 600 - - - - - - - - - - - - 3 - - - - - - - 601 - - - - - - - - - - - - - - - - - - 602 - - - - - - - - - - - - 4 - - - - - - - 603 - - - - - - - - - - - - 13 - - - - - - - 604 - - - - - - - - - - - - 6 - - - - - - - 605 - - - - - - - - - - - - 13 - - - - - - - 606 - - - - - - - - - - - - 2 - - - - - - - 607 - - - - - - - - - - - - 9 - - - - - - - 608 - - - - - - - - - - - - 2 - - - - - - - 609 - - - - - - - - - - - - 2 - - - - - - - 610 - - - - - - - - - - - - 10 - - - - - - - 611 - - - - - - - - - - - - 9 - - - - - - - 612 - - - - - - - - - - - - 11 - - - - - - - 613 - - - - - - - - - - - - 6 - - - - - - - 614 - - - - - - - - - - - - 10 - - - - - - - 615 - - - - - - - - - - - - 11 - - - - - - - 616 - - - - - - - - - - - - 8 - - - - - - - 617 - - - - - - - - - - - - 6 - - - - - - - 618 - - - - - - - - - - - - 10 - - - - - - - 619 - - - - - - - - - - - - 7 - - - - - - - 620 - - - - - - - - - - - - 9 - - - - - - - 621 - - - - - - - - - - - - 12 - - - - - - - 622 - - - - - - - - - - - - 4 - - - - - - - 623 - - - - - - - - - - - - 2 - - - - - - - 624 - - - - - - - - - - - - 3 - - - - - - - 625 - - - - - - - - - - - - 13 - - - - - - - 626 - - - - - - - - - - - - 9 - - - - - - - 627 - - - - - - - - - - - - 9 - - - - - - - 628 - - - - - - - - - - - - 11 - - - - - - - 629 - - - - - - - - - - - - 12 - - - - - - - 630 - - - - - - - - - - - - 5 - - - - - - - 631 - - - - - - - - - - - - 13 - - - - - - - 632 - - - - - - - - - - - - 2 - - - - - - - 633 - - - - - - - - - - - - 10 - - - - - - - 634 - - - - - - - - - - - - 11 - - - - - - - 635 - - - - - - - - - - - - 13 - - - - - - - 636 - - - - - - - - - - - - - - - - - - 637 - - - - - - - - - - - - 11 - - - - - - - 638 - - - - - - - - - - - - 10 - - - - - - - 639 - - - - - - - - - - - - 3 - - - - - - - 640 - - - - - - - - - - - - 14 - - - - - - - 641 - - - - - - - - - - - - 6 - - - - - - - 642 - - - - - - - - - - - - 2 - - - - - - - 643 - - - - - - - - - - - - 9 - - - - - - - 644 - - - - - - - - - - - - 2 - - - - - - - 645 - - - - - - - - - - - - 4 - - - - - - - 646 - - - - - - - - - - - - 2 - - - - - - - 647 - - - - - - - - - - - - 7 - - - - - - - 648 - - - - - - - - - - - - 2 - - - - - - - 649 - - - - - - - - - - - - 4 - - - - - - - 650 - - - - - - - - - - - - 10 - - - - - - - 651 - - - - - - - - - - - - - - - - - - 652 - - - - - - - - - - - - 3 - - - - - - - 653 - - - - - - - - - - - - 6 - - - - - - - 654 - - - - - - - - - - - - 11 - - - - - - - 655 - - - - - - - - - - - - 14 - - - - - - - 656 - - - - - - - - - - - - 12 - - - - - - - 657 - - - - - - - - - - - - 10 - - - - - - - 658 - - - - - - - - - - - - 13 - - - - - - - 659 - - - - - - - - - - - - 14 - - - - - - - 660 - - - - - - - - - - - - 8 - - - - - - - 661 - - - - - - - - - - - - 10 - - - - - - - 662 - - - - - - - - - - - - 11 - - - - - - - 663 - - - - - - - - - - - - 13 - - - - - - - 664 - - - - - - - - - - - - 6 - - - - - - - 665 - - - - - - - - - - - - 12 - - - - - - - 666 - - - - - - - - - - - - 9 - - - - - - - 667 - - - - - - - - - - - - 4 - - - - - - - 668 - - - - - - - - - - - - 14 - - - - - - - 669 - - - - - - - - - - - - 11 - - - - - - - 670 - - - - - - - - - - - - 12 - - - - - - - 671 - - - - - - - - - - - - 12 - - - - - - - 672 - - - - - - - - - - - - 11 - - - - - - - 673 - - - - - - - - - - - - - - - - - - 674 - - - - - - - - - - - - 13 - - - - - - - 675 - - - - - - - - - - - - 2 - - - - - - - 676 - - - - - - - - - - - - 13 - - - - - - - 677 - - - - - - - - - - - - 10 - - - - - - - 678 - - - - - - - - - - - - 7 - - - - - - - 679 - - - - - - - - - - - - 2 - - - - - - - 680 - - - - - - - - - - - - 2 - - - - - - - 681 - - - - - - - - - - - - 7 - - - - - - - 682 - - - - - - - - - - - - 13 - - - - - - - 683 - - - - - - - - - - - - 9 - - - - - - - 684 - - - - - - - - - - - - 7 - - - - - - - 685 - - - - - - - - - - - - 10 - - - - - - - 686 - - - - - - - - - - - - 6 - - - - - - - 687 - - - - - - - - - - - - 10 - - - - - - - 688 - - - - - - - - - - - - 12 - - - - - - - 689 - - - - - - - - - - - - - - - - - - 690 - - - - - - - - - - - - 12 - - - - - - - 691 - - - - - - - - - - - - 7 - - - - - - - 692 - - - - - - - - - - - - 4 - - - - - - - 693 - - - - - - - - - - - - 13 - - - - - - - 694 - - - - - - - - - - - - 2 - - - - - - - 695 - - - - - - - - - - - - 8 - - - - - - - 696 - - - - - - - - - - - - 5 - - - - - - - 697 - - - - - - - - - - - - 2 - - - - - - - 698 - - - - - - - - - - - - 10 - - - - - - - 699 - - - - - - - - - - - - 10 - - - - - - - 700 - - - - - - - - - - - - 14 - - - - - - - 701 - - - - - - - - - - - - 9 - - - - - - - 702 - - - - - - - - - - - - 11 - - - - - - - 703 - - - - - - - - - - - - 6 - - - - - - - 704 - - - - - - - - - - - - 2 - - - - - - - 705 - - - - - - - - - - - - 3 - - - - - - - 706 - - - - - - - - - - - - 10 - - - - - - - 707 - - - - - - - - - - - - 14 - - - - - - - 708 - - - - - - - - - - - - 6 - - - - - - - 709 - - - - - - - - - - - - 9 - - - - - - - 710 - - - - - - - - - - - - 2 - - - - - - - 711 - - - - - - - - - - - - 7 - - - - - - - 712 - - - - - - - - - - - - 2 - - - - - - - 713 - - - - - - - - - - - - 8 - - - - - - - 714 - - - - - - - - - - - - 11 - - - - - - - 715 - - - - - - - - - - - - 4 - - - - - - - 716 - - - - - - - - - - - - 7 - - - - - - - 717 - - - - - - - - - - - - 5 - - - - - - - 718 - - - - - - - - - - - - 7 - - - - - - - 719 - - - - - - - - - - - - 2 - - - - - - - 720 - - - - - - - - - - - - 2 - - - - - - - 721 - - - - - - - - - - - - 9 - - - - - - - 722 - - - - - - - - - - - - 13 - - - - - - - 723 - - - - - - - - - - - - 12 - - - - - - - 724 - - - - - - - - - - - - 13 - - - - - - - 725 - - - - - - - - - - - - 10 - - - - - - - 726 - - - - - - - - - - - - 12 - - - - - - - 727 - - - - - - - - - - - - 9 - - - - - - - 728 - - - - - - - - - - - - 6 - - - - - - - 729 - - - - - - - - - - - - 4 - - - - - - - 730 - - - - - - - - - - - - 6 - - - - - - - 731 - - - - - - - - - - - - 13 - - - - - - - 732 - - - - - - - - - - - - 13 - - - - - - - 733 - - - - - - - - - - - - 2 - - - - - - - 734 - - - - - - - - - - - - 4 - - - - - - - 735 - - - - - - - - - - - - 13 - - - - - - - 736 - - - - - - - - - - - - 8 - - - - - - - 737 - - - - - - - - - - - - - - - - - - 738 - - - - - - - - - - - - 11 - - - - - - - 739 - - - - - - - - - - - - 2 - - - - - - - 740 - - - - - - - - - - - - - - - - - - 741 - - - - - - - - - - - - - - - - - - 742 - - - - - - - - - - - - 12 - - - - - - - 743 - - - - - - - - - - - - 4 - - - - - - - 744 - - - - - - - - - - - - 5 - - - - - - - 745 - - - - - - - - - - - - 2 - - - - - - - 746 - - - - - - - - - - - - 6 - - - - - - - 747 - - - - - - - - - - - - 4 - - - - - - - 748 - - - - - - - - - - - - 10 - - - - - - - 749 - - - - - - - - - - - - 3 - - - - - - - 750 - - - - - - - - - - - - 4 - - - - - - - 751 - - - - - - - - - - - - 2 - - - - - - - 752 - - - - - - - - - - - - 5 - - - - - - - 753 - - - - - - - - - - - - - - - - - - 754 - - - - - - - - - - - - 9 - - - - - - - 755 - - - - - - - - - - - - 3 - - - - - - - 756 - - - - - - - - - - - - 2 - - - - - - - 757 - - - - - - - - - - - - 2 - - - - - - - 758 - - - - - - - - - - - - 11 - - - - - - - 759 - - - - - - - - - - - - 2 - - - - - - - 760 - - - - - - - - - - - - 10 - - - - - - - 761 - - - - - - - - - - - - 10 - - - - - - - 762 - - - - - - - - - - - - 2 - - - - - - - 763 - - - - - - - - - - - - 10 - - - - - - - 764 - - - - - - - - - - - - 2 - - - - - - - 765 - - - - - - - - - - - - 10 - - - - - - - 766 - - - - - - - - - - - - 10 - - - - - - - 767 - - - - - - - - - - - - - - - - - - 768 - - - - - - - - - - - - 5 - - - - - - - 769 - - - - - - - - - - - - 6 - - - - - - - 770 - - - - - - - - - - - - 5 - - - - - - - 771 - - - - - - - - - - - - 2 - - - - - - - 772 - - - - - - - - - - - - 2 - - - - - - - 773 - - - - - - - - - - - - 8 - - - - - - - 774 - - - - - - - - - - - - 13 - - - - - - - 775 - - - - - - - - - - - - 7 - - - - - - - 776 - - - - - - - - - - - - 2 - - - - - - - 777 - - - - - - - - - - - - 12 - - - - - - - 778 - - - - - - - - - - - - 6 - - - - - - - 779 - - - - - - - - - - - - 6 - - - - - - - 780 - - - - - - - - - - - - 13 - - - - - - - 781 - - - - - - - - - - - - 10 - - - - - - - 782 - - - - - - - - - - - - 11 - - - - - - - 783 - - - - - - - - - - - - 2 - - - - - - - 784 - - - - - - - - - - - - 6 - - - - - - - 785 - - - - - - - - - - - - 2 - - - - - - - 786 - - - - - - - - - - - - 14 - - - - - - - 787 - - - - - - - - - - - - 4 - - - - - - - 788 - - - - - - - - - - - - 12 - - - - - - - 789 - - - - - - - - - - - - 5 - - - - - - - 790 - - - - - - - - - - - - 8 - - - - - - - 791 - - - - - - - - - - - - 8 - - - - - - - 792 - - - - - - - - - - - - 12 - - - - - - - 793 - - - - - - - - - - - - 6 - - - - - - - 794 - - - - - - - - - - - - 5 - - - - - - - 795 - - - - - - - - - - - - 2 - - - - - - - 796 - - - - - - - - - - - - 4 - - - - - - - 797 - - - - - - - - - - - - 13 - - - - - - - 798 - - - - - - - - - - - - 7 - - - - - - - 799 - - - - - - - - - - - - 2 - - - - - - - 800 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/blank_labels.graphml b/tests/blank_labels.graphml deleted file mode 100644 index 28cea65d..00000000 --- a/tests/blank_labels.graphml +++ /dev/null @@ -1,829 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" -"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Creator: fig2dev Version 3.2 Patchlevel 5d --> -<!-- CreationDate: Thu Jul 19 14:02:38 2012 --> -<!-- Magnification: 1.000 --> -<svg xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - width="0.5in" height="0.4in" - viewBox="-9 12772 637 437"> -<g style="stroke-width:.025in; fill:none"> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="240,12856 -265,12894 -169,12916 -190,12899 -43,12874 -80,12846 -222,12870 -240,12856 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="375,12948 -357,12909 -444,12892 -429,12905 -573,12930 -538,12958 -395,12931 -375,12948 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="327,12836 -424,12809 -425,12851 -401,12846 -354,12885 -309,12878 -357,12840 -327,12836 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="286,12984 -194,13001 -190,12959 -217,12964 -268,12921 -312,12929 -258,12976 -286,12984 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -</g> -</svg> - - - - diff --git a/tests/deploy/test_deploy.py b/tests/deploy/test_deploy.py deleted file mode 100644 index 300c598b..00000000 --- a/tests/deploy/test_deploy.py +++ /dev/null @@ -1,46 +0,0 @@ -import autonetkit.console_script as console_script -import os -import autonetkit -import autonetkit.load.graphml as graphml -import autonetkit.diff - -automated = True # whether to open ksdiff, log to file... -enabled = False -if __name__ == "__main__": - # not called by test - automated = False - enabled = True - - - -remote_server = "54.252.205.75" - -if enabled: - dirname, filename = os.path.split(os.path.abspath(__file__)) - - input_file = os.path.join(dirname, "../big.graphml") - input_graph = graphml.load_graphml(input_file) - - import autonetkit.build_network as build_network - anm = build_network.initialise(input_graph) - anm = build_network.apply_design_rules(anm) - - render_hostname = "localhost" - - nidb = console_script.create_nidb(anm) - import autonetkit.compilers.platform.netkit as pl_netkit - nk_compiler = pl_netkit.NetkitCompiler(nidb, anm, render_hostname) - nk_compiler.compile() - - import autonetkit.render - autonetkit.render.render(nidb) - - import autonetkit.deploy.netkit as nk_deploy - username = "ubuntu" - home_dir = os.path.expanduser("~") - key_filename = os.path.join(home_dir, ".ssh/aws.pem") - dst_folder = nidb.topology['localhost'].render_dst_folder - nk_deploy.deploy(remote_server, username, dst_folder, - key_filename, parallel_count = 10) - - #console_script.measure_network(anm, nidb) diff --git a/tests/diff/expected_diff.json b/tests/diff/expected_diff.json deleted file mode 100644 index 4a0bc8dd..00000000 --- a/tests/diff/expected_diff.json +++ /dev/null @@ -1 +0,0 @@ -{'graph': {'topologies': {'localhost': {'tap_ips': [{'device': {1: 'as300r1', 2: 'as300r2'}, 'id': {1: '1', 2: '2'}}, {'device': {1: 'as300r2', 2: 'as300r3'}}, {'device': {1: 'as300r3', 2: 'as300r4'}}], 'machines': {1: 'as1r1 as20r1 as20r2 as20r3 as30r1 as40r1 as100r1 as100r2 as100r3 as200r1 as300r1 as300r2 as300r3 as300r4', 2: 'as1r1 as20r1 as20r2 as20r3 as30r1 as40r1 as100r1 as100r2 as100r3 as200r1 as300r2 as300r3 as300r4'}, 'config_items': [{'device': {1: 'as300r1', 2: 'as300r2'}, 'value': {1: '10.5.0.0.30', 2: '10.5.0.4.30'}}, {'key': {1: 0, 2: 1}, 'value': {1: '10.5.0.4.30', 2: '10.5.128.0.30'}}, {'device': {1: 'as300r2', 2: 'as300r3'}, 'key': {1: 1, 2: 0}, 'value': {1: '10.5.128.0.30', 2: '10.5.0.0.30'}}, {'key': {1: 0, 2: 1}, 'value': {1: '10.5.0.0.30', 2: '10.5.128.4.30'}}, {'device': {1: 'as300r3', 2: 'as300r4'}, 'key': {1: 1, 2: 0}, 'value': {1: '10.5.128.4.30', 2: '10.5.0.4.30'}}, {'key': {1: 0, 2: 1}, 'value': {1: '10.5.0.4.30', 2: '10.5.128.4.30'}}, {'device': {1: 'as300r4', 2: 'as30r1'}, 'key': {1: 1, 2: 0}, 'value': {1: '10.5.128.4.30', 2: '10.0.128.0.17'}}, {'key': {1: 0, 2: 1}, 'value': {1: '10.0.128.0.17', 2: '10.0.0.0.17'}}, {'device': {1: 'as30r1', 2: 'as40r1'}, 'key': {1: 1, 2: 0}, 'value': {1: '10.0.0.0.17', 2: '10.1.0.0.16'}}, {'key': {1: 0, 2: 1}, 'value': {1: '10.1.0.0.16', 2: '10.5.128.0.30'}}]}}}, 'nodes': {'m': {'as300r4': {'bgp': {'ibgp_neighbors': [{'asn': 'different types'}]}, 'tap': {'ip': {1: IPAddress('172.16.0.16'), 2: IPAddress('172.16.0.15')}}}, 'as300r2': {'bgp': {'ibgp_neighbors': [{'asn': 'different types'}]}, 'tap': {'ip': {1: IPAddress('172.16.0.14'), 2: IPAddress('172.16.0.13')}}}, 'as300r3': {'_interfaces': {1: {'description': {1: 'to as300r1', 2: 'as300r3 to as300r1'}}}, 'bgp': {'ibgp_neighbors': [{'asn': 'different types'}]}, 'tap': {'ip': {1: IPAddress('172.16.0.15'), 2: IPAddress('172.16.0.14')}}}, 'as300r1': {'r': ['_interfaces', 'asn', 'tap', 'Network', 'render', 'loopback', 'ip', 'loopback_subnet', 'hostname', 'update', 'label', 'bgp', 'platform', 'host', 'zebra', 'input_label', 'ospf', 'interfaces', 'ssh']}}}} \ No newline at end of file diff --git a/tests/diff/result.json b/tests/diff/result.json deleted file mode 100644 index 4281d476..00000000 --- a/tests/diff/result.json +++ /dev/null @@ -1 +0,0 @@ -{'graph': None} \ No newline at end of file diff --git a/tests/diff/test_diff.py b/tests/diff/test_diff.py deleted file mode 100644 index 56cac19d..00000000 --- a/tests/diff/test_diff.py +++ /dev/null @@ -1,97 +0,0 @@ -import autonetkit.console_script as console_script -import os -import autonetkit -import autonetkit.load.graphml as graphml -import autonetkit.diff - -automated = True # whether to open ksdiff, log to file... -if __name__ == "__main__": - automated = False - -dirname, filename = os.path.split(os.path.abspath(__file__)) -parent_dir = os.path.abspath(os.path.join(dirname, os.pardir)) - -input_file = os.path.join(parent_dir, "small_internet.graphml") -input_graph = graphml.load_graphml(input_file) - -import autonetkit.build_network as build_network -anm = build_network.initialise(input_graph) -anm = build_network.apply_design_rules(anm) - -import autonetkit.console_script as console_script -render_hostname = "localhost" - -g_phy = anm["input"] -g_test_a = anm.add_overlay("test_a") -g_test_b = anm.add_overlay("test_b") -g_test_a.add_nodes_from(g_phy) -g_test_b.add_nodes_from(g_phy) - -g_test_a.add_edges_from(g_phy.edges()) -g_test_b.add_edges_from(g_phy.edges()) - -g_test_b.remove_node("as300r1") - -graph_a = g_test_a._graph -graph_b = g_test_b._graph - -result = autonetkit.diff.compare(graph_a, graph_b) -result = str(result) - -expected = "{'graph': None, 'nodes': {'r': ['as300r1']}, 'edges': {'r': [('as300r1', 'as300r3')]}}" - -assert(result == expected) - -#nidb_a = console_script.create_nidb(anm_a) -#import autonetkit.compilers.platform.netkit as pl_netkit -#nk_compiler = pl_netkit.NetkitCompiler(nidb_a, anm_a, render_hostname) -#nk_compiler.compile() -# -#anm_b = build_network.apply_design_rules(anm) -#nidb_b = console_script.create_nidb(anm_b) -#import autonetkit.compilers.platform.netkit as pl_netkit -#nk_compiler = pl_netkit.NetkitCompiler(nidb_b, anm_b, render_hostname) -#nk_compiler.compile() -# -#result = autonetkit.diff.compare_nidb(nidb_a, nidb_b) -#with open(os.path.join(dirname, "expected_diff.json"), "r") as fh: -# expected = fh.read() -# -#with open(os.path.join(dirname, "result.json"), "w") as fh: -# fh.write(str(result)) -# -##print expected - -#TODO: check match - once stable generation -#assert(expected == result) - -if False: - #Test NIDB level archived diffing - # stdio redirect from stackoverflow.com/q/2654834 - - #TODO: add feature that reports if only IP addresses have changed: match the diff to an IP regex - - automated = True # whether to open ksdiff, log to file... - if __name__ == "__main__": - automated = False - - dirname, filename = os.path.split(os.path.abspath(__file__)) - parent_dir = os.path.abspath(os.path.join(dirname, os.pardir)) - - anm = autonetkit.ANM() - input_file = os.path.join(parent_dir, "small_internet.graphml") - arg_string = "-f %s --archive" % input_file - args = console_script.parse_options(arg_string) - console_script.main(args) - - - dirname, filename = os.path.split(os.path.abspath(__file__)) - anm = autonetkit.ANM() - input_file = os.path.join(dirname, "small_internet_modified.graphml") - arg_string = "-f %s --archive --diff" % input_file - args = console_script.parse_options(arg_string) - console_script.main(args) - - #TODO: need to compare diff versions.... and ignore timestamps... and find - - #shutil.rmtree("versions") \ No newline at end of file diff --git a/tests/duplicate_labels.graphml b/tests/duplicate_labels.graphml deleted file mode 100644 index cf48d850..00000000 --- a/tests/duplicate_labels.graphml +++ /dev/null @@ -1,826 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - r1 - - - - - - - - - - - - - - r1 - - - - - - - - - - - - - - r2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" -"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Creator: fig2dev Version 3.2 Patchlevel 5d --> -<!-- CreationDate: Thu Jul 19 14:02:38 2012 --> -<!-- Magnification: 1.000 --> -<svg xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - width="0.5in" height="0.4in" - viewBox="-9 12772 637 437"> -<g style="stroke-width:.025in; fill:none"> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="240,12856 -265,12894 -169,12916 -190,12899 -43,12874 -80,12846 -222,12870 -240,12856 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="375,12948 -357,12909 -444,12892 -429,12905 -573,12930 -538,12958 -395,12931 -375,12948 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="327,12836 -424,12809 -425,12851 -401,12846 -354,12885 -309,12878 -357,12840 -327,12836 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="286,12984 -194,13001 -190,12959 -217,12964 -268,12921 -312,12929 -258,12976 -286,12984 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -</g> -</svg> - - - - diff --git a/tests/house.json b/tests/house.json deleted file mode 100644 index e92b1534..00000000 --- a/tests/house.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "directed": false, - "graph": [], - "links": [ - { - "dst": "r2", - "dst_port": "eth0", - "src": "r1", - "src_port": "eth0" - }, - { - "dst": "r3", - "dst_port": "eth0", - "src": "r1", - "src_port": "eth1" - }, - { - "dst": "r3", - "dst_port": "eth1", - "src": "r2", - "src_port": "eth1" - }, - { - "dst": "r2", - "dst_port": "eth2", - "src": "r4", - "src_port": "eth0" - }, - { - "dst": "r5", - "dst_port": "eth0", - "src": "r4", - "src_port": "eth1" - }, - { - "dst": "r3", - "dst_port": "eth2", - "src": "r5", - "src_port": "eth1" - } - ], - "multigraph": false, - "nodes": [ - { - "asn": 1, - "device_type": "router", - "id": "r1", - "label": "r1", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r1 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r1 to r3", - "id": "eth1" - } - ], - "x": 350, - "y": 400 - }, - { - "asn": 1, - "device_type": "router", - "id": "r2", - "label": "r2", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r2 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r2 to r3", - "id": "eth1" - }, - { - "category": "physical", - "description": "r2 to r4", - "id": "eth2" - } - ], - "x": 500, - "y": 300 - }, - { - "asn": 1, - "device_type": "router", - "id": "r3", - "label": "r3", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r3 to r1", - "id": "eth0" - }, - { - "category": "physical", - "description": "r3 to r2", - "id": "eth1" - }, - { - "category": "physical", - "description": "r3 to r5", - "id": "eth2" - } - ], - "x": 500, - "y": 500 - }, - { - "asn": 2, - "device_type": "router", - "id": "r4", - "label": "r4", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r4 to r2", - "id": "eth0" - }, - { - "category": "physical", - "description": "r4 to r5", - "id": "eth1" - } - ], - "x": 675, - "y": 300 - }, - { - "asn": 2, - "device_type": "router", - "id": "r5", - "label": "r5", - "ports": [ - { - "category": "physical", - "description": null, - "id": "Loopback0" - }, - { - "category": "physical", - "description": "r5 to r4", - "id": "eth0" - }, - { - "category": "physical", - "description": "r5 to r3", - "id": "eth1" - } - ], - "x": 675, - "y": 500 - } - ] -} \ No newline at end of file diff --git a/tests/multigraph.graphml b/tests/multigraph.graphml deleted file mode 100644 index a8e99a2d..00000000 --- a/tests/multigraph.graphml +++ /dev/null @@ -1,820 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - r1 - - - - - - - - - - - - - - r2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" -"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Creator: fig2dev Version 3.2 Patchlevel 5d --> -<!-- CreationDate: Thu Jul 19 14:02:38 2012 --> -<!-- Magnification: 1.000 --> -<svg xmlns="http://www.w3.org/2000/svg" - xmlns:xlink="http://www.w3.org/1999/xlink" - width="0.5in" height="0.4in" - viewBox="-9 12772 637 437"> -<g style="stroke-width:.025in; fill:none"> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="616,12903 -616,12904 -613,12910 -608,12922 -604,12934 -601,12940 -601,12941 -600,12942 -598,12944 -592,12950 -584,12956 -573,12964 -557,12973 -544,12980 -535,12984 -530,12986 -527,12987 -525,12988 -519,12990 -508,12994 -491,12999 -473,13004 -461,13007 -455,13008 -452,13009 -448,13010 -441,13011 -428,13013 -407,13016 -385,13018 -366,13020 -350,13021 -335,13022 -323,13022 -315,13022 -311,13022 -310,13022 -309,13022 -301,13021 -284,13020 -262,13019 -239,13018 -222,13017 -214,13016 -213,13016 -212,13016 -209,13015 -201,13014 -190,13013 -178,13010 -164,13008 -148,13004 -129,12999 -112,12994 -101,12990 -95,12988 -92,12987 -90,12986 -85,12984 -75,12980 -62,12973 -47,12964 -39,12960 -36,12958 -34,12956 -29,12951 -19,12941 -11,12930 -7,12921 -4,12913 -3,12907 -3,12904 -3,12903 -3,12904 -3,12908 -3,12919 -3,12937 -3,12962 -3,12990 -3,13018 -3,13043 -3,13061 -3,13072 -3,13076 -3,13077 -3,13078 -6,13084 -11,13096 -16,13108 -19,13114 -19,13115 -19,13116 -22,13119 -28,13124 -35,13131 -46,13139 -62,13148 -75,13155 -85,13159 -90,13161 -92,13162 -95,13163 -101,13165 -112,13169 -129,13174 -147,13179 -159,13182 -165,13183 -169,13184 -172,13185 -179,13186 -192,13188 -213,13191 -235,13193 -254,13195 -270,13196 -285,13197 -297,13197 -305,13197 -309,13197 -310,13197 -311,13197 -319,13196 -336,13195 -359,13194 -381,13193 -398,13192 -406,13191 -407,13191 -408,13191 -411,13190 -419,13189 -430,13188 -442,13185 -456,13183 -473,13179 -491,13174 -508,13169 -519,13165 -525,13163 -527,13162 -530,13161 -535,13159 -544,13155 -557,13148 -572,13139 -580,13134 -583,13132 -586,13131 -591,13125 -601,13115 -609,13104 -613,13095 -615,13087 -616,13081 -616,13078 -616,13077 -616,13076 -616,13072 -616,13061 -616,13043 -616,13018 -616,12990 -616,12962 -616,12937 -616,12919 -616,12908 -616,12904 -616,12903 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#026c9b;stroke-width:0; -stroke-linejoin:bevel; stroke-linecap:butt; -fill:#026c9b; -"/> -<!-- Line --> -<!-- spline --> -<polyline points="310,13022 -311,13022 -319,13021 -336,13020 -359,13019 -381,13018 -398,13017 -406,13016 -407,13016 -408,13016 -411,13015 -419,13014 -430,13013 -442,13010 -456,13008 -473,13004 -491,12999 -508,12994 -519,12990 -525,12988 -527,12987 -530,12986 -535,12984 -544,12980 -557,12973 -572,12964 -580,12960 -583,12958 -586,12956 -591,12951 -601,12941 -609,12930 -613,12921 -615,12913 -616,12907 -616,12904 -616,12903 -616,12902 -613,12896 -608,12884 -604,12872 -601,12866 -601,12865 -600,12864 -598,12861 -592,12856 -584,12849 -573,12841 -557,12832 -544,12825 -535,12821 -530,12819 -527,12818 -525,12817 -519,12816 -508,12812 -491,12807 -473,12802 -461,12799 -455,12798 -452,12797 -448,12796 -441,12795 -428,12793 -407,12790 -385,12788 -366,12786 -350,12785 -335,12784 -323,12784 -315,12784 -311,12784 -310,12784 -309,12784 -301,12785 -284,12786 -262,12787 -239,12788 -222,12789 -214,12790 -213,12790 -212,12790 -209,12791 -201,12792 -190,12794 -178,12796 -164,12799 -148,12802 -129,12807 -112,12812 -101,12816 -95,12817 -92,12818 -90,12819 -85,12821 -75,12825 -62,12832 -47,12841 -39,12846 -36,12847 -34,12849 -29,12855 -19,12865 -11,12876 -7,12885 -4,12893 -3,12899 -3,12902 -3,12903 -3,12904 -6,12910 -11,12922 -16,12934 -19,12940 -19,12941 -19,12942 -22,12944 -28,12950 -35,12956 -46,12964 -62,12973 -75,12980 -85,12984 -90,12986 -92,12987 -95,12988 -101,12990 -112,12994 -129,12999 -147,13004 -159,13007 -165,13008 -169,13009 -172,13010 -179,13011 -192,13013 -213,13016 -235,13018 -254,13020 -270,13021 -285,13022 -297,13022 -305,13022 -309,13022 -310,13022 -" style="stroke:#ffffff;stroke-width:7; -stroke-linejoin:bevel; stroke-linecap:butt; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="240,12856 -265,12894 -169,12916 -190,12899 -43,12874 -80,12846 -222,12870 -240,12856 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="375,12948 -357,12909 -444,12892 -429,12905 -573,12930 -538,12958 -395,12931 -375,12948 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="327,12836 -424,12809 -425,12851 -401,12846 -354,12885 -309,12878 -357,12840 -327,12836 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -<!-- Line --> -<!-- polyline --> -<polyline points="286,12984 -194,13001 -190,12959 -217,12964 -268,12921 -312,12929 -258,12976 -286,12984 -" style="stroke:#ffffff;stroke-width:0; -stroke-linejoin:miter; stroke-linecap:butt; -fill:#ffffff; -"/> -</g> -</svg> - - - - diff --git a/tests/small_internet.graphml b/tests/small_internet.graphml deleted file mode 100644 index 45b756be..00000000 --- a/tests/small_internet.graphml +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - 100 - - - - - - - as100r2 - - - - - - - - - 100 - - - - - - - as100r3 - - - - - - - - - 100 - - - - - - - as100r1 - - - - - - - - - 20 - - - - - - - as20r2 - - - - - - - - - 20 - - - - - - - as20r3 - - - - - - - - - 20 - - - - - - - as20r1 - - - - - - - - - 1 - - - - - - - as1r1 - - - - - - - - - 40 - - - - - - - as40r1 - - - - - - - - - 30 - - - - - - - as30r1 - - - - - - - - - 300 - - - - - - - as300r1 - - - - - - - - - 300 - - - - - - - as300r3 - - - - - - - - - 300 - - - - - - - as300r2 - - - - - - - - - 200 - - - - - - - as200r1 - - - - - - - - - 300 - - - - - - - as300r4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - version="1.1" - width="0.5in" - height="0.40000001in" - viewBox="-9 12772 637 437" - id="svg2" - inkscape:version="0.48.2 r9819" - sodipodi:docname="router_small.svg"> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1032" - id="namedview14" - showgrid="false" - inkscape:zoom="6.5555556" - inkscape:cx="22.5" - inkscape:cy="18" - inkscape:window-x="1876" - inkscape:window-y="68" - inkscape:window-maximized="0" - inkscape:current-layer="svg2" /> - <metadata - id="metadata26"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs24" /> - <g - transform="matrix(1.0485581,0,0,1.0485581,14.41976,-671.91828)" - id="g4" - style="fill:none;stroke-width:0.025in"> - <polyline - points="616,12903 616,12904 613,12910 608,12922 604,12934 601,12940 601,12941 600,12942 598,12944 592,12950 584,12956 573,12964 557,12973 544,12980 535,12984 530,12986 527,12987 525,12988 519,12990 508,12994 491,12999 473,13004 461,13007 455,13008 452,13009 448,13010 441,13011 428,13013 407,13016 385,13018 366,13020 350,13021 335,13022 323,13022 315,13022 311,13022 310,13022 309,13022 301,13021 284,13020 262,13019 239,13018 222,13017 214,13016 213,13016 212,13016 209,13015 201,13014 190,13013 178,13010 164,13008 148,13004 129,12999 112,12994 101,12990 95,12988 92,12987 90,12986 85,12984 75,12980 62,12973 47,12964 39,12960 36,12958 34,12956 29,12951 19,12941 11,12930 7,12921 4,12913 3,12907 3,12904 3,12903 3,12904 3,12908 3,12919 3,12937 3,12962 3,12990 3,13018 3,13043 3,13061 3,13072 3,13076 3,13077 3,13078 6,13084 11,13096 16,13108 19,13114 19,13115 19,13116 22,13119 28,13124 35,13131 46,13139 62,13148 75,13155 85,13159 90,13161 92,13162 95,13163 101,13165 112,13169 129,13174 147,13179 159,13182 165,13183 169,13184 172,13185 179,13186 192,13188 213,13191 235,13193 254,13195 270,13196 285,13197 297,13197 305,13197 309,13197 310,13197 311,13197 319,13196 336,13195 359,13194 381,13193 398,13192 406,13191 407,13191 408,13191 411,13190 419,13189 430,13188 442,13185 456,13183 473,13179 491,13174 508,13169 519,13165 525,13163 527,13162 530,13161 535,13159 544,13155 557,13148 572,13139 580,13134 583,13132 586,13131 591,13125 601,13115 609,13104 613,13095 615,13087 616,13081 616,13078 616,13077 616,13076 616,13072 616,13061 616,13043 616,13018 616,12990 616,12962 616,12937 616,12919 616,12908 616,12904 616,12903 " - style="fill:#026c9b;stroke:#026c9b;stroke-width:0;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline6" /> - <polyline - points="616,12903 616,12904 613,12910 608,12922 604,12934 601,12940 601,12941 600,12942 598,12944 592,12950 584,12956 573,12964 557,12973 544,12980 535,12984 530,12986 527,12987 525,12988 519,12990 508,12994 491,12999 473,13004 461,13007 455,13008 452,13009 448,13010 441,13011 428,13013 407,13016 385,13018 366,13020 350,13021 335,13022 323,13022 315,13022 311,13022 310,13022 309,13022 301,13021 284,13020 262,13019 239,13018 222,13017 214,13016 213,13016 212,13016 209,13015 201,13014 190,13013 178,13010 164,13008 148,13004 129,12999 112,12994 101,12990 95,12988 92,12987 90,12986 85,12984 75,12980 62,12973 47,12964 39,12960 36,12958 34,12956 29,12951 19,12941 11,12930 7,12921 4,12913 3,12907 3,12904 3,12903 3,12904 3,12908 3,12919 3,12937 3,12962 3,12990 3,13018 3,13043 3,13061 3,13072 3,13076 3,13077 3,13078 6,13084 11,13096 16,13108 19,13114 19,13115 19,13116 22,13119 28,13124 35,13131 46,13139 62,13148 75,13155 85,13159 90,13161 92,13162 95,13163 101,13165 112,13169 129,13174 147,13179 159,13182 165,13183 169,13184 172,13185 179,13186 192,13188 213,13191 235,13193 254,13195 270,13196 285,13197 297,13197 305,13197 309,13197 310,13197 311,13197 319,13196 336,13195 359,13194 381,13193 398,13192 406,13191 407,13191 408,13191 411,13190 419,13189 430,13188 442,13185 456,13183 473,13179 491,13174 508,13169 519,13165 525,13163 527,13162 530,13161 535,13159 544,13155 557,13148 572,13139 580,13134 583,13132 586,13131 591,13125 601,13115 609,13104 613,13095 615,13087 616,13081 616,13078 616,13077 616,13076 616,13072 616,13061 616,13043 616,13018 616,12990 616,12962 616,12937 616,12919 616,12908 616,12904 616,12903 " - style="stroke:#ffffff;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline8" /> - <polyline - points="310,13022 311,13022 319,13021 336,13020 359,13019 381,13018 398,13017 406,13016 407,13016 408,13016 411,13015 419,13014 430,13013 442,13010 456,13008 473,13004 491,12999 508,12994 519,12990 525,12988 527,12987 530,12986 535,12984 544,12980 557,12973 572,12964 580,12960 583,12958 586,12956 591,12951 601,12941 609,12930 613,12921 615,12913 616,12907 616,12904 616,12903 616,12902 613,12896 608,12884 604,12872 601,12866 601,12865 600,12864 598,12861 592,12856 584,12849 573,12841 557,12832 544,12825 535,12821 530,12819 527,12818 525,12817 519,12816 508,12812 491,12807 473,12802 461,12799 455,12798 452,12797 448,12796 441,12795 428,12793 407,12790 385,12788 366,12786 350,12785 335,12784 323,12784 315,12784 311,12784 310,12784 309,12784 301,12785 284,12786 262,12787 239,12788 222,12789 214,12790 213,12790 212,12790 209,12791 201,12792 190,12794 178,12796 164,12799 148,12802 129,12807 112,12812 101,12816 95,12817 92,12818 90,12819 85,12821 75,12825 62,12832 47,12841 39,12846 36,12847 34,12849 29,12855 19,12865 11,12876 7,12885 4,12893 3,12899 3,12902 3,12903 3,12904 6,12910 11,12922 16,12934 19,12940 19,12941 19,12942 22,12944 28,12950 35,12956 46,12964 62,12973 75,12980 85,12984 90,12986 92,12987 95,12988 101,12990 112,12994 129,12999 147,13004 159,13007 165,13008 169,13009 172,13010 179,13011 192,13013 213,13016 235,13018 254,13020 270,13021 285,13022 297,13022 305,13022 309,13022 310,13022 " - style="fill:#026c9b;stroke:#026c9b;stroke-width:0;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline10" /> - <polyline - points="310,13022 311,13022 319,13021 336,13020 359,13019 381,13018 398,13017 406,13016 407,13016 408,13016 411,13015 419,13014 430,13013 442,13010 456,13008 473,13004 491,12999 508,12994 519,12990 525,12988 527,12987 530,12986 535,12984 544,12980 557,12973 572,12964 580,12960 583,12958 586,12956 591,12951 601,12941 609,12930 613,12921 615,12913 616,12907 616,12904 616,12903 616,12902 613,12896 608,12884 604,12872 601,12866 601,12865 600,12864 598,12861 592,12856 584,12849 573,12841 557,12832 544,12825 535,12821 530,12819 527,12818 525,12817 519,12816 508,12812 491,12807 473,12802 461,12799 455,12798 452,12797 448,12796 441,12795 428,12793 407,12790 385,12788 366,12786 350,12785 335,12784 323,12784 315,12784 311,12784 310,12784 309,12784 301,12785 284,12786 262,12787 239,12788 222,12789 214,12790 213,12790 212,12790 209,12791 201,12792 190,12794 178,12796 164,12799 148,12802 129,12807 112,12812 101,12816 95,12817 92,12818 90,12819 85,12821 75,12825 62,12832 47,12841 39,12846 36,12847 34,12849 29,12855 19,12865 11,12876 7,12885 4,12893 3,12899 3,12902 3,12903 3,12904 6,12910 11,12922 16,12934 19,12940 19,12941 19,12942 22,12944 28,12950 35,12956 46,12964 62,12973 75,12980 85,12984 90,12986 92,12987 95,12988 101,12990 112,12994 129,12999 147,13004 159,13007 165,13008 169,13009 172,13010 179,13011 192,13013 213,13016 235,13018 254,13020 270,13021 285,13022 297,13022 305,13022 309,13022 310,13022 " - style="stroke:#ffffff;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline12" /> - <polyline - points="240,12856 265,12894 169,12916 190,12899 43,12874 80,12846 222,12870 240,12856 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline14" /> - <polyline - points="375,12948 357,12909 444,12892 429,12905 573,12930 538,12958 395,12931 375,12948 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline16" /> - <polyline - points="327,12836 424,12809 425,12851 401,12846 354,12885 309,12878 357,12840 327,12836 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline18" /> - <polyline - points="286,12984 194,13001 190,12959 217,12964 268,12921 312,12929 258,12976 286,12984 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline20" /> - </g> -</svg> - - - - diff --git a/tests/small_internet/as1r1_bgpd.conf b/tests/small_internet/as1r1_bgpd.conf deleted file mode 100644 index 91e54172..00000000 --- a/tests/small_internet/as1r1_bgpd.conf +++ /dev/null @@ -1,31 +0,0 @@ -! -hostname as1r1 -password 1234 -banner motd file /etc/quagga/motd.txt -!enable password 1234 -! - router bgp 1 - bgp router-id 10.0.0.17 - no synchronization -! ibgp -! ebgp - ! as20r3 - neighbor 10.4.0.2 remote-as 20 - neighbor 10.4.0.2 update-source 10.4.0.1 - neighbor 10.4.0.2 send-community - ! as30r1 - neighbor 10.0.0.1 remote-as 30 - neighbor 10.0.0.1 update-source 10.0.0.2 - neighbor 10.0.0.1 send-community - ! as40r1 - neighbor 10.1.0.2 remote-as 40 - neighbor 10.1.0.2 update-source 10.1.0.1 - neighbor 10.1.0.2 send-community - -debug bgp -debug bgp events -debug bgp filters -debug bgp fsm -debug bgp keepalives -debug bgp updates -log file /var/log/zebra/bgpd.log diff --git a/tests/small_internet/as1r1_ospfd.conf b/tests/small_internet/as1r1_ospfd.conf deleted file mode 100644 index 581e837a..00000000 --- a/tests/small_internet/as1r1_ospfd.conf +++ /dev/null @@ -1,16 +0,0 @@ -hostname as1r1 -password 1234 -banner motd file /etc/quagga/motd.txt -! -! -router ospf - network 10.4.0.0/30 area 0 - network 10.0.0.0/17 area 0 - network 10.1.0.0/16 area 0 - ! - passive-interface eth0 - passive-interface eth1 - passive-interface eth2 - ! - network 10.0.0.17/32 area 0 -! diff --git a/tests/small_internet/as1r1_zebra.conf b/tests/small_internet/as1r1_zebra.conf deleted file mode 100644 index 1b113891..00000000 --- a/tests/small_internet/as1r1_zebra.conf +++ /dev/null @@ -1,13 +0,0 @@ -hostname as1r1 -password 1234 -enable password 1234 -banner motd file /etc/quagga/motd.txt -! -interface lo -description local loopback -ip address 127.0.0.1/8 -ip address 10.0.0.17/32 -! - - -log file /var/log/zebra/zebra.log diff --git a/tests/small_internet/as20r3_bgpd.conf b/tests/small_internet/as20r3_bgpd.conf deleted file mode 100644 index 7993a565..00000000 --- a/tests/small_internet/as20r3_bgpd.conf +++ /dev/null @@ -1,35 +0,0 @@ -! -hostname as20r3 -password 1234 -banner motd file /etc/quagga/motd.txt -!enable password 1234 -! - router bgp 20 - bgp router-id 10.0.0.35 - no synchronization - network 10.4.0.0/16 -! ibgp - ! ibgp peers - ! as20r1 - neighbor 10.0.0.33 remote-as 20 - neighbor 10.0.0.33 update-source 10.0.0.35 - neighbor 10.0.0.33 send-community - neighbor 10.0.0.33 next-hop-self - ! as20r2 - neighbor 10.0.0.34 remote-as 20 - neighbor 10.0.0.34 update-source 10.0.0.35 - neighbor 10.0.0.34 send-community - neighbor 10.0.0.34 next-hop-self -! ebgp - ! as1r1 - neighbor 10.4.0.1 remote-as 1 - neighbor 10.4.0.1 update-source 10.4.0.2 - neighbor 10.4.0.1 send-community - -debug bgp -debug bgp events -debug bgp filters -debug bgp fsm -debug bgp keepalives -debug bgp updates -log file /var/log/zebra/bgpd.log diff --git a/tests/small_internet/as20r3_ospfd.conf b/tests/small_internet/as20r3_ospfd.conf deleted file mode 100644 index f09e7e5d..00000000 --- a/tests/small_internet/as20r3_ospfd.conf +++ /dev/null @@ -1,22 +0,0 @@ -hostname as20r3 -password 1234 -banner motd file /etc/quagga/motd.txt -! - interface eth0 - #Link to to as20r2 - ip ospf cost 1 - ! - interface eth2 - #Link to to as20r1 - ip ospf cost 1 - ! -! -router ospf - network 10.4.0.12/30 area 0 - network 10.4.0.8/30 area 0 - network 10.4.0.0/30 area 0 - ! - passive-interface eth1 - ! - network 10.0.0.35/32 area 0 -! diff --git a/tests/small_internet/as20r3_zebra.conf b/tests/small_internet/as20r3_zebra.conf deleted file mode 100644 index c8be5b12..00000000 --- a/tests/small_internet/as20r3_zebra.conf +++ /dev/null @@ -1,13 +0,0 @@ -hostname as20r3 -password 1234 -enable password 1234 -banner motd file /etc/quagga/motd.txt -! -interface lo -description local loopback -ip address 127.0.0.1/8 -ip address 10.0.0.35/32 -! - - -log file /var/log/zebra/zebra.log diff --git a/tests/small_internet/lab.conf b/tests/small_internet/lab.conf deleted file mode 100644 index ffa25ad4..00000000 --- a/tests/small_internet/lab.conf +++ /dev/null @@ -1,57 +0,0 @@ -LAB_DESCRIPTION="AutoNetkit Lab" -LAB_VERSION="autonetkit_0.4.5" -LAB_AUTHOR="AutoNetkit" -LAB_EMAIL="None" -LAB_WEB="www.autonetkit.org" - -machines="as1r1 as20r1 as20r2 as20r3 as30r1 as40r1 as100r1 as100r2 as100r3 as200r1 as300r1 as300r2 as300r3 as300r4" - -as100r1[0]=10.4.0.4.30 -as100r1[1]=10.4.128.0.30 -as100r1[2]=10.4.0.0.30 -as100r1[3]=10.0.0.12.30 -as100r2[0]=10.4.0.4.30 -as100r2[1]=10.4.128.4.30 -as100r3[0]=10.4.128.0.30 -as100r3[1]=10.4.128.4.30 -as1r1[0]=10.0.128.0.30 -as1r1[1]=10.2.0.0.30 -as1r1[2]=10.3.0.0.30 -as200r1[0]=10.5.0.0.30 -as20r1[0]=10.5.0.0.30 -as20r1[1]=10.4.0.0.30 -as20r1[2]=10.2.0.4.30 -as20r1[3]=10.0.0.0.30 -as20r1[4]=10.0.0.8.30 -as20r2[0]=10.0.0.12.30 -as20r2[1]=10.0.0.0.30 -as20r2[2]=10.0.0.4.30 -as20r3[0]=10.0.0.4.30 -as20r3[1]=10.0.128.0.30 -as20r3[2]=10.0.0.8.30 -as300r1[0]=10.1.0.0.30 -as300r2[0]=10.1.0.4.30 -as300r2[1]=10.1.128.0.30 -as300r3[0]=10.1.0.0.30 -as300r3[1]=10.1.128.4.30 -as300r4[0]=10.1.0.4.30 -as300r4[1]=10.1.128.4.30 -as30r1[0]=10.2.0.4.30 -as30r1[1]=10.2.0.0.30 -as40r1[0]=10.3.0.0.30 -as40r1[1]=10.1.128.0.30 - -as1r1[3]=tap,172.16.0.1,172.16.0.3 -as20r1[5]=tap,172.16.0.1,172.16.0.4 -as20r2[3]=tap,172.16.0.1,172.16.0.5 -as20r3[3]=tap,172.16.0.1,172.16.0.6 -as30r1[2]=tap,172.16.0.1,172.16.0.7 -as40r1[2]=tap,172.16.0.1,172.16.0.8 -as100r1[4]=tap,172.16.0.1,172.16.0.9 -as100r2[2]=tap,172.16.0.1,172.16.0.10 -as100r3[2]=tap,172.16.0.1,172.16.0.11 -as200r1[1]=tap,172.16.0.1,172.16.0.12 -as300r1[1]=tap,172.16.0.1,172.16.0.13 -as300r2[2]=tap,172.16.0.1,172.16.0.14 -as300r3[2]=tap,172.16.0.1,172.16.0.15 -as300r4[2]=tap,172.16.0.1,172.16.0.16 diff --git a/tests/small_internet/small_internet.graphml b/tests/small_internet/small_internet.graphml deleted file mode 100644 index 45b756be..00000000 --- a/tests/small_internet/small_internet.graphml +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - 0 - - - - - - - - - - - - - - - - - - - - - 100 - - - - - - - as100r2 - - - - - - - - - 100 - - - - - - - as100r3 - - - - - - - - - 100 - - - - - - - as100r1 - - - - - - - - - 20 - - - - - - - as20r2 - - - - - - - - - 20 - - - - - - - as20r3 - - - - - - - - - 20 - - - - - - - as20r1 - - - - - - - - - 1 - - - - - - - as1r1 - - - - - - - - - 40 - - - - - - - as40r1 - - - - - - - - - 30 - - - - - - - as30r1 - - - - - - - - - 300 - - - - - - - as300r1 - - - - - - - - - 300 - - - - - - - as300r3 - - - - - - - - - 300 - - - - - - - as300r2 - - - - - - - - - 200 - - - - - - - as200r1 - - - - - - - - - 300 - - - - - - - as300r4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - version="1.1" - width="0.5in" - height="0.40000001in" - viewBox="-9 12772 637 437" - id="svg2" - inkscape:version="0.48.2 r9819" - sodipodi:docname="router_small.svg"> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1920" - inkscape:window-height="1032" - id="namedview14" - showgrid="false" - inkscape:zoom="6.5555556" - inkscape:cx="22.5" - inkscape:cy="18" - inkscape:window-x="1876" - inkscape:window-y="68" - inkscape:window-maximized="0" - inkscape:current-layer="svg2" /> - <metadata - id="metadata26"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title /> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs24" /> - <g - transform="matrix(1.0485581,0,0,1.0485581,14.41976,-671.91828)" - id="g4" - style="fill:none;stroke-width:0.025in"> - <polyline - points="616,12903 616,12904 613,12910 608,12922 604,12934 601,12940 601,12941 600,12942 598,12944 592,12950 584,12956 573,12964 557,12973 544,12980 535,12984 530,12986 527,12987 525,12988 519,12990 508,12994 491,12999 473,13004 461,13007 455,13008 452,13009 448,13010 441,13011 428,13013 407,13016 385,13018 366,13020 350,13021 335,13022 323,13022 315,13022 311,13022 310,13022 309,13022 301,13021 284,13020 262,13019 239,13018 222,13017 214,13016 213,13016 212,13016 209,13015 201,13014 190,13013 178,13010 164,13008 148,13004 129,12999 112,12994 101,12990 95,12988 92,12987 90,12986 85,12984 75,12980 62,12973 47,12964 39,12960 36,12958 34,12956 29,12951 19,12941 11,12930 7,12921 4,12913 3,12907 3,12904 3,12903 3,12904 3,12908 3,12919 3,12937 3,12962 3,12990 3,13018 3,13043 3,13061 3,13072 3,13076 3,13077 3,13078 6,13084 11,13096 16,13108 19,13114 19,13115 19,13116 22,13119 28,13124 35,13131 46,13139 62,13148 75,13155 85,13159 90,13161 92,13162 95,13163 101,13165 112,13169 129,13174 147,13179 159,13182 165,13183 169,13184 172,13185 179,13186 192,13188 213,13191 235,13193 254,13195 270,13196 285,13197 297,13197 305,13197 309,13197 310,13197 311,13197 319,13196 336,13195 359,13194 381,13193 398,13192 406,13191 407,13191 408,13191 411,13190 419,13189 430,13188 442,13185 456,13183 473,13179 491,13174 508,13169 519,13165 525,13163 527,13162 530,13161 535,13159 544,13155 557,13148 572,13139 580,13134 583,13132 586,13131 591,13125 601,13115 609,13104 613,13095 615,13087 616,13081 616,13078 616,13077 616,13076 616,13072 616,13061 616,13043 616,13018 616,12990 616,12962 616,12937 616,12919 616,12908 616,12904 616,12903 " - style="fill:#026c9b;stroke:#026c9b;stroke-width:0;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline6" /> - <polyline - points="616,12903 616,12904 613,12910 608,12922 604,12934 601,12940 601,12941 600,12942 598,12944 592,12950 584,12956 573,12964 557,12973 544,12980 535,12984 530,12986 527,12987 525,12988 519,12990 508,12994 491,12999 473,13004 461,13007 455,13008 452,13009 448,13010 441,13011 428,13013 407,13016 385,13018 366,13020 350,13021 335,13022 323,13022 315,13022 311,13022 310,13022 309,13022 301,13021 284,13020 262,13019 239,13018 222,13017 214,13016 213,13016 212,13016 209,13015 201,13014 190,13013 178,13010 164,13008 148,13004 129,12999 112,12994 101,12990 95,12988 92,12987 90,12986 85,12984 75,12980 62,12973 47,12964 39,12960 36,12958 34,12956 29,12951 19,12941 11,12930 7,12921 4,12913 3,12907 3,12904 3,12903 3,12904 3,12908 3,12919 3,12937 3,12962 3,12990 3,13018 3,13043 3,13061 3,13072 3,13076 3,13077 3,13078 6,13084 11,13096 16,13108 19,13114 19,13115 19,13116 22,13119 28,13124 35,13131 46,13139 62,13148 75,13155 85,13159 90,13161 92,13162 95,13163 101,13165 112,13169 129,13174 147,13179 159,13182 165,13183 169,13184 172,13185 179,13186 192,13188 213,13191 235,13193 254,13195 270,13196 285,13197 297,13197 305,13197 309,13197 310,13197 311,13197 319,13196 336,13195 359,13194 381,13193 398,13192 406,13191 407,13191 408,13191 411,13190 419,13189 430,13188 442,13185 456,13183 473,13179 491,13174 508,13169 519,13165 525,13163 527,13162 530,13161 535,13159 544,13155 557,13148 572,13139 580,13134 583,13132 586,13131 591,13125 601,13115 609,13104 613,13095 615,13087 616,13081 616,13078 616,13077 616,13076 616,13072 616,13061 616,13043 616,13018 616,12990 616,12962 616,12937 616,12919 616,12908 616,12904 616,12903 " - style="stroke:#ffffff;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline8" /> - <polyline - points="310,13022 311,13022 319,13021 336,13020 359,13019 381,13018 398,13017 406,13016 407,13016 408,13016 411,13015 419,13014 430,13013 442,13010 456,13008 473,13004 491,12999 508,12994 519,12990 525,12988 527,12987 530,12986 535,12984 544,12980 557,12973 572,12964 580,12960 583,12958 586,12956 591,12951 601,12941 609,12930 613,12921 615,12913 616,12907 616,12904 616,12903 616,12902 613,12896 608,12884 604,12872 601,12866 601,12865 600,12864 598,12861 592,12856 584,12849 573,12841 557,12832 544,12825 535,12821 530,12819 527,12818 525,12817 519,12816 508,12812 491,12807 473,12802 461,12799 455,12798 452,12797 448,12796 441,12795 428,12793 407,12790 385,12788 366,12786 350,12785 335,12784 323,12784 315,12784 311,12784 310,12784 309,12784 301,12785 284,12786 262,12787 239,12788 222,12789 214,12790 213,12790 212,12790 209,12791 201,12792 190,12794 178,12796 164,12799 148,12802 129,12807 112,12812 101,12816 95,12817 92,12818 90,12819 85,12821 75,12825 62,12832 47,12841 39,12846 36,12847 34,12849 29,12855 19,12865 11,12876 7,12885 4,12893 3,12899 3,12902 3,12903 3,12904 6,12910 11,12922 16,12934 19,12940 19,12941 19,12942 22,12944 28,12950 35,12956 46,12964 62,12973 75,12980 85,12984 90,12986 92,12987 95,12988 101,12990 112,12994 129,12999 147,13004 159,13007 165,13008 169,13009 172,13010 179,13011 192,13013 213,13016 235,13018 254,13020 270,13021 285,13022 297,13022 305,13022 309,13022 310,13022 " - style="fill:#026c9b;stroke:#026c9b;stroke-width:0;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline10" /> - <polyline - points="310,13022 311,13022 319,13021 336,13020 359,13019 381,13018 398,13017 406,13016 407,13016 408,13016 411,13015 419,13014 430,13013 442,13010 456,13008 473,13004 491,12999 508,12994 519,12990 525,12988 527,12987 530,12986 535,12984 544,12980 557,12973 572,12964 580,12960 583,12958 586,12956 591,12951 601,12941 609,12930 613,12921 615,12913 616,12907 616,12904 616,12903 616,12902 613,12896 608,12884 604,12872 601,12866 601,12865 600,12864 598,12861 592,12856 584,12849 573,12841 557,12832 544,12825 535,12821 530,12819 527,12818 525,12817 519,12816 508,12812 491,12807 473,12802 461,12799 455,12798 452,12797 448,12796 441,12795 428,12793 407,12790 385,12788 366,12786 350,12785 335,12784 323,12784 315,12784 311,12784 310,12784 309,12784 301,12785 284,12786 262,12787 239,12788 222,12789 214,12790 213,12790 212,12790 209,12791 201,12792 190,12794 178,12796 164,12799 148,12802 129,12807 112,12812 101,12816 95,12817 92,12818 90,12819 85,12821 75,12825 62,12832 47,12841 39,12846 36,12847 34,12849 29,12855 19,12865 11,12876 7,12885 4,12893 3,12899 3,12902 3,12903 3,12904 6,12910 11,12922 16,12934 19,12940 19,12941 19,12942 22,12944 28,12950 35,12956 46,12964 62,12973 75,12980 85,12984 90,12986 92,12987 95,12988 101,12990 112,12994 129,12999 147,13004 159,13007 165,13008 169,13009 172,13010 179,13011 192,13013 213,13016 235,13018 254,13020 270,13021 285,13022 297,13022 305,13022 309,13022 310,13022 " - style="stroke:#ffffff;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel" - id="polyline12" /> - <polyline - points="240,12856 265,12894 169,12916 190,12899 43,12874 80,12846 222,12870 240,12856 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline14" /> - <polyline - points="375,12948 357,12909 444,12892 429,12905 573,12930 538,12958 395,12931 375,12948 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline16" /> - <polyline - points="327,12836 424,12809 425,12851 401,12846 354,12885 309,12878 357,12840 327,12836 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline18" /> - <polyline - points="286,12984 194,13001 190,12959 217,12964 268,12921 312,12929 258,12976 286,12984 " - style="fill:#ffffff;stroke:#ffffff;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter" - id="polyline20" /> - </g> -</svg> - - - - diff --git a/tests/small_internet/test_small_internet.py b/tests/small_internet/test_small_internet.py deleted file mode 100644 index a2e92c82..00000000 --- a/tests/small_internet/test_small_internet.py +++ /dev/null @@ -1,88 +0,0 @@ -import autonetkit -import autonetkit.load.graphml as graphml -import os -import gzip -import json -import unittest -import subprocess -import shutil - -def gzip_to_json(filename): - with gzip.open(filename, "r") as json_fh: - return json.load(json_fh) - -def json_to_gzip(data, filename): - with gzip.open(filename, "wb") as json_fh: - return json.dump(data, json_fh) - -def test(): - automated = True # whether to open ksdiff, log to file... - if __name__ == "__main__": - automated = False - - dirname, filename = os.path.split(os.path.abspath(__file__)) - - anm = autonetkit.NetworkModel() - input_file = os.path.join(dirname, "small_internet.graphml") - input_graph = graphml.load_graphml(input_file) - - import autonetkit.build_network as build_network - anm = build_network.initialise(input_graph) - anm = build_network.apply_design_rules(anm) - - import autonetkit.console_script as console_script - render_hostname = "localhost" - - nidb = console_script.create_nidb(anm) - import autonetkit.compilers.platform.netkit as pl_netkit - nk_compiler = pl_netkit.NetkitCompiler(nidb, anm, render_hostname) - nk_compiler.compile() - - import autonetkit.render - autonetkit.render.render(nidb) - - import os - dst_folder = nidb.topology['localhost'].render_dst_folder - - # test folder structure - dir_structure = {} - for path, dirs, files in os.walk(dst_folder): - dir_structure[path] = list(files) - - # record folder structure - structure_filename = os.path.join(dirname, "dir_structure_expected.tar.gz") - json_to_gzip(dir_structure, structure_filename) - dir_structure_expected = gzip_to_json(structure_filename) - assert dir_structure == dir_structure_expected - - routernames = ["as1r1", "as20r3"] - config_files = ["bgpd.conf", "ospfd.conf", "zebra.conf"] - - for routername in routernames: - router = nidb.node(routername) - zebra_dir = os.path.join(router.render.base_dst_folder, "etc", "zebra") - - for conf_file in config_files: - expected_filename = os.path.join(dirname, "%s_%s" % (routername, conf_file)) - with open(expected_filename, "r") as fh: - expected_result = fh.read() - - actual_filename = os.path.join(zebra_dir, conf_file) - with open(actual_filename, "r") as fh: - actual_result = fh.read() - - if expected_result != actual_result: - if automated: - #TODO: use difflib - print "Expected" - print expected_result - print "Actual" - print actual_result - raise AssertionError("Invalid result") - else: - cmd = ["ksdiff", expected_filename, actual_filename] - child = subprocess.Popen(cmd) - answer = raw_input("Merge (answer yes to merge): ") - if answer == "yes": - print "Replacing expected with output" - shutil.move(actual_filename, expected_filename) diff --git a/tests/test_2.py b/tests/test_2.py deleted file mode 100644 index e1700eaa..00000000 --- a/tests/test_2.py +++ /dev/null @@ -1,28 +0,0 @@ -import autonetkit -import autonetkit.log as log - - -anm = autonetkit.NetworkModel() -g_phy = anm.add_overlay("phy") -for index in range(5): - node_id = "r_%s" % index - g_phy.add_node(node_id) - -print g_phy.nodes() -for node in g_phy: - print node - print node._ports - for interface in range(3): - node.add_interface() - -sw = g_phy.add_node("sw1") -sw.device_type = "switch" - -for node in g_phy: - for iface in node: - g_phy.add_edge(sw, iface) - print sw.edges() - - -for edge in g_phy.edges(): - print edge._ports diff --git a/tests/test_console_script.py b/tests/test_console_script.py deleted file mode 100644 index fc82547a..00000000 --- a/tests/test_console_script.py +++ /dev/null @@ -1,24 +0,0 @@ -import autonetkit.console_script as console_script -import subprocess -import os -import sys -#from cStringIO import StringIO -import shutil -import autonetkit.log as log -import autonetkit - -def test(): - # stdio redirect from stackoverflow.com/q/2654834 - - #TODO: add feature that reports if only IP addresses have changed: match the diff to an IP regex - dirname, filename = os.path.split(os.path.abspath(__file__)) - - input_file = os.path.join(dirname, "small_internet.graphml") - print input_file - - arg_string = "-f %s --diff --render" % input_file - args = console_script.parse_options(arg_string) - - console_script.main(args) - - #TODO: test output works \ No newline at end of file diff --git a/tests/test_graphml.py b/tests/test_graphml.py deleted file mode 100644 index 9d327236..00000000 --- a/tests/test_graphml.py +++ /dev/null @@ -1,75 +0,0 @@ -import autonetkit.console_script as console_script -import subprocess -import os -import sys -#from cStringIO import StringIO -import shutil -import autonetkit.log as log -import autonetkit -import autonetkit.load.graphml as graphml - - -# stdio redirect from stackoverflow.com/q/2654834 - -#TODO: add feature that reports if only IP addresses have changed: match the diff to an IP regex - -def compare_output_expected(topology_name, automated = True): - log.info("Testing %s" % topology_name) - input_filename = "%s.graphml" % topology_name - - dirname, filename = os.path.split(os.path.abspath(__file__)) - input_file = os.path.join(dirname, input_filename) - - arg_string = "-f %s --quiet --render" % input_file - args = console_script.parse_options(arg_string) - - console_script.main(args) - - #TODO: check the output files - -topologies = [ -"small_internet", -"Aarnet", -"multigraph", -] - - -automated = True # whether to open ksdiff, log to file... -if __name__ == "__main__": - automated = False - -for topology in topologies: - print "Testing topology", topology - compare_output_expected(topology, automated = automated) - - -# special case testing -def build_anm(topology_name): - print "Building anm for %s" % topology_name - dirname, filename = os.path.split(os.path.abspath(__file__)) - input_filename = os.path.join(dirname, "%s.graphml" % topology_name) - - anm = autonetkit.NetworkModel() - input_graph = graphml.load_graphml(input_filename) - - import autonetkit.build_network as build_network - anm = build_network.initialise(input_graph) - anm = build_network.apply_design_rules(anm) - return anm - -def test(): - anm = build_anm("blank_labels") - actual_labels = sorted(anm['phy'].nodes()) - expected_labels = ["none___0", "none___1", "none___2"] - assert(actual_labels == expected_labels) - - anm = build_anm("duplicate_labels") - actual_labels = sorted(anm['phy'].nodes()) - expected_labels = ["none___0", "none___1", "none___2"] - #TODO: need to log this as a bug - #assert(actual_labels == expected_labels) - - anm = build_anm("asn_zero") - actual_asns = [n.asn for n in anm['phy']] - expected_asns = [1, 1, 1] - assert(actual_asns == expected_asns) \ No newline at end of file diff --git a/tests/test_json_load.py b/tests/test_json_load.py deleted file mode 100644 index 64221ddb..00000000 --- a/tests/test_json_load.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import autonetkit -from autonetkit.load.load_json import simple_to_nx, nx_to_simple - -house = autonetkit.example.house() -data = house['phy']._graph -s1 = nx_to_simple(data) - -with open("house.json", "w") as fh: - import json - fh.write(json.dumps(s1, sort_keys=True, indent=2)) - -nx1 = simple_to_nx(s1) -s2 = nx_to_simple(nx1) -nx2 = simple_to_nx(s2) - -graph = nx2 -print graph.nodes(data=True) -print graph.edges(data=True) - diff --git a/tests/test_serialization.py b/tests/test_serialization.py deleted file mode 100644 index c6b31e00..00000000 --- a/tests/test_serialization.py +++ /dev/null @@ -1,45 +0,0 @@ -import autonetkit -import os -import autonetkit.load.graphml as graphml -import shutil - -def test(): - - - automated = True # whether to open ksdiff, log to file... - if __name__ == "__main__": - automated = False - - dirname, filename = os.path.split(os.path.abspath(__file__)) - - anm = autonetkit.NetworkModel() - input_file = os.path.join(dirname, "small_internet.graphml") - input_graph = graphml.load_graphml(input_file) - - import autonetkit.build_network as build_network - anm = build_network.initialise(input_graph) - anm = build_network.apply_design_rules(anm) - anm.save() - anm_restored = autonetkit.NetworkModel() - anm_restored.restore_latest() - g_phy_original = anm['phy'] - g_phy_restored = anm_restored['phy'] - assert(all(n in g_phy_restored for n in g_phy_original)) - - #TODO: do more extensive deep check of parameters - - import autonetkit.workflow as workflow - render_hostname = "localhost" - - nidb = workflow.create_nidb(anm) - import autonetkit.compilers.platform.netkit as pl_netkit - nk_compiler = pl_netkit.NetkitCompiler(nidb, anm, render_hostname) - nk_compiler.compile() - - nidb.save() - nidb_restored = autonetkit.DeviceModel() - nidb_restored.restore_latest() - assert(all(n in nidb_restored for n in nidb)) - - # cleanup - shutil.rmtree("versions") diff --git a/tests/webserver/expected_nidb.json b/tests/webserver/expected_nidb.json deleted file mode 100644 index 4b2c0828..00000000 --- a/tests/webserver/expected_nidb.json +++ /dev/null @@ -1 +0,0 @@ -{"directed": false, "graph": [["topologies", {"localhost": {"tap_host": "172.16.0.1", "web": "www.autonetkit.org", "render_dst_file": "lab.conf", "description": "AutoNetkit Lab", "author": "AutoNetkit", "render_dst_folder": "rendered/localhost/netkit", "tap_ips": [{"device": "as1r1", "ip": "172.16.0.3", "id": "3"}, {"device": "as20r1", "ip": "172.16.0.4", "id": "5"}, {"device": "as20r2", "ip": "172.16.0.5", "id": "3"}, {"device": "as20r3", "ip": "172.16.0.6", "id": "3"}, {"device": "as30r1", "ip": "172.16.0.7", "id": "2"}, {"device": "as40r1", "ip": "172.16.0.8", "id": "2"}, {"device": "as100r1", "ip": "172.16.0.9", "id": "4"}, {"device": "as100r2", "ip": "172.16.0.10", "id": "2"}, {"device": "as100r3", "ip": "172.16.0.11", "id": "2"}, {"device": "as200r1", "ip": "172.16.0.12", "id": "1"}, {"device": "as300r1", "ip": "172.16.0.13", "id": "1"}, {"device": "as300r2", "ip": "172.16.0.14", "id": "2"}, {"device": "as300r3", "ip": "172.16.0.15", "id": "2"}, {"device": "as300r4", "ip": "172.16.0.16", "id": "2"}], "tap_vm": "172.16.0.2", "render_template": "templates/netkit_lab_conf.mako", "config_items": [{"device": "as100r1", "value": "10.2.0.4.30", "key": 0}, {"device": "as100r1", "value": "10.2.128.0.30", "key": 1}, {"device": "as100r1", "value": "10.2.0.0.30", "key": 2}, {"device": "as100r1", "value": "10.4.128.0.30", "key": 3}, {"device": "as100r2", "value": "10.2.0.4.30", "key": 0}, {"device": "as100r2", "value": "10.2.128.4.30", "key": 1}, {"device": "as100r3", "value": "10.2.128.0.30", "key": 0}, {"device": "as100r3", "value": "10.2.128.4.30", "key": 1}, {"device": "as1r1", "value": "10.4.0.0.30", "key": 0}, {"device": "as1r1", "value": "10.0.0.0.17", "key": 1}, {"device": "as1r1", "value": "10.1.0.0.16", "key": 2}, {"device": "as200r1", "value": "10.3.0.0.16", "key": 0}, {"device": "as20r1", "value": "10.3.0.0.16", "key": 0}, {"device": "as20r1", "value": "10.2.0.0.30", "key": 1}, {"device": "as20r1", "value": "10.0.128.0.17", "key": 2}, {"device": "as20r1", "value": "10.4.0.4.30", "key": 3}, {"device": "as20r1", "value": "10.4.0.8.30", "key": 4}, {"device": "as20r2", "value": "10.4.128.0.30", "key": 0}, {"device": "as20r2", "value": "10.4.0.4.30", "key": 1}, {"device": "as20r2", "value": "10.4.0.12.30", "key": 2}, {"device": "as20r3", "value": "10.4.0.12.30", "key": 0}, {"device": "as20r3", "value": "10.4.0.0.30", "key": 1}, {"device": "as20r3", "value": "10.4.0.8.30", "key": 2}, {"device": "as300r1", "value": "10.5.0.0.30", "key": 0}, {"device": "as300r2", "value": "10.5.0.4.30", "key": 0}, {"device": "as300r2", "value": "10.5.128.0.30", "key": 1}, {"device": "as300r3", "value": "10.5.0.0.30", "key": 0}, {"device": "as300r3", "value": "10.5.128.4.30", "key": 1}, {"device": "as300r4", "value": "10.5.0.4.30", "key": 0}, {"device": "as300r4", "value": "10.5.128.4.30", "key": 1}, {"device": "as30r1", "value": "10.0.128.0.17", "key": 0}, {"device": "as30r1", "value": "10.0.0.0.17", "key": 1}, {"device": "as40r1", "value": "10.1.0.0.16", "key": 0}, {"device": "as40r1", "value": "10.5.128.0.30", "key": 1}], "machines": "as1r1 as20r1 as20r2 as20r3 as30r1 as40r1 as100r1 as100r2 as100r3 as200r1 as300r1 as300r2 as300r3 as300r4"}}], ["timestamp", "123456"]], "nodes": [{"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as300r1_as300r3", "y": 371.41619805610765, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 231.41886049299083, "x": 421.9775321775071, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.5.0.0/30", "x": 504.26496614181326, "id": "cdas300r1_as300r3"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r2_as20r3", "y": 91.1, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": -48.89733756311684, "x": 55.607268022448785, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.4.0.12/30", "x": 137.89470198675494, "id": "cdas20r2_as20r3"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r1_as100r1", "y": 227.63474110246526, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 87.63740353934841, "x": 85.60726802244878, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.2.0.0/30", "x": 167.89470198675494, "id": "cdas100r1_as20r1"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r2_as100r1", "y": 227.63474110246526, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 87.63740353934841, "x": 25.607268022448785, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.4.128.0/30", "x": 107.89470198675494, "id": "cdas100r1_as20r2"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r1_as20r3", "y": 91.1, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": -48.89733756311684, "x": 115.60726802244878, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.4.0.8/30", "x": 197.89470198675494, "id": "cdas20r1_as20r3"}, {"tap": {"ip": "172.16.0.14", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as300r2", "dst_file": "as300r2.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.42/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as300r2", "id": "as300r2", "_interfaces": {"1": {"numeric_id": 0, "description": "to as300r4", "ipv4_cidr": "10.5.0.5/30", "ipv4_address": "10.5.0.5", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.5.0.4/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.42", "ipv4_subnet": "10.0.0.42/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as40r1", "ipv4_cidr": "10.5.128.2/30", "ipv4_address": "10.5.128.2", "use_ipv4": true, "ipv4_subnet": "10.5.128.0/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as300r2", "label": "as300r2", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 194.07214464181368, "x": 516.4816506372013, "device_type": "router", "device_subtype": null}, "asn": 300, "loopback": "10.0.0.42", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.21", "dst_int_ip": "10.5.128.1", "use_ipv4": true, "neighbor": "as40r1", "use_ipv6": false, "local_int_ip": "10.5.128.2", "asn": 40}], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.41", "use_ipv4": true, "neighbor": "as300r1", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.43", "use_ipv4": true, "neighbor": "as300r3", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.44", "use_ipv4": true, "neighbor": "as300r4", "use_ipv6": false, "asn": 300}], "debug": true, "ipv4_advertise_subnets": ["10.5.0.0/16"], "ibgp_rr_clients": []}, "y": 334.0694822049305, "x": 598.7690846015074, "ospf": {"ospf_links": [{"network": "10.5.0.4/30", "area": 0}, {"network": "10.5.128.0/30", "area": 0}], "passive_interfaces": [{"id": "eth1"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as300r3_as300r4", "y": 408.66291390728486, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 268.665576344168, "x": 469.2795914073542, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.5.128.4/30", "x": 551.5670253716603, "id": "cdas300r3_as300r4"}, {"tap": {"ip": "172.16.0.9", "id": "eth4"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as100r1", "dst_file": "as100r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.1/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as100r1", "id": "as100r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as100r2", "ipv4_cidr": "10.2.0.6/30", "ipv4_address": "10.2.0.6", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.2.0.4/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.1", "ipv4_subnet": "10.0.0.1/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "3": {"numeric_id": 2, "description": "to as20r1", "ipv4_cidr": "10.2.0.1/30", "ipv4_address": "10.2.0.1", "use_ipv4": true, "ipv4_subnet": "10.2.0.0/30", "id_brief": "eth2", "type": "physical", "id": "eth2", "physical": true}, "2": {"numeric_id": 1, "description": "to as100r3", "ipv4_cidr": "10.2.128.1/30", "ipv4_address": "10.2.128.1", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.2.128.0/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}, "4": {"numeric_id": 3, "description": "to as20r2", "ipv4_cidr": "10.4.128.1/30", "ipv4_address": "10.4.128.1", "use_ipv4": true, "ipv4_subnet": "10.4.128.0/30", "id_brief": "eth3", "type": "physical", "id": "eth3", "physical": true}}, "device_subtype": null, "hostname": "as100r1", "label": "as100r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 194.07214464181368, "x": 25.507268022448784, "device_type": "router", "device_subtype": null}, "asn": 100, "loopback": "10.0.0.1", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.33", "dst_int_ip": "10.2.0.2", "use_ipv4": true, "neighbor": "as20r1", "use_ipv6": false, "local_int_ip": "10.2.0.1", "asn": 20}, {"update_source": null, "loopback": "10.0.0.34", "dst_int_ip": "10.4.128.2", "use_ipv4": true, "neighbor": "as20r2", "use_ipv6": false, "local_int_ip": "10.4.128.1", "asn": 20}], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.2", "use_ipv4": true, "neighbor": "as100r2", "use_ipv6": false, "asn": 100}, {"update_source": null, "loopback": "10.0.0.3", "use_ipv4": true, "neighbor": "as100r3", "use_ipv6": false, "asn": 100}], "debug": true, "ipv4_advertise_subnets": ["10.2.0.0/16"], "ibgp_rr_clients": []}, "y": 334.0694822049305, "x": 107.79470198675494, "ospf": {"ospf_links": [{"network": "10.2.0.4/30", "area": 0}, {"network": "10.2.128.0/30", "area": 0}, {"network": "10.2.0.0/30", "area": 0}, {"network": "10.4.128.0/30", "area": 0}], "passive_interfaces": [{"id": "eth2"}, {"id": "eth3"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"tap": {"ip": "172.16.0.10", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as100r2", "dst_file": "as100r2.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.2/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as100r2", "id": "as100r2", "_interfaces": {"1": {"numeric_id": 0, "description": "to as100r1", "ipv4_cidr": "10.2.0.5/30", "ipv4_address": "10.2.0.5", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.2.0.4/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.2", "ipv4_subnet": "10.0.0.2/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as100r3", "ipv4_cidr": "10.2.128.5/30", "ipv4_address": "10.2.128.5", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.2.128.4/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as100r2", "label": "as100r2", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 194.07214464181368, "x": -82.28743396430616, "device_type": "router", "device_subtype": null}, "asn": 100, "loopback": "10.0.0.2", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.1", "use_ipv4": true, "neighbor": "as100r1", "use_ipv6": false, "asn": 100}, {"update_source": null, "loopback": "10.0.0.3", "use_ipv4": true, "neighbor": "as100r3", "use_ipv6": false, "asn": 100}], "debug": true, "ipv4_advertise_subnets": ["10.2.0.0/16"], "ibgp_rr_clients": []}, "y": 334.0694822049305, "x": 0.0, "ospf": {"ospf_links": [{"network": "10.2.0.4/30", "area": 0}, {"network": "10.2.128.4/30", "area": 0}], "passive_interfaces": [], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"tap": {"ip": "172.16.0.11", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as100r3", "dst_file": "as100r3.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.3/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as100r3", "id": "as100r3", "_interfaces": {"1": {"numeric_id": 0, "description": "to as100r1", "ipv4_cidr": "10.2.128.2/30", "ipv4_address": "10.2.128.2", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.2.128.0/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.3", "ipv4_subnet": "10.0.0.3/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as100r2", "ipv4_cidr": "10.2.128.6/30", "ipv4_address": "10.2.128.6", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.2.128.4/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as100r3", "label": "as100r3", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 254.07214464181368, "x": -34.492731977551216, "device_type": "router", "device_subtype": null}, "asn": 100, "loopback": "10.0.0.3", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.1", "use_ipv4": true, "neighbor": "as100r1", "use_ipv6": false, "asn": 100}, {"update_source": null, "loopback": "10.0.0.2", "use_ipv4": true, "neighbor": "as100r2", "use_ipv6": false, "asn": 100}], "debug": true, "ipv4_advertise_subnets": ["10.2.0.0/16"], "ibgp_rr_clients": []}, "y": 394.0694822049305, "x": 47.794701986754944, "ospf": {"ospf_links": [{"network": "10.2.128.0/30", "area": 0}, {"network": "10.2.128.4/30", "area": 0}], "passive_interfaces": [], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r1_as20r2", "y": 121.1, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": -18.897337563116842, "x": 85.60726802244878, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.4.0.4/30", "x": 167.89470198675494, "id": "cdas20r1_as20r2"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as100r1_as100r2", "y": 334.16948220493055, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 194.17214464181367, "x": -28.290082970928687, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.2.0.4/30", "x": 53.997350993377474, "id": "cdas100r1_as100r2"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as100r1_as100r3", "y": 364.16948220493055, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 224.17214464181367, "x": -4.392731977551216, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.2.128.0/30", "x": 77.89470198675494, "id": "cdas100r1_as100r3"}, {"tap": {"ip": "172.16.0.5", "id": "eth3"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as20r2", "dst_file": "as20r2.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.34/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as20r2", "id": "as20r2", "_interfaces": {"1": {"numeric_id": 0, "description": "to as100r1", "ipv4_cidr": "10.4.128.2/30", "ipv4_address": "10.4.128.2", "use_ipv4": true, "ipv4_subnet": "10.4.128.0/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.34", "ipv4_subnet": "10.0.0.34/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "3": {"numeric_id": 2, "description": "to as20r3", "ipv4_cidr": "10.4.0.13/30", "ipv4_address": "10.4.0.13", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.4.0.12/30", "id_brief": "eth2", "type": "physical", "id": "eth2", "physical": true}, "2": {"numeric_id": 1, "description": "to as20r1", "ipv4_cidr": "10.4.0.5/30", "ipv4_address": "10.4.0.5", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.4.0.4/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as20r2", "label": "as20r2", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": -18.997337563116844, "x": 25.507268022448784, "device_type": "router", "device_subtype": null}, "asn": 20, "loopback": "10.0.0.34", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.1", "dst_int_ip": "10.4.128.1", "use_ipv4": true, "neighbor": "as100r1", "use_ipv6": false, "local_int_ip": "10.4.128.2", "asn": 100}], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.33", "use_ipv4": true, "neighbor": "as20r1", "use_ipv6": false, "asn": 20}, {"update_source": null, "loopback": "10.0.0.35", "use_ipv4": true, "neighbor": "as20r3", "use_ipv6": false, "asn": 20}], "debug": true, "ipv4_advertise_subnets": ["10.4.0.0/16"], "ibgp_rr_clients": []}, "y": 121.0, "x": 107.79470198675494, "ospf": {"ospf_links": [{"network": "10.4.0.4/30", "area": 0}, {"network": "10.4.0.12/30", "area": 0}, {"network": "10.4.128.0/30", "area": 0}], "passive_interfaces": [{"id": "eth0"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"tap": {"ip": "172.16.0.6", "id": "eth3"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as20r3", "dst_file": "as20r3.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.35/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as20r3", "id": "as20r3", "_interfaces": {"1": {"numeric_id": 0, "description": "to as20r2", "ipv4_cidr": "10.4.0.14/30", "ipv4_address": "10.4.0.14", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.4.0.12/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.35", "ipv4_subnet": "10.0.0.35/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "3": {"numeric_id": 2, "description": "to as20r1", "ipv4_cidr": "10.4.0.10/30", "ipv4_address": "10.4.0.10", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.4.0.8/30", "id_brief": "eth2", "type": "physical", "id": "eth2", "physical": true}, "2": {"numeric_id": 1, "description": "to as1r1", "ipv4_cidr": "10.4.0.2/30", "ipv4_address": "10.4.0.2", "use_ipv4": true, "ipv4_subnet": "10.4.0.0/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as20r3", "label": "as20r3", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": -78.99733756311684, "x": 85.50726802244878, "device_type": "router", "device_subtype": null}, "asn": 20, "loopback": "10.0.0.35", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.17", "dst_int_ip": "10.4.0.1", "use_ipv4": true, "neighbor": "as1r1", "use_ipv6": false, "local_int_ip": "10.4.0.2", "asn": 1}], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.33", "use_ipv4": true, "neighbor": "as20r1", "use_ipv6": false, "asn": 20}, {"update_source": null, "loopback": "10.0.0.34", "use_ipv4": true, "neighbor": "as20r2", "use_ipv6": false, "asn": 20}], "debug": true, "ipv4_advertise_subnets": ["10.4.0.0/16"], "ibgp_rr_clients": []}, "y": 61.0, "x": 167.79470198675494, "ospf": {"ospf_links": [{"network": "10.4.0.12/30", "area": 0}, {"network": "10.4.0.8/30", "area": 0}, {"network": "10.4.0.0/30", "area": 0}], "passive_interfaces": [{"id": "eth1"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"tap": {"ip": "172.16.0.4", "id": "eth5"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as20r1", "dst_file": "as20r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.33/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as20r1", "id": "as20r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as200r1", "ipv4_cidr": "10.3.0.2/16", "ipv4_address": "10.3.0.2", "use_ipv4": true, "ipv4_subnet": "10.3.0.0/16", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.33", "ipv4_subnet": "10.0.0.33/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "3": {"numeric_id": 2, "description": "to as30r1", "ipv4_cidr": "10.0.128.1/17", "ipv4_address": "10.0.128.1", "use_ipv4": true, "ipv4_subnet": "10.0.128.0/17", "id_brief": "eth2", "type": "physical", "id": "eth2", "physical": true}, "2": {"numeric_id": 1, "description": "to as100r1", "ipv4_cidr": "10.2.0.2/30", "ipv4_address": "10.2.0.2", "use_ipv4": true, "ipv4_subnet": "10.2.0.0/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}, "5": {"numeric_id": 4, "description": "to as20r3", "ipv4_cidr": "10.4.0.9/30", "ipv4_address": "10.4.0.9", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.4.0.8/30", "id_brief": "eth4", "type": "physical", "id": "eth4", "physical": true}, "4": {"numeric_id": 3, "description": "to as20r2", "ipv4_cidr": "10.4.0.6/30", "ipv4_address": "10.4.0.6", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.4.0.4/30", "id_brief": "eth3", "type": "physical", "id": "eth3", "physical": true}}, "device_subtype": null, "hostname": "as20r1", "label": "as20r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": -18.997337563116844, "x": 145.50726802244878, "device_type": "router", "device_subtype": null}, "asn": 20, "loopback": "10.0.0.33", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.9", "dst_int_ip": "10.0.128.2", "use_ipv4": true, "neighbor": "as30r1", "use_ipv6": false, "local_int_ip": "10.0.128.1", "asn": 30}, {"update_source": null, "loopback": "10.0.0.1", "dst_int_ip": "10.2.0.1", "use_ipv4": true, "neighbor": "as100r1", "use_ipv6": false, "local_int_ip": "10.2.0.2", "asn": 100}, {"update_source": null, "loopback": "10.0.0.13", "dst_int_ip": "10.3.0.1", "use_ipv4": true, "neighbor": "as200r1", "use_ipv6": false, "local_int_ip": "10.3.0.2", "asn": 200}], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.34", "use_ipv4": true, "neighbor": "as20r2", "use_ipv6": false, "asn": 20}, {"update_source": null, "loopback": "10.0.0.35", "use_ipv4": true, "neighbor": "as20r3", "use_ipv6": false, "asn": 20}], "debug": true, "ipv4_advertise_subnets": ["10.4.0.0/16"], "ibgp_rr_clients": []}, "y": 121.0, "x": 227.79470198675494, "ospf": {"ospf_links": [{"network": "10.4.0.4/30", "area": 0}, {"network": "10.4.0.8/30", "area": 0}, {"network": "10.3.0.0/16", "area": 0}, {"network": "10.2.0.0/30", "area": 0}, {"network": "10.0.128.0/17", "area": 0}], "passive_interfaces": [{"id": "eth0"}, {"id": "eth1"}, {"id": "eth2"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as1r1_as30r1", "y": 112.69602649006626, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": -27.301311073050577, "x": 335.53359252576007, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.0.0.0/17", "x": 417.82102649006623, "id": "cdas1r1_as30r1"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as100r2_as100r3", "y": 364.16948220493055, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 224.17214464181367, "x": -58.29008297092869, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.2.128.4/30", "x": 23.997350993377474, "id": "cdas100r2_as100r3"}, {"tap": {"ip": "172.16.0.16", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as300r4", "dst_file": "as300r4.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.44/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as300r4", "id": "as300r4", "_interfaces": {"1": {"numeric_id": 0, "description": "to as300r2", "ipv4_cidr": "10.5.0.6/30", "ipv4_address": "10.5.0.6", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.5.0.4/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.44", "ipv4_subnet": "10.0.0.44/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as300r3", "ipv4_cidr": "10.5.128.6/30", "ipv4_address": "10.5.128.6", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.5.128.4/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as300r4", "label": "as300r4", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 268.565576344168, "x": 516.4816506372013, "device_type": "router", "device_subtype": null}, "asn": 300, "loopback": "10.0.0.44", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.41", "use_ipv4": true, "neighbor": "as300r1", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.42", "use_ipv4": true, "neighbor": "as300r2", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.43", "use_ipv4": true, "neighbor": "as300r3", "use_ipv6": false, "asn": 300}], "debug": true, "ipv4_advertise_subnets": ["10.5.0.0/16"], "ibgp_rr_clients": []}, "y": 408.56291390728484, "x": 598.7690846015074, "ospf": {"ospf_links": [{"network": "10.5.0.4/30", "area": 0}, {"network": "10.5.128.4/30", "area": 0}], "passive_interfaces": [], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as40r1_as300r2", "y": 279.7307675925315, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 139.73343002941468, "x": 484.09445932982504, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.5.128.0/30", "x": 566.3818932941313, "id": "cdas300r2_as40r1"}, {"tap": {"ip": "172.16.0.15", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as300r3", "dst_file": "as300r3.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.43/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as300r3", "id": "as300r3", "_interfaces": {"1": {"numeric_id": 0, "description": "to as300r1", "ipv4_cidr": "10.5.0.2/30", "ipv4_address": "10.5.0.2", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.5.0.0/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.43", "ipv4_subnet": "10.0.0.43/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as300r4", "ipv4_cidr": "10.5.128.5/30", "ipv4_address": "10.5.128.5", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.5.128.4/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as300r3", "label": "as300r3", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 268.565576344168, "x": 421.8775321775071, "device_type": "router", "device_subtype": null}, "asn": 300, "loopback": "10.0.0.43", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.41", "use_ipv4": true, "neighbor": "as300r1", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.42", "use_ipv4": true, "neighbor": "as300r2", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.44", "use_ipv4": true, "neighbor": "as300r4", "use_ipv6": false, "asn": 300}], "debug": true, "ipv4_advertise_subnets": ["10.5.0.0/16"], "ibgp_rr_clients": []}, "y": 408.56291390728484, "x": 504.16496614181324, "ospf": {"ospf_links": [{"network": "10.5.0.0/30", "area": 0}, {"network": "10.5.128.4/30", "area": 0}], "passive_interfaces": [], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as1r1_as40r1", "y": 112.69602649006626, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": -27.301311073050577, "x": 398.9822680224488, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.1.0.0/16", "x": 481.26970198675497, "id": "cdas1r1_as40r1"}, {"tap": {"ip": "172.16.0.13", "id": "eth1"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as300r1", "dst_file": "as300r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.41/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as300r1", "id": "as300r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as300r3", "ipv4_cidr": "10.5.0.1/30", "ipv4_address": "10.5.0.1", "ospf_cost": 1, "use_ipv4": true, "ipv4_subnet": "10.5.0.0/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.41", "ipv4_subnet": "10.0.0.41/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}}, "device_subtype": null, "hostname": "as300r1", "label": "as300r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 194.07214464181368, "x": 421.8775321775071, "device_type": "router", "device_subtype": null}, "asn": 300, "loopback": "10.0.0.41", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [], "ibgp_neighbors": [{"update_source": null, "loopback": "10.0.0.42", "use_ipv4": true, "neighbor": "as300r2", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.43", "use_ipv4": true, "neighbor": "as300r3", "use_ipv6": false, "asn": 300}, {"update_source": null, "loopback": "10.0.0.44", "use_ipv4": true, "neighbor": "as300r4", "use_ipv6": false, "asn": 300}], "debug": true, "ipv4_advertise_subnets": ["10.5.0.0/16"], "ibgp_rr_clients": []}, "y": 334.0694822049305, "x": 504.16496614181324, "ospf": {"ospf_links": [{"network": "10.5.0.0/30", "area": 0}], "passive_interfaces": [], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r1_as30r1", "y": 173.19602649006626, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 33.19868892694942, "x": 235.15859252576004, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.0.128.0/17", "x": 317.4460264900662, "id": "cdas20r1_as30r1"}, {"tap": {"ip": "172.16.0.8", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as40r1", "dst_file": "as40r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.21/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as40r1", "id": "as40r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as1r1", "ipv4_cidr": "10.1.0.2/16", "ipv4_address": "10.1.0.2", "use_ipv4": true, "ipv4_subnet": "10.1.0.0/16", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.21", "ipv4_subnet": "10.0.0.21/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as300r2", "ipv4_cidr": "10.5.128.1/30", "ipv4_address": "10.5.128.1", "use_ipv4": true, "ipv4_subnet": "10.5.128.0/30", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as40r1", "label": "as40r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 85.19471541701569, "x": 451.5072680224488, "device_type": "router", "device_subtype": null}, "asn": 40, "loopback": "10.0.0.21", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.17", "dst_int_ip": "10.1.0.1", "use_ipv4": true, "neighbor": "as1r1", "use_ipv6": false, "local_int_ip": "10.1.0.2", "asn": 1}, {"update_source": null, "loopback": "10.0.0.42", "dst_int_ip": "10.5.128.2", "use_ipv4": true, "neighbor": "as300r2", "use_ipv6": false, "local_int_ip": "10.5.128.1", "asn": 300}], "ibgp_neighbors": [], "debug": true, "ipv4_advertise_subnets": ["10.1.0.0/16"], "ibgp_rr_clients": []}, "y": 225.19205298013253, "x": 533.794701986755, "ospf": {"ospf_links": [{"network": "10.1.0.0/16", "area": 0}, {"network": "10.5.128.0/30", "area": 0}], "passive_interfaces": [{"id": "eth0"}, {"id": "eth1"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"tap": {"ip": "172.16.0.3", "id": "eth3"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as1r1", "dst_file": "as1r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.17/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as1r1", "id": "as1r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as20r3", "ipv4_cidr": "10.4.0.1/30", "ipv4_address": "10.4.0.1", "use_ipv4": true, "ipv4_subnet": "10.4.0.0/30", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.17", "ipv4_subnet": "10.0.0.17/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "3": {"numeric_id": 2, "description": "to as40r1", "ipv4_cidr": "10.1.0.1/16", "ipv4_address": "10.1.0.1", "use_ipv4": true, "ipv4_subnet": "10.1.0.0/16", "id_brief": "eth2", "type": "physical", "id": "eth2", "physical": true}, "2": {"numeric_id": 1, "description": "to as30r1", "ipv4_cidr": "10.0.0.2/17", "ipv4_address": "10.0.0.2", "use_ipv4": true, "ipv4_subnet": "10.0.0.0/17", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as1r1", "label": "as1r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": -139.99733756311684, "x": 346.2572680224488, "device_type": "router", "device_subtype": null}, "asn": 1, "loopback": "10.0.0.17", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.35", "dst_int_ip": "10.4.0.2", "use_ipv4": true, "neighbor": "as20r3", "use_ipv6": false, "local_int_ip": "10.4.0.1", "asn": 20}, {"update_source": null, "loopback": "10.0.0.9", "dst_int_ip": "10.0.0.1", "use_ipv4": true, "neighbor": "as30r1", "use_ipv6": false, "local_int_ip": "10.0.0.2", "asn": 30}, {"update_source": null, "loopback": "10.0.0.21", "dst_int_ip": "10.1.0.2", "use_ipv4": true, "neighbor": "as40r1", "use_ipv6": false, "local_int_ip": "10.1.0.1", "asn": 40}], "ibgp_neighbors": [], "debug": true, "ipv4_advertise_subnets": [], "ibgp_rr_clients": []}, "y": 0.0, "x": 428.54470198675494, "ospf": {"ospf_links": [{"network": "10.4.0.0/30", "area": 0}, {"network": "10.0.0.0/17", "area": 0}, {"network": "10.1.0.0/16", "area": 0}], "passive_interfaces": [{"id": "eth0"}, {"id": "eth1"}, {"id": "eth2"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as1r1_as20r3", "y": 30.599999999999994, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": -109.39733756311685, "x": 215.98226802244878, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.4.0.0/30", "x": 298.26970198675497, "id": "cdas1r1_as20r3"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as20r1_as200r1", "y": 173.19602649006626, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 33.19868892694942, "x": 171.7099170290713, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.3.0.0/16", "x": 253.99735099337747, "id": "cdas200r1_as20r1"}, {"_interfaces": {}, "device_subtype": null, "collision_domain": true, "ipv6_subnet": null, "label": "cd_as300r2_as300r4", "y": 371.41619805610765, "host": "localhost", "device_type": "collision_domain", "graphics": {"y": 231.41886049299083, "x": 516.5816506372013, "device_type": "collision_domain", "device_subtype": null}, "ipv4_subnet": "10.5.0.4/30", "x": 598.8690846015074, "id": "cdas300r2_as300r4"}, {"tap": {"ip": "172.16.0.12", "id": "eth1"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as200r1", "dst_file": "as200r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.13/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as200r1", "id": "as200r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as20r1", "ipv4_cidr": "10.3.0.1/16", "ipv4_address": "10.3.0.1", "use_ipv4": true, "ipv4_subnet": "10.3.0.0/16", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.13", "ipv4_subnet": "10.0.0.13/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}}, "device_subtype": null, "hostname": "as200r1", "label": "as200r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 85.19471541701569, "x": 197.71256603569384, "device_type": "router", "device_subtype": null}, "asn": 200, "loopback": "10.0.0.13", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.33", "dst_int_ip": "10.3.0.2", "use_ipv4": true, "neighbor": "as20r1", "use_ipv6": false, "local_int_ip": "10.3.0.1", "asn": 20}], "ibgp_neighbors": [], "debug": true, "ipv4_advertise_subnets": ["10.3.0.0/16"], "ibgp_rr_clients": []}, "y": 225.19205298013253, "x": 280.0, "ospf": {"ospf_links": [{"network": "10.3.0.0/16", "area": 0}], "passive_interfaces": [{"id": "eth0"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}, {"tap": {"ip": "172.16.0.7", "id": "eth2"}, "Network": null, "render": {"base_dst_folder": "rendered/localhost/netkit/as30r1", "dst_file": "as30r1.startup", "custom": {"abc": "def.txt"}, "dst_folder": "rendered/localhost/netkit", "base": "templates/quagga", "template": "templates/netkit_startup.mako"}, "ip": {"use_ipv4": true, "use_ipv6": false}, "loopback_subnet": "10.0.0.9/32", "zebra": {"password": "1234", "static_routes": []}, "device_type": "router", "input_label": "as30r1", "id": "as30r1", "_interfaces": {"1": {"numeric_id": 0, "description": "to as20r1", "ipv4_cidr": "10.0.128.2/17", "ipv4_address": "10.0.128.2", "use_ipv4": true, "ipv4_subnet": "10.0.128.0/17", "id_brief": "eth0", "type": "physical", "id": "eth0", "physical": true}, "0": {"description": "Loopback", "ipv4_address": "10.0.0.9", "ipv4_subnet": "10.0.0.9/32", "id_brief": "lo0:1", "type": "loopback", "id": "lo0:1"}, "2": {"numeric_id": 1, "description": "to as1r1", "ipv4_cidr": "10.0.0.1/17", "ipv4_address": "10.0.0.1", "use_ipv4": true, "ipv4_subnet": "10.0.0.0/17", "id_brief": "eth1", "type": "physical", "id": "eth1", "physical": true}}, "device_subtype": null, "hostname": "as30r1", "label": "as30r1", "platform": "netkit", "interfaces": [], "update": null, "host": "localhost", "ssh": {"use_key": true}, "graphics": {"y": 85.19471541701569, "x": 324.6099170290713, "device_type": "router", "device_subtype": null}, "asn": 30, "loopback": "10.0.0.9", "bgp": {"ibgp_rr_parents": [], "ipv6_advertise_subnets": [], "ebgp_neighbors": [{"update_source": null, "loopback": "10.0.0.17", "dst_int_ip": "10.0.0.2", "use_ipv4": true, "neighbor": "as1r1", "use_ipv6": false, "local_int_ip": "10.0.0.1", "asn": 1}, {"update_source": null, "loopback": "10.0.0.33", "dst_int_ip": "10.0.128.1", "use_ipv4": true, "neighbor": "as20r1", "use_ipv6": false, "local_int_ip": "10.0.128.2", "asn": 20}], "ibgp_neighbors": [], "debug": true, "ipv4_advertise_subnets": ["10.0.0.0/16"], "ibgp_rr_clients": []}, "y": 225.19205298013253, "x": 406.8973509933775, "ospf": {"ospf_links": [{"network": "10.0.128.0/17", "area": 0}, {"network": "10.0.0.0/17", "area": 0}], "passive_interfaces": [{"id": "eth0"}, {"id": "eth1"}], "process_id": 1, "loopback_area": 0, "lo_interface": "lo0:1"}}], "links": [{"_interfaces": {"as300r3": 1}, "source": 0, "target": 20, "edge_id": "as300r1_as300r3"}, {"_interfaces": {"as300r1": 1}, "source": 0, "target": 22, "edge_id": "as300r1_as300r3"}, {"_interfaces": {"as20r2": 3}, "source": 1, "target": 13, "edge_id": "as20r3_as20r2"}, {"_interfaces": {"as20r3": 1}, "source": 1, "target": 14, "edge_id": "as20r3_as20r2"}, {"_interfaces": {"as100r1": 3}, "source": 2, "target": 7, "edge_id": "as100r1_as20r1"}, {"_interfaces": {"as20r1": 2}, "source": 2, "target": 15, "edge_id": "as100r1_as20r1"}, {"_interfaces": {"as20r2": 1}, "source": 3, "target": 13, "edge_id": "as20r2_as100r1"}, {"_interfaces": {"as100r1": 4}, "source": 3, "target": 7, "edge_id": "as20r2_as100r1"}, {"_interfaces": {"as20r3": 3}, "source": 4, "target": 14, "edge_id": "as20r1_as20r3"}, {"_interfaces": {"as20r1": 5}, "source": 4, "target": 15, "edge_id": "as20r1_as20r3"}, {"_interfaces": {"as300r2": 1}, "source": 5, "target": 28, "edge_id": "as300r4_as300r2"}, {"_interfaces": {"as300r2": 2}, "source": 5, "target": 19, "edge_id": "as40r1_as300r2"}, {"_interfaces": {"as300r4": 2}, "source": 6, "target": 18, "edge_id": "as300r3_as300r4"}, {"_interfaces": {"as300r3": 2}, "source": 6, "target": 20, "edge_id": "as300r3_as300r4"}, {"_interfaces": {"as100r1": 1}, "source": 7, "target": 11, "edge_id": "as100r2_as100r1"}, {"_interfaces": {"as100r1": 2}, "source": 7, "target": 12, "edge_id": "as100r1_as100r3"}, {"_interfaces": {"as100r2": 1}, "source": 8, "target": 11, "edge_id": "as100r2_as100r1"}, {"_interfaces": {"as100r2": 2}, "source": 8, "target": 17, "edge_id": "as100r3_as100r2"}, {"_interfaces": {"as100r3": 1}, "source": 9, "target": 12, "edge_id": "as100r1_as100r3"}, {"_interfaces": {"as100r3": 2}, "source": 9, "target": 17, "edge_id": "as100r3_as100r2"}, {"_interfaces": {"as20r2": 2}, "source": 10, "target": 13, "edge_id": "as20r2_as20r1"}, {"_interfaces": {"as20r1": 4}, "source": 10, "target": 15, "edge_id": "as20r2_as20r1"}, {"_interfaces": {"as20r3": 2}, "source": 14, "target": 26, "edge_id": "as1r1_as20r3"}, {"_interfaces": {"as20r1": 1}, "source": 15, "target": 27, "edge_id": "as200r1_as20r1"}, {"_interfaces": {"as20r1": 3}, "source": 15, "target": 23, "edge_id": "as20r1_as30r1"}, {"_interfaces": {"as30r1": 2}, "source": 16, "target": 30, "edge_id": "as30r1_as1r1"}, {"_interfaces": {"as1r1": 2}, "source": 16, "target": 25, "edge_id": "as30r1_as1r1"}, {"_interfaces": {"as300r4": 1}, "source": 18, "target": 28, "edge_id": "as300r4_as300r2"}, {"_interfaces": {"as40r1": 2}, "source": 19, "target": 24, "edge_id": "as40r1_as300r2"}, {"_interfaces": {"as40r1": 1}, "source": 21, "target": 24, "edge_id": "as1r1_as40r1"}, {"_interfaces": {"as1r1": 3}, "source": 21, "target": 25, "edge_id": "as1r1_as40r1"}, {"_interfaces": {"as30r1": 1}, "source": 23, "target": 30, "edge_id": "as20r1_as30r1"}, {"_interfaces": {"as1r1": 1}, "source": 25, "target": 26, "edge_id": "as1r1_as20r3"}, {"_interfaces": {"as200r1": 1}, "source": 27, "target": 29, "edge_id": "as200r1_as20r1"}], "multigraph": false} \ No newline at end of file diff --git a/tests/webserver/expected_phy.json b/tests/webserver/expected_phy.json deleted file mode 100644 index 7b120adb..00000000 --- a/tests/webserver/expected_phy.json +++ /dev/null @@ -1 +0,0 @@ -{"directed": false, "graph": [["uuid", "my_uuid"]], "nodes": [{"_interfaces": {"1": {"type": "physical", "description": "as20r2 to as100r1"}, "0": {"type": "loopback", "description": "loopback"}, "3": {"type": "physical", "description": "as20r2 to as20r3"}, "2": {"type": "physical", "description": "as20r2 to as20r1"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 20, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 121.0, "x": 107.79470198675494, "label": "as20r2", "id": "as20r2", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as20r3 to as20r2"}, "0": {"type": "loopback", "description": "loopback"}, "3": {"type": "physical", "description": "as20r3 to as20r1"}, "2": {"type": "physical", "description": "as20r3 to as1r1"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 20, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 61.0, "x": 167.79470198675494, "label": "as20r3", "id": "as20r3", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as1r1 to as20r3"}, "0": {"type": "loopback", "description": "loopback"}, "3": {"type": "physical", "description": "as1r1 to as40r1"}, "2": {"type": "physical", "description": "as1r1 to as30r1"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 1, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 0.0, "x": 428.54470198675494, "label": "as1r1", "id": "as1r1", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as30r1 to as20r1"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as30r1 to as1r1"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 30, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 225.19205298013253, "x": 406.8973509933775, "label": "as30r1", "id": "as30r1", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as40r1 to as1r1"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as40r1 to as300r2"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 40, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 225.19205298013253, "x": 533.794701986755, "label": "as40r1", "id": "as40r1", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as20r1 to as200r1"}, "0": {"type": "loopback", "description": "loopback"}, "3": {"type": "physical", "description": "as20r1 to as30r1"}, "2": {"type": "physical", "description": "as20r1 to as100r1"}, "5": {"type": "physical", "description": "as20r1 to as20r3"}, "4": {"type": "physical", "description": "as20r1 to as20r2"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 20, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 121.0, "x": 227.79470198675494, "label": "as20r1", "id": "as20r1", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as100r1 to as100r2"}, "0": {"type": "loopback", "description": "loopback"}, "3": {"type": "physical", "description": "as100r1 to as20r1"}, "2": {"type": "physical", "description": "as100r1 to as100r3"}, "4": {"type": "physical", "description": "as100r1 to as20r2"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 100, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 334.0694822049305, "x": 107.79470198675494, "label": "as100r1", "id": "as100r1", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as100r2 to as100r1"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as100r2 to as100r3"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 100, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 334.0694822049305, "x": 0.0, "label": "as100r2", "id": "as100r2", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as100r3 to as100r1"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as100r3 to as100r2"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 100, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 394.0694822049305, "x": 47.794701986754944, "label": "as100r3", "id": "as100r3", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as200r1 to as20r1"}, "0": {"type": "loopback", "description": "loopback"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 200, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 225.19205298013253, "x": 280.0, "label": "as200r1", "id": "as200r1", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as300r2 to as300r4"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as300r2 to as40r1"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 300, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 334.0694822049305, "x": 598.7690846015074, "label": "as300r2", "id": "as300r2", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as300r4 to as300r2"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as300r4 to as300r3"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 300, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 408.56291390728484, "x": 598.7690846015074, "label": "as300r4", "id": "as300r4", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as300r3 to as300r1"}, "0": {"type": "loopback", "description": "loopback"}, "2": {"type": "physical", "description": "as300r3 to as300r4"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 300, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 408.56291390728484, "x": 504.16496614181324, "label": "as300r3", "id": "as300r3", "specified_int_names": null}, {"_interfaces": {"1": {"type": "physical", "description": "as300r1 to as300r3"}, "0": {"type": "loopback", "description": "loopback"}}, "device_subtype": null, "syntax": "quagga", "update": null, "pop": null, "asn": 300, "platform": "netkit", "host": "localhost", "use_ipv4": true, "device_type": "router", "y": 334.0694822049305, "x": 504.16496614181324, "label": "as300r1", "id": "as300r1", "specified_int_names": null}], "links": [{"_interfaces": {"as20r2": 1, "as100r1": 4}, "source": 0, "target": 6, "edge_id": "as20r2_as100r1"}, {"_interfaces": {"as20r2": 3, "as20r3": 1}, "source": 0, "target": 1, "edge_id": "as20r3_as20r2"}, {"_interfaces": {"as20r2": 2, "as20r1": 4}, "source": 0, "target": 5, "edge_id": "as20r2_as20r1"}, {"_interfaces": {"as20r3": 2, "as1r1": 1}, "source": 1, "target": 2, "edge_id": "as1r1_as20r3"}, {"_interfaces": {"as20r3": 3, "as20r1": 5}, "source": 1, "target": 5, "edge_id": "as20r1_as20r3"}, {"_interfaces": {"as30r1": 2, "as1r1": 2}, "source": 2, "target": 3, "edge_id": "as30r1_as1r1"}, {"_interfaces": {"as40r1": 1, "as1r1": 3}, "source": 2, "target": 4, "edge_id": "as1r1_as40r1"}, {"_interfaces": {"as30r1": 1, "as20r1": 3}, "source": 3, "target": 5, "edge_id": "as20r1_as30r1"}, {"_interfaces": {"as300r2": 2, "as40r1": 2}, "source": 4, "target": 10, "edge_id": "as40r1_as300r2"}, {"_interfaces": {"as200r1": 1, "as20r1": 1}, "source": 5, "target": 9, "edge_id": "as200r1_as20r1"}, {"_interfaces": {"as100r1": 3, "as20r1": 2}, "source": 5, "target": 6, "edge_id": "as100r1_as20r1"}, {"_interfaces": {"as100r1": 1, "as100r2": 1}, "source": 6, "target": 7, "edge_id": "as100r2_as100r1"}, {"_interfaces": {"as100r1": 2, "as100r3": 1}, "source": 6, "target": 8, "edge_id": "as100r1_as100r3"}, {"_interfaces": {"as100r2": 2, "as100r3": 2}, "source": 7, "target": 8, "edge_id": "as100r3_as100r2"}, {"_interfaces": {"as300r4": 1, "as300r2": 1}, "source": 10, "target": 11, "edge_id": "as300r4_as300r2"}, {"_interfaces": {"as300r4": 2, "as300r3": 2}, "source": 11, "target": 12, "edge_id": "as300r3_as300r4"}, {"_interfaces": {"as300r3": 1, "as300r1": 1}, "source": 12, "target": 13, "edge_id": "as300r1_as300r3"}], "multigraph": false} \ No newline at end of file diff --git a/tests/webserver/test_webserver.py b/tests/webserver/test_webserver.py deleted file mode 100644 index 02c5c97a..00000000 --- a/tests/webserver/test_webserver.py +++ /dev/null @@ -1,72 +0,0 @@ -import autonetkit -import os -import autonetkit.load.graphml as graphml -import shutil - -def test(): - automated = True # whether to open ksdiff, log to file... - if __name__ == "__main__": - automated = False - - dirname, filename = os.path.split(os.path.abspath(__file__)) - parent_dir = os.path.abspath(os.path.join(dirname, os.pardir)) - - anm = autonetkit.NetworkModel() - input_file = os.path.join(parent_dir, "small_internet.graphml") - input_graph = graphml.load_graphml(input_file) - - import autonetkit.build_network as build_network - anm = build_network.initialise(input_graph) - anm = build_network.apply_design_rules(anm) - - - - try: - from websocket import create_connection - except ImportError: - print "websocket-client package not installed" - else: - autonetkit.update_http(anm) - - ws = create_connection("ws://localhost:8000/ws") - ws.send("overlay_list") - result = ws.recv() - expected = '{"overlay_list": ["bgp", "ebgp", "ebgp_v4", "ebgp_v6", "eigrp", graphics", "ibgp_v4", "ibgp_v6", "ibgp_vpn_v4", "input", "input_directed", "ip", "ipv4", "ipv6", "isis", "l3_conn", "ospf", "phy", "vrf"]}' - assert(result == expected) - - overlay_id = "phy" - ws.send("overlay_id=" + overlay_id) - result = ws.recv() - with open(os.path.join(dirname, "expected_phy.json"), "r") as fh: - expected = fh.read() - - assert(result == expected) - ws.close() - - import autonetkit.console_script as console_script - render_hostname = "localhost" - - nidb = console_script.create_nidb(anm) - nidb._graph.graph['timestamp'] = "123456" - - import autonetkit.compilers.platform.netkit as pl_netkit - nk_compiler = pl_netkit.NetkitCompiler(nidb, anm, render_hostname) - nk_compiler.compile() - autonetkit.update_http(anm, nidb) - - ws = create_connection("ws://localhost:8000/ws") - ws.send("overlay_list") - result = ws.recv() - expected = '{"overlay_list": ["bgp", "ebgp", "ebgp_v4", "ebgp_v6", "eigrp", graphics", "ibgp_v4", "ibgp_v6", "ibgp_vpn_v4", "input", "input_directed", "ip", "ipv4", "ipv6", "isis", "l3_conn", "nidb", "ospf", "phy", "vrf"]}' - assert(result == expected) - - overlay_id = "nidb" - ws.send("overlay_id=" + overlay_id) - result = ws.recv() - with open(os.path.join(dirname, "expected_nidb.json"), "r") as fh: - expected = fh.read() - - assert(result == expected) - ws.close() - - #TODO: test highlight, and getting response back (needs callback, refer https://pypi.python.org/pypi/websocket-client/0.7.0) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index b1f17264..00000000 --- a/tox.ini +++ /dev/null @@ -1,7 +0,0 @@ -[tox] -envlist = py27 -[testenv] -deps=nose - coverage -commands= - nosetests --with-coverage --cover-erase --cover-package=autonetkit --cover-html --cover-html-dir=htmlcov --with-xunit --xunit-file=junit-{envname}.xml tests \ No newline at end of file