annet 3.4.0__py3-none-any.whl → 3.4.2__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 (50) hide show
  1. annet/__init__.py +1 -1
  2. annet/adapters/netbox/common/models.py +13 -5
  3. annet/annlib/filter_acl.py +2 -1
  4. annet/annlib/patching.py +1 -1
  5. annet/annlib/rbparser/syntax.py +2 -1
  6. annet/api/__init__.py +2 -3
  7. annet/bgp_models.py +9 -1
  8. annet/diff.py +2 -2
  9. annet/gen.py +2 -1
  10. annet/generators/__init__.py +11 -7
  11. annet/generators/base.py +2 -1
  12. annet/mesh/device_models.py +6 -0
  13. annet/mesh/executor.py +8 -16
  14. annet/mesh/models_converter.py +1 -1
  15. annet/mesh/peer_models.py +1 -5
  16. annet/parallel.py +0 -6
  17. annet/rpl/match_builder.py +1 -1
  18. annet/rpl_generators/aspath.py +16 -0
  19. annet/rpl_generators/community.py +37 -3
  20. annet/rpl_generators/policy.py +340 -5
  21. annet/rpl_generators/prefix_lists.py +51 -1
  22. annet/rpl_generators/rd.py +16 -0
  23. annet/rulebook/juniper/__init__.py +1 -1
  24. annet/rulebook/texts/cisco.order +2 -0
  25. annet/rulebook/texts/iosxr.order +21 -4
  26. annet/vendors/base.py +1 -1
  27. annet/vendors/library/arista.py +1 -1
  28. annet/vendors/library/aruba.py +1 -1
  29. annet/vendors/library/b4com.py +1 -1
  30. annet/vendors/library/cisco.py +1 -1
  31. annet/vendors/library/h3c.py +1 -1
  32. annet/vendors/library/huawei.py +1 -1
  33. annet/vendors/library/iosxr.py +1 -1
  34. annet/vendors/library/juniper.py +1 -1
  35. annet/vendors/library/nexus.py +1 -1
  36. annet/vendors/library/nokia.py +1 -1
  37. annet/vendors/library/optixtrans.py +1 -1
  38. annet/vendors/library/pc.py +1 -1
  39. annet/vendors/library/ribbon.py +1 -1
  40. annet/vendors/library/routeros.py +1 -1
  41. annet/vendors/registry.py +1 -1
  42. annet/{annlib → vendors}/tabparser.py +3 -3
  43. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/METADATA +1 -1
  44. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/RECORD +50 -50
  45. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/WHEEL +1 -1
  46. annet_generators/rpl_example/items.py +13 -5
  47. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/entry_points.txt +0 -0
  48. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/licenses/AUTHORS +0 -0
  49. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/licenses/LICENSE +0 -0
  50. {annet-3.4.0.dist-info → annet-3.4.2.dist-info}/top_level.txt +0 -0
annet/__init__.py CHANGED
@@ -11,7 +11,7 @@ from contextlog import patch_logging, patch_threading
11
11
  from valkit.python import valid_logging_level
12
12
 
13
13
  import annet.argparse
