annet 0.16.7__py3-none-any.whl → 0.16.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

Files changed (37) hide show
  1. annet/adapters/fetchers/__init__.py +0 -0
  2. annet/adapters/fetchers/stub/__init__.py +0 -0
  3. annet/adapters/fetchers/stub/fetcher.py +19 -0
  4. annet/adapters/file/__init__.py +0 -0
  5. annet/adapters/file/provider.py +226 -0
  6. annet/adapters/netbox/common/models.py +108 -3
  7. annet/adapters/netbox/v37/storage.py +31 -3
  8. annet/annlib/netdev/views/hardware.py +31 -0
  9. annet/bgp_models.py +266 -0
  10. annet/configs/context.yml +2 -3
  11. annet/configs/logging.yaml +1 -0
  12. annet/generators/__init__.py +18 -4
  13. annet/implicit.py +5 -0
  14. annet/mesh/__init__.py +16 -0
  15. annet/mesh/basemodel.py +180 -0
  16. annet/mesh/device_models.py +62 -0
  17. annet/mesh/executor.py +248 -0
  18. annet/mesh/match_args.py +165 -0
  19. annet/mesh/models_converter.py +84 -0
  20. annet/mesh/peer_models.py +98 -0
  21. annet/mesh/registry.py +212 -0
  22. annet/rulebook/routeros/__init__.py +0 -0
  23. annet/rulebook/routeros/file.py +5 -0
  24. annet/rulebook/texts/cisco.order +5 -2
  25. annet/rulebook/texts/cisco.rul +6 -0
  26. annet/storage.py +41 -2
  27. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/METADATA +3 -1
  28. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/RECORD +37 -18
  29. annet_generators/example/__init__.py +1 -3
  30. annet_generators/mesh_example/__init__.py +9 -0
  31. annet_generators/mesh_example/bgp.py +43 -0
  32. annet_generators/mesh_example/mesh_logic.py +28 -0
  33. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/AUTHORS +0 -0
  34. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/LICENSE +0 -0
  35. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/WHEEL +0 -0
  36. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/entry_points.txt +0 -0
  37. {annet-0.16.7.dist-info → annet-0.16.9.dist-info}/top_level.txt +0 -0
