Skip to content

Commit fe2da0a

Browse files
committed
Formatting modifiers for BGP graphs
New formatting options for BGP graphs: * VRF sessions are either omitted or shown as dotted lines * The graph can be limited to a subset of address families Unified parsing of BGP formatting options: * D2 and Graph output modules use the same code to parse BGP formatting parameters Changes in integration tests: * The BGP topology includes VRFs and EVPN * The graph creation scripts create more BGP graphs with various formatting options
1 parent 85cf9d6 commit fe2da0a

File tree

11 files changed

+100
-33
lines changed

11 files changed

+100
-33
lines changed

docs/outputs/d2.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ The *d2* output module is invoked by specifying the `-o d2` parameter in the **[
88
A single formatting modifier can be used to specify the graph type:
99

1010
* **topology** (default) -- Includes point-to-point links, multi-access bridges, and stub subnets. When the network topology contains BGP information, the graph groups nodes into autonomous systems. Alternatively, you could set **defaults.outputs.graph.groups** attribute to use topology **[groups](topo-groups)** to group graph nodes.
11-
* **bgp** -- Include autonomous systems, nodes, and BGP sessions. With the **rr** option (specified with `netlab create -o graph:bgp:rr`), RR-client sessions are drawn as directed arrows.
11+
* **bgp** -- Include autonomous systems, nodes, and BGP sessions. The formatting modifier can include the [BGP formatting parameters](outputs-d2-bgp-parameters). For example, `netlab create -o graph:bgp:vrf` draws VRF BGP sessions as dotted lines.
1212

1313
```{tip}
1414
The network topology graph description contains nodes and links, but no placement information. D2 is pretty good at figuring out how to draw the required graph, but it pays off to test out the [layout engines](https://d2lang.com/tour/layouts). Changing the [order of links](outputs-d2-link-node-attributes) might also unclutter the diagrams.
@@ -23,10 +23,19 @@ Graphing routines use **[default](topo-defaults)** topology settings to modify t
2323
* **outputs.d2.node_address_label** (default: True) -- add node loopback IP addresses or IP addresses of the first interface (for hosts) to node labels.
2424
* **outputs.d2.node_interfaces** (default: False) -- add list of interfaces and their IP addresses to nodes[^DG].
2525
* **outputs.d2.as_clusters** (default: True) -- use BGP autonomous systems to cluster nodes in the topology graph. BGP AS clusters are always used in BGP graphs.
26-
* **outputs.d2.rr_sessions** (default: True) -- draw IBGP sessions between BGP route reflectors and clients as directional connections.
2726

2827
[^DG]: The results look disgusting. If you find a better way to get it done, please submit a PR. Thank you!
2928

29+
(outputs-d2-bgp-parameters)=
30+
These default settings modify how the BGP graphs look:
31+
32+
* **outputs.d2.bgp.rr** (default: True) -- draw arrows on BGP sessions to indicate peer-to-peer versus reflector-client sessions
33+
* **outputs.d2.bgp.vrf** (default: False) -- draw VRF BGP sessions as dotted lines
34+
* **outputs.d2.bgp.af._af_** (default: all address families) -- when one or more **af** parameters (valid keys: **ipv4**, **ipv6**, **vpnv4**, **vpnv6**, **6pe**, **evpn**) are set to *True*, the graph is limited to BGP sessions of the specified address families.
35+
* **outputs.d2.bgp.novrf** (default: False) -- do not include VRF BGP sessions in the graph
36+
37+
You can specify the above BGP parameters in the *graph format* CLI argument.
38+
3039
(outputs-d2-link-node-attributes)=
3140
## Modifying Link and Node Attributes
3241

docs/outputs/graph.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The *graph* output module is invoked with the **[netlab graph](netlab-graph)** c
99
A single formatting modifier can be used to specify the graph type:
1010

1111
* **topology** (default) -- Display inter-node links, multi-access- and stub subnets. When the network topology contains BGP information, the graph groups nodes into autonomous systems. Alternatively, you could set **defaults.outputs.graph.groups** attribute to use topology **[groups](topo-groups)** to group graph nodes.
12-
* **bgp** -- Include autonomous systems, nodes, and BGP sessions. With the **rr** option (specified with `netlab create -o graph:bgp:rr`), RR-client sessions are drawn as directed arrows.
12+
* **bgp** -- Include autonomous systems, nodes, and BGP sessions. The formatting modifier can include [BGP formatting parameters](outputs-graph-bgp-parameters). For example, `netlab create -o graph:bgp:rr` draws RR-client sessions as directed arrows.
1313

1414
## Modifying Graph Attributes
1515

@@ -19,7 +19,16 @@ Graphing routines use **[default](topo-defaults)** topology settings to modify t
1919
* **outputs.graph.groups** -- use the specified list of groups (or all groups when set to *True*) to create graph clusters
2020
* **outputs.graph.interface_labels** -- Add IP addresses to links in **topology** graph. Results in a cluttered image (but feel free to fix that and submit a pull request).
2121
* **outputs.graphs.node_address_label** (default: *True*) -- add node loopback IP addresses or IP addresses of the first interface (for hosts) to node labels.
22-
* **outputs.graph.rr_sessions** (default: *False*) -- draw IBGP sessions between BGP route reflectors and clients as directional connections.
22+
23+
(outputs-graph-bgp-parameters)=
24+
These default settings modify how the BGP graphs look:
25+
26+
* **outputs.graph.bgp.rr** (default: True) -- draw arrows on BGP sessions to indicate peer-to-peer versus reflector-client sessions
27+
* **outputs.graph.bgp.vrf** (default: False) -- draw VRF BGP sessions as dotted lines
28+
* **outputs.graph.bgp.af._af_** (default: all address families) -- when one or more **af** parameters (valid keys: **ipv4**, **ipv6**, **vpnv4**, **vpnv6**, **6pe**, **evpn**) are set to *True*, the graph is limited to BGP sessions of the specified address families.
29+
* **outputs.graph.bgp.novrf** (default: False) -- do not include VRF BGP sessions in the graph
30+
31+
You can specify the above BGP parameters in the *graph format* CLI argument.
2332

2433
(outputs-graph-link-node-attributes)=
2534
## Modifying Link and Node Attributes

netsim/outputs/_graph.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def append_edge(graph: Box, if_a: Box, if_b: Box, g_type: str) -> None:
126126
'node': intf.node,
127127
'attr': intf_attr,
128128
'label': addr})
129-
for kw in ['type','_subnet']:
129+
for kw in ['type','_subnet','vrf']:
130130
if kw in intf:
131131
e_data[kw] = intf[kw]
132132

@@ -167,16 +167,30 @@ def topology_graph(topology: Box, settings: Box,g_type: str) -> Box:
167167
log.exit_on_error()
168168
return graph
169169

170-
def bgp_sessions(graph: Box, topology: Box, settings: Box, g_type: str, rr_sessions: bool) -> None:
170+
def bgp_sessions(graph: Box, topology: Box, settings: Box, g_type: str) -> None:
171+
rr_sessions = settings.get('bgp.rr',None)
172+
no_vrf = settings.get('bgp.novrf',None)
173+
add_vrf = settings.get('bgp.vrf',None)
174+
bgp_af = settings.get('bgp.af')
171175
for n_name,n_data in topology.nodes.items():
172176
if 'bgp' not in n_data:
173177
continue
174178
for neighbor in _routing.neighbors(n_data,vrf=True):
179+
is_vrf = '_vrf' in neighbor or '_src_vrf' in neighbor
180+
if is_vrf and no_vrf:
181+
continue
175182
if neighbor.name < n_name:
176183
continue
184+
if bgp_af:
185+
if not [ neighbor[af] for af in bgp_af if af in neighbor ]:
186+
continue
177187

178188
e_1 = get_box({ 'node': n_name, 'type': neighbor.type })
189+
if '_src_vrf' in neighbor and add_vrf:
190+
e_1.vrf = neighbor._src_vrf
179191
e_2 = get_box({ 'node': neighbor.name, 'type': neighbor.type })
192+
if '_vrf' in neighbor and add_vrf:
193+
e_2.vrf = neighbor._vrf
180194
dir = '<->'
181195
if 'ibgp' in neighbor.type:
182196
if n_data.bgp.get('rr',False) and not neighbor.get('rr',False):
@@ -186,10 +200,10 @@ def bgp_sessions(graph: Box, topology: Box, settings: Box, g_type: str, rr_sessi
186200
e_1, e_2 = e_2, e_1
187201

188202
if rr_sessions:
189-
e_1.graph.dir = dir
203+
e_1[g_type].dir = dir
190204
append_edge(graph,e_1,e_2,g_type)
191205

192-
def bgp_graph(topology: Box, settings: Box, g_type: str, rr_sessions: bool) -> typing.Optional[Box]:
206+
def bgp_graph(topology: Box, settings: Box, g_type: str) -> typing.Optional[Box]:
193207
set_shared_attributes(topology)
194208
if 'bgp' not in topology.module:
195209
log.error(
@@ -201,6 +215,29 @@ def bgp_graph(topology: Box, settings: Box, g_type: str, rr_sessions: bool) -> t
201215
graph = build_nodes(topology,g_type)
202216
graph.clusters = graph.bgp
203217
graph.edges = []
204-
bgp_sessions(graph,topology,settings,g_type,rr_sessions)
218+
bgp_sessions(graph,topology,settings,g_type)
205219
log.exit_on_error()
206220
return graph
221+
222+
def parse_bgp_params(settings: Box, format: typing.Optional[list]) -> None:
223+
if 'rr_sessions' in settings and settings.get('bgp.rr',None) is None:
224+
settings.bgp.rr = settings.rr_sessions
225+
226+
if not format:
227+
return
228+
229+
for kw in format[1:]:
230+
if kw in ('rr','vrf','novrf'):
231+
settings.bgp[kw] = True
232+
elif kw in log.BGP_AF:
233+
settings.bgp.af[kw] = True
234+
else:
235+
log.error(
236+
'Invalid BGP graph parameter {kw}',
237+
category=log.IncorrectValue,
238+
module='graph',
239+
skip_header=True)
240+
241+
if log.VERBOSE:
242+
log.info('BGP graph parameters',more_data=settings.bgp.to_yaml().split('\n'))
243+
log.exit_on_error()

netsim/outputs/d2.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from ..utils import files as _files
1010
from ..utils import log
1111
from . import _TopologyOutput
12-
from ._graph import bgp_graph, map_style, topology_graph
12+
from ._graph import bgp_graph, map_style, parse_bgp_params, topology_graph
1313

1414
'''
1515
Copy default settings into a D2 map converting Python dictionaries into
@@ -169,13 +169,8 @@ def graph_topology(topology: Box, fname: str, settings: Box,g_format: typing.Opt
169169
return True
170170

171171
def graph_bgp(topology: Box, fname: str, settings: Box, g_format: typing.Optional[list]) -> bool:
172-
rr_session = settings.get('rr_sessions',False)
173-
g_format = g_format or []
174-
for kw in g_format[1:]:
175-
if kw == 'rr':
176-
rr_session = True
177-
178-
graph = bgp_graph(topology,settings,'d2',rr_sessions=rr_session)
172+
parse_bgp_params(settings,g_format)
173+
graph = bgp_graph(topology,settings,'d2')
179174
if graph is None:
180175
return False
181176

@@ -187,6 +182,8 @@ def graph_bgp(topology: Box, fname: str, settings: Box, g_format: typing.Optiona
187182
for edge in graph.edges:
188183
if edge.nodes[0].type in settings:
189184
edge.attr.format = settings[edge.nodes[0].type] + edge.attr.format
185+
if 'vrf' in edge.nodes[0] or 'vrf' in edge.nodes[1]:
186+
edge.attr.format = settings.vrf + edge.attr.format
190187

191188
d2_links(f,graph,topology,settings)
192189

netsim/outputs/d2.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
node_address_label: True
55
interface_labels: False
66
as_clusters: True
7-
rr_sessions: True
7+
bgp:
8+
rr: True
89

910
router:
1011
shape: oval
@@ -58,6 +59,10 @@ confed_ebgp:
5859
target-arrowhead:
5960
shape: arrow
6061

62+
vrf:
63+
style:
64+
stroke-dash: 3
65+
6166
style_map:
6267
color: stroke
6368
width: stroke-width

netsim/outputs/graph.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ..utils import files as _files
99
from ..utils import log
1010
from . import _TopologyOutput
11-
from ._graph import bgp_graph, map_style, topology_graph
11+
from ._graph import bgp_graph, map_style, parse_bgp_params, topology_graph
1212

1313

1414
def edge_label(f : typing.TextIO, direction: str, data: Box, subnet: bool = True) -> None:
@@ -143,6 +143,8 @@ def gv_links(f: typing.TextIO, graph: Box, topology: Box, settings: Box) -> None
143143
for n_data in edge.nodes:
144144
if 'type' in n_data:
145145
attr = attr + settings.styles[n_data.type]
146+
if 'vrf' in n_data:
147+
attr = attr + settings.styles.vrf
146148

147149
if '<-' in dir:
148150
attr.arrowtail = 'normal'
@@ -190,13 +192,8 @@ def graph_topology(topology: Box, fname: str, settings: Box,g_format: typing.Opt
190192
return True
191193

192194
def graph_bgp(topology: Box, fname: str, settings: Box,g_format: typing.Optional[list]) -> bool:
193-
rr_session = settings.get('rr_sessions',False)
194-
g_format = g_format or []
195-
for kw in g_format[1:]:
196-
if kw == 'rr':
197-
rr_session = True
198-
199-
graph = bgp_graph(topology,settings,'graph',rr_sessions=rr_session)
195+
parse_bgp_params(settings,g_format)
196+
graph = bgp_graph(topology,settings,'graph')
200197
if graph is None:
201198
return False
202199

netsim/outputs/graph.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ styles:
2727
confed_ebgp:
2828
color: '#d26400'
2929
penwidth: 2
30+
vrf:
31+
style: dashed
3032
graph:
3133
bgcolor: transparent
3234
nodesep: 0.5

netsim/utils/log.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
RAISE_ON_ERROR : bool = False
2323
WARNING : bool = False
2424

25-
AF_LIST = ['ipv4','ipv6']
26-
BGP_SESSIONS = ['ibgp','ebgp']
25+
AF_LIST = ('ipv4','ipv6')
26+
BGP_AF = ('ipv4','ipv6','vpnv4','vpnv6','6pe','evpn')
27+
BGP_SESSIONS = ('ibgp','ebgp')
2728

2829
_ERROR_LOG: list = []
2930
_WARNING_LOG: list = []

tests/platform-integration/graph/bgp.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
defaults.device: frr
22
defaults.provider: clab
33

4-
module: [ ospf, bgp ]
4+
module: [ ospf, bgp, vrf, vlan, vxlan, evpn ]
5+
defaults.vrf.warnings.inactive: False
56

67
bgp.as_list:
78
65000:
@@ -12,6 +13,11 @@ bgp.as_list:
1213

1314
nodes: [ r1, r2, r3, r4, r5, r6 ]
1415

16+
vrfs:
17+
tenant:
18+
evpn.transit_vni: True
19+
links: [ r1-r4, r3-r6 ]
20+
1521
links:
1622
- interfaces: [ r2, r1 ]
1723
graph.linkorder: 1
@@ -21,5 +27,3 @@ links:
2127
- r4-r5
2228
- r5-r6
2329
- r4-r6
24-
- r1-r4
25-
- r3-r6

tests/platform-integration/graph/create-d2.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ if [[ "$OPTS" == *"topo"* ]]; then
1414
fi
1515
if [[ "$OPTS" == *"bgp"* ]]; then
1616
netlab create -o d2:bgp bgp.yml && d2 graph.d2 d2-bgp-default.svg
17-
NETLAB_OUTPUTS_D2_RR__SESSIONS=False netlab create -o d2:bgp bgp.yml && d2 graph.d2 d2-bgp-no-rr.svg
17+
NETLAB_OUTPUTS_D2_BGP_RR=False netlab create -o d2:bgp bgp.yml && d2 graph.d2 d2-bgp-no-rr.svg
18+
netlab create -o d2:bgp:vrf bgp.yml && d2 graph.d2 d2-bgp-vrf.svg
19+
netlab create -o d2:bgp:evpn bgp.yml && d2 graph.d2 d2-bgp-evpn.svg
1820
fi

0 commit comments

Comments
 (0)