Skip to content

Commit 5fb4e60

Browse files
committed
Use 'files' plugin to implement inline configuration templates
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 f0097fd commit 5fb4e60

File tree

7 files changed

+180
-13
lines changed

7 files changed

+180
-13
lines changed

docs/plugins/files.md

Lines changed: 86 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

@@ -60,8 +69,14 @@ For example, the following **configlets** dictionary creates **ifup.j2** and **i
6069

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

6782
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 +125,72 @@ configlets:
110125
interface Management1
111126
description Management interface
112127
```
128+
129+
## Inline Node/Group Configuration Templates
130+
131+
The **files** plugin allows you to specify the contents of custom configuration templates directly in the node- or group **config** attribute.
132+
133+
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).
134+
135+
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.
136+
137+
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:
138+
139+
```
140+
plugin: [ files ]
141+
module: [ bgp ]
142+
143+
nodes:
144+
dut:
145+
bgp.as: 65000
146+
147+
x1:
148+
id: 10
149+
bgp.as: 65010
150+
bgp.originate: 172.0.42.0/24
151+
config.inline: |
152+
route-map setcomm permit 10
153+
set community 65000:1 additive
154+
set extcommunity bandwidth 100
155+
set large-community 65000:0:1 additive
156+
exit
157+
!
158+
router bgp {{ bgp.as }}
159+
!
160+
address-family ipv4 unicast
161+
{% for n in bgp.neighbors %}
162+
neighbor {{ n.ipv4 }} route-map setcomm out
163+
{% endfor %}
164+
165+
x2:
166+
id: 11
167+
bgp.as: 65011
168+
```
169+
170+
```{tip}
171+
Always use the YAML *literal ‌block scalar header* (`|`) for the custom configuration content; otherwise, the whole content will be folded into a single line.
172+
```
173+
174+
## Inline Configuration Changes in Validation Tests
175+
176+
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:
177+
178+
```
179+
validate:
180+
...
181+
bgp_lbd:
182+
description: Shut down the loopback interface
183+
config:
184+
inline:
185+
frr: |
186+
#!/bin/bash
187+
ip link set lo down
188+
pass: BGP prefix is no longer announced
189+
nodes: [ xf ]
190+
```
191+
192+
```{tip}
193+
Always use the YAML *literal ‌block scalar header* (`|`) for the configuration content; otherwise, the whole content will be folded into a single line.
194+
```
195+
196+

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)