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
annet/rpl/statement_builder.py
CHANGED
|
@@ -11,7 +11,9 @@ from .result import ResultType
|
|
|
11
11
|
|
|
12
12
|
class ThenField(str, Enum):
|
|
13
13
|
community = "community"
|
|
14
|
-
|
|
14
|
+
large_community = "large_community"
|
|
15
|
+
extcommunity_rt = "extcommunity_rt"
|
|
16
|
+
extcommunity_soo = "extcommunity_soo"
|
|
15
17
|
as_path = "as_path"
|
|
16
18
|
local_pref = "local_pref"
|
|
17
19
|
metric = "metric"
|
|
@@ -141,7 +143,9 @@ class StatementBuilder:
|
|
|
141
143
|
self._statement = statement
|
|
142
144
|
self._added_as_path: list[int] = []
|
|
143
145
|
self._community = CommunityActionValue()
|
|
144
|
-
self.
|
|
146
|
+
self._large_community = CommunityActionValue()
|
|
147
|
+
self._extcommunity_rt = CommunityActionValue()
|
|
148
|
+
self._extcommunity_soo = CommunityActionValue()
|
|
145
149
|
self._as_path = AsPathActionValue()
|
|
146
150
|
self._next_hop = NextHopActionValue()
|
|
147
151
|
|
|
@@ -158,8 +162,16 @@ class StatementBuilder:
|
|
|
158
162
|
return CommunityActionBuilder(self._community)
|
|
159
163
|
|
|
160
164
|
@property
|
|
161
|
-
def
|
|
162
|
-
return CommunityActionBuilder(self.
|
|
165
|
+
def large_community(self) -> CommunityActionBuilder:
|
|
166
|
+
return CommunityActionBuilder(self._large_community)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def extcommunity_rt(self) -> CommunityActionBuilder:
|
|
170
|
+
return CommunityActionBuilder(self._extcommunity_rt)
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def extcommunity_soo(self) -> CommunityActionBuilder:
|
|
174
|
+
return CommunityActionBuilder(self._extcommunity_soo)
|
|
163
175
|
|
|
164
176
|
def _set(self, field: str, value: ValueT) -> None:
|
|
165
177
|
action = self._statement.then
|
|
@@ -228,11 +240,23 @@ class StatementBuilder:
|
|
|
228
240
|
type=ActionType.CUSTOM,
|
|
229
241
|
value=self._community,
|
|
230
242
|
))
|
|
231
|
-
if self.
|
|
243
|
+
if self._large_community:
|
|
244
|
+
self._statement.then.append(SingleAction(
|
|
245
|
+
field=ThenField.large_community,
|
|
246
|
+
type=ActionType.CUSTOM,
|
|
247
|
+
value=self._extcommunity_rt,
|
|
248
|
+
))
|
|
249
|
+
if self._extcommunity_rt:
|
|
250
|
+
self._statement.then.append(SingleAction(
|
|
251
|
+
field=ThenField.extcommunity_rt,
|
|
252
|
+
type=ActionType.CUSTOM,
|
|
253
|
+
value=self._extcommunity_rt,
|
|
254
|
+
))
|
|
255
|
+
if self._extcommunity_soo:
|
|
232
256
|
self._statement.then.append(SingleAction(
|
|
233
|
-
field=ThenField.
|
|
257
|
+
field=ThenField.extcommunity_soo,
|
|
234
258
|
type=ActionType.CUSTOM,
|
|
235
|
-
value=self.
|
|
259
|
+
value=self._extcommunity_rt,
|
|
236
260
|
))
|
|
237
261
|
if self._as_path:
|
|
238
262
|
self._statement.then.append(SingleAction(
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"AsPathFilter",
|
|
3
|
+
"AsPathFilterGenerator",
|
|
4
|
+
"CommunityList",
|
|
5
|
+
"CommunityType",
|
|
6
|
+
"CommunityLogic",
|
|
7
|
+
"CommunityListGenerator",
|
|
8
|
+
"CumulusPolicyGenerator",
|
|
9
|
+
"RoutingPolicyGenerator",
|
|
10
|
+
"RDFilterFilterGenerator",
|
|
11
|
+
"RDFilter",
|
|
12
|
+
"IpPrefixList",
|
|
13
|
+
"PrefixListFilterGenerator",
|
|
14
|
+
"get_policies",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
from .aspath import AsPathFilterGenerator
|
|
18
|
+
from .community import CommunityListGenerator
|
|
19
|
+
from .cumulus_frr import CumulusPolicyGenerator
|
|
20
|
+
from .entities import CommunityList, AsPathFilter, CommunityType, CommunityLogic, RDFilter, IpPrefixList
|
|
21
|
+
from .execute import get_policies
|
|
22
|
+
from .policy import RoutingPolicyGenerator
|
|
23
|
+
from .prefix_lists import PrefixListFilterGenerator
|
|
24
|
+
from .rd import RDFilterFilterGenerator
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from annet.generators import PartialGenerator
|
|
6
|
+
from annet.rpl import RouteMap, MatchField, RoutingPolicy
|
|
7
|
+
from .entities import AsPathFilter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_used_as_path_filters(
|
|
11
|
+
as_path_filters: Sequence[AsPathFilter], policies: list[RoutingPolicy],
|
|
12
|
+
) -> Sequence[AsPathFilter]:
|
|
13
|
+
filters = {c.name: c for c in as_path_filters}
|
|
14
|
+
|
|
15
|
+
used_filters = set()
|
|
16
|
+
for policy in policies:
|
|
17
|
+
for statement in policy.statements:
|
|
18
|
+
for condition in statement.match.find_all(MatchField.as_path_filter):
|
|
19
|
+
used_filters.add(condition.value)
|
|
20
|
+
return [filters[name] for name in sorted(used_filters)]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AsPathFilterGenerator(PartialGenerator, ABC):
|
|
24
|
+
TAGS = ["policy", "rpl", "routing"]
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def get_policies(self, device: Any) -> list[RoutingPolicy]:
|
|
28
|
+
raise NotImplementedError()
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_as_path_filters(self, device: Any) -> Sequence[AsPathFilter]:
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
def get_used_as_path_filters(self, device: Any) -> Sequence[AsPathFilter]:
|
|
35
|
+
filters = self.get_as_path_filters(device)
|
|
36
|
+
policies = self.get_policies(device)
|
|
37
|
+
return get_used_as_path_filters(filters, policies)
|
|
38
|
+
|
|
39
|
+
def acl_huawei(self, _):
|
|
40
|
+
return r"""
|
|
41
|
+
ip as-path-filter
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def run_huawei(self, device: Any):
|
|
45
|
+
for as_path_filter in self.get_used_as_path_filters(device):
|
|
46
|
+
values = "_".join((x for x in as_path_filter.filters if x != ".*"))
|
|
47
|
+
yield "ip as-path-filter", as_path_filter.name, "index 10 permit", f"_{values}_"
|
|
48
|
+
|
|
49
|
+
def acl_arista(self, _):
|
|
50
|
+
return r"""
|
|
51
|
+
ip as-path access-list
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def run_arista(self, device: Any):
|
|
55
|
+
for as_path_filter in self.get_used_as_path_filters(device):
|
|
56
|
+
values = "_".join((x for x in as_path_filter.filters if x != ".*"))
|
|
57
|
+
yield "ip as-path access-list", as_path_filter.name, "permit", f"_{values}_"
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import Sequence, Collection
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from annet.generators import PartialGenerator
|
|
6
|
+
from annet.rpl import RouteMap, SingleCondition, MatchField, ThenField, RoutingPolicy, ConditionOperator
|
|
7
|
+
from .entities import (
|
|
8
|
+
CommunityList, CommunityLogic, CommunityType, arista_well_known_community, mangle_united_community_list_name,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_used_community_lists(
|
|
13
|
+
communities: Collection[CommunityList], policies: Collection[RoutingPolicy],
|
|
14
|
+
) -> list[CommunityList]:
|
|
15
|
+
communities_dict = {c.name: c for c in communities}
|
|
16
|
+
used_communities: set[str] = set()
|
|
17
|
+
for policy in policies:
|
|
18
|
+
for statement in policy.statements:
|
|
19
|
+
condition: SingleCondition[Sequence[str]]
|
|
20
|
+
for match_field in (
|
|
21
|
+
MatchField.community, MatchField.large_community,
|
|
22
|
+
MatchField.extcommunity_rt, MatchField.extcommunity_soo
|
|
23
|
+
):
|
|
24
|
+
for condition in statement.match.find_all(match_field):
|
|
25
|
+
used_communities.update(condition.value)
|
|
26
|
+
for then_field in (
|
|
27
|
+
ThenField.community, ThenField.large_community,
|
|
28
|
+
ThenField.extcommunity_rt, ThenField.extcommunity_soo
|
|
29
|
+
):
|
|
30
|
+
for action in statement.then.find_all(then_field):
|
|
31
|
+
if action.value.replaced is not None:
|
|
32
|
+
used_communities.update(action.value.replaced)
|
|
33
|
+
used_communities.update(action.value.added)
|
|
34
|
+
used_communities.update(action.value.removed)
|
|
35
|
+
return [
|
|
36
|
+
communities_dict[name] for name in sorted(used_communities)
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_used_united_community_lists(
|
|
41
|
+
communities: Collection[CommunityList], policies: Collection[RoutingPolicy],
|
|
42
|
+
) -> list[list[CommunityList]]:
|
|
43
|
+
"""
|
|
44
|
+
Return communities united into groups according to HAS_ANY policy
|
|
45
|
+
"""
|
|
46
|
+
communities_dict = {c.name: c for c in communities}
|
|
47
|
+
used_communities: dict[str, list[CommunityList]] = {}
|
|
48
|
+
for policy in policies:
|
|
49
|
+
for statement in policy.statements:
|
|
50
|
+
condition: SingleCondition[Sequence[str]]
|
|
51
|
+
for match_field in (
|
|
52
|
+
MatchField.community, MatchField.large_community,
|
|
53
|
+
MatchField.extcommunity_rt, MatchField.extcommunity_soo
|
|
54
|
+
):
|
|
55
|
+
for condition in statement.match.find_all(match_field):
|
|
56
|
+
if (
|
|
57
|
+
condition.operator == ConditionOperator.HAS_ANY and
|
|
58
|
+
len(condition.value) > 1
|
|
59
|
+
):
|
|
60
|
+
united_name = mangle_united_community_list_name(condition.value)
|
|
61
|
+
united_communities: list[CommunityList] = [
|
|
62
|
+
communities_dict[name] for name in condition.value
|
|
63
|
+
]
|
|
64
|
+
if not all(united_communities[0].type == c.type for c in united_communities):
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Cannot apply HAS_ANY to communities of different types, "
|
|
67
|
+
f"found for policy: `{policy.name}`, statement: {statement.name}"
|
|
68
|
+
)
|
|
69
|
+
if not all(united_communities[0].use_regex == c.use_regex for c in united_communities):
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"Cannot apply HAS_ANY to communities with different use_regex flag, "
|
|
72
|
+
f"found for policy: `{policy.name}`, statement: {statement.name}"
|
|
73
|
+
)
|
|
74
|
+
used_communities[united_name] = united_communities
|
|
75
|
+
else:
|
|
76
|
+
for name in condition.value:
|
|
77
|
+
used_communities[name] = [communities_dict[name]]
|
|
78
|
+
for then_field in (
|
|
79
|
+
ThenField.community, ThenField.large_community,
|
|
80
|
+
ThenField.extcommunity_rt, ThenField.extcommunity_soo
|
|
81
|
+
):
|
|
82
|
+
for action in statement.then.find_all(then_field):
|
|
83
|
+
if action.value.replaced is not None:
|
|
84
|
+
for name in action.value.replaced:
|
|
85
|
+
used_communities[name] = [communities_dict[name]]
|
|
86
|
+
for name in action.value.added:
|
|
87
|
+
used_communities[name] = [communities_dict[name]]
|
|
88
|
+
for name in action.value.removed:
|
|
89
|
+
used_communities[name] = [communities_dict[name]]
|
|
90
|
+
return [
|
|
91
|
+
used_communities[name] for name in sorted(used_communities)
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class CommunityListGenerator(PartialGenerator, ABC):
|
|
96
|
+
TAGS = ["policy", "rpl", "routing"]
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def get_policies(self, device: Any) -> list[RoutingPolicy]:
|
|
100
|
+
raise NotImplementedError()
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def get_community_lists(self, device: Any) -> list[CommunityList]:
|
|
104
|
+
raise NotImplementedError()
|
|
105
|
+
|
|
106
|
+
def get_used_community_lists(self, device: Any) -> list[CommunityList]:
|
|
107
|
+
return get_used_community_lists(
|
|
108
|
+
communities=self.get_community_lists(device),
|
|
109
|
+
policies=self.get_policies(device),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def get_used_united_community_lists(self, device: Any) -> list[list[CommunityList]]:
|
|
113
|
+
return get_used_united_community_lists(
|
|
114
|
+
communities=self.get_community_lists(device),
|
|
115
|
+
policies=self.get_policies(device),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def acl_huawei(self, _):
|
|
119
|
+
return r"""
|
|
120
|
+
ip community-filter
|
|
121
|
+
ip extcommunity-filter
|
|
122
|
+
ip extcommunity-list
|
|
123
|
+
ip large-community-filter
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def ref_huawei(self, _):
|
|
127
|
+
return """
|
|
128
|
+
route-policy
|
|
129
|
+
if-match community-filter <name>
|
|
130
|
+
if-match extcommunity-filter <name>
|
|
131
|
+
if-match extcommunity-list soo <name>
|
|
132
|
+
if-match large-community-filter <name>
|
|
133
|
+
apply comm-filter <name>
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def _huawei_community_filter(self, index: int, community_list: CommunityList, members: str) -> Sequence[str]:
|
|
137
|
+
if community_list.use_regex:
|
|
138
|
+
match_type = "advanced"
|
|
139
|
+
else:
|
|
140
|
+
match_type = "basic"
|
|
141
|
+
if community_list.type is CommunityType.BASIC:
|
|
142
|
+
return "ip community-filter", match_type, community_list.name, f"index {index}", "permit", members
|
|
143
|
+
elif community_list.type is CommunityType.RT:
|
|
144
|
+
return "ip extcommunity-filter", match_type, community_list.name, f"index {index}", "permit", members
|
|
145
|
+
elif community_list.type is CommunityType.SOO:
|
|
146
|
+
return "ip extcommunity-list soo", match_type, community_list.name, f"index {index}", "permit", members
|
|
147
|
+
elif community_list.type is CommunityType.LARGE:
|
|
148
|
+
return "ip large-community-filter", match_type, community_list.name, f"index {index}", "permit", members
|
|
149
|
+
else:
|
|
150
|
+
raise NotImplementedError(f"CommunityList type {community_list.type} not implemented for huawei")
|
|
151
|
+
|
|
152
|
+
def run_huawei(self, device: Any):
|
|
153
|
+
for community_list in self.get_used_community_lists(device):
|
|
154
|
+
if community_list.use_regex and len(community_list.members) > 1:
|
|
155
|
+
raise NotImplementedError("Multiple regex is not supported for huawei")
|
|
156
|
+
if community_list.type is CommunityType.RT:
|
|
157
|
+
# RT communities used with prefix rt
|
|
158
|
+
members: Sequence[str] = [f"rt {m}" for m in community_list.members]
|
|
159
|
+
else:
|
|
160
|
+
members = community_list.members
|
|
161
|
+
|
|
162
|
+
if community_list.logic == CommunityLogic.AND:
|
|
163
|
+
# to get AND logic the communities should be in one sting
|
|
164
|
+
yield self._huawei_community_filter(10, community_list, " ".join(members))
|
|
165
|
+
elif community_list.logic == CommunityLogic.OR:
|
|
166
|
+
for i, member in enumerate(members):
|
|
167
|
+
member_id = (i + 1) * 10 + 5
|
|
168
|
+
yield self._huawei_community_filter(member_id, community_list, member)
|
|
169
|
+
else:
|
|
170
|
+
raise NotImplementedError(f"Community logic {community_list.logic} is not implemented for huawei")
|
|
171
|
+
|
|
172
|
+
def acl_arista(self, _):
|
|
173
|
+
return r"""
|
|
174
|
+
ip community-list
|
|
175
|
+
ip extcommunity-list
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def _arista_community_list(
|
|
179
|
+
self, name: str, use_regex: bool, comm_type: CommunityType, members: str,
|
|
180
|
+
) -> Sequence[str]:
|
|
181
|
+
if use_regex:
|
|
182
|
+
match_type = "regexp"
|
|
183
|
+
else:
|
|
184
|
+
match_type = ""
|
|
185
|
+
if comm_type is CommunityType.BASIC:
|
|
186
|
+
return "ip community-list", match_type, name, "permit", members
|
|
187
|
+
elif comm_type is CommunityType.RT:
|
|
188
|
+
return "ip extcommunity-list", match_type, name, "permit", members
|
|
189
|
+
elif comm_type is CommunityType.SOO:
|
|
190
|
+
return "ip extcommunity-list", match_type, name, "permit", members
|
|
191
|
+
elif comm_type is CommunityType.LARGE:
|
|
192
|
+
return "ip large-community-list", match_type, name, "permit", members
|
|
193
|
+
else:
|
|
194
|
+
raise NotImplementedError(f"CommunityList type {comm_type} not implemented for arista")
|
|
195
|
+
|
|
196
|
+
def _arista_community_prefix(self, community_list: CommunityList) -> str:
|
|
197
|
+
if community_list.type is CommunityType.BASIC:
|
|
198
|
+
return ""
|
|
199
|
+
elif community_list.type is CommunityType.RT:
|
|
200
|
+
if community_list.use_regex:
|
|
201
|
+
return "RT:"
|
|
202
|
+
return "rt "
|
|
203
|
+
elif community_list.type is CommunityType.SOO:
|
|
204
|
+
if community_list.use_regex:
|
|
205
|
+
return "SoO:"
|
|
206
|
+
return "soo "
|
|
207
|
+
elif community_list.type is CommunityType.LARGE:
|
|
208
|
+
return ""
|
|
209
|
+
else:
|
|
210
|
+
raise NotImplementedError(f"CommunityList type {community_list.type} not implemented for arista")
|
|
211
|
+
|
|
212
|
+
def run_arista(self, device):
|
|
213
|
+
for community_list_union in self.get_used_united_community_lists(device):
|
|
214
|
+
name = mangle_united_community_list_name([c.name for c in community_list_union])
|
|
215
|
+
for community_list in community_list_union:
|
|
216
|
+
if community_list.use_regex and len(community_list.members) > 1:
|
|
217
|
+
raise NotImplementedError("Multiple regex is not supported for arista")
|
|
218
|
+
|
|
219
|
+
member_prefix = self._arista_community_prefix(community_list)
|
|
220
|
+
|
|
221
|
+
if community_list.logic == CommunityLogic.AND:
|
|
222
|
+
# to get AND logic the communities should be in one sting
|
|
223
|
+
member_str = " ".join(
|
|
224
|
+
member_prefix + arista_well_known_community(m)
|
|
225
|
+
for m in community_list.members
|
|
226
|
+
)
|
|
227
|
+
yield self._arista_community_list(
|
|
228
|
+
name=name,
|
|
229
|
+
use_regex=community_list.use_regex,
|
|
230
|
+
comm_type=community_list.type,
|
|
231
|
+
members=member_str,
|
|
232
|
+
)
|
|
233
|
+
elif community_list.logic == CommunityLogic.OR:
|
|
234
|
+
for member in community_list.members:
|
|
235
|
+
yield self._arista_community_list(
|
|
236
|
+
name=name,
|
|
237
|
+
use_regex=community_list.use_regex,
|
|
238
|
+
comm_type=community_list.type,
|
|
239
|
+
members=member_prefix + member,
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
raise NotImplementedError(f"Community logic {community_list.logic} is not implemented for arista")
|