blob: 545eb942a38509dfb1c5b3b5d14337e9f8214230 [file] [log] [blame]
Alex0bcf31b2022-03-29 17:38:58 -05001import json
2import ipaddress
3
4from copy import deepcopy
5from cfg_checker.modules.network.network_errors import NetworkErrors
6
7_network_item = {
8 "runtime": {},
9 "config": {},
10 "reclass": {}
11}
12errors = NetworkErrors()
13_console = []
14
15
16def cprint(_str):
17 print(_str)
18 _console.append(_str)
19
20
21# adding net data to tree
22def _add_data(_list, _n, _h, _d):
23 if _n not in _list:
24 _list[_n] = {}
25 _list[_n][_h] = [_d]
26 elif _h not in _list[_n]:
27 # there is no such host, just create it
28 _list[_n][_h] = [_d]
29 else:
30 # there is such host... this is an error
31 errors.add_error(
32 errors.NET_DUPLICATE_IF,
33 host=_h,
34 dup_if=_d['name']
35 )
36 _list[_n][_h].append(_d)
37
38
39def _map_network_for_host(host, if_class, net_list, data):
40 # filter networks for this IF IP
41 _nets = [n for n in net_list.keys() if if_class.ip in n]
42 _masks = [n.netmask for n in _nets]
43 if len(_nets) > 1:
44 # There a multiple network found for this IP, Error
45 errors.add_error(
46 errors.NET_SUBNET_INTERSECT,
47 host=host,
48 ip=str(if_class.exploded),
49 networks="; ".join([str(_n) for _n in _nets])
50 )
51 # check mask match
52 if len(_nets) > 0 and if_class.netmask not in _masks:
53 errors.add_error(
54 errors.NET_MASK_MISMATCH,
55 host=host,
56 if_name=data['name'],
57 if_cidr=if_class.exploded,
58 if_mapped_networks=", ".join([str(_n) for _n in _nets])
59 )
60
61 if len(_nets) < 1:
62 _add_data(net_list, if_class.network, host, data)
63 else:
64 # add all data
65 for net in _nets:
66 _add_data(net_list, net, host, data)
67
68 return net_list
69
70
71host = "tmp-node-00000"
72node_data = {}
73interfaces = {
74 host: {}
75}
76_runtime = {}
77
78with open("ifs_data.json", "rt") as ff:
79 jj = json.loads(ff.read())
80
81node_data['routes'] = jj.pop("routes")
82node_data['networks'] = jj
83
84cprint("# {} Networks".format(len(jj)))
85
86for net_name, net_data in node_data['networks'].items():
87 # cut net name
88 _i = net_name.find('@')
89 _name = net_name if _i < 0 else net_name[:_i]
90 # get ips and calculate subnets
91 if _name in ['lo']:
92 # skip the localhost
93 continue
94 else:
95 # add collected data to interface storage
96 if _name not in interfaces[host]:
97 interfaces[host][_name] = \
98 deepcopy(_network_item)
99 interfaces[host][_name]['runtime'] = \
100 deepcopy(net_data)
101
102 # get data and make sure that wide mask goes first
103 _ip4s = sorted(
104 net_data['ipv4'],
105 key=lambda s: s[s.index('/'):]
106 )
107 for _ip_str in _ip4s:
108 # create interface class
109 _if = ipaddress.IPv4Interface(_ip_str)
110 # check if this is a VIP
111 # ...all those will have /32 mask
112 net_data['vip'] = None
113 if _if.network.prefixlen == 32:
114 net_data['vip'] = str(_if.exploded)
115 if 'name' not in net_data:
116 net_data['name'] = _name
117 if 'ifs' not in net_data:
118 net_data['ifs'] = [_if]
119 # map it
120 _runtime = _map_network_for_host(
121 host,
122 _if,
123 _runtime,
124 net_data
125 )
126 else:
127 # data is already there, just add VIP
128 net_data['ifs'].append(_if)
129
130
131def process_interface(lvl, interface, tree, res):
132 if abs(lvl) > 50:
133 cprint("### ERROR: Probable cyclic dependency, exiting")
134 return
135 cprint("{}{}:{}".format(" "*(10+lvl), lvl, interface))
136 # get childs for each root
137 # tree row item (<if_name>, [<parents>], [<childs>])
138 if lvl not in tree:
139 # - no level - add it
140 tree[lvl] = {}
141 # there is such interface in this level?
142 if interface not in tree[lvl]:
143 # - IF not present
144 _n = ''
145 if interface not in res:
146 _n = 'unknown IF'
147 _p = None
148 _c = None
149 else:
150 # -- get parents, add
151 _p = res[interface]['lower']
152 # -- get childs, add
153 _c = res[interface]['upper']
154
155 # if None, put empty list
156 _p = _p if _p else []
157 # if None, put empty list
158 _c = _c if _c else []
159 tree[lvl].update({
160 interface: {
161 "note": _n,
162 "parents": _p,
163 "children": _c,
164 "size": len(_p) if len(_p) > len(_c) else len(_c)
165 }
166 })
167 for p_if in tree[lvl][interface]["parents"]:
168 # -- cycle: execute process for next parent, lvl-1
169 process_interface(lvl-1, p_if, tree, res)
170 for c_if in tree[lvl][interface]["children"]:
171 # -- cycle: execute process for next child, lvl+1
172 process_interface(lvl+1, c_if, tree, res)
173 else:
174 # - IF present - exit (been here already)
175 cprint("{}{}".format(" "*(10+lvl), "--branch-end--\n"))
176 return
177
178
179def _put(cNet, cIndex, _list):
180 _added = False
181 _actual_index = -1
182 # Check list len
183 _len = len(_list)
184 if cIndex >= _len:
185 # grow list to meet index
186 _list = _list + [''] * (cIndex - _len + 1)
187 _len = len(_list)
188
189 for _cI in range(cIndex, _len):
190 # add child per index
191 # if space is free
192 if not _list[_cI]:
193 _list[_cI] = cNet
194 _added = True
195 _actual_index = _cI
196 break
197 if not _added:
198 # grow list by one entry
199 _list = _list + [cNet]
200 _actual_index = len(_list) - 1
201 return _actual_index, _list
202
203
204# build network hierachy
205nr = node_data['networks']
206# walk interface tree
207for _ifname in node_data['networks']:
208 _tree = {}
209 _level = 0
210 cprint("# -> {}".format(_ifname))
211 process_interface(_level, _ifname, _tree, nr)
212 # save tree for node/if
213 node_data['networks'][_ifname]['tree'] = _tree
214
215 # debug, print built tree
216 cprint("# end '{}'".format(_ifname))
217 lvls = list(_tree.keys())
218 lvls.sort()
219 n = len(lvls)
220 m = max([len(_tree[k].keys()) for k in _tree.keys()])
221 matrix = [["" for i in range(m)] for j in range(n)]
222 x = 0
223 while True:
224 _lv = lvls.pop(0)
225 # get all interfaces on this level
226 nets = iter(_tree[_lv].keys())
227 while True:
228 y = 0
229 # get next interface
230 try:
231 _net = next(nets)
232 except StopIteration:
233 break
234 # all nets
235 _a = [_net]
236 # put current interface if this is only one left
237 if not _tree[_lv][_net]['children']:
238 if _net not in matrix[x]:
239 _, matrix[x] = _put(
240 _net,
241 y,
242 matrix[x]
243 )
244 y += 1
245 else:
246 # get all nets with same child
247 for _c in _tree[_lv][_net]['children']:
248 for _o_net in nets:
249 if _c in _tree[_lv][_o_net]['children']:
250 _a.append(_o_net)
251 # flush collected nets
252 for idx in range(len(_a)):
253 if _a[idx] in matrix[x]:
254 # there is such interface on this level
255 # get index
256 _nI = matrix[x].index(_a[idx])
257 _, matrix[x+1] = _put(
258 _c,
259 _nI,
260 matrix[x+1]
261 )
262 else:
263 # there is no such interface
264 # add it
265 _t, matrix[x] = _put(
266 _a[idx],
267 0,
268 matrix[x]
269 )
270 # also, put child
271 _, matrix[x+1] = _put(
272 _c,
273 _t,
274 matrix[x+1]
275 )
276 # remove collected nets from processing
277 if _a[idx] in nets:
278 nets.remove(_a[idx])
279 y += len(_a)
280 if not nets:
281 x += 1
282 break
283 if not lvls:
284 break
285
286 lines = []
287 _columns = [len(max([i for i in li])) for li in matrix]
288 for idx_y in range(m):
289 line = ""
290 for idx_x in range(n):
291 _len = _columns[idx_x] if _columns[idx_x] else 1
292 _fmt = "{" + ":{}".format(_len) + "} "
293 line += _fmt.format(matrix[idx_x][idx_y])
294 lines.append(line)
295 node_data['networks'][_ifname]['matrix'] = matrix
296 node_data['networks'][_ifname]['lines'] = lines
297 cprint("#### Tree")
298 cprint("\n".join(lines))
299
300with open("debug_net_console.log", "w+") as ff:
301 ff.write("\n".join(_console))