annet/mesh/executor.py ADDED
@@ -0,0 +1,248 @@
1
+ from dataclasses import dataclass
2
+ from logging import getLogger
3
+ from typing import Annotated, Callable, Optional
4
+
5
+ from annet.bgp_models import Peer, GlobalOptions
6
+ from annet.storage import Device, Storage
7
+ from .basemodel import merge, BaseMeshModel, Merge, UseLast, MergeForbiddenError
8
+ from .device_models import GlobalOptionsDTO
9
+ from .models_converter import to_bgp_global_options, to_bgp_peer, InterfaceChanges, to_interface_changes
10
+ from .peer_models import PeerDTO
11
+ from .registry import MeshRulesRegistry, GlobalOptions as MeshGlobalOptions, DirectPeer, MeshSession, IndirectPeer
12
+
13
+
14
+ logger = getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class MeshExecutionResult:
19
+ global_options: GlobalOptions
20
+ peers: list[Peer]
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class PeerKey:
25
+ fqdn: str
26
+ addr: str
27
+ vrf: str
28
+
29
+
30
+ class Pair(BaseMeshModel):
31
+ local: Annotated[PeerDTO, Merge()]
32
+ connected: Annotated[PeerDTO, Merge()]
33
+ device: Annotated[Device, UseLast()]
34
+
35
+
36
+ class MeshExecutor:
37
+ def __init__(
38
+ self,
39
+ registry: MeshRulesRegistry,
40
+ storage: Storage,
41
+ ):
42
+ self._registry = registry
43
+ self._storage = storage
44
+
45
+ def _execute_globals(self, device: Device) -> GlobalOptionsDTO:
46
+ global_opts = GlobalOptionsDTO()
47
+ for rule in self._registry.lookup_global(device.fqdn):
48
+ handler_name = self._handler_name(rule.handler)
49
+ rule_global_opts = MeshGlobalOptions(rule.match, device)
50
+ logger.debug("Running device handler: %s", handler_name)
51
+ rule.handler(rule_global_opts)
52
+ try:
53
+ global_opts = merge(global_opts, rule_global_opts)
54
+ except MergeForbiddenError as e:
55
+ raise ValueError(
56
+ f"Handler `{handler_name}` global options conflicting with "
57
+ f"previously loaded for device `{device.fqdn}`:\n" + str(e)
58
+ ) from e
59
+ return global_opts
60
+
61
+ def _handler_name(self, handler: Callable) -> str:
62
+ try:
63
+ return f"{handler.__module__}.{handler.__qualname__}"
64
+ except AttributeError:
65
+ return str(handler)
66
+
67
+ def _execute_direct(self, device: Device) -> list[Pair]:
68
+ # we can have multiple rules for the same pair
69
+ # we merge them according to remote fqdn
70
+ neighbor_peers: dict[PeerKey, Pair] = {}
71
+ # TODO batch resolve
72
+ for rule in self._registry.lookup_direct(device.fqdn, device.neighbours_fqdns):
73
+ session = MeshSession()
74
+ handler_name = self._handler_name(rule.handler)
75
+ logger.debug("Running direct handler: %s", handler_name)
76
+ if rule.direct_order:
77
+ neighbor_device = self._storage.make_devices([rule.name_right])[0]
78
+ peer_device = DirectPeer(rule.match_left, device, [])
79
+ peer_neighbor = DirectPeer(rule.match_right, neighbor_device, [])
80
+ else:
81
+ neighbor_device = self._storage.make_devices([rule.name_left])[0]
82
+ peer_neighbor = DirectPeer(rule.match_left, neighbor_device, [])
83
+ peer_device = DirectPeer(rule.match_right, device, [])
84
+
85
+ interfaces = self._storage.search_connections(device, neighbor_device)
86
+ for local_port, remote_port in interfaces:
87
+ peer_device.ports.append(local_port.name)
88
+ peer_neighbor.ports.append(remote_port.name)
89
+
90
+ if rule.direct_order:
91
+ rule.handler(peer_device, peer_neighbor, session)
92
+ else:
93
+ rule.handler(peer_neighbor, peer_device, session)
94
+
95
+ try:
96
+ neighbor_dto = merge(PeerDTO(), peer_neighbor, session)
97
+ except MergeForbiddenError as e:
98
+ raise ValueError(
99
+ f"Handler `{handler_name}` provided session data conflicting with "
100
+ f"peer data for device `{neighbor_device.fqdn}`:\n" + str(e)
101
+ ) from e
102
+ try:
103
+ device_dto = merge(PeerDTO(), peer_device, session)
104
+ except MergeForbiddenError as e:
105
+ raise ValueError(
106
+ f"Handler `{handler_name}` provided session data conflicting with "
107
+ f"peer data for device `{device.fqdn}`:\n" + str(e)
108
+ ) from e
109
+
110
+ pair = Pair(local=device_dto, connected=neighbor_dto, device=neighbor_device)
111
+ addr = getattr(neighbor_dto, "addr", None)
112
+ if addr is None:
113
+ raise ValueError(f"Handler `{handler_name}` returned no peer addr")
114
+ peer_key = PeerKey(
115
+ fqdn=neighbor_device.fqdn,
116
+ addr=addr,
117
+ vrf=getattr(neighbor_dto, "vrf", "")
118
+ )
119
+ try:
120
+ if peer_key in neighbor_peers:
121
+ pair = merge(neighbor_peers[peer_key], pair)
122
+ except MergeForbiddenError as e:
123
+ if rule.direct_order:
124
+ pair_names = device.fqdn, neighbor_device.fqdn
125
+ else:
126
+ pair_names = neighbor_device.fqdn, device.fqdn
127
+ raise ValueError(
128
+ f"Handler `{handler_name}` provides data conflicting with "
129
+ f"previously loaded for device pair {pair_names} "
130
+ f"with addr={peer_key.addr}, vrf{peer_key.vrf}:\n" + str(e)
131
+ ) from e
132
+ neighbor_peers[peer_key] = pair
133
+ return list(neighbor_peers.values())
134
+
135
+ def _execute_indirect(self, device: Device, all_fqdns: list[str]) -> list[Pair]:
136
+ # we can have multiple rules for the same pair
137
+ # we merge them according to remote fqdn
138
+ connected_peers: dict[PeerKey, Pair] = {}
139
+ for rule in self._registry.lookup_indirect(device.fqdn, all_fqdns):
140
+ session = MeshSession()
141
+ handler_name = self._handler_name(rule.handler)
142
+ logger.debug("Running indirect handler: %s", handler_name)
143
+ if rule.direct_order:
144
+ connected_device = self._storage.make_devices([rule.name_right])[0]
145
+ peer_device = IndirectPeer(rule.match_left, device)
146
+ peer_connected = IndirectPeer(rule.match_right, connected_device)
147
+ rule.handler(peer_device, peer_connected, session)
148
+ else:
149
+ connected_device = self._storage.make_devices([rule.name_left])[0]
150
+ peer_connected = IndirectPeer(rule.match_left, connected_device)
151
+ peer_device = IndirectPeer(rule.match_right, device)
152
+ rule.handler(peer_connected, peer_device, session)
153
+
154
+ try:
155
+ connected_dto = merge(PeerDTO(), peer_connected, session)
156
+ except MergeForbiddenError as e:
157
+ raise ValueError(
158
+ f"Handler `{handler_name}` provided session data conflicting with "
159
+ f"peer data for device `{connected_device.fqdn}`:\n" + str(e)
160
+ ) from e
161
+ try:
162
+ device_dto = merge(PeerDTO(), peer_device, session)
163
+ except MergeForbiddenError as e:
164
+ raise ValueError(
165
+ f"Handler `{handler_name}` provided session data conflicting with "
166
+ f"peer data for device `{device.fqdn}`:\n" + str(e)
167
+ ) from e
168
+
169
+ pair = Pair(local=device_dto, connected=connected_dto, device=connected_device)
170
+ addr = getattr(connected_dto, "addr", None)
171
+ if addr is None:
172
+ raise ValueError(f"Handler `{handler_name}` returned no peer addr")
173
+ peer_key = PeerKey(
174
+ fqdn=connected_device.fqdn,
175
+ addr=addr,
176
+ vrf=getattr(connected_dto, "vrf", "")
177
+ )
178
+ try:
179
+ if peer_key in connected_peers:
180
+ pair = merge(connected_peers[peer_key], pair)
181
+ except MergeForbiddenError as e:
182
+ if rule.direct_order:
183
+ pair_names = device.fqdn, connected_device.fqdn
184
+ else:
185
+ pair_names = connected_device.fqdn, device.fqdn
186
+ raise ValueError(
187
+ f"Handler `{handler_name}` provides data conflicting with "
188
+ f"previously loaded for device pair {pair_names} "
189
+ f"with addr={peer_key.addr}, vrf{peer_key.vrf}:\n" + str(e)
190
+ ) from e
191
+ connected_peers[peer_key] = pair
192
+
193
+ return list(connected_peers.values()) # FIXME
194
+
195
+ def _to_bgp_peer(self, pair: Pair, interface: Optional[str]) -> Peer:
196
+ return to_bgp_peer(pair.local, pair.connected, pair.device, interface)
197
+
198
+ def _to_bgp_global(self, global_options: GlobalOptionsDTO) -> GlobalOptions:
199
+ return to_bgp_global_options(global_options)
200
+
201
+ def _apply_interface_changes(self, device: Device, neighbor: Device, changes: InterfaceChanges) -> str:
202
+ port_pairs = self._storage.search_connections(device, neighbor)
203
+ if len(port_pairs) > 1:
204
+ if changes.lag is changes.svi is None:
205
+ raise ValueError(
206
+ f"Multiple connections found between {device.fqdn} and {neighbor.fqdn}."
207
+ "Specify LAG or SVI"
208
+ )
209
+ if changes.lag is not None:
210
+ target_interface = device.make_lag(
211
+ lag=changes.lag,
212
+ ports=[local_port.name for local_port, remote_port in port_pairs],
213
+ lag_min_links=changes.lag_links_min,
214
+ )
215
+ if changes.subif is not None:
216
+ target_interface = device.add_subif(target_interface.name, changes.subif)
217
+ elif changes.subif is not None:
218
+ # single connection
219
+ local_port, remote_port = port_pairs[0]
220
+ target_interface = device.add_subif(local_port.name, changes.subif)
221
+ elif changes.svi is not None:
222
+ target_interface = device.add_svi(changes.svi)
223
+ else:
224
+ target_interface, _ = port_pairs[0]
225
+ target_interface.add_addr(changes.addr, changes.vrf)
226
+ return target_interface.name
227
+
228
+ def execute_for(self, device: Device) -> MeshExecutionResult:
229
+ all_fqdns = self._storage.resolve_all_fdnds()
230
+
231
+ global_options = self._to_bgp_global(self._execute_globals(device))
232
+
233
+ peers = []
234
+ for direct_pair in self._execute_direct(device):
235
+ target_interface = self._apply_interface_changes(
236
+ device,
237
+ direct_pair.device,
238
+ to_interface_changes(direct_pair.local),
239
+ )
240
+ peers.append(self._to_bgp_peer(direct_pair, target_interface))
241
+
242
+ for connected_pair in self._execute_indirect(device, all_fqdns):
243
+ peers.append(self._to_bgp_peer(connected_pair, None))
244
+
245
+ return MeshExecutionResult(
246
+ global_options=global_options,
247
+ peers=peers,
248
+ )
@@ -0,0 +1,165 @@
1
+ import re
2
+ from collections.abc import Callable
3
+ from dataclasses import dataclass
4
+ from types import SimpleNamespace
5
+ from typing import Any, Sequence, Optional
6
+
7
+ MatchedArgs = SimpleNamespace
8
+
9
+
10
+ def identity(x: Any) -> Any:
11
+ return x
12
+
13
+
14
+ class MatchExpr:
15
+ def __init__(self, expr: Callable[[Any], Any] = identity):
16
+ self.expr = expr
17
+
18
+ def __getattr__(self, item: str):
19
+ return MatchExpr(lambda x: getattr(self.expr(x), item))
20
+
21
+ def __getitem__(self, item: Any):
22
+ return MatchExpr(lambda x: self.expr(x)[item])
23
+
24
+ def __eq__(self, other) -> "MatchExpr": # type: ignore[override] # https://github.com/python/mypy/issues/5951
25
+ if isinstance(other, MatchExpr):
26
+ return MatchExpr(lambda x: self.expr(x) == other.expr(x))
27
+ else:
28
+ return MatchExpr(lambda x: self.expr(x) == other)
29
+
30
+ def __ne__(self, other) -> "MatchExpr": # type: ignore[override] # https://github.com/python/mypy/issues/5951
31
+ if isinstance(other, MatchExpr):
32
+ return MatchExpr(lambda x: self.expr(x) != other.expr(x))
33
+ else:
34
+ return MatchExpr(lambda x: self.expr(x) != other)
35
+
36
+ def __lt__(self, other) -> "MatchExpr":
37
+ if isinstance(other, MatchExpr):
38
+ return MatchExpr(lambda x: self.expr(x) < other.expr(x))
39
+ else:
40
+ return MatchExpr(lambda x: self.expr(x) < other)
41
+
42
+ def __gt__(self, other) -> "MatchExpr":
43
+ if isinstance(other, MatchExpr):
44
+ return MatchExpr(lambda x: self.expr(x) > other.expr(x))
45
+ else:
46
+ return MatchExpr(lambda x: self.expr(x) > other)
47
+
48
+ def __le__(self, other) -> "MatchExpr":
49
+ if isinstance(other, MatchExpr):
50
+ return MatchExpr(lambda x: self.expr(x) <= other.expr(x))
51
+ else:
52
+ return MatchExpr(lambda x: self.expr(x) <= other)
53
+
54
+ def __ge__(self, other) -> "MatchExpr":
55
+ if isinstance(other, MatchExpr):
56
+ return MatchExpr(lambda x: self.expr(x) >= other.expr(x))
57
+ else:
58
+ return MatchExpr(lambda x: self.expr(x) >= other)
59
+
60
+ def __or__(self, other: "MatchExpr") -> "MatchExpr":
61
+ return MatchExpr(lambda x: self.expr(x) or other.expr(x))
62
+
63
+ def __and__(self, other: "MatchExpr") -> "MatchExpr":
64
+ return MatchExpr(lambda x: self.expr(x) and other.expr(x))
65
+
66
+ def cast_(self, type_: Callable[[Any], Any]) -> "MatchExpr":
67
+ return MatchExpr(lambda x: type_(self.expr(x)))
68
+
69
+ def in_(self, value: Any) -> "MatchExpr":
70
+ if isinstance(value, MatchExpr):
71
+ return MatchExpr(lambda x: self.expr(x) in value.expr(x))
72
+ else:
73
+ return MatchExpr(lambda x: self.expr(x) in value)
74
+
75
+
76
+ Match = MatchExpr()[0]
77
+ Left = MatchExpr()[0]
78
+ Right = MatchExpr()[1]
79
+
80
+
81
+ class PeerNameTemplate:
82
+ def __init__(self, raw_str):
83
+ self._str = str(raw_str)
84
+ self._regex, self._types = self._compile(self._str)
85
+
86
+ def __str__(self):
87
+ return self._str
88
+
89
+ @staticmethod
90
+ def _compile(value: str) -> tuple[re.Pattern[str], dict[str, type]]:
91
+ int_groups = re.findall(r"{(?P<group_name>\w+)}", value)
92
+ # '{name}' -> (?P<name>\d+)
93
+ regex_string = re.sub(r"{(?P<group_name>\w+)}", r"(?P<\g<group_name>>\\d+)", value)
94
+ # '{name:regex}' -> (?P<name>regex)
95
+ regex_string = re.sub(
96
+ r"{(?P<group_name>\w+):(?P<custom_regex>.*?)}", r"(?P<\g<group_name>>\g<custom_regex>)",
97
+ regex_string
98
+ )
99
+ pattern = re.compile(regex_string)
100
+ types: dict[str, type] = {
101
+ name: (int if name in int_groups else str)
102
+ for name in pattern.groupindex
103
+ }
104
+ return pattern, types
105
+
106
+ def match(self, hostname: str) -> Optional[dict[str, str]]:
107
+ reg_match = self._regex.fullmatch(hostname)
108
+ if reg_match:
109
+ return {
110
+ key: self._types[key](value)
111
+ for key, value in reg_match.groupdict().items()
112
+ }
113
+ return None
114
+
115
+
116
+ def match_safe(match_expressions: Sequence[MatchExpr], value: Any) -> bool:
117
+ for matcher in match_expressions:
118
+ try:
119
+ res = matcher.expr(value)
120
+ if not bool(res):
121
+ return False
122
+ except (TypeError, ValueError, AttributeError, KeyError, IndexError):
123
+ return False
124
+ return True
125
+
126
+
127
+ @dataclass
128
+ class SingleMatcher:
129
+ def __init__(self, rule: str, match_expressions: Sequence[MatchExpr]):
130
+ self.rule = PeerNameTemplate(rule)
131
+ self.match_expressions = match_expressions
132
+
133
+ def match_one(self, host: str) -> Optional[MatchedArgs]:
134
+ data = self.rule.match(host)
135
+ if data is None:
136
+ return None
137
+ args = MatchedArgs(**data)
138
+ if not match_safe(self.match_expressions, (args,)):
139
+ return None
140
+ return args
141
+
142
+
143
+ @dataclass
144
+ class PairMatcher:
145
+ def __init__(self, left_rule: str, right_rule: str, match_expressions: Sequence[MatchExpr]):
146
+ self.left_rule = PeerNameTemplate(left_rule)
147
+ self.right_rule = PeerNameTemplate(right_rule)
148
+ self.match_expressions = match_expressions
149
+
150
+ def match_pair(self, left: str, right: str) -> Optional[tuple[MatchedArgs, MatchedArgs]]:
151
+ left_args = self._match_host(self.left_rule, left)
152
+ if left_args is None:
153
+ return None
154
+ right_args = self._match_host(self.right_rule, right)
155
+ if right_args is None:
156
+ return None
157
+ if not match_safe(self.match_expressions, (left_args, right_args)):
158
+ return None
159
+ return left_args, right_args
160
+
161
+ def _match_host(self, rule: PeerNameTemplate, host: str) -> Optional[MatchedArgs]:
162
+ data = rule.match(host)
163
+ if data is None:
164
+ return None
165
+ return MatchedArgs(**data)
@@ -0,0 +1,84 @@
1
+ from dataclasses import dataclass
2
+ from ipaddress import ip_interface
3
+ from typing import Optional
4
+
5
+ from adaptix import Retort, loader, Chain, name_mapping
6
+
7
+ from .peer_models import PeerDTO
8
+ from ..bgp_models import GlobalOptions, VrfOptions, FamilyOptions, Peer, PeerGroup, ASN, PeerOptions
9
+ from ..storage import Device
10
+
11
+
12
+ @dataclass
13
+ class InterfaceChanges:
14
+ addr: str
15
+ lag: Optional[int] = None
16
+ lag_links_min: Optional[int] = None
17
+ svi: Optional[int] = None
18
+ subif: Optional[int] = None
19
+ vrf: Optional[str] = None
20
+
21
+ def __post_init__(self):
22
+ if self.lag is not None and self.svi is not None:
23
+ raise ValueError("Cannot use LAG and SVI together")
24
+ if self.svi is not None and self.subif is not None:
25
+ raise ValueError("Cannot use Subif and SVI together")
26
+
27
+
28
+ class ObjMapping:
29
+ def __init__(self, obj):
30
+ self.obj = obj
31
+
32
+ def __contains__(self, item):
33
+ return hasattr(self.obj, item)
34
+
35
+ def __getitem__(self, name):
36
+ return getattr(self.obj, name)
37
+
38
+ def get(self, name, default=None):
39
+ return getattr(self.obj, name, default)
40
+
41
+
42
+ retort = Retort(
43
+ recipe=[
44
+ loader(InterfaceChanges, ObjMapping, Chain.FIRST),
45
+ loader(ASN, ASN),
46
+ loader(GlobalOptions, ObjMapping, Chain.FIRST),
47
+ loader(VrfOptions, ObjMapping, Chain.FIRST),
48
+ loader(FamilyOptions, ObjMapping, Chain.FIRST),
49
+ loader(PeerOptions, ObjMapping, Chain.FIRST),
50
+ name_mapping(PeerOptions, map={
51
+ "local_as": "asnum",
52
+ }),
53
+ loader(list[PeerGroup], lambda x: list(x.values()), Chain.FIRST),
54
+ loader(PeerGroup, ObjMapping, Chain.FIRST),
55
+ name_mapping(PeerGroup, map={
56
+ "local_as": "asnum",
57
+ }),
58
+ ]
59
+ )
60
+
61
+ to_bgp_global_options = retort.get_loader(GlobalOptions)
62
+ to_interface_changes = retort.get_loader(InterfaceChanges)
63
+
64
+
65
+ def to_bgp_peer(local: PeerDTO, connected: PeerDTO, connected_device: Device, interface: Optional[str]) -> Peer:
66
+ options = retort.load(local, PeerOptions)
67
+ # TODO validate `lagg_links` before conversion
68
+ result = Peer(
69
+ addr=str(ip_interface(connected.addr).ip),
70
+ interface=interface,
71
+ remote_as=ASN(connected.asnum),
72
+ hostname=connected_device.hostname,
73
+ options=options,
74
+ )
75
+ # connected
76
+ result.vrf_name = getattr(connected, "vrf", result.vrf_name)
77
+ result.group_name = getattr(connected, "group_name", result.group_name)
78
+ result.description = getattr(connected, "description", result.description)
79
+ result.families = getattr(connected, "families", result.families)
80
+ # local
81
+ result.import_policy = getattr(connected, "import_policy", result.import_policy)
82
+ result.export_policy = getattr(connected, "export_policy", result.export_policy)
83
+ result.update_source = getattr(connected, "update_source", result.update_source)
84
+ return result
@@ -0,0 +1,98 @@
1
+ from typing import Literal, Annotated, Union, Optional
2
+
3
+ from .basemodel import BaseMeshModel, Concat
4
+ from ..bgp_models import BFDTimers
5
+
6
+ FamilyName = Literal["ipv4_unicast", "ipv6_unicast", "ipv4_labeled", "ipv6_labeled"]
7
+
8
+
9
+ class _SharedOptionsDTO(BaseMeshModel):
10
+ """
11
+ Options which can be set on connected pair or group of peers
12
+ """
13
+ add_path: bool
14
+ multipath: bool
15
+ advertise_irb: bool
16
+ send_labeled: bool
17
+ send_community: bool
18
+ bfd: bool
19
+ bfd_timers: BFDTimers
20
+
21
+
22
+ class MeshSession(_SharedOptionsDTO):
23
+ """
24
+ Options which are set on connected pair
25
+ """
26
+ asnum: Union[int, str]
27
+ vrf: str
28
+ families: Annotated[set[FamilyName], Concat()]
29
+ group_name: str
30
+
31
+ bmp_monitor: bool
32
+
33
+ import_policy: str
34
+ export_policy: str
35
+
36
+
37
+ class _OptionsDTO(_SharedOptionsDTO):
38
+ """
39
+ Options which can be set on group of peers or peer itself
40
+ """
41
+ unnumbered: bool
42
+ rr_client: bool
43
+ next_hop_self: bool
44
+ extended_next_hop: bool
45
+ send_lcommunity: bool
46
+ send_extcommunity: bool
47
+ import_limit: bool
48
+ teardown_timeout: bool
49
+ redistribute: bool
50
+ passive: bool
51
+ mtu_discovery: bool
52
+ advertise_inactive: bool
53
+ advertise_bgp_static: bool
54
+ allowas_in: bool
55
+ auth_key: bool
56
+ multihop: bool
57
+ multihop_no_nexthop_change: bool
58
+ af_no_install: bool
59
+ rib: bool
60
+ resolve_vpn: bool
61
+ af_rib_group: Optional[str]
62
+ af_loops: int
63
+ hold_time: int
64
+ listen_network: bool
65
+ remove_private: bool
66
+ as_override: bool
67
+ aigp: bool
68
+ no_prepend: bool
69
+ no_explicit_null: bool
70
+ uniq_iface: bool
71
+ advertise_peer_as: bool
72
+ connect_retry: bool
73
+ advertise_external: bool
74
+ listen_only: bool
75
+ soft_reconfiguration_inbound: bool
76
+ not_active: bool
77
+ mtu: int
78
+
79
+
80
+ class PeerDTO(MeshSession, _OptionsDTO):
81
+ pod: int
82
+ addr: str
83
+ description: str
84
+
85
+ subif: int
86
+ lag: Optional[int]
87
+ lag_links_min: Optional[int]
88
+ svi: Optional[int]
89
+
90
+ group_name: str
91
+
92
+
93
+ class MeshPeerGroup(_OptionsDTO):
94
+ name: str
95
+ remote_as: Union[int, str]
96
+ internal_name: str
97
+ update_source: str
98
+ description: str