Skip to content

Commit ef5e287

Browse files
authored
Transform community lists into type/value format (#2851)
This is a preparatory step for the introduction of extended/large community lists. The final format of the community lists is unchanged, but the transformation into 'type'/'value' format is done earlier in the process as we'll use 'type'/'value' format as the canonical format for the more complex community lists. The final data structure is unchanged, which means that this refactoring does not impact the device configuration templates or any user-side functionality depending on the transformed data structures. The 'rp-clist-expansion' transformation test was modified to include the single-entry box format and the canonical type/value format to test all possible lab topology formats that can be used to specify a standard clist.
1 parent 76970c2 commit ef5e287

File tree

7 files changed

+90
-52
lines changed

7 files changed

+90
-52
lines changed

netsim/modules/routing.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ attributes:
3939
type: dict
4040
_keytype: id
4141
_subtype:
42-
type: list
43-
make_list: True
44-
_subtype: community_entry
42+
type: dict
43+
_keys:
44+
type: { type: str, valid_values: [ standard, extended, large ]}
45+
value:
46+
type: list
47+
make_list: True
48+
_subtype: community_entry
4549
static:
4650
type: dict
4751
_keytype: id

netsim/modules/routing/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
'import' : import_routing_object,
3838
'check' : check_routing_object },
3939
'community': {
40-
'import' : clist.import_community_list,
40+
'import' : import_routing_object,
4141
'check' : check_routing_object },
4242
'static': {
4343
'start' : static.include_global_static_routes
@@ -63,6 +63,7 @@
6363
'community':
6464
{ 'namespace': 'routing.community',
6565
'object' : 'BGP community filter',
66+
'list_attr': 'value',
6667
'callback' : aspath.normalize_aspath_entry },
6768
}
6869

