annet 0.16.30__py3-none-any.whl → 0.16.32__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.

@@ -54,6 +54,8 @@ def get_breed(manufacturer: str, model: str):
54
54
  return "bcom-os"
55
55
  elif manufacturer == "MikroTik":
56
56
  return "routeros"
57
+ elif manufacturer == "Moxa":
58
+ return "moxa"
57
59
  elif manufacturer == "PC":
58
60
  return "pc"
59
61
  return ""
@@ -214,7 +214,7 @@ class NetboxDevice(Entity):
214
214
  return type(self) is type(other) and self.url == other.url
215
215
 
216
216
  def is_pc(self) -> bool:
217
- return self.device_type.manufacturer.name == "Mellanox" or self.breed == "pc"
217
+ return self.device_type.manufacturer.name in ("Mellanox", "NVIDIA", "Moxa") or self.breed == "pc"
218
218
 
219
219
  def _make_interface(self, name: str, type: InterfaceType) -> Interface:
220
220
  return Interface(
annet/api/__init__.py CHANGED
@@ -274,12 +274,14 @@ def patch(args: cli_args.ShowPatchOptions, loader: ann_gen.Loader):
274
274
 
275
275
  def _patch_worker(device_id, args: cli_args.ShowPatchOptions, stdin, loader: ann_gen.Loader, filterer: filtering.Filterer):
276
276
  for res, _, patch_tree in res_diff_patch(device_id, args, stdin, loader, filterer):
277
+ old_files = res.old_files
277
278
  new_files = res.get_new_files(args.acl_safe)
278
279
  new_json_fragment_files = res.get_new_file_fragments(args.acl_safe)
279
280
  if new_files:
280
281
  for path, (cfg_text, _cmds) in new_files.items():
281
282
  label = res.device.hostname + os.sep + path
282
- yield label, cfg_text, False
283
+ if old_files.get(path) != cfg_text:
284
+ yield label, cfg_text, False
283
285
  elif res.old_json_fragment_files or new_json_fragment_files:
284
286
  for path, (new_json_cfg, _cmds) in new_json_fragment_files.items():
285
287
  label = res.device.hostname + os.sep + path
@@ -468,6 +470,9 @@ class PCDeployerJob(DeployerJob):
468
470
  elif not new_files and not new_json_fragment_files:
469
471
  return
470
472
 
473
+ enable_reload = self.args.entire_reload is not cli_args.EntireReloadFlag.no
474
+ force_reload = self.args.entire_reload is cli_args.EntireReloadFlag.force
475
+
471
476
  upload_files: Dict[str, bytes] = {}
472
477
  reload_cmds: Dict[str, bytes] = {}
473
478
  generator_types: Dict[str, GeneratorType] = {}
@@ -483,18 +488,23 @@ class PCDeployerJob(DeployerJob):
483
488
  old_text = jsontools.format_json(old_json_cfg)
484
489
  new_text = jsontools.format_json(file_content_or_json_cfg)
485
490
  diff_content = "\n".join(_diff_file(old_text, new_text))
486
- if diff_content:
487
- self._has_diff = True
488
- upload_files[file], reload_cmds[file] = file_content.encode(), cmds.encode()
489
- generator_types[file] = generator_type
490
- self.cmd_lines.append("= Deploy cmds %s/%s " % (device.hostname, file))
491
- self.cmd_lines.extend([cmds, ""])
492
- self.cmd_lines.append("= %s/%s " % (device.hostname, file))
493
- self.cmd_lines.extend([file_content, ""])
494
- self.diff_lines.append("= %s/%s " % (device.hostname, file))
495
- self.diff_lines.extend([diff_content, ""])
496
-
497
- if upload_files:
491
+
492
+ if diff_content or force_reload:
493
+ self._has_diff |= True
494
+
495
+ upload_files[file] = file_content.encode()
496
+ generator_types[file] = generator_type
497
+ self.cmd_lines.append("= %s/%s " % (device.hostname, file))
498
+ self.cmd_lines.extend([file_content, ""])
499
+ self.diff_lines.append("= %s/%s " % (device.hostname, file))
500
+ self.diff_lines.extend([diff_content, ""])
501
+
502
+ if enable_reload:
503
+ reload_cmds[file] = cmds.encode()
504
+ self.cmd_lines.append("= Deploy cmds %s/%s " % (device.hostname, file))
505
+ self.cmd_lines.extend([cmds, ""])
506
+
507
+ if self._has_diff:
498
508
  self.deploy_cmds[device] = {
499
509
  "files": upload_files,
500
510
  "cmds": reload_cmds,
@@ -534,13 +544,12 @@ class Deployer:
534
544
  self._filterer = filtering.filterer_connector.get()
535
545
 
536
546
  def parse_result(self, job: DeployerJob, result: ann_gen.OldNewResult):
537
- entire_reload = self.args.entire_reload
538
547
  logger = get_logger(job.device.hostname)
539
548
 
540
549
  job.parse_result(result)
541
550
  self.failed_configs.update(job.failed_configs)
542
551
 
543
- if job.has_diff() or entire_reload is entire_reload.force:
552
+ if job.has_diff():
544
553
  self.cmd_lines.extend(job.cmd_lines)
545
554
  self.deploy_cmds.update(job.deploy_cmds)
546
555
  self.diffs.update(job.diffs)
annet/bgp_models.py CHANGED
@@ -68,7 +68,7 @@ class BFDTimers:
68
68
  multiplier: int = 4
69
69
 
70
70
 
71
- Family = Literal["ipv4_unicast", "ipv6_unicast", "ipv4_labeled_unicast", "ipv6_labeled_unicast"]
71
+ Family = Literal["ipv4_unicast", "ipv6_unicast", "ipv4_labeled_unicast", "ipv6_labeled_unicast", "l2vpn_evpn"]
72
72
 
73
73
 
74
74
  @dataclass(frozen=True)
@@ -207,7 +207,7 @@ class PeerGroup:
207
207
  auth_key: bool = False
208
208
  add_path: bool = False
209
209
  multipath: bool = False
210
- multihop: bool = False
210
+ multihop: Optional[int] = None
211
211
  multihop_no_nexthop_change: bool = False
212
212
  af_no_install: bool = False
213
213
  bfd: bool = False
@@ -243,8 +243,10 @@ class VrfOptions:
243
243
  ipv6_unicast: FamilyOptions
244
244
  ipv4_labeled_unicast: FamilyOptions
245
245
  ipv6_labeled_unicast: FamilyOptions
246
+ l2vpn_evpn: FamilyOptions
246
247
 
247
248
  vrf_name_global: Optional[str] = None
249
+ l3vni: Optional[int] = None
248
250
  as_path_relax: bool = False
249
251
  rt_import: list[str] = field(default_factory=list)
250
252
  rt_export: list[str] = field(default_factory=list)
@@ -262,6 +264,7 @@ class GlobalOptions:
262
264
  ipv6_unicast: FamilyOptions
263
265
  ipv4_labeled_unicast: FamilyOptions
264
266
  ipv6_labeled_unicast: FamilyOptions
267
+ l2vpn_evpn: FamilyOptions
265
268
 
266
269
  as_path_relax: bool = False
267
270
  local_as: ASN = ASN(None)
@@ -299,6 +302,9 @@ def _used_redistribute_policies(opts: Union[GlobalOptions, VrfOptions]) -> Itera
299
302
  for red in opts.ipv6_labeled_unicast.redistributes:
300
303
  if red.policy:
301
304
  yield red.policy
305
+ for red in opts.l2vpn_evpn.redistributes:
306
+ if red.policy:
307
+ yield red.policy
302
308
 
303
309
 
304
310
  def extract_policies(config: BgpConfig) -> Sequence[str]:
annet/mesh/basemodel.py CHANGED
@@ -133,6 +133,9 @@ class BaseMeshModel:
133
133
  raise AttributeError(f"{self.__class__.__name__} has no field {key}")
134
134
  super().__setattr__(key, value)
135
135
 
136
+ def is_empty(self):
137
+ return not self.__dict__
138
+
136
139
 
137
140
  ModelT = TypeVar("ModelT", bound=BaseMeshModel)
138
141
 
@@ -43,11 +43,13 @@ class _FamiliesMixin:
43
43
  kwargs.setdefault("ipv6_unicast", FamilyOptions(family="ipv6_unicast"))
44
44
  kwargs.setdefault("ipv4_labeled_unicast", FamilyOptions(family="ipv4_labeled_unicast"))
45
45
  kwargs.setdefault("ipv6_labeled_unicast", FamilyOptions(family="ipv6_labeled_unicast"))
46
+ kwargs.setdefault("l2vpn_evpn", FamilyOptions(family="l2vpn_evpn"))
46
47
  super().__init__(**kwargs)
47
48
  ipv4_unicast: Annotated[FamilyOptions, Merge()]
48
49
  ipv6_unicast: Annotated[FamilyOptions, Merge()]
49
50
  ipv4_labeled_unicast: Annotated[FamilyOptions, Merge()]
50
51
  ipv6_labeled_unicast: Annotated[FamilyOptions, Merge()]
52
+ l2vpn_evpn: Annotated[FamilyOptions, Merge()]
51
53
 
52
54
 
53
55
  class VrfOptions(_FamiliesMixin, BaseMeshModel):
@@ -56,6 +58,7 @@ class VrfOptions(_FamiliesMixin, BaseMeshModel):
56
58
  kwargs.setdefault("ipv6_unicast", FamilyOptions(family="ipv6_unicast", vrf_name=vrf_name))
57
59
  kwargs.setdefault("ipv4_labeled_unicast", FamilyOptions(family="ipv4_labeled_unicast", vrf_name=vrf_name))
58
60
  kwargs.setdefault("ipv6_labeled_unicast", FamilyOptions(family="ipv6_labeled_unicast", vrf_name=vrf_name))
61
+ kwargs.setdefault("l2vpn_evpn", FamilyOptions(family="l2vpn_evpn", vrf_name=vrf_name))
59
62
  kwargs.setdefault("groups", KeyDefaultDict(lambda x: MeshPeerGroup(name=x)))
60
63
  super().__init__(vrf_name=vrf_name, **kwargs)
61
64
 
@@ -64,6 +67,7 @@ class VrfOptions(_FamiliesMixin, BaseMeshModel):
64
67
  as_path_relax: bool
65
68
  import_policy: Optional[str]
66
69
  export_policy: Optional[str]
70
+ l3vni: Optional[int]
67
71
  rt_import: Annotated[tuple[str, ...], Concat()]
68
72
  rt_export: Annotated[tuple[str, ...], Concat()]
69
73
  rt_import_v4: Annotated[tuple[str, ...], Concat()]
annet/mesh/executor.py CHANGED
@@ -57,6 +57,11 @@ class MeshExecutor:
57
57
  rule_global_opts = MeshGlobalOptions(rule.match, device)
58
58
  logger.debug("Running device handler: %s", handler_name)
59
59
  rule.handler(rule_global_opts)
60
+
61
+ if rule_global_opts.is_empty():
62
+ # nothing was set
63
+ continue
64
+
60
65
  try:
61
66
  global_opts = merge(global_opts, rule_global_opts)
62
67
  except MergeForbiddenError as e:
@@ -79,7 +84,7 @@ class MeshExecutor:
79
84
  rule: MatchedDirectPair,
80
85
  ports: list[tuple[str, str]],
81
86
  all_connected_ports: list[tuple[str, str]],
82
- ) -> Pair:
87
+ ) -> Optional[Pair]:
83
88
  session = MeshSession()
