Skip to content

Commit 77d67bd

Browse files
committed
Recognize user-defined types in 'type:' attribute definition
Fixes the original complaint from #3431 Also: * Update documentation * Explain how to create new boxes in AGENTS.md * Add a code coverage test
1 parent e7af44e commit 77d67bd

5 files changed

Lines changed: 54 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ With the Box settings used in the topology data structure:
9292
* The interim dictionaries are created automatically: `a.b.c=1` works even when `a.b` does not exist yet.
9393
* The dotted paths can be used in indices (for example, `a['b.c'] = 1`) and in **in** tests (for example `'b.c' in a`)
9494

95+
IMPORTANT -- the Box objects must have the default_box and box_dots flags set to work as expected:
96+
97+
* When creating a new Box object from a dictionary, use data.get_box function
98+
* When creating an empty Box object, use data.get_empty_box function
99+
95100
### Using Jinja2 templates
96101

97102
* The Jinja2 environment uses a custom `undefined` method that can handle dictionary hierarchy. For example, `a.b.c is defined` returns False instead of crashing even when `a.b` does not exist. There is no need for an extra `a.b is defined` guard.

docs/dev/validation.md

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ The dictionary-based approach allows in-depth validation of nested attributes, w
8383
(dev-valid-data-types)=
8484
## Valid Data Types
8585

86-
Validator recognizes standard Python data types (**str**, **int**, **float**, **bool**, **list** or **dict**) and the following networking-specific data types:
86+
Validator recognizes standard Python data types (**str**, **int**, **float**, **bool**, **list** or **dict**), the following networking-specific data types, and [user-defined data types](validate-user-types).
8787

8888
| Data type. | Meaning |
8989
|----------------|---------|
@@ -104,7 +104,7 @@ Validator recognizes standard Python data types (**str**, **int**, **float**, **
104104
| **r_proto** | Routing protocol identifier |
105105
| **time** | Time duration specified in seconds (`s`) or milliseconds (`ms`) |
106106

107-
The data type can be specified as a string (without additional parameters) or a dictionary with a **type** attribute (data type as a string) and other type-specific validation parameters.
107+
The data type can be specified as a string (without additional parameters) or a dictionary with a **type** attribute (data type as a string) and other type-specific validation parameters. When specifying a user-defined data type with the **type** attribute, the definition of the user-defined data type is *merged* with the type definition.
108108

109109
For example, VLAN link parameters include **access** and **native**, which can be any string value, as well as **mode** that must have one of the predefined values:
110110

@@ -412,16 +412,6 @@ bgp:
412412
timers: exbs_timers
413413
```
414414

415-
Please note that the following definition (using `exbs_timers` as the value of `timers` **type** attribute) would not work:
416-
417-
```
418-
bgp:
419-
attributes:
420-
global:
421-
timers:
422-
type: exbs_timers ### INVALID, WON'T WORK
423-
```
424-
425415
You can use the `_namespace` attribute within the user-defined data types to add attributes from other objects. For example, as you can use link attributes in VLAN definitions, the **vlan** definition (see `modules/vlan.yml`) includes the `_namespace` attribute:
426416

427417
```

netsim/data/validate.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from box import Box
88

99
from ..utils import log
10-
from . import get_a_list, get_empty_box
10+
from . import get_a_list, get_box, get_empty_box
1111

1212
# We also need to import the whole data.types module to be able to do validation function lookup
1313
from . import types as _tv
@@ -395,6 +395,11 @@ def transform_validation_shortcuts(data_type: typing.Any) -> typing.Union[Box,di
395395

396396
# Validating a dictionary against a dictionary of elements without a specified type
397397
if isinstance(data_type,Box):
398+
if 'type' in data_type and isinstance(data_type.type,str):
399+
if topo_attributes and data_type.type in topo_attributes:
400+
base_type = transform_validation_shortcuts(topo_attributes[data_type.type])
401+
data_type = get_box(base_type) + get_box({ k:v for k,v in data_type.items() if k != 'type' })
402+
398403
if not 'type' in data_type:
399404
data_keys = { k:v for k,v in data_type.items() if not k.startswith('_') }
400405
data_type = Box({ k:v for k,v in data_type.items() if k.startswith('_') })
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
IncorrectType in nodes: attribute 'nodes.b.bri' must be an integer or bgp_role_name, found str
2+
... use 'netlab show attributes node' to display valid attributes
3+
IncorrectValue in nodes: attribute nodes.f.brn has invalid value(s): cx
4+
... valid values are: provider, customer, peer, rs-server, rs-client
5+
IncorrectValue in nodes: attribute nodes.f.brx has invalid value(s): x
6+
... valid values are: a, b
7+
IncorrectType in nodes: attribute 'nodes.f.bri' must be an integer or bgp_role_name, found str
8+
Fatal error in netlab: Cannot proceed beyond this point due to errors, exiting
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#
2+
# Code coverage test for user-defined data types used in "unusual" places
3+
#
4+
# Using user-defined data type in type: definition already works.
5+
# We still cannot use a user-defined data type as _alt_type
6+
#
7+
defaults.device: none
8+
9+
defaults.attributes:
10+
bgp_role_name:
11+
type: str
12+
valid_values: [ provider, customer, peer, rs-server, rs-client ]
13+
node:
14+
brn:
15+
type: bgp_role_name
16+
brx:
17+
type: bgp_role_name
18+
valid_values: [ a, b ]
19+
bri:
20+
type: int
21+
_alt_types: [ bgp_role_name ]
22+
23+
nodes:
24+
a:
25+
brn: provider # Should succeed
26+
brx: a # Should succeed
27+
bri: 1 # Should succeed
28+
b:
29+
bri: provider # Should succeed
30+
f:
31+
brn: cx # Should fail (invalid value)
32+
brx: x # Should fail (invalid value)
33+
bri: x # Should fail (invalid value)

0 commit comments

Comments
 (0)