annet 0.0__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 (137) hide show
  1. annet/__init__.py +61 -0
  2. annet/adapters/__init__.py +0 -0
  3. annet/adapters/netbox/__init__.py +0 -0
  4. annet/adapters/netbox/common/__init__.py +0 -0
  5. annet/adapters/netbox/common/client.py +87 -0
  6. annet/adapters/netbox/common/manufacturer.py +62 -0
  7. annet/adapters/netbox/common/models.py +105 -0
  8. annet/adapters/netbox/common/query.py +23 -0
  9. annet/adapters/netbox/common/status_client.py +25 -0
  10. annet/adapters/netbox/common/storage_opts.py +14 -0
  11. annet/adapters/netbox/provider.py +34 -0
  12. annet/adapters/netbox/v24/__init__.py +0 -0
  13. annet/adapters/netbox/v24/api_models.py +73 -0
  14. annet/adapters/netbox/v24/client.py +59 -0
  15. annet/adapters/netbox/v24/storage.py +196 -0
  16. annet/adapters/netbox/v37/__init__.py +0 -0
  17. annet/adapters/netbox/v37/api_models.py +38 -0
  18. annet/adapters/netbox/v37/client.py +62 -0
  19. annet/adapters/netbox/v37/storage.py +149 -0
  20. annet/annet.py +25 -0
  21. annet/annlib/__init__.py +7 -0
  22. annet/annlib/command.py +49 -0
  23. annet/annlib/diff.py +158 -0
  24. annet/annlib/errors.py +8 -0
  25. annet/annlib/filter_acl.py +196 -0
  26. annet/annlib/jsontools.py +116 -0
  27. annet/annlib/lib.py +495 -0
  28. annet/annlib/netdev/__init__.py +0 -0
  29. annet/annlib/netdev/db.py +62 -0
  30. annet/annlib/netdev/devdb/__init__.py +28 -0
  31. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  32. annet/annlib/netdev/views/__init__.py +0 -0
  33. annet/annlib/netdev/views/dump.py +121 -0
  34. annet/annlib/netdev/views/hardware.py +112 -0
  35. annet/annlib/output.py +246 -0
  36. annet/annlib/patching.py +533 -0
  37. annet/annlib/rbparser/__init__.py +0 -0
  38. annet/annlib/rbparser/acl.py +120 -0
  39. annet/annlib/rbparser/deploying.py +55 -0
  40. annet/annlib/rbparser/ordering.py +52 -0
  41. annet/annlib/rbparser/platform.py +51 -0
  42. annet/annlib/rbparser/syntax.py +115 -0
  43. annet/annlib/rulebook/__init__.py +0 -0
  44. annet/annlib/rulebook/common.py +350 -0
  45. annet/annlib/tabparser.py +648 -0
  46. annet/annlib/types.py +35 -0
  47. annet/api/__init__.py +826 -0
  48. annet/argparse.py +415 -0
  49. annet/cli.py +237 -0
  50. annet/cli_args.py +503 -0
  51. annet/configs/context.yml +18 -0
  52. annet/configs/logging.yaml +39 -0
  53. annet/connectors.py +77 -0
  54. annet/deploy.py +536 -0
  55. annet/diff.py +84 -0
  56. annet/executor.py +551 -0
  57. annet/filtering.py +40 -0
  58. annet/gen.py +865 -0
  59. annet/generators/__init__.py +435 -0
  60. annet/generators/base.py +136 -0
  61. annet/generators/common/__init__.py +0 -0
  62. annet/generators/common/initial.py +33 -0
  63. annet/generators/entire.py +97 -0
  64. annet/generators/exceptions.py +10 -0
  65. annet/generators/jsonfragment.py +125 -0
  66. annet/generators/partial.py +119 -0
  67. annet/generators/perf.py +79 -0
  68. annet/generators/ref.py +15 -0
  69. annet/generators/result.py +127 -0
  70. annet/hardware.py +45 -0
  71. annet/implicit.py +139 -0
  72. annet/lib.py +128 -0
  73. annet/output.py +167 -0
  74. annet/parallel.py +448 -0
  75. annet/patching.py +25 -0
  76. annet/reference.py +148 -0
  77. annet/rulebook/__init__.py +114 -0
  78. annet/rulebook/arista/__init__.py +0 -0
  79. annet/rulebook/arista/iface.py +16 -0
  80. annet/rulebook/aruba/__init__.py +16 -0
  81. annet/rulebook/aruba/ap_env.py +146 -0
  82. annet/rulebook/aruba/misc.py +8 -0
  83. annet/rulebook/cisco/__init__.py +0 -0
  84. annet/rulebook/cisco/iface.py +68 -0
  85. annet/rulebook/cisco/misc.py +57 -0
  86. annet/rulebook/cisco/vlandb.py +90 -0
  87. annet/rulebook/common.py +19 -0
  88. annet/rulebook/deploying.py +87 -0
  89. annet/rulebook/huawei/__init__.py +0 -0
  90. annet/rulebook/huawei/aaa.py +75 -0
  91. annet/rulebook/huawei/bgp.py +97 -0
  92. annet/rulebook/huawei/iface.py +33 -0
  93. annet/rulebook/huawei/misc.py +337 -0
  94. annet/rulebook/huawei/vlandb.py +115 -0
  95. annet/rulebook/juniper/__init__.py +107 -0
  96. annet/rulebook/nexus/__init__.py +0 -0
  97. annet/rulebook/nexus/iface.py +92 -0
  98. annet/rulebook/patching.py +143 -0
  99. annet/rulebook/ribbon/__init__.py +12 -0
  100. annet/rulebook/texts/arista.deploy +20 -0
  101. annet/rulebook/texts/arista.order +125 -0
  102. annet/rulebook/texts/arista.rul +59 -0
  103. annet/rulebook/texts/aruba.deploy +20 -0
  104. annet/rulebook/texts/aruba.order +83 -0
  105. annet/rulebook/texts/aruba.rul +87 -0
  106. annet/rulebook/texts/cisco.deploy +27 -0
  107. annet/rulebook/texts/cisco.order +82 -0
  108. annet/rulebook/texts/cisco.rul +105 -0
  109. annet/rulebook/texts/huawei.deploy +188 -0
  110. annet/rulebook/texts/huawei.order +388 -0
  111. annet/rulebook/texts/huawei.rul +471 -0
  112. annet/rulebook/texts/juniper.rul +120 -0
  113. annet/rulebook/texts/nexus.deploy +24 -0
  114. annet/rulebook/texts/nexus.order +85 -0
  115. annet/rulebook/texts/nexus.rul +83 -0
  116. annet/rulebook/texts/nokia.rul +31 -0
  117. annet/rulebook/texts/pc.order +5 -0
  118. annet/rulebook/texts/pc.rul +9 -0
  119. annet/rulebook/texts/ribbon.deploy +22 -0
  120. annet/rulebook/texts/ribbon.rul +77 -0
  121. annet/rulebook/texts/routeros.order +38 -0
  122. annet/rulebook/texts/routeros.rul +45 -0
  123. annet/storage.py +125 -0
  124. annet/tabparser.py +36 -0
  125. annet/text_term_format.py +95 -0
  126. annet/tracing.py +170 -0
  127. annet/types.py +227 -0
  128. annet-0.0.dist-info/AUTHORS +21 -0
  129. annet-0.0.dist-info/LICENSE +21 -0
  130. annet-0.0.dist-info/METADATA +26 -0
  131. annet-0.0.dist-info/RECORD +137 -0
  132. annet-0.0.dist-info/WHEEL +5 -0
  133. annet-0.0.dist-info/entry_points.txt +5 -0
  134. annet-0.0.dist-info/top_level.txt +2 -0
  135. annet_generators/__init__.py +0 -0
  136. annet_generators/example/__init__.py +12 -0
  137. annet_generators/example/lldp.py +53 -0
