annet 0.16.12__tar.gz → 0.16.14__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.12/annet.egg-info → annet-0.16.14}/PKG-INFO +1 -1
- {annet-0.16.12 → annet-0.16.14}/README.md +1 -1
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/storage_opts.py +7 -2
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/provider.py +3 -1
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/v37/storage.py +26 -19
- {annet-0.16.12 → annet-0.16.14}/annet/bgp_models.py +14 -11
- {annet-0.16.12 → annet-0.16.14}/annet/implicit.py +4 -2
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/__init__.py +5 -2
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/device_models.py +34 -10
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/executor.py +72 -11
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/models_converter.py +10 -6
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/peer_models.py +27 -3
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/registry.py +61 -4
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/cisco.rul +3 -0
- {annet-0.16.12 → annet-0.16.14/annet.egg-info}/PKG-INFO +1 -1
- {annet-0.16.12 → annet-0.16.14}/annet_generators/mesh_example/bgp.py +1 -0
- {annet-0.16.12 → annet-0.16.14}/annet_generators/mesh_example/mesh_logic.py +9 -1
- {annet-0.16.12 → annet-0.16.14}/AUTHORS +0 -0
- {annet-0.16.12 → annet-0.16.14}/LICENSE +0 -0
- {annet-0.16.12 → annet-0.16.14}/MANIFEST.in +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/fetchers/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/fetchers/stub/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/fetchers/stub/fetcher.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/file/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/file/provider.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/client.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/manufacturer.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/models.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/query.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/common/status_client.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/v24/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/v24/storage.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/adapters/netbox/v37/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annet.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/command.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/diff.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/errors.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/filter_acl.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/jsontools.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/lib.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/db.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/devdb/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/views/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/views/dump.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/netdev/views/hardware.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/output.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/patching.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rbparser/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rbparser/acl.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rbparser/deploying.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rbparser/ordering.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rbparser/platform.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rbparser/syntax.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rulebook/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/rulebook/common.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/tabparser.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/annlib/types.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/api/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/argparse.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/cli.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/cli_args.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/configs/context.yml +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/configs/logging.yaml +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/connectors.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/deploy.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/diff.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/executor.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/filtering.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/gen.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/base.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/common/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/common/initial.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/entire.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/exceptions.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/jsonfragment.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/partial.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/perf.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/ref.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/generators/result.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/hardware.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/lib.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/basemodel.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/mesh/match_args.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/output.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/parallel.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/patching.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/reference.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/arista/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/arista/iface.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/aruba/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/aruba/ap_env.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/aruba/misc.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/b4com/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/b4com/file.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/cisco/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/cisco/iface.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/cisco/misc.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/cisco/vlandb.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/common.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/deploying.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/huawei/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/huawei/aaa.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/huawei/bgp.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/huawei/iface.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/huawei/misc.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/huawei/vlandb.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/juniper/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/nexus/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/nexus/iface.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/patching.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/ribbon/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/routeros/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/routeros/file.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/arista.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/arista.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/arista.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/aruba.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/aruba.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/aruba.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/b4com.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/b4com.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/b4com.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/cisco.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/cisco.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/huawei.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/huawei.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/huawei.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/juniper.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/nexus.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/nexus.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/nexus.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/nokia.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/optixtrans.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/optixtrans.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/optixtrans.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/pc.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/pc.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/pc.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/ribbon.deploy +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/ribbon.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/routeros.order +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/rulebook/texts/routeros.rul +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/storage.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/tabparser.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/text_term_format.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/tracing.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet/types.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet.egg-info/SOURCES.txt +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet.egg-info/dependency_links.txt +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet.egg-info/entry_points.txt +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet.egg-info/requires.txt +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet.egg-info/top_level.txt +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet_generators/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet_generators/example/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet_generators/example/lldp.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/annet_generators/mesh_example/__init__.py +0 -0
- {annet-0.16.12 → annet-0.16.14}/requirements.txt +0 -0
- {annet-0.16.12 → annet-0.16.14}/setup.cfg +0 -0
- {annet-0.16.12 → annet-0.16.14}/setup.py +0 -0
|
@@ -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 = {
|
|
@@ -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)
|
|
@@ -213,7 +213,7 @@ class PeerGroup:
|
|
|
213
213
|
af_rib_group: Optional[str] = None
|
|
214
214
|
af_loops: int = 0
|
|
215
215
|
hold_time: int = 0
|
|
216
|
-
listen_network:
|
|
216
|
+
listen_network: list[str] = field(default_factory=list)
|
|
217
217
|
remove_private: bool = False
|
|
218
218
|
as_override: bool = False
|
|
219
219
|
aigp: bool = False
|
|
@@ -234,6 +234,12 @@ class PeerGroup:
|
|
|
234
234
|
@dataclass
|
|
235
235
|
class VrfOptions:
|
|
236
236
|
vrf_name: str
|
|
237
|
+
|
|
238
|
+
ipv4_unicast: FamilyOptions
|
|
239
|
+
ipv6_unicast: FamilyOptions
|
|
240
|
+
ipv4_labeled_unicast: FamilyOptions
|
|
241
|
+
ipv6_labeled_unicast: FamilyOptions
|
|
242
|
+
|
|
237
243
|
vrf_name_global: Optional[str] = None
|
|
238
244
|
import_policy: Optional[str] = None
|
|
239
245
|
export_policy: Optional[str] = None
|
|
@@ -244,23 +250,20 @@ class VrfOptions:
|
|
|
244
250
|
route_distinguisher: Optional[str] = None
|
|
245
251
|
static_label: Optional[int] = None # FIXME: str?
|
|
246
252
|
|
|
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
253
|
groups: list[PeerGroup] = field(default_factory=list)
|
|
252
254
|
|
|
253
255
|
|
|
254
256
|
@dataclass
|
|
255
257
|
class GlobalOptions:
|
|
258
|
+
ipv4_unicast: FamilyOptions
|
|
259
|
+
ipv6_unicast: FamilyOptions
|
|
260
|
+
ipv4_labeled_unicast: FamilyOptions
|
|
261
|
+
ipv6_labeled_unicast: FamilyOptions
|
|
262
|
+
|
|
256
263
|
local_as: ASN = ASN(None)
|
|
257
264
|
loops: int = 0
|
|
258
265
|
multipath: int = 0
|
|
259
266
|
router_id: str = ""
|
|
260
267
|
vrf: dict[str, VrfOptions] = field(default_factory=dict)
|
|
261
268
|
|
|
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
269
|
groups: list[PeerGroup] = field(default_factory=list)
|
|
@@ -135,9 +135,11 @@ def _implicit_tree(device):
|
|
|
135
135
|
"""
|
|
136
136
|
elif device.hw.Cisco:
|
|
137
137
|
text += r"""
|
|
138
|
-
!interface
|
|
138
|
+
!interface *Ethernet*
|
|
139
|
+
mtu 1500
|
|
139
140
|
no shutdown
|
|
140
|
-
|
|
141
|
+
|
|
142
|
+
|
|
141
143
|
"""
|
|
142
144
|
if device.hw.Cisco.Catalyst:
|
|
143
145
|
# this configuration is not visible in running-config when enabled
|
|
@@ -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,14 +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
5
|
from adaptix import Retort, loader, Chain, name_mapping
|
|
6
6
|
|
|
7
|
-
from .peer_models import
|
|
8
|
-
from ..bgp_models import GlobalOptions, VrfOptions, FamilyOptions, Peer, PeerGroup, ASN, PeerOptions
|
|
7
|
+
from .peer_models import DirectPeerDTO, IndirectPeerDTO, VirtualPeerDTO, VirtualLocalDTO
|
|
8
|
+
from ..bgp_models import Aggregate, GlobalOptions, VrfOptions, FamilyOptions, Peer, PeerGroup, ASN, PeerOptions
|
|
9
9
|
from ..storage import Device
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
PeerDTO = Union[DirectPeerDTO, IndirectPeerDTO, VirtualPeerDTO]
|
|
13
|
+
LocalDTO = Union[DirectPeerDTO, IndirectPeerDTO, VirtualLocalDTO]
|
|
14
|
+
|
|
15
|
+
|
|
12
16
|
@dataclass
|
|
13
17
|
class InterfaceChanges:
|
|
14
18
|
addr: str
|
|
@@ -46,6 +50,7 @@ retort = Retort(
|
|
|
46
50
|
loader(GlobalOptions, ObjMapping, Chain.FIRST),
|
|
47
51
|
loader(VrfOptions, ObjMapping, Chain.FIRST),
|
|
48
52
|
loader(FamilyOptions, ObjMapping, Chain.FIRST),
|
|
53
|
+
loader(Aggregate, ObjMapping, Chain.FIRST),
|
|
49
54
|
loader(PeerOptions, ObjMapping, Chain.FIRST),
|
|
50
55
|
name_mapping(PeerOptions, map={
|
|
51
56
|
"local_as": "asnum",
|
|
@@ -62,14 +67,13 @@ to_bgp_global_options = retort.get_loader(GlobalOptions)
|
|
|
62
67
|
to_interface_changes = retort.get_loader(InterfaceChanges)
|
|
63
68
|
|
|
64
69
|
|
|
65
|
-
def to_bgp_peer(local:
|
|
70
|
+
def to_bgp_peer(local: LocalDTO, connected: PeerDTO, connected_hostname: str, interface: Optional[str]) -> Peer:
|
|
66
71
|
options = retort.load(local, PeerOptions)
|
|
67
|
-
# TODO validate `lagg_links` before conversion
|
|
68
72
|
result = Peer(
|
|
69
73
|
addr=str(ip_interface(connected.addr).ip),
|
|
70
74
|
interface=interface,
|
|
71
75
|
remote_as=ASN(connected.asnum),
|
|
72
|
-
hostname=
|
|
76
|
+
hostname=connected_hostname,
|
|
73
77
|
options=options,
|
|
74
78
|
)
|
|
75
79
|
# connected
|
|
@@ -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,17 +77,41 @@ 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(BaseMeshModel):
|
|
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, _OptionsDTO):
|
|
113
|
+
addr: str
|
|
114
|
+
description: str
|
|
91
115
|
|
|
92
116
|
|
|
93
117
|
class MeshPeerGroup(_OptionsDTO):
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Callable, Any
|
|
2
|
+
from typing import Callable, Any, Sequence
|
|
3
3
|
|
|
4
4
|
from .match_args import MatchExpr, PairMatcher, SingleMatcher
|
|
5
5
|
from .match_args import MatchedArgs
|
|
6
6
|
from .device_models import GlobalOptionsDTO
|
|
7
|
-
from .peer_models import
|
|
7
|
+
from .peer_models import MeshSession, IndirectPeerDTO, VirtualLocalDTO, VirtualPeerDTO, DirectPeerDTO
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class DirectPeer(
|
|
10
|
+
class DirectPeer(DirectPeerDTO):
|
|
11
11
|
match: MatchedArgs
|
|
12
12
|
device: Any
|
|
13
13
|
ports: list[str]
|
|
@@ -19,7 +19,7 @@ class DirectPeer(PeerDTO):
|
|
|
19
19
|
self.ports = ports
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class IndirectPeer(
|
|
22
|
+
class IndirectPeer(IndirectPeerDTO):
|
|
23
23
|
match: MatchedArgs
|
|
24
24
|
device: Any
|
|
25
25
|
|
|
@@ -29,6 +29,20 @@ class IndirectPeer(PeerDTO):
|
|
|
29
29
|
self.device = device
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
class VirtualLocal(VirtualLocalDTO):
|
|
33
|
+
match: MatchedArgs
|
|
34
|
+
device: Any
|
|
35
|
+
|
|
36
|
+
def __init__(self, match: MatchedArgs, device: Any) -> None:
|
|
37
|
+
super().__init__()
|
|
38
|
+
self.match = match
|
|
39
|
+
self.device = device
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class VirtualPeer(VirtualPeerDTO):
|
|
43
|
+
num: int
|
|
44
|
+
|
|
45
|
+
|
|
32
46
|
class GlobalOptions(GlobalOptionsDTO):
|
|
33
47
|
match: MatchedArgs
|
|
34
48
|
device: Any
|
|
@@ -50,6 +64,7 @@ class GlobalRule:
|
|
|
50
64
|
|
|
51
65
|
DirectHandler = Callable[[DirectPeer, DirectPeer, MeshSession], None]
|
|
52
66
|
IndirectHandler = Callable[[IndirectPeer, IndirectPeer, MeshSession], None]
|
|
67
|
+
VirtualHandler = Callable[[VirtualLocal, VirtualPeer, MeshSession], None]
|
|
53
68
|
|
|
54
69
|
|
|
55
70
|
@dataclass
|
|
@@ -66,6 +81,14 @@ class IndirectRule:
|
|
|
66
81
|
handler: IndirectHandler
|
|
67
82
|
|
|
68
83
|
|
|
84
|
+
@dataclass
|
|
85
|
+
class VirtualRule:
|
|
86
|
+
__slots__ = ("matcher", "num", "handler")
|
|
87
|
+
matcher: SingleMatcher
|
|
88
|
+
num: Sequence[int]
|
|
89
|
+
handler: VirtualHandler
|
|
90
|
+
|
|
91
|
+
|
|
69
92
|
@dataclass
|
|
70
93
|
class MatchedGlobal:
|
|
71
94
|
__slots__ = ("match", "handler")
|
|
@@ -95,11 +118,20 @@ class MatchedIndirectPair:
|
|
|
95
118
|
match_right: MatchedArgs
|
|
96
119
|
|
|
97
120
|
|
|
121
|
+
@dataclass
|
|
122
|
+
class MatchedVirtualPair:
|
|
123
|
+
__slots__ = ("match", "num", "handler")
|
|
124
|
+
match: MatchedArgs
|
|
125
|
+
num: Sequence[int]
|
|
126
|
+
handler: VirtualHandler
|
|
127
|
+
|
|
128
|
+
|
|
98
129
|
class MeshRulesRegistry:
|
|
99
130
|
def __init__(self, match_short_name: bool = False):
|
|
100
131
|
self.direct_rules: list[DirectRule] = []
|
|
101
132
|
self.indirect_rules: list[IndirectRule] = []
|
|
102
133
|
self.global_rules: list[GlobalRule] = []
|
|
134
|
+
self.virtual_rules: list[VirtualRule] = []
|
|
103
135
|
self.nested: list[MeshRulesRegistry] = []
|
|
104
136
|
self.match_short_name = match_short_name
|
|
105
137
|
|
|
@@ -142,6 +174,17 @@ class MeshRulesRegistry:
|
|
|
142
174
|
|
|
143
175
|
return register
|
|
144
176
|
|
|
177
|
+
def virtual(
|
|
178
|
+
self, peer_mask: str, num: Sequence[int], *match: MatchExpr,
|
|
179
|
+
) -> Callable[[VirtualHandler], VirtualHandler]:
|
|
180
|
+
matcher = SingleMatcher(peer_mask, match)
|
|
181
|
+
|
|
182
|
+
def register(handler: VirtualHandler) -> VirtualHandler:
|
|
183
|
+
self.virtual_rules.append(VirtualRule(matcher, num, handler))
|
|
184
|
+
return handler
|
|
185
|
+
|
|
186
|
+
return register
|
|
187
|
+
|
|
145
188
|
def lookup_direct(self, device: str, neighbors: list[str]) -> list[MatchedDirectPair]:
|
|
146
189
|
found = []
|
|
147
190
|
device = self._normalize_host(device)
|
|
@@ -198,6 +241,20 @@ class MeshRulesRegistry:
|
|
|
198
241
|
found.extend(registry.lookup_indirect(device, devices))
|
|
199
242
|
return found
|
|
200
243
|
|
|
244
|
+
def lookup_virtual(self, device: str) -> list[MatchedVirtualPair]:
|
|
245
|
+
found = []
|
|
246
|
+
device = self._normalize_host(device)
|
|
247
|
+
for rule in self.virtual_rules:
|
|
248
|
+
if args := rule.matcher.match_one(device):
|
|
249
|
+
found.append(MatchedVirtualPair(
|
|
250
|
+
handler=rule.handler,
|
|
251
|
+
match=args,
|
|
252
|
+
num=rule.num,
|
|
253
|
+
))
|
|
254
|
+
for registry in self.nested:
|
|
255
|
+
found.extend(registry.lookup_virtual(device))
|
|
256
|
+
return found
|
|
257
|
+
|
|
201
258
|
def lookup_global(self, device: str) -> list[MatchedGlobal]:
|
|
202
259
|
found = []
|
|
203
260
|
device = self._normalize_host(device)
|