Custom Plugins¶
netlab supports dynamically loadable plugins allowing you to implement custom data model transformations without adding nerd knobs to the core topology transformation.
Warning
This is an underdocumented feature. A few commonly-used functions are defined in netsim.api
; performing operations beyond simple data transformation might require digging through the source code. You might want to open a discussion on netsim-tools GitHub repository before proceeding.
Plugins needed by a topology file are listed in the plugin top-level element, for example:
plugin: [ bgp-anycast ]
module: [ ospf, bgp ]
...
Plugins are either Python files or directories containing Python code plus configuration templates. They are loaded from the current directory or netsim/extra
directory.
For simple plugins, the plugin name specifies the file name (without the .py
extension). For plugin packages, the plugin name specifies the directory with plugin.py
Python module and one or more Jinja2 templates (one per supported netlab_device_type/ansible_network_os).
Plugins can define well-known functions that are invoked during the topology transformation process which includes these steps:
execute plugin init function
check topology top-level elements
adjust global parameters (defaults), node list, link list, and address pools
execute plugin pre_transform function
execute module pre_transform function
adjust groups (including setting node data from node_data)
execute plugin pre_node_transform function
transform node data
execute plugin post_node_transform function
execute plugin pre_link_transform function
transform link data
execute plugin post_link_transform function
execute module post_transform function
execute plugin post_transform function
Every plugin function is called with a single topology argument: the current topology data structure. The node- or link-manipulation functions must iterate over topology.nodes
or topology.links
lists.
Plugins extending configuration modules might have to define additional module attributes. The module attribute lists have to be extended in the init function before any module validation code is executed.
Example¶
All anycast servers in a BGP anycast topology should have the same AS number, but do not need IBGP sessions between themselves. A custom plugin deletes IBGP sessions for any node with bgp.anycast attribute.
This is the topology file used in BGP anycast example. It uses node_data attribute on a BGP AS group to set bgp.anycast node attribute on any node in AS 65101
plugin: [ bgp-anycast ]
module: [ ospf, bgp ]
defaults:
device: iosv
bgp:
as_list:
65000:
members: [ l1, l2, l3, s1 ]
rr: [ s1 ]
65101:
members: [ a1,a2,a3 ]
groups:
as65101:
node_data:
bgp.anycast: 10.42.42.42/32
nodes:
[ l1, l2, l3, s1, a1, a2, a3 ]
links: [ s1-l1, s1-l2, s1-l3, l2-a1, l2-a2, l3-a3 ]
The plugin imports common netsim module to create error messages, and api module to get common utility functions.
import sys
from box import Box
from netsim import common
from netsim import api
The initialization function adds anycast attribute to bgp node attributes:
def init(topo: Box) -> None:
topo.defaults.bgp.attributes.node.append('anycast')
The custom transformation is executed as the last step of the topology transformation – the post_transform function removes IBGP neighbors from all nodes with bgp.anycast attribute.
def post_transform(topo: Box) -> None:
...
for node in topo.nodes.values():
if 'bgp' in node:
if 'anycast' in node.bgp:
node.bgp.advertise_loopback = False
node.bgp.neighbors = [
n for n in node.bgp.neighbors
if n.type != 'ibgp' ]
...
The post_transform function should also set the config node parameter to deploy custom configuration template that creates additional loopback interface with the anycast IP address.
def post_transform(topo: Box) -> None:
config_name = api.get_config_name(globals())
for node in topo.nodes.values():
if 'bgp' in node:
if 'anycast' in node.bgp:
...
api.node_config(node,config_name)
Notes:
api.get_config_name
gets theconfig_name
plugin attribute (set to the plugin directory during the plugin initialization process) and reports an error if the plugin calling it isn’t a part of a plugin package.api.node_config
appends the specified custom configuration template to the list of node configuration templates. While equivalent to…
node.config = node.get('config',[]).append(template)
… the utility function handles edge cases like missing config attribute or duplicate configuration templates.