84
89
  handler_name = self._handler_name(rule.handler)
85
90
  logger.debug("Running direct handler: %s", handler_name)
@@ -102,6 +107,10 @@ class MeshExecutor:
102
107
  else:
103
108
  rule.handler(peer_neighbor, peer_device, session)
104
109
 
110
+ if peer_neighbor.is_empty() and peer_device.is_empty() and session.is_empty():
111
+ # nothing was set
112
+ return None
113
+
105
114
  try:
106
115
  neighbor_dto = merge(DirectPeerDTO(), peer_neighbor, session)
107
116
  except MergeForbiddenError as e:
@@ -144,6 +153,9 @@ class MeshExecutor:
144
153
  ]
145
154
  for ports in rule.port_processor(all_connected_ports):
146
155
  pair = self._execute_direct_pair(device, neighbor_device, rule, ports, all_connected_ports)
156
+ if pair is None:
157
+ # nothing was set
158
+ continue
147
159
  addr = getattr(pair.connected, "addr", None)
148
160
  if addr is None:
149
161
  raise ValueError(f"Handler `{handler_name}` returned no peer addr")
@@ -179,6 +191,9 @@ class MeshExecutor:
179
191
  peer_virtual = VirtualPeer(num=order_number)
180
192
 
181
193
  rule.handler(peer_device, peer_virtual, session)
194
+ if peer_virtual.is_empty() and peer_device.is_empty() and session.is_empty():
195
+ # nothing was set
196
+ continue
182
197
 
183
198
  try:
184
199
  virtual_dto = merge(VirtualPeerDTO(), peer_virtual, session)
@@ -232,6 +247,10 @@ class MeshExecutor:
232
247
  peer_device = IndirectPeer(rule.match_right, device)
233
248
  rule.handler(peer_connected, peer_device, session)
234
249
 
250
+ if peer_connected.is_empty() and peer_device.is_empty() and session.is_empty():
251
+ # nothing was set
252
+ continue
253
+
235
254
  try:
