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.

@@ -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
- from .prefix_lists import get_used_prefix_lists, new_prefix_list_name_generator
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 = new_prefix_list_name_generator(policies)
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, prefix in enumerate(plist.members):
98
- addr_mask = ip_interface(prefix)
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", f"{addr_mask.ip}/{addr_mask.network.prefixlen}",
98
+ "permit", str(m.prefix),
105
99
  ) + (
106
- ("ge", str(match.greater_equal)) if match.greater_equal is not None else ()
100
+ ("ge", str(ge)) if ge is not None else ()
107
101
  ) + (
108
- ("le", str(match.less_equal)) if match.less_equal is not None else ()
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
- prefix_list_name_generator: PrefixListNameGenerator,
108
+ name_generator: PrefixListNameGenerator,
115
109
  ) -> Iterable[Sequence[str]]:
116
- plists = {p.name: p for p in self.get_used_prefix_lists(device, prefix_list_name_generator)}
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
- mangled_name = prefix_list_name_generator.get_prefix_name(
127
- name=name,
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(mangled_name, "ip", cond.value, plists[name])
134
- precessed_names.add(mangled_name)
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
- mangled_name = prefix_list_name_generator.get_prefix_name(
138
- name=name,
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(mangled_name, "ipv6", cond.value, plists[name])
145
- precessed_names.add(mangled_name)
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
- prefix_list_name_generator: PrefixListNameGenerator,
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
- mangled_name = prefix_list_name_generator.get_prefix_name(
255
- name=name,
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
- mangled_name = prefix_list_name_generator.get_prefix_name(
264
- name=name,
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
- for community_name in action.value.removed:
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,
@@ -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(frozen=True)
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: Sequence[str]
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 = defaultdict(set)
63
-
64
- def add_prefix(self, name: str, greater_equal: Optional[int], less_equal: Optional[int]) -> None:
65
- self._prefix_lists[name].add((greater_equal, less_equal))
66
-
67
- def is_used(self, name: str):
68
- return name in self._prefix_lists
69
-
70
- def get_prefix_name(self, name: str, greater_equal: Optional[int], less_equal: Optional[int]) -> str:
71
- if len(self._prefix_lists[name]) == 1:
72
- return name
73
- if greater_equal is less_equal is None:
74
- return name
75
- if greater_equal is None:
76
- ge_str = "unset"
77
- else:
78
- ge_str = str(greater_equal)
79
- if less_equal is None:
80
- le_str = "unset"
81
- else:
82
- le_str = str(less_equal)
83
- return f"{name}_{ge_str}_{le_str}"
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
@@ -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
- from annet.rpl_generators.prefix_lists import new_prefix_list_name_generator
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
- prefix_name_generator: PrefixListNameGenerator,
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
- mangled_name = prefix_name_generator.get_prefix_name(
140
- name=name,
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
- mangled_name = prefix_name_generator.get_prefix_name(
149
- name=name,
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 = new_prefix_list_name_generator(policies)
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
- prefix_name_generator: PrefixListNameGenerator,
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
- mangled_name = prefix_name_generator.get_prefix_name(
440
- name=name,
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
- mangled_name = prefix_name_generator.get_prefix_name(
449
- name=name,
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
- for community_name in action.value.added:
496
- yield "set", "community community-list", community_name, "additive"
497
- for community_name in action.value.removed:
498
- community = communities[community_name]
499
- for comm_value in community.members:
500
- yield "set community", arista_well_known_community(comm_value), "delete"
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
- for community_name in action.value.added:
524
- yield "set", "large-community large-community-list", community_name, "additive"
525
- for community_name in action.value.removed:
526
- yield "set large-community large-community-list", community_name, "delete"
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
- for community_name in action.value.added:
537
- community = communities[community_name]
538
- for comm_value in community.members:
539
- yield "set", "extcommunity rt", comm_value, "additive"
540
- for community_name in action.value.removed:
541
- community = communities[community_name]
542
- for comm_value in community.members:
543
- yield "set extcommunity rt", comm_value, "delete"
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
- for community_name in action.value.added:
554
- community = communities[community_name]
555
- for comm_value in community.members:
556
- yield "set", "extcommunity soo", comm_value, "additive"
557
- for community_name in action.value.removed:
558
- community = communities[community_name]
559
- for comm_value in community.members:
560
- yield "set", "extcommunity soo", comm_value, "delete"
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 = new_prefix_list_name_generator(policies)
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