14
- from annet.annlib import tabparser # pylint: disable=unused-import
14
+ from annet.vendors import tabparser # pylint: disable=unused-import
15
15
  from annet.annlib.errors import ( # pylint: disable=wrong-import-position
16
16
  DeployCancelled,
17
17
  ExecError,
@@ -151,14 +151,22 @@ class Interface(Entity, Generic[_IpAddressT]):
151
151
  speed: int | None = None
152
152
 
153
153
  def add_addr(self, address_mask: str, vrf: str | None) -> None:
154
+ addr = ip_interface(address_mask)
155
+
156
+ # when comparing ip addressess
157
+ # we dont care about the vrf of an addr
158
+ # because the actual vrf will be
159
+ # determined by an interface's vrf
160
+ # and there can be only one
161
+ #
162
+ # also we dont care about a mask
163
+ # because no device will allow
164
+ # the same addr with multiple masks
154
165
  for existing_addr in self.ip_addresses:
155
- if existing_addr.address == address_mask and (
156
- (existing_addr.vrf is None and vrf is None) or
157
- (existing_addr.vrf is not None and existing_addr.vrf.name == vrf)
158
- ):
166
+ existing = ip_interface(existing_addr.address)
167
+ if existing.ip == addr.ip:
159
168
  return
160
169
 
161
- addr = ip_interface(address_mask)
162
170
  vrf_obj = vrf_object(vrf)
163
171
  if isinstance(addr, IPv6Interface):
164
172
  family = IpFamily(value=6, label="IPv6")
@@ -2,7 +2,8 @@ import collections
2
2
  import re
3
3
  import typing
4
4
 
5
- from . import patching, tabparser
5
+ from . import patching
6
+ from annet.vendors import tabparser
6
7
  from .diff import diff_ops, ops_sign
7
8
  from .rbparser import acl
8
9
 
annet/annlib/patching.py CHANGED
@@ -17,7 +17,7 @@ from .rbparser import platform
17
17
  from .rbparser.ordering import compile_ordering_text
18
18
  from .rulebook.common import call_diff_logic
19
19
  from .rulebook.common import default as common_default
20
- from .tabparser import CommonFormatter
20
+ from annet.vendors.tabparser import CommonFormatter
21
21
  from .types import Diff, Op
22
22
  from ..vendors import registry_connector
23
23
 
@@ -2,7 +2,8 @@ import functools
2
2
  import re
3
3
  from collections import OrderedDict as odict
4
4
 
5
- from annet.annlib import lib, tabparser
5
+ from annet.annlib import lib
6
+ from annet.vendors import tabparser
6
7
 
7
8
 
8
9
  # =====
annet/api/__init__.py CHANGED
@@ -31,7 +31,8 @@ from annet import cli_args
31
31
  from annet import diff as ann_diff
32
32
  from annet import filtering
33
33
  from annet import gen as ann_gen
34
- from annet import patching, rulebook, tabparser, tracing
34
+ from annet import patching, rulebook, tracing
35
+ from annet.vendors import tabparser
35
36
  from annet.annlib import jsontools
36
37
  from annet.annlib.netdev.views.hardware import HardwareView
37
38
  from annet.annlib.types import GeneratorType
@@ -188,8 +189,6 @@ def _print_pre_as_diff(pre, show_rules, indent, file=None, _level=0):
188
189
  (Op.AFFECTED, colorama.Fore.CYAN + " "),
189
190
  ]:
190
191
  items = content["items"].items()
191
- if not content["attrs"]["multiline"]:
192
- items = sorted(items, key=itemgetter(0))
193
192
  for (_, diff) in items: # pylint: disable=redefined-outer-name
194
193
  if show_rules and not rule_printed and not raw_rule == "__MULTILINE_BODY__":
