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.
- annet/__init__.py +61 -0
- annet/adapters/__init__.py +0 -0
- annet/adapters/netbox/__init__.py +0 -0
- annet/adapters/netbox/common/__init__.py +0 -0
- annet/adapters/netbox/common/client.py +87 -0
- annet/adapters/netbox/common/manufacturer.py +62 -0
- annet/adapters/netbox/common/models.py +105 -0
- annet/adapters/netbox/common/query.py +23 -0
- annet/adapters/netbox/common/status_client.py +25 -0
- annet/adapters/netbox/common/storage_opts.py +14 -0
- annet/adapters/netbox/provider.py +34 -0
- annet/adapters/netbox/v24/__init__.py +0 -0
- annet/adapters/netbox/v24/api_models.py +73 -0
- annet/adapters/netbox/v24/client.py +59 -0
- annet/adapters/netbox/v24/storage.py +196 -0
- annet/adapters/netbox/v37/__init__.py +0 -0
- annet/adapters/netbox/v37/api_models.py +38 -0
- annet/adapters/netbox/v37/client.py +62 -0
- annet/adapters/netbox/v37/storage.py +149 -0
- annet/annet.py +25 -0
- annet/annlib/__init__.py +7 -0
- annet/annlib/command.py +49 -0
- annet/annlib/diff.py +158 -0
- annet/annlib/errors.py +8 -0
- annet/annlib/filter_acl.py +196 -0
- annet/annlib/jsontools.py +116 -0
- annet/annlib/lib.py +495 -0
- annet/annlib/netdev/__init__.py +0 -0
- annet/annlib/netdev/db.py +62 -0
- annet/annlib/netdev/devdb/__init__.py +28 -0
- annet/annlib/netdev/devdb/data/devdb.json +137 -0
- annet/annlib/netdev/views/__init__.py +0 -0
- annet/annlib/netdev/views/dump.py +121 -0
- annet/annlib/netdev/views/hardware.py +112 -0
- annet/annlib/output.py +246 -0
- annet/annlib/patching.py +533 -0
- annet/annlib/rbparser/__init__.py +0 -0
- annet/annlib/rbparser/acl.py +120 -0
- annet/annlib/rbparser/deploying.py +55 -0
- annet/annlib/rbparser/ordering.py +52 -0
- annet/annlib/rbparser/platform.py +51 -0
- annet/annlib/rbparser/syntax.py +115 -0
- annet/annlib/rulebook/__init__.py +0 -0
- annet/annlib/rulebook/common.py +350 -0
- annet/annlib/tabparser.py +648 -0
- annet/annlib/types.py +35 -0
- annet/api/__init__.py +826 -0
- annet/argparse.py +415 -0
- annet/cli.py +237 -0
- annet/cli_args.py +503 -0
- annet/configs/context.yml +18 -0
- annet/configs/logging.yaml +39 -0
- annet/connectors.py +77 -0
- annet/deploy.py +536 -0
- annet/diff.py +84 -0
- annet/executor.py +551 -0
- annet/filtering.py +40 -0
- annet/gen.py +865 -0
- annet/generators/__init__.py +435 -0
- annet/generators/base.py +136 -0
- annet/generators/common/__init__.py +0 -0
- annet/generators/common/initial.py +33 -0
- annet/generators/entire.py +97 -0
- annet/generators/exceptions.py +10 -0
- annet/generators/jsonfragment.py +125 -0
- annet/generators/partial.py +119 -0
- annet/generators/perf.py +79 -0
- annet/generators/ref.py +15 -0
- annet/generators/result.py +127 -0
- annet/hardware.py +45 -0
- annet/implicit.py +139 -0
- annet/lib.py +128 -0
- annet/output.py +167 -0
- annet/parallel.py +448 -0
- annet/patching.py +25 -0
- annet/reference.py +148 -0
- annet/rulebook/__init__.py +114 -0
- annet/rulebook/arista/__init__.py +0 -0
- annet/rulebook/arista/iface.py +16 -0
- annet/rulebook/aruba/__init__.py +16 -0
- annet/rulebook/aruba/ap_env.py +146 -0
- annet/rulebook/aruba/misc.py +8 -0
- annet/rulebook/cisco/__init__.py +0 -0
- annet/rulebook/cisco/iface.py +68 -0
- annet/rulebook/cisco/misc.py +57 -0
- annet/rulebook/cisco/vlandb.py +90 -0
- annet/rulebook/common.py +19 -0
- annet/rulebook/deploying.py +87 -0
- annet/rulebook/huawei/__init__.py +0 -0
- annet/rulebook/huawei/aaa.py +75 -0
- annet/rulebook/huawei/bgp.py +97 -0
- annet/rulebook/huawei/iface.py +33 -0
- annet/rulebook/huawei/misc.py +337 -0
- annet/rulebook/huawei/vlandb.py +115 -0
- annet/rulebook/juniper/__init__.py +107 -0
- annet/rulebook/nexus/__init__.py +0 -0
- annet/rulebook/nexus/iface.py +92 -0
- annet/rulebook/patching.py +143 -0
- annet/rulebook/ribbon/__init__.py +12 -0
- annet/rulebook/texts/arista.deploy +20 -0
- annet/rulebook/texts/arista.order +125 -0
- annet/rulebook/texts/arista.rul +59 -0
- annet/rulebook/texts/aruba.deploy +20 -0
- annet/rulebook/texts/aruba.order +83 -0
- annet/rulebook/texts/aruba.rul +87 -0
- annet/rulebook/texts/cisco.deploy +27 -0
- annet/rulebook/texts/cisco.order +82 -0
- annet/rulebook/texts/cisco.rul +105 -0
- annet/rulebook/texts/huawei.deploy +188 -0
- annet/rulebook/texts/huawei.order +388 -0
- annet/rulebook/texts/huawei.rul +471 -0
- annet/rulebook/texts/juniper.rul +120 -0
- annet/rulebook/texts/nexus.deploy +24 -0
- annet/rulebook/texts/nexus.order +85 -0
- annet/rulebook/texts/nexus.rul +83 -0
- annet/rulebook/texts/nokia.rul +31 -0
- annet/rulebook/texts/pc.order +5 -0
- annet/rulebook/texts/pc.rul +9 -0
- annet/rulebook/texts/ribbon.deploy +22 -0
- annet/rulebook/texts/ribbon.rul +77 -0
- annet/rulebook/texts/routeros.order +38 -0
- annet/rulebook/texts/routeros.rul +45 -0
- annet/storage.py +125 -0
- annet/tabparser.py +36 -0
- annet/text_term_format.py +95 -0
- annet/tracing.py +170 -0
- annet/types.py +227 -0
- annet-0.0.dist-info/AUTHORS +21 -0
- annet-0.0.dist-info/LICENSE +21 -0
- annet-0.0.dist-info/METADATA +26 -0
- annet-0.0.dist-info/RECORD +137 -0
- annet-0.0.dist-info/WHEEL +5 -0
- annet-0.0.dist-info/entry_points.txt +5 -0
- annet-0.0.dist-info/top_level.txt +2 -0
- annet_generators/__init__.py +0 -0
- annet_generators/example/__init__.py +12 -0
- annet_generators/example/lldp.py +53 -0
annet/annlib/lib.py
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import bisect
|
|
3
|
+
import contextlib
|
|
4
|
+
import functools
|
|
5
|
+
import ipaddress
|
|
6
|
+
import itertools
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import pathlib
|
|
10
|
+
import re
|
|
11
|
+
import socket
|
|
12
|
+
import sys
|
|
13
|
+
import textwrap
|
|
14
|
+
import typing
|
|
15
|
+
from collections import OrderedDict as odict
|
|
16
|
+
from collections.abc import Iterable
|
|
17
|
+
from functools import lru_cache
|
|
18
|
+
from typing import List, NamedTuple, Optional, Tuple, Union
|
|
19
|
+
|
|
20
|
+
import contextlog
|
|
21
|
+
import jinja2
|
|
22
|
+
import mako.template
|
|
23
|
+
|
|
24
|
+
_logger = contextlog.get_logger()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# =====
|
|
28
|
+
class HuaweiNumBlock(metaclass=abc.ABCMeta):
|
|
29
|
+
def __init__(self, generator, block_name, start=5, step=5):
|
|
30
|
+
self._gen = generator
|
|
31
|
+
self._block_name = block_name
|
|
32
|
+
self._rule_num = start
|
|
33
|
+
self._rule_step = step
|
|
34
|
+
self._block = None
|
|
35
|
+
|
|
36
|
+
def _current(self):
|
|
37
|
+
(current, self._rule_num) = (self._rule_num, self._rule_num + self._rule_step)
|
|
38
|
+
return current
|
|
39
|
+
|
|
40
|
+
def __enter__(self):
|
|
41
|
+
self._block = self._gen.block(self._block_name)
|
|
42
|
+
self._block.__enter__()
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __exit__(self, exc_type, exc_val, trace):
|
|
46
|
+
self._block.__exit__(exc_type, exc_val, trace)
|
|
47
|
+
self._block = None
|
|
48
|
+
|
|
49
|
+
def __call__(self, rule_str, min_number=None):
|
|
50
|
+
if min_number is not None:
|
|
51
|
+
self._rule_num = max(self._rule_num, int(min_number))
|
|
52
|
+
return self.format_rule_string(rule_str)
|
|
53
|
+
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def format_rule_string(self, rule_str):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def huawei_expand_vlandb(row):
|
|
60
|
+
expanded = set()
|
|
61
|
+
row_parts = row.split()
|
|
62
|
+
for (index, part) in enumerate(row_parts):
|
|
63
|
+
if part == "to":
|
|
64
|
+
left = int(row_parts[index - 1])
|
|
65
|
+
right = int(row_parts[index + 1])
|
|
66
|
+
expanded = expanded.union(range(left + 1, right))
|
|
67
|
+
else:
|
|
68
|
+
expanded.add(int(part))
|
|
69
|
+
return expanded
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def cisco_expand_vlandb(row):
|
|
73
|
+
expanded = set()
|
|
74
|
+
for part in row.split(","):
|
|
75
|
+
range_parts = [int(p.strip()) for p in part.strip().split("-")]
|
|
76
|
+
assert len(range_parts) in (1, 2)
|
|
77
|
+
if len(range_parts) > 1:
|
|
78
|
+
left = range_parts[0]
|
|
79
|
+
right = range_parts[1]
|
|
80
|
+
expanded.update(range(left, right + 1))
|
|
81
|
+
else:
|
|
82
|
+
expanded.add(int(range_parts[0]))
|
|
83
|
+
return expanded
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def huawei_collapse_vlandb(vlans, chunk_len=0):
|
|
87
|
+
return collapse_vlandb(vlans, " to ", chunk_len=chunk_len)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def cisco_collapse_vlandb(vlans, tiny_ranges=True):
|
|
91
|
+
return collapse_vlandb(vlans, "-", tiny_ranges)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def juniper_fmt_prefix_lists_acl(prefix_list_names: typing.Iterable[str]) -> str:
|
|
95
|
+
"""Форматирует строку для acl из prefix-list-имён.
|
|
96
|
+
|
|
97
|
+
Производит следующие операции:
|
|
98
|
+
|
|
99
|
+
- вырезает дубликаты
|
|
100
|
+
- сортирует
|
|
101
|
+
- добавляет quoted-вариант имени (NOCDEV-5321)
|
|
102
|
+
- соединяет всё в строку
|
|
103
|
+
"""
|
|
104
|
+
sorted_unique_names = sorted(frozenset(prefix_list_names))
|
|
105
|
+
quoted_and_unquoted_names = []
|
|
106
|
+
for prefix_list_name in sorted_unique_names:
|
|
107
|
+
quoted_and_unquoted_names.append(prefix_list_name)
|
|
108
|
+
quoted_and_unquoted_names.append('"{}"'.format(prefix_list_name))
|
|
109
|
+
joined_prefix_lists = "|".join(quoted_and_unquoted_names)
|
|
110
|
+
return joined_prefix_lists
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# tiny_ranges - если выставлен в False, то два рядом стоящих влана не конвертятся в
|
|
114
|
+
# рендж (поведение cisco catalyst)
|
|
115
|
+
def collapse_vlandb(vlans, range_sep, tiny_ranges=True, chunk_len=0):
|
|
116
|
+
assert len(vlans) != 0
|
|
117
|
+
vlans = sorted(set(vlans))
|
|
118
|
+
res = []
|
|
119
|
+
row = [vlans[0], vlans[0]]
|
|
120
|
+
for vlan in vlans[1::]:
|
|
121
|
+
if row[1] == (vlan - 1):
|
|
122
|
+
row[1] = vlan
|
|
123
|
+
elif not tiny_ranges and row[1] - row[0] == 1:
|
|
124
|
+
res.append([row[0], row[0]])
|
|
125
|
+
res.append([row[1], row[1]])
|
|
126
|
+
row = [vlan, vlan]
|
|
127
|
+
else:
|
|
128
|
+
res.append(row)
|
|
129
|
+
row = [vlan, vlan]
|
|
130
|
+
res.append(row)
|
|
131
|
+
res = list(map(lambda x: x[0] != x[1] and "%s%s%s" % (x[0], range_sep, x[1]) or str(x[0]), res))
|
|
132
|
+
|
|
133
|
+
# Устройства бьют списки вланов на чанки при добавлении в конфиг
|
|
134
|
+
if chunk_len:
|
|
135
|
+
chunks = [res[i:i + chunk_len] for i in range(0, len(res), chunk_len)]
|
|
136
|
+
return chunks
|
|
137
|
+
|
|
138
|
+
return res
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def huawei_iface_ranges(iface_names):
|
|
142
|
+
ret = []
|
|
143
|
+
grouped = odict()
|
|
144
|
+
for iface in iface_names:
|
|
145
|
+
match = re.match(r"(.*[:/])(\d+)$", iface)
|
|
146
|
+
if not match:
|
|
147
|
+
ret.append(iface)
|
|
148
|
+
else:
|
|
149
|
+
prefix, index = match.groups()
|
|
150
|
+
grouped.setdefault(prefix, []).append((int(index), iface))
|
|
151
|
+
|
|
152
|
+
for ifaces in grouped.values():
|
|
153
|
+
start = 0
|
|
154
|
+
while start < len(ifaces):
|
|
155
|
+
end = start
|
|
156
|
+
while end + 1 < len(ifaces) and ifaces[end + 1][0] == ifaces[end][0] + 1:
|
|
157
|
+
end += 1
|
|
158
|
+
if start == end:
|
|
159
|
+
ret.append(ifaces[start][1])
|
|
160
|
+
else:
|
|
161
|
+
ret.append((ifaces[start][1], ifaces[end][1]))
|
|
162
|
+
start = end + 1
|
|
163
|
+
|
|
164
|
+
return ret
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def juniper_port_split(iface_name):
|
|
168
|
+
ret = dict(zip(["speed", "fpc", "pic", "port"], re.split(r"-+|/+|:", iface_name)))
|
|
169
|
+
return ret
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def make_ip4_mask(prefix_len, inverse=False):
|
|
173
|
+
"""
|
|
174
|
+
Returns dotted-quad string representing IPv4 mask of given length
|
|
175
|
+
|
|
176
|
+
E.g., for prefix_len=25 it returns "255.255.255.128"
|
|
177
|
+
"""
|
|
178
|
+
if not isinstance(prefix_len, int) or prefix_len < 0 or prefix_len > 32:
|
|
179
|
+
raise ValueError("invalid prefix_len %r" % prefix_len)
|
|
180
|
+
bin_mask = (0xffffffff & (0xffffffff << (32 - prefix_len))).to_bytes(4, byteorder="big")
|
|
181
|
+
if inverse:
|
|
182
|
+
bin_mask = bytes(byte ^ 255 for byte in bin_mask)
|
|
183
|
+
return socket.inet_ntop(socket.AF_INET, bin_mask)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def merge_dicts(*args):
|
|
187
|
+
if len(args) == 0:
|
|
188
|
+
return odict()
|
|
189
|
+
if len(args) == 1:
|
|
190
|
+
return args[0]
|
|
191
|
+
if all(map(lambda x: x[0] == x[1], zip(args[:-1], args[1:]))):
|
|
192
|
+
return args[0]
|
|
193
|
+
|
|
194
|
+
merged = odict()
|
|
195
|
+
for dictionary in args:
|
|
196
|
+
assert isinstance(dictionary, (dict, odict))
|
|
197
|
+
for (key, value) in dictionary.items():
|
|
198
|
+
if isinstance(value, (dict, odict)):
|
|
199
|
+
merged[key] = merge_dicts(*[x[key] for x in args if key in x])
|
|
200
|
+
elif key not in merged:
|
|
201
|
+
merged[key] = value
|
|
202
|
+
elif isinstance(merged[key], (list, tuple)):
|
|
203
|
+
merged[key] = merged[key].__class__(itertools.chain(merged[key], value))
|
|
204
|
+
else:
|
|
205
|
+
merged[key] = value
|
|
206
|
+
return merged
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def meta_decor(*method_list):
|
|
210
|
+
def wrapper(*args, **kwds):
|
|
211
|
+
for method in method_list:
|
|
212
|
+
res = method(*args, **kwds)
|
|
213
|
+
return res
|
|
214
|
+
|
|
215
|
+
return wrapper
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def first(iterable, default=None):
|
|
219
|
+
return next(iter(iterable), default)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def flatten(iterable):
|
|
223
|
+
for x in iterable:
|
|
224
|
+
if not isinstance(x, (str, bytes)) and isinstance(x, Iterable):
|
|
225
|
+
yield from flatten(x)
|
|
226
|
+
else:
|
|
227
|
+
yield x
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def uniq(*iterables):
|
|
231
|
+
seen = set()
|
|
232
|
+
for iterable in iterables:
|
|
233
|
+
for item in iterable:
|
|
234
|
+
if item not in seen:
|
|
235
|
+
seen.add(item)
|
|
236
|
+
yield item
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Хелпер чтобы при итерировании по вложенным структурам
|
|
240
|
+
# правильно подбирать значения и флаги (local_as, update_source, multipath, etc)
|
|
241
|
+
# которые должны наследоваться от верхних блоков к нижним
|
|
242
|
+
# XXX Покрыть тестами
|
|
243
|
+
class ContextOrderedDict:
|
|
244
|
+
def __init__(self, iterable):
|
|
245
|
+
self.dict = odict(iterable)
|
|
246
|
+
|
|
247
|
+
@contextlib.contextmanager
|
|
248
|
+
def update(self, peers_sub_dict):
|
|
249
|
+
values_backup = self.dict.copy()
|
|
250
|
+
for key in set(self.dict).intersection(peers_sub_dict.keys()):
|
|
251
|
+
if not isinstance(peers_sub_dict[key], dict):
|
|
252
|
+
self.dict[key] = peers_sub_dict[key]
|
|
253
|
+
yield
|
|
254
|
+
self.dict = values_backup
|
|
255
|
+
del values_backup
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# {{{ http://code.activestate.com/recipes/511478/ (r1)
|
|
259
|
+
def percentile(list_numbers, percent, key=lambda x: x):
|
|
260
|
+
"""
|
|
261
|
+
Find the percentile of a list of values.
|
|
262
|
+
|
|
263
|
+
@parameter N - is a list of values.
|
|
264
|
+
@parameter percent - a float value from 0.0 to 1.0.
|
|
265
|
+
@parameter key - optional key function to compute value from each element of N.
|
|
266
|
+
|
|
267
|
+
@return - the percentile of the values
|
|
268
|
+
"""
|
|
269
|
+
if not list_numbers:
|
|
270
|
+
return None
|
|
271
|
+
list_numbers = sorted(list_numbers, key=key)
|
|
272
|
+
kkk = (len(list_numbers) - 1) * percent
|
|
273
|
+
floo = math.floor(kkk)
|
|
274
|
+
cei = math.ceil(kkk)
|
|
275
|
+
if floo == cei:
|
|
276
|
+
return key(list_numbers[int(kkk)])
|
|
277
|
+
d0 = key(list_numbers[int(floo)]) * (cei - kkk)
|
|
278
|
+
d1 = key(list_numbers[int(cei)]) * (kkk - floo)
|
|
279
|
+
return d0 + d1
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def mako_render(template, dedent=False, **kwargs):
|
|
283
|
+
@lru_cache(None)
|
|
284
|
+
def _compile_mako(template, dedent):
|
|
285
|
+
if dedent:
|
|
286
|
+
template = textwrap.dedent(template).strip()
|
|
287
|
+
return mako.template.Template(template)
|
|
288
|
+
|
|
289
|
+
ret = _compile_mako(template, dedent).render(**kwargs)
|
|
290
|
+
if dedent:
|
|
291
|
+
ret = ret.strip()
|
|
292
|
+
return ret
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def jinja_render(template, dedent=False, **kwargs):
|
|
296
|
+
@lru_cache(None)
|
|
297
|
+
def _compile_jinja(template, dedent):
|
|
298
|
+
if dedent:
|
|
299
|
+
template = textwrap.dedent(template).strip()
|
|
300
|
+
return jinja2.Template(template)
|
|
301
|
+
|
|
302
|
+
ret = _compile_jinja(template, dedent).render(**kwargs)
|
|
303
|
+
if dedent:
|
|
304
|
+
ret = ret.strip()
|
|
305
|
+
return ret
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# =====
|
|
309
|
+
def find_exc_in_stack(
|
|
310
|
+
container_exc: Exception,
|
|
311
|
+
target_exc_type: Union[type, Tuple[type, ...]]
|
|
312
|
+
) -> Optional[BaseException]:
|
|
313
|
+
curr_err: Optional[BaseException] = container_exc
|
|
314
|
+
while curr_err is not None:
|
|
315
|
+
if isinstance(curr_err, target_exc_type):
|
|
316
|
+
return curr_err
|
|
317
|
+
curr_err = curr_err.__context__
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def find_modules(base_dir):
|
|
322
|
+
"""
|
|
323
|
+
Рекурсивно ищем в base_dir файлы python модулей
|
|
324
|
+
"""
|
|
325
|
+
for entry in os.scandir(base_dir):
|
|
326
|
+
if entry.name[:1] in ["_", "."]:
|
|
327
|
+
continue
|
|
328
|
+
|
|
329
|
+
if entry.is_dir():
|
|
330
|
+
child_dir = os.path.join(base_dir, entry.name)
|
|
331
|
+
yield from (".".join((entry.name, child_name)) for child_name in find_modules(child_dir))
|
|
332
|
+
elif entry.is_file() and entry.name.endswith(".py"):
|
|
333
|
+
yield entry.name[:-3]
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def catch_ctrl_c(func):
|
|
337
|
+
@functools.wraps(func)
|
|
338
|
+
def wrapper(*args, **kwargs):
|
|
339
|
+
try:
|
|
340
|
+
return func(*args, **kwargs)
|
|
341
|
+
except KeyboardInterrupt:
|
|
342
|
+
_logger.error("Got Ctrl+C, exiting")
|
|
343
|
+
sys.exit(127)
|
|
344
|
+
|
|
345
|
+
return wrapper
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class LMSegment(NamedTuple):
|
|
349
|
+
"""
|
|
350
|
+
LMSegment хранит подсеть в виде целочисленных пар (int(network_addr), int(broadcast_addr)):
|
|
351
|
+
0.0.0.0/0: (0b0000000000000000000000000, 0b1111111111111111111111111)
|
|
352
|
+
0.0.0.0/16: (0b0000000000000000000000000, 0b0000000000001111111111111)
|
|
353
|
+
1.0.0.0/24: (0b1000000000000000000000000, 0b1000000000000000011111111)
|
|
354
|
+
|
|
355
|
+
Для двух x=(A, B), y=(C, D) таких пар (A <= B, C <= D по определению):
|
|
356
|
+
|
|
357
|
+
Отношение включения: x contains y <-> A <= C && D <= B:
|
|
358
|
+
1.0.0.0/24 contains 1.0.0.1/32
|
|
359
|
+
0.0.0.0/0 contains 0.0.0.0/16
|
|
360
|
+
|
|
361
|
+
Отношение порядка: x < y <-> A < C || (A == C && B > D), или проще (A, -B) < (C, -D):
|
|
362
|
+
1.0.0.0/24 < 1.0.0.1/32
|
|
363
|
+
0.0.0.0/0 < 0.0.0.0/16
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
begin: int
|
|
367
|
+
end: int
|
|
368
|
+
|
|
369
|
+
def contains(self, other: "LMSegment") -> bool:
|
|
370
|
+
return self.begin <= other.begin and other.end <= self.end
|
|
371
|
+
|
|
372
|
+
@classmethod
|
|
373
|
+
def from_net(cls, net: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]):
|
|
374
|
+
return cls(
|
|
375
|
+
begin=int(net.network_address),
|
|
376
|
+
end=int(net.network_address) | int(net.hostmask),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def _cmp_identity(self) -> Tuple[int, int]:
|
|
380
|
+
return (self.begin, -self.end)
|
|
381
|
+
|
|
382
|
+
def __eq__(self, other: "LMSegment"):
|
|
383
|
+
return self._cmp_identity() == other._cmp_identity()
|
|
384
|
+
|
|
385
|
+
def __lt__(self, other: "LMSegment"):
|
|
386
|
+
return self._cmp_identity() < other._cmp_identity()
|
|
387
|
+
|
|
388
|
+
def __le__(self, other: "LMSegment"):
|
|
389
|
+
return self._cmp_identity() <= other._cmp_identity()
|
|
390
|
+
|
|
391
|
+
def __ge__(self, other: "LMSegment"):
|
|
392
|
+
return self._cmp_identity() >= other._cmp_identity()
|
|
393
|
+
|
|
394
|
+
def __gt__(self, other: "LMSegment"):
|
|
395
|
+
return self._cmp_identity() > other._cmp_identity()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class LMSegmentList:
|
|
399
|
+
"""Упорядоченный список подсетей
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
def __init__(self):
|
|
403
|
+
self.pfxs: List[LMSegment] = []
|
|
404
|
+
|
|
405
|
+
def add(self, pref: LMSegment) -> None:
|
|
406
|
+
"""Добавляем новый префикс в упорядоченный список, если он не дублируется
|
|
407
|
+
"""
|
|
408
|
+
idx = bisect.bisect(self.pfxs, pref) - 1
|
|
409
|
+
if 0 <= idx < len(self.pfxs):
|
|
410
|
+
if pref == self.pfxs[idx]:
|
|
411
|
+
return
|
|
412
|
+
self.pfxs.insert(idx + 1, pref)
|
|
413
|
+
|
|
414
|
+
def find(self, target: LMSegment) -> Optional[LMSegment]:
|
|
415
|
+
"""LPM поиск в добавленных
|
|
416
|
+
"""
|
|
417
|
+
upper_bound = bisect.bisect(self.pfxs, target)
|
|
418
|
+
for i in reversed(range(0, upper_bound)):
|
|
419
|
+
if self.pfxs[i].contains(target):
|
|
420
|
+
return self.pfxs[i]
|
|
421
|
+
if self.pfxs[i] > target:
|
|
422
|
+
break
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class LMSMatcher:
|
|
427
|
+
"""Обертка над парой LMSegmentList над парой LMSegmentList для v4/v6
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
def __init__(self):
|
|
431
|
+
self.v4 = LMSegmentList()
|
|
432
|
+
self.v6 = LMSegmentList()
|
|
433
|
+
|
|
434
|
+
def add(self, pref: str) -> None:
|
|
435
|
+
net = ipaddress.ip_network(pref)
|
|
436
|
+
self._af(net).add(LMSegment.from_net(net))
|
|
437
|
+
|
|
438
|
+
def find(self, pref: str) -> Optional[str]:
|
|
439
|
+
net = ipaddress.ip_network(pref)
|
|
440
|
+
found = self._af(net).find(LMSegment.from_net(net))
|
|
441
|
+
if found:
|
|
442
|
+
net_addr = net.__class__(found.begin).network_address
|
|
443
|
+
net_hostmask = (found.begin ^ found.end).bit_length()
|
|
444
|
+
net_mask = net_addr.max_prefixlen - net_hostmask
|
|
445
|
+
return str(net_addr) + "/" + str(net_mask)
|
|
446
|
+
|
|
447
|
+
def _af(self, net):
|
|
448
|
+
if net.version == 4:
|
|
449
|
+
return self.v4
|
|
450
|
+
elif net.version == 6:
|
|
451
|
+
return self.v6
|
|
452
|
+
else:
|
|
453
|
+
raise ValueError(str(net))
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def is_relative(path: pathlib.PurePath, *other: str) -> bool:
|
|
457
|
+
"""Проверяет является ли путь path относительным любого из other
|
|
458
|
+
"""
|
|
459
|
+
for checkpath in other:
|
|
460
|
+
try:
|
|
461
|
+
path.relative_to(checkpath)
|
|
462
|
+
return True
|
|
463
|
+
except ValueError:
|
|
464
|
+
pass
|
|
465
|
+
return False
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
_ANNOTATION_SEP = "\t# "
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def add_annotation(row: str, annotation: str) -> str:
|
|
472
|
+
return _ANNOTATION_SEP.join((row, annotation))
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def strip_annotation(row: str) -> str:
|
|
476
|
+
return row.rsplit(_ANNOTATION_SEP, 1)[0]
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def jun_inactive_pfx() -> str:
|
|
480
|
+
return "inactive: "
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def jun_is_inactive(key) -> bool:
|
|
484
|
+
return key.startswith(jun_inactive_pfx())
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def jun_activate(key) -> str:
|
|
488
|
+
if jun_is_inactive(key):
|
|
489
|
+
key = key[len(jun_inactive_pfx()):]
|
|
490
|
+
return key
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
# TODO: Remove with python3.10
|
|
494
|
+
async def anext(async_iter):
|
|
495
|
+
return await async_iter.__anext__() # pylint: disable=unnecessary-dunder-call
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import functools
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_db(prepared):
|
|
6
|
+
allowed = _make_allowed_by_seq(prepared)
|
|
7
|
+
return (
|
|
8
|
+
_build_tree(prepared, allowed),
|
|
9
|
+
functools.reduce(set.union, allowed.values()), # All sequences
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_true_sequences(hw_model, tree):
|
|
14
|
+
sequences = set()
|
|
15
|
+
for (regexp, meta) in tree.items():
|
|
16
|
+
if regexp.search(hw_model):
|
|
17
|
+
sequences.update(meta["sequences"])
|
|
18
|
+
sequences.update(find_true_sequences(hw_model, meta["children"]))
|
|
19
|
+
return sequences
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _build_tree(prepared, allowed_by_seq):
|
|
23
|
+
tree = {}
|
|
24
|
+
for (seq, regexp) in prepared.items():
|
|
25
|
+
sub = tree
|
|
26
|
+
for sub_seq in _seq_subs(seq):
|
|
27
|
+
regexp = prepared[sub_seq]
|
|
28
|
+
if regexp not in sub:
|
|
29
|
+
sub[regexp] = {
|
|
30
|
+
"sequences": allowed_by_seq[sub_seq],
|
|
31
|
+
"children": {},
|
|
32
|
+
}
|
|
33
|
+
sub = sub[regexp]["children"]
|
|
34
|
+
return tree
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _seq_subs(seq):
|
|
38
|
+
for index in range(1, len(seq) + 1):
|
|
39
|
+
yield seq[:index]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _make_allowed_by_seq(sequences):
|
|
43
|
+
all_variants = collections.Counter()
|
|
44
|
+
variants_by_seq = {}
|
|
45
|
+
|
|
46
|
+
for seq in sequences:
|
|
47
|
+
variants = _make_seq_variants(seq)
|
|
48
|
+
all_variants.update(variants)
|
|
49
|
+
variants_by_seq[seq] = variants
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
seq: set(variant for variant in variants if all_variants[variant] <= 1)
|
|
53
|
+
for (seq, variants) in variants_by_seq.items()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _make_seq_variants(seq):
|
|
58
|
+
return set(
|
|
59
|
+
seq[left:-right] + (seq[-1],)
|
|
60
|
+
for left in range(len(seq))
|
|
61
|
+
for right in range(1, len(seq[left:]) + 1)
|
|
62
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from os import path
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from annet.annlib.netdev.db import find_true_sequences, get_db
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@functools.lru_cache(None)
|
|
11
|
+
def parse_hw_model(hw_model):
|
|
12
|
+
prepared = _prepare_db()
|
|
13
|
+
(tree, all_sequences) = get_db(prepared)
|
|
14
|
+
true_sequences = find_true_sequences(hw_model, tree)
|
|
15
|
+
return (
|
|
16
|
+
sorted(true_sequences),
|
|
17
|
+
all_sequences.difference(true_sequences),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _prepare_db() -> Dict[str, Any]:
|
|
22
|
+
try:
|
|
23
|
+
from library.python import resource
|
|
24
|
+
raw = json.loads(resource.resfs_read("contrib/python/annet/annet/annlib/netdev/devdb/data/devdb.json").decode("utf-8"))
|
|
25
|
+
except ImportError:
|
|
26
|
+
with open(path.join(path.dirname(__file__), "data", "devdb.json"), "r") as f:
|
|
27
|
+
raw = json.load(f)
|
|
28
|
+
return {tuple(seq.split(".")): re.compile(regexp) for (seq, regexp) in raw.items()}
|