236
255
  connected_dto = merge(IndirectPeerDTO(), peer_connected, session)
237
256
  except MergeForbiddenError as e:
annet/mesh/peer_models.py CHANGED
@@ -3,7 +3,7 @@ from typing import Literal, Annotated, Union, Optional
3
3
  from .basemodel import BaseMeshModel, Concat, Unite
4
4
  from ..bgp_models import BFDTimers
5
5
 
6
- FamilyName = Literal["ipv4_unicast", "ipv6_unicast", "ipv4_labeled_unicast", "ipv6_labeled_unicast"]
6
+ FamilyName = Literal["ipv4_unicast", "ipv6_unicast", "ipv4_labeled_unicast", "ipv6_labeled_unicast", "l2vpn_evpn"]
7
7
 
8
8
 
9
9
  class _SharedOptionsDTO(BaseMeshModel):
@@ -53,7 +53,7 @@ class _OptionsDTO(_SharedOptionsDTO):
53
53
  advertise_bgp_static: bool
54
54
  allowas_in: bool
55
55
  auth_key: bool
56
- multihop: bool
56
+ multihop: Optional[int]
57
57
  multihop_no_nexthop_change: bool
58
58
  af_no_install: bool
59
59
  rib: bool
@@ -101,7 +101,7 @@ class IndirectPeerDTO(MeshSession, _OptionsDTO):
101
101
 
102
102
 
103
103
  class VirtualLocalDTO(_OptionsDTO):
104
- asnum: int
104
+ asnum: Union[int, str]
105
105
  pod: int
106
106
  addr: str
107
107
  description: str
annet/mesh/registry.py CHANGED
@@ -21,6 +21,9 @@ class DirectPeer(DirectPeerDTO):
21
21
  self.ports = ports
22
22
  self.all_connected_ports = all_connected_ports
23
23
 
24
+ def is_empty(self):
25
+ return self.__dict__.keys() == {"match", "device", "ports", "all_connected_ports"}
26
+
24
27
 
25
28
  class IndirectPeer(IndirectPeerDTO):
26
29
  match: MatchedArgs
@@ -31,6 +34,9 @@ class IndirectPeer(IndirectPeerDTO):
31
34
  self.match = match
32
35
  self.device = device
33
36
 
37
+ def is_empty(self):
38
+ return self.__dict__.keys() == {"match", "device"}
39
+
34
40
 
35
41
  class VirtualLocal(VirtualLocalDTO):
36
42
  match: MatchedArgs
@@ -41,10 +47,16 @@ class VirtualLocal(VirtualLocalDTO):
41
47
  self.match = match
42
48
  self.device = device
43
49
 
50
+ def is_empty(self):
51
+ return self.__dict__.keys() == {"match", "device"}
52
+
44
53
 
45
54
  class VirtualPeer(VirtualPeerDTO):
46
55
  num: int
47
56
 
57
+ def is_empty(self):
58
+ return self.__dict__.keys() == {"num"}
59
+
48
60
 
49
61
  class GlobalOptions(GlobalOptionsDTO):
50
62
  match: MatchedArgs
@@ -55,6 +67,9 @@ class GlobalOptions(GlobalOptionsDTO):
55
67
  self.match = match
56
68
  self.device = device
57
69
 
70
+ def is_empty(self):
71
+ return self.__dict__.keys() == {"match", "device"}
72
+
58
73
 
59
74
  GlobalHandler = Callable[[GlobalOptions], None]
60
75
 
@@ -11,10 +11,10 @@ from annet.rpl.statement_builder import NextHopActionValue, AsPathActionValue, C
11
11
  from .aspath import get_used_as_path_filters
12
12
  from .community import get_used_united_community_lists
13
13
  from .entities import (
14
- AsPathFilter, IpPrefixList, mangle_ranged_prefix_list_name, CommunityList, CommunityLogic, CommunityType,
15
- mangle_united_community_list_name,
14
+ AsPathFilter, IpPrefixList, CommunityList, CommunityLogic, CommunityType,
15
+ mangle_united_community_list_name, PrefixListNameGenerator,
16
16
  )
17
- from .prefix_lists import get_used_prefix_lists
17
+ from .prefix_lists import get_used_prefix_lists, new_prefix_list_name_generator
18
18
 
