annet 0.16.25__py3-none-any.whl → 0.16.27__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/file/provider.py +28 -10
  2. annet/adapters/netbox/v37/storage.py +1 -1
  3. annet/annlib/netdev/devdb/data/devdb.json +3 -2
  4. annet/annlib/patching.py +50 -14
  5. annet/bgp_models.py +28 -0
  6. annet/mesh/__init__.py +4 -0
  7. annet/mesh/basemodel.py +5 -0
  8. annet/mesh/device_models.py +2 -0
  9. annet/mesh/executor.py +90 -66
  10. annet/mesh/peer_models.py +3 -3
  11. annet/mesh/port_processor.py +18 -0
  12. annet/mesh/registry.py +12 -4
  13. annet/rpl/match_builder.py +30 -9
  14. annet/rpl/routemap.py +5 -3
  15. annet/rpl/statement_builder.py +31 -7
  16. annet/rpl_generators/__init__.py +24 -0
  17. annet/rpl_generators/aspath.py +57 -0
  18. annet/rpl_generators/community.py +242 -0
  19. annet/rpl_generators/cumulus_frr.py +458 -0
  20. annet/rpl_generators/entities.py +70 -0
  21. annet/rpl_generators/execute.py +12 -0
  22. annet/rpl_generators/policy.py +676 -0
  23. annet/rpl_generators/prefix_lists.py +158 -0
  24. annet/rpl_generators/rd.py +40 -0
  25. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/METADATA +2 -2
  26. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/RECORD +36 -25
  27. annet_generators/rpl_example/__init__.py +3 -5
  28. annet_generators/rpl_example/generator.py +127 -0
  29. annet_generators/rpl_example/items.py +21 -31
  30. annet_generators/rpl_example/mesh.py +9 -0
  31. annet_generators/rpl_example/route_policy.py +43 -9
  32. annet_generators/rpl_example/policy_generator.py +0 -233
  33. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/AUTHORS +0 -0
  34. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/LICENSE +0 -0
  35. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/WHEEL +0 -0
  36. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/entry_points.txt +0 -0
  37. {annet-0.16.25.dist-info → annet-0.16.27.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,458 @@
1
+ from abc import abstractmethod, ABC
2
+ from collections.abc import Sequence
3
+ from ipaddress import ip_interface
4
+ from typing import Any, Literal, Iterable, Iterator, Optional, cast
5
+
6
+ from annet.rpl import (
7
+ RoutingPolicy, PrefixMatchValue, SingleCondition, MatchField, RoutingPolicyStatement, ResultType,
8
+ SingleAction, ConditionOperator, ThenField, ActionType,
9
+ )
10
+ from annet.rpl.statement_builder import NextHopActionValue, AsPathActionValue, CommunityActionValue
11
+ from .aspath import get_used_as_path_filters
12
+ from .community import get_used_united_community_lists
13
+ from .entities import (
14
+ AsPathFilter, IpPrefixList, mangle_ranged_prefix_list_name, CommunityList, CommunityLogic, CommunityType,
15
+ mangle_united_community_list_name,
16
+ )
17
+ from .prefix_lists import get_used_prefix_lists
18
+
19
+ FRR_RESULT_MAP = {
20
+ ResultType.ALLOW: "permit",
21
+ ResultType.DENY: "deny",
22
+ ResultType.NEXT: "permit",
23
+ }
24
+ FRR_MATCH_COMMAND_MAP: dict[str, str] = {
25
+ MatchField.as_path_filter: "as-path {option_value}",
26
+ MatchField.metric: "metric {option_value}",
27
+ MatchField.protocol: "source-protocol {option_value}",
28
+ MatchField.interface: "interface {option_value}",
29
+ # unsupported: as_path_length
30
+ # unsupported: rd
31
+ }
32
+ FRR_THEN_COMMAND_MAP: dict[str, str] = {
33
+ ThenField.local_pref: "local-preference {option_value}",
34
+ ThenField.metric_type: "metric-type {option_value}",
35
+ ThenField.origin: "origin {option_value}",
36
+ ThenField.tag: "tag {option_value}",
37
+ # unsupported: resolution
38
+ # unsupported: rpki_valid_state
39
+ # unsupported: mpls-label
40
+ }
41
+
42
+ FRR_INDENT = " "
43
+
44
+
45
+ class CumulusPolicyGenerator(ABC):
46
+ @abstractmethod
47
+ def get_policies(self, device: Any) -> list[RoutingPolicy]:
48
+ raise NotImplementedError()
49
+
50
+ @abstractmethod
51
+ def get_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
52
+ raise NotImplementedError()
53
+
54
+ @abstractmethod
55
+ def get_community_lists(self, device: Any) -> list[CommunityList]:
56
+ raise NotImplementedError()
57
+
58
+ @abstractmethod
59
+ def get_as_path_filters(self, device: Any) -> Sequence[AsPathFilter]:
60
+ raise NotImplementedError
61
+
62
+ def generate_cumulus_rpl(self, device: Any) -> Iterator[Sequence[str]]:
63
+ policies = self.get_policies(device)
64
+ communities = {c.name: c for c in self.get_community_lists(device)}
65
+ yield from self._cumulus_as_path_filters(device, policies)
66
+ yield from self._cumulus_communities(device, communities, policies)
67
+ yield from self._cumulus_prefix_lists(device, policies)
68
+ yield from self._cumulus_policy_config(device, communities, policies)
69
+
70
+ def _cumulus_as_path_filters(
71
+ self,
72
+ device: Any,
73
+ policies: list[RoutingPolicy],
74
+ ):
75
+ as_path_filters = get_used_as_path_filters(self.get_as_path_filters(device), policies)
76
+ if not as_path_filters:
77
+ return
78
+ for as_path_filter in as_path_filters:
79
+ values = "_".join(x for x in as_path_filter.filters if x != ".*")
80
+ yield "ip as-path access-list", as_path_filter.name, "permit", f"_{values}_"
81
+
82
+ def _cumulus_prefix_list(
83
+ self,
84
+ name: str,
85
+ ip_type: Literal["ipv6", "ip"],
86
+ match: PrefixMatchValue,
87
+ plist: IpPrefixList,
88
+ ) -> Iterable[Sequence[str]]:
89
+ for i, prefix in enumerate(plist.members):
90
+ addr_mask = ip_interface(prefix)
91
+ yield (
92
+ ip_type,
93
+ "prefix-list",
94
+ name,
95
+ f"seq {i * 10 + 5}",
96
+ "permit", f"{addr_mask.ip}/{addr_mask.network.prefixlen}",
97
+ ) + (
98
+ ("ge", str(match.greater_equal)) if match.greater_equal is not None else ()
99
+ ) + (
100
+ ("le", str(match.less_equal)) if match.less_equal is not None else ()
101
+ )
102
+
103
+ def _cumulus_prefix_lists(self, device: Any, policies: list[RoutingPolicy]) -> Iterable[Sequence[str]]:
104
+ plists = {p.name: p for p in get_used_prefix_lists(
105
+ prefix_lists=self.get_prefix_lists(device),
106
+ policies=policies,
107
+ )}
108
+ if not plists.values():
109
+ return
110
+
111
+ precessed_names = set()
112
+ for policy in policies:
113
+ for statement in policy.statements:
114
+ cond: SingleCondition[PrefixMatchValue]
115
+ for cond in statement.match.find_all(MatchField.ip_prefix):
116
+ for name in cond.value.names:
117
+ mangled_name = mangle_ranged_prefix_list_name(
118
+ name=name,
119
+ greater_equal=cond.value.greater_equal,
120
+ less_equal=cond.value.less_equal,
121
+ )
122
+ if mangled_name in precessed_names:
123
+ continue
124
+ yield from self._cumulus_prefix_list(mangled_name, "ip", cond.value, plists[name])
125
+ precessed_names.add(mangled_name)
126
+ for cond in statement.match.find_all(MatchField.ipv6_prefix):
127
+ for name in cond.value.names:
128
+ mangled_name = mangle_ranged_prefix_list_name(
129
+ name=name,
130
+ greater_equal=cond.value.greater_equal,
131
+ less_equal=cond.value.less_equal,
132
+ )
133
+ if mangled_name in precessed_names:
134
+ continue
135
+ yield from self._cumulus_prefix_list(mangled_name, "ipv6", cond.value, plists[name])
136
+ precessed_names.add(mangled_name)
137
+ yield "!"
138
+
139
+ def get_used_united_community_lists(
140
+ self, communities: dict[str, CommunityList], policies: list[RoutingPolicy],
141
+ ) -> list[list[CommunityList]]:
142
+ return get_used_united_community_lists(communities=communities.values(), policies=policies)
143
+
144
+ def _cumulus_community(
145
+ self, name: str, cmd: str, member: str, use_regex: bool,
146
+ ) -> Iterable[Sequence[str]]:
147
+ if use_regex:
148
+ yield (
149
+ cmd,
150
+ "expanded",
151
+ name,
152
+ "permit",
153
+ member,
154
+ )
155
+ else:
156
+ yield (
157
+ cmd,
158
+ "standard",
159
+ name,
160
+ "permit",
161
+ member,
162
+ )
163
+
164
+ def _cumulus_communities(
165
+ self,
166
+ device: Any,
167
+ communities: dict[str, CommunityList],
168
+ policies: list[RoutingPolicy],
169
+ ) -> Iterable[Sequence[str]]:
170
+ """ BGP community-lists section configuration """
171
+ community_unions = self.get_used_united_community_lists(communities, policies)
172
+ if not community_unions:
173
+ return
174
+ for community_list_union in community_unions:
175
+ name = mangle_united_community_list_name([c.name for c in community_list_union])
176
+
177
+ for clist in community_list_union:
178
+ if clist.type is CommunityType.BASIC:
179
+ member_prefix = ""
180
+ cmd = "bgp community-list"
181
+ elif clist.type is CommunityType.RT:
182
+ member_prefix = "rt "
183
+ cmd = "bgp extcommunity"
184
+ elif clist.type is CommunityType.SOO:
185
+ member_prefix = "soo "
186
+ cmd = "bgp extcommunity"
187
+ elif clist.type is CommunityType.LARGE:
188
+ member_prefix = ""
189
+ cmd = "bgp large-community-list"
190
+ else:
191
+ raise NotImplementedError(f"Community type {clist.type} is not supported on Cumulus")
192
+
193
+ if clist.logic == CommunityLogic.AND:
194
+ if clist.use_regex:
195
+ if len(clist.members) > 1:
196
+ raise NotImplementedError("Multiple regexes with AND logic are not supported on Cumulus")
197
+ member = member_prefix + clist.members[0]
198
+ else:
199
+ member = " ".join(f"{member_prefix}{m}" for m in clist.members)
200
+ yield from self._cumulus_community(
201
+ name=name, cmd=cmd, member=member, use_regex=clist.use_regex,
202
+ )
203
+ else:
204
+ for member_value in clist.members:
205
+ yield from self._cumulus_community(
206
+ name=name, cmd=cmd, member=member_prefix + member_value, use_regex=clist.use_regex,
207
+ )
208
+ yield "!"
209
+
210
+ def _get_match_community_names(self, condition: SingleCondition[Sequence[str]]) -> Sequence[str]:
211
+ if condition.operator is ConditionOperator.HAS_ANY:
212
+ return [mangle_united_community_list_name(condition.value)]
213
+ else:
214
+ return condition.value
215
+
216
+ def _cumulus_policy_match(
217
+ self,
218
+ device: Any,
219
+ condition: SingleCondition[Any],
220
+ ) -> Iterator[Sequence[str]]:
221
+ if condition.field == MatchField.community:
222
+ for comm_name in self._get_match_community_names(condition):
223
+ yield "match community", comm_name
224
+ return
225
+ if condition.field == MatchField.large_community:
226
+ for comm_name in self._get_match_community_names(condition):
227
+ yield "match large-community-list", comm_name
228
+ return
229
+ if condition.field == MatchField.extcommunity_rt:
230
+ for comm_name in self._get_match_community_names(condition):
231
+ yield "match extcommunity", comm_name
232
+ return
233
+ if condition.field == MatchField.extcommunity_soo:
234
+ for comm_name in self._get_match_community_names(condition):
235
+ yield "match extcommunity", comm_name
236
+ return
237
+ if condition.field == MatchField.ip_prefix:
238
+ for name in condition.value.names:
239
+ mangled_name = mangle_ranged_prefix_list_name(
240
+ name=name,
241
+ greater_equal=condition.value.greater_equal,
242
+ less_equal=condition.value.less_equal,
243
+ )
244
+ yield "match", "ip address prefix-list", mangled_name
245
+ return
246
+ if condition.field == MatchField.ipv6_prefix:
247
+ for name in condition.value.names:
248
+ mangled_name = mangle_ranged_prefix_list_name(
249
+ name=name,
250
+ greater_equal=condition.value.greater_equal,
251
+ less_equal=condition.value.less_equal,
252
+ )
253
+ yield "match", "ipv6 address prefix-list", mangled_name
254
+ return
255
+ if condition.operator is not ConditionOperator.EQ:
256
+ raise NotImplementedError(
257
+ f"`{condition.field}` with operator {condition.operator} is not supported for Cumulus",
258
+ )
259
+ if condition.field not in FRR_MATCH_COMMAND_MAP:
260
+ raise NotImplementedError(f"Match using `{condition.field}` is not supported for Cumulus")
261
+ cmd = FRR_MATCH_COMMAND_MAP[condition.field]
262
+ yield "match", cmd.format(option_value=condition.value)
263
+
264
+ def _cumulus_then_community(
265
+ self,
266
+ communities: dict[str, CommunityList],
267
+ device: Any,
268
+ action: SingleAction[CommunityActionValue],
269
+ ) -> Iterator[Sequence[str]]:
270
+ if action.value.replaced is not None:
271
+ if action.value.added or action.value.replaced:
272
+ raise NotImplementedError(
273
+ "Cannot set community together with add/replace on cumulus",
274
+ )
275
+ members = [m for name in action.value.replaced for m in communities[name].members]
276
+ if members:
277
+ yield "set", "community", *members
278
+ else:
279
+ yield "set", "community", "none"
280
+ if action.value.added:
281
+ members = [m for name in action.value.added for m in communities[name].members]
282
+ yield "set", "community", *members, "additive"
283
+ for community_name in action.value.removed:
284
+ yield "set comm-list", community_name, "delete"
285
+
286
+ def _cumulus_then_large_community(
287
+ self,
288
+ communities: dict[str, CommunityList],
289
+ device: Any,
290
+ action: SingleAction[CommunityActionValue],
291
+ ) -> Iterator[Sequence[str]]:
292
+ if action.value.replaced is not None:
293
+ raise NotImplementedError("Replacing Large community is not supported for Cumulus")
294
+ for community_name in action.value.added:
295
+ yield "set", "large-community", community_name, "additive"
296
+ for community_name in action.value.removed:
297
+ raise NotImplementedError("Large-community remove is not supported for Cumulus")
298
+
299
+ def _cumulus_then_rt_community(
300
+ self,
301
+ communities: dict[str, CommunityList],
302
+ device: Any,
303
+ action: SingleAction[CommunityActionValue],
304
+ ) -> Iterator[Sequence[str]]:
305
+ if action.value.replaced is not None:
306
+ raise NotImplementedError("Replacing RT extcommunity is not supported for Cumulus")
307
+ for community_name in action.value.added:
308
+ yield "set", "extcommunity rt", community_name, "additive"
309
+ for community_name in action.value.removed:
310
+ raise NotImplementedError("RT extcommunity remove is not supported for Cumulus")
311
+
312
+ def _cumulus_then_soo_community(
313
+ self,
314
+ communities: dict[str, CommunityList],
315
+ device: Any,
316
+ action: SingleAction[CommunityActionValue],
317
+ ) -> Iterator[Sequence[str]]:
318
+ if action.value.replaced is not None:
319
+ raise NotImplementedError("Replacing SOO extcommunity is not supported for Cumulus")
320
+ for community_name in action.value.added:
321
+ yield "set", "extcommunity soo", community_name, "additive"
322
+ for community_name in action.value.removed:
323
+ raise NotImplementedError("SOO extcommunity remove is not supported for Cumulus")
324
+
325
+ def _cumulus_then_as_path(
326
+ self,
327
+ device: Any,
328
+ action: SingleAction[AsPathActionValue],
329
+ ) -> Iterator[Sequence[str]]:
330
+ if action.value.prepend:
331
+ for path_item in action.value.prepend:
332
+ yield "set as-path prepend", path_item
333
+ if action.value.expand:
334
+ raise NotImplementedError("asp_path.expand is not supported for Cumulus")
335
+ if action.value.delete:
336
+ for path_item in action.value.delete:
337
+ yield "set as-path exclude", path_item
338
+ if action.value.set is not None:
339
+ yield "set as-path exclude all"
340
+ for path_item in action.value.set:
341
+ yield "set as-path prepend", path_item
342
+ if action.value.expand_last_as:
343
+ yield "set as-path prepend last-as", action.value.expand_last_as
344
+
345
+ def _cumulus_policy_then(
346
+ self,
347
+ communities: dict[str, CommunityList],
348
+ device: Any,
349
+ action: SingleAction[Any],
350
+ ) -> Iterator[Sequence[str]]:
351
+ if action.field == ThenField.community:
352
+ yield from self._cumulus_then_community(
353
+ communities,
354
+ device,
355
+ cast(SingleAction[CommunityActionValue], action),
356
+ )
357
+ return
358
+ if action.field == ThenField.large_community:
359
+ yield from self._cumulus_then_large_community(
360
+ communities,
361
+ device,
362
+ cast(SingleAction[CommunityActionValue], action),
363
+ )
364
+ return
365
+ if action.field == ThenField.extcommunity_rt:
366
+ yield from self._cumulus_then_rt_community(
367
+ communities,
368
+ device,
369
+ cast(SingleAction[CommunityActionValue], action),
370
+ )
371
+ return
372
+ if action.field == ThenField.extcommunity_soo:
373
+ yield from self._cumulus_then_soo_community(
374
+ communities,
375
+ device,
376
+ cast(SingleAction[CommunityActionValue], action),
377
+ )
378
+ return
379
+ if action.field == ThenField.metric:
380
+ if action.type is ActionType.ADD:
381
+ yield "set", f"metric +{action.value}"
382
+ elif action.type is ActionType.REMOVE:
383
+ yield "set", f"metric -{action.value}"
384
+ elif action.type is ActionType.SET:
385
+ yield "set", f"metric {action.value}"
386
+ else:
387
+ raise NotImplementedError(f"Action type {action.type} for metric is not supported for Cumulus")
388
+ return
389
+ if action.field == ThenField.as_path:
390
+ yield from self._cumulus_then_as_path(device, action)
391
+ return
392
+ if action.field == ThenField.next_hop:
393
+ next_hop_action_value = cast(NextHopActionValue, action.value)
394
+ if next_hop_action_value.target == "self":
395
+ yield "set", "metric 1"
396
+ elif next_hop_action_value.target == "discard":
397
+ pass
398
+ elif next_hop_action_value.target == "peer":
399
+ pass
400
+ elif next_hop_action_value.target == "ipv4_addr":
401
+ yield "set", f"ip next-hop {next_hop_action_value.addr}"
402
+ elif next_hop_action_value.target == "ipv6_addr":
403
+ yield "set", f"ipv6 next-hop {next_hop_action_value.addr}"
404
+ elif next_hop_action_value.target == "mapped_ipv4":
405
+ yield "set", "ipv6 next-hop ::FFFF:{next_hop_action_value.addr}"
406
+ else:
407
+ raise NotImplementedError(
408
+ f"Next_hop target {next_hop_action_value.target} is not supported for Cumulus")
409
+ return
410
+
411
+ if action.type is not ActionType.SET:
412
+ raise NotImplementedError(f"Action type {action.type} for `{action.field}` is not supported for Cumulus")
413
+ if action.field not in FRR_THEN_COMMAND_MAP:
414
+ raise NotImplementedError(f"Then action using `{action.field}` is not supported for Cumulus")
415
+ cmd = FRR_THEN_COMMAND_MAP[action.field]
416
+ yield "set", cmd.format(option_value=action.value)
417
+
418
+ def _cumulus_policy_statement(
419
+ self,
420
+ communities: dict[str, CommunityList],
421
+ device: Any,
422
+ policy: RoutingPolicy,
423
+ statement: RoutingPolicyStatement,
424
+ ) -> Iterable[Sequence[str]]:
425
+ yield "route-map", policy.name, FRR_RESULT_MAP[statement.result], str(statement.number)
426
+
427
+ for condition in statement.match:
428
+ for row in self._cumulus_policy_match(device, condition):
429
+ yield FRR_INDENT, *row
430
+ for action in statement.then:
431
+ for row in self._cumulus_policy_then(communities, device, action):
432
+ yield FRR_INDENT, *row
433
+ if statement.result is ResultType.NEXT:
434
+ yield FRR_INDENT, "on-match next"
435
+ yield "!"
436
+
437
+ def _cumulus_policy_config(
438
+ self,
439
+ device: Any,
440
+ communities: dict[str, CommunityList],
441
+ policies: list[RoutingPolicy],
442
+ ) -> Iterable[Sequence[str]]:
443
+ """ Route maps configuration """
444
+
445
+ for policy in policies:
446
+ applied_stmts: dict[int, Optional[str]] = {}
447
+ for statement in policy.statements:
448
+ if statement.number is None:
449
+ raise RuntimeError(
450
+ f"Statement number should not be empty on Cumulus (found for policy: {policy.name})"
451
+ )
452
+
453
+ if statement.number in applied_stmts:
454
+ raise RuntimeError(
455
+ f"Multiple statements have same number {statement.number} for policy `{policy.name}`: "
456
+ f"`{statement.name}` and `{applied_stmts[statement.number]}`")
457
+ yield from self._cumulus_policy_statement(communities, device, policy, statement)
458
+ applied_stmts[statement.number] = statement.name
@@ -0,0 +1,70 @@
1
+ from collections.abc import Sequence
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Optional
5
+
6
+
7
+ class CommunityLogic(Enum):
8
+ AND = "AND"
9
+ OR = "OR"
10
+
11
+
12
+ class CommunityType(Enum):
13
+ BASIC = "BASIC"
14
+ RT = "RT"
15
+ SOO = "SOO"
16
+ COST = "COST"
17
+ LARGE = "LARGE"
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class CommunityList:
22
+ name: str
23
+ members: Sequence[str]
24
+ type: CommunityType = CommunityType.BASIC
25
+ logic: CommunityLogic = CommunityLogic.OR
26
+ use_regex: bool = False
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class RDFilter:
31
+ name: str
32
+ number: int
33
+ members: Sequence[str]
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class AsPathFilter:
38
+ name: str
39
+ filters: Sequence[str]
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class IpPrefixList:
44
+ name: str
45
+ members: Sequence[str]
46
+
47
+
48
+ def arista_well_known_community(community: str) -> str:
49
+ if community == "65535:0":
50
+ return "GSHUT"
51
+ return community
52
+
53
+
54
+ def mangle_united_community_list_name(values: Sequence[str]) -> str:
55
+ """Name for a list used as HAS_ANY between multiple lists"""
56
+ return "_OR_".join(values)
57
+
58
+
59
+ def mangle_ranged_prefix_list_name(name: str, greater_equal: Optional[int], less_equal: Optional[int]) -> str:
60
+ if greater_equal is less_equal is None:
61
+ return name
62
+ if greater_equal is None:
63
+ ge_str = "unset"
64
+ else:
65
+ ge_str = str(greater_equal)
66
+ if less_equal is None:
67
+ le_str = "unset"
68
+ else:
69
+ le_str = str(less_equal)
70
+ return f"{name}_{ge_str}_{le_str}"
@@ -0,0 +1,12 @@
1
+ from typing import TypeVar
2
+
3
+ from annet.bgp_models import extract_policies
4
+ from annet.mesh import MeshExecutor
5
+ from annet.rpl import RouteMap, RoutingPolicy
6
+
7
+ DeviceT = TypeVar("DeviceT")
8
+
9
+
10
+ def get_policies(routemap: RouteMap[DeviceT], mesh_executor: MeshExecutor, device: DeviceT) -> list[RoutingPolicy]:
11
+ allowed_policies = extract_policies(mesh_executor.execute_for(device))
12
+ return routemap.apply(device, allowed_policies)