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

@@ -17,7 +17,8 @@ _VENDORS = {
17
17
  "aruba": "Aruba",
18
18
  "routeros": "RouterOS",
19
19
  "ribbon": "Ribbon",
20
- "b4com": "B4com"
20
+ "b4com": "B4com",
21
+ "h3c": "H3C",
21
22
  }
22
23
 
23
24
 
@@ -37,6 +38,8 @@ def get_breed(manufacturer: str, model: str):
37
38
  return "vrp85"
38
39
  elif manufacturer == "Huawei":
39
40
  return "vrp55"
41
+ elif manufacturer == "H3C":
42
+ return "h3c"
40
43
  elif manufacturer in ("Mellanox", "NVIDIA"):
41
44
  return "cuml2"
42
45
  elif manufacturer == "Juniper":
@@ -53,6 +53,7 @@
53
53
  "Huawei.CE.CE8800.CE8850": " CE8850",
54
54
  "Huawei.CE.CE8800.CE8851": " CE8851",
55
55
  "Huawei.CE.CE8800.CE8855": " CE8855",
56
+ "Huawei.CE.CE8800.CE8875": " CE8875",
56
57
  "Huawei.CE.CE9800": " CE98\\d\\d",
57
58
  "Huawei.CE.CE9800.CE9855": " CE9855",
58
59
  "Huawei.CE.CE9800.CE9860": " CE9860",