@@ -0,0 +1,10 @@
1
+ class GeneratorError(Exception):
2
+ pass
3
+
4
+
5
+ class InvalidValueFromGenerator(ValueError):
6
+ pass
7
+
8
+
9
+ class NotSupportedDevice(GeneratorError):
10
+ pass
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ from typing import (
5
+ Any,
6
+ Dict,
7
+ List,
8
+ Optional,
9
+ Set,
10
+ Union,
11
+ )
12
+
13
+ from annet.storage import Device, Storage
14
+ from .base import TreeGenerator, _filter_str
15
+ from .exceptions import NotSupportedDevice
16
+
17
+
18
+ class JSONFragment(TreeGenerator):
19
+ """Generates parts of JSON config file."""
20
+
21
+ TYPE = "JSON_FRAGMENT"
22
+ TAGS: List[str] = []
23
+
24
+ def __init__(self, storage: Storage):
25
+ super().__init__()
26
+ self.storage = storage
27
+ self._json_config: Dict[str, Any] = {}
28
+ self._config_pointer: List[str] = []
29
+
30
+ # if two generators edit same file, commands from generator with greater `reload_prio` will be used
31
+ if not hasattr(self, "reload_prio"):
32
+ self.reload_prio = 100
33
+
34
+ def supports_device(self, device: Device):
35
+ return bool(self.path(device))
36
+
37
+ def path(self, device: Device) -> Optional[str]:
38
+ raise NotImplementedError("Required PATH for JSON_FRAGMENT generator")
39
+
40
+ @classmethod
41
+ def get_aliases(cls) -> Set[str]:
42
+ return {cls.__name__, *cls.TAGS}
43
+
44
+ def acl(self, device: Device) -> Union[str, List[str]]:
45
+ """
46
+ Restrict the generator to a specified ACL using JSON Pointer syntax.
47
+
48
+ Expected ACL to be a list of strings, but a single string is also allowed.
49
+ """
50
+ raise NotImplementedError("Required ACL for JSON_FRAGMENT generator")
51
+
52
+ def acl_safe(self, device: Device) -> Union[str, List[str]]:
53
+ """
54
+ Restrict the generator to a specified safe ACL using JSON Pointer syntax.
55
+
56
+ Expected ACL to be a list of strings, but a single string is also allowed.
57
+ """
58
+ raise NotImplementedError("Required ACL for JSON_FRAGMENT generator")
59
+
60
+ def run(self, device: Device):
61
+ raise NotImplementedError
62
+
63
+ def get_reload_cmds(self, device: Device) -> str:
64
+ ret = self.reload(device) or ""
65
+ return ret
66
+
67
+ def reload(self, device) -> Optional[str]:
68
+ raise NotImplementedError
69
+
70
+ @contextlib.contextmanager
71
+ def block(self, *tokens, indent=None): # pylint: disable=unused-argument
72
+ block_str = "".join(map(_filter_str, tokens))
73
+ self._config_pointer.append(block_str)
74
+ try:
75
+ yield
76
+ finally:
77
+ self._config_pointer.pop()
78
+
79
+ @contextlib.contextmanager
80
+ def block_piped(self, *tokens, indent=None): # pylint: disable=unused-argument
81
+ block_str = "|".join(map(_filter_str, tokens))
82
+ self._config_pointer.append(block_str)
83
+ try:
84
+ yield
85
+ finally:
86
+ self._config_pointer.pop()
87
+
88
+ def __call__(self, device: Device, annotate: bool = False):
89
+ for cfg_fragment in self.run(device):
90
+ self._set_or_replace_dict(self._config_pointer, cfg_fragment)
91
+ return self._json_config
92
+
93
+ def _set_or_replace_dict(self, pointer, value):
94
+ if not pointer:
95
+ if self._json_config == {}:
96
+ self._json_config = value
97
+ else:
98
+ self._json_config = [self._json_config, value]
99
+ else:
100
+ self._set_dict(self._json_config, pointer, value)
101
+
102
+ @classmethod
103
+ def _to_str(cls, value: Any) -> str:
104
+ if isinstance(value, str):
105
+ return value
106
+ elif isinstance(value, list):
107
+ return [cls._to_str(x) for x in value]
108
+ elif isinstance(value, dict):
109
+ for k, v in value.items():
110
+ value[k] = cls._to_str(v)
111
+ return value
112
+ return str(value)
113
+
114
+ @classmethod
115
+ def _set_dict(cls, cfg, pointer, value):
116
+ # pointer has at least one key
117
+ if len(pointer) == 1:
118
+ if pointer[0] in cfg:
119
+ cfg[pointer[0]] = [cfg[pointer[0]], cls._to_str(value)]
120
+ else:
121
+ cfg[pointer[0]] = cls._to_str(value)
122
+ else:
123
+ if pointer[0] not in cfg:
124
+ cfg[pointer[0]] = {}
125
+ cls._set_dict(cfg[pointer[0]], pointer[1:], cls._to_str(value))
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import (
5
+ Iterable,
6
+ List,
7
+ Set,
8
+ Union,
9
+ )
10
+
11
+ from annet.lib import (
12
+ add_annotation,
13
+ flatten,
14
+ )
15
+ from .base import TreeGenerator, _filter_str
16
+ from .exceptions import NotSupportedDevice
17
+
18
+
19
+ class PartialGenerator(TreeGenerator):
20
+ TYPE = "PARTIAL"
21
+ TAGS: List[str] = []
22
+
23
+ def __init__(self, storage):
24
+ super().__init__()
25
+ self.storage = storage
26
+ self._annotate = False
27
+ self._running_gen = None
28
+ self._annotations = []
29
+ self._annotation_module = self.__class__.__module__ or ""
30
+
31
+ def supports_device(self, device) -> bool:
32
+ if self.__class__.run is PartialGenerator.run:
33
+ return bool(self._get_vendor_func(device.hw.vendor, "run"))
34
+ else:
35
+ return True
36
+
37
+ def acl(self, device):
38
+ acl_func = self._get_vendor_func(device.hw.vendor, "acl")
39
+ if acl_func:
40
+ return acl_func(device)
41
+
42
+ def acl_safe(self, device):
43
+ acl_func = self._get_vendor_func(device.hw.vendor, "acl_safe")
44
+ if acl_func:
45
+ return acl_func(device)
46
+
47
+ def run(self, device) -> Iterable[Union[str, tuple]]:
48
+ run_func = self._get_vendor_func(device.hw.vendor, "run")
49
+ if run_func:
50
+ return run_func(device)
51
+
52
+ def get_user_runner(self, device):
53
+ if self.__class__.run is not PartialGenerator.run:
54
+ return self.run
55
+ return self._get_vendor_func(device.hw.vendor, "run")
56
+
57
+ def _get_vendor_func(self, vendor: str, func_name: str):
58
+ attr_name = f"{func_name}_{vendor}"
59
+ if hasattr(self, attr_name):
60
+ return getattr(self, attr_name)
61
+ return None
62
+
63
+ # =====
64
+
65
+ @classmethod
66
+ def get_aliases(cls) -> Set[str]:
67
+ return {cls.__name__, *cls.TAGS}
68
+
69
+ def __call__(self, device, annotate=False):
70
+ self._indents = []
71
+ self._rows = []
72
+ self._running_gen = self.run(device)
73
+ self._annotate = annotate
74
+
75
+ if annotate and self.__class__.__module__:
76
+ self._annotation_module = ".".join(
77
+ self.__class__.__module__.split(".")[-2:])
78
+
79
+ for text in self._running_gen:
80
+ if isinstance(text, tuple):
81
+ text = " ".join(map(_filter_str, flatten(text)))
82
+ else:
83
+ text = _filter_str(text)
84
+ self._append_text(text)
85
+
86
+ for row in self._rows:
87
+ assert re.search(r"\bNone\b", row) is None, \
88
+ "Found 'None' in yield result: %s" % (row)
89
+ if annotate:
90
+ generated_rows = (
91
+ add_annotation(x, y)
92
+ for (x, y) in zip(self._rows, self._annotations)
93
+ )
94
+ else:
95
+ generated_rows = self._rows
96
+ return "\n".join(generated_rows) + "\n"
97
+
98
+ def _append_text(self, text):
99
+ def annotation_cb(row):
100
+ annotation = "%s:%d" % self.get_running_line()
101
+ self._annotations.append(annotation)
102
+ return row
103
+
104
+ self._append_text_cb(
105
+ text,
106
+ annotation_cb if self._annotate else None
107
+ )
108
+
109
+ def get_running_line(self):
110
+ if not self._running_gen or not self._running_gen.gi_frame:
111
+ return (repr(self._running_gen), -1)
112
+ return self._annotation_module, self._running_gen.gi_frame.f_lineno
113
+
114
+ @classmethod
115
+ def literal(cls, item):
116
+ return '"{}"'.format(item)
117
+
118
+ def __repr__(self):
119
+ return "<%s>" % self.__class__.__name__
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import (
5
+ Optional,
6
+ Union,
7
+ )
8
+
9
+ from annet import tracing
10
+ from annet.tracing import tracing_connector
11
+ from annet.types import (
12
+ GeneratorPartialRunArgs,
13
+ GeneratorPerf,
14
+ )
15
+ from .entire import Entire
16
+ from .partial import PartialGenerator
17
+
18
+
19
+ class GeneratorPerfMesurer:
20
+ def __init__(
21
+ self,
22
+ gen: Union[PartialGenerator, Entire],
23
+ run_args: Optional[GeneratorPartialRunArgs] = None,
24
+ trace_min_duration: tracing.MinDurationT = None
25
+ ):
26
+ self._gen = gen
27
+ self._run_args = run_args
28
+
29
+ self._start_time: float = 0.0
30
+ self._span_ctx = None
31
+ self._span = None
32
+ self._trace_min_duration = trace_min_duration
33
+
34
+ self.last_result: Optional[GeneratorPerf] = None
35
+
36
+ def start(self) -> None:
37
+ self.last_result = None
38
+
39
+ self._span_ctx = tracing_connector.get().start_as_current_span(
40
+ "gen:call",
41
+ tracer_name=self._gen.__class__.__module__,
42
+ min_duration=self._trace_min_duration,
43
+ )
44
+ self._span = self._span_ctx.__enter__() # pylint: disable=unnecessary-dunder-call
45
+
46
+ if self._span:
47
+ self._span.set_attributes(
48
+ {"generator.class": self._gen.__class__.__name__})
49
+ if self._run_args:
50
+ tracing_connector.get().set_device_attributes(
51
+ self._span, self._run_args.device,
52
+ )
53
+
54
+ self._start_time = time.monotonic()
55
+
56
+ def finish(self, exc_type=None, exc_val=None,
57
+ exc_tb=None) -> GeneratorPerf:
58
+ total = time.monotonic() - self._start_time
59
+ self._span_ctx.__exit__(exc_type, exc_val, exc_tb)
60
+
61
+ meta = {}
62
+ if tracing_connector.get().enabled:
63
+ span_context = self._span.get_span_context()
64
+ meta = {
65
+ "span": {
66
+ "trace_id": str(span_context.trace_id),
67
+ "span_id": str(span_context.span_id),
68
+ },
69
+ }
70
+
71
+ self.last_result = GeneratorPerf(total=total, rt=total, meta=meta)
72
+ return self.last_result
73
+
74
+ def __enter__(self):
75
+ self.start()
76
+ return self
77
+
78
+ def __exit__(self, exc_type, exc_val, exc_tb):
79
+ self.finish(exc_type, exc_val, exc_tb)
@@ -0,0 +1,15 @@
1
+ from .partial import PartialGenerator
2
+
3
+
4
+ class RefGenerator(PartialGenerator):
5
+ def __init__(self, storage, groups=None):
6
+ super().__init__(storage)
7
+ self.groups = groups
8
+
9
+ def ref(self, device):
10
+ if hasattr(self, "ref_" + device.hw.vendor):
11
+ return getattr(self, "ref_" + device.hw.vendor)(device)
12
+ return ""
13
+
14
+ def with_groups(self, groups):
15
+ return type(self)(self.storage, groups)
@@ -0,0 +1,127 @@
1
+ from __future__ import annotations
2
+
3
+ import textwrap
4
+ from collections import OrderedDict as odict
5
+ from typing import (
6
+ Any,
7
+ Dict,
8
+ Optional,
9
+ Tuple, Callable,
10
+ )
11
+
12
+ from annet.annlib import jsontools
13
+ from annet.lib import (
14
+ merge_dicts,
15
+ )
16
+ from annet.reference import RefMatcher, RefTracker
17
+ from annet.types import (
18
+ GeneratorEntireResult,
19
+ GeneratorJSONFragmentResult,
20
+ GeneratorPartialResult,
21
+ )
22
+
23
+
24
+ def _combine_acl_text(
25
+ partial_results: Dict[str, GeneratorPartialResult],
26
+ acl_getter: Callable[[GeneratorPartialResult], str]
27
+ ) -> str:
28
+ acl_text = ""
29
+ for gr in partial_results.values():
30
+ for line in textwrap.dedent(acl_getter(gr)).split("\n"):
31
+ if line and not line.isspace():
32
+ acl_text += line.rstrip()
33
+ acl_text += fr" %generator_names={gr.name}"
34
+ acl_text += "\n"
35
+ return acl_text
36
+
37
+
38
+ class RunGeneratorResult:
39
+ """
40
+ Результат запуска run_partial_generators/run_file_generators
41
+ """
42
+
43
+ def __init__(self):
44
+ self.partial_results: Dict[str, GeneratorPartialResult] = {}
45
+ self.entire_results: Dict[str, GeneratorEntireResult] = {}
46
+ self.json_fragment_results: Dict[str, GeneratorJSONFragmentResult] = {}
47
+ self.ref_track: RefTracker = RefTracker()
48
+ self.ref_matcher: RefMatcher = RefMatcher()
49
+
50
+ def add_partial(self, result: GeneratorPartialResult):
51
+ self.partial_results[result.name] = result
52
+
53
+ def add_entire(self, result: GeneratorEntireResult) -> None:
54
+ # Если есть несколько генераторов на один файл, выбрать тот, что с большим приоритетом
55
+ if result.path:
56
+ if result.path not in self.entire_results or \
57
+ result.prio > self.entire_results[result.path].prio:
58
+ self.entire_results[result.path] = result
59
+
60
+ def add_json_fragment(self, result: GeneratorJSONFragmentResult) -> None:
61
+ self.json_fragment_results[result.name] = result
62
+
63
+ def config_tree(self, safe: bool = False) -> Dict[str, Any]: # OrderedDict
64
+ tree = odict()
65
+ for gr in self.partial_results.values():
66
+ config = gr.safe_config if safe else gr.config
67
+ tree = merge_dicts(tree, config)
68
+ return tree
69
+
70
+ def new_files(self, safe: bool = False) -> Dict[str, Tuple[str, str]]:
71
+ files = {}
72
+ for gr in self.entire_results.values():
73
+ if not safe or gr.is_safe:
74
+ files[gr.path] = (gr.output, gr.reload)
75
+ return files
76
+
77
+ def acl_text(self) -> str:
78
+ return _combine_acl_text(self.partial_results, lambda gr: gr.acl)
79
+
80
+ def acl_safe_text(self) -> str:
81
+ return _combine_acl_text(self.partial_results, lambda gr: gr.acl_safe)
82
+
83
+ def new_json_fragment_files(
84
+ self,
85
+ old_files: Dict[str, Optional[str]],
86
+ safe: bool = False,
87
+ ) -> Dict[str, Tuple[Any, Optional[str]]]:
88
+ # TODO: safe
89
+ files: Dict[str, Tuple[Any, Optional[str]]] = {}
90
+ reload_prios: Dict[str, int] = {}
91
+ for generator_result in self.json_fragment_results.values():
92
+ filepath = generator_result.path
93
+ if filepath not in files:
94
+ if old_files.get(filepath) is not None:
95
+ files[filepath] = (old_files[filepath], None)
96
+ else:
97
+ files[filepath] = ({}, None)
98
+ result_acl = generator_result.acl
99
+ if safe:
100
+ result_acl = generator_result.acl_safe
101
+ previous_config: Dict[str, Any] = files[filepath][0]
102
+ new_fragment = generator_result.config
103
+ new_config = jsontools.apply_json_fragment(
104
+ previous_config,
105
+ new_fragment,
106
+ result_acl,
107
+ )
108
+ if filepath in reload_prios and \
109
+ reload_prios[filepath] > generator_result.reload_prio:
110
+ _, reload_cmd = files[filepath]
111
+ else:
112
+ reload_cmd = generator_result.reload
113
+ reload_prios[filepath] = generator_result.reload_prio
114
+ files[filepath] = (new_config, reload_cmd)
115
+ return files
116
+
117
+ def perf_mesures(self) -> Dict[str, Dict[str, int]]:
118
+ mesures = {}
119
+ for gr in self.partial_results.values():
120
+ mesures[gr.name] = {"total": gr.perf.total,
121
+ "rt": gr.perf.rt,
122
+ "meta": gr.perf.meta}
123
+ for gr in self.entire_results.values():
124
+ mesures[gr.name] = {"total": gr.perf.total,
125
+ "rt": gr.perf.rt,
126
+ "meta": gr.perf.meta}
127
+ return mesures
annet/hardware.py ADDED
@@ -0,0 +1,45 @@
1
+ import abc
2
+ from typing import Any
3
+
4
+ from annet.annlib.netdev.views.hardware import HardwareView, hw_to_vendor
5
+
6
+ from annet.connectors import Connector
7
+
8
+
9
+ try:
10
+ from annet.annlib.netdev.views.hardware import vendor_to_hw
11
+ except ImportError:
12
+ from netdev.views.hardware import vendor_to_hw
13
+
14
+
15
+ class _HardwareConnector(Connector["HarwareProvider"]):
16
+ name = "Hardware"
17
+ ep_name = "hardware"
18
+
19
+
20
+ hardware_connector = _HardwareConnector()
21
+
22
+
23
+ class HarwareProvider(abc.ABC):
24
+ @abc.abstractmethod
25
+ def make_hw(self, hw_model: str, sw_version: str) -> Any:
26
+ pass
27
+
28
+ @abc.abstractmethod
29
+ def vendor_to_hw(self, vendor: str) -> Any:
30
+ pass
31
+
32
+ @abc.abstractmethod
33
+ def hw_to_vendor(self, hw: Any) -> str:
34
+ pass
35
+
36
+
37
+ class AnnetHardwareProvider(HarwareProvider):
38
+ def make_hw(self, hw_model: str, sw_version: str) -> HardwareView:
39
+ return HardwareView(hw_model, sw_version)
40
+
41
+ def vendor_to_hw(self, vendor: str) -> HardwareView:
42
+ return vendor_to_hw(vendor)
43
+
44
+ def hw_to_vendor(self, hw: HardwareView) -> str:
45
+ return hw_to_vendor(hw)
annet/implicit.py ADDED
@@ -0,0 +1,139 @@
1
+ from collections import OrderedDict as odict
2
+
3
+ from annet.annlib.rbparser import syntax
4
+
5
+
6
+ def config(config_tree, rules):
7
+ implicit_config_tree = odict()
8
+ for (row, rule) in rules.items():
9
+ matched_lines = [line for line in config_tree.keys() if rule["regexp"].match(line)]
10
+ if rule["type"] != "ignore":
11
+ if not any(matched_lines) and row not in config_tree:
12
+ implicit_config_tree[row] = odict()
13
+ for line in matched_lines:
14
+ implicit_config_tree[line] = config(config_tree[line], rule["children"])
15
+ return implicit_config_tree
16
+
17
+
18
+ def compile_rules(device):
19
+ return compile_tree(_implicit_tree(device))
20
+
21
+
22
+ def compile_tree(tree):
23
+ rules = odict()
24
+ for (_, attrs) in tree.items():
25
+ rule = {
26
+ "type": attrs["type"],
27
+ "children": compile_tree(attrs["children"]) if attrs.get("children") else odict(),
28
+ "regexp": syntax.compile_row_regexp(attrs["row"]),
29
+ }
30
+ rules[attrs["row"]] = rule
31
+ return rules
32
+
33
+
34
+ def _implicit_tree(device):
35
+ text = ""
36
+ if device.hw.Huawei:
37
+ if device.hw.Huawei.CE:
38
+ text = """
39
+ stp mode mstp
40
+ stp enable
41
+ undo ntp server disable
42
+ undo telnet server disable
43
+ undo dhcp enable
44
+ !user-interface con *
45
+ user privilege level 3
46
+ !user-interface vty ~
47
+ protocol inbound all
48
+ netconf
49
+ """
50
+ elif device.hw.Huawei.NE:
51
+ text = """
52
+ !bgp *
53
+ !ipv4-family unicast
54
+ undo synchronization
55
+ !ipv6-family unicast
56
+ undo synchronization
57
+ !user-interface con *
58
+ user privilege level 3
59
+ !user-interface vty ~
60
+ protocol inbound all
61
+ aaa
62
+ undo user-password complexity-check
63
+ netconf
64
+ """
65
+ else:
66
+ text = """
67
+ stp mode mstp
68
+ !interface X?GigabitEthernet*
69
+ bpdu enable
70
+ netconf
71
+ """
72
+ elif device.hw.Arista:
73
+ # эта часть конфигурации будет не видна в конфиге, если она включена с таким набором полей:
74
+ text = r"""
75
+ ip load-sharing trident fields ipv6 destination-port source-ip ingress-interface destination-ip source-port flow-label
76
+ ip load-sharing trident fields ip source-ip source-port destination-ip destination-port ingress-interface
77
+ """
78
+ elif device.hw.Nexus:
79
+ text = r"""
80
+ # часть конфигурации скрытая если включена
81
+ snmp-server enable traps link linkDown
82
+ snmp-server enable traps link linkUp
83
+ """
84
+ if device.hw.Nexus.N3x.N3432 \
85
+ or (device.hw.Nexus.N9x.N9500 and "spine1" in device.tags) \
86
+ or device.hw.Nexus.N9x.N9316 or device.hw.Cisco.Nexus.N9x.N9364:
87
+ text += r"""
88
+ # SVI
89
+ !interface Vlan*
90
+ !shutdown
91
+ !interface mgmt[0-9]*
92
+ no shutdown
93
+ mtu 1500
94
+ !interface Ethernet1*
95
+ no shutdown
96
+ # Лупбеки
97
+ !interface */Loopback[0-9.]+/
98
+ no shutdown
99
+ # Агрегаты
100
+ !interface */port-channel[0-9.]+/
101
+ no shutdown
102
+ # BGP
103
+ !router bgp *
104
+ !neighbor *
105
+ no shutdown
106
+ """
107
+
108
+ elif device.hw.Nexus.N3x:
109
+ # У нексуса сложные взаимоотношения с
110
+ # shutdown командой вездe
111
+ # в данный момент поведение проверенно для Cisco Nexus 3132Q 6.0(2)U6(7)
112
+ text += r"""
113
+ # SVI
114
+ !interface Vlan*
115
+ !shutdown
116
+ !interface mgmt[0-9]*
117
+ no shutdown
118
+ # Физические НЕ сплитованные интерфейсы и subif'ы
119
+ !interface */Ethernet1\/[0-9.]*/
120
+ no shutdown
121
+ # Физические НЕ сплитованные интерфейсы и subif'ы
122
+ !interface */Ethernet1\/[0-9]+\/[0-9.]+/
123
+ # только explicit
124
+ # Лупбеки
125
+ !interface */Loopback[0-9.]+/
126
+ no shutdown
127
+ # Агрегаты
128
+ !interface */port-channel[0-9.]+/
129
+ no shutdown
130
+ # BGP
131
+ !router bgp *
132
+ !neighbor *
133
+ no shutdown
134
+ """
135
+ return parse_text(text)
136
+
137
+
138
+ def parse_text(text):
139
+ return syntax.parse_text(text, {})