annet 0.16.26__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.
- annet/adapters/file/provider.py +28 -10
- annet/adapters/netbox/v37/storage.py +1 -1
- annet/annlib/netdev/devdb/data/devdb.json +3 -2
- annet/bgp_models.py +28 -0
- annet/mesh/__init__.py +4 -0
- annet/mesh/basemodel.py +5 -0
- annet/mesh/device_models.py +2 -0
- annet/mesh/executor.py +90 -66
- annet/mesh/peer_models.py +3 -3
- annet/mesh/port_processor.py +18 -0
- annet/mesh/registry.py +12 -4
- annet/rpl/match_builder.py +30 -9
- annet/rpl/routemap.py +5 -3
- annet/rpl/statement_builder.py +31 -7
- annet/rpl_generators/__init__.py +24 -0
- annet/rpl_generators/aspath.py +57 -0
- annet/rpl_generators/community.py +242 -0
- annet/rpl_generators/cumulus_frr.py +458 -0
- annet/rpl_generators/entities.py +70 -0
- annet/rpl_generators/execute.py +12 -0
- annet/rpl_generators/policy.py +676 -0
- annet/rpl_generators/prefix_lists.py +158 -0
- annet/rpl_generators/rd.py +40 -0
- {annet-0.16.26.dist-info → annet-0.16.27.dist-info}/METADATA +2 -2
- {annet-0.16.26.dist-info → annet-0.16.27.dist-info}/RECORD +35 -24
- annet_generators/rpl_example/__init__.py +3 -5
- annet_generators/rpl_example/generator.py +127 -0
- annet_generators/rpl_example/items.py +21 -31
- annet_generators/rpl_example/mesh.py +9 -0
- annet_generators/rpl_example/route_policy.py +43 -9
- annet_generators/rpl_example/policy_generator.py +0 -233
- {annet-0.16.26.dist-info → annet-0.16.27.dist-info}/AUTHORS +0 -0
- {annet-0.16.26.dist-info → annet-0.16.27.dist-info}/LICENSE +0 -0
- {annet-0.16.26.dist-info → annet-0.16.27.dist-info}/WHEEL +0 -0
- {annet-0.16.26.dist-info → annet-0.16.27.dist-info}/entry_points.txt +0 -0
- {annet-0.16.26.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)
|