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
@@ -7,7 +7,9 @@ from .condition import SingleCondition, ConditionOperator, AndCondition
7
7
 
8
8
  class MatchField(str, Enum):
9
9
  community = "community"
10
- extcommunity = "extcommunity"
10
+ large_community = "large_community"
11
+ extcommunity_rt = "extcommunity_rt"
12
+ extcommunity_soo = "extcommunity_soo"
11
13
  rd = "rd"
12
14
  interface = "interface"
13
15
  protocol = "protocol"
@@ -63,14 +65,17 @@ class SetConditionFactory(Generic[ValueT]):
63
65
 
64
66
  @dataclass(frozen=True)
65
67
  class PrefixMatchValue:
66
- names: Sequence[str]
67
- or_longer: Optional[tuple[int, int]] # ????
68
+ names: tuple[str, ...]
69
+ greater_equal: Optional[int]
70
+ less_equal: Optional[int]
68
71
 
69
72
 
70
73
  class Checkable:
71
74
  def __init__(self):
72
75
  self.community = SetConditionFactory[str](MatchField.community)
73
- self.extcommunity = SetConditionFactory[str](MatchField.extcommunity)
76
+ self.large_community = SetConditionFactory[str](MatchField.large_community)
77
+ self.extcommunity_rt = SetConditionFactory[str](MatchField.extcommunity_rt)
78
+ self.extcommunity_soo = SetConditionFactory[str](MatchField.extcommunity_soo)
74
79
  self.rd = SetConditionFactory[str](MatchField.rd)
75
80
  self.interface = ConditionFactory[str](MatchField.interface, ["=="])
76
81
  self.protocol = ConditionFactory[str](MatchField.protocol, ["=="])
@@ -83,11 +88,27 @@ class Checkable:
83
88
  def as_path_filter(self, name: str) -> SingleCondition[str]:
84
89
  return SingleCondition(MatchField.as_path_filter, ConditionOperator.EQ, name)
85
90
 
86
- def match_v6(self, *names: str, or_longer: Optional[tuple[int, int]] = None) -> SingleCondition[PrefixMatchValue]:
87
- return SingleCondition(MatchField.ipv6_prefix, ConditionOperator.CUSTOM, PrefixMatchValue(names, or_longer))
88
-
89
- def match_v4(self, *names: str, or_longer: Optional[tuple[int, int]] = None) -> SingleCondition[PrefixMatchValue]:
90
- return SingleCondition(MatchField.ip_prefix, ConditionOperator.CUSTOM, PrefixMatchValue(names, or_longer))
91
+ def match_v6(
92
+ self,
93
+ *names: str,
94
+ or_longer: tuple[Optional[int], Optional[int]] = (None, None),
95
+ ) -> SingleCondition[PrefixMatchValue]:
96
+ return SingleCondition(
97
+ MatchField.ipv6_prefix,
98
+ ConditionOperator.CUSTOM,
99
+ PrefixMatchValue(names, greater_equal=or_longer[0], less_equal=or_longer[1]),
100
+ )
101
+
102
+ def match_v4(
103
+ self,
104
+ *names: str,
105
+ or_longer: tuple[Optional[int], Optional[int]] = (None, None),
106
+ ) -> SingleCondition[PrefixMatchValue]:
107
+ return SingleCondition(
108
+ MatchField.ip_prefix,
109
+ ConditionOperator.CUSTOM,
110
+ PrefixMatchValue(names, greater_equal=or_longer[0], less_equal=or_longer[1]),
111
+ )
91
112
 
92
113
 
93
114
  def merge_conditions(and_condition: AndCondition) -> AndCondition:
annet/rpl/routemap.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections.abc import Sequence
1
2
  from dataclasses import dataclass
2
3
  from typing import Optional, Callable, Generic, TypeVar, Union
3
4
 
@@ -64,13 +65,14 @@ class RouteMap(Generic[DeviceT]):
64
65
  def include(self, other: "RouteMap[DeviceT]") -> None:
65
66
  self.submaps.append(other)
66
67
 
67
- def apply(self, device: DeviceT) -> list[RoutingPolicy]:
68
+ def apply(self, device: DeviceT, rules: Optional[Sequence[str]] = None) -> list[RoutingPolicy]:
68
69
  result: list[RoutingPolicy] = []
69
-
70
70
  for handler in self.handlers:
71
+ if rules is not None and handler.name not in rules:
72
+ continue
71
73
  route = Route(handler.name)
72
74
  handler.func(device, route)
73
75
  result.append(RoutingPolicy(route.name, route.statements))
74
76
  for submap in self.submaps:
75
- result.extend(submap.apply(device))
77
+ result.extend(submap.apply(device, rules))
76
78
  return result
@@ -11,7 +11,9 @@ from .result import ResultType
11
11
 
12
12
  class ThenField(str, Enum):
13
13
  community = "community"
14
- extcommunity = "extcommunity"
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._extcommunity = CommunityActionValue()
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 extcommunity(self) -> CommunityActionBuilder:
162
- return CommunityActionBuilder(self._extcommunity)
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._extcommunity:
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.extcommunity,
257
+ field=ThenField.extcommunity_soo,
234
258
  type=ActionType.CUSTOM,
235
- value=self._extcommunity,
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")