-
Notifications
You must be signed in to change notification settings - Fork 1
Module System
This page explains how to use modules in dot2net.
A topology says what the network is. A module turns that into something a particular tool can act on. Which module you load depends on what you want done for you, and the modules are not all the same kind of thing:
-
Platform modules —
containerlab,tinet,kathara. They write the file their platform reads, so a lab can be deployed by running a command instead of by writing those files by hand:topo.yaml,spec.yaml,kathara/lab.conf, along with the mounts, the links and the commands each of them expresses in its own way. This is where the automation is: the same topology becomes a lab on any of the three. -
Software modules —
frr. They help write the configuration of the software running inside the nodes: a format that turns a block of configuration into vtysh commands, a class that gives a node file logging. They know nothing about how the lab is deployed. -
Checking modules —
assert. They verify what the topology expects of itself, such as a class that must apply to something. -
builtinis always loaded and supplies the default formats.
Loading a module adds some or all of:
-
File definitions - files the module writes (
topo.yaml,spec.yaml, ...) - FormatStyles - ready-made formatting rules
-
Classes - some for its own use, some for a topology to
use: - Requirements - what it needs from the topology, reported when missing
Modules are loaded via the module section in YAML configuration:
module:
- containerlab
- frrEach module defines its output files. For example:
| Module | File | Description |
|---|---|---|
| containerlab | topo.yaml | Containerlab topology file |
| tinet | spec.yaml | TiNET specification file |
These files are generated automatically based on the network model.
Modules may provide FormatStyles for specific configuration formats:
# FRR module provides these FormatStyles
interfaceclass:
- name: ospf
config:
- name: ospf_config
format: FRRVtyshCLI # Provided by frr module
template:
- "router ospf"
- " network {{ .ip_network }} area 0"| Module | FormatStyles |
|---|---|
| frr | FRRVtyshCLI |
| containerlab | clabCmd |
| tinet | tinetSpecCmd |
Modules may require specific config blocks or parameters:
| Module | Required Parameters |
|---|---|
| containerlab |
image, kind
|
| tinet | image |
| kathara |
image (a device without one takes Kathara's base image) |
| frr | - |
If requirements are not met, dot2net reports an error.
A module and a topology have to pass names to each other, and which names may cross is a short list rather than a growing one. Two kinds, and no others:
| What crosses | Where the topology writes it | What it means |
|---|---|---|
| the class names a module publishes | use: |
"give this node what that class carries" |
| the value names a module publishes | values: |
"and set it up like this" |
| the hook names dot2net itself owns | config: - name: |
"run this, wherever this platform runs such things" |
A module's own block names are its business. A topology naming one would be reaching into a module's insides, and the ways the two can talk to each other would multiply with every module and every feature until nobody could say what they are.
A hook is a name a topology writes for, without naming a platform. Write
startup once and containerlab puts it in exec:, TiNET in cmds:, Kathara in
<device>.startup. Without hooks, a topology that runs on three platforms would
have to say the same thing three times, in three places, and keep them in step.
| Hook | When | Where each platform puts it |
|---|---|---|
startup |
once the node is up | containerlab exec:, TiNET cmds:, Kathara <device>.startup
|
teardown |
while the node is still up, before the lab is destroyed | the entry script runs them |
worker_deploy |
before the platform is asked to bring the lab up | the entry script, on the machine |
worker_exec |
before a command is carried into a node | the entry script, on the machine |
worker_collect |
beside the files copied out of the nodes | the entry script, on the machine |
worker_destroy |
after the platform has taken the lab down | the entry script, on the machine |
The first two run inside a node, the four worker_ ones on the machine it
sits on — see
Machine-side hooks. A hook is a hook on
either side of that line: the topology writes the same way, and which platform
is generated for does not change what it wrote.
nodeclass:
- name: router
config:
- name: startup
template:
- "ip link set lo up"A class pulled in with use: can define a template under a hook name too, and
what the module has to do wraps what the topology asked for: merged ahead of
it where the hook sets something up, and after it where the hook takes something
apart (teardown, worker_destroy). A topology's commands run on ground the
module has prepared, and the ground is taken up only once they have finished
with it. So a topology writes one line and nothing else:
nodeclass:
- name: router
use: [frrLogFile] # the module adds its own commands to this node's startupTwo classes of the topology's own naming one hook is still rejected: nothing would say which of them wins.
A module's internal names are _clabNode, _katharaInterface,
_clab_src_endpoint and so on. The underscore says the same thing the rule
above says: these are not for a topology to name. Everything without one is,
and the list below is all of it.
These are the names a topology may write. Anything a module carries that is not here is its own business, and naming it is reaching inside.
containerlab
| Kind | Name | What it is |
|---|---|---|
| class | clabOvsBridgeSetup |
makes and removes an OVS bridge the topology names |
| class | clabLinuxBridgeSetup |
the same for a Linux bridge |
| value | image |
the container image |
| value | kind |
containerlab's own node kind (linux, ovs-bridge, ...) |
| parameter | clab_bridge |
what a switch node is called on the machine, as opposed to in the model |
| parameter | clab_host_port |
what the veth reaching that bridge is called on the machine. On a switch node's interfaces, so the node facing one reads {{ .opp_clab_host_port }}
|
| format | clabCmd |
a command as containerlab's exec: takes it |
| format | clabCopy |
a file copy as containerlab writes one |
| setting | module_config.containerlab.generate_scripts |
write containerlab.sh
|
| setting | module_config.containerlab.management_network |
give nodes containerlab's management network (off by default) |
TiNET
| Kind | Name | What it is |
|---|---|---|
| value | image |
the container image |
| format | tinetSpecCmd |
a command as TiNET's cmds: takes it |
| setting | module_config.tinet.generate_scripts |
write tinet.sh
|
Kathara
| Kind | Name | What it is |
|---|---|---|
| param_rule | kathara_volumes |
the directories mounted into a device |
| param_rule | kathara_copies |
the files placed once a device is up |
| setting | module_config.kathara.mount_dirs |
which directories Kathara mounts |
| setting | module_config.kathara.generate_scripts |
write kathara/kathara.sh
|
FRR
| Kind | Name | What it is |
|---|---|---|
| class | frrLogFile |
makes the log file and names it to a running FRR |
| parameter | frr_log_path |
where that log is written |
| parameter | frr_log_level |
how much FRR writes to it |
| format | FRRVtyshCLI |
a block as vtysh takes it |
assert
| Kind | Name | What it is |
|---|---|---|
| value key | assert_used |
states that a value is meant to be read, so that one never read is reported |
The hook names — startup, teardown, worker_deploy, worker_exec,
worker_collect, worker_destroy — belong to dot2net rather than to any
module, and are listed under The hooks above.
{{ .name }} is not one of these. It is the object's name in the model, and
for a switch node that is not what the machine calls the bridge - write
{{ .clab_bridge }} for that. A command using the wrong one builds, passes
sh -n, and fails when it runs.
A topology is written once, but the platforms are not alike, and where they differ dot2net either hides it or reports it. This table is what was measured while the modules were built; it is here because the differences decide what a topology can ask for.
| containerlab | TiNET | Kathara | |
|---|---|---|---|
| Entry file |
topo.yaml at the output root |
spec.yaml at the output root |
kathara/lab.conf — the lab is a directory |
| How a file reaches a container | bind mount, per file | bind mount, per file |
volume, per directory only — hence mount_dirs
|
| Mount source paths | relative, resolved against the topology file's directory |
absolute: the output is piped to a shell, so $PWD/ is prefixed |
relative, resolved against the lab directory |
| Where startup commands go | exec: |
cmds: |
<device>.startup |
| Are commands run through a shell |
no — the line is split into words, so &&, pipes and redirection do not work |
no | yes — the startup file is a shell script |
| Management network | attached by default, off in dot2net's output unless asked for | none | none |
| Interface names | dot2net's own | dot2net's own |
ethN only for the interfaces it lists (deploy: link), derived from their index in lab.conf. A device the node builds for itself keeps the topology's name |
| A shared medium | a bridge node | a switches: entry |
a collision domain, which has no line of its own |
| More than one machine | one topology file per machine | one spec file per machine | one lab.conf per machine, in a directory of its own. Joining the machines needs Kathara's bridge plugin — see Module: Kathara
|
| A place to describe tests | none | a test: section in spec.yaml, which dot2net does not write yet |
ltest was removed in 3.8.0, replaced by the separate kathara-lab-checker |
The one that catches people is the shell. A startup command with && in it
works on Kathara, whose startup file is a shell script, and goes wrong on the
other two in different ways: containerlab splits the line into words and runs it
directly, so everything after && becomes an argument and the command still
exits 0; TiNET's output is piped to a shell on the host, so the part after
&& runs there instead of in the container. Write one command per line.
assert checks that classes the topology expects to matter really are applied.
A class that nothing carries produces nothing, and a class label that never
matched is invisible — a typo in a DOT file looks exactly like a class with
nothing to say.
module:
- assert
nodeclass:
- name: router
values:
assert_used: "true" # at least one object must carry this classIf no object carries it, dot2net reports it rather than generating a lab with a
piece silently missing. The mark lives in values so that no core vocabulary is
spent on a checking concern.
Multiple modules can be loaded together. Each module's files are generated simultaneously:
module:
- containerlab # Generates topo.yaml
- frr # Provides FormatStyles for FRR configThis generates:
-
topo.yaml- Containerlab topology with node definitions -
r1/etc/frr/frr.conf,r2/etc/frr/frr.conf- FRR configuration files (user-defined)
module:
- containerlab
- frr
file:
- name: frr.conf
path: /etc/frr/frr.conf
nodeclass:
- name: router
values:
image: quay.io/frrouting/frr:8.5.4
kind: linux
config:
- file: frr.conf # From frr module
template:
- "hostname {{ .name }}"
- "!"
- name: startup
template:
- "vtysh -b"Result:
output/
├── topo.yaml # From containerlab module
├── r1/
│ └── etc/frr/frr.conf # User-defined, laid out by its path in the container
└── r2/
└── etc/frr/frr.conf
| Module | Purpose | Documentation |
|---|---|---|
| containerlab | Containerlab topology generation | Module: Containerlab |
| tinet | TiNET specification generation | Module: TiNET |
| kathara | Kathara lab.conf generation |
Module: Kathara |
| frr | FRR FormatStyle and file logging | Module: FRR |
| assert | Checks that classes the topology expects really are applied | Module System |
The interfaces a module implements, and when each one runs, are on Module Development.
- YAML Configuration - Module Configuration
- FormatStyle Design - How FormatStyles work
- File Output - File generation control