Skip to content

Commit 4e8e85c

Browse files
authored
Implement '_invalid_with' data validation option (#2849)
Some attributes (for example, 'set' in a routing policy entry) cannot be used with other attributes, or when another attribute has a specific value. This commit adds '_invalid_with' data validation option and uses it in routing policy entry definition to resolve #2841
1 parent 3b9051b commit 4e8e85c

File tree

8 files changed

+99
-8
lines changed

8 files changed

+99
-8
lines changed

docs/dev/validation.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,17 @@ All attributes defined with a dictionary (**mode** in the above example, but not
122122
* **true_value** -- value to use when the parameter is set to *True*
123123
* **_requires** -- a list of modules that must be enabled in global- or node context to allow the use of this attribute. See `vrfs` in `modules/vrf.yml` and `vlans` in `modules/vlan.yml` for more details.
124124
* **_required** (bool) -- the attribute must be present in the parent dictionary[^CRQ]
125+
* **_valid_with** -- a list or dictionary of attributes that can be used with this attribute ([example](inter-attribute-examples)).
126+
* **_invalid_with** -- specifies attributes that cannot be used together with this attribute. Can be:
127+
128+
- A list of attribute names (this attribute cannot be used when any of the listed attributes are present)
129+
- A dictionary where keys are attribute names and values specify that the conflict occurs when the conflicting attribute has (one of the) the specified value(s)
130+
125131
* **_alt_types** -- [alternate data types](validation-alt-types)
126132

127133
[^CRQ]: This does not make the parent dictionary mandatory, but if it's present, it must have the required attribute. Use a chain of `_required` attributes if you want to enforce the presence of an attribute deep in the data structure.
128134

129-
See [](validation-definition-examples) for more details.
135+
See [](validation-definition-examples) and [](inter-attribute-examples) for more details.
130136

131137
### Further Data Type Validation Options
132138

@@ -275,6 +281,40 @@ mpls:
275281
6pe: { type: list, true_value: [ ibgp ] }
276282
```
277283

284+
(inter-attribute-examples)=
285+
### Attribute Dependency Validation Examples
286+
287+
The **delete.community.list** in a routing policy entry cannot be used with any other **delete.community** attributes. ****
288+
289+
```
290+
delete:
291+
community:
292+
standard: list
293+
extended: list
294+
large: list
295+
list:
296+
type: str
297+
_valid_with: [ ]
298+
299+
```
300+
301+
The **set** or **delete** attributes of a routing policy cannot be used when the **action** attribute is set to **deny** (because the route is dropped anyway):
302+
303+
```
304+
attributes:
305+
rp_entry: # Define routing policy entry
306+
_description: Routing policy entry
307+
action:
308+
type: str
309+
valid_values: [ permit, deny ]
310+
set:
311+
_invalid_with: { action: deny }
312+
...
313+
delete:
314+
_invalid_with: { action: deny }
315+
...
316+
```
317+
278318
(validation-shortcut-type)=
279319
## Shortcut Data Type Definitions
280320

netsim/data/validate.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,28 +424,59 @@ def check_valid_with(
424424
if log.debug_active('validate'):
425425
print(f'check_valid_with {path} {data} against {data_name} {data_type}')
426426

427-
attr_list = list(data.keys()) # Get the list of data attributes
428427
_keys = data_type.get('_keys',data_type) # Get the definition of keys
429428
report_list: typing.List[str] = [] # List of already-reported incompatibilities
430429

431-
for attr in attr_list:
432-
if not isinstance(_keys.get(attr,None),Box): # Skip invalid attributes or simple types
433-
continue
430+
# Get the list of data attributes with complex types
431+
#
432+
data_attr = [ k for k in data.keys() if not isinstance(k,str) or not k.startswith('_') ]
433+
cplx_attr = [ k for k in data_attr if isinstance(_keys.get(k,None),Box) ]
434+
435+
# Check 'valid with' restrictions
436+
#
437+
for attr in cplx_attr:
434438
if '_valid_with' not in _keys[attr]: # Attribute has no restrictions
435439
continue
436440
if attr in report_list: # Incompatibility has already been reported
437441
continue
438-
inv_list = [ x for x in attr_list
442+
inv_list = [ x for x in data_attr
439443
if x != attr and
440-
x not in _keys[attr]._valid_with and
441-
not x.startswith('_') ] # Build a list of incompatible attributes
444+
x not in _keys[attr]._valid_with ] # Build a list of incompatible attributes
442445
if inv_list: # ... and report them
443446
log.error(
444447
f'Attribute(s) {",".join(inv_list)} cannot be used with attribute {attr} in {path}',
445448
category=log.IncorrectAttr,
446449
module=module)
447450
report_list = report_list + inv_list
448451

452+
# Check 'invalid with' restrictions
453+
#
454+
for attr in cplx_attr:
455+
if '_invalid_with' not in _keys[attr]: # Attribute has no incompatibility specs
456+
continue
457+
if attr in report_list: # Incompatibility has already been reported
458+
continue
459+
460+
invalid_with = _keys[attr]._invalid_with # Get the incompatible attribute specs
461+
cplx_invalid = isinstance(invalid_with,Box) # And figure out whether it's a simple list or a box
462+
463+
for inv_attr in invalid_with: # Now iterate over potential incompatible attributes
464+
if inv_attr not in data: # Incompatible attribute not in data structure
465+
continue # ... we're OK, move on
466+
467+
# Get the optional list of incompatible values
468+
inv_values = invalid_with[inv_attr] if cplx_invalid else None
469+
if inv_values is None:
470+
log.error(
471+
f"Attribute '{attr}' cannot be used together with '{inv_attr}' in {path}",
472+
category=log.IncorrectAttr,
473+
module=module)
474+
elif data[inv_attr] in inv_values if isinstance(inv_values,list) else data[inv_attr] == inv_values:
475+
log.error(
476+
f"Attribute '{attr}' cannot be used together with '{inv_attr}' set to '{data[inv_attr]}' in {path}",
477+
category=log.IncorrectAttr,
478+
module=module)
479+
449480
"""
450481
validate_item -- validate a single item from an object:
451482

netsim/modules/routing.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ _top: # Modification of global defaults
9292
min_value: 1
9393
max_value: 32767
9494
set:
95+
_invalid_with: { action: deny }
9596
locpref:
9697
type: int
9798
min_value: 0
@@ -124,6 +125,7 @@ _top: # Modification of global defaults
124125
type: str
125126
_valid_with: [ ]
126127
delete:
128+
_invalid_with: { action: deny }
127129
community:
128130
standard: list
129131
extended: list
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
IncorrectAttr in routing: Attribute 'set' cannot be used together with 'action' set to 'deny' in topology.routing.policy.p1[1]
2+
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Test the "normalize and merge" routing policy processing
2+
#
3+
4+
module: [routing]
5+
defaults.device: none
6+
7+
routing.policy:
8+
p1:
9+
- locpref: 10
10+
action: deny
11+
- set.med: 100
12+
13+
nodes:
14+
r1:
15+
routing.policy:
16+
p1: # Copy from global
File renamed without changes.
File renamed without changes.

tests/run-coverage-tests.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)