Developing Plugins
Plugins are either Python files or directories containing Python code plus configuration templates. netlab tries to locate them in the plugin search path (specified in the defaults.paths.plugin setting), which usually includes the user search path and the netsim/extra networklab package directory.
Tip
You can inspect the plugin search path with the netlab show defaults paths.plugin command and modify it if needed.
The plugin name specifies either a Python file name (without the .py extension) or a directory with the plugin.py or __init__.py, optional plugin defaults (defaults.yml), and Jinja2 templates (one per supported netlab_device_type/ansible_network_os).
Warning
This is an underdocumented feature. Performing operations beyond simple data transformation might require digging through the source code. Before proceeding, you might want to open a discussion on netlab GitHub repository.
Plugin Hooks
Plugins can define well-known functions that are invoked during the topology transformation process.
The plugin functions (hooks) are executed at these stages of the transformation process:
Hook |
Executed at |
|---|---|
topology_expand |
Very early in the transformation process, after node roles have been initialized, but before the link data structures are checked or initialized. Use it to add nodes and links to the topology (example: the fabric plugin) |
init |
Early in the transformation process, after all data structures have been initialized, but before they’ve been validated. Executes before module normalize hook |
pre_transform |
Executes after the data structures have been validated, but before any other (module, provider) pre_transform hooks |
pre_node_transform |
Executes before node data transformation. Called after the pre_transform hooks and the module/provider pre_transform functions |
post_node_transform |
Executes after the node data transformation has been completed, before the module post_node_transform hooks |
pre_link_transform |
Executes before link data transformation. Called only when the topology contains links, after link validation and before the module pre_link_transform hooks |
post_link_transform |
Executes after the link data transformation has been completed. Called only when the topology contains links |
post_transform |
Executes after all other data transformations have been completed; the module post_transform hooks are called before the plugin post_transform hook |
pre_quirks |
Executes before the device quirks, but after the core data transformation has been completed. The post-transform hooks have been called, the node module lists have been sorted in dependency order, the group custom configs have been fully evaluated, and the node data structures have been cleaned up |
post_quirks |
Executes after the device quirks, but before the provider post-transform hooks. Use this hook when you have to inspect the (almost) final data structure |
post_provider_transform |
Executes after the provider post-transform hooks. Use this hook when you have to modify the provider-specific data structures. |
cleanup |
Executes as the very last step of the transformation process, after the topology data structures have been finalized (module cleanup hooks are called before the plugin cleanup hook). Use it to delete temporary data structures |
Notes:
Every plugin function is called with a single topology argument: the current topology data structure. The node or link-manipulation functions must iterate over the
topology.nodesdictionary or thetopology.linkslist.Plugins extending configuration modules might have to define additional module attributes. The module attribute lists must be extended before any module validation code is executed, either by defining the attributes in the plugin defaults (see also sample plugin) or in the plugin init function.
Plugin CLI Hooks
Plugins are usually used in the data transformation process, but could also be used in later stages of configuration file creation and lab management.
A plugin can add its name to the defaults.netlab.create.plugin list to be called at the time the configuration files are created:
The
outputhook is called before any output modules are called and before netlab finalizes the search path lists.The
post_outputhook is called after the output modules have created the configuration files.
A plugin can also be called during the netlab up/netlab down processing:
The plugin must add its name to the
defaults.netlab._command_.pluginlist (where command is up or down)Whenever a netlab command calls a CLI hook, it calls the plugin pre-shell hook before the CLI command is executed and the plugin post-shell hook after the CLI command has successfully completed[1]
The plugin hook name is created from the CLI hook name and the
pre_shell_andpost_shell_prefixes. For example,pre_shell_pre_start_labhook is called before thepre_start_labCLI command is executed, and thepost_shell_pre_start_labhook is called after the CLI command has completed.
Plugin Metadata
A plugin can specify global variables that are used to influence the plugin’s behavior or order of execution:
_requires: A list of prerequisite modules and plugins. netlab will abort if any prerequisite plugins are not listed in the topology.plugin list, or if any of the prerequisite modules are not used by at least one node._execute_after: A list of plugins that should execute before the current plugin. For example, the ebgp.multihop plugin has to be executed after ebgp.utils plugin, and therefore defines_execute_after = [ 'ebgp.utils' ]_config_name: The name of extra configuration templates to add to the node config attribute when a node using the plugin functionality requires additional device configuration. This variable is set during the plugin initialization process, but it’s still recommended to define it in the plugin and set its value to a string to prevent mypy complaints.
Plugin Defaults
A directory containing the plugin.py Python module can include plugin defaults (defaults.yml). The contents of the defaults.yml file are merged with the topology defaults after processing merge and copy requests for attributes and device features.
Merging Attributes
Within the defaults.yml file, you can copy an attribute definition from another attribute with the copy or merge parameter that contains the namespace of the other attribute. The copy/merge parameter must be specified on a top-level attribute within an attribute namespace.
For example, the bgp.session plugin copies the node definition of the bgp.gtsm attribute from the global definition. This is the relevant part of the defaults.yml file:
bgp:
attributes:
global:
gtsm:
type: int
min_value: 1
max_value: 254
true_value: 1
node:
gtsm:
copy: global
Merging Device Features
If your plugin relies on device features, you can copy/merge them between similar devices with the copy or merge keyword anywhere below the device name. For example, this is the definition of BGP policy features for all IOS-based devices:
devices:
iosv.features.bgp:
_default_locpref: True
bandwidth:
in: auto
cat8000v:
copy: iosv
csr:
copy: iosv
iol:
copy: iosv
ioll2:
copy: iosv
However, most device features (at least for system plugins) should be predefined in the devices YAML files.
Sample Plugin
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.
The topology file used in the BGP anycast example uses group node data 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:
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 bgp.anycast attribute is defined in the plugin defaults (anycast/defaults.yml) (see Lab Topology Attribute Validation for details):
bgp.attributes.node.anycast:
type: ipv4
use: prefix
The plugin imports netsim.api module to get access to the plugin helper functions.
import sys
from box import Box
from netsim import api
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 also sets the config node parameter to deploy a custom configuration template that creates additional loopback interface with the anycast IP address.
def post_transform(topo: Box) -> None:
global _config_name
for node in topo.nodes.values():
if 'bgp' in node:
if 'anycast' in node.bgp:
...
api.node_config(node,_config_name)
Notes:
The global
_config_namevariable is set during the plugin initialization.api.node_configappends the specified custom configuration template to the list of node configuration templates. While equivalent tonode.config.append(template), the utility function handles edge cases like a missing config attribute or duplicate configuration templates.