@@ -1,5 +1,6 @@
1
1
  VENDOR_REVERSES = {
2
2
  "huawei": "undo",
3
+ "h3c": "undo",
3
4
  "optixtrans": "undo",
4
5
  "cisco": "no",
5
6
  "nexus": "no",
@@ -45,6 +46,7 @@ VENDOR_DIFF_ORDERED = {
45
46
 
46
47
  VENDOR_EXIT = {
47
48
  "huawei": "quit",
49
+ "h3c": "quit",
48
50
  "optixtrans": "quit",
49
51
  "cisco": "exit",
50
52
  "nexus": "exit",
@@ -57,3 +59,7 @@ VENDOR_EXIT = {
57
59
  "ribbon": "exit",
58
60
  "b4com": "exit",
59
61
  }
62
+
63
+ VENDOR_ALIASES = {
64
+ "h3c": "huawei",
65
+ }
@@ -356,6 +356,10 @@ def apply(hw, do_commit, do_finalize, **_):
356
356
  after.add_cmd(Command("commit"))
357
357
  if do_finalize:
358
358
  after.add_cmd(Command("write", timeout=40))
359
+ elif hw.H3C:
360
+ before.add_cmd(Command("system-view"))
361
+ if do_finalize:
362
+ after.add_cmd(Command("save force", timeout=20))
359
363
  else:
360
364
  raise Exception("unknown hw %s" % hw)
361
365
 
annet/annlib/tabparser.py CHANGED
@@ -2,7 +2,7 @@ import dataclasses
2
2
  import itertools
3
3
  import re
4
4
  from collections import OrderedDict as odict
5
- from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union
5
+ from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union, List
6
6
 
7
7
  from .types import Op
8
8
 
@@ -271,8 +271,63 @@ class CiscoFormatter(BlockExitFormatter):
271
271
  def __init__(self, indent=" "):
272
272
  super().__init__("exit", indent)
273
273
 
274
+ def _split_indent(
275
+ self, line: str, indent: int, block_exit_strings: List[str]
276
+ ) -> Tuple[List[str], int]:
277
+ """
278
+ The small helper calculates indent shift based on block exit string.
279
+ If configuration line has non-default block exit string it means that
280
+ new subsection is started and indent should be increased.
281
+ If configuration line exists in list of block exit strings,
282
+ it means that subsection is finished and indent should be decreased
283
+
284
+ Args:
285
+ line: just configuration line
286
+ indent: current indent
287
+ block_exit_strings: list of previously seen block exit strings
288
+ Returns:
289
+ new indent and list of previously seen block exit strings
290
+ """
291
+
292
+ if line.strip() in block_exit_strings:
293
+ indent -= 1
294
+ block_exit_strings.remove(line.strip())
295
+ return block_exit_strings, indent
296
+
297
+ block_exit_wrapped = [
298
+ v for v in self.block_exit(FormatterContext(current=(line.strip(), {})))
299
+ ]
300
+
301
+ if not block_exit_wrapped or len(block_exit_wrapped) != 3:
302
+ return block_exit_strings, indent
303
+ if not isinstance(block_exit_wrapped[1], str):
304
+ return block_exit_strings, indent
305
+ if block_exit_wrapped[1] == self._block_exit:
306
+ return block_exit_strings, indent
307
+
308
+ indent += 1
309
+ block_exit_strings.append(block_exit_wrapped[1])
310
+ return block_exit_strings, indent
311
+
274
312
  def split(self, text):
275
- return self.split_remove_spaces(text)
313
+ additional_indent = 0
314
+ block_exit_strings = [self._block_exit]
315
+ tree = self.split_remove_spaces(text)
316
+ for i, item in enumerate(tree):
317
+ block_exit_strings, new_indent = self._split_indent(
318
+ item, additional_indent, block_exit_strings
319
+ )
320
+ tree[i] = f"{' ' * additional_indent}{item}"
321
+ additional_indent = new_indent
322
+ return tree
323
+
324
+ def block_exit(self, context: Optional[FormatterContext]) -> str:
325
+ current = context and context.row or ""
326
+
327
+ if current.startswith(("address-family")):
328
+ yield from block_wrapper("exit-address-family")
329
+ else:
330
+ yield from super().block_exit(context)
276
331
 
277
332
 
278
333
  class AsrFormatter(BlockExitFormatter):
annet/configs/context.yml CHANGED
@@ -2,6 +2,7 @@ generators:
2
2
  default:
3
3
  - annet_generators.example
4
4
  - annet_generators.mesh_example
5
+ - annet_generators.rpl_example
5
6
 
6
7
  storage:
7
8
  default:
annet/rpl/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ __all__ = [
2
+ "MatchField",
3
+ "ThenField",
4
+ "RouteMap",
5
+ "Route",
6
+ "ResultType",
7
+ "ActionType",
8
+ "Action",
9
+ "SingleAction",
10
+ "AndCondition",
11
+ "R",
12
+ "ConditionOperator",
13
+ "Condition",
14
+ "SingleCondition",
15
+ "RoutingPolicyStatement",
16
+ "RoutingPolicy",
17
+ "CommunityActionValue",
18
+ "PrefixMatchValue",
19
+ ]
20
+
21
+ from .action import Action, ActionType, SingleAction
22
+ from .condition import AndCondition, Condition, ConditionOperator, SingleCondition
23
+ from .match_builder import R, MatchField, PrefixMatchValue
24
+ from .policy import RoutingPolicyStatement, RoutingPolicy
25
+ from .result import ResultType
26
+ from .routemap import RouteMap, Route
27
+ from .statement_builder import ThenField, CommunityActionValue
annet/rpl/action.py ADDED
@@ -0,0 +1,51 @@
1
+ from collections.abc import Iterator, Iterable
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Generic, TypeVar, Any
5
+
6
+
7
+ class ActionType(Enum):
8
+ SET = "set"
9
+ ADD = "add"
10
+ REMOVE = "delete"
11
+
12
+ CUSTOM = "custom"
13
+
14
+
15
+ ValueT = TypeVar("ValueT")
16
+
17
+
18
+ @dataclass
19
+ class SingleAction(Generic[ValueT]):
20
+ field: str
21
+ type: ActionType
22
+ value: ValueT
23
+
24
+
25
+ class Action:
26
+ def __init__(self) -> None:
27
+ self.actions: list[SingleAction[Any]] = []
28
+
29
+ def append(self, action: SingleAction[Any]) -> None:
30
+ self.actions.append(action)
31
+
32
+ def __repr__(self):
33
+ actions = ", ".join(repr(c) for c in self.actions)
34
+ return f"Action({actions})"
35
+
36
+ def __getitem__(self, item: str) -> SingleAction[Any]:
37
+ return next(c for c in self.actions if c.field == item)
38
+
39
+ def __contains__(self, item: str) -> bool:
40
+ return any(c.field == item for c in self.actions)
41
+
42
+ def __len__(self) -> int:
43
+ return len(self.actions)
44
+
45
+ def __iter__(self) -> Iterator[SingleAction[Any]]:
46
+ return iter(self.actions)
47
+
48
+ def find_all(self, item: str) -> Iterable[SingleAction[Any]]:
49
+ for action in self.actions:
50
+ if action.field == item:
51
+ yield action
annet/rpl/condition.py ADDED
@@ -0,0 +1,94 @@
1
+ from collections.abc import Iterator, Iterable
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from typing import Generic, TypeVar, Sequence, Union, Any
5
+
6
+
7
+ class ConditionOperator(Enum):
8
+ EQ = "=="
9
+ GE = ">="
10
+ GT = ">"
11
+ LE = "<="
12
+ LT = "<"
13
+ BETWEEN_INCLUDED = "BETWEEN_INCLUDED"
14
+
15
+ HAS = "has"
16
+ HAS_ANY = "has_any"
17
+
18
+ CUSTOM = "custom"
19
+
20
+
21
+ ValueT = TypeVar("ValueT")
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class SingleCondition(Generic[ValueT]):
26
+ field: str
27
+ operator: ConditionOperator
28
+ value: ValueT
29
+
30
+ def __and__(self, other: "Condition") -> "Condition":
31
+ return AndCondition(self, other)
32
+
33
+ def merge(self, other: "SingleCondition[Any]") -> "SingleCondition[Any]":
34
+ if other.field != self.field:
35
+ raise ValueError(f"Cannot merge conditions with different fields: {self.field} != {other.field}")
36
+ if self.operator is ConditionOperator.LE:
37
+ if other.operator is ConditionOperator.GE:
38
+ return SingleCondition(self.field, ConditionOperator.BETWEEN_INCLUDED, (other.value, self.value))
39
+ elif other.operator is ConditionOperator.LE:
40
+ return SingleCondition(self.field, ConditionOperator.LE, min(self.value, other.value))
41
+ elif self.operator is ConditionOperator.GE:
42
+ if other.operator is ConditionOperator.LE:
43
+ return other.merge(self)
44
+ elif other.operator is ConditionOperator.GE:
45
+ return SingleCondition(self.field, ConditionOperator.GE, max(self.value, other.value))
46
+ elif self.operator is ConditionOperator.LT:
47
+ if other.operator is ConditionOperator.LT:
48
+ return SingleCondition(self.field, ConditionOperator.LT, min(self.value, other.value))
49
+ elif self.operator is ConditionOperator.GT:
50
+ if other.operator is ConditionOperator.GT:
51
+ return SingleCondition(self.field, ConditionOperator.GT, max(self.value, other.value))
52
+ raise ValueError(f"Cannot merge condition with operator {self.operator} and {other.operator}")
53
+
54
+
55
+ Condition = Union[SingleCondition, "AndCondition"]
56
+
57
+
58
+ class AndCondition:
59
+ def __init__(self, *conditions: Condition):
60
+ self.conditions: list[SingleCondition[Any]] = []
61
+ for c in conditions:
62
+ self.conditions.extend(self._unpack(c))
63
+
64
+ def _unpack(self, other: Condition) -> Sequence[SingleCondition]:
65
+ if isinstance(other, AndCondition):
66
+ return other.conditions
67
+ return [other]
68
+
69
+ def __and__(self, other: Condition) -> "AndCondition":
70
+ return AndCondition(*self.conditions, other)
71
+
72
+ def __iadd__(self, other):
73
+ self.conditions.extend(self._unpack(other))
74
+
75
+ def __repr__(self):
76
+ conditions = ", ".join(repr(c) for c in self.conditions)
77
+ return f"AndCondition({conditions})"
78
+
79
+ def __getitem__(self, item: str) -> SingleCondition[Any]:
80
+ return next(c for c in self.conditions if c.field == item)
81
+
82
+ def __contains__(self, item: str) -> bool:
83
+ return any(c.field == item for c in self.conditions)
84
+
85
+ def __len__(self) -> int:
86
+ return len(self.conditions)
87
+
88
+ def __iter__(self) -> Iterator[SingleCondition[Any]]:
89
+ return iter(self.conditions)
90
+
91
+ def find_all(self, item: str) -> Iterable[SingleCondition[Any]]:
92
+ for condition in self.conditions:
93
+ if condition.field == item:
94
+ yield condition
@@ -0,0 +1,103 @@
1
+ from dataclasses import dataclass
2
+ from enum import Enum
3
+ from typing import Generic, Sequence, Callable, Optional, TypeVar, Any
4
+
5
+ from .condition import SingleCondition, ConditionOperator, AndCondition
6
+
7
+
8
+ class MatchField(str, Enum):
9
+ community = "community"
10
+ extcommunity = "extcommunity"
11
+ rd = "rd"
12
+ interface = "interface"
13
+ protocol = "protocol"
14
+ net_len = "net_len"
15
+ local_pref = "local_pref"
16
+ metric = "metric"
17
+ family = "family"
18
+
19
+ as_path_length = "as_path_length"
20
+ as_path_filter = "as_path_filter"
21
+ ipv6_prefix = "ipv6_prefix"
22
+ ip_prefix = "ip_prefix"
23
+
24
+
25
+ ValueT = TypeVar("ValueT")
26
+ _ConditionMethod = Callable[["ConditionFactory[ValueT]", ValueT], SingleCondition[ValueT]]
27
+
28
+
29
+ def condition_method(operator: ConditionOperator) -> _ConditionMethod:
30
+ def method(self: "ConditionFactory[ValueT]", other: ValueT) -> SingleCondition[ValueT]:
31
+ if operator.value not in self.supported_ops:
32
+ raise NotImplementedError(f"Operator {operator.value} is not supported for field {self.field}")
33
+ return SingleCondition(self.field, operator, other)
34
+
35
+ method.__name__ = operator.value
36
+ return method
37
+
38
+
39
+ class ConditionFactory(Generic[ValueT]):
40
+ def __init__(self, field: str, supported_ops: list[str]):
41
+ self.field = field
42
+ self.supported_ops = supported_ops
43
+
44
+ # https://github.com/python/typeshed/issues/3685
45
+ eq = __eq__ = condition_method(ConditionOperator.EQ) # type: ignore[assignment]
46
+ gt = __gt__ = condition_method(ConditionOperator.GT)
47
+ ge = __ge__ = condition_method(ConditionOperator.GE)
48
+ lt = __lt__ = condition_method(ConditionOperator.LT)
49
+ le = __le__ = condition_method(ConditionOperator.LE)
50
+ between_included = condition_method(ConditionOperator.BETWEEN_INCLUDED)
51
+
52
+
53
+ class SetConditionFactory(Generic[ValueT]):
54
+ def __init__(self, field: str) -> None:
55
+ self.field = field
56
+
57
+ def has(self, *values: ValueT) -> SingleCondition[Sequence[ValueT]]:
58
+ return SingleCondition(self.field, ConditionOperator.HAS, values)
59
+
60
+ def has_any(self, *values: ValueT) -> SingleCondition[Sequence[ValueT]]:
61
+ return SingleCondition(self.field, ConditionOperator.HAS_ANY, values)
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class PrefixMatchValue:
66
+ names: Sequence[str]
67
+ or_longer: Optional[tuple[int, int]] # ????
68
+
69
+
70
+ class Checkable:
71
+ def __init__(self):
72
+ self.community = SetConditionFactory[str](MatchField.community)
73
+ self.extcommunity = SetConditionFactory[str](MatchField.extcommunity)
74
+ self.rd = SetConditionFactory[str](MatchField.rd)
75
+ self.interface = ConditionFactory[str](MatchField.interface, ["=="])
76
+ self.protocol = ConditionFactory[str](MatchField.protocol, ["=="])
77
+ self.net_len = ConditionFactory[int](MatchField.net_len, ["==", "!="])
78
+ self.local_pref = ConditionFactory[int](MatchField.local_pref, ["<"])
79
+ self.metric = ConditionFactory[int](MatchField.metric, ["=="])
80
+ self.family = ConditionFactory[int](MatchField.family, ["=="])
81
+ self.as_path_length = ConditionFactory[int](MatchField.as_path_length, ["==", ">=", "<=", "BETWEEN_INCLUDED"])
82
+
83
+ def as_path_filter(self, name: str) -> SingleCondition[str]:
84
+ return SingleCondition(MatchField.as_path_filter, ConditionOperator.EQ, name)
85
+
86
+ def match_v6(self, *names: str, or_longer: Optional[tuple[int, int]] = None) -> SingleCondition[PrefixMatchValue]:
87
+ return SingleCondition(MatchField.ipv6_prefix, ConditionOperator.CUSTOM, PrefixMatchValue(names, or_longer))
88
+
89
+ def match_v4(self, *names: str, or_longer: Optional[tuple[int, int]] = None) -> SingleCondition[PrefixMatchValue]:
90
+ return SingleCondition(MatchField.ip_prefix, ConditionOperator.CUSTOM, PrefixMatchValue(names, or_longer))
91
+
92
+
93
+ def merge_conditions(and_condition: AndCondition) -> AndCondition:
94
+ conditions: dict[str, SingleCondition[Any]] = {}
95
+ for condition in and_condition.conditions:
96
+ if condition.field in conditions:
97
+ conditions[condition.field] = conditions[condition.field].merge(condition)
98
+ else:
99
+ conditions[condition.field] = condition
100
+ return AndCondition(*conditions.values())
101
+
102
+
103
+ R = Checkable()
annet/rpl/policy.py ADDED
@@ -0,0 +1,22 @@
1
+ from collections.abc import Sequence
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+ from .action import Action
6
+ from .condition import AndCondition
7
+ from .result import ResultType
8
+
9
+
10
+ @dataclass
11
+ class RoutingPolicyStatement:
12
+ name: Optional[str]
13
+ number: Optional[int]
14
+ match: AndCondition
15
+ then: Action
16
+ result: ResultType
17
+
18
+
19
+ @dataclass
20
+ class RoutingPolicy:
21
+ name: str
22
+ statements: Sequence[RoutingPolicyStatement]
annet/rpl/result.py ADDED
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ResultType(str, Enum):
5
+ ALLOW = "allow"
6
+ DENY = "deny"
7
+ NEXT = "next"
8
+ NEXT_POLICY = "next_policy"
annet/rpl/routemap.py ADDED
@@ -0,0 +1,76 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Callable, Generic, TypeVar, Union
3
+
4
+ from .action import Action
5
+ from .condition import AndCondition, Condition
6
+ from .match_builder import merge_conditions
7
+ from .policy import RoutingPolicy, RoutingPolicyStatement
8
+ from .result import ResultType
9
+ from .statement_builder import StatementBuilder
10
+
11
+
12
+ class Route:
13
+ def __init__(self, name: str):
14
+ self.name = name
15
+ self.statements: list[RoutingPolicyStatement] = []
16
+
17
+ def __call__(
18
+ self,
19
+ *conditions: Condition,
20
+ name: Optional[str] = None,
21
+ number: Optional[int] = None,
22
+ ) -> "StatementBuilder":
23
+ statement = RoutingPolicyStatement(
24
+ name=name,
25
+ number=number,
26
+ match=merge_conditions(AndCondition(*conditions)),
27
+ then=Action(),
28
+ result=ResultType.NEXT,
29
+ )
30
+ self.statements.append(statement)
31
+ return StatementBuilder(statement=statement)
32
+
33
+
34
+ DeviceT = TypeVar("DeviceT")
35
+ RouteHandlerFunc = Callable[[DeviceT, Route], None]
36
+ Decorator = Callable[[RouteHandlerFunc[DeviceT]], RouteHandlerFunc[DeviceT]]
37
+
38
+
39
+ @dataclass
40
+ class Handler(Generic[DeviceT]):
41
+ name: str
42
+ func: RouteHandlerFunc[DeviceT]
43
+
44
+
45
+ class RouteMap(Generic[DeviceT]):
46
+ def __init__(self) -> None:
47
+ self.handlers: list[Handler[DeviceT]] = []
48
+ self.submaps: list[RouteMap[DeviceT]] = []
49
+
50
+ def __call__(
51
+ self, func: Optional[RouteHandlerFunc[DeviceT]] = None, *, name: str = "",
52
+ ) -> Union[RouteHandlerFunc[DeviceT], Decorator[DeviceT]]:
53
+ def decorator(func: RouteHandlerFunc[DeviceT]) -> RouteHandlerFunc[DeviceT]:
54
+ nonlocal name
55
+ if not name:
56
+ name = func.__name__
57
+ self.handlers.append(Handler(name, func))
58
+ return func
59
+
60
+ if func is None:
61
+ return decorator
62
+ return decorator(func)
63
+
64
+ def include(self, other: "RouteMap[DeviceT]") -> None:
65
+ self.submaps.append(other)
66
+
67
+ def apply(self, device: DeviceT) -> list[RoutingPolicy]:
68
+ result: list[RoutingPolicy] = []
69
+
70
+ for handler in self.handlers:
71
+ route = Route(handler.name)
72
+ handler.func(device, route)
73
+ result.append(RoutingPolicy(route.name, route.statements))
74
+ for submap in self.submaps:
75
+ result.extend(submap.apply(device))
76
+ return result