195
194
  print("%s%s# %s%s%s" % (colorama.Style.BRIGHT, colorama.Fore.BLACK, (indent * _level),
annet/bgp_models.py CHANGED
@@ -139,7 +139,15 @@ class BFDTimers:
139
139
  multiplier: int = 4
140
140
 
141
141
 
142
- Family = Literal["ipv4_unicast", "ipv6_unicast", "ipv4_labeled_unicast", "ipv6_labeled_unicast", "l2vpn_evpn"]
142
+ Family = Literal[
143
+ "ipv4_unicast",
144
+ "ipv6_unicast",
145
+ "ipv4_vpn_unicast",
146
+ "ipv6_vpn_unicast",
147
+ "ipv4_labeled_unicast",
148
+ "ipv6_labeled_unicast",
149
+ "l2vpn_evpn",
150
+ ]
143
151
 
144
152
 
145
153
  @dataclass(frozen=True)
annet/diff.py CHANGED
@@ -6,7 +6,7 @@ from itertools import groupby
6
6
  from pathlib import Path
7
7
  from typing import Any, Dict, Generator, List, Mapping, Optional, Protocol, Tuple, Union
8
8
 
9
- from annet import cli_args, filtering, patching, rulebook, tabparser
9
+ from annet import cli_args, filtering, patching, rulebook
10
10
  from annet.annlib import jsontools
11
11
  from annet.annlib.diff import ( # pylint: disable=unused-import
12
12
  colorize_line,
@@ -22,7 +22,7 @@ from annet.connectors import CachedConnector
22
22
  from annet.output import output_driver_connector
23
23
  from annet.storage import Device
24
24
  from annet.types import Diff, PCDiff, PCDiffFile
25
- from annet.vendors import registry_connector
25
+ from annet.vendors import registry_connector, tabparser
26
26
 
27
27
  from .gen import Loader, old_new
28
28
 
annet/gen.py CHANGED
@@ -25,7 +25,8 @@ from typing import (
25
25
  import tabulate
26
26
  from contextlog import get_logger
27
27
 
28
- from annet import generators, implicit, patching, tabparser, tracing
28
+ from annet import generators, implicit, patching, tracing
29
+ from annet.vendors import tabparser
29
30
  from annet.annlib import jsontools
30
31
  from annet.annlib.rbparser.acl import compile_acl_text
31
32
  from annet.cli_args import DeployOptions, GenOptions, ShowGenOptions
@@ -11,7 +11,7 @@ from typing import FrozenSet, Iterable, List, Optional, Union
11
11
 
12
12
  from contextlog import get_logger
13
13
 
14
- from annet import patching, tabparser, tracing
14
+ from annet import patching, tracing
15
15
  from annet.annlib.rbparser.acl import compile_acl_text
16
16
  from annet.cli_args import GenSelectOptions, ShowGeneratorsOptions
17
17
  from annet.lib import get_context
@@ -24,7 +24,7 @@ from annet.types import (
24
24
  GeneratorPartialRunArgs,
25
25
  GeneratorResult,
26
26
  )
27
- from annet.vendors import registry_connector
27
+ from annet.vendors import registry_connector, tabparser
28
28
 
29
29
  from .base import BaseGenerator
30
30
  from .base import ParamsList as ParamsList
@@ -313,17 +313,20 @@ def _run_entire_generator(gen: "Entire", device: "Device") -> Optional[Generator
313
313
 
314
314
  logger.info("Generating ENTIRE ...")
315
315
 
316
- output = gen(device)
316
+ gen_output = gen(device)
317
+ gen_reload = gen.get_reload_cmds(device)
318
+ gen_is_safe = gen.is_safe(device)
319
+ gen_prio = gen.prio
317
320
 
318
321
  return GeneratorEntireResult(
319
322
  name=gen.__class__.__name__,
320
323
  tags=gen.TAGS,
321
324
  path=path,
322
- output=output,
323
- reload=gen.get_reload_cmds(device),
324
- prio=gen.prio,
325
+ output=gen_output,
326
+ reload=gen_reload,
327
+ prio=gen_prio,
325
328
  perf=pm.last_result,
326
- is_safe=gen.is_safe(device),
329
+ is_safe=gen_is_safe,
327
330
  )
328
331
 
329
332
 
@@ -363,6 +366,7 @@ def _run_json_fragment_generator(
363
366
 
364
367
  config = gen(device)
365
368
  reload_cmds = gen.get_reload_cmds(device)
369
+
366
370
  return GeneratorJSONFragmentResult(
367
371
  name=gen.__class__.__name__,
368
372
  tags=gen.TAGS,
annet/generators/base.py CHANGED
@@ -5,7 +5,8 @@ import contextlib
5
5
  import textwrap
6
6
  from typing import Union, List
7
7
 
8
- from annet import tabparser, tracing
8
+ from annet import tracing
9
+ from annet.vendors import tabparser
9
10
  from annet.tracing import tracing_connector
10
11
  from .exceptions import InvalidValueFromGenerator
11
12
 
@@ -40,12 +40,16 @@ class _FamiliesMixin:
40
40
  def __init__(self, **kwargs):
41
41
  kwargs.setdefault("ipv4_unicast", FamilyOptions(family="ipv4_unicast"))
42
42
  kwargs.setdefault("ipv6_unicast", FamilyOptions(family="ipv6_unicast"))
43
+ kwargs.setdefault("ipv4_vpn_unicast", FamilyOptions(family="ipv4_vpn_unicast"))
44
+ kwargs.setdefault("ipv6_vpn_unicast", FamilyOptions(family="ipv6_vpn_unicast"))
43
45
  kwargs.setdefault("ipv4_labeled_unicast", FamilyOptions(family="ipv4_labeled_unicast"))
44
46
  kwargs.setdefault("ipv6_labeled_unicast", FamilyOptions(family="ipv6_labeled_unicast"))
45
47
  kwargs.setdefault("l2vpn_evpn", FamilyOptions(family="l2vpn_evpn"))
46
48
  super().__init__(**kwargs)
47
49
  ipv4_unicast: Annotated[FamilyOptions, Merge()]
48
50
  ipv6_unicast: Annotated[FamilyOptions, Merge()]
51
+ ipv4_vpn_unicast: Annotated[FamilyOptions, Merge()]
52
+ ipv6_vpn_unicast: Annotated[FamilyOptions, Merge()]
49
53
  ipv4_labeled_unicast: Annotated[FamilyOptions, Merge()]
50
54
  ipv6_labeled_unicast: Annotated[FamilyOptions, Merge()]
51
55
  l2vpn_evpn: Annotated[FamilyOptions, Merge()]
@@ -55,6 +59,8 @@ class VrfOptions(_FamiliesMixin, BaseMeshModel):
55
59
  def __init__(self, vrf_name: str, **kwargs):
56
60
  kwargs.setdefault("ipv4_unicast", FamilyOptions(family="ipv4_unicast", vrf_name=vrf_name))
57
61
  kwargs.setdefault("ipv6_unicast", FamilyOptions(family="ipv6_unicast", vrf_name=vrf_name))
62
+ kwargs.setdefault("ipv4_vpn_unicast", FamilyOptions(family="ipv4_unicast", vrf_name=vrf_name))
63
+ kwargs.setdefault("ipv6_vpn_unicast", FamilyOptions(family="ipv6_unicast", vrf_name=vrf_name))
58
64
  kwargs.setdefault("ipv4_labeled_unicast", FamilyOptions(family="ipv4_labeled_unicast", vrf_name=vrf_name))
59
65
  kwargs.setdefault("ipv6_labeled_unicast", FamilyOptions(family="ipv6_labeled_unicast", vrf_name=vrf_name))
60
66
  kwargs.setdefault("l2vpn_evpn", FamilyOptions(family="l2vpn_evpn", vrf_name=vrf_name))
annet/mesh/executor.py CHANGED
@@ -194,7 +194,7 @@ class MeshExecutor:
194
194
  for rule in self._registry.lookup_virtual(device.fqdn):
195
195
  for order_number in rule.num:
196
196
  handler_name = self._handler_name(rule.handler)
197
- logger.debug("Running direct handler: %s", handler_name)
197
+ logger.debug("Running virtual handler: %s", handler_name)
198
198
  session = MeshSession()
199
199
  peer_device = VirtualLocal(rule.match, device)
200
200
  peer_virtual = VirtualPeer(num=order_number)
@@ -219,11 +219,6 @@ class MeshExecutor:
219
219
  f"peer data for device `{device.fqdn}`:\n" + str(e)
220
220
  ) from e
221
221
 
222
- if not hasattr(device_dto, "svi"):
223
- raise ValueError(
224
- f"Handler `{handler_name}` did not provide `svi` number. "
225
- "Virtual peer must be connected to SVI interface."
226
- )
227
222
  pair = VirtualPair(local=device_dto, connected=virtual_dto)
228
223
  virtual_peers.append(pair)
229
224
  return virtual_peers
@@ -345,11 +340,11 @@ class MeshExecutor:
345
340
  target_interface.add_addr(changes.addr, changes.vrf)
346
341
  return target_interface.name
347
342
 
348
- def _apply_indirect_interface_changes(
349
- self, device: Device, neighbor: Device, ifname: Optional[str], changes: InterfaceChanges,
343
+ def _apply_nondirect_interface_changes(
344
+ self, device: Device, ifname: Optional[str], changes: InterfaceChanges,
350
345
  ) -> Optional[str]:
351
346
  if changes.lag is not None:
352
- raise ValueError("LAG creation unsupported for indirect peers")
347
+ raise ValueError("LAG creation unsupported for indirect and virtual peers")
353
348
  elif changes.subif is not None:
354
349
  target_interface = device.add_subif(ifname, changes.subif)
355
350
  elif changes.svi is not None:
@@ -363,9 +358,6 @@ class MeshExecutor:
363
358
  target_interface.add_addr(changes.addr, changes.vrf)
364
359
  return target_interface.name
365
360
 
366
- def _apply_virtual_interface_changes(self, device: Device, local: VirtualLocalDTO) -> str:
367
- return device.add_svi(local.svi).name # we check if SVI configured in execute method
368
-
369
361
  def execute_for(self, device: Device) -> BgpConfig:
370
362
  all_fqdns = self._storage.resolve_all_fdnds()
371
363
 
@@ -383,16 +375,16 @@ class MeshExecutor:
383
375
  peers.append(self._to_bgp_peer(direct_pair, target_interface))
384
376
 
385
377
  for virtual_pair in self._execute_virtual(device):
386
- target_interface = self._apply_virtual_interface_changes(
378
+ target_interface = self._apply_nondirect_interface_changes(
387
379
  device,
388
- virtual_pair.local,
380
+ getattr(virtual_pair.local, "ifname", None),
381
+ to_interface_changes(virtual_pair.local),
389
382
  )
390
383
  peers.append(self._virtual_to_bgp_peer(virtual_pair, target_interface))
391
384
 
392
385
  for connected_pair in self._execute_indirect(device, all_fqdns):
393
- target_interface = self._apply_indirect_interface_changes(
386
+ target_interface = self._apply_nondirect_interface_changes(
394
387
  device,
395
- connected_pair.device,
396
388
  getattr(connected_pair.local, "ifname", None),
397
389
  to_interface_changes(connected_pair.local),
398
390
  )
@@ -17,7 +17,7 @@ LocalDTO = Union[DirectPeerDTO, IndirectPeerDTO, VirtualLocalDTO]
17
17
 
18
18
  @dataclass
19
19
  class InterfaceChanges:
20
- addr: str
20
+ addr: Optional[str] = None
21
21
  lag: Optional[int] = None
22
22
  lag_links_min: Optional[int] = None
23
23
  svi: Optional[int] = None
annet/mesh/peer_models.py CHANGED
@@ -100,14 +100,10 @@ class IndirectPeerDTO(MeshSession, _OptionsDTO):
100
100
  svi: Optional[int]
101
101
 
102
102
 
103
- class VirtualLocalDTO(_OptionsDTO):
104
- asnum: Union[int, str]
103
+ class VirtualLocalDTO(MeshSession, _OptionsDTO):
105
104
  pod: int
106
105
  addr: str
107
106
  description: str
108
-
109
- import_policy: str
110
- export_policy: str
111
107
  update_source: str
112
108
 
113
109
  svi: int
annet/parallel.py CHANGED
@@ -156,12 +156,6 @@ def _pool_worker(pool, index, task_queue, done_queue):
156
156
  capture_output_ctx = capture_output(cap_stdout, cap_stderr)
157
157
 
158
158
  with invoke_span_ctx as invoke_span, capture_output_ctx as _:
159
- invoke_trace_id = invoke_span.get_span_context().trace_id
160
- if invoke_trace_id:
161
- span.set_attribute(
162
- "link",
163
- f"https://t.yandex-team.ru/trace/{invoke_trace_id:x}"
164
- )
165
159
  invoke_span.set_attribute("func", pool.func.__name__)
166
160
  invoke_span.set_attribute("worker.id", worker_id)
167
161
  if device_id:
@@ -84,7 +84,7 @@ class Checkable:
84
84
  self.interface = ConditionFactory[str](MatchField.interface, ["=="])
85
85
  self.protocol = ConditionFactory[str](MatchField.protocol, ["=="])
86
86
  self.net_len = ConditionFactory[int](MatchField.net_len, ["==", "!="])
87
- self.local_pref = ConditionFactory[int](MatchField.local_pref, ["<"])
87
+ self.local_pref = ConditionFactory[int](MatchField.local_pref, ["<", "==", ">=", "<=", "BETWEEN_INCLUDED"])
88
88
  self.metric = ConditionFactory[int](MatchField.metric, ["=="])
89
89
  self.family = ConditionFactory[int](MatchField.family, ["=="])
90
90
  self.as_path_length = ConditionFactory[int](MatchField.as_path_length, ["==", ">=", "<=", "BETWEEN_INCLUDED"])
@@ -55,3 +55,19 @@ class AsPathFilterGenerator(PartialGenerator, ABC):
55
55
  for as_path_filter in self.get_used_as_path_filters(device):
56
56
  values = "_".join((x for x in as_path_filter.filters if x != ".*"))
57
57
  yield "ip as-path access-list", as_path_filter.name, "permit", f"_{values}_"
58
+
59
+ def acl_iosxr(self, _):
60
+ return r"""
61
+ as-path-set *
62
+ ~ %global=1
63
+ """
64
+
65
+ def run_iosxr(self, device: Any):
66
+ for as_path_filter in self.get_used_as_path_filters(device):
67
+ with self.block("as-path-set", as_path_filter.name):
68
+ for n, filter_item in enumerate(as_path_filter.filters):
69
+ if n + 1 < len(as_path_filter.filters):
70
+ comma = ","
71
+ else:
72
+ comma = ""
73
+ yield "ios-regex", f"'{filter_item}'{comma}"
@@ -1,9 +1,9 @@
1
1
  from abc import ABC, abstractmethod
2
- from collections.abc import Sequence, Collection
2
+ from collections.abc import Sequence, Collection, Iterator
3
3
  from typing import Any
4
4
 
5
5
  from annet.generators import PartialGenerator
6
- from annet.rpl import RouteMap, SingleCondition, MatchField, ThenField, RoutingPolicy, ConditionOperator
6
+ from annet.rpl import SingleCondition, MatchField, ThenField, RoutingPolicy, ConditionOperator
7
7
  from .entities import (
8
8
  CommunityList, CommunityLogic, CommunityType, arista_well_known_community, mangle_united_community_list_name,
9
9
  )
@@ -25,7 +25,8 @@ def get_used_community_lists(
25
25
  used_communities.update(condition.value)
26
26
  for then_field in (
27
27
  ThenField.community, ThenField.large_community,
28
- ThenField.extcommunity_rt, ThenField.extcommunity_soo
28
+ ThenField.extcommunity_rt, ThenField.extcommunity_soo,
29
+ ThenField.extcommunity,
29
30
  ):
30
31
  for action in statement.then.find_all(then_field):
31
32
  if action.value.replaced is not None:
@@ -240,3 +241,36 @@ class CommunityListGenerator(PartialGenerator, ABC):
240
241
  )
241
242
  else:
242
243
  raise NotImplementedError(f"Community logic {community_list.logic} is not implemented for arista")
244
+
245
+ def acl_iosxr(self, _) -> str:
246
+ return r"""
247
+ community-set *
248
+ ~ %global=1
249
+ extcommunity-set *
250
+ ~ %global=1
251
+ """
252
+
253
+ def _iosxr_community_list(self, community_list: CommunityList) -> Iterator[Sequence[str]]:
254
+ if community_list.type is CommunityType.BASIC:
255
+ name = "community-set"
256
+ elif community_list.type is CommunityType.RT:
257
+ name = "extcommunity-set rt"
258
+ elif community_list.type is CommunityType.SOO:
259
+ name = "extcommunity-set soo"
260
+ else:
261
+ raise NotImplementedError(f"CommunityList type {community_list.type} not implemented for Cisco IOS XR")
262
+
263
+ with self.block(name, community_list.name):
264
+ for n, community in enumerate(community_list.members):
265
+ if n + 1 < len(community_list.members):
266
+ comma = ","
267
+ else:
268
+ comma = ""
269
+ if community_list.use_regex:
270
+ yield "ios-regex", f"'{community}'"
271
+ else:
272
+ yield f"{community}{comma}",
273
+
274
+ def run_iosxr(self, device):
275
+ for community_list in self.get_used_community_lists(device):
276
+ yield from self._iosxr_community_list(community_list)