annet 1.1.2__py3-none-any.whl → 2.0.1__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/netbox/provider.py +2 -1
- annet/annet.py +2 -1
- annet/annlib/filter_acl.py +6 -2
- annet/api/__init__.py +34 -28
- annet/diff.py +71 -2
- annet/gen.py +0 -46
- annet/output.py +9 -2
- annet/rpl/__init__.py +2 -1
- annet/rpl/match_builder.py +10 -6
- annet/rpl/statement_builder.py +17 -2
- annet/rpl_generators/__init__.py +3 -1
- annet/rpl_generators/cumulus_frr.py +65 -53
- annet/rpl_generators/entities.py +80 -26
- annet/rpl_generators/policy.py +159 -55
- annet/rpl_generators/prefix_lists.py +36 -81
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/METADATA +1 -1
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/RECORD +23 -23
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/WHEEL +1 -1
- annet_generators/rpl_example/items.py +3 -3
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/AUTHORS +0 -0
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/LICENSE +0 -0
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/entry_points.txt +0 -0
- {annet-1.1.2.dist-info → annet-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from abc import abstractmethod, ABC
|
|
2
|
+
from collections import defaultdict
|
|
2
3
|
from collections.abc import Sequence
|
|
3
4
|
from ipaddress import ip_interface
|
|
4
5
|
from typing import Any, Literal, Iterable, Iterator, Optional, cast
|
|
@@ -12,9 +13,9 @@ from .aspath import get_used_as_path_filters
|
|
|
12
13
|
from .community import get_used_united_community_lists
|
|
13
14
|
from .entities import (
|
|
14
15
|
AsPathFilter, IpPrefixList, CommunityList, CommunityLogic, CommunityType,
|
|
15
|
-
mangle_united_community_list_name, PrefixListNameGenerator,
|
|
16
|
+
mangle_united_community_list_name, PrefixListNameGenerator, group_community_members,
|
|
16
17
|
)
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
|
|
19
20
|
FRR_RESULT_MAP = {
|
|
20
21
|
ResultType.ALLOW: "permit",
|
|
@@ -51,12 +52,6 @@ class CumulusPolicyGenerator(ABC):
|
|
|
51
52
|
def get_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
|
|
52
53
|
raise NotImplementedError()
|
|
53
54
|
|
|
54
|
-
def get_used_prefix_lists(self, device: Any, name_generator: PrefixListNameGenerator) -> Sequence[IpPrefixList]:
|
|
55
|
-
return get_used_prefix_lists(
|
|
56
|
-
prefix_lists=self.get_prefix_lists(device),
|
|
57
|
-
name_generator=name_generator,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
55
|
@abstractmethod
|
|
61
56
|
def get_community_lists(self, device: Any) -> list[CommunityList]:
|
|
62
57
|
raise NotImplementedError()
|
|
@@ -66,8 +61,9 @@ class CumulusPolicyGenerator(ABC):
|
|
|
66
61
|
raise NotImplementedError
|
|
67
62
|
|
|
68
63
|
def generate_cumulus_rpl(self, device: Any) -> Iterator[Sequence[str]]:
|
|
64
|
+
prefix_lists = self.get_prefix_lists(device)
|
|
69
65
|
policies = self.get_policies(device)
|
|
70
|
-
prefix_list_name_generator =
|
|
66
|
+
prefix_list_name_generator = PrefixListNameGenerator(prefix_lists, policies)
|
|
71
67
|
|
|
72
68
|
communities = {c.name: c for c in self.get_community_lists(device)}
|
|
73
69
|
yield from self._cumulus_as_path_filters(device, policies)
|
|
@@ -89,60 +85,46 @@ class CumulusPolicyGenerator(ABC):
|
|
|
89
85
|
|
|
90
86
|
def _cumulus_prefix_list(
|
|
91
87
|
self,
|
|
92
|
-
name: str,
|
|
93
88
|
ip_type: Literal["ipv6", "ip"],
|
|
94
|
-
match: PrefixMatchValue,
|
|
95
89
|
plist: IpPrefixList,
|
|
96
90
|
) -> Iterable[Sequence[str]]:
|
|
97
|
-
for i,
|
|
98
|
-
|
|
91
|
+
for i, m in enumerate(plist.members):
|
|
92
|
+
ge, le = m.or_longer
|
|
99
93
|
yield (
|
|
100
94
|
ip_type,
|
|
101
95
|
"prefix-list",
|
|
102
|
-
name,
|
|
96
|
+
plist.name,
|
|
103
97
|
f"seq {i * 5 + 5}",
|
|
104
|
-
"permit",
|
|
98
|
+
"permit", str(m.prefix),
|
|
105
99
|
) + (
|
|
106
|
-
("ge", str(
|
|
100
|
+
("ge", str(ge)) if ge is not None else ()
|
|
107
101
|
) + (
|
|
108
|
-
("le", str(
|
|
102
|
+
("le", str(le)) if le is not None else ()
|
|
109
103
|
)
|
|
110
104
|
|
|
111
105
|
def _cumulus_prefix_lists(
|
|
112
106
|
self, device: Any,
|
|
113
107
|
policies: list[RoutingPolicy],
|
|
114
|
-
|
|
108
|
+
name_generator: PrefixListNameGenerator,
|
|
115
109
|
) -> Iterable[Sequence[str]]:
|
|
116
|
-
|
|
117
|
-
if not plists.values():
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
precessed_names = set()
|
|
110
|
+
processed_names = set()
|
|
121
111
|
for policy in policies:
|
|
122
112
|
for statement in policy.statements:
|
|
123
113
|
cond: SingleCondition[PrefixMatchValue]
|
|
124
114
|
for cond in statement.match.find_all(MatchField.ip_prefix):
|
|
125
115
|
for name in cond.value.names:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
greater_equal=cond.value.greater_equal,
|
|
129
|
-
less_equal=cond.value.less_equal,
|
|
130
|
-
)
|
|
131
|
-
if mangled_name in precessed_names:
|
|
116
|
+
plist = name_generator.get_prefix(name, cond.value)
|
|
117
|
+
if plist.name in processed_names:
|
|
132
118
|
continue
|
|
133
|
-
yield from self._cumulus_prefix_list(
|
|
134
|
-
|
|
119
|
+
yield from self._cumulus_prefix_list("ip", plist)
|
|
120
|
+
processed_names.add(plist.name)
|
|
135
121
|
for cond in statement.match.find_all(MatchField.ipv6_prefix):
|
|
136
122
|
for name in cond.value.names:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
greater_equal=cond.value.greater_equal,
|
|
140
|
-
less_equal=cond.value.less_equal,
|
|
141
|
-
)
|
|
142
|
-
if mangled_name in precessed_names:
|
|
123
|
+
plist = name_generator.get_prefix(name, cond.value)
|
|
124
|
+
if plist.name in processed_names:
|
|
143
125
|
continue
|
|
144
|
-
yield from self._cumulus_prefix_list(
|
|
145
|
-
|
|
126
|
+
yield from self._cumulus_prefix_list("ipv6", plist)
|
|
127
|
+
processed_names.add(plist.name)
|
|
146
128
|
yield "!"
|
|
147
129
|
|
|
148
130
|
def get_used_united_community_lists(
|
|
@@ -231,7 +213,7 @@ class CumulusPolicyGenerator(ABC):
|
|
|
231
213
|
self,
|
|
232
214
|
device: Any,
|
|
233
215
|
condition: SingleCondition[Any],
|
|
234
|
-
|
|
216
|
+
name_generator: PrefixListNameGenerator,
|
|
235
217
|
) -> Iterator[Sequence[str]]:
|
|
236
218
|
if condition.field == MatchField.community:
|
|
237
219
|
for comm_name in self._get_match_community_names(condition):
|
|
@@ -251,21 +233,13 @@ class CumulusPolicyGenerator(ABC):
|
|
|
251
233
|
return
|
|
252
234
|
if condition.field == MatchField.ip_prefix:
|
|
253
235
|
for name in condition.value.names:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
greater_equal=condition.value.greater_equal,
|
|
257
|
-
less_equal=condition.value.less_equal,
|
|
258
|
-
)
|
|
259
|
-
yield "match", "ip address prefix-list", mangled_name
|
|
236
|
+
plist = name_generator.get_prefix(name, condition.value)
|
|
237
|
+
yield "match", "ip address prefix-list", plist.name
|
|
260
238
|
return
|
|
261
239
|
if condition.field == MatchField.ipv6_prefix:
|
|
262
240
|
for name in condition.value.names:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
greater_equal=condition.value.greater_equal,
|
|
266
|
-
less_equal=condition.value.less_equal,
|
|
267
|
-
)
|
|
268
|
-
yield "match", "ipv6 address prefix-list", mangled_name
|
|
241
|
+
plist = name_generator.get_prefix(name, condition.value)
|
|
242
|
+
yield "match", "ipv6 address prefix-list", plist.name
|
|
269
243
|
return
|
|
270
244
|
if condition.operator is not ConditionOperator.EQ:
|
|
271
245
|
raise NotImplementedError(
|
|
@@ -334,9 +308,44 @@ class CumulusPolicyGenerator(ABC):
|
|
|
334
308
|
raise NotImplementedError("Replacing SOO extcommunity is not supported for Cumulus")
|
|
335
309
|
for community_name in action.value.added:
|
|
336
310
|
yield "set", "extcommunity soo", community_name, "additive"
|
|
337
|
-
|
|
311
|
+
if action.value.removed:
|
|
338
312
|
raise NotImplementedError("SOO extcommunity remove is not supported for Cumulus")
|
|
339
313
|
|
|
314
|
+
def _cumulus_extcommunity_type_str(self, comm_type: CommunityType) -> str:
|
|
315
|
+
if comm_type is CommunityType.SOO:
|
|
316
|
+
return "soo"
|
|
317
|
+
elif comm_type is CommunityType.RT:
|
|
318
|
+
return "rt"
|
|
319
|
+
elif comm_type is CommunityType.LARGE:
|
|
320
|
+
raise ValueError("Large community is not subtype of extcommunity")
|
|
321
|
+
elif comm_type is CommunityType.BASIC:
|
|
322
|
+
raise ValueError("Basic community is not subtype of extcommunity")
|
|
323
|
+
else:
|
|
324
|
+
raise NotImplementedError(f"Community type {comm_type} is not supported on cumulus")
|
|
325
|
+
|
|
326
|
+
def _cumulus_then_extcommunity(
|
|
327
|
+
self,
|
|
328
|
+
communities: dict[str, CommunityList],
|
|
329
|
+
device: Any,
|
|
330
|
+
action: SingleAction[CommunityActionValue],
|
|
331
|
+
):
|
|
332
|
+
if action.value.replaced is not None:
|
|
333
|
+
if action.value.added or action.value.removed:
|
|
334
|
+
raise NotImplementedError(
|
|
335
|
+
"Cannot set extcommunity together with add/delete on cumulus",
|
|
336
|
+
)
|
|
337
|
+
if not action.value.replaced:
|
|
338
|
+
yield "set", "extcommunity", "none"
|
|
339
|
+
return
|
|
340
|
+
members = group_community_members(communities, action.value.replaced)
|
|
341
|
+
for community_type, replaced_members in members.items():
|
|
342
|
+
type_str = self._cumulus_extcommunity_type_str(community_type)
|
|
343
|
+
yield "set", "extcommunity", type_str, *replaced_members
|
|
344
|
+
if action.value.added:
|
|
345
|
+
raise NotImplementedError("extcommunity add is not supported for Cumulus")
|
|
346
|
+
if action.value.removed:
|
|
347
|
+
raise NotImplementedError("extcommunity remove is not supported for Cumulus")
|
|
348
|
+
|
|
340
349
|
def _cumulus_then_as_path(
|
|
341
350
|
self,
|
|
342
351
|
device: Any,
|
|
@@ -377,6 +386,9 @@ class CumulusPolicyGenerator(ABC):
|
|
|
377
386
|
cast(SingleAction[CommunityActionValue], action),
|
|
378
387
|
)
|
|
379
388
|
return
|
|
389
|
+
if action.field == ThenField.extcommunity:
|
|
390
|
+
yield from self._cumulus_then_extcommunity(communities, device, action)
|
|
391
|
+
return
|
|
380
392
|
if action.field == ThenField.extcommunity_rt:
|
|
381
393
|
yield from self._cumulus_then_rt_community(
|
|
382
394
|
communities,
|
annet/rpl_generators/entities.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
from ipaddress import IPv4Network, IPv6Network, ip_network
|
|
1
2
|
from collections import defaultdict
|
|
2
3
|
from collections.abc import Sequence
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from enum import Enum
|
|
5
|
-
from typing import Optional
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
|
|
8
|
+
from annet.rpl import RoutingPolicy, PrefixMatchValue, OrLonger
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class CommunityLogic(Enum):
|
|
@@ -40,10 +43,48 @@ class AsPathFilter:
|
|
|
40
43
|
filters: Sequence[str]
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
@dataclass
|
|
46
|
+
@dataclass
|
|
47
|
+
class IpPrefixListMember:
|
|
48
|
+
prefix: IPv4Network | IPv6Network
|
|
49
|
+
or_longer: OrLonger = (None, None)
|
|
50
|
+
|
|
51
|
+
def __post_init__(self):
|
|
52
|
+
self.prefix = ip_network(self.prefix)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
44
56
|
class IpPrefixList:
|
|
45
57
|
name: str
|
|
46
|
-
members:
|
|
58
|
+
members: list[IpPrefixListMember]
|
|
59
|
+
|
|
60
|
+
def __post_init__(self):
|
|
61
|
+
for (i, m) in enumerate(self.members):
|
|
62
|
+
if isinstance(m, str):
|
|
63
|
+
self.members[i] = IpPrefixListMember(m)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ip_prefix_list(
|
|
67
|
+
name: str,
|
|
68
|
+
members_or_str: Sequence[IpPrefixListMember | str],
|
|
69
|
+
or_longer: OrLonger = (None, None),
|
|
70
|
+
) -> IpPrefixList:
|
|
71
|
+
members: List[IpPrefixListMember] = []
|
|
72
|
+
for m in members_or_str:
|
|
73
|
+
if isinstance(m, str):
|
|
74
|
+
m = IpPrefixListMember(
|
|
75
|
+
prefix=ip_network(m),
|
|
76
|
+
or_longer=or_longer,
|
|
77
|
+
)
|
|
78
|
+
elif m.or_longer == (None, None):
|
|
79
|
+
m = IpPrefixListMember(
|
|
80
|
+
prefix=m.prefix,
|
|
81
|
+
or_longer=or_longer,
|
|
82
|
+
)
|
|
83
|
+
members.append(m)
|
|
84
|
+
return IpPrefixList(
|
|
85
|
+
name=name,
|
|
86
|
+
members=members,
|
|
87
|
+
)
|
|
47
88
|
|
|
48
89
|
|
|
49
90
|
def arista_well_known_community(community: str) -> str:
|
|
@@ -58,26 +99,39 @@ def mangle_united_community_list_name(values: Sequence[str]) -> str:
|
|
|
58
99
|
|
|
59
100
|
|
|
60
101
|
class PrefixListNameGenerator:
|
|
61
|
-
def __init__(self):
|
|
62
|
-
self._prefix_lists =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
def __init__(self, prefix_lists: Sequence[IpPrefixList], policies: Sequence[RoutingPolicy]):
|
|
103
|
+
self._prefix_lists = {x.name: x for x in prefix_lists}
|
|
104
|
+
self._policies = {x.name: x for x in policies} # this is here for a later use ~azryve@
|
|
105
|
+
|
|
106
|
+
def get_prefix(self, name: str, match: PrefixMatchValue) -> IpPrefixList:
|
|
107
|
+
orig_prefix = self._prefix_lists[name]
|
|
108
|
+
override_name: Optional[str] = None
|
|
109
|
+
override_orlonger: Optional[OrLonger] = None
|
|
110
|
+
|
|
111
|
+
if any(match.or_longer):
|
|
112
|
+
ge, le = match.or_longer
|
|
113
|
+
ge_str = "unset" if ge is None else str(ge)
|
|
114
|
+
le_str = "unset" if le is None else str(le)
|
|
115
|
+
override_name = f"{orig_prefix.name}_{ge_str}_{le_str}"
|
|
116
|
+
override_orlonger = match.or_longer
|
|
117
|
+
|
|
118
|
+
return IpPrefixList(
|
|
119
|
+
name=override_name or name,
|
|
120
|
+
members=[
|
|
121
|
+
IpPrefixListMember(
|
|
122
|
+
x.prefix,
|
|
123
|
+
or_longer=override_orlonger or x.or_longer,
|
|
124
|
+
)
|
|
125
|
+
for x in orig_prefix.members
|
|
126
|
+
],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def group_community_members(
|
|
131
|
+
all_communities: dict[str, CommunityList], communities: list[str],
|
|
132
|
+
) -> dict[CommunityType, list[str]]:
|
|
133
|
+
members: dict[CommunityType, list[str]] = defaultdict(list)
|
|
134
|
+
for community_name in communities:
|
|
135
|
+
community = all_communities[community_name]
|
|
136
|
+
members[community.type].extend(community.members)
|
|
137
|
+
return members
|
annet/rpl_generators/policy.py
CHANGED
|
@@ -12,8 +12,9 @@ from annet.rpl.statement_builder import AsPathActionValue, NextHopActionValue, T
|
|
|
12
12
|
from annet.rpl_generators.entities import (
|
|
13
13
|
arista_well_known_community,
|
|
14
14
|
CommunityList, RDFilter, PrefixListNameGenerator, CommunityLogic, mangle_united_community_list_name,
|
|
15
|
+
IpPrefixList, group_community_members, CommunityType,
|
|
15
16
|
)
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
HUAWEI_MATCH_COMMAND_MAP: dict[str, str] = {
|
|
19
20
|
MatchField.as_path_filter: "as-path-filter {option_value}",
|
|
@@ -62,6 +63,10 @@ ARISTA_THEN_COMMAND_MAP: dict[str, str] = {
|
|
|
62
63
|
class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
63
64
|
TAGS = ["policy", "rpl", "routing"]
|
|
64
65
|
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def get_prefix_lists(self, device: Any) -> list[IpPrefixList]:
|
|
68
|
+
raise NotImplementedError()
|
|
69
|
+
|
|
65
70
|
@abstractmethod
|
|
66
71
|
def get_policies(self, device: Any) -> list[RoutingPolicy]:
|
|
67
72
|
raise NotImplementedError()
|
|
@@ -87,7 +92,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
87
92
|
condition: SingleCondition[Any],
|
|
88
93
|
communities: dict[str, CommunityList],
|
|
89
94
|
rd_filters: dict[str, RDFilter],
|
|
90
|
-
|
|
95
|
+
name_generator: PrefixListNameGenerator,
|
|
91
96
|
) -> Iterator[Sequence[str]]:
|
|
92
97
|
if condition.field == MatchField.community:
|
|
93
98
|
if condition.operator is ConditionOperator.HAS:
|
|
@@ -136,21 +141,13 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
136
141
|
return
|
|
137
142
|
if condition.field == MatchField.ip_prefix:
|
|
138
143
|
for name in condition.value.names:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
greater_equal=condition.value.greater_equal,
|
|
142
|
-
less_equal=condition.value.less_equal,
|
|
143
|
-
)
|
|
144
|
-
yield "if-match", "ip-prefix", mangled_name
|
|
144
|
+
plist = name_generator.get_prefix(name, condition.value)
|
|
145
|
+
yield "if-match", "ip-prefix", plist.name
|
|
145
146
|
return
|
|
146
147
|
if condition.field == MatchField.ipv6_prefix:
|
|
147
148
|
for name in condition.value.names:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
greater_equal=condition.value.greater_equal,
|
|
151
|
-
less_equal=condition.value.less_equal,
|
|
152
|
-
)
|
|
153
|
-
yield "if-match", "ipv6 address prefix-list", mangled_name
|
|
149
|
+
plist = name_generator.get_prefix(name, condition.value)
|
|
150
|
+
yield "if-match", "ipv6 address prefix-list", plist.name
|
|
154
151
|
return
|
|
155
152
|
if condition.field == MatchField.as_path_length:
|
|
156
153
|
if condition.operator is ConditionOperator.EQ:
|
|
@@ -248,6 +245,52 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
248
245
|
if action.value.removed:
|
|
249
246
|
raise NotImplementedError("Extcommunity_soo remove is not supported for huawei")
|
|
250
247
|
|
|
248
|
+
def _huawei_render_ext_community_members(
|
|
249
|
+
self, comm_type: CommunityType, members: list[str]
|
|
250
|
+
) -> Sequence[Sequence[str]]:
|
|
251
|
+
if comm_type is CommunityType.SOO:
|
|
252
|
+
return "soo", *members
|
|
253
|
+
if comm_type is CommunityType.RT:
|
|
254
|
+
return [f"rt {member}" for member in members]
|
|
255
|
+
elif comm_type is CommunityType.LARGE:
|
|
256
|
+
raise ValueError("Large community is not subtype of extcommunity")
|
|
257
|
+
elif comm_type is CommunityType.BASIC:
|
|
258
|
+
raise ValueError("Basic community is not subtype of extcommunity")
|
|
259
|
+
else:
|
|
260
|
+
raise NotImplementedError(f"Community type {comm_type} is not supported on huawei")
|
|
261
|
+
|
|
262
|
+
def _huawei_then_extcommunity(
|
|
263
|
+
self,
|
|
264
|
+
communities: dict[str, CommunityList],
|
|
265
|
+
device: Any,
|
|
266
|
+
action: SingleAction[CommunityActionValue],
|
|
267
|
+
):
|
|
268
|
+
if action.value.replaced is not None:
|
|
269
|
+
if action.value.added or action.value.removed:
|
|
270
|
+
raise NotImplementedError(
|
|
271
|
+
"Cannot set extcommunity together with add/delete on huawei",
|
|
272
|
+
)
|
|
273
|
+
if not action.value.replaced:
|
|
274
|
+
raise NotImplementedError(
|
|
275
|
+
"Cannot reset extcommunity on huawei",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
members = group_community_members(communities, action.value.replaced)
|
|
279
|
+
for community_type, replaced_members in members.items():
|
|
280
|
+
if community_type is CommunityType.SOO:
|
|
281
|
+
raise NotImplementedError(
|
|
282
|
+
"Cannot set extcommunity soo on huawei",
|
|
283
|
+
)
|
|
284
|
+
rendered_memebers = self._huawei_render_ext_community_members(community_type, replaced_members)
|
|
285
|
+
yield "apply", "extcommunity", *rendered_memebers
|
|
286
|
+
if action.value.added:
|
|
287
|
+
members = group_community_members(communities, action.value.added)
|
|
288
|
+
for community_type, added_members in members.items():
|
|
289
|
+
rendered_memebers = self._huawei_render_ext_community_members(community_type, added_members)
|
|
290
|
+
yield "apply", "extcommunity", *rendered_memebers, "additive"
|
|
291
|
+
if action.value.removed:
|
|
292
|
+
raise NotImplementedError("Cannot remove extcommunity on huawei")
|
|
293
|
+
|
|
251
294
|
def _huawei_then_as_path(
|
|
252
295
|
self,
|
|
253
296
|
device: Any,
|
|
@@ -286,6 +329,10 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
286
329
|
yield from self._huawei_then_large_community(communities, device,
|
|
287
330
|
cast(SingleAction[CommunityActionValue], action))
|
|
288
331
|
return
|
|
332
|
+
if action.field == ThenField.extcommunity:
|
|
333
|
+
yield from self._huawei_then_extcommunity(communities, device,
|
|
334
|
+
cast(SingleAction[CommunityActionValue], action))
|
|
335
|
+
return
|
|
289
336
|
if action.field == ThenField.extcommunity_rt:
|
|
290
337
|
yield from self._huawei_then_extcommunity_rt(communities, device,
|
|
291
338
|
cast(SingleAction[CommunityActionValue], action))
|
|
@@ -353,10 +400,11 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
353
400
|
yield "goto next-node"
|
|
354
401
|
|
|
355
402
|
def run_huawei(self, device):
|
|
403
|
+
prefix_lists = self.get_prefix_lists(device)
|
|
356
404
|
policies = self.get_policies(device)
|
|
357
405
|
communities = {c.name: c for c in self.get_community_lists(device)}
|
|
358
406
|
rd_filters = {f.name: f for f in self.get_rd_filters(device)}
|
|
359
|
-
prefix_name_generator =
|
|
407
|
+
prefix_name_generator = PrefixListNameGenerator(prefix_lists, policies)
|
|
360
408
|
|
|
361
409
|
for policy in self.get_policies(device):
|
|
362
410
|
for statement in policy.statements:
|
|
@@ -383,7 +431,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
383
431
|
condition: SingleCondition[Any],
|
|
384
432
|
communities: dict[str, CommunityList],
|
|
385
433
|
rd_filters: dict[str, RDFilter],
|
|
386
|
-
|
|
434
|
+
name_generator: PrefixListNameGenerator,
|
|
387
435
|
) -> Iterator[Sequence[str]]:
|
|
388
436
|
if condition.field == MatchField.community:
|
|
389
437
|
if condition.operator is ConditionOperator.HAS_ANY:
|
|
@@ -436,21 +484,13 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
436
484
|
return
|
|
437
485
|
if condition.field == MatchField.ip_prefix:
|
|
438
486
|
for name in condition.value.names:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
greater_equal=condition.value.greater_equal,
|
|
442
|
-
less_equal=condition.value.less_equal,
|
|
443
|
-
)
|
|
444
|
-
yield "match", "ip address prefix-list", mangled_name
|
|
487
|
+
plist = name_generator.get_prefix(name, condition.value)
|
|
488
|
+
yield "match", "ip address prefix-list", plist.name
|
|
445
489
|
return
|
|
446
490
|
if condition.field == MatchField.ipv6_prefix:
|
|
447
491
|
for name in condition.value.names:
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
greater_equal=condition.value.greater_equal,
|
|
451
|
-
less_equal=condition.value.less_equal,
|
|
452
|
-
)
|
|
453
|
-
yield "match", "ipv6 address prefix-list", mangled_name
|
|
492
|
+
plist = name_generator.get_prefix(name, condition.value)
|
|
493
|
+
yield "match", "ipv6 address prefix-list", plist.name
|
|
454
494
|
return
|
|
455
495
|
if condition.field == MatchField.as_path_length:
|
|
456
496
|
if condition.operator is ConditionOperator.EQ:
|
|
@@ -492,12 +532,15 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
492
532
|
yield "set", "community community-list", *action.value.replaced
|
|
493
533
|
else:
|
|
494
534
|
yield "set", "community", "none"
|
|
495
|
-
|
|
496
|
-
yield "set", "community community-list",
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
535
|
+
if action.value.added:
|
|
536
|
+
yield "set", "community community-list", *action.value.added, "additive"
|
|
537
|
+
if action.value.removed:
|
|
538
|
+
members = [
|
|
539
|
+
arista_well_known_community(member)
|
|
540
|
+
for community_name in action.value.removed
|
|
541
|
+
for member in communities[community_name].members
|
|
542
|
+
]
|
|
543
|
+
yield "set community", *members, "delete"
|
|
501
544
|
|
|
502
545
|
def _arista_then_large_community(
|
|
503
546
|
self,
|
|
@@ -520,10 +563,10 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
520
563
|
first = False
|
|
521
564
|
else:
|
|
522
565
|
yield "set", "large-community large-community-list", community_name, "additive"
|
|
523
|
-
|
|
524
|
-
yield "set", "large-community large-community-list",
|
|
525
|
-
|
|
526
|
-
yield "set large-community large-community-list",
|
|
566
|
+
if action.value.added:
|
|
567
|
+
yield "set", "large-community large-community-list", *action.value.added, "additive"
|
|
568
|
+
if action.value.removed:
|
|
569
|
+
yield "set large-community large-community-list", *action.value.removed, "delete"
|
|
527
570
|
|
|
528
571
|
def _arista_then_extcommunity_rt(
|
|
529
572
|
self,
|
|
@@ -533,14 +576,20 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
533
576
|
) -> Iterator[Sequence[str]]:
|
|
534
577
|
if action.value.replaced is not None:
|
|
535
578
|
raise NotImplementedError("Extcommunity_rt replace is not supported for arista")
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
579
|
+
if action.value.added:
|
|
580
|
+
members = [
|
|
581
|
+
f"rt {member}"
|
|
582
|
+
for community_name in action.value.removed
|
|
583
|
+
for member in communities[community_name].members
|
|
584
|
+
]
|
|
585
|
+
yield "set", "extcommunity", *members, "additive"
|
|
586
|
+
if action.value.removed:
|
|
587
|
+
members = [
|
|
588
|
+
f"rt {member}"
|
|
589
|
+
for community_name in action.value.removed
|
|
590
|
+
for member in communities[community_name].members
|
|
591
|
+
]
|
|
592
|
+
yield "set extcommunity", *members, "delete"
|
|
544
593
|
|
|
545
594
|
def _arista_then_extcommunity_soo(
|
|
546
595
|
self,
|
|
@@ -550,14 +599,65 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
550
599
|
) -> Iterator[Sequence[str]]:
|
|
551
600
|
if action.value.replaced is not None:
|
|
552
601
|
raise NotImplementedError("Extcommunity_soo replace is not supported for arista")
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
602
|
+
if action.value.added:
|
|
603
|
+
members = [
|
|
604
|
+
f"soo {member}"
|
|
605
|
+
for community_name in action.value.removed
|
|
606
|
+
for member in communities[community_name].members
|
|
607
|
+
]
|
|
608
|
+
yield "set", "extcommunity", *members, "additive"
|
|
609
|
+
if action.value.removed:
|
|
610
|
+
members = [
|
|
611
|
+
f"soo {member}"
|
|
612
|
+
for community_name in action.value.removed
|
|
613
|
+
for member in communities[community_name].members
|
|
614
|
+
]
|
|
615
|
+
yield "set", "extcommunity", *members, "delete"
|
|
616
|
+
|
|
617
|
+
def _arista_extcommunity_type_str(self, comm_type: CommunityType) -> str:
|
|
618
|
+
if comm_type is CommunityType.SOO:
|
|
619
|
+
return "soo"
|
|
620
|
+
elif comm_type is CommunityType.RT:
|
|
621
|
+
return "rt"
|
|
622
|
+
elif comm_type is CommunityType.LARGE:
|
|
623
|
+
raise ValueError("Large community is not subtype of extcommunity")
|
|
624
|
+
elif comm_type is CommunityType.BASIC:
|
|
625
|
+
raise ValueError("Basic community is not subtype of extcommunity")
|
|
626
|
+
else:
|
|
627
|
+
raise NotImplementedError(f"Community type {comm_type} is not supported on arista")
|
|
628
|
+
|
|
629
|
+
def _arista_render_ext_community_members(
|
|
630
|
+
self, all_communities: dict[str, CommunityList], communities: list[str],
|
|
631
|
+
) -> Iterator[str]:
|
|
632
|
+
for community_name in communities:
|
|
633
|
+
community = all_communities[community_name]
|
|
634
|
+
comm_type = self._arista_extcommunity_type_str(community.type)
|
|
635
|
+
for member in community.members:
|
|
636
|
+
yield f"{comm_type} {member}"
|
|
637
|
+
|
|
638
|
+
def _arista_then_extcommunity(
|
|
639
|
+
self,
|
|
640
|
+
communities: dict[str, CommunityList],
|
|
641
|
+
device: Any,
|
|
642
|
+
action: SingleAction[CommunityActionValue],
|
|
643
|
+
):
|
|
644
|
+
if action.value.replaced is not None:
|
|
645
|
+
if action.value.added or action.value.removed:
|
|
646
|
+
raise NotImplementedError(
|
|
647
|
+
"Cannot set extcommunity together with add/delete on arista",
|
|
648
|
+
)
|
|
649
|
+
if not action.value.replaced:
|
|
650
|
+
yield "set", "extcommunity", "none"
|
|
651
|
+
return
|
|
652
|
+
members = list(self._arista_render_ext_community_members(communities, action.value.replaced))
|
|
653
|
+
yield "set extcommunity", *members
|
|
654
|
+
return
|
|
655
|
+
if action.value.added:
|
|
656
|
+
members = list(self._arista_render_ext_community_members(communities, action.value.added))
|
|
657
|
+
yield "set extcommunity", *members, "additive"
|
|
658
|
+
if action.value.removed:
|
|
659
|
+
members = list(self._arista_render_ext_community_members(communities, action.value.removed))
|
|
660
|
+
yield "set extcommunity", *members, "delete"
|
|
561
661
|
|
|
562
662
|
def _arista_then_as_path(
|
|
563
663
|
self,
|
|
@@ -605,6 +705,9 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
605
705
|
communities, device, cast(SingleAction[CommunityActionValue], action),
|
|
606
706
|
)
|
|
607
707
|
return
|
|
708
|
+
if action.field == ThenField.extcommunity:
|
|
709
|
+
yield from self._arista_then_extcommunity(communities, device, action)
|
|
710
|
+
return
|
|
608
711
|
if action.field == ThenField.extcommunity_rt:
|
|
609
712
|
yield from self._arista_then_extcommunity_rt(
|
|
610
713
|
communities, device, cast(SingleAction[CommunityActionValue], action),
|
|
@@ -675,8 +778,9 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
|
|
|
675
778
|
yield "continue"
|
|
676
779
|
|
|
677
780
|
def run_arista(self, device):
|
|
781
|
+
prefix_lists = self.get_prefix_lists(device)
|
|
678
782
|
policies = self.get_policies(device)
|
|
679
|
-
prefix_name_generator =
|
|
783
|
+
prefix_name_generator = PrefixListNameGenerator(prefix_lists, policies)
|
|
680
784
|
communities = {c.name: c for c in self.get_community_lists(device)}
|
|
681
785
|
rd_filters = {f.name: f for f in self.get_rd_filters(device)}
|
|
682
786
|
|