diff --git a/docs/outputs/graph.md b/docs/outputs/graph.md index f43b7fc732..26df63bbdd 100644 --- a/docs/outputs/graph.md +++ b/docs/outputs/graph.md @@ -73,9 +73,43 @@ _netlab_ uses the node **graph.rank** attribute to: The default value of the **graph.rank** attribute is 100, allowing you to push some nodes (with rank below 100) toward the top of the graph and others (with rank above 100) toward the bottom. -You can also use the **graph.rank** on links to influence how Graphviz draws multi-access links. +You can also use the **graph.rank** on links to influence how Graphviz positions multi-access links relative to other links and nodes. -Finally, the link/interface **graph.linkorder** attribute allows you to specify the node order in individual links. The default **graph.linkorder** value is 50 for interfaces and 100 for subnets (multi-access links), resulting in subnets being "below" nodes unless you change the link- or interface **graph.linkorder** value. +Finally, the **graph.linkorder** attribute allows you to specify the node order in individual links. The default **graph.linkorder** value is 50 for interfaces and 100 for subnets (multi-access links). **graph.rank** is used to sort objects with the same **graph.linkorder**. + +The default value of **graph.linkorder** places subnets (multi-access links) below all nodes attached to them. For example, the following lab topology results in a graph with the subnet below sw, h1, and h2: + +``` +nodes: [ sw, h1, h2 ] +links: +- interfaces: [ sw, h1, h2 ] +``` + +You can change the **graph.linkorder** on the host interfaces (h1, h2) to put the host nodes below the subnet: + +``` +nodes: [ sw, h1, h2 ] +links: +- sw: + h1: + graph.linkorder: 200 + h2: + graph.linkorder: 200 +``` + +You can also change the node **graph.linkorder** attribute to put nodes below subnets *on all links they're connected to*. For example, you could use a `hosts` group to set the **graph.linkorder** for all hosts to a value higher than the default link value (100): + +``` +groups: + hosts: + members: [ h1, h2 ] + graph.linkorder: 200 + +nodes: [ r1, h1, h2 ] + +links: +- interfaces: [ r1, h1, h2 ] +``` (outputs-graph-appearance)= ## Modifying Graph Appearance diff --git a/netsim/outputs/_graph.py b/netsim/outputs/_graph.py index 6359d1faa3..964122b0e4 100644 --- a/netsim/outputs/_graph.py +++ b/netsim/outputs/_graph.py @@ -112,8 +112,20 @@ def get_graph_attributes(obj: Box, g_type: str, exclude: list = []) -> Box: """ Get a graph attribute from shared or graph-specific dictionary """ -def get_attr(obj: Box, g_type: str, attr: str, default: typing.Any) -> typing.Any: - return obj.get(f'{g_type}.{attr}',obj.get(f'graph.{attr}',default)) +def get_attr(obj: Box, topology: Box, g_type: str, attr: str, default: typing.Any) -> typing.Any: + kw_list = [ f'{g_type}.{attr}', f'graph.{attr}' ] # Try graph-specific (graph/d2) and shared graph attributes + for kw in kw_list: # Try to find the value in interface/link object + if kw in obj: + return obj[kw] + + if 'node' in obj: # Try to get node data from object.node (if present) + ndata = topology.nodes.get(obj.node,None) + if ndata: # If we found node data, try to get the keyword value from it + for kw in kw_list: + if kw in ndata: + return ndata[kw] + + return default # Nothing works, return the default value """ Propagate link graph attributes to interfaces @@ -182,14 +194,14 @@ def append_segment(graph: Box,link: Box, g_type: str, topology: Box) -> None: for intf in link.interfaces: e_data = [ intf, link ] - e_data.sort(key = lambda x: x.get('graph.rank',topology.nodes[intf.node].get('graph.rank',100))) - e_data.sort(key = lambda x: get_attr(x,g_type,'linkorder',50)) + e_data.sort(key = lambda x: get_attr(x,topology,g_type,'rank',100)) + e_data.sort(key = lambda x: get_attr(x,topology,g_type,'linkorder',50)) append_edge(graph,e_data[0],e_data[1],g_type) def topo_edges(graph: Box, topology: Box, settings: Box,g_type: str) -> None: graph.edges = [] - for link in sorted(topology.links,key=lambda x: get_attr(x,g_type,'linkorder',100)): - propagate_link_attributes(link,g_type,['linkorder','type']) + for link in sorted(topology.links,key=lambda x: get_attr(x,topology,g_type,'linkorder',100)): + propagate_link_attributes(link,g_type,['linkorder','rank','type']) if settings.get('topology.vlan',False): for intf in link.interfaces: @@ -197,7 +209,7 @@ def topo_edges(graph: Box, topology: Box, settings: Box,g_type: str) -> None: if len(link.interfaces) == 2 and link.get('graph.type','') != 'lan': intf_list = sorted(link.interfaces,key=lambda intf: topology.nodes[intf.node].get('graph.rank',100)) - intf_list.sort(key = lambda x: get_attr(x,g_type,'linkorder',50)) + intf_list.sort(key = lambda x: get_attr(x,topology,g_type,'linkorder',50)) append_edge(graph,intf_list[0],intf_list[1],g_type) else: append_segment(graph,link,g_type,topology) @@ -300,8 +312,8 @@ def get_node_interface(topology: Box, intf: Box) -> Box: def isis_edges(topology: Box, graph: Box, g_type: str) -> None: graph.edges = [] - for link in sorted(topology.links,key=lambda x: get_attr(x,g_type,'linkorder',100)): - propagate_link_attributes(link,g_type,['linkorder','type']) + for link in sorted(topology.links,key=lambda x: get_attr(x,topology,g_type,'linkorder',100)): + propagate_link_attributes(link,g_type,['linkorder','rank','type']) isis_nodes = [ intf.node for intf in link.interfaces diff --git a/netsim/outputs/graph.yml b/netsim/outputs/graph.yml index 66fb4e7376..43fab3c0ae 100644 --- a/netsim/outputs/graph.yml +++ b/netsim/outputs/graph.yml @@ -81,6 +81,7 @@ attributes: fill: str width: { type: int, min_value: 1, max_value: 32 } rank: { type: int, min_value: 1, max_value: 200 } + linkorder: { type: int, min_value: 1, max_value: 200 } format: dict class: { type: list, _subtype: str} link: diff --git a/tests/platform-integration/graph/01-topo.yml b/tests/platform-integration/graph/01-topo.yml index 7e10e4e90f..37328853b9 100644 --- a/tests/platform-integration/graph/01-topo.yml +++ b/tests/platform-integration/graph/01-topo.yml @@ -16,6 +16,9 @@ groups: host: members: [ h1, h2, h3, h4 ] device: linux + edge_host: + members: [ h1, h2 ] + graph.linkorder: 200 spine: members: [ s-1, s-b-2 ] graph.rank: 1 @@ -42,11 +45,7 @@ links: l2: graph.format.style: dashed d2.format.style.stroke-dash: 5 -- l1: - h1: - graph.linkorder: 200 - h2: - graph.linkorder: 200 +- interfaces: [ l1, h1, h2 ] graph.color: "#FF0000" graph.width: 3 d2.format.shape: oval