Skip to content

Commit 1719cd0

Browse files
authored
Use 'files' plugin to implement inline configuration templates (#2580)
This change adds support for inline custom configuration templates in node-, group- and validation test definitions. It allows the user to specify small additions to device configurations directly in the node- or group definition, or to define small changes to device configurations directly in the validation tests.
1 parent 9e66ddd commit 1719cd0

File tree

9 files changed

+193
-16
lines changed

9 files changed

+193
-16
lines changed

docs/custom-config-templates.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(custom-config)=
22
# Custom Configuration Templates
33

4-
You can build complex labs with functionality that is [not yet part of *netlab*](netlab-customize) with the custom configuration templates that can be deployed with **[netlab config](netlab-config)**, **[netlab initial](netlab-initial)** or **[netlab up](netlab-up)** commands. The custom configuration templates could be stored in the lab topology directory, the user's defaults directory, or within the _netlab_ package. See [](dev-find-custom) for more details.
4+
You can build complex labs with functionality that is [not yet part of *netlab*](netlab-customize) with the custom configuration templates that can be deployed with **[netlab config](netlab-config)**, **[netlab initial](netlab-initial)**, or **[netlab up](netlab-up)** commands. The custom configuration templates could be stored in the lab topology directory, the user's defaults directory, or within the _netlab_ package. See [](dev-find-custom) for more details. You can also use the **[files](plugin-files)** plugin to [store the configuration templates in the lab topology](plugin-files-configlets).
55

66
For a one-off deployment of custom configuration templates, use the **netlab config** command. To make the deployment of custom configuration template(s) part of a regular lab initialization process[^CC], use **config** group- or node attribute that can specify either a single template or a list of templates.
77

@@ -53,6 +53,8 @@ Node **config** attributes are merged with the group **‌config** attributes. [
5353
_netlab_ sorts custom configuration templates in the order specified in groups and nodes to speed up their deployment. Specifying `custom: [ a,b ]` on one node and `custom: [ b,a ]` on another will result in a sorting loop and a fatal error.
5454
```
5555

56+
Finally, the **files** plugin [allows you to use **config.inline** node— or group parameter](plugin-files-node-config) and store the configuration changes directly in the lab topology.
57+
5658
(custom-config-groups)=
5759
## Custom Configuration Templates in Hierarchical Groups
5860

@@ -125,6 +127,10 @@ The following configuration templates would be applied to individual nodes in th
125127
| e | g2a, g2b (from g2 via g3), g3 (from g3), -g1 is ignored, e (from e) |
126128
| f | none (it's not a member of any group) |
127129

130+
```{tip}
131+
With the **files** plugin, you can use the [**config.inline** group parameter](plugin-files-node-config) to apply a configuration change to a group of lab devices.
132+
```
133+
128134
(custom-config-multivendor)=
129135
## Multi-Vendor and Device-Specific Templates
130136

docs/plugins/files.md

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ The plugin defines two new topology attributes:
88
* **files** -- a dictionary or list of extra files to create in the lab directory
99
* **configlets** -- a dictionary of custom configuration templates
1010

11+
It also extends the meaning of the node/group **config** attribute and adds the **config.inline** attribute to validation tests.
12+
13+
```eval_rst
14+
.. contents:: Table of Contents
15+
:depth: 2
16+
:local:
17+
:backlinks: none
18+
```
19+
1120
(plugin-files-create)=
1221
## Creating Extra Files
1322

@@ -52,6 +61,7 @@ The files specified in the **files** attribute are created just before the outpu
5261
* The files specified in the **‌files** list will be removed during the `netlab down --cleanup` process, even when they existed before the `netlab up` was executed.
5362
```
5463

64+
(plugin-files-configlets)=
5565
## Creating Custom Configuration Templates
5666

5767
The **configlets** dictionary can be used to create custom configuration templates. Its contents are automatically converted to [elements of the **files** list](plugin-files-create), but provide a more intuitive way of specifying the templates.
@@ -60,8 +70,14 @@ For example, the following **configlets** dictionary creates **ifup.j2** and **i
6070

6171
```
6272
configlets:
63-
ifup: ip link set eth1 up
64-
ifdown: ip link set eth1 down
73+
ifup: |
74+
ip link set eth1 up
75+
ifdown: |
76+
ip link set eth1 down
77+
```
78+
79+
```{tip}
80+
Always use the YAML *literal ‌block scalar header* (`|`) for the configlet content; otherwise, the whole content will be folded into a single line.
6581
```
6682

6783
You can use a more structured **configlets** dictionary to create custom configuration templates for multiple nodes, devices, or providers. For example, the following dictionary creates **ifup/eos.j2** and **ifup/frr.j2** files that can then be used as `ifup` custom configuration templates on Arista EOS or FRR:
@@ -110,3 +126,74 @@ configlets:
110126
interface Management1
111127
description Management interface
112128
```
129+
130+
(plugin-files-node-config)=
131+
## Inline Node/Group Configuration Templates
132+
133+
The **files** plugin allows you to specify the contents of custom configuration templates directly in the node- or group **config** attribute.
134+
135+
If you enabled the **files** plugin, and the content of the **config** attribute is a dictionary, the **files** plugin copies the dictionary into the **configlets** dictionary and replaces it with a list of custom configuration templates (the dictionary keys).
136+
137+
If you don't care about the configuration template names, use the **config.inline** template. **files** plugin will auto-generate a template name and use it to apply extra configuration to the node (or group of nodes) where the **config.inline** template is defined.
138+
139+
For example, the following lab topology creates a template `_n_x1.j2` that is then used to add a route map and extra BGP configuration to X1:
140+
141+
```
142+
plugin: [ files ]
143+
module: [ bgp ]
144+
145+
nodes:
146+
dut:
147+
bgp.as: 65000
148+
149+
x1:
150+
id: 10
151+
bgp.as: 65010
152+
bgp.originate: 172.0.42.0/24
153+
config.inline: |
154+
route-map setcomm permit 10
155+
set community 65000:1 additive
156+
set extcommunity bandwidth 100
157+
set large-community 65000:0:1 additive
158+
exit
159+
!
160+
router bgp {{ bgp.as }}
161+
!
162+
address-family ipv4 unicast
163+
{% for n in bgp.neighbors %}
164+
neighbor {{ n.ipv4 }} route-map setcomm out
165+
{% endfor %}
166+
167+
x2:
168+
id: 11
169+
bgp.as: 65011
170+
```
171+
172+
```{tip}
173+
Always use the YAML *literal ‌block scalar header* (`|`) for the custom configuration content; otherwise, the whole content will be folded into a single line.
174+
```
175+
176+
(plugin-files-validation-config)=
177+
## Inline Configuration Changes in Validation Tests
178+
179+
Once the **files** plugin is activated, the validation tests can use the **config.inline** attribute to specify the changes that should be made to the device configuration directly in the validation test, for example:
180+
181+
```
182+
validate:
183+
...
184+
bgp_lbd:
185+
description: Shut down the loopback interface
186+
config:
187+
inline:
188+
frr: |
189+
#!/bin/bash
190+
ip link set lo down
191+
pass: BGP prefix is no longer announced
192+
nodes: [ xf ]
193+
```
194+
195+
```{tip}
196+
Always use the YAML *literal ‌block scalar header* (`|`) for the configuration content; otherwise, the whole content will be folded into a single line.
197+
```
198+
199+

docs/topology/validate.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ You can also set these test string attributes to prettify the test results:
4040

4141
The **show**, **exec**, and **valid** parameters can be strings or dictionaries. If you're building a lab that will be used with a single platform, specify them as strings; if you want to execute tests on different platforms, specify a dictionary of commands and Python validation snippets. The values of these parameters can be Jinja2 expressions (see [](validate-multi-platform) for more details).
4242

43-
The **config** parameter can be a string (the template to deploy) or a dictionary with two parameters:
43+
The **config** parameter can be a string (the template to deploy) or a dictionary with these parameters:
4444

45-
* **template**: the template to deploy
45+
* **template**: the template to deploy, or
46+
* **inline**: the [configuration change that has to be applied](plugin-files-validation-config) (needs **[files](plugin-files)** plugin to work)
4647
* **variable**: a dictionary of variable values that will be passed to the Ansible playbook as external variables. You can use these variables to influence the functionality of the configuration template ([example](validate-config))
4748

4849
**Notes:**

netsim/defaults/attributes.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ node:
9292
device: device
9393
box: str
9494
id: { type: int, min_value: 1, max_value: 150 }
95-
config: list
95+
config:
96+
type: dict
97+
_subtype:
98+
type: error
99+
_err_msg: Enable "files" plugin to use the inline node/group configuration template(s)
100+
_alt_types: [ list ]
96101
group: list
97102
role: { type: str, valid_values: [ router, host, bridge, gateway ] }
98103
mgmt:
@@ -183,6 +188,9 @@ _v_entry: # Validation entry
183188
config:
184189
template: str
185190
variable: dict
191+
inline:
192+
type: error
193+
_err_msg: Enable "files" plugin to use the "inline" configuration template
186194
_alt_types: [ str ]
187195
valid: _v_option
188196
suzieq:

netsim/extra/files/plugin.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,73 @@ def build_files(cfg: Box, path: str) -> None: # Recursively transform conf
6262

6363
topology.pop('configlets',None) # We no longer need this
6464

65+
"""
66+
Change 'validation._test_.config.inline' validation elements into templates
67+
"""
68+
def inline_validation_templates(topology: Box) -> bool:
69+
found_inline = False
70+
if not isinstance(topology.validate,Box): # We're in pre-validation phase, so we can't trust anything
71+
return False
72+
for t_name,test in topology.validate.items(): # Iterate over tests
73+
i_value = test.get('config.inline',None) # Does this test have 'config.inline' element?
74+
if i_value is None: # Nope, we're good
75+
continue
76+
v_template = f'_v_{t_name}' # Configlet name is derived from test name
77+
topology.configlets[v_template] = i_value # Save the template value, the next phase will take it from here
78+
test.config.pop('inline',None) # Pop the 'inline' element from test config attribute
79+
test.config.template = v_template # ... and replace it with a configlet template
80+
found_inline = True
81+
82+
return found_inline
83+
84+
"""
85+
Process 'config.inline' node- or group objects
86+
"""
87+
def process_inline_config(topology: Box, o_type: str) -> None:
88+
if o_type not in topology: # The object type is not in current topology
89+
return # --> nothing to do
90+
if not isinstance(topology[o_type],Box): # ... or it's not a Box. Let someone else deal with that
91+
return
92+
93+
for o_name,o_data in topology[o_type].items(): # We got something, iterate over it
94+
if not isinstance(o_data,Box): # The data is not a Box, nothing to do, move on
95+
continue
96+
if not 'config' in o_data: # The object does not have a 'config' element, move on
97+
continue
98+
if not isinstance(o_data.config,Box): # Or maybe it's a traditional 'config' element
99+
continue
100+
c_templates = [] # The fun part starts ;)
101+
c_inline = '_' + o_type[0] + '_' + o_name # Build the name for 'inline' template
102+
for c_n,c_v in o_data.config.items(): # Now change the config keys/values into templates
103+
if c_n == 'inline': # Change 'inline' into object-specific template name
104+
c_n = c_inline
105+
if c_n in topology.configlets:
106+
log.error(
107+
f'{o_type} {o_name} tries to define config template {c_n} that already exists in configlets',
108+
category=log.IncorrectAttr,
109+
module='files') # Overlap in naming
110+
else:
111+
topology.configlets[c_n] = c_v # Add node- or group-defined configlet
112+
c_templates.append(c_n) # ... and remember what templates the node/group uses
113+
114+
o_data.config = c_templates # Finally, make node/group config element a list of templates
115+
65116
"""
66117
Restructure the 'files' and 'configlets'
67118
"""
68119
def init(topology: Box) -> None:
69120
output_hook = False
121+
process_inline_config(topology,'nodes')
122+
process_inline_config(topology,'groups')
123+
if 'validate' in topology:
124+
if inline_validation_templates(topology):
125+
output_hook = True
70126
if 'files' in topology:
71127
restructure_files(topology)
72128
output_hook = True
73129
if 'configlets' in topology:
74130
restructure_configlets(topology)
75131
output_hook = True
76-
77132
if output_hook:
78133
append_to_list(topology.defaults.netlab.create,'output','files')
79134

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
IncorrectType in nodes: attribute 'nodes.a.config' must be a scalar or a list, found dictionary
2-
... use 'netlab show attributes node' to display valid attributes
1+
IncorrectAttr in nodes: Incorrect node attribute 'x' in nodes.a.config
2+
... Enable "files" plugin to use the inline node/group configuration template(s)
3+
IncorrectAttr in nodes: Incorrect node attribute 'y' in nodes.a.config
34
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting

tests/integration/ospf/ospfv2/21-default-policy.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ message: |
66
77
defaults.sources.extra: [ ../../wait_times.yml, ../../warnings.yml ]
88
module: [ ospf ]
9+
plugin: [ files ]
910

1011
groups:
1112
probes:
@@ -62,8 +63,10 @@ validate:
6263
bgp_lbd:
6364
description: Shut down the loopback interface
6465
config:
65-
template: bgp_loopback
66-
variable.lb_state: down
66+
inline:
67+
frr: |
68+
#!/bin/bash
69+
ip link set lo down
6770
pass: BGP prefix is no longer announced
6871
nodes: [ xf ]
6972
df_c:
@@ -75,7 +78,9 @@ validate:
7578
bgp_lbe:
7679
description: Enable the loopback interface
7780
config:
78-
template: bgp_loopback
79-
variable.lb_state: up
81+
inline:
82+
frr: |
83+
#!/bin/bash
84+
ip link set lo up
8085
pass: BGP prefix should be announced
8186
nodes: [ xf ]

tests/integration/ospf/ospfv2/bgp_loopback.j2

Lines changed: 0 additions & 2 deletions
This file was deleted.

tests/integration/vrf/16-vrf-bgp-community.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ message: |
88
99
defaults.sources.extra: [ ../wait_times.yml, ../warnings.yml ]
1010

11+
plugin: [ files ]
12+
1113
groups:
1214
probes:
1315
members: [ x1, x2 ]
@@ -25,11 +27,25 @@ nodes:
2527
dut:
2628
bgp.as: 65000
2729
module: [ vrf, bgp ]
30+
2831
x1:
2932
id: 10
3033
bgp.as: 65010
3134
bgp.originate: 172.0.42.0/24
32-
config: [ frr-community ]
35+
config.inline: |
36+
route-map setcomm permit 10
37+
set community 65000:1 additive
38+
set extcommunity bandwidth 100
39+
set large-community 65000:0:1 additive
40+
exit
41+
!
42+
router bgp {{ bgp.as }}
43+
!
44+
address-family ipv4 unicast
45+
{% for n in bgp.neighbors %}
46+
neighbor {{ n.ipv4 }} route-map setcomm out
47+
{% endfor %}
48+
3349
x2:
3450
id: 11
3551
bgp.as: 65011

0 commit comments

Comments
 (0)