annet 0.16.29__tar.gz → 0.16.31__tar.gz
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-0.16.29/annet.egg-info → annet-0.16.31}/PKG-INFO +8 -2
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/file/provider.py +3 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/manufacturer.py +2 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/models.py +7 -1
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/devdb/data/devdb.json +1 -0
- {annet-0.16.29 → annet-0.16.31}/annet/api/__init__.py +24 -15
- {annet-0.16.29 → annet-0.16.31}/annet/bgp_models.py +17 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/basemodel.py +3 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/executor.py +71 -10
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/peer_models.py +4 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/registry.py +15 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/community.py +1 -1
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/cumulus_frr.py +30 -16
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/entities.py +25 -12
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/policy.py +26 -15
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/prefix_lists.py +26 -16
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/cisco.deploy +1 -1
- {annet-0.16.29 → annet-0.16.31}/annet/storage.py +4 -0
- {annet-0.16.29 → annet-0.16.31/annet.egg-info}/PKG-INFO +8 -2
- {annet-0.16.29 → annet-0.16.31}/AUTHORS +0 -0
- {annet-0.16.29 → annet-0.16.31}/LICENSE +0 -0
- {annet-0.16.29 → annet-0.16.31}/MANIFEST.in +0 -0
- {annet-0.16.29 → annet-0.16.31}/README.md +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/fetchers/stub/fetcher.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/file/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/query.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/common/storage_opts.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/provider.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/adapters/netbox/v37/storage.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annet.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/command.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/diff.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/errors.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/filter_acl.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/jsontools.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/lib.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/db.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/output.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/patching.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/tabparser.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/annlib/types.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/argparse.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/cli.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/cli_args.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/configs/context.yml +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/configs/logging.yaml +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/connectors.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/deploy.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/diff.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/executor.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/filtering.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/gen.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/base.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/common/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/common/initial.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/entire.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/exceptions.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/jsonfragment.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/partial.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/perf.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/ref.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/generators/result.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/hardware.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/implicit.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/lib.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/device_models.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/match_args.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/models_converter.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/mesh/port_processor.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/output.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/parallel.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/patching.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/reference.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/action.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/condition.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/match_builder.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/policy.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/result.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/routemap.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl/statement_builder.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/aspath.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/execute.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rpl_generators/rd.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/b4com/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/b4com/file.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/common.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/deploying.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/patching.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/routeros/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/routeros/file.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/b4com.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/pc.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/tabparser.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/text_term_format.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/tracing.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet/types.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet.egg-info/SOURCES.txt +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet.egg-info/requires.txt +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet.egg-info/top_level.txt +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/example/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/example/lldp.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/mesh_example/bgp.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/mesh_example/mesh_logic.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/rpl_example/__init__.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/rpl_example/generator.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/rpl_example/items.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/rpl_example/mesh.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/annet_generators/rpl_example/route_policy.py +0 -0
- {annet-0.16.29 → annet-0.16.31}/requirements.txt +0 -0
- {annet-0.16.29 → annet-0.16.31}/setup.cfg +0 -0
- {annet-0.16.29 → annet-0.16.31}/setup.py +0 -0
|
@@ -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
|
|
@@ -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",
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -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}"
|