diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 53738e94..c6c6a218 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.12 +current_version = 0.11.0 files = setup.py commit = True tag = True diff --git a/autonetkit/ank.py b/autonetkit/ank.py index 675bdd17..a8933611 100644 --- a/autonetkit/ank.py +++ b/autonetkit/ank.py @@ -767,3 +767,53 @@ def boundary_nodes(nm_graph, nodes): 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 index 375f0d38..1785dc2a 100644 --- a/autonetkit/ank_json.py +++ b/autonetkit/ank_json.py @@ -15,6 +15,7 @@ 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): @@ -277,7 +278,15 @@ def jsonify_anm_with_graphics(anm, nidb=None): key=lambda x: nodes_by_layer.get(x, 0)) for overlay_id in overlay_ids: - nm_graph = anm[overlay_id]._graph.copy() + 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) @@ -335,12 +344,6 @@ def jsonify_anm_with_graphics(anm, nidb=None): # store on graph nm_graph.node[node] = node_data - if nm_graph.is_multigraph(): - for u, v, k in nm_graph.edges(keys=True): - # Store key: nx node_link_data ignores it - #anm_graph[u][v][k]['_key'] = k - pass # is this needed? as key itself holds no value? - try: del nm_graph.node[node]['id'] except KeyError: diff --git a/autonetkit/anm/ank_element.py b/autonetkit/anm/ank_element.py index 342781a3..e4af31ff 100644 --- a/autonetkit/anm/ank_element.py +++ b/autonetkit/anm/ank_element.py @@ -6,6 +6,7 @@ class AnkElement(object): #TODO: put this into parent __init__? def init_logging(self, my_type): + return try: self_id = str(self) except Exception, e: diff --git a/autonetkit/anm/graph.py b/autonetkit/anm/graph.py index 1c3d0dd9..0155ee04 100644 --- a/autonetkit/anm/graph.py +++ b/autonetkit/anm/graph.py @@ -192,6 +192,9 @@ def allocate_input_interfaces(self): 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') diff --git a/autonetkit/anm/graph_data.py b/autonetkit/anm/graph_data.py index f5e4cc31..3f1e2f83 100644 --- a/autonetkit/anm/graph_data.py +++ b/autonetkit/anm/graph_data.py @@ -26,6 +26,9 @@ def _graph(self): 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) diff --git a/autonetkit/anm/node.py b/autonetkit/anm/node.py index 2c9b923f..23e7221a 100644 --- a/autonetkit/anm/node.py +++ b/autonetkit/anm/node.py @@ -407,6 +407,15 @@ def is_router(self): 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""" diff --git a/autonetkit/build_network.py b/autonetkit/build_network.py index 17a4a9dd..e110cf21 100644 --- a/autonetkit/build_network.py +++ b/autonetkit/build_network.py @@ -123,6 +123,7 @@ def check_server_asns(anm): 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()) @@ -161,9 +162,11 @@ def apply_design_rules(anm): cisco_build_network.post_phy(anm) g_phy = anm['phy'] - from autonetkit.design.osi_layers import build_layer2, build_layer3 + 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) @@ -295,6 +298,7 @@ def build_phy(anm): 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() diff --git a/autonetkit/compilers/device/cisco.py b/autonetkit/compilers/device/cisco.py index 8a0c8a88..39129e1e 100644 --- a/autonetkit/compilers/device/cisco.py +++ b/autonetkit/compilers/device/cisco.py @@ -50,6 +50,11 @@ def compile(self, node): 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) @@ -339,6 +344,25 @@ def vrf(self, node): 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(): @@ -475,6 +499,9 @@ def compile(self, node): 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 @@ -722,6 +749,32 @@ def mpls_te(self, node): 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) @@ -823,6 +876,13 @@ def interfaces(self, node): 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 diff --git a/autonetkit/compilers/device/router_base.py b/autonetkit/compilers/device/router_base.py index 761f5b28..7ede352b 100644 --- a/autonetkit/compilers/device/router_base.py +++ b/autonetkit/compilers/device/router_base.py @@ -123,6 +123,9 @@ def compile(self, node): 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 @@ -421,31 +424,28 @@ def bgp(self, node): return - def eigrp(self, node): - - g_eigrp = self.anm['eigrp'] + def rip(self, node): + g_rip = self.anm['rip'] 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 + 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 # don't configure IGP for this interface + continue # discontinue configuring 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 + rip_int = g_rip.interface(interface) + if not rip_int.is_bound: + continue # not an rip interface network = ipv4_int.subnet - if eigrp_int and eigrp_int.is_bound and interface.use_ipv4: + if rip_int and rip_int.is_bound and interface.use_ipv4: ipv4_networks.add(network) - # Loopback zero subnet - ipv4_networks.add(node.loopback_zero.ipv4_cidr) + node.rip.ipv4_networks = sorted(list(ipv4_networks)) - node.eigrp.ipv4_networks = sorted(list(ipv4_networks)) def isis(self, node): g_isis = self.anm['isis'] @@ -490,3 +490,30 @@ def isis(self, node): '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/platform/cisco.py b/autonetkit/compilers/platform/cisco.py index 9d35f89e..5c0b5afb 100644 --- a/autonetkit/compilers/platform/cisco.py +++ b/autonetkit/compilers/platform/cisco.py @@ -139,6 +139,13 @@ def compile_devices(self): 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) diff --git a/autonetkit/console_script.py b/autonetkit/console_script.py index dea88d08..6a33d369 100644 --- a/autonetkit/console_script.py +++ b/autonetkit/console_script.py @@ -150,7 +150,7 @@ def main(options): grid=options.grid, **build_options) except Exception, err: log.error( - "Error generating network configurations: %s. More information may be available in the debug log." % err) + "Error generating network configurations: %s" % err) log.debug("Error generating network configurations", exc_info=True) if settings['General']['stack_trace']: print traceback.print_exc() diff --git a/autonetkit/design/igp.py b/autonetkit/design/igp.py index e6654a85..27b8059b 100644 --- a/autonetkit/design/igp.py +++ b/autonetkit/design/igp.py @@ -10,10 +10,11 @@ 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"] + igp_protocols = ["ospf", "eigrp", "isis", "rip"] for protocol in igp_protocols: g_protocol = anm[protocol] g_igp.add_nodes_from(g_protocol, igp=protocol) @@ -245,6 +246,43 @@ def build_network_entity_title(anm): 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'] diff --git a/autonetkit/design/ip.py b/autonetkit/design/ip.py index e851b088..433cf8fc 100644 --- a/autonetkit/design/ip.py +++ b/autonetkit/design/ip.py @@ -15,17 +15,30 @@ #@call_log def build_ip(anm): g_ip = anm.add_overlay('ip') - g_l2_bc = anm['layer2_bc'] + g_l2 = anm['layer2'] g_phy = anm['phy'] # Retain arbitrary ASN allocation for IP addressing - g_ip.add_nodes_from(g_l2_bc, retain=["asn", "broadcast_domain"]) - g_ip.add_edges_from(g_l2_bc.edges()) + 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: @@ -41,7 +54,6 @@ def build_ip(anm): break - # copy over skipped loopbacks #TODO: check if loopbck copy attr for node in g_ip.l3devices(): diff --git a/autonetkit/design/layer1.py b/autonetkit/design/layer1.py new file mode 100644 index 00000000..9add9fc2 --- /dev/null +++ b/autonetkit/design/layer1.py @@ -0,0 +1,90 @@ +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 index 08e92b16..a8740504 100644 --- a/autonetkit/design/layer2.py +++ b/autonetkit/design/layer2.py @@ -5,18 +5,23 @@ 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_phy = anm['phy'] - g_l2.add_nodes_from(g_phy) - g_l2.add_edges_from(g_phy.edges()) + g_l1 = anm['layer1'] + + g_l2.add_nodes_from(g_l1) + g_l2.add_edges_from(g_l1.edges()) # Don't aggregate managed switches - unmanaged_switches = [n for n in g_l2.switches() - if n.device_subtype != "managed"] - ank_utils.aggregate_nodes(g_l2, unmanaged_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 @@ -25,6 +30,26 @@ def build_layer2_base(anm): 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 @@ -55,6 +80,7 @@ def check_layer2(anm): 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'] @@ -69,7 +95,6 @@ def build_layer2_broadcast(anm): 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 - # print "edges to split", edges_to_split for edge in edges_to_split: edge.split = True # mark as split for use in building nidb @@ -80,9 +105,9 @@ def build_layer2_broadcast(anm): # TODO: if parallel nodes, offset # TODO: remove graphics, assign directly if len(g_graphics): - co_ords_overlay = g_graphics # source from graphics overlay + co_ords_overlay = g_graphics # source from graphics overlay else: - co_ords_overlay = g_phy # source from phy overlay + 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', @@ -169,37 +194,110 @@ def build_layer2_broadcast(anm): 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_vlan_trunk = anm.add_overlay('vlan_trunk') - g_vlans = anm.add_overlay('vlans') + 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"] - # copy across vlans from input graph - for router in g_l2.routers(): - for interface in router.physical_interfaces(): - interface.vlan = interface['input'].vlan - if not interface.vlan: - pass - # check if connectde to a managed switch, if so, warn + 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) - subs = ank_utils.connected_subgraphs(g_l2, managed_switches) + # 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: - neigh_ints = [i.neighbors()[0] for i in switch.interfaces() - if i.is_bound] - router_ints = [i for i in neigh_ints if i.node.is_router()] - for interface in router_ints: - # store keyed by vlan id - vlans[interface.vlan].append(interface) + 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 @@ -208,7 +306,10 @@ def build_vlans(anm): for vlan, interfaces in vlans.items(): # create a virtual switch vswitch_id = "vswitch%s" % vswitch_id_counter.next() - vswitch = g_vlans.add_node(vswitch_id) + # 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) @@ -221,14 +322,19 @@ def build_vlans(anm): i.node['phy'].y for i in interfaces) / len(interfaces) + 50 vswitch.vlan = vlan - g_vlan_trunk.add_node(vswitch) + 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 @@ -240,5 +346,5 @@ def build_vlans(anm): edges_to_add = list(itertools.combinations(vswitches, 2)) # TODO: ensure only once # TODO: filter so only one direction - g_l2.add_edges_from(edges_to_add, trunk=True) - g_vlan_trunk.add_edges_from(edges_to_add, trunk=True) \ No newline at end of file + # 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 index 17d043db..73431bb1 100644 --- a/autonetkit/design/mpls.py +++ b/autonetkit/design/mpls.py @@ -138,7 +138,7 @@ def build_ibgp_vpn_v4(anm): 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: 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'] @@ -167,6 +167,7 @@ def build_ibgp_vpn_v4(anm): # 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"]) diff --git a/autonetkit/design/osi_layers.py b/autonetkit/design/osi_layers.py index 02b724dc..a57dee3a 100644 --- a/autonetkit/design/osi_layers.py +++ b/autonetkit/design/osi_layers.py @@ -1,19 +1,22 @@ 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) - autonetkit.update_http(anm) def build_layer3(anm): """ l3_connectivity graph: switch nodes aggregated and exploded""" g_in = anm['input'] - g_l2 = anm['layer2'] + gl2_conn = anm['layer2_conn'] g_l3 = anm.add_overlay("layer3") - g_l3.add_nodes_from(g_l2, retain=['label']) + g_l3.add_nodes_from(gl2_conn, retain=['label']) g_l3.add_nodes_from(g_in.switches(), retain=['asn']) - g_l3.add_edges_from(g_l2.edges()) + g_l3.add_edges_from(gl2_conn.edges()) switches = g_l3.switches() @@ -30,4 +33,4 @@ def build_layer3(anm): for edge in exploded_edges: edge.multipoint = True edge.src_int.multipoint = True - edge.dst_int.multipoint = True + edge.dst_int.multipoint = True \ No newline at end of file diff --git a/autonetkit/load/load_json.py b/autonetkit/load/load_json.py index d71812dd..941dfcd9 100644 --- a/autonetkit/load/load_json.py +++ b/autonetkit/load/load_json.py @@ -66,6 +66,8 @@ def nx_to_simple(graph): 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'] @@ -93,23 +95,24 @@ def simple_to_nx(j_data): unmapped_links = [] - mapped_links = j_data['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']] + 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} + interfaces = {src: src_port_id, + dst: dst_port_id} - unmapped_links.append({'source': src_pos, - 'target': dst_pos, - '_ports': interfaces - }) + unmapped_links.append({'source': src_pos, + 'target': dst_pos, + '_ports': interfaces + }) j_data['links'] = unmapped_links return json_graph.node_link_graph(j_data) diff --git a/autonetkit/nidb/device_model.py b/autonetkit/nidb/device_model.py index b6cd6845..d458682f 100644 --- a/autonetkit/nidb/device_model.py +++ b/autonetkit/nidb/device_model.py @@ -85,12 +85,13 @@ def __init__(self, network_model=None): 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 + # 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', - 'Network', 'update', 'asn', ]) + "syntax", 'Network', 'update', 'asn', ]) self.add_edges_from(g_phy.edges()) self.copy_graphics(network_model) diff --git a/autonetkit/templates/ios.mako b/autonetkit/templates/ios.mako index d60bd549..43eb3e20 100644 --- a/autonetkit/templates/ios.mako +++ b/autonetkit/templates/ios.mako @@ -133,6 +133,11 @@ interface ${interface.id} % 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: @@ -242,6 +247,20 @@ router ospfv3 ${node.ospf.process_id} 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} diff --git a/autonetkit/workflow.py b/autonetkit/workflow.py index f565b242..dea1d1cb 100644 --- a/autonetkit/workflow.py +++ b/autonetkit/workflow.py @@ -66,9 +66,11 @@ def manage_network(input_graph_string, timestamp, build=True, except Exception, e: # Send the visualisation to help debugging try: - if visualise: - import autonetkit - autonetkit.update_vis(anm) + # 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 @@ -77,11 +79,12 @@ def manage_network(input_graph_string, timestamp, build=True, else: if visualise: # log.info("Visualising network") - import autonetkit - autonetkit.update_vis(anm) + # import autonetkit + # autonetkit.update_vis(anm) + pass - if not compile: - # autonetkit.update_vis(anm) + if not compile and visualise: + autonetkit.update_vis(anm) pass if validate: diff --git a/setup.py b/setup.py index e2ec830f..281001d7 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup ( name = "autonetkit", - version = "0.10.12", + version = "0.11.0", description = 'Automatic configuration generation for emulated networks', long_description = 'Automatic configuration generation for emulated networks',