annet 0.16.13__tar.gz → 0.16.15__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.13/annet.egg-info → annet-0.16.15}/PKG-INFO +1 -1
- {annet-0.16.13 → annet-0.16.15}/README.md +1 -1
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/models.py +7 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/storage_opts.py +7 -2
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/provider.py +3 -1
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/v37/storage.py +26 -19
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/devdb/data/devdb.json +2 -0
- {annet-0.16.13 → annet-0.16.15}/annet/bgp_models.py +15 -11
- {annet-0.16.13 → annet-0.16.15}/annet/implicit.py +16 -10
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/__init__.py +5 -2
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/device_models.py +34 -10
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/executor.py +72 -11
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/models_converter.py +19 -11
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/peer_models.py +28 -3
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/registry.py +61 -4
- {annet-0.16.13 → annet-0.16.15/annet.egg-info}/PKG-INFO +1 -1
- {annet-0.16.13 → annet-0.16.15}/annet_generators/mesh_example/bgp.py +1 -0
- {annet-0.16.13 → annet-0.16.15}/annet_generators/mesh_example/mesh_logic.py +14 -1
- {annet-0.16.13 → annet-0.16.15}/AUTHORS +0 -0
- {annet-0.16.13 → annet-0.16.15}/LICENSE +0 -0
- {annet-0.16.13 → annet-0.16.15}/MANIFEST.in +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/fetchers/stub/fetcher.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/file/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/file/provider.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/manufacturer.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/query.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annet.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/command.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/diff.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/errors.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/filter_acl.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/jsontools.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/lib.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/db.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/output.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/patching.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/tabparser.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/annlib/types.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/api/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/argparse.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/cli.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/cli_args.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/configs/context.yml +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/configs/logging.yaml +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/connectors.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/deploy.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/diff.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/executor.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/filtering.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/gen.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/base.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/common/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/common/initial.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/entire.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/exceptions.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/jsonfragment.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/partial.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/perf.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/ref.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/generators/result.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/hardware.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/lib.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/basemodel.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/mesh/match_args.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/output.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/parallel.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/patching.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/reference.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/b4com/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/b4com/file.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/common.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/deploying.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/patching.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/routeros/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/routeros/file.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/b4com.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/cisco.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/pc.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/storage.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/tabparser.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/text_term_format.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/tracing.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet/types.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet.egg-info/SOURCES.txt +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet.egg-info/requires.txt +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet.egg-info/top_level.txt +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet_generators/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet_generators/example/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet_generators/example/lldp.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-0.16.13 → annet-0.16.15}/requirements.txt +0 -0
- {annet-0.16.13 → annet-0.16.15}/setup.cfg +0 -0
- {annet-0.16.13 → annet-0.16.15}/setup.py +0 -0
|
@@ -139,6 +139,13 @@ class Interface(Entity):
|
|
|
139
139
|
family = IpFamily(value=6, label="IPv6")
|
|
140
140
|
else:
|
|
141
141
|
family = IpFamily(value=4, label="IPv4")
|
|
142
|
+
|
|
143
|
+
for existing_addr in self.ip_addresses:
|
|
144
|
+
if existing_addr.address == address_mask and (
|
|
145
|
+
(existing_addr.vrf is None and vrf is None) or
|
|
146
|
+
(existing_addr.vrf is not None and existing_addr.vrf.name == vrf)
|
|
147
|
+
):
|
|
148
|
+
return
|
|
142
149
|
self.ip_addresses.append(IpAddress(
|
|
143
150
|
id=0,
|
|
144
151
|
display=address_mask,
|
|
@@ -5,10 +5,11 @@ DEFAULT_URL = "http://localhost"
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class NetboxStorageOpts:
|
|
8
|
-
def __init__(self, url: str, token: str, insecure: bool = False):
|
|
8
|
+
def __init__(self, url: str, token: str, insecure: bool = False, exact_host_filter: bool = False):
|
|
9
9
|
self.url = url
|
|
10
10
|
self.token = token
|
|
11
11
|
self.insecure = insecure
|
|
12
|
+
self.exact_host_filter = exact_host_filter
|
|
12
13
|
|
|
13
14
|
@classmethod
|
|
14
15
|
def parse_params(cls, conf_params: Optional[dict[str, str]], cli_opts: Any):
|
|
@@ -19,4 +20,8 @@ class NetboxStorageOpts:
|
|
|
19
20
|
insecure = insecure_env in ("true", "1", "t")
|
|
20
21
|
else:
|
|
21
22
|
insecure = bool(conf_params.get("insecure") or False)
|
|
22
|
-
|
|
23
|
+
if exact_host_filter_env := os.getenv("NETBOX_EXACT_HOST_FILTER", "").lower():
|
|
24
|
+
exact_host_filter = exact_host_filter_env in ("true", "1", "t")
|
|
25
|
+
else:
|
|
26
|
+
exact_host_filter = bool(conf_params.get("exact_host_filter") or False)
|
|
27
|
+
return cls(url=url, token=token, insecure=insecure, exact_host_filter=exact_host_filter)
|
|
@@ -27,10 +27,12 @@ def storage_factory(opts: NetboxStorageOpts) -> Storage:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class NetboxProvider(StorageProvider, AdapterWithName, AdapterWithConfig):
|
|
30
|
-
def __init__(self, url: Optional[str] = None, token: Optional[str] = None, insecure: bool = False
|
|
30
|
+
def __init__(self, url: Optional[str] = None, token: Optional[str] = None, insecure: bool = False,
|
|
31
|
+
exact_host_filter: bool = False):
|
|
31
32
|
self.url = url
|
|
32
33
|
self.token = token
|
|
33
34
|
self.insecure = insecure
|
|
35
|
+
self.exact_host_filter = exact_host_filter
|
|
34
36
|
|
|
35
37
|
@classmethod
|
|
36
38
|
def with_config(cls, **kwargs: Dict[str, Any]) -> T:
|
|
@@ -87,16 +87,19 @@ def extend_ip_address(
|
|
|
87
87
|
|
|
88
88
|
class NetboxStorageV37(Storage):
|
|
89
89
|
def __init__(self, opts: Optional[NetboxStorageOpts] = None):
|
|
90
|
-
ctx: ssl.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
ctx: Optional[ssl.SSLContext] = None
|
|
91
|
+
url = ""
|
|
92
|
+
token = ""
|
|
93
|
+
self.exact_host_filter = False
|
|
94
|
+
if opts:
|
|
95
|
+
if opts.insecure:
|
|
96
|
+
ctx = ssl.create_default_context()
|
|
97
|
+
ctx.check_hostname = False
|
|
98
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
99
|
+
url = opts.url
|
|
100
|
+
token = opts.token
|
|
101
|
+
self.exact_host_filter = opts.exact_host_filter
|
|
102
|
+
self.netbox = NetboxV37(url=url, token=token, ssl_context=ctx)
|
|
100
103
|
self._all_fqdns: Optional[list[str]] = None
|
|
101
104
|
|
|
102
105
|
def __enter__(self):
|
|
@@ -147,7 +150,7 @@ class NetboxStorageV37(Storage):
|
|
|
147
150
|
|
|
148
151
|
interfaces = self._load_interfaces(list(device_ids))
|
|
149
152
|
neighbours = {x.id: x for x in self._load_neighbours(interfaces)}
|
|
150
|
-
neighbours_seen = defaultdict(set)
|
|
153
|
+
neighbours_seen: dict[str, set] = defaultdict(set)
|
|
151
154
|
|
|
152
155
|
for interface in interfaces:
|
|
153
156
|
device_ids[interface.device.id].interfaces.append(interface)
|
|
@@ -162,14 +165,18 @@ class NetboxStorageV37(Storage):
|
|
|
162
165
|
def _load_devices(self, query: NetboxQuery) -> List[api_models.Device]:
|
|
163
166
|
if not query.globs:
|
|
164
167
|
return []
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
if self.exact_host_filter:
|
|
169
|
+
devices = self.netbox.dcim_all_devices(name__ie=query.globs).results
|
|
170
|
+
else:
|
|
171
|
+
query = _hostname_dot_hack(query)
|
|
172
|
+
devices = [
|
|
173
|
+
device
|
|
174
|
+
for device in self.netbox.dcim_all_devices(
|
|
175
|
+
name__ic=query.globs,
|
|
176
|
+
).results
|
|
177
|
+
if _match_query(query, device)
|
|
178
|
+
]
|
|
179
|
+
return devices
|
|
173
180
|
|
|
174
181
|
def _extend_interfaces(self, interfaces: List[models.Interface]) -> List[models.Interface]:
|
|
175
182
|
extended_ifaces = {
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
"Huawei.CE.CE8800.CE8851": " CE8851",
|
|
55
55
|
"Huawei.CE.CE8800.CE8855": " CE8855",
|
|
56
56
|
"Huawei.CE.CE9800": " CE98\\d\\d",
|
|
57
|
+
"Huawei.CE.CE9800.CE9855": " CE9855",
|
|
58
|
+
"Huawei.CE.CE9800.CE9860": " CE9860",
|
|
57
59
|
"Huawei.Quidway": " (LS-)?S",
|
|
58
60
|
"Huawei.Quidway.S2x": "2\\d{3}",
|
|
59
61
|
"Huawei.Quidway.S2x.S2300": "23\\d\\d",
|
|
@@ -103,7 +103,7 @@ class PeerOptions:
|
|
|
103
103
|
af_rib_group: Optional[str] = None
|
|
104
104
|
af_loops: Optional[int] = None
|
|
105
105
|
hold_time: Optional[int] = None
|
|
106
|
-
listen_network: Optional[
|
|
106
|
+
listen_network: Optional[list[str]] = None
|
|
107
107
|
remove_private: Optional[bool] = None
|
|
108
108
|
as_override: Optional[bool] = None
|
|
109
109
|
aigp: Optional[bool] = None
|
|
@@ -158,7 +158,7 @@ class Redistribute:
|
|
|
158
158
|
@dataclass
|
|
159
159
|
class FamilyOptions:
|
|
160
160
|
family: Family
|
|
161
|
-
vrf_name: str
|
|
161
|
+
vrf_name: str = ""
|
|
162
162
|
multipath: int = 0
|
|
163
163
|
global_multipath: int = 0
|
|
164
164
|
aggregate: Aggregate = field(default_factory=Aggregate)
|
|
@@ -178,6 +178,7 @@ class FamilyOptions:
|
|
|
178
178
|
class PeerGroup:
|
|
179
179
|
name: str
|
|
180
180
|
remote_as: ASN = ASN(None)
|
|
181
|
+
families: set[Family] = field(default_factory=set)
|
|
181
182
|
internal_name: str = ""
|
|
182
183
|
description: str = ""
|
|
183
184
|
update_source: str = ""
|
|
@@ -213,7 +214,7 @@ class PeerGroup:
|
|
|
213
214
|
af_rib_group: Optional[str] = None
|
|
214
215
|
af_loops: int = 0
|
|
215
216
|
hold_time: int = 0
|
|
216
|
-
listen_network:
|
|
217
|
+
listen_network: list[str] = field(default_factory=list)
|
|
217
218
|
remove_private: bool = False
|
|
218
219
|
as_override: bool = False
|
|
219
220
|
aigp: bool = False
|
|
@@ -234,6 +235,12 @@ class PeerGroup:
|
|
|
234
235
|
@dataclass
|
|
235
236
|
class VrfOptions:
|
|
236
237
|
vrf_name: str
|
|
238
|
+
|
|
239
|
+
ipv4_unicast: FamilyOptions
|
|
240
|
+
ipv6_unicast: FamilyOptions
|
|
241
|
+
ipv4_labeled_unicast: FamilyOptions
|
|
242
|
+
ipv6_labeled_unicast: FamilyOptions
|
|
243
|
+
|
|
237
244
|
vrf_name_global: Optional[str] = None
|
|
238
245
|
import_policy: Optional[str] = None
|
|
239
246
|
export_policy: Optional[str] = None
|
|
@@ -244,23 +251,20 @@ class VrfOptions:
|
|
|
244
251
|
route_distinguisher: Optional[str] = None
|
|
245
252
|
static_label: Optional[int] = None # FIXME: str?
|
|
246
253
|
|
|
247
|
-
ipv4_unicast: Optional[FamilyOptions] = None
|
|
248
|
-
ipv6_unicast: Optional[FamilyOptions] = None
|
|
249
|
-
ipv4_labeled_unicast: Optional[FamilyOptions] = None
|
|
250
|
-
ipv6_labeled_unicast: Optional[FamilyOptions] = None
|
|
251
254
|
groups: list[PeerGroup] = field(default_factory=list)
|
|
252
255
|
|
|
253
256
|
|
|
254
257
|
@dataclass
|
|
255
258
|
class GlobalOptions:
|
|
259
|
+
ipv4_unicast: FamilyOptions
|
|
260
|
+
ipv6_unicast: FamilyOptions
|
|
261
|
+
ipv4_labeled_unicast: FamilyOptions
|
|
262
|
+
ipv6_labeled_unicast: FamilyOptions
|
|
263
|
+
|
|
256
264
|
local_as: ASN = ASN(None)
|
|
257
265
|
loops: int = 0
|
|
258
266
|
multipath: int = 0
|
|
259
267
|
router_id: str = ""
|
|
260
268
|
vrf: dict[str, VrfOptions] = field(default_factory=dict)
|
|
261
269
|
|
|
262
|
-
ipv4_unicast: Optional[FamilyOptions] = None
|
|
263
|
-
ipv6_unicast: Optional[FamilyOptions] = None
|
|
264
|
-
ipv4_labeled_unicast: Optional[FamilyOptions] = None
|
|
265
|
-
ipv6_labeled_unicast: Optional[FamilyOptions] = None
|
|
266
270
|
groups: list[PeerGroup] = field(default_factory=list)
|
|
@@ -71,14 +71,14 @@ def _implicit_tree(device):
|
|
|
71
71
|
netconf
|
|
72
72
|
"""
|
|
73
73
|
elif device.hw.Arista:
|
|
74
|
-
#
|
|
74
|
+
# This part of configuration will not be visible in configuration
|
|
75
75
|
text = r"""
|
|
76
76
|
ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface destination-ip source-port flow-label
|
|
77
77
|
ip load-sharing trident fields ip source-ip source-port destination-ip destination-port ingress-interface
|
|
78
78
|
"""
|
|
79
79
|
elif device.hw.Nexus:
|
|
80
80
|
text = r"""
|
|
81
|
-
#
|
|
81
|
+
# This part of configuration will not be visible in configuration if enabled
|
|
82
82
|
snmp-server enable traps link linkDown
|
|
83
83
|
snmp-server enable traps link linkUp
|
|
84
84
|
"""
|
|
@@ -107,25 +107,24 @@ def _implicit_tree(device):
|
|
|
107
107
|
"""
|
|
108
108
|
|
|
109
109
|
elif device.hw.Nexus.N3x:
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
# в данный момент поведение проверенно для Cisco Nexus 3132Q 6.0(2)U6(7)
|
|
110
|
+
# Cisco Nexus has some specific related to "shutdown" command
|
|
111
|
+
# Behavior is cheked on Cisco Nexus 3132Q 6.0(2)U6(7)
|
|
113
112
|
text += r"""
|
|
114
113
|
# SVI
|
|
115
114
|
!interface Vlan*
|
|
116
115
|
!shutdown
|
|
117
116
|
!interface mgmt[0-9]*
|
|
118
117
|
no shutdown
|
|
119
|
-
#
|
|
118
|
+
# Physical and NOT splitted interfaces and subifs
|
|
120
119
|
!interface */Ethernet1\/[0-9.]*/
|
|
121
120
|
no shutdown
|
|
122
|
-
#
|
|
121
|
+
# Physical and NOT splitted interfaces and subifs
|
|
123
122
|
!interface */Ethernet1\/[0-9]+\/[0-9.]+/
|
|
124
123
|
# только explicit
|
|
125
|
-
#
|
|
124
|
+
# Loopbacks
|
|
126
125
|
!interface */Loopback[0-9.]+/
|
|
127
126
|
no shutdown
|
|
128
|
-
#
|
|
127
|
+
# Port-Channels
|
|
129
128
|
!interface */port-channel[0-9.]+/
|
|
130
129
|
no shutdown
|
|
131
130
|
# BGP
|
|
@@ -135,7 +134,14 @@ def _implicit_tree(device):
|
|
|
135
134
|
"""
|
|
136
135
|
elif device.hw.Cisco:
|
|
137
136
|
text += r"""
|
|
138
|
-
!interface
|
|
137
|
+
!interface */\S*Ethernet\S+/
|
|
138
|
+
mtu 1500
|
|
139
|
+
no shutdown
|
|
140
|
+
!interface */Loopback[0-9.]+/
|
|
141
|
+
mtu 1500
|
|
142
|
+
no shutdown
|
|
143
|
+
!interface */port-channel[0-9.]+/
|
|
144
|
+
mtu 1500
|
|
139
145
|
no shutdown
|
|
140
146
|
"""
|
|
141
147
|
if device.hw.Cisco.Catalyst:
|
|
@@ -8,9 +8,12 @@ __all__ = [
|
|
|
8
8
|
"Left",
|
|
9
9
|
"Right",
|
|
10
10
|
"Match",
|
|
11
|
+
"VirtualLocal",
|
|
12
|
+
"VirtualPeer",
|
|
11
13
|
]
|
|
12
14
|
|
|
13
15
|
from .executor import MeshExecutor
|
|
14
16
|
from .match_args import Left, Right, Match
|
|
15
|
-
from .registry import
|
|
16
|
-
|
|
17
|
+
from .registry import (
|
|
18
|
+
DirectPeer, IndirectPeer, MeshSession, GlobalOptions, MeshRulesRegistry, VirtualLocal, VirtualPeer,
|
|
19
|
+
)
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
from typing import Annotated, Optional, Union
|
|
2
2
|
|
|
3
|
-
from annet.bgp_models import Family,
|
|
3
|
+
from annet.bgp_models import Family, Redistribute
|
|
4
4
|
from .basemodel import BaseMeshModel, Concat, DictMerge, Merge, KeyDefaultDict
|
|
5
5
|
from .peer_models import MeshPeerGroup
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class Aggregate(BaseMeshModel):
|
|
9
|
+
policy: str
|
|
10
|
+
routes: Annotated[tuple[str, ...], Concat()]
|
|
11
|
+
export_policy: str
|
|
12
|
+
as_path: str
|
|
13
|
+
reference: str
|
|
14
|
+
suppress: bool
|
|
15
|
+
discard: bool
|
|
16
|
+
as_set: bool
|
|
17
|
+
|
|
18
|
+
|
|
8
19
|
class FamilyOptions(BaseMeshModel):
|
|
20
|
+
def __init__(self, **kwargs):
|
|
21
|
+
kwargs.setdefault("aggregate", Aggregate())
|
|
22
|
+
super().__init__(**kwargs)
|
|
9
23
|
family: Family
|
|
10
24
|
vrf_name: str
|
|
11
25
|
multipath: int = 0
|
|
12
26
|
global_multipath: int
|
|
13
|
-
aggregate: Aggregate
|
|
27
|
+
aggregate: Annotated[Aggregate, Merge()]
|
|
14
28
|
redistributes: Annotated[tuple[Redistribute, ...], Concat()]
|
|
15
29
|
allow_default: bool
|
|
16
30
|
aspath_relax: bool
|
|
@@ -24,16 +38,26 @@ class FamilyOptions(BaseMeshModel):
|
|
|
24
38
|
|
|
25
39
|
|
|
26
40
|
class _FamiliesMixin:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
def __init__(self, **kwargs):
|
|
42
|
+
kwargs.setdefault("ipv4_unicast", FamilyOptions(family="ipv4_unicast"))
|
|
43
|
+
kwargs.setdefault("ipv6_unicast", FamilyOptions(family="ipv6_unicast"))
|
|
44
|
+
kwargs.setdefault("ipv4_labeled_unicast", FamilyOptions(family="ipv4_labeled"))
|
|
45
|
+
kwargs.setdefault("ipv6_labeled_unicast", FamilyOptions(family="ipv6_labeled"))
|
|
46
|
+
super().__init__(**kwargs)
|
|
47
|
+
ipv4_unicast: Annotated[FamilyOptions, Merge()]
|
|
48
|
+
ipv6_unicast: Annotated[FamilyOptions, Merge()]
|
|
49
|
+
ipv4_labeled_unicast: Annotated[FamilyOptions, Merge()]
|
|
50
|
+
ipv6_labeled_unicast: Annotated[FamilyOptions, Merge()]
|
|
31
51
|
|
|
32
52
|
|
|
33
|
-
class VrfOptions(
|
|
34
|
-
def __init__(self, **kwargs):
|
|
53
|
+
class VrfOptions(_FamiliesMixin, BaseMeshModel):
|
|
54
|
+
def __init__(self, vrf_name: str, **kwargs):
|
|
55
|
+
kwargs.setdefault("ipv4_unicast", FamilyOptions(family="ipv4_unicast", vrf_name=vrf_name))
|
|
56
|
+
kwargs.setdefault("ipv6_unicast", FamilyOptions(family="ipv6_unicast", vrf_name=vrf_name))
|
|
57
|
+
kwargs.setdefault("ipv4_labeled_unicast", FamilyOptions(family="ipv4_labeled", vrf_name=vrf_name))
|
|
58
|
+
kwargs.setdefault("ipv6_labeled_unicast", FamilyOptions(family="ipv6_labeled", vrf_name=vrf_name))
|
|
35
59
|
kwargs.setdefault("groups", KeyDefaultDict(lambda x: MeshPeerGroup(name=x)))
|
|
36
|
-
super().__init__(**kwargs)
|
|
60
|
+
super().__init__(vrf_name=vrf_name, **kwargs)
|
|
37
61
|
|
|
38
62
|
vrf_name: str
|
|
39
63
|
vrf_name_global: Optional[str]
|
|
@@ -48,7 +72,7 @@ class VrfOptions(BaseMeshModel, _FamiliesMixin):
|
|
|
48
72
|
groups: Annotated[dict[str, MeshPeerGroup], DictMerge(Merge())]
|
|
49
73
|
|
|
50
74
|
|
|
51
|
-
class GlobalOptionsDTO(
|
|
75
|
+
class GlobalOptionsDTO(_FamiliesMixin, BaseMeshModel):
|
|
52
76
|
def __init__(self, **kwargs):
|
|
53
77
|
kwargs.setdefault("groups", KeyDefaultDict(lambda x: MeshPeerGroup(name=x)))
|
|
54
78
|
kwargs.setdefault("vrf", KeyDefaultDict(lambda x: VrfOptions(vrf_name=x)))
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from logging import getLogger
|
|
3
|
-
from typing import Annotated, Callable, Optional
|
|
3
|
+
from typing import Annotated, Callable, Optional, Union
|
|
4
4
|
|
|
5
5
|
from annet.bgp_models import Peer, GlobalOptions
|
|
6
6
|
from annet.storage import Device, Storage
|
|
7
7
|
from .basemodel import merge, BaseMeshModel, Merge, UseLast, MergeForbiddenError
|
|
8
8
|
from .device_models import GlobalOptionsDTO
|
|
9
9
|
from .models_converter import to_bgp_global_options, to_bgp_peer, InterfaceChanges, to_interface_changes
|
|
10
|
-
from .peer_models import
|
|
11
|
-
from .registry import
|
|
12
|
-
|
|
10
|
+
from .peer_models import DirectPeerDTO, IndirectPeerDTO, VirtualLocalDTO, VirtualPeerDTO
|
|
11
|
+
from .registry import (
|
|
12
|
+
DirectPeer,
|
|
13
|
+
GlobalOptions as MeshGlobalOptions,
|
|
14
|
+
IndirectPeer,
|
|
15
|
+
MeshRulesRegistry,
|
|
16
|
+
MeshSession,
|
|
17
|
+
VirtualLocal,
|
|
18
|
+
VirtualPeer,
|
|
19
|
+
)
|
|
13
20
|
|
|
14
21
|
logger = getLogger(__name__)
|
|
15
22
|
|
|
@@ -28,11 +35,16 @@ class PeerKey:
|
|
|
28
35
|
|
|
29
36
|
|
|
30
37
|
class Pair(BaseMeshModel):
|
|
31
|
-
local: Annotated[
|
|
32
|
-
connected: Annotated[
|
|
38
|
+
local: Annotated[Union[DirectPeerDTO, IndirectPeerDTO], Merge()]
|
|
39
|
+
connected: Annotated[Union[DirectPeerDTO, IndirectPeerDTO], Merge()]
|
|
33
40
|
device: Annotated[Device, UseLast()]
|
|
34
41
|
|
|
35
42
|
|
|
43
|
+
class VirtualPair(BaseMeshModel):
|
|
44
|
+
local: Annotated[VirtualLocalDTO, Merge()]
|
|
45
|
+
connected: Annotated[VirtualPeerDTO, Merge()]
|
|
46
|
+
|
|
47
|
+
|
|
36
48
|
class MeshExecutor:
|
|
37
49
|
def __init__(
|
|
38
50
|
self,
|
|
@@ -93,14 +105,14 @@ class MeshExecutor:
|
|
|
93
105
|
rule.handler(peer_neighbor, peer_device, session)
|
|
94
106
|
|
|
95
107
|
try:
|
|
96
|
-
neighbor_dto = merge(
|
|
108
|
+
neighbor_dto = merge(DirectPeerDTO(), peer_neighbor, session)
|
|
97
109
|
except MergeForbiddenError as e:
|
|
98
110
|
raise ValueError(
|
|
99
111
|
f"Handler `{handler_name}` provided session data conflicting with "
|
|
100
112
|
f"peer data for device `{neighbor_device.fqdn}`:\n" + str(e)
|
|
101
113
|
) from e
|
|
102
114
|
try:
|
|
103
|
-
device_dto = merge(
|
|
115
|
+
device_dto = merge(DirectPeerDTO(), peer_device, session)
|
|
104
116
|
except MergeForbiddenError as e:
|
|
105
117
|
raise ValueError(
|
|
106
118
|
f"Handler `{handler_name}` provided session data conflicting with "
|
|
@@ -132,6 +144,42 @@ class MeshExecutor:
|
|
|
132
144
|
neighbor_peers[peer_key] = pair
|
|
133
145
|
return list(neighbor_peers.values())
|
|
134
146
|
|
|
147
|
+
def _execute_virtual(self, device: Device) -> list[VirtualPair]:
|
|
148
|
+
virtual_peers: list[VirtualPair] = []
|
|
149
|
+
for rule in self._registry.lookup_virtual(device.fqdn):
|
|
150
|
+
for order_number in rule.num:
|
|
151
|
+
handler_name = self._handler_name(rule.handler)
|
|
152
|
+
logger.debug("Running direct handler: %s", handler_name)
|
|
153
|
+
session = MeshSession()
|
|
154
|
+
peer_device = VirtualLocal(rule.match, device)
|
|
155
|
+
peer_virtual = VirtualPeer(num=order_number)
|
|
156
|
+
|
|
157
|
+
rule.handler(peer_device, peer_virtual, session)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
virtual_dto = merge(VirtualPeerDTO(), peer_virtual, session)
|
|
161
|
+
except MergeForbiddenError as e:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"Handler `{handler_name}` provided session data conflicting with "
|
|
164
|
+
f"virtual peer data for device `{device.fqdn}` and num={order_number}:\n" + str(e)
|
|
165
|
+
) from e
|
|
166
|
+
try:
|
|
167
|
+
device_dto = merge(VirtualLocalDTO(), peer_device, session)
|
|
168
|
+
except MergeForbiddenError as e:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"Handler `{handler_name}` provided session data conflicting with "
|
|
171
|
+
f"peer data for device `{device.fqdn}`:\n" + str(e)
|
|
172
|
+
) from e
|
|
173
|
+
|
|
174
|
+
if not hasattr(device_dto, "svi"):
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"Handler `{handler_name}` did not provide `svi` number. "
|
|
177
|
+
"Virtual peer must be connected to SVI interface."
|
|
178
|
+
)
|
|
179
|
+
pair = VirtualPair(local=device_dto, connected=virtual_dto)
|
|
180
|
+
virtual_peers.append(pair)
|
|
181
|
+
return virtual_peers
|
|
182
|
+
|
|
135
183
|
def _execute_indirect(self, device: Device, all_fqdns: list[str]) -> list[Pair]:
|
|
136
184
|
# we can have multiple rules for the same pair
|
|
137
185
|
# we merge them according to remote fqdn
|
|
@@ -152,14 +200,14 @@ class MeshExecutor:
|
|
|
152
200
|
rule.handler(peer_connected, peer_device, session)
|
|
153
201
|
|
|
154
202
|
try:
|
|
155
|
-
connected_dto = merge(
|
|
203
|
+
connected_dto = merge(IndirectPeerDTO(), peer_connected, session)
|
|
156
204
|
except MergeForbiddenError as e:
|
|
157
205
|
raise ValueError(
|
|
158
206
|
f"Handler `{handler_name}` provided session data conflicting with "
|
|
159
207
|
f"peer data for device `{connected_device.fqdn}`:\n" + str(e)
|
|
160
208
|
) from e
|
|
161
209
|
try:
|
|
162
|
-
device_dto = merge(
|
|
210
|
+
device_dto = merge(IndirectPeerDTO(), peer_device, session)
|
|
163
211
|
except MergeForbiddenError as e:
|
|
164
212
|
raise ValueError(
|
|
165
213
|
f"Handler `{handler_name}` provided session data conflicting with "
|
|
@@ -193,7 +241,10 @@ class MeshExecutor:
|
|
|
193
241
|
return list(connected_peers.values()) # FIXME
|
|
194
242
|
|
|
195
243
|
def _to_bgp_peer(self, pair: Pair, interface: Optional[str]) -> Peer:
|
|
196
|
-
return to_bgp_peer(pair.local, pair.connected, pair.device, interface)
|
|
244
|
+
return to_bgp_peer(pair.local, pair.connected, pair.device.hostname, interface)
|
|
245
|
+
|
|
246
|
+
def _virtual_to_bgp_peer(self, pair: VirtualPair, interface: Optional[str]) -> Peer:
|
|
247
|
+
return to_bgp_peer(pair.local, pair.connected, "", interface)
|
|
197
248
|
|
|
198
249
|
def _to_bgp_global(self, global_options: GlobalOptionsDTO) -> GlobalOptions:
|
|
199
250
|
return to_bgp_global_options(global_options)
|
|
@@ -225,6 +276,9 @@ class MeshExecutor:
|
|
|
225
276
|
target_interface.add_addr(changes.addr, changes.vrf)
|
|
226
277
|
return target_interface.name
|
|
227
278
|
|
|
279
|
+
def _apply_virtual_interface_changes(self, device: Device, local: VirtualLocalDTO) -> str:
|
|
280
|
+
return device.add_svi(local.svi).name # we check if SVI configured in execute method
|
|
281
|
+
|
|
228
282
|
def execute_for(self, device: Device) -> MeshExecutionResult:
|
|
229
283
|
all_fqdns = self._storage.resolve_all_fdnds()
|
|
230
284
|
|
|
@@ -239,6 +293,13 @@ class MeshExecutor:
|
|
|
239
293
|
)
|
|
240
294
|
peers.append(self._to_bgp_peer(direct_pair, target_interface))
|
|
241
295
|
|
|
296
|
+
for virtual_pair in self._execute_virtual(device):
|
|
297
|
+
target_interface = self._apply_virtual_interface_changes(
|
|
298
|
+
device,
|
|
299
|
+
virtual_pair.local,
|
|
300
|
+
)
|
|
301
|
+
peers.append(self._virtual_to_bgp_peer(virtual_pair, target_interface))
|
|
302
|
+
|
|
242
303
|
for connected_pair in self._execute_indirect(device, all_fqdns):
|
|
243
304
|
peers.append(self._to_bgp_peer(connected_pair, None))
|
|
244
305
|
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from ipaddress import ip_interface
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional, Union
|
|
4
4
|
|
|
5
|
-
from adaptix import Retort, loader, Chain, name_mapping
|
|
5
|
+
from adaptix import Retort, loader, Chain, name_mapping, as_is_loader
|
|
6
6
|
|
|
7
|
-
from .peer_models import
|
|
8
|
-
from ..bgp_models import
|
|
9
|
-
|
|
7
|
+
from .peer_models import DirectPeerDTO, IndirectPeerDTO, VirtualPeerDTO, VirtualLocalDTO
|
|
8
|
+
from ..bgp_models import (
|
|
9
|
+
Aggregate, GlobalOptions, VrfOptions, FamilyOptions, Peer, PeerGroup, ASN, PeerOptions,
|
|
10
|
+
Redistribute, BFDTimers,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
PeerDTO = Union[DirectPeerDTO, IndirectPeerDTO, VirtualPeerDTO]
|
|
15
|
+
LocalDTO = Union[DirectPeerDTO, IndirectPeerDTO, VirtualLocalDTO]
|
|
10
16
|
|
|
11
17
|
|
|
12
18
|
@dataclass
|
|
@@ -46,7 +52,10 @@ retort = Retort(
|
|
|
46
52
|
loader(GlobalOptions, ObjMapping, Chain.FIRST),
|
|
47
53
|
loader(VrfOptions, ObjMapping, Chain.FIRST),
|
|
48
54
|
loader(FamilyOptions, ObjMapping, Chain.FIRST),
|
|
55
|
+
loader(Aggregate, ObjMapping, Chain.FIRST),
|
|
49
56
|
loader(PeerOptions, ObjMapping, Chain.FIRST),
|
|
57
|
+
as_is_loader(Redistribute),
|
|
58
|
+
as_is_loader(BFDTimers),
|
|
50
59
|
name_mapping(PeerOptions, map={
|
|
51
60
|
"local_as": "asnum",
|
|
52
61
|
}),
|
|
@@ -62,14 +71,13 @@ to_bgp_global_options = retort.get_loader(GlobalOptions)
|
|
|
62
71
|
to_interface_changes = retort.get_loader(InterfaceChanges)
|
|
63
72
|
|
|
64
73
|
|
|
65
|
-
def to_bgp_peer(local:
|
|
74
|
+
def to_bgp_peer(local: LocalDTO, connected: PeerDTO, connected_hostname: str, interface: Optional[str]) -> Peer:
|
|
66
75
|
options = retort.load(local, PeerOptions)
|
|
67
|
-
# TODO validate `lagg_links` before conversion
|
|
68
76
|
result = Peer(
|
|
69
77
|
addr=str(ip_interface(connected.addr).ip),
|
|
70
78
|
interface=interface,
|
|
71
79
|
remote_as=ASN(connected.asnum),
|
|
72
|
-
hostname=
|
|
80
|
+
hostname=connected_hostname,
|
|
73
81
|
options=options,
|
|
74
82
|
)
|
|
75
83
|
# connected
|
|
@@ -78,7 +86,7 @@ def to_bgp_peer(local: PeerDTO, connected: PeerDTO, connected_device: Device, in
|
|
|
78
86
|
result.description = getattr(connected, "description", result.description)
|
|
79
87
|
result.families = getattr(connected, "families", result.families)
|
|
80
88
|
# local
|
|
81
|
-
result.import_policy = getattr(
|
|
82
|
-
result.export_policy = getattr(
|
|
83
|
-
result.update_source = getattr(
|
|
89
|
+
result.import_policy = getattr(local, "import_policy", result.import_policy)
|
|
90
|
+
result.export_policy = getattr(local, "export_policy", result.export_policy)
|
|
91
|
+
result.update_source = getattr(local, "update_source", result.update_source)
|
|
84
92
|
return result
|
|
@@ -61,7 +61,7 @@ class _OptionsDTO(_SharedOptionsDTO):
|
|
|
61
61
|
af_rib_group: Optional[str]
|
|
62
62
|
af_loops: int
|
|
63
63
|
hold_time: int
|
|
64
|
-
listen_network:
|
|
64
|
+
listen_network: list[str]
|
|
65
65
|
remove_private: bool
|
|
66
66
|
as_override: bool
|
|
67
67
|
aigp: bool
|
|
@@ -77,21 +77,46 @@ class _OptionsDTO(_SharedOptionsDTO):
|
|
|
77
77
|
mtu: int
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
class
|
|
80
|
+
class DirectPeerDTO(MeshSession, _OptionsDTO):
|
|
81
81
|
pod: int
|
|
82
82
|
addr: str
|
|
83
83
|
description: str
|
|
84
|
+
update_source: str
|
|
84
85
|
|
|
85
86
|
subif: int
|
|
86
87
|
lag: Optional[int]
|
|
87
88
|
lag_links_min: Optional[int]
|
|
88
89
|
svi: Optional[int]
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
|
|
92
|
+
class IndirectPeerDTO(MeshSession, _OptionsDTO):
|
|
93
|
+
pod: int
|
|
94
|
+
addr: str
|
|
95
|
+
description: str
|
|
96
|
+
update_source: str
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class VirtualLocalDTO(_OptionsDTO):
|
|
100
|
+
asnum: int
|
|
101
|
+
pod: int
|
|
102
|
+
addr: str
|
|
103
|
+
description: str
|
|
104
|
+
|
|
105
|
+
import_policy: str
|
|
106
|
+
export_policy: str
|
|
107
|
+
update_source: str
|
|
108
|
+
|
|
109
|
+
svi: int
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class VirtualPeerDTO(MeshSession):
|
|
113
|
+
addr: str
|
|
114
|
+
description: str
|
|
91
115
|
|
|
92
116
|
|
|
93
117
|
class MeshPeerGroup(_OptionsDTO):
|
|
94
118
|
name: str
|
|
119
|
+
families: Annotated[set[FamilyName], Concat()]
|
|
95
120
|
remote_as: Union[int, str]
|
|
96
121
|
internal_name: str
|
|
97
122
|
update_source: str
|