@@ -85,6 +86,7 @@ def normalize_routing_data(r_object: Box, topo_object: bool = False, o_name: str
8586
o_dict=r_object.get(dp['namespace'],None),
8687
o_type=dp['object'],
8788
normalize_callback=dp['callback'],
89+
list_attr=dp.get('list_attr',None),
8890
topo_object=topo_object)
8991
except Exception as ex:
9092
log.warning(

netsim/modules/routing/clist.py

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,6 @@
1212

1313
from ...augment import devices
1414
from ...utils import log
15-
from .normalize import (
16-
import_routing_object,
17-
)
18-
19-
"""
20-
import_community_list -- has to check whether the target clist was already fixed, which means
21-
the transform_dispatch has already been called
22-
"""
23-
def import_community_list(pname: str,o_name: str,node: Box,topology: Box) -> typing.Optional[list]:
24-
clist = node.get(f'routing.{o_name}.{pname}',None)
25-
if isinstance(clist,Box):
26-
return None
27-
28-
return import_routing_object(pname,o_name,node,topology)
2915

3016
"""
3117
replace_community_delete:
@@ -55,7 +41,7 @@ def replace_community_delete(node: Box, p_name: str, p_entry: Box, topology: Box
5541

5642
if clist:
5743
cname = f"DEL_{p_name}_{p_entry.sequence}"
58-
node.routing.community[cname] = { 'action': 'permit', 'cl_type': 'standard', 'value': clist }
44+
node.routing.community[cname] = { 'type': 'standard', 'cl_type': 'standard', 'value': clist }
5945
p_entry.delete.community.list = cname
6046

6147
for kw in ['standard','large','extended']:
@@ -73,6 +59,8 @@ class MissingQuotes(Exception):
7359
pass
7460

7561
def fix_clist_entry(e_clist: Box, topology: Box) -> None:
62+
if '_value' in e_clist: # Is this entry already in the expected format?
63+
return
7664
if 'regexp' in e_clist: # Do we know we have a regexp?
7765
e_clist._value = e_clist.regexp # Copy regular expression into a common variable
7866
return
@@ -101,14 +89,8 @@ def fix_clist_entry(e_clist: Box, topology: Box) -> None:
10189
"""
10290
def expand_community_list(p_name: str,o_name: str,node: Box,topology: Box) -> typing.Optional[list]:
10391
p_clist = node.routing[o_name][p_name] # Shortcut pointer to current community list
104-
if isinstance(p_clist,Box) and 'value' in p_clist: # Have we already transformed this clist?
105-
return None
106-
107-
node.routing[o_name][p_name] = { # Move the permit/deny list into 'value
108-
'value': p_clist
109-
}
110-
111-
p_clist = node.routing[o_name][p_name] # ... and fetch the new shortcut pointer
92+
if 'type' not in p_clist:
93+
p_clist.type = 'standard' # Assume the clist filter for standard communities
11294
regexp = False # Figure out whether we need expanded clist
11395
for (p_idx,p_entry) in enumerate(p_clist.value):
11496
try:

netsim/modules/routing/normalize.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def normalize_routing_objects(
3636
o_dict: typing.Optional[Box],
3737
o_type: str,
3838
normalize_callback: typing.Callable,
39+
list_attr: typing.Optional[str] = None,
3940
topo_object: bool = False) -> None:
4041

4142
if o_dict is None: # Nothing to do, I'm OK with that ;)
@@ -53,10 +54,25 @@ def normalize_routing_objects(
5354
module='routing')
5455
continue
5556

56-
if not isinstance(o_dict[o_name],list): # Object not a list? Let's make it one ;)
57-
o_dict[o_name] = [ o_dict[o_name] ]
57+
o_value = o_dict[o_name]
5858

59-
normalize_routing_object(o_dict[o_name],normalize_callback)
59+
# Convert a non-list value to list only if it's not a box with an attribute
60+
# that is expected to contain a list
61+
#
62+
if not isinstance(o_value,Box) or list_attr is None or list_attr not in o_value:
63+
if not isinstance(o_dict[o_name],list): # Object not a list? Let's make it one ;)
64+
o_dict[o_name] = [ o_dict[o_name] ]
65+
66+
if list_attr: # Are we expected to have a box with a list?
67+
if isinstance(o_dict[o_name],list): # ... but only have a list (maybe created above)?
68+
o_list = o_dict[o_name] # Save the list value for the next step
69+
o_dict[o_name] = { list_attr: o_list } # Convert it to the expected box format
70+
else: # Otherwise the data structure must be a box with a
71+
o_list = o_dict[o_name][list_attr] # ... list attribute or it would have been converted
72+
else: # Or maybe this object is a simple list?
73+
o_list = o_dict[o_name] # ... so store the list value, I guess
74+
75+
normalize_routing_object(o_list,normalize_callback) # Finally, normalize the list
6076

6177
"""
6278
check_routing_object: validate that a device supports the requested routing object
@@ -77,7 +93,13 @@ def check_routing_object(p_name: str,o_type: str, node: Box,topology: Box) -> bo
7793
7894
Return merged routing object or None if the node routing object has not been modified or does not exist
7995
"""
80-
def import_routing_object(pname: str,o_name: str,node: Box,topology: Box) -> typing.Optional[list]:
96+
def import_routing_object(
97+
pname: str,
98+
o_name: str,
99+
node: Box,
100+
topology: Box) -> typing.Optional[list]:
101+
102+
from . import normalize_dispatch
81103
topo_pdata = topology.get(f'routing.{o_name}',{})
82104

83105
# First check whether the node routing object is missing or set to None (no value)
@@ -99,6 +121,11 @@ def import_routing_object(pname: str,o_name: str,node: Box,topology: Box) -> typ
99121

100122
np_data = node.routing[o_name][pname] # Prepare for merge: get node- and global entries
101123
tp_data = topo_pdata[pname]
124+
list_attr = normalize_dispatch[o_name].get('list_attr',None)
125+
if list_attr: # Are we storing the list value in a dict attribute?
126+
np_data = np_data[list_attr] # ... if we do, work with those values
127+
tp_data = tp_data[list_attr]
128+
102129
sqlist = [ pe.sequence for pe in np_data ] # Get the list of sequence numbers from the local policy
103130

104131
# Now get global entries that are missing in the local policy,
@@ -109,8 +136,12 @@ def import_routing_object(pname: str,o_name: str,node: Box,topology: Box) -> typ
109136
return None
110137

111138
np_data = sorted(np_data + tp_add, key= lambda x: x.sequence)
112-
node.routing[o_name][pname] = np_data
113-
return np_data
139+
if list_attr: # Finally, the sorted list must be stored
140+
node.routing[o_name][pname][list_attr] = np_data # ... in the expected dict attribute
141+
else:
142+
node.routing[o_name][pname] = np_data # ... or replace the original list
143+
144+
return node.routing[o_name][pname]
114145

115146
"""
116147
Import or merge global routing policies into node routing policies

netsim/modules/routing/policy.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ def import_policy_filters(pname: str, o_name: str, node: Box, topology: Box) ->
192192
f_import = import_routing_object(p_entry[kw],r_object,node,topology)
193193
if f_import: # If we imported any new data...
194194
if r_object in normalize_dispatch: # ... normalize the filter entries
195-
normalize_routing_object(f_import,normalize_dispatch[r_object]['callback'])
195+
if 'list_attr' in normalize_dispatch[r_object]: # Do we have list entries in a box?
196+
f_import = f_import[normalize_dispatch[r_object]['list_attr']]
197+
if f_import: # Is there anything to process?
198+
normalize_routing_object(f_import,normalize_dispatch[r_object]['callback'])
196199
if r_object in import_dispatch and 'check' in import_dispatch[r_object]:
197200
import_dispatch[r_object]['check'](p_entry[kw],r_object,node,topology)
198201
if r_object in transform_dispatch: # ... and transform the filter into its final form

tests/topology/expected/rp-clist-expansion.yml

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ groups:
66
routing:
77
community:
88
cg1:
9-
- action: permit
10-
path:
11-
- 65000:100
12-
sequence: 10
9+
value:
10+
- action: permit
11+
path:
12+
- 65000:100
13+
sequence: 10
1314
input:
1415
- topology/input/rp-clist-expansion.yml
1516
- package:topology-defaults.yml
@@ -43,13 +44,15 @@ nodes:
4344
cg1:
4445
cl_type: standard
4546
regexp: ''
47+
type: standard
4648
value:
4749
- _value: 65000:100
4850
action: permit
4951
sequence: 10
5052
cl2:
5153
cl_type: standard
5254
regexp: ''
55+
type: standard
5356
value:
5457
- _value: 65000:100
5558
action: permit
@@ -60,6 +63,7 @@ nodes:
6063
cl3:
6164
cl_type: expanded
6265
regexp: regexp
66+
type: standard
6367
value:
6468
- _value: _65000:10[1-2]_
6569
action: permit
@@ -68,6 +72,7 @@ nodes:
6872
cl4:
6973
cl_type: standard
7074
regexp: ''
75+
type: standard
7176
value:
7277
- _value: 65000:104
7378
action: permit
@@ -81,6 +86,7 @@ nodes:
8186
cl5:
8287
cl_type: expanded
8388
regexp: regexp
89+
type: standard
8490
value:
8591
- _value: 65000:100 65001:100
8692
action: deny
@@ -95,6 +101,7 @@ nodes:
95101
cl6:
96102
cl_type: expanded
97103
regexp: regexp
104+
type: standard
98105
value:
99106
- _value: 65000:100 65001:100
100107
action: deny
@@ -106,6 +113,7 @@ nodes:
106113
cl7:
107114
cl_type: standard
108115
regexp: ''
116+
type: standard
109117
value:
110118
- _value: 65000:106
111119
action: permit
@@ -128,15 +136,19 @@ provider: libvirt
128136
routing:
129137
community:
130138
cl4:
131-
- action: permit
132-
path:
133-
- 65000:104
134-
sequence: 10
139+
value:
140+
- action: permit
141+
path:
142+
- 65000:104
143+
sequence: 10
135144
cl5:
136-
- action: permit
137-
path: 65000:100
138-
sequence: 100
145+
value:
146+
- action: permit
147+
path:
148+
- 65000:100
149+
sequence: 100
139150
cl7:
140-
- _value: 65000:106
141-
action: permit
142-
sequence: 10
151+
value:
152+
- _value: 65000:106
153+
action: permit
154+
sequence: 10

tests/topology/input/rp-clist-expansion.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ routing:
1818
- sequence: 100
1919
action: permit
2020
path: 65000:100
21-
cl7: 65000:106
21+
cl7:
22+
action: permit
23+
path: 65000:106
2224

2325
nodes:
2426
r1:
@@ -41,9 +43,11 @@ nodes:
4143
path: [65000:103, 65001:103]
4244
sequence: 30
4345
cl5: # A mix standard and extended conditions ==> extended
44-
- action: deny
45-
path: [65000:100, 65001:100] # first entry is a list of communities
46-
- _6510.:307_ # the second entry is a regexp
46+
type: standard
47+
value:
48+
- action: deny
49+
path: [65000:100, 65001:100] # first entry is a list of communities
50+
- _6510.:307_ # the second entry is a regexp
4751
cl6: # Permit any at the end forces an extended clist
4852
- action: deny
4953
path: [65000:100, 65001:100]

0 commit comments

Comments
 (0)