annet 0.16.29__py3-none-any.whl → 0.16.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of annet might be problematic. Click here for more details.
- annet/adapters/file/provider.py +3 -0
- annet/adapters/netbox/common/manufacturer.py +2 -0
- annet/adapters/netbox/common/models.py +7 -1
- annet/annlib/netdev/devdb/data/devdb.json +1 -0
- annet/api/__init__.py +24 -15
- annet/bgp_models.py +17 -0
- annet/mesh/basemodel.py +3 -0
- annet/mesh/executor.py +71 -10
- annet/mesh/peer_models.py +4 -0
- annet/mesh/registry.py +15 -0
- annet/rpl_generators/community.py +1 -1
- annet/rpl_generators/cumulus_frr.py +30 -16
- annet/rpl_generators/entities.py +25 -12
- annet/rpl_generators/policy.py +26 -15
- annet/rpl_generators/prefix_lists.py +26 -16
- annet/rulebook/texts/cisco.deploy +1 -1
- annet/storage.py +4 -0
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/METADATA +8 -2
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/RECORD +24 -24
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/WHEEL +1 -1
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/AUTHORS +0 -0
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/LICENSE +0 -0
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/entry_points.txt +0 -0
- {annet-0.16.29.dist-info → annet-0.16.31.dist-info}/top_level.txt +0 -0
annet/adapters/file/provider.py
CHANGED
|
@@ -102,6 +102,9 @@ class Device(DeviceCls, DumpableView):
|
|
|
102
102
|
def add_subif(self, interface: str, subif: int) -> Interface:
|
|
103
103
|
raise NotImplementedError
|
|
104
104
|
|
|
105
|
+
def find_interface(self, name: str) -> Optional[Interface]:
|
|
106
|
+
raise NotImplementedError
|
|
107
|
+
|
|
105
108
|
def neighbours_fqdns(self) -> list[str]:
|
|
106
109
|
return []
|
|
107
110
|
|
|
@@ -214,7 +214,7 @@ class NetboxDevice(Entity):
|
|
|
214
214
|
return type(self) is type(other) and self.url == other.url
|
|
215
215
|
|
|
216
216
|
def is_pc(self) -> bool:
|
|
217
|
-
return self.device_type.manufacturer.name
|
|
217
|
+
return self.device_type.manufacturer.name in ("Mellanox", "Moxa") or self.breed == "pc"
|
|
218
218
|
|
|
219
219
|
def _make_interface(self, name: str, type: InterfaceType) -> Interface:
|
|
220
220
|
return Interface(
|
|
@@ -281,3 +281,9 @@ class NetboxDevice(Entity):
|
|
|
281
281
|
)
|
|
282
282
|
self.interfaces.append(target_port)
|
|
283
283
|
return target_port
|
|
284
|
+
|
|
285
|
+
def find_interface(self, name: str) -> Optional[Interface]:
|
|
286
|
+
for iface in self.interfaces:
|
|
287
|
+
if iface.name == name:
|
|
288
|
+
return iface
|
|
289
|
+
return None
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
"PC.Whitebox.Edgecore": "[Ee]dge-?[Cc]ore",
|
|
117
117
|
"PC.Whitebox.Edgecore.AS": "AS",
|
|
118
118
|
"PC.Whitebox.Edgecore.AS9736": "AS9736",
|
|
119
|
+
"PC.Whitebox.Edgecore.AS9817": "AS9817",
|
|
119
120
|
"PC.Whitebox.NVIDIA": "NVIDIA",
|
|
120
121
|
"PC.Whitebox.NVIDIA.SN": " SN",
|
|
121
122
|
"PC.Whitebox.NVIDIA.SN.SN5400": " SN5400",
|
annet/api/__init__.py
CHANGED
|
@@ -274,12 +274,14 @@ def patch(args: cli_args.ShowPatchOptions, loader: ann_gen.Loader):
|
|
|
274
274
|
|
|
275
275
|
def _patch_worker(device_id, args: cli_args.ShowPatchOptions, stdin, loader: ann_gen.Loader, filterer: filtering.Filterer):
|
|
276
276
|
for res, _, patch_tree in res_diff_patch(device_id, args, stdin, loader, filterer):
|
|
277
|
+
old_files = res.old_files
|
|
277
278
|
new_files = res.get_new_files(args.acl_safe)
|
|
278
279
|
new_json_fragment_files = res.get_new_file_fragments(args.acl_safe)
|
|
279
280
|
if new_files:
|
|
280
281
|
for path, (cfg_text, _cmds) in new_files.items():
|
|
281
282
|
label = res.device.hostname + os.sep + path
|
|
282
|
-
|
|
283
|
+
if old_files.get(path) != cfg_text:
|
|
284
|
+
yield label, cfg_text, False
|
|
283
285
|
elif res.old_json_fragment_files or new_json_fragment_files:
|
|
284
286
|
for path, (new_json_cfg, _cmds) in new_json_fragment_files.items():
|
|
285
287
|
label = res.device.hostname + os.sep + path
|
|
@@ -468,6 +470,9 @@ class PCDeployerJob(DeployerJob):
|
|
|
468
470
|
elif not new_files and not new_json_fragment_files:
|
|
469
471
|
return
|
|
470
472
|
|
|
473
|
+
enable_reload = self.args.entire_reload is not cli_args.EntireReloadFlag.no
|
|
474
|
+
force_reload = self.args.entire_reload is cli_args.EntireReloadFlag.force
|
|
475
|
+
|
|
471
476
|
upload_files: Dict[str, bytes] = {}
|
|
472
477
|
reload_cmds: Dict[str, bytes] = {}
|
|
473
478
|
generator_types: Dict[str, GeneratorType] = {}
|
|
@@ -483,18 +488,23 @@ class PCDeployerJob(DeployerJob):
|
|
|
483
488
|
old_text = jsontools.format_json(old_json_cfg)
|
|
484
489
|
new_text = jsontools.format_json(file_content_or_json_cfg)
|
|
485
490
|
diff_content = "\n".join(_diff_file(old_text, new_text))
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
491
|
+
|
|
492
|
+
if diff_content or force_reload:
|
|
493
|
+
self._has_diff |= True
|
|
494
|
+
|
|
495
|
+
upload_files[file] = file_content.encode()
|
|
496
|
+
generator_types[file] = generator_type
|
|
497
|
+
self.cmd_lines.append("= %s/%s " % (device.hostname, file))
|
|
498
|
+
self.cmd_lines.extend([file_content, ""])
|
|
499
|
+
self.diff_lines.append("= %s/%s " % (device.hostname, file))
|
|
500
|
+
self.diff_lines.extend([diff_content, ""])
|
|
501
|
+
|
|
502
|
+
if enable_reload:
|
|
503
|
+
reload_cmds[file] = cmds.encode()
|
|
504
|
+
self.cmd_lines.append("= Deploy cmds %s/%s " % (device.hostname, file))
|
|
505
|
+
self.cmd_lines.extend([cmds, ""])
|
|
506
|
+
|
|
507
|
+
if self._has_diff:
|
|
498
508
|
self.deploy_cmds[device] = {
|
|
499
509
|
"files": upload_files,
|
|
500
510
|
"cmds": reload_cmds,
|
|
@@ -534,13 +544,12 @@ class Deployer:
|
|
|
534
544
|
self._filterer = filtering.filterer_connector.get()
|
|
535
545
|
|
|
536
546
|
def parse_result(self, job: DeployerJob, result: ann_gen.OldNewResult):
|
|
537
|
-
entire_reload = self.args.entire_reload
|
|
538
547
|
logger = get_logger(job.device.hostname)
|
|
539
548
|
|
|
540
549
|
job.parse_result(result)
|
|
541
550
|
self.failed_configs.update(job.failed_configs)
|
|
542
551
|
|
|
543
|
-
if job.has_diff()
|
|
552
|
+
if job.has_diff():
|
|
544
553
|
self.cmd_lines.extend(job.cmd_lines)
|
|
545
554
|
self.deploy_cmds.update(job.deploy_cmds)
|
|
546
555
|
self.diffs.update(job.diffs)
|
annet/bgp_models.py
CHANGED
|
@@ -286,13 +286,30 @@ def _used_policies(peer: Union[Peer, PeerGroup]) -> Iterable[str]:
|
|
|
286
286
|
yield peer.export_policy
|
|
287
287
|
|
|
288
288
|
|
|
289
|
+
def _used_redistribute_policies(opts: Union[GlobalOptions, VrfOptions]) -> Iterable[str]:
|
|
290
|
+
for red in opts.ipv4_unicast.redistributes:
|
|
291
|
+
if red.policy:
|
|
292
|
+
yield red.policy
|
|
293
|
+
for red in opts.ipv6_unicast.redistributes:
|
|
294
|
+
if red.policy:
|
|
295
|
+
yield red.policy
|
|
296
|
+
for red in opts.ipv4_labeled_unicast.redistributes:
|
|
297
|
+
if red.policy:
|
|
298
|
+
yield red.policy
|
|
299
|
+
for red in opts.ipv6_labeled_unicast.redistributes:
|
|
300
|
+
if red.policy:
|
|
301
|
+
yield red.policy
|
|
302
|
+
|
|
303
|
+
|
|
289
304
|
def extract_policies(config: BgpConfig) -> Sequence[str]:
|
|
290
305
|
result: list[str] = []
|
|
291
306
|
for vrf in config.global_options.vrf.values():
|
|
292
307
|
for group in vrf.groups:
|
|
293
308
|
result.extend(_used_policies(group))
|
|
309
|
+
result.extend(_used_redistribute_policies(vrf))
|
|
294
310
|
for group in config.global_options.groups:
|
|
295
311
|
result.extend(_used_policies(group))
|
|
296
312
|
for peer in config.peers:
|
|
297
313
|
result.extend(_used_policies(peer))
|
|
314
|
+
result.extend(_used_redistribute_policies(config.global_options))
|
|
298
315
|
return result
|
annet/mesh/basemodel.py
CHANGED
|
@@ -133,6 +133,9 @@ class BaseMeshModel:
|
|
|
133
133
|
raise AttributeError(f"{self.__class__.__name__} has no field {key}")
|
|
134
134
|
super().__setattr__(key, value)
|
|
135
135
|
|
|
136
|
+
def is_empty(self):
|
|
137
|
+
return not self.__dict__
|
|
138
|
+
|
|
136
139
|
|
|
137
140
|
ModelT = TypeVar("ModelT", bound=BaseMeshModel)
|
|
138
141
|
|
annet/mesh/executor.py
CHANGED
|
@@ -57,6 +57,11 @@ class MeshExecutor:
|
|
|
57
57
|
rule_global_opts = MeshGlobalOptions(rule.match, device)
|
|
58
58
|
logger.debug("Running device handler: %s", handler_name)
|
|
59
59
|
rule.handler(rule_global_opts)
|
|
60
|
+
|
|
61
|
+
if rule_global_opts.is_empty():
|
|
62
|
+
# nothing was set
|
|
63
|
+
continue
|
|
64
|
+
|
|
60
65
|
try:
|
|
61
66
|
global_opts = merge(global_opts, rule_global_opts)
|
|
62
67
|
except MergeForbiddenError as e:
|
|
@@ -79,7 +84,7 @@ class MeshExecutor:
|
|
|
79
84
|
rule: MatchedDirectPair,
|
|
80
85
|
ports: list[tuple[str, str]],
|
|
81
86
|
all_connected_ports: list[tuple[str, str]],
|
|
82
|
-
) -> Pair:
|
|
87
|
+
) -> Optional[Pair]:
|
|
83
88
|
session = MeshSession()
|
|
84
89
|
handler_name = self._handler_name(rule.handler)
|
|
85
90
|
logger.debug("Running direct handler: %s", handler_name)
|
|
@@ -102,6 +107,10 @@ class MeshExecutor:
|
|
|
102
107
|
else:
|
|
103
108
|
rule.handler(peer_neighbor, peer_device, session)
|
|
104
109
|
|
|
110
|
+
if peer_neighbor.is_empty() and peer_device.is_empty() and session.is_empty():
|
|
111
|
+
# nothing was set
|
|
112
|
+
return None
|
|
113
|
+
|
|
105
114
|
try:
|
|
106
115
|
neighbor_dto = merge(DirectPeerDTO(), peer_neighbor, session)
|
|
107
116
|
except MergeForbiddenError as e:
|
|
@@ -124,18 +133,29 @@ class MeshExecutor:
|
|
|
124
133
|
# we merge them according to remote fqdn
|
|
125
134
|
neighbor_peers: dict[PeerKey, Pair] = {}
|
|
126
135
|
# TODO batch resolve
|
|
127
|
-
|
|
136
|
+
rules = self._registry.lookup_direct(device.fqdn, device.neighbours_fqdns)
|
|
137
|
+
fqdns = {
|
|
138
|
+
rule.name_right if rule.direct_order else rule.name_left
|
|
139
|
+
for rule in rules
|
|
140
|
+
}
|
|
141
|
+
neigbors = {
|
|
142
|
+
d.fqdn: d for d in self._storage.make_devices(list(fqdns))
|
|
143
|
+
}
|
|
144
|
+
for rule in rules:
|
|
128
145
|
handler_name = self._handler_name(rule.handler)
|
|
129
146
|
if rule.direct_order:
|
|
130
|
-
neighbor_device =
|
|
147
|
+
neighbor_device = neigbors[rule.name_right]
|
|
131
148
|
else:
|
|
132
|
-
neighbor_device =
|
|
149
|
+
neighbor_device = neigbors[rule.name_left]
|
|
133
150
|
all_connected_ports = [
|
|
134
151
|
(p1.name, p2.name)
|
|
135
152
|
for p1, p2 in self._storage.search_connections(device, neighbor_device)
|
|
136
153
|
]
|
|
137
154
|
for ports in rule.port_processor(all_connected_ports):
|
|
138
155
|
pair = self._execute_direct_pair(device, neighbor_device, rule, ports, all_connected_ports)
|
|
156
|
+
if pair is None:
|
|
157
|
+
# nothing was set
|
|
158
|
+
continue
|
|
139
159
|
addr = getattr(pair.connected, "addr", None)
|
|
140
160
|
if addr is None:
|
|
141
161
|
raise ValueError(f"Handler `{handler_name}` returned no peer addr")
|
|
@@ -171,6 +191,9 @@ class MeshExecutor:
|
|
|
171
191
|
peer_virtual = VirtualPeer(num=order_number)
|
|
172
192
|
|
|
173
193
|
rule.handler(peer_device, peer_virtual, session)
|
|
194
|
+
if peer_virtual.is_empty() and peer_device.is_empty() and session.is_empty():
|
|
195
|
+
# nothing was set
|
|
196
|
+
continue
|
|
174
197
|
|
|
175
198
|
try:
|
|
176
199
|
virtual_dto = merge(VirtualPeerDTO(), peer_virtual, session)
|
|
@@ -200,21 +223,34 @@ class MeshExecutor:
|
|
|
200
223
|
# we can have multiple rules for the same pair
|
|
201
224
|
# we merge them according to remote fqdn
|
|
202
225
|
connected_peers: dict[PeerKey, Pair] = {}
|
|
203
|
-
|
|
226
|
+
|
|
227
|
+
rules = self._registry.lookup_indirect(device.fqdn, all_fqdns)
|
|
228
|
+
fqdns = {
|
|
229
|
+
rule.name_right if rule.direct_order else rule.name_left
|
|
230
|
+
for rule in rules
|
|
231
|
+
}
|
|
232
|
+
connected_devices = {
|
|
233
|
+
d.fqdn: d for d in self._storage.make_devices(list(fqdns))
|
|
234
|
+
}
|
|
235
|
+
for rule in rules:
|
|
204
236
|
session = MeshSession()
|
|
205
237
|
handler_name = self._handler_name(rule.handler)
|
|
206
238
|
logger.debug("Running indirect handler: %s", handler_name)
|
|
207
239
|
if rule.direct_order:
|
|
208
|
-
connected_device =
|
|
240
|
+
connected_device = connected_devices[rule.name_right]
|
|
209
241
|
peer_device = IndirectPeer(rule.match_left, device)
|
|
210
242
|
peer_connected = IndirectPeer(rule.match_right, connected_device)
|
|
211
243
|
rule.handler(peer_device, peer_connected, session)
|
|
212
244
|
else:
|
|
213
|
-
connected_device =
|
|
245
|
+
connected_device = connected_devices[rule.name_left]
|
|
214
246
|
peer_connected = IndirectPeer(rule.match_left, connected_device)
|
|
215
247
|
peer_device = IndirectPeer(rule.match_right, device)
|
|
216
248
|
rule.handler(peer_connected, peer_device, session)
|
|
217
249
|
|
|
250
|
+
if peer_connected.is_empty() and peer_device.is_empty() and session.is_empty():
|
|
251
|
+
# nothing was set
|
|
252
|
+
continue
|
|
253
|
+
|
|
218
254
|
try:
|
|
219
255
|
connected_dto = merge(IndirectPeerDTO(), peer_connected, session)
|
|
220
256
|
except MergeForbiddenError as e:
|
|
@@ -265,7 +301,7 @@ class MeshExecutor:
|
|
|
265
301
|
def _to_bgp_global(self, global_options: GlobalOptionsDTO) -> GlobalOptions:
|
|
266
302
|
return to_bgp_global_options(global_options)
|
|
267
303
|
|
|
268
|
-
def
|
|
304
|
+
def _apply_direct_interface_changes(
|
|
269
305
|
self, device: Device, neighbor: Device, ports: list[str], changes: InterfaceChanges,
|
|
270
306
|
) -> str:
|
|
271
307
|
# filter ports according to processed in pair
|
|
@@ -299,6 +335,24 @@ class MeshExecutor:
|
|
|
299
335
|
target_interface.add_addr(changes.addr, changes.vrf)
|
|
300
336
|
return target_interface.name
|
|
301
337
|
|
|
338
|
+
def _apply_indirect_interface_changes(
|
|
339
|
+
self, device: Device, neighbor: Device, ifname: Optional[str], changes: InterfaceChanges,
|
|
340
|
+
) -> Optional[str]:
|
|
341
|
+
if changes.lag is not None:
|
|
342
|
+
raise ValueError("LAG creation unsupported for indirect peers")
|
|
343
|
+
elif changes.subif is not None:
|
|
344
|
+
target_interface = device.add_subif(ifname, changes.subif)
|
|
345
|
+
elif changes.svi is not None:
|
|
346
|
+
target_interface = device.add_svi(changes.svi)
|
|
347
|
+
elif not ifname:
|
|
348
|
+
return None
|
|
349
|
+
else:
|
|
350
|
+
target_interface = device.find_interface(ifname)
|
|
351
|
+
if not target_interface:
|
|
352
|
+
raise ValueError(f"Interface {ifname} not found for device {device.fqdn}")
|
|
353
|
+
target_interface.add_addr(changes.addr, changes.vrf)
|
|
354
|
+
return target_interface.name
|
|
355
|
+
|
|
302
356
|
def _apply_virtual_interface_changes(self, device: Device, local: VirtualLocalDTO) -> str:
|
|
303
357
|
return device.add_svi(local.svi).name # we check if SVI configured in execute method
|
|
304
358
|
|
|
@@ -308,8 +362,9 @@ class MeshExecutor:
|
|
|
308
362
|
global_options = self._to_bgp_global(self._execute_globals(device))
|
|
309
363
|
|
|
310
364
|
peers = []
|
|
365
|
+
target_interface: Optional[str]
|
|
311
366
|
for direct_pair in self._execute_direct(device):
|
|
312
|
-
target_interface = self.
|
|
367
|
+
target_interface = self._apply_direct_interface_changes(
|
|
313
368
|
device,
|
|
314
369
|
direct_pair.device,
|
|
315
370
|
direct_pair.ports,
|
|
@@ -325,7 +380,13 @@ class MeshExecutor:
|
|
|
325
380
|
peers.append(self._virtual_to_bgp_peer(virtual_pair, target_interface))
|
|
326
381
|
|
|
327
382
|
for connected_pair in self._execute_indirect(device, all_fqdns):
|
|
328
|
-
|
|
383
|
+
target_interface = self._apply_indirect_interface_changes(
|
|
384
|
+
device,
|
|
385
|
+
connected_pair.device,
|
|
386
|
+
getattr(connected_pair.local, "ifname", None),
|
|
387
|
+
to_interface_changes(connected_pair.local),
|
|
388
|
+
)
|
|
389
|
+
peers.append(self._to_bgp_peer(connected_pair, target_interface))
|
|
329
390
|
|
|
330
391
|
return BgpConfig(
|
|
331
392
|
global_options=global_options,
|
annet/mesh/peer_models.py
CHANGED
annet/mesh/registry.py
CHANGED
|
@@ -21,6 +21,9 @@ class DirectPeer(DirectPeerDTO):
|
|
|
21
21
|
self.ports = ports
|
|
22
22
|
self.all_connected_ports = all_connected_ports
|
|
23
23
|
|
|
24
|
+
def is_empty(self):
|
|
25
|
+
return self.__dict__.keys() == {"match", "device", "ports", "all_connected_ports"}
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class IndirectPeer(IndirectPeerDTO):
|
|
26
29
|
match: MatchedArgs
|
|
@@ -31,6 +34,9 @@ class IndirectPeer(IndirectPeerDTO):
|
|
|
31
34
|
self.match = match
|
|
32
35
|
self.device = device
|
|
33
36
|
|
|
37
|
+
def is_empty(self):
|
|
38
|
+
return self.__dict__.keys() == {"match", "device"}
|
|
39
|
+
|
|
34
40
|
|
|
35
41
|
class VirtualLocal(VirtualLocalDTO):
|
|
36
42
|
match: MatchedArgs
|
|
@@ -41,10 +47,16 @@ class VirtualLocal(VirtualLocalDTO):
|
|
|
41
47
|
self.match = match
|
|
42
48
|
self.device = device
|
|
43
49
|
|
|
50
|
+
def is_empty(self):
|
|
51
|
+
return self.__dict__.keys() == {"match", "device"}
|
|
52
|
+
|
|
44
53
|
|
|
45
54
|
class VirtualPeer(VirtualPeerDTO):
|
|
46
55
|
num: int
|
|
47
56
|
|
|
57
|
+
def is_empty(self):
|
|
58
|
+
return self.__dict__.keys() == {"num"}
|
|
59
|
+
|
|
48
60
|
|
|
49
61
|
class GlobalOptions(GlobalOptionsDTO):
|
|
50
62
|
match: MatchedArgs
|
|
@@ -55,6 +67,9 @@ class GlobalOptions(GlobalOptionsDTO):
|
|
|
55
67
|
self.match = match
|
|
56
68
|
self.device = device
|
|
57
69
|
|
|
70
|
+
def is_empty(self):
|
|
71
|
+
return self.__dict__.keys() == {"match", "device"}
|
|
72
|
+
|
|
58
73
|
|
|
59
74
|
GlobalHandler = Callable[[GlobalOptions], None]
|
|
60
75
|
|
|
@@ -164,7 +164,7 @@ class CommunityListGenerator(PartialGenerator, ABC):
|
|
|
164
164
|
yield self._huawei_community_filter(10, community_list, " ".join(members))
|
|
165
165
|
elif community_list.logic == CommunityLogic.OR:
|
|
166
166
|
for i, member in enumerate(members):
|
|
167
|
-
member_id = (i + 1) * 10
|
|
167
|
+
member_id = (i + 1) * 10
|
|
168
168
|
yield self._huawei_community_filter(member_id, community_list, member)
|
|
169
169
|
else:
|
|
170
170
|
raise NotImplementedError(f"Community logic {community_list.logic} is not implemented for huawei")
|
|
@@ -11,10 +11,10 @@ from annet.rpl.statement_builder import NextHopActionValue, AsPathActionValue, C
|
|
|
11
11
|
from .aspath import get_used_as_path_filters
|
|
12
12
|
from .community import get_used_united_community_lists
|
|
13
13
|
from .entities import (
|
|
14
|
-
AsPathFilter, IpPrefixList,
|
|
15
|
-
mangle_united_community_list_name,
|
|
14
|
+
AsPathFilter, IpPrefixList, CommunityList, CommunityLogic, CommunityType,
|
|
15
|
+
mangle_united_community_list_name, PrefixListNameGenerator,
|
|
16
16
|
)
|
|
17
|
-
from .prefix_lists import get_used_prefix_lists
|
|
17
|
+
from .prefix_lists import get_used_prefix_lists, new_prefix_list_name_generator
|
|
18
18
|
|
|
19
19
|
FRR_RESULT_MAP = {
|
|
20
20
|
ResultType.ALLOW: "permit",
|
|
@@ -51,6 +51,12 @@ class CumulusPolicyGenerator(ABC):
|
|
|
51
51
|
def get_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
|
|
52
52
|
raise NotImplementedError()
|
|
53
53
|
|
|
54
|
+
def get_used_prefix_lists(self, device: Any, name_generator: PrefixListNameGenerator) -> Sequence[IpPrefixList]:
|
|
55
|
+
return get_used_prefix_lists(
|
|
56
|
+
prefix_lists=self.get_prefix_lists(device),
|
|
57
|
+
name_generator=name_generator,
|
|
58
|
+
)
|
|
59
|
+
|
|
54
60
|
@abstractmethod
|
|
55
61
|
def get_community_lists(self, device: Any) -> list[CommunityList]:
|
|
56
62
|
raise NotImplementedError()
|
|
@@ -61,11 +67,13 @@ class CumulusPolicyGenerator(ABC):
|
|
|
61
67
|
|
|
62
68
|
def generate_cumulus_rpl(self, device: Any) -> Iterator[Sequence[str]]:
|
|
63
69
|
policies = self.get_policies(device)
|
|
70
|
+
prefix_list_name_generator = new_prefix_list_name_generator(policies)
|
|
71
|
+
|
|
64
72
|
communities = {c.name: c for c in self.get_community_lists(device)}
|
|
65
73
|
yield from self._cumulus_as_path_filters(device, policies)
|
|
66
74
|
yield from self._cumulus_communities(device, communities, policies)
|
|
67
|
-
yield from self._cumulus_prefix_lists(device, policies)
|
|
68
|
-
yield from self._cumulus_policy_config(device, communities, policies)
|
|
75
|
+
yield from self._cumulus_prefix_lists(device, policies, prefix_list_name_generator)
|
|
76
|
+
yield from self._cumulus_policy_config(device, communities, policies, prefix_list_name_generator)
|
|
69
77
|
|
|
70
78
|
def _cumulus_as_path_filters(
|
|
71
79
|
self,
|
|
@@ -100,11 +108,12 @@ class CumulusPolicyGenerator(ABC):
|
|
|
100
108
|
("le", str(match.less_equal)) if match.less_equal is not None else ()
|
|
101
109
|
)
|
|
102
110
|
|
|
103
|
-
def _cumulus_prefix_lists(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
def _cumulus_prefix_lists(
|
|
112
|
+
self, device: Any,
|
|
113
|
+
policies: list[RoutingPolicy],
|
|
114
|
+
prefix_list_name_generator: PrefixListNameGenerator,
|
|
115
|
+
) -> Iterable[Sequence[str]]:
|
|
116
|
+
plists = {p.name: p for p in self.get_used_prefix_lists(device, prefix_list_name_generator)}
|
|
108
117
|
if not plists.values():
|
|
109
118
|
return
|
|
110
119
|
|
|
@@ -114,7 +123,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
114
123
|
cond: SingleCondition[PrefixMatchValue]
|
|
115
124
|
for cond in statement.match.find_all(MatchField.ip_prefix):
|
|
116
125
|
for name in cond.value.names:
|
|
117
|
-
mangled_name =
|
|
126
|
+
mangled_name = prefix_list_name_generator.get_prefix_name(
|
|
118
127
|
name=name,
|
|
119
128
|
greater_equal=cond.value.greater_equal,
|
|
120
129
|
less_equal=cond.value.less_equal,
|
|
@@ -125,7 +134,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
125
134
|
precessed_names.add(mangled_name)
|
|
126
135
|
for cond in statement.match.find_all(MatchField.ipv6_prefix):
|
|
127
136
|
for name in cond.value.names:
|
|
128
|
-
mangled_name =
|
|
137
|
+
mangled_name = prefix_list_name_generator.get_prefix_name(
|
|
129
138
|
name=name,
|
|
130
139
|
greater_equal=cond.value.greater_equal,
|
|
131
140
|
less_equal=cond.value.less_equal,
|
|
@@ -217,6 +226,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
217
226
|
self,
|
|
218
227
|
device: Any,
|
|
219
228
|
condition: SingleCondition[Any],
|
|
229
|
+
prefix_list_name_generator: PrefixListNameGenerator,
|
|
220
230
|
) -> Iterator[Sequence[str]]:
|
|
221
231
|
if condition.field == MatchField.community:
|
|
222
232
|
for comm_name in self._get_match_community_names(condition):
|
|
@@ -236,7 +246,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
236
246
|
return
|
|
237
247
|
if condition.field == MatchField.ip_prefix:
|
|
238
248
|
for name in condition.value.names:
|
|
239
|
-
mangled_name =
|
|
249
|
+
mangled_name = prefix_list_name_generator.get_prefix_name(
|
|
240
250
|
name=name,
|
|
241
251
|
greater_equal=condition.value.greater_equal,
|
|
242
252
|
less_equal=condition.value.less_equal,
|
|
@@ -245,7 +255,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
245
255
|
return
|
|
246
256
|
if condition.field == MatchField.ipv6_prefix:
|
|
247
257
|
for name in condition.value.names:
|
|
248
|
-
mangled_name =
|
|
258
|
+
mangled_name = prefix_list_name_generator.get_prefix_name(
|
|
249
259
|
name=name,
|
|
250
260
|
greater_equal=condition.value.greater_equal,
|
|
251
261
|
less_equal=condition.value.less_equal,
|
|
@@ -421,11 +431,12 @@ class CumulusPolicyGenerator(ABC):
|
|
|
421
431
|
device: Any,
|
|
422
432
|
policy: RoutingPolicy,
|
|
423
433
|
statement: RoutingPolicyStatement,
|
|
434
|
+
prefix_list_name_generator: PrefixListNameGenerator,
|
|
424
435
|
) -> Iterable[Sequence[str]]:
|
|
425
436
|
yield "route-map", policy.name, FRR_RESULT_MAP[statement.result], str(statement.number)
|
|
426
437
|
|
|
427
438
|
for condition in statement.match:
|
|
428
|
-
for row in self._cumulus_policy_match(device, condition):
|
|
439
|
+
for row in self._cumulus_policy_match(device, condition, prefix_list_name_generator):
|
|
429
440
|
yield FRR_INDENT, *row
|
|
430
441
|
for action in statement.then:
|
|
431
442
|
for row in self._cumulus_policy_then(communities, device, action):
|
|
@@ -439,6 +450,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
439
450
|
device: Any,
|
|
440
451
|
communities: dict[str, CommunityList],
|
|
441
452
|
policies: list[RoutingPolicy],
|
|
453
|
+
prefix_list_name_generator: PrefixListNameGenerator,
|
|
442
454
|
) -> Iterable[Sequence[str]]:
|
|
443
455
|
""" Route maps configuration """
|
|
444
456
|
|
|
@@ -454,5 +466,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
454
466
|
raise RuntimeError(
|
|
455
467
|
f"Multiple statements have same number {statement.number} for policy `{policy.name}`: "
|
|
456
468
|
f"`{statement.name}` and `{applied_stmts[statement.number]}`")
|
|
457
|
-
yield from self._cumulus_policy_statement(
|
|
469
|
+
yield from self._cumulus_policy_statement(
|
|
470
|
+
communities, device, policy, statement, prefix_list_name_generator,
|
|
471
|
+
)
|
|
458
472
|
applied_stmts[statement.number] = statement.name
|
annet/rpl_generators/entities.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
1
2
|
from collections.abc import Sequence
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from enum import Enum
|
|
@@ -56,15 +57,27 @@ def mangle_united_community_list_name(values: Sequence[str]) -> str:
|
|
|
56
57
|
return "_OR_".join(values)
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
class PrefixListNameGenerator:
|
|
61
|
+
def __init__(self):
|
|
62
|
+
self._prefix_lists = defaultdict(set)
|
|
63
|
+
|
|
64
|
+
def add_prefix(self, name: str, greater_equal: Optional[int], less_equal: Optional[int]) -> None:
|
|
65
|
+
self._prefix_lists[name].add((greater_equal, less_equal))
|
|
66
|
+
|
|
67
|
+
def is_used(self, name: str):
|
|
68
|
+
return name in self._prefix_lists
|
|
69
|
+
|
|
70
|
+
def get_prefix_name(self, name: str, greater_equal: Optional[int], less_equal: Optional[int]) -> str:
|
|
71
|
+
if len(self._prefix_lists[name]) == 1:
|
|
72
|
+
return name
|
|
73
|
+
if greater_equal is less_equal is None:
|
|
74
|
+
return name
|
|
75
|
+
if greater_equal is None:
|
|
76
|
+
ge_str = "unset"
|
|
77
|
+
else:
|
|
78
|
+
ge_str = str(greater_equal)
|
|
79
|
+
if less_equal is None:
|
|
80
|
+
le_str = "unset"
|
|
81
|
+
else:
|
|
82
|
+
le_str = str(less_equal)
|
|
83
|
+
return f"{name}_{ge_str}_{le_str}"
|
annet/rpl_generators/policy.py
CHANGED
|
@@ -6,13 +6,14 @@ from annet.generators import PartialGenerator
|
|
|
6
6
|
from annet.rpl import (
|
|
7
7
|
CommunityActionValue,
|
|
8
8
|
ResultType, RoutingPolicyStatement, RoutingPolicy, ConditionOperator, SingleCondition, SingleAction, ActionType,
|
|
9
|
-
|
|
9
|
+
MatchField,
|
|
10
10
|
)
|
|
11
11
|
from annet.rpl.statement_builder import AsPathActionValue, NextHopActionValue, ThenField
|
|
12
12
|
from annet.rpl_generators.entities import (
|
|
13
13
|
arista_well_known_community,
|
|
14
|
-
CommunityList, RDFilter,
|
|
14
|
+
CommunityList, RDFilter, PrefixListNameGenerator, CommunityLogic, mangle_united_community_list_name,
|
|
15
15
|
)
|
|
16
|
+
from annet.rpl_generators.prefix_lists import new_prefix_list_name_generator
|
|
16
17
|
|
|
17
18
|
HUAWEI_MATCH_COMMAND_MAP: dict[str, str] = {
|
|
18
19
|
MatchField.as_path_filter: "as-path-filter {option_value}",
|
|
@@ -86,6 +87,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
86
87
|
condition: SingleCondition[Any],
|
|
87
88
|
communities: dict[str, CommunityList],
|
|
88
89
|
rd_filters: dict[str, RDFilter],
|
|
90
|
+
prefix_name_generator: PrefixListNameGenerator,
|
|
89
91
|
) -> Iterator[Sequence[str]]:
|
|
90
92
|
if condition.field == MatchField.community:
|
|
91
93
|
if condition.operator is ConditionOperator.HAS:
|
|
@@ -134,7 +136,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
134
136
|
return
|
|
135
137
|
if condition.field == MatchField.ip_prefix:
|
|
136
138
|
for name in condition.value.names:
|
|
137
|
-
mangled_name =
|
|
139
|
+
mangled_name = prefix_name_generator.get_prefix_name(
|
|
138
140
|
name=name,
|
|
139
141
|
greater_equal=condition.value.greater_equal,
|
|
140
142
|
less_equal=condition.value.less_equal,
|
|
@@ -143,7 +145,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
143
145
|
return
|
|
144
146
|
if condition.field == MatchField.ipv6_prefix:
|
|
145
147
|
for name in condition.value.names:
|
|
146
|
-
mangled_name =
|
|
148
|
+
mangled_name = prefix_name_generator.get_prefix_name(
|
|
147
149
|
name=name,
|
|
148
150
|
greater_equal=condition.value.greater_equal,
|
|
149
151
|
less_equal=condition.value.less_equal,
|
|
@@ -180,7 +182,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
180
182
|
action: SingleAction[CommunityActionValue],
|
|
181
183
|
) -> Iterator[Sequence[str]]:
|
|
182
184
|
if action.value.replaced is not None:
|
|
183
|
-
if action.value.added or action.value.
|
|
185
|
+
if action.value.added or action.value.removed:
|
|
184
186
|
raise NotImplementedError(
|
|
185
187
|
"Cannot set community together with add/remove on huawei",
|
|
186
188
|
)
|
|
@@ -202,7 +204,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
202
204
|
action: SingleAction[CommunityActionValue],
|
|
203
205
|
) -> Iterator[Sequence[str]]:
|
|
204
206
|
if action.value.replaced is not None:
|
|
205
|
-
if action.value.added or action.value.
|
|
207
|
+
if action.value.added or action.value.removed:
|
|
206
208
|
raise NotImplementedError(
|
|
207
209
|
"Cannot set large-community together with add/remove on huawei",
|
|
208
210
|
)
|
|
@@ -334,6 +336,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
334
336
|
device: Any,
|
|
335
337
|
policy: RoutingPolicy,
|
|
336
338
|
statement: RoutingPolicyStatement,
|
|
339
|
+
prefix_name_generator: PrefixListNameGenerator,
|
|
337
340
|
) -> Iterator[Sequence[str]]:
|
|
338
341
|
if statement.number is None:
|
|
339
342
|
raise RuntimeError(f"Statement number should not be empty on Huawei (found for policy: {policy.name})")
|
|
@@ -343,19 +346,21 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
343
346
|
"node", statement.number
|
|
344
347
|
):
|
|
345
348
|
for condition in statement.match:
|
|
346
|
-
yield from self._huawei_match(device, condition, communities, rd_filters)
|
|
349
|
+
yield from self._huawei_match(device, condition, communities, rd_filters, prefix_name_generator)
|
|
347
350
|
for action in statement.then:
|
|
348
351
|
yield from self._huawei_then(communities, device, action)
|
|
349
352
|
if statement.result is ResultType.NEXT:
|
|
350
353
|
yield "goto next-node"
|
|
351
354
|
|
|
352
355
|
def run_huawei(self, device):
|
|
356
|
+
policies = self.get_policies(device)
|
|
353
357
|
communities = {c.name: c for c in self.get_community_lists(device)}
|
|
354
358
|
rd_filters = {f.name: f for f in self.get_rd_filters(device)}
|
|
359
|
+
prefix_name_generator = new_prefix_list_name_generator(policies)
|
|
355
360
|
|
|
356
361
|
for policy in self.get_policies(device):
|
|
357
362
|
for statement in policy.statements:
|
|
358
|
-
yield from self._huawei_statement(communities, rd_filters, device, policy, statement)
|
|
363
|
+
yield from self._huawei_statement(communities, rd_filters, device, policy, statement, prefix_name_generator)
|
|
359
364
|
|
|
360
365
|
# arista
|
|
361
366
|
def acl_arista(self, device):
|
|
@@ -378,6 +383,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
378
383
|
condition: SingleCondition[Any],
|
|
379
384
|
communities: dict[str, CommunityList],
|
|
380
385
|
rd_filters: dict[str, RDFilter],
|
|
386
|
+
prefix_name_generator: PrefixListNameGenerator,
|
|
381
387
|
) -> Iterator[Sequence[str]]:
|
|
382
388
|
if condition.field == MatchField.community:
|
|
383
389
|
if condition.operator is ConditionOperator.HAS_ANY:
|
|
@@ -430,7 +436,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
430
436
|
return
|
|
431
437
|
if condition.field == MatchField.ip_prefix:
|
|
432
438
|
for name in condition.value.names:
|
|
433
|
-
mangled_name =
|
|
439
|
+
mangled_name = prefix_name_generator.get_prefix_name(
|
|
434
440
|
name=name,
|
|
435
441
|
greater_equal=condition.value.greater_equal,
|
|
436
442
|
less_equal=condition.value.less_equal,
|
|
@@ -439,7 +445,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
439
445
|
return
|
|
440
446
|
if condition.field == MatchField.ipv6_prefix:
|
|
441
447
|
for name in condition.value.names:
|
|
442
|
-
mangled_name =
|
|
448
|
+
mangled_name = prefix_name_generator.get_prefix_name(
|
|
443
449
|
name=name,
|
|
444
450
|
greater_equal=condition.value.greater_equal,
|
|
445
451
|
less_equal=condition.value.less_equal,
|
|
@@ -477,7 +483,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
477
483
|
action: SingleAction[CommunityActionValue],
|
|
478
484
|
) -> Iterator[Sequence[str]]:
|
|
479
485
|
if action.value.replaced is not None:
|
|
480
|
-
if action.value.added or action.value.
|
|
486
|
+
if action.value.added or action.value.removed:
|
|
481
487
|
raise NotImplementedError(
|
|
482
488
|
"Cannot set community together with add/remove on arista",
|
|
483
489
|
)
|
|
@@ -500,7 +506,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
500
506
|
action: SingleAction[CommunityActionValue],
|
|
501
507
|
) -> Iterator[Sequence[str]]:
|
|
502
508
|
if action.value.replaced is not None:
|
|
503
|
-
if action.value.added or action.value.
|
|
509
|
+
if action.value.added or action.value.removed:
|
|
504
510
|
raise NotImplementedError(
|
|
505
511
|
"Cannot set large-community together with add/remove on arista",
|
|
506
512
|
)
|
|
@@ -653,6 +659,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
653
659
|
device: Any,
|
|
654
660
|
policy: RoutingPolicy,
|
|
655
661
|
statement: RoutingPolicyStatement,
|
|
662
|
+
prefix_name_generator: PrefixListNameGenerator,
|
|
656
663
|
) -> Iterator[Sequence[str]]:
|
|
657
664
|
with self.block(
|
|
658
665
|
"route-map",
|
|
@@ -661,16 +668,20 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
661
668
|
statement.number,
|
|
662
669
|
):
|
|
663
670
|
for condition in statement.match:
|
|
664
|
-
yield from self._arista_match(device, condition, communities, rd_filters)
|
|
671
|
+
yield from self._arista_match(device, condition, communities, rd_filters, prefix_name_generator)
|
|
665
672
|
for action in statement.then:
|
|
666
673
|
yield from self._arista_then(communities, device, action)
|
|
667
674
|
if statement.result is ResultType.NEXT:
|
|
668
675
|
yield "continue"
|
|
669
676
|
|
|
670
677
|
def run_arista(self, device):
|
|
678
|
+
policies = self.get_policies(device)
|
|
679
|
+
prefix_name_generator = new_prefix_list_name_generator(policies)
|
|
671
680
|
communities = {c.name: c for c in self.get_community_lists(device)}
|
|
672
681
|
rd_filters = {f.name: f for f in self.get_rd_filters(device)}
|
|
673
682
|
|
|
674
|
-
for policy in
|
|
683
|
+
for policy in policies:
|
|
675
684
|
for statement in policy.statements:
|
|
676
|
-
yield from self._arista_statement(
|
|
685
|
+
yield from self._arista_statement(
|
|
686
|
+
communities, rd_filters, device, policy, statement, prefix_name_generator,
|
|
687
|
+
)
|
|
@@ -4,20 +4,28 @@ from ipaddress import ip_interface
|
|
|
4
4
|
from typing import Any, Literal
|
|
5
5
|
|
|
6
6
|
from annet.generators import PartialGenerator
|
|
7
|
-
from annet.rpl import
|
|
8
|
-
from .entities import IpPrefixList,
|
|
7
|
+
from annet.rpl import PrefixMatchValue, MatchField, SingleCondition, RoutingPolicy
|
|
8
|
+
from .entities import IpPrefixList, PrefixListNameGenerator
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def get_used_prefix_lists(
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
def get_used_prefix_lists(
|
|
12
|
+
prefix_lists: Sequence[IpPrefixList], name_generator: PrefixListNameGenerator,
|
|
13
|
+
) -> list[IpPrefixList]:
|
|
14
|
+
return [c for c in prefix_lists if name_generator.is_used(c.name)]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def new_prefix_list_name_generator(policies: list[RoutingPolicy]) -> PrefixListNameGenerator:
|
|
18
|
+
name_gen = PrefixListNameGenerator()
|
|
14
19
|
for policy in policies:
|
|
15
20
|
for statement in policy.statements:
|
|
21
|
+
condition: SingleCondition[PrefixMatchValue]
|
|
16
22
|
for condition in statement.match.find_all(MatchField.ipv6_prefix):
|
|
17
|
-
|
|
23
|
+
for name in condition.value.names:
|
|
24
|
+
name_gen.add_prefix(name, condition.value.greater_equal, condition.value.less_equal)
|
|
18
25
|
for condition in statement.match.find_all(MatchField.ip_prefix):
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
for name in condition.value.names:
|
|
27
|
+
name_gen.add_prefix(name, condition.value.greater_equal, condition.value.less_equal)
|
|
28
|
+
return name_gen
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
@@ -31,10 +39,10 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
|
31
39
|
def get_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
|
|
32
40
|
raise NotImplementedError()
|
|
33
41
|
|
|
34
|
-
def get_used_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
|
|
42
|
+
def get_used_prefix_lists(self, device: Any, name_generator: PrefixListNameGenerator) -> Sequence[IpPrefixList]:
|
|
35
43
|
return get_used_prefix_lists(
|
|
36
44
|
prefix_lists=self.get_prefix_lists(device),
|
|
37
|
-
|
|
45
|
+
name_generator=name_generator,
|
|
38
46
|
)
|
|
39
47
|
|
|
40
48
|
# huawei
|
|
@@ -68,15 +76,16 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
|
68
76
|
)
|
|
69
77
|
|
|
70
78
|
def run_huawei(self, device: Any):
|
|
71
|
-
plists = {p.name: p for p in self.get_used_prefix_lists(device)}
|
|
72
79
|
policies = self.get_policies(device)
|
|
80
|
+
name_generator = new_prefix_list_name_generator(policies)
|
|
81
|
+
plists = {p.name: p for p in self.get_used_prefix_lists(device, name_generator)}
|
|
73
82
|
precessed_names = set()
|
|
74
83
|
for policy in policies:
|
|
75
84
|
for statement in policy.statements:
|
|
76
85
|
cond: SingleCondition[PrefixMatchValue]
|
|
77
86
|
for cond in statement.match.find_all(MatchField.ip_prefix):
|
|
78
87
|
for name in cond.value.names:
|
|
79
|
-
mangled_name =
|
|
88
|
+
mangled_name = name_generator.get_prefix_name(
|
|
80
89
|
name=name,
|
|
81
90
|
greater_equal=cond.value.greater_equal,
|
|
82
91
|
less_equal=cond.value.less_equal,
|
|
@@ -87,7 +96,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
|
87
96
|
precessed_names.add(mangled_name)
|
|
88
97
|
for cond in statement.match.find_all(MatchField.ipv6_prefix):
|
|
89
98
|
for name in cond.value.names:
|
|
90
|
-
mangled_name =
|
|
99
|
+
mangled_name = name_generator.get_prefix_name(
|
|
91
100
|
name=name,
|
|
92
101
|
greater_equal=cond.value.greater_equal,
|
|
93
102
|
less_equal=cond.value.less_equal,
|
|
@@ -128,15 +137,16 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
|
128
137
|
)
|
|
129
138
|
|
|
130
139
|
def run_arista(self, device: Any):
|
|
131
|
-
plists = {p.name: p for p in self.get_used_prefix_lists(device)}
|
|
132
140
|
policies = self.get_policies(device)
|
|
141
|
+
name_generator = new_prefix_list_name_generator(policies)
|
|
142
|
+
plists = {p.name: p for p in self.get_used_prefix_lists(device, name_generator)}
|
|
133
143
|
precessed_names = set()
|
|
134
144
|
for policy in policies:
|
|
135
145
|
for statement in policy.statements:
|
|
136
146
|
cond: SingleCondition[PrefixMatchValue]
|
|
137
147
|
for cond in statement.match.find_all(MatchField.ip_prefix):
|
|
138
148
|
for name in cond.value.names:
|
|
139
|
-
mangled_name =
|
|
149
|
+
mangled_name = name_generator.get_prefix_name(
|
|
140
150
|
name=name,
|
|
141
151
|
greater_equal=cond.value.greater_equal,
|
|
142
152
|
less_equal=cond.value.less_equal,
|
|
@@ -147,7 +157,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
|
|
|
147
157
|
precessed_names.add(mangled_name)
|
|
148
158
|
for cond in statement.match.find_all(MatchField.ipv6_prefix):
|
|
149
159
|
for name in cond.value.names:
|
|
150
|
-
mangled_name =
|
|
160
|
+
mangled_name = name_generator.get_prefix_name(
|
|
151
161
|
name=name,
|
|
152
162
|
greater_equal=cond.value.greater_equal,
|
|
153
163
|
less_equal=cond.value.less_equal,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
crypto key generate rsa %timeout=60
|
|
21
21
|
dialog: Do you really want to replace them? [yes/no]: ::: no
|
|
22
22
|
dialog: How many bits in the modulus [512]: ::: 2048
|
|
23
|
-
no username * privilege * secret
|
|
23
|
+
no username * privilege * secret * *
|
|
24
24
|
dialog: This operation will remove all username related configurations with same name.Do you want to continue? [confirm] ::: Y %send_nl=0
|
|
25
25
|
|
|
26
26
|
copy running-config startup-config
|
annet/storage.py
CHANGED
|
@@ -168,6 +168,10 @@ class Device(Protocol):
|
|
|
168
168
|
"""Add sub interface or return existing one"""
|
|
169
169
|
raise NotImplementedError
|
|
170
170
|
|
|
171
|
+
@abc.abstractmethod
|
|
172
|
+
def find_interface(self, name: str) -> Optional[Interface]:
|
|
173
|
+
raise NotImplementedError
|
|
174
|
+
|
|
171
175
|
|
|
172
176
|
def get_storage() -> tuple[StorageProvider, Dict[str, Any]]:
|
|
173
177
|
connectors = storage_connector.get_all()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: annet
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.31
|
|
4
4
|
Summary: annet
|
|
5
5
|
Home-page: https://github.com/annetutil/annet
|
|
6
6
|
License: MIT
|
|
@@ -24,3 +24,9 @@ Requires-Dist: adaptix==3.0.0b7
|
|
|
24
24
|
Requires-Dist: dataclass-rest==0.4
|
|
25
25
|
Provides-Extra: netbox
|
|
26
26
|
Requires-Dist: annetbox[sync]>=0.1.10; extra == "netbox"
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: license
|
|
29
|
+
Dynamic: provides-extra
|
|
30
|
+
Dynamic: requires-dist
|
|
31
|
+
Dynamic: requires-python
|
|
32
|
+
Dynamic: summary
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
annet/__init__.py,sha256=W8kkZ3Axu-6VJwgQ0cn4UeOVNy6jab0cqgHKLQny1D0,2141
|
|
2
2
|
annet/annet.py,sha256=TMdEuM7GJQ4TjRVmuK3bCTZN-21lxjQ9sXqEdILUuBk,725
|
|
3
3
|
annet/argparse.py,sha256=v1MfhjR0B8qahza0WinmXClpR8UiDFhmwDDWtNroJPA,12855
|
|
4
|
-
annet/bgp_models.py,sha256=
|
|
4
|
+
annet/bgp_models.py,sha256=5FnXMeIbZJIQRdUk6Jf2f3YlkQSLAgmBH8bq2o94MUM,9457
|
|
5
5
|
annet/cli.py,sha256=hDpjIr3w47lgQ_CvCQS1SXFDK-SJrf5slbT__5u6GIA,12342
|
|
6
6
|
annet/cli_args.py,sha256=KQlihxSl-Phhq1-9oJDdNSbIllEX55LlPfH6viEKOuw,13483
|
|
7
7
|
annet/connectors.py,sha256=-Lghz3PtWCBU8Ohrp0KKQcmm1AUZtN0EnOaZ6IQgCQI,5105
|
|
@@ -17,7 +17,7 @@ annet/output.py,sha256=FYMcWCc43-b51KsCiKnXPZHawhgWNoVtY9gRqw__Ce0,7473
|
|
|
17
17
|
annet/parallel.py,sha256=hLkzEht0KhzmzUWDdO4QFYQHzhxs3wPlTA8DxbB2ziw,17160
|
|
18
18
|
annet/patching.py,sha256=nILbY5oJajN0b1j3f0HEJm05H3HVThnWvB7vDVh7UQw,559
|
|
19
19
|
annet/reference.py,sha256=B8mH8VUMcecPnzULiTVb_kTQ7jQrCL7zp4pfIZQa5fk,4035
|
|
20
|
-
annet/storage.py,sha256=
|
|
20
|
+
annet/storage.py,sha256=44f2aHNFOsQMSJwFvGxyu26oRcs3QmS24TKivcfPlxU,4064
|
|
21
21
|
annet/tabparser.py,sha256=rRzfZSurR5P1eSdVWXMjABvV4PX83NRRQY0CzwabExw,1242
|
|
22
22
|
annet/text_term_format.py,sha256=CHb6viv45vmYl-SK1A1vyPHGhaEni6jVybBusaQnct8,2813
|
|
23
23
|
annet/tracing.py,sha256=ndpM-au1c88uBBpOuH_z52qWZL773edYozNyys_wA68,4044
|
|
@@ -27,13 +27,13 @@ annet/adapters/fetchers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
27
27
|
annet/adapters/fetchers/stub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
annet/adapters/fetchers/stub/fetcher.py,sha256=bJGNJcvjrxSa4d8gly6NkaRcxoK57zbZhzkW5vq278I,656
|
|
29
29
|
annet/adapters/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
annet/adapters/file/provider.py,sha256=
|
|
30
|
+
annet/adapters/file/provider.py,sha256=KyHuA5w9E4T3Lxeko8b5M7jRmu7yhTJVLqLkas4ue1A,6654
|
|
31
31
|
annet/adapters/netbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
32
|
annet/adapters/netbox/provider.py,sha256=3IrfZ6CfCxf-lTnJlIC2TQ8M_rDxOB_B7HGXZ92vAgA,1643
|
|
33
33
|
annet/adapters/netbox/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
annet/adapters/netbox/common/client.py,sha256=PaxHG4W9H8_uunIwMBNYkLq4eQJYoO6p6gY-ciQs7Nc,2563
|
|
35
|
-
annet/adapters/netbox/common/manufacturer.py,sha256=
|
|
36
|
-
annet/adapters/netbox/common/models.py,sha256=
|
|
35
|
+
annet/adapters/netbox/common/manufacturer.py,sha256=LAPT6OlV_ew96GhtwNrCpeiT0IGrg2_9__MMdZk431U,1733
|
|
36
|
+
annet/adapters/netbox/common/models.py,sha256=TnWEKI6_MxuZe24xfpkAijofkn-dOXwWe_ZOCWaYX58,7374
|
|
37
37
|
annet/adapters/netbox/common/query.py,sha256=ziUFM7cUEbEIf3k1szTll4aO-OCUa-2Ogxbebi7Tegs,646
|
|
38
38
|
annet/adapters/netbox/common/status_client.py,sha256=W4nTb2yvBlJ2UkWUmUhKQ2PaSQb1shjhHj5ebb4s2s4,591
|
|
39
39
|
annet/adapters/netbox/common/storage_opts.py,sha256=5tt6wxUUJTIzNbOVXMnYBwZedNAIqYlve3YWl6GdbZM,1197
|
|
@@ -55,7 +55,7 @@ annet/annlib/types.py,sha256=VHU0CBADfYmO0xzB_c5f-mcuU3dUumuJczQnqGlib9M,852
|
|
|
55
55
|
annet/annlib/netdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
56
|
annet/annlib/netdev/db.py,sha256=fI_u5aya4l61mbYSjj4JwlVfi3s7obt2jqERSuXGRUI,1634
|
|
57
57
|
annet/annlib/netdev/devdb/__init__.py,sha256=aKYjjLbJebdKBjnGDpVLQdSqrV2JL24spGm58tmMWVU,892
|
|
58
|
-
annet/annlib/netdev/devdb/data/devdb.json,sha256=
|
|
58
|
+
annet/annlib/netdev/devdb/data/devdb.json,sha256=Zt9pj6h2clMi0i26bGVsx0gqQl8vCSk6iLajIYWeKX4,6192
|
|
59
59
|
annet/annlib/netdev/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
60
|
annet/annlib/netdev/views/dump.py,sha256=rIlyvnA3uM8bB_7oq1nS2KDxTp6dQh2hz-FbNhYIpOU,4630
|
|
61
61
|
annet/annlib/netdev/views/hardware.py,sha256=3JCZLH7deIHhCguwPJTUX-WDvWjG_xt6BdSEZSO6zkQ,4226
|
|
@@ -67,7 +67,7 @@ annet/annlib/rbparser/platform.py,sha256=hnxznTfV9txXi1PkR1hZrprTrQJvlwgqXVL8vXk
|
|
|
67
67
|
annet/annlib/rbparser/syntax.py,sha256=iZ7Y-4QQBw4L3UtjEh54qisiRDhobl7HZxFNdP8mi54,3577
|
|
68
68
|
annet/annlib/rulebook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
69
|
annet/annlib/rulebook/common.py,sha256=bx_Iwui-JJeyctUPF1OsEll0Aa-IQZadBPQjaeuoWgw,16638
|
|
70
|
-
annet/api/__init__.py,sha256=
|
|
70
|
+
annet/api/__init__.py,sha256=WGpVMfIxVy9F_jH6nqHSQypEEcsSNa9yF2WFEwhUVwI,34156
|
|
71
71
|
annet/configs/context.yml,sha256=RVLrKLIHpCty7AGwOnmqf7Uu0iZQCn-AjYhophDJer8,259
|
|
72
72
|
annet/configs/logging.yaml,sha256=EUagfir99QqA73Scc3k7sfQccbU3E1SvEQdyhLFtCl4,997
|
|
73
73
|
annet/generators/__init__.py,sha256=rVHHDTPKHPZsml1eNEAj3o-8RweFTN8J7LX3tKMXdIY,16402
|
|
@@ -82,14 +82,14 @@ annet/generators/result.py,sha256=zMAvGOYQU803bGy6datZduHLgrEqK2Zba_Jcf9Qn9p0,49
|
|
|
82
82
|
annet/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
83
|
annet/generators/common/initial.py,sha256=qYBxXFhyOPy34cxc6hsIXseod-lYCmmbuNHpM0uteY0,1244
|
|
84
84
|
annet/mesh/__init__.py,sha256=lcgdnBIxc2MAN7Er1bcErEKPqrjWO4uIp_1FldMXTYg,557
|
|
85
|
-
annet/mesh/basemodel.py,sha256=
|
|
85
|
+
annet/mesh/basemodel.py,sha256=E6NTOneiMDwB1NCpjDRECoaeQ0f3n_fmTLnKTrSHTU4,4917
|
|
86
86
|
annet/mesh/device_models.py,sha256=YcL6_vGjnt67BTovN8Eq38U5wGcbJDhqiq8613WpYtQ,3381
|
|
87
|
-
annet/mesh/executor.py,sha256=
|
|
87
|
+
annet/mesh/executor.py,sha256=0RLsdtldozoFgDGFuhU5mdckuMwGg0y_DFIji1RfTQg,16969
|
|
88
88
|
annet/mesh/match_args.py,sha256=CR3kdIV9NGtyk9E2JbcOQ3TRuYEryTWP3m2yCo2VCWg,5751
|
|
89
89
|
annet/mesh/models_converter.py,sha256=3q2zs7K8S3pfYSUKKRdtl5CJGbeg4TtYxofAVs_MBsk,3085
|
|
90
|
-
annet/mesh/peer_models.py,sha256=
|
|
90
|
+
annet/mesh/peer_models.py,sha256=uD3h7HWl9_zNdBlQqZCMcjCnzjr8dTbSFqc8216JDu0,2735
|
|
91
91
|
annet/mesh/port_processor.py,sha256=RHiMS5W8qoDkTKiarQ748bcr8bNx4g_R4Y4vZg2k4TU,478
|
|
92
|
-
annet/mesh/registry.py,sha256=
|
|
92
|
+
annet/mesh/registry.py,sha256=BCRIOrINP0krcgvF59uFJqyhmSviQ03GSzWpxLVYlIg,9527
|
|
93
93
|
annet/rpl/__init__.py,sha256=0kcIktE3AmS0rlm9xzVDf53xk08OeZXgD-6ZLCt_KCs,731
|
|
94
94
|
annet/rpl/action.py,sha256=PY6W66j908RuqQ1_ioxayqVN-70rxDk5Z59EGHtxI98,1246
|
|
95
95
|
annet/rpl/condition.py,sha256=MJri4MbWtPkLHIsLMAtsIEF7e8IAS9dIImjmJs5vS5U,3418
|
|
@@ -100,12 +100,12 @@ annet/rpl/routemap.py,sha256=SIyk73OzPp2oH_XwrDv2xczuY2Zt1VsJmB0TT5r7F5g,2593
|
|
|
100
100
|
annet/rpl/statement_builder.py,sha256=sVGOYsCV0s_SFQUy2WtUyQqKy5H4MOfmRCJWGj-UOJ4,9403
|
|
101
101
|
annet/rpl_generators/__init__.py,sha256=ZLWs-flcpyIbdhxSDfNt-ORDrLe8ins25sWdXTWeUoA,748
|
|
102
102
|
annet/rpl_generators/aspath.py,sha256=kZakwPLfGGiXu9fC6I1z-pvy7Fe-4dy93_-lYcx39_4,2038
|
|
103
|
-
annet/rpl_generators/community.py,sha256=
|
|
104
|
-
annet/rpl_generators/cumulus_frr.py,sha256=
|
|
105
|
-
annet/rpl_generators/entities.py,sha256=
|
|
103
|
+
annet/rpl_generators/community.py,sha256=SWpaOvoQUNISuRm41-IvGPFmntvgFv9ee4zegsMyeo0,11496
|
|
104
|
+
annet/rpl_generators/cumulus_frr.py,sha256=D8JnnXTXYaIUayPwL_KORUCkpBV32-CHatlyKQSSJWw,20855
|
|
105
|
+
annet/rpl_generators/entities.py,sha256=DIpgAQ8Tslo2hq6iFBaYkJX12BFBiccN8GOaRVxR1Uk,1985
|
|
106
106
|
annet/rpl_generators/execute.py,sha256=wS6e6fwcPWywsHB0gBMqZ17eF0s4YOBgDgwPB_cr5Rw,431
|
|
107
|
-
annet/rpl_generators/policy.py,sha256=
|
|
108
|
-
annet/rpl_generators/prefix_lists.py,sha256=
|
|
107
|
+
annet/rpl_generators/policy.py,sha256=NeqB0reRN_KuY8LYkeGT3dRPe2HFDT9RfmVy5fcA3zw,32570
|
|
108
|
+
annet/rpl_generators/prefix_lists.py,sha256=2x2NO52npcB3VKcQ4tthsWFwirVpexLdLtWPA5sHzjg,7246
|
|
109
109
|
annet/rpl_generators/rd.py,sha256=YGXgx1D2D0-pixgspXJzA6NvW8lx3AmHMxIY2l5rraI,1457
|
|
110
110
|
annet/rulebook/__init__.py,sha256=oafL5HC8QHdkO9CH2q_fxohPMxOgjn-dNQa5kPjuqsA,3942
|
|
111
111
|
annet/rulebook/common.py,sha256=zK1s2c5lc5HQbIlMUQ4HARQudXSgOYiZ_Sxc2I_tHqg,721
|
|
@@ -143,7 +143,7 @@ annet/rulebook/texts/aruba.rul,sha256=zvGVpoYyJvMoL0fb1NQ8we_GCLZXno8nwWpZIOScLQ
|
|
|
143
143
|
annet/rulebook/texts/b4com.deploy,sha256=SVX8-yLHM90tJC4M-ekpGuGM1aQZW3euSGyg67l--R0,781
|
|
144
144
|
annet/rulebook/texts/b4com.order,sha256=G3aToAIHHzKzDCM3q7_lyr9wJvuVOXVbVvF3wm5PiTE,707
|
|
145
145
|
annet/rulebook/texts/b4com.rul,sha256=5mqyUg_oLRSny2iH6QdhfDWVu6kzgDURtlSATD7DFno,1056
|
|
146
|
-
annet/rulebook/texts/cisco.deploy,sha256=
|
|
146
|
+
annet/rulebook/texts/cisco.deploy,sha256=Hu0NkcGv3f1CWUrnbzI3eQOPXJxtH4NNOPRV68IrW4U,1226
|
|
147
147
|
annet/rulebook/texts/cisco.order,sha256=OvNHMNqkCc-DN2dEjLCTKv_7ZhiaHt4q2X4Y4Z8dvR4,1901
|
|
148
148
|
annet/rulebook/texts/cisco.rul,sha256=jgL5_xnSwd_H4E8cx4gcneSvJC5W1zz6_BWSb64iuxI,3017
|
|
149
149
|
annet/rulebook/texts/huawei.deploy,sha256=azEC6_jQRzwnTSrNgag0hHh6L7hezS_eMk6ZDZfWyXI,10444
|
|
@@ -175,10 +175,10 @@ annet_generators/rpl_example/generator.py,sha256=zndIGfV4ZlTxPgAGYs7bMQvTc_tYScO
|
|
|
175
175
|
annet_generators/rpl_example/items.py,sha256=Ez1RF5YhcXNCusBmeApIjRL3rBlMazNZd29Gpw1_IsA,766
|
|
176
176
|
annet_generators/rpl_example/mesh.py,sha256=z_WgfDZZ4xnyh3cSf75igyH09hGvtexEVwy1gCD_DzA,288
|
|
177
177
|
annet_generators/rpl_example/route_policy.py,sha256=z6nPb0VDeQtKD1NIg9sFvmUxBD5tVs2frfNIuKdM-5c,2318
|
|
178
|
-
annet-0.16.
|
|
179
|
-
annet-0.16.
|
|
180
|
-
annet-0.16.
|
|
181
|
-
annet-0.16.
|
|
182
|
-
annet-0.16.
|
|
183
|
-
annet-0.16.
|
|
184
|
-
annet-0.16.
|
|
178
|
+
annet-0.16.31.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
|
|
179
|
+
annet-0.16.31.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
|
|
180
|
+
annet-0.16.31.dist-info/METADATA,sha256=CpR6RlgdzRXD13wUkktgrcw2nuWeqI0dZNJ1dyNn7hE,854
|
|
181
|
+
annet-0.16.31.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
182
|
+
annet-0.16.31.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
|
|
183
|
+
annet-0.16.31.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
|
|
184
|
+
annet-0.16.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|