19
19
  FRR_RESULT_MAP = {
20
20
  ResultType.ALLOW: "permit",
@@ -51,6 +51,12 @@ class CumulusPolicyGenerator(ABC):
51
51
  def get_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
52
52
  raise NotImplementedError()
53
53
 
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
+
54
60
  @abstractmethod
55
61
  def get_community_lists(self, device: Any) -> list[CommunityList]:
56
62
  raise NotImplementedError()
@@ -61,11 +67,13 @@ class CumulusPolicyGenerator(ABC):
61
67
 
62
68
  def generate_cumulus_rpl(self, device: Any) -> Iterator[Sequence[str]]:
63
69
  policies = self.get_policies(device)
70
+ prefix_list_name_generator = new_prefix_list_name_generator(policies)
71
+
64
72
  communities = {c.name: c for c in self.get_community_lists(device)}
65
73
  yield from self._cumulus_as_path_filters(device, policies)
66
74
  yield from self._cumulus_communities(device, communities, policies)
67
- yield from self._cumulus_prefix_lists(device, policies)
68
- yield from self._cumulus_policy_config(device, communities, policies)
75
+ yield from self._cumulus_prefix_lists(device, policies, prefix_list_name_generator)
76
+ yield from self._cumulus_policy_config(device, communities, policies, prefix_list_name_generator)
69
77
 
70
78
  def _cumulus_as_path_filters(
71
79
  self,
@@ -100,11 +108,12 @@ class CumulusPolicyGenerator(ABC):
100
108
  ("le", str(match.less_equal)) if match.less_equal is not None else ()
101
109
  )
102
110
 
103
- def _cumulus_prefix_lists(self, device: Any, policies: list[RoutingPolicy]) -> Iterable[Sequence[str]]:
104
- plists = {p.name: p for p in get_used_prefix_lists(
105
- prefix_lists=self.get_prefix_lists(device),
106
- policies=policies,
107
- )}
111
+ def _cumulus_prefix_lists(
112
+ self, device: Any,
113
+ policies: list[RoutingPolicy],
114
+ prefix_list_name_generator: PrefixListNameGenerator,
115
+ ) -> Iterable[Sequence[str]]:
116
+ plists = {p.name: p for p in self.get_used_prefix_lists(device, prefix_list_name_generator)}
108
117
  if not plists.values():
109
118
  return
110
119
 
@@ -114,7 +123,7 @@ class CumulusPolicyGenerator(ABC):
114
123
  cond: SingleCondition[PrefixMatchValue]
115
124
  for cond in statement.match.find_all(MatchField.ip_prefix):
116
125
  for name in cond.value.names:
117
- mangled_name = mangle_ranged_prefix_list_name(
126
+ mangled_name = prefix_list_name_generator.get_prefix_name(
118
127
  name=name,
119
128
  greater_equal=cond.value.greater_equal,
120
129
  less_equal=cond.value.less_equal,
@@ -125,7 +134,7 @@ class CumulusPolicyGenerator(ABC):
125
134
  precessed_names.add(mangled_name)
126
135
  for cond in statement.match.find_all(MatchField.ipv6_prefix):
127
136
  for name in cond.value.names:
128
- mangled_name = mangle_ranged_prefix_list_name(
137
+ mangled_name = prefix_list_name_generator.get_prefix_name(
129
138
  name=name,
130
139
  greater_equal=cond.value.greater_equal,
131
140
  less_equal=cond.value.less_equal,
@@ -217,6 +226,7 @@ class CumulusPolicyGenerator(ABC):
217
226
  self,
218
227
  device: Any,
219
228
  condition: SingleCondition[Any],
229
+ prefix_list_name_generator: PrefixListNameGenerator,
220
230
  ) -> Iterator[Sequence[str]]:
221
231
  if condition.field == MatchField.community:
222
232
  for comm_name in self._get_match_community_names(condition):
@@ -236,7 +246,7 @@ class CumulusPolicyGenerator(ABC):
236
246
  return
237
247
  if condition.field == MatchField.ip_prefix:
238
248
  for name in condition.value.names:
239
- mangled_name = mangle_ranged_prefix_list_name(
249
+ mangled_name = prefix_list_name_generator.get_prefix_name(
240
250
  name=name,
241
251
  greater_equal=condition.value.greater_equal,
242
252
  less_equal=condition.value.less_equal,
@@ -245,7 +255,7 @@ class CumulusPolicyGenerator(ABC):
245
255
  return
246
256
  if condition.field == MatchField.ipv6_prefix:
247
257
  for name in condition.value.names:
248
- mangled_name = mangle_ranged_prefix_list_name(
258
+ mangled_name = prefix_list_name_generator.get_prefix_name(
249
259
  name=name,
250
260
  greater_equal=condition.value.greater_equal,
251
261
  less_equal=condition.value.less_equal,
@@ -268,7 +278,7 @@ class CumulusPolicyGenerator(ABC):
268
278
  action: SingleAction[CommunityActionValue],
269
279
  ) -> Iterator[Sequence[str]]:
270
280
  if action.value.replaced is not None:
271
- if action.value.added or action.value.replaced:
281
+ if action.value.added or action.value.removed:
272
282
  raise NotImplementedError(
273
283
  "Cannot set community together with add/replace on cumulus",
274
284
  )
@@ -421,11 +431,12 @@ class CumulusPolicyGenerator(ABC):
421
431
  device: Any,
422
432
  policy: RoutingPolicy,
423
433
  statement: RoutingPolicyStatement,
434
+ prefix_list_name_generator: PrefixListNameGenerator,
424
435
  ) -> Iterable[Sequence[str]]:
425
436
  yield "route-map", policy.name, FRR_RESULT_MAP[statement.result], str(statement.number)
426
437
 
427
438
  for condition in statement.match:
428
- for row in self._cumulus_policy_match(device, condition):
439
+ for row in self._cumulus_policy_match(device, condition, prefix_list_name_generator):
429
440
  yield FRR_INDENT, *row
430
441
  for action in statement.then:
431
442
  for row in self._cumulus_policy_then(communities, device, action):
@@ -439,6 +450,7 @@ class CumulusPolicyGenerator(ABC):
439
450
  device: Any,
440
451
  communities: dict[str, CommunityList],
441
452
  policies: list[RoutingPolicy],
453
+ prefix_list_name_generator: PrefixListNameGenerator,
442
454
  ) -> Iterable[Sequence[str]]:
443
455
  """ Route maps configuration """
444
456
 
@@ -454,5 +466,7 @@ class CumulusPolicyGenerator(ABC):
454
466
  raise RuntimeError(
455
467
  f"Multiple statements have same number {statement.number} for policy `{policy.name}`: "
456
468
  f"`{statement.name}` and `{applied_stmts[statement.number]}`")
457
- yield from self._cumulus_policy_statement(communities, device, policy, statement)
469
+ yield from self._cumulus_policy_statement(
470
+ communities, device, policy, statement, prefix_list_name_generator,
471
+ )
458
472
  applied_stmts[statement.number] = statement.name
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  from collections.abc import Sequence
2
3
  from dataclasses import dataclass
3
4
  from enum import Enum
@@ -56,15 +57,27 @@ def mangle_united_community_list_name(values: Sequence[str]) -> str:
56
57
  return "_OR_".join(values)
57
58
 
58
59
 
59
- def mangle_ranged_prefix_list_name(name: str, greater_equal: Optional[int], less_equal: Optional[int]) -> str:
60
- if greater_equal is less_equal is None:
61
- return name
62
- if greater_equal is None:
63
- ge_str = "unset"
64
- else:
65
- ge_str = str(greater_equal)
66
- if less_equal is None:
67
- le_str = "unset"
68
- else:
69
- le_str = str(less_equal)
70
- return f"{name}_{ge_str}_{le_str}"
60
+ 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}"
@@ -6,13 +6,14 @@ from annet.generators import PartialGenerator
6
6
  from annet.rpl import (
7
7
  CommunityActionValue,
8
8
  ResultType, RoutingPolicyStatement, RoutingPolicy, ConditionOperator, SingleCondition, SingleAction, ActionType,
9
- RouteMap, MatchField,
9
+ MatchField,
10
10
  )
11
11
  from annet.rpl.statement_builder import AsPathActionValue, NextHopActionValue, ThenField
12
12
  from annet.rpl_generators.entities import (
13
13
  arista_well_known_community,
14
- CommunityList, RDFilter, mangle_ranged_prefix_list_name, CommunityLogic, mangle_united_community_list_name,
14
+ CommunityList, RDFilter, PrefixListNameGenerator, CommunityLogic, mangle_united_community_list_name,
15
15
  )
16
+ from annet.rpl_generators.prefix_lists import new_prefix_list_name_generator
16
17
 
17
18
  HUAWEI_MATCH_COMMAND_MAP: dict[str, str] = {
18
19
  MatchField.as_path_filter: "as-path-filter {option_value}",
@@ -86,6 +87,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
86
87
  condition: SingleCondition[Any],
87
88
  communities: dict[str, CommunityList],
88
89
  rd_filters: dict[str, RDFilter],
90
+ prefix_name_generator: PrefixListNameGenerator,
89
91
  ) -> Iterator[Sequence[str]]:
90
92
  if condition.field == MatchField.community:
91
93
  if condition.operator is ConditionOperator.HAS:
@@ -134,7 +136,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
134
136
  return
135
137
  if condition.field == MatchField.ip_prefix:
136
138
  for name in condition.value.names:
137
- mangled_name = mangle_ranged_prefix_list_name(
139
+ mangled_name = prefix_name_generator.get_prefix_name(
138
140
  name=name,
139
141
  greater_equal=condition.value.greater_equal,
140
142
  less_equal=condition.value.less_equal,
@@ -143,7 +145,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
143
145
  return
144
146
  if condition.field == MatchField.ipv6_prefix:
145
147
  for name in condition.value.names:
146
- mangled_name = mangle_ranged_prefix_list_name(
148
+ mangled_name = prefix_name_generator.get_prefix_name(
147
149
  name=name,
148
150
  greater_equal=condition.value.greater_equal,
149
151
  less_equal=condition.value.less_equal,
@@ -180,7 +182,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
180
182
  action: SingleAction[CommunityActionValue],
181
183
  ) -> Iterator[Sequence[str]]:
182
184
  if action.value.replaced is not None:
183
- if action.value.added or action.value.replaced:
185
+ if action.value.added or action.value.removed:
184
186
  raise NotImplementedError(
185
187
  "Cannot set community together with add/remove on huawei",
186
188
  )
@@ -202,7 +204,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
202
204
  action: SingleAction[CommunityActionValue],
203
205
  ) -> Iterator[Sequence[str]]:
204
206
  if action.value.replaced is not None:
205
- if action.value.added or action.value.replaced:
207
+ if action.value.added or action.value.removed:
206
208
  raise NotImplementedError(
207
209
  "Cannot set large-community together with add/remove on huawei",
208
210
  )
@@ -334,6 +336,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
334
336
  device: Any,
335
337
  policy: RoutingPolicy,
336
338
  statement: RoutingPolicyStatement,
339
+ prefix_name_generator: PrefixListNameGenerator,
337
340
  ) -> Iterator[Sequence[str]]:
338
341
  if statement.number is None:
339
342
  raise RuntimeError(f"Statement number should not be empty on Huawei (found for policy: {policy.name})")
@@ -343,19 +346,21 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
343
346
  "node", statement.number
344
347
  ):
345
348
  for condition in statement.match:
346
- yield from self._huawei_match(device, condition, communities, rd_filters)
349
+ yield from self._huawei_match(device, condition, communities, rd_filters, prefix_name_generator)
347
350
  for action in statement.then:
348
351
  yield from self._huawei_then(communities, device, action)
349
352
  if statement.result is ResultType.NEXT:
350
353
  yield "goto next-node"
351
354
 
352
355
  def run_huawei(self, device):
356
+ policies = self.get_policies(device)
353
357
  communities = {c.name: c for c in self.get_community_lists(device)}
354
358
  rd_filters = {f.name: f for f in self.get_rd_filters(device)}
359
+ prefix_name_generator = new_prefix_list_name_generator(policies)
355
360
 
356
361
  for policy in self.get_policies(device):
357
362
  for statement in policy.statements:
358
- yield from self._huawei_statement(communities, rd_filters, device, policy, statement)
363
+ yield from self._huawei_statement(communities, rd_filters, device, policy, statement, prefix_name_generator)
359
364
 
360
365
  # arista
361
366
  def acl_arista(self, device):
@@ -378,6 +383,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
378
383
  condition: SingleCondition[Any],
379
384
  communities: dict[str, CommunityList],
380
385
  rd_filters: dict[str, RDFilter],
386
+ prefix_name_generator: PrefixListNameGenerator,
381
387
  ) -> Iterator[Sequence[str]]:
382
388
  if condition.field == MatchField.community:
383
389
  if condition.operator is ConditionOperator.HAS_ANY:
@@ -430,7 +436,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
430
436
  return
431
437
  if condition.field == MatchField.ip_prefix:
432
438
  for name in condition.value.names:
433
- mangled_name = mangle_ranged_prefix_list_name(
439
+ mangled_name = prefix_name_generator.get_prefix_name(
434
440
  name=name,
435
441
  greater_equal=condition.value.greater_equal,
436
442
  less_equal=condition.value.less_equal,
@@ -439,7 +445,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
439
445
  return
440
446
  if condition.field == MatchField.ipv6_prefix:
441
447
  for name in condition.value.names:
442
- mangled_name = mangle_ranged_prefix_list_name(
448
+ mangled_name = prefix_name_generator.get_prefix_name(
443
449
  name=name,
444
450
  greater_equal=condition.value.greater_equal,
445
451
  less_equal=condition.value.less_equal,
@@ -477,7 +483,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
477
483
  action: SingleAction[CommunityActionValue],
478
484
  ) -> Iterator[Sequence[str]]:
479
485
  if action.value.replaced is not None:
480
- if action.value.added or action.value.replaced:
486
+ if action.value.added or action.value.removed:
481
487
  raise NotImplementedError(
482
488
  "Cannot set community together with add/remove on arista",
483
489
  )
@@ -500,7 +506,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
500
506
  action: SingleAction[CommunityActionValue],
501
507
  ) -> Iterator[Sequence[str]]:
502
508
  if action.value.replaced is not None:
503
- if action.value.added or action.value.replaced:
509
+ if action.value.added or action.value.removed:
504
510
  raise NotImplementedError(
505
511
  "Cannot set large-community together with add/remove on arista",
506
512
  )
@@ -653,6 +659,7 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
653
659
  device: Any,
654
660
  policy: RoutingPolicy,
655
661
  statement: RoutingPolicyStatement,
662
+ prefix_name_generator: PrefixListNameGenerator,
656
663
  ) -> Iterator[Sequence[str]]:
657
664
  with self.block(
658
665
  "route-map",
@@ -661,16 +668,20 @@ class RoutingPolicyGenerator(PartialGenerator, ABC):
661
668
  statement.number,
662
669
  ):
663
670
  for condition in statement.match:
664
- yield from self._arista_match(device, condition, communities, rd_filters)
671
+ yield from self._arista_match(device, condition, communities, rd_filters, prefix_name_generator)
665
672
  for action in statement.then:
666
673
  yield from self._arista_then(communities, device, action)
667
674
  if statement.result is ResultType.NEXT:
668
675
  yield "continue"
669
676
 
670
677
  def run_arista(self, device):
678
+ policies = self.get_policies(device)
679
+ prefix_name_generator = new_prefix_list_name_generator(policies)
671
680
  communities = {c.name: c for c in self.get_community_lists(device)}
672
681
  rd_filters = {f.name: f for f in self.get_rd_filters(device)}
673
682
 
674
- for policy in self.get_policies(device):
683
+ for policy in policies:
675
684
  for statement in policy.statements:
676
- yield from self._arista_statement(communities, rd_filters, device, policy, statement)
685
+ yield from self._arista_statement(
686
+ communities, rd_filters, device, policy, statement, prefix_name_generator,
687
+ )
@@ -4,20 +4,28 @@ from ipaddress import ip_interface
4
4
  from typing import Any, Literal
5
5
 
6
6
  from annet.generators import PartialGenerator
7
- from annet.rpl import RouteMap, PrefixMatchValue, MatchField, SingleCondition, RoutingPolicy
8
- from .entities import IpPrefixList, mangle_ranged_prefix_list_name
7
+ from annet.rpl import PrefixMatchValue, MatchField, SingleCondition, RoutingPolicy
8
+ from .entities import IpPrefixList, PrefixListNameGenerator
9
9
 
10
10
 
11
- def get_used_prefix_lists(prefix_lists: Sequence[IpPrefixList], policies: list[RoutingPolicy]) -> list[IpPrefixList]:
12
- plist_map = {c.name: c for c in prefix_lists}
13
- used_names = set()
11
+ def get_used_prefix_lists(
12
+ prefix_lists: Sequence[IpPrefixList], name_generator: PrefixListNameGenerator,
13
+ ) -> list[IpPrefixList]:
14
+ return [c for c in prefix_lists if name_generator.is_used(c.name)]
15
+
16
+
17
+ def new_prefix_list_name_generator(policies: list[RoutingPolicy]) -> PrefixListNameGenerator:
18
+ name_gen = PrefixListNameGenerator()
14
19
  for policy in policies:
15
20
  for statement in policy.statements:
21
+ condition: SingleCondition[PrefixMatchValue]
16
22
  for condition in statement.match.find_all(MatchField.ipv6_prefix):
17
- used_names.update(condition.value.names)
23
+ for name in condition.value.names:
24
+ name_gen.add_prefix(name, condition.value.greater_equal, condition.value.less_equal)
18
25
  for condition in statement.match.find_all(MatchField.ip_prefix):
19
- used_names.update(condition.value.names)
20
- return [plist_map[name] for name in sorted(used_names)]
26
+ for name in condition.value.names:
27
+ name_gen.add_prefix(name, condition.value.greater_equal, condition.value.less_equal)
28
+ return name_gen
21
29
 
22
30
 
23
31
  class PrefixListFilterGenerator(PartialGenerator, ABC):
@@ -31,10 +39,10 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
31
39
  def get_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
32
40
  raise NotImplementedError()
33
41
 
34
- def get_used_prefix_lists(self, device: Any) -> Sequence[IpPrefixList]:
42
+ def get_used_prefix_lists(self, device: Any, name_generator: PrefixListNameGenerator) -> Sequence[IpPrefixList]:
35
43
  return get_used_prefix_lists(
36
44
  prefix_lists=self.get_prefix_lists(device),
37
- policies=self.get_policies(device),
45
+ name_generator=name_generator,
38
46
  )
39
47
 
40
48
  # huawei
@@ -57,7 +65,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
57
65
  "ip",
58
66
  prefix_type,
59
67
  name,
60
- f"index {i * 10 + 5}",
68
+ f"index {i * 5 + 5}",
61
69
  "permit",
62
70
  str(addr_mask.ip).upper(),
63
71
  str(addr_mask.network.prefixlen),
@@ -68,15 +76,16 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
68
76
  )
69
77
 
70
78
  def run_huawei(self, device: Any):
71
- plists = {p.name: p for p in self.get_used_prefix_lists(device)}
72
79
  policies = self.get_policies(device)
80
+ name_generator = new_prefix_list_name_generator(policies)
81
+ plists = {p.name: p for p in self.get_used_prefix_lists(device, name_generator)}
73
82
  precessed_names = set()
74
83
  for policy in policies:
75
84
  for statement in policy.statements:
76
85
  cond: SingleCondition[PrefixMatchValue]
77
86
  for cond in statement.match.find_all(MatchField.ip_prefix):
78
87
  for name in cond.value.names:
79
- mangled_name = mangle_ranged_prefix_list_name(
88
+ mangled_name = name_generator.get_prefix_name(
80
89
  name=name,
81
90
  greater_equal=cond.value.greater_equal,
82
91
  less_equal=cond.value.less_equal,
@@ -87,7 +96,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
87
96
  precessed_names.add(mangled_name)
88
97
  for cond in statement.match.find_all(MatchField.ipv6_prefix):
89
98
  for name in cond.value.names:
90
- mangled_name = mangle_ranged_prefix_list_name(
99
+ mangled_name = name_generator.get_prefix_name(
91
100
  name=name,
92
101
  greater_equal=cond.value.greater_equal,
93
102
  less_equal=cond.value.less_equal,
@@ -117,7 +126,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
117
126
  prefix_type,
118
127
  "prefix-list",
119
128
  name,
120
- f"seq {i * 10 + 5}",
129
+ f"seq {i * 5 + 5}",
121
130
  "permit",
122
131
  str(addr_mask.ip).upper(),
123
132
  str(addr_mask.network.prefixlen),
@@ -128,15 +137,16 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
128
137
  )
129
138
 
130
139
  def run_arista(self, device: Any):
131
- plists = {p.name: p for p in self.get_used_prefix_lists(device)}
132
140
  policies = self.get_policies(device)
141
+ name_generator = new_prefix_list_name_generator(policies)
142
+ plists = {p.name: p for p in self.get_used_prefix_lists(device, name_generator)}
133
143
  precessed_names = set()
134
144
  for policy in policies:
135
145
  for statement in policy.statements:
136
146
  cond: SingleCondition[PrefixMatchValue]
137
147
  for cond in statement.match.find_all(MatchField.ip_prefix):
138
148
  for name in cond.value.names:
139
- mangled_name = mangle_ranged_prefix_list_name(
149
+ mangled_name = name_generator.get_prefix_name(
140
150
  name=name,
141
151
  greater_equal=cond.value.greater_equal,
142
152
  less_equal=cond.value.less_equal,
@@ -147,7 +157,7 @@ class PrefixListFilterGenerator(PartialGenerator, ABC):
147
157
  precessed_names.add(mangled_name)
148
158
  for cond in statement.match.find_all(MatchField.ipv6_prefix):
149
159
  for name in cond.value.names:
150
- mangled_name = mangle_ranged_prefix_list_name(
160
+ mangled_name = name_generator.get_prefix_name(
151
161
  name=name,
152
162
  greater_equal=cond.value.greater_equal,
153
163
  less_equal=cond.value.less_equal,
@@ -20,7 +20,7 @@
20
20
  crypto key generate rsa %timeout=60
21
21
  dialog: Do you really want to replace them? [yes/no]: ::: no
22
22
  dialog: How many bits in the modulus [512]: ::: 2048
23
- no username * privilege * secret 5 *
23
+ no username * privilege * secret * *
24
24
  dialog: This operation will remove all username related configurations with same name.Do you want to continue? [confirm] ::: Y %send_nl=0
25
25
 
26
26
  copy running-config startup-config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: annet
3
- Version: 0.16.30
3
+ Version: 0.16.32
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  annet/__init__.py,sha256=W8kkZ3Axu-6VJwgQ0cn4UeOVNy6jab0cqgHKLQny1D0,2141
2
2
  annet/annet.py,sha256=TMdEuM7GJQ4TjRVmuK3bCTZN-21lxjQ9sXqEdILUuBk,725
3
3
  annet/argparse.py,sha256=v1MfhjR0B8qahza0WinmXClpR8UiDFhmwDDWtNroJPA,12855
4
- annet/bgp_models.py,sha256=5FnXMeIbZJIQRdUk6Jf2f3YlkQSLAgmBH8bq2o94MUM,9457
4
+ annet/bgp_models.py,sha256=I9DQpf3LU71RBk9kCUdA-RnGkA3eORngYkC3_obsLmk,9669
5
5
  annet/cli.py,sha256=hDpjIr3w47lgQ_CvCQS1SXFDK-SJrf5slbT__5u6GIA,12342
6
6
  annet/cli_args.py,sha256=KQlihxSl-Phhq1-9oJDdNSbIllEX55LlPfH6viEKOuw,13483
7
7
  annet/connectors.py,sha256=-Lghz3PtWCBU8Ohrp0KKQcmm1AUZtN0EnOaZ6IQgCQI,5105
@@ -32,8 +32,8 @@ annet/adapters/netbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
32
32
  annet/adapters/netbox/provider.py,sha256=3IrfZ6CfCxf-lTnJlIC2TQ8M_rDxOB_B7HGXZ92vAgA,1643
33
33
  annet/adapters/netbox/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  annet/adapters/netbox/common/client.py,sha256=PaxHG4W9H8_uunIwMBNYkLq4eQJYoO6p6gY-ciQs7Nc,2563
35
- annet/adapters/netbox/common/manufacturer.py,sha256=1kmw3YzitXwOh4zitsWfZda4CQiMr4CnAxkUkUm_z3A,1678
36
- annet/adapters/netbox/common/models.py,sha256=E-w1ptCpcrJcdAENcgJjXF-sVhUu2_7fZPNaDuhYnuw,7364
35
+ annet/adapters/netbox/common/manufacturer.py,sha256=LAPT6OlV_ew96GhtwNrCpeiT0IGrg2_9__MMdZk431U,1733
36
+ annet/adapters/netbox/common/models.py,sha256=Xq6Dc3kY9_QyvS9DiKEq1AxjTxiF4qEKhs1EMtBw-k4,7384
37
37
  annet/adapters/netbox/common/query.py,sha256=ziUFM7cUEbEIf3k1szTll4aO-OCUa-2Ogxbebi7Tegs,646
38
38
  annet/adapters/netbox/common/status_client.py,sha256=W4nTb2yvBlJ2UkWUmUhKQ2PaSQb1shjhHj5ebb4s2s4,591
39
39
  annet/adapters/netbox/common/storage_opts.py,sha256=5tt6wxUUJTIzNbOVXMnYBwZedNAIqYlve3YWl6GdbZM,1197
@@ -67,7 +67,7 @@ annet/annlib/rbparser/platform.py,sha256=hnxznTfV9txXi1PkR1hZrprTrQJvlwgqXVL8vXk
67
67
  annet/annlib/rbparser/syntax.py,sha256=iZ7Y-4QQBw4L3UtjEh54qisiRDhobl7HZxFNdP8mi54,3577
68
68
  annet/annlib/rulebook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
69
  annet/annlib/rulebook/common.py,sha256=bx_Iwui-JJeyctUPF1OsEll0Aa-IQZadBPQjaeuoWgw,16638
70
- annet/api/__init__.py,sha256=dgdyBDeFhBVBwKB8QVOOTZ2LZvi0fEcVGfOQEUQyDHo,33863
70
+ annet/api/__init__.py,sha256=WGpVMfIxVy9F_jH6nqHSQypEEcsSNa9yF2WFEwhUVwI,34156
71
71
  annet/configs/context.yml,sha256=RVLrKLIHpCty7AGwOnmqf7Uu0iZQCn-AjYhophDJer8,259
72
72
  annet/configs/logging.yaml,sha256=EUagfir99QqA73Scc3k7sfQccbU3E1SvEQdyhLFtCl4,997
73
73
  annet/generators/__init__.py,sha256=rVHHDTPKHPZsml1eNEAj3o-8RweFTN8J7LX3tKMXdIY,16402
@@ -82,14 +82,14 @@ annet/generators/result.py,sha256=zMAvGOYQU803bGy6datZduHLgrEqK2Zba_Jcf9Qn9p0,49
82
82
  annet/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  annet/generators/common/initial.py,sha256=qYBxXFhyOPy34cxc6hsIXseod-lYCmmbuNHpM0uteY0,1244
84
84
  annet/mesh/__init__.py,sha256=lcgdnBIxc2MAN7Er1bcErEKPqrjWO4uIp_1FldMXTYg,557
85
- annet/mesh/basemodel.py,sha256=TXVINzmvKlyxiVcIdOaoewY_c4TvuMstluqfzlbNmPU,4859
86
- annet/mesh/device_models.py,sha256=YcL6_vGjnt67BTovN8Eq38U5wGcbJDhqiq8613WpYtQ,3381
87
- annet/mesh/executor.py,sha256=V5GR_m9T6hVWR5sFY-x4JkS0LpI6UH5Q7HxuhC8KXFE,16299
85
+ annet/mesh/basemodel.py,sha256=E6NTOneiMDwB1NCpjDRECoaeQ0f3n_fmTLnKTrSHTU4,4917
86
+ annet/mesh/device_models.py,sha256=KkbD8tuWfnh4gR8iOV9C7ihLR4jxjyuF52AU2DPPLXA,3627
87
+ annet/mesh/executor.py,sha256=0RLsdtldozoFgDGFuhU5mdckuMwGg0y_DFIji1RfTQg,16969
88
88
  annet/mesh/match_args.py,sha256=CR3kdIV9NGtyk9E2JbcOQ3TRuYEryTWP3m2yCo2VCWg,5751
89
89
  annet/mesh/models_converter.py,sha256=3q2zs7K8S3pfYSUKKRdtl5CJGbeg4TtYxofAVs_MBsk,3085
90
- annet/mesh/peer_models.py,sha256=uD3h7HWl9_zNdBlQqZCMcjCnzjr8dTbSFqc8216JDu0,2735
90
+ annet/mesh/peer_models.py,sha256=9vn5ENiEZqOZFRFSOJReT8E3E2GzBte628mkmS3cplI,2770
91
91
  annet/mesh/port_processor.py,sha256=RHiMS5W8qoDkTKiarQ748bcr8bNx4g_R4Y4vZg2k4TU,478
92
- annet/mesh/registry.py,sha256=VvZKZ5ujGcRXvWFP051rr2KoXI8OrTivkhpTOrUm33A,9087
92
+ annet/mesh/registry.py,sha256=BCRIOrINP0krcgvF59uFJqyhmSviQ03GSzWpxLVYlIg,9527
93
93
  annet/rpl/__init__.py,sha256=0kcIktE3AmS0rlm9xzVDf53xk08OeZXgD-6ZLCt_KCs,731
94
94
  annet/rpl/action.py,sha256=PY6W66j908RuqQ1_ioxayqVN-70rxDk5Z59EGHtxI98,1246
95
95
  annet/rpl/condition.py,sha256=MJri4MbWtPkLHIsLMAtsIEF7e8IAS9dIImjmJs5vS5U,3418
@@ -101,11 +101,11 @@ annet/rpl/statement_builder.py,sha256=sVGOYsCV0s_SFQUy2WtUyQqKy5H4MOfmRCJWGj-UOJ
101
101
  annet/rpl_generators/__init__.py,sha256=ZLWs-flcpyIbdhxSDfNt-ORDrLe8ins25sWdXTWeUoA,748
102
102
  annet/rpl_generators/aspath.py,sha256=kZakwPLfGGiXu9fC6I1z-pvy7Fe-4dy93_-lYcx39_4,2038
103
103
  annet/rpl_generators/community.py,sha256=SWpaOvoQUNISuRm41-IvGPFmntvgFv9ee4zegsMyeo0,11496
104
- annet/rpl_generators/cumulus_frr.py,sha256=yMxm264PZq8MnSYQ2jmLv2CSa2Jl2ais_7XBTXKowj0,20053
105
- annet/rpl_generators/entities.py,sha256=052ggNCGbEE-m--MdzLJhJ-1ch6IxDsL6xnMH0OHgtg,1491
104
+ annet/rpl_generators/cumulus_frr.py,sha256=iY4e5gEVVlTbUsEqAHroS76ryRixewNBBzIVJRcfeVs,20854
105
+ annet/rpl_generators/entities.py,sha256=DIpgAQ8Tslo2hq6iFBaYkJX12BFBiccN8GOaRVxR1Uk,1985
106
106
  annet/rpl_generators/execute.py,sha256=wS6e6fwcPWywsHB0gBMqZ17eF0s4YOBgDgwPB_cr5Rw,431
107
- annet/rpl_generators/policy.py,sha256=YbhlTHNdv-GrpWv05iXMtbX-AdylWSZgJuTuzQREz7E,31896
108
- annet/rpl_generators/prefix_lists.py,sha256=bLacWP4M303mefZXK04VL7R_uIukZJ4itB5hKpFzCwY,6692
107
+ annet/rpl_generators/policy.py,sha256=NeqB0reRN_KuY8LYkeGT3dRPe2HFDT9RfmVy5fcA3zw,32570
108
+ annet/rpl_generators/prefix_lists.py,sha256=GyNbqXvRVGiuetLSXG2kQidRUXPMEAhR74AJJFSDQZc,7244
109
109
  annet/rpl_generators/rd.py,sha256=YGXgx1D2D0-pixgspXJzA6NvW8lx3AmHMxIY2l5rraI,1457
110
110
  annet/rulebook/__init__.py,sha256=oafL5HC8QHdkO9CH2q_fxohPMxOgjn-dNQa5kPjuqsA,3942
111
111
  annet/rulebook/common.py,sha256=zK1s2c5lc5HQbIlMUQ4HARQudXSgOYiZ_Sxc2I_tHqg,721
@@ -143,7 +143,7 @@ annet/rulebook/texts/aruba.rul,sha256=zvGVpoYyJvMoL0fb1NQ8we_GCLZXno8nwWpZIOScLQ
143
143
  annet/rulebook/texts/b4com.deploy,sha256=SVX8-yLHM90tJC4M-ekpGuGM1aQZW3euSGyg67l--R0,781
144
144
  annet/rulebook/texts/b4com.order,sha256=G3aToAIHHzKzDCM3q7_lyr9wJvuVOXVbVvF3wm5PiTE,707
145
145
  annet/rulebook/texts/b4com.rul,sha256=5mqyUg_oLRSny2iH6QdhfDWVu6kzgDURtlSATD7DFno,1056
146
- annet/rulebook/texts/cisco.deploy,sha256=XvXWeOMahE8Uc9RF0xkJj8jGknD4vit8H_f24ubPX7w,1226
146
+ annet/rulebook/texts/cisco.deploy,sha256=Hu0NkcGv3f1CWUrnbzI3eQOPXJxtH4NNOPRV68IrW4U,1226
147
147
  annet/rulebook/texts/cisco.order,sha256=OvNHMNqkCc-DN2dEjLCTKv_7ZhiaHt4q2X4Y4Z8dvR4,1901
148
148
  annet/rulebook/texts/cisco.rul,sha256=jgL5_xnSwd_H4E8cx4gcneSvJC5W1zz6_BWSb64iuxI,3017
149
149
  annet/rulebook/texts/huawei.deploy,sha256=azEC6_jQRzwnTSrNgag0hHh6L7hezS_eMk6ZDZfWyXI,10444
@@ -175,10 +175,10 @@ annet_generators/rpl_example/generator.py,sha256=zndIGfV4ZlTxPgAGYs7bMQvTc_tYScO
175
175
  annet_generators/rpl_example/items.py,sha256=Ez1RF5YhcXNCusBmeApIjRL3rBlMazNZd29Gpw1_IsA,766
176
176
  annet_generators/rpl_example/mesh.py,sha256=z_WgfDZZ4xnyh3cSf75igyH09hGvtexEVwy1gCD_DzA,288
177
177
  annet_generators/rpl_example/route_policy.py,sha256=z6nPb0VDeQtKD1NIg9sFvmUxBD5tVs2frfNIuKdM-5c,2318
178
- annet-0.16.30.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
179
- annet-0.16.30.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
180
- annet-0.16.30.dist-info/METADATA,sha256=HEQle9FUcien2kzE20RedAMAzPAfatx1Hw852aelJiA,854
181
- annet-0.16.30.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
182
- annet-0.16.30.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
183
- annet-0.16.30.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
184
- annet-0.16.30.dist-info/RECORD,,
178
+ annet-0.16.32.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
179
+ annet-0.16.32.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
180
+ annet-0.16.32.dist-info/METADATA,sha256=AuXHwlU5wtRbDB9bSbMuU0AgcIYa5Xt_oAjBm70YVsg,854
181
+ annet-0.16.32.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
182
+ annet-0.16.32.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
183
+ annet-0.16.32.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
184
+ annet-0.16.32.dist-info/RECORD,,