blob: dea7d4ef2ea93f31a6484784f6012aa9871035bb [file] [log] [blame]
Alexe0c5b9e2019-04-23 18:51:23 -05001import ipaddress
2import json
Alex6b633ec2019-06-06 19:44:34 -05003from copy import deepcopy
Alexe0c5b9e2019-04-23 18:51:23 -05004
5from cfg_checker.common import logger_cli
6from cfg_checker.common.exception import InvalidReturnException
Alex1f90e7b2021-09-03 15:31:28 -05007from cfg_checker.common.exception import ConfigException
8from cfg_checker.common.exception import KubeException
Alexe0c5b9e2019-04-23 18:51:23 -05009from cfg_checker.modules.network.network_errors import NetworkErrors
Alex205546c2020-12-30 19:22:30 -060010from cfg_checker.nodes import SaltNodes, KubeNodes
Alexe0c5b9e2019-04-23 18:51:23 -050011
12# TODO: use templated approach
13# net interface structure should be the same
14_if_item = {
15 "name": "unnamed interface",
16 "mac": "",
17 "routes": {},
Alex6b633ec2019-06-06 19:44:34 -050018 "proto": "",
Alexe0c5b9e2019-04-23 18:51:23 -050019 "ip": [],
20 "parameters": {}
21}
22
23# collection of configurations
24_network_item = {
25 "runtime": {},
26 "config": {},
27 "reclass": {}
28}
29
30
31class NetworkMapper(object):
32 RECLASS = "reclass"
33 CONFIG = "config"
34 RUNTIME = "runtime"
35
Alexe9908f72020-05-19 16:04:53 -050036 def __init__(
37 self,
Alex9a4ad212020-10-01 18:04:25 -050038 config,
Alexe9908f72020-05-19 16:04:53 -050039 errors_class=None,
40 skip_list=None,
41 skip_list_file=None
42 ):
Alexe0c5b9e2019-04-23 18:51:23 -050043 logger_cli.info("# Initializing mapper")
Alex205546c2020-12-30 19:22:30 -060044 self.env_config = config
Alex6b633ec2019-06-06 19:44:34 -050045 # init networks and nodes
Alexe0c5b9e2019-04-23 18:51:23 -050046 self.networks = {}
Alex205546c2020-12-30 19:22:30 -060047 self.nodes = self.master.get_nodes(
Alexe9908f72020-05-19 16:04:53 -050048 skip_list=skip_list,
49 skip_list_file=skip_list_file
50 )
Alex205546c2020-12-30 19:22:30 -060051 self.cluster = self.master.get_info()
52 self.domain = self.master.domain
Alex6b633ec2019-06-06 19:44:34 -050053 # init and pre-populate interfaces
54 self.interfaces = {k: {} for k in self.nodes}
55 # Init errors class
Alexe0c5b9e2019-04-23 18:51:23 -050056 if errors_class:
57 self.errors = errors_class
58 else:
59 logger_cli.debug("... init error logs folder")
60 self.errors = NetworkErrors()
61
62 # adding net data to tree
63 def _add_data(self, _list, _n, _h, _d):
64 if _n not in _list:
65 _list[_n] = {}
66 _list[_n][_h] = [_d]
67 elif _h not in _list[_n]:
68 # there is no such host, just create it
69 _list[_n][_h] = [_d]
70 else:
71 # there is such host... this is an error
72 self.errors.add_error(
73 self.errors.NET_DUPLICATE_IF,
74 host=_h,
75 dup_if=_d['name']
76 )
77 _list[_n][_h].append(_d)
78
79 # TODO: refactor map creation. Build one map instead of two separate
80 def _map_network_for_host(self, host, if_class, net_list, data):
81 # filter networks for this IF IP
82 _nets = [n for n in net_list.keys() if if_class.ip in n]
83 _masks = [n.netmask for n in _nets]
84 if len(_nets) > 1:
85 # There a multiple network found for this IP, Error
86 self.errors.add_error(
87 self.errors.NET_SUBNET_INTERSECT,
88 host=host,
89 ip=str(if_class.exploded),
90 networks="; ".join([str(_n) for _n in _nets])
91 )
92 # check mask match
93 if len(_nets) > 0 and if_class.netmask not in _masks:
94 self.errors.add_error(
95 self.errors.NET_MASK_MISMATCH,
96 host=host,
97 if_name=data['name'],
98 if_cidr=if_class.exploded,
99 if_mapped_networks=", ".join([str(_n) for _n in _nets])
100 )
101
102 if len(_nets) < 1:
103 self._add_data(net_list, if_class.network, host, data)
104 else:
105 # add all data
106 for net in _nets:
107 self._add_data(net_list, net, host, data)
108
109 return net_list
110
111 def _map_reclass_networks(self):
112 # class uses nodes from self.nodes dict
113 _reclass = {}
114 # Get required pillars
Alex205546c2020-12-30 19:22:30 -0600115 self.master.get_specific_pillar_for_nodes("linux:network")
116 for node in self.master.nodes.keys():
Alexe0c5b9e2019-04-23 18:51:23 -0500117 # check if this node
Alex205546c2020-12-30 19:22:30 -0600118 if not self.master.is_node_available(node):
Alexe0c5b9e2019-04-23 18:51:23 -0500119 continue
120 # get the reclass value
Alex9a4ad212020-10-01 18:04:25 -0500121 _pillar = \
Alex205546c2020-12-30 19:22:30 -0600122 self.master.nodes[node]['pillars']['linux']['network']
Alexe0c5b9e2019-04-23 18:51:23 -0500123 # we should be ready if there is no interface in reclass for a node
Alex92e07ce2019-05-31 16:00:03 -0500124 # for example on APT node
Alexe0c5b9e2019-04-23 18:51:23 -0500125 if 'interface' in _pillar:
126 _pillar = _pillar['interface']
127 else:
128 logger_cli.info(
129 "... node '{}' skipped, no IF section in reclass".format(
130 node
131 )
132 )
133 continue
Alex92e07ce2019-05-31 16:00:03 -0500134
Alex6b633ec2019-06-06 19:44:34 -0500135 # build map based on IPs and save info too
Alex3bc95f62020-03-05 17:00:04 -0600136 for if_name, _dat in _pillar.items():
Alexb3dc8592019-06-11 13:20:36 -0500137 # get proper IF name
138 _if_name = if_name if 'name' not in _dat else _dat['name']
139 # place it
Alex6b633ec2019-06-06 19:44:34 -0500140 if _if_name not in self.interfaces[node]:
141 self.interfaces[node][_if_name] = deepcopy(_network_item)
Alexb3dc8592019-06-11 13:20:36 -0500142 self.interfaces[node][_if_name]['reclass'] = deepcopy(_dat)
Alex6b633ec2019-06-06 19:44:34 -0500143 # map network if any
Alexb3dc8592019-06-11 13:20:36 -0500144 if 'address' in _dat:
Alexe0c5b9e2019-04-23 18:51:23 -0500145 _if = ipaddress.IPv4Interface(
Alexb3dc8592019-06-11 13:20:36 -0500146 _dat['address'] + '/' + _dat['netmask']
Alexe0c5b9e2019-04-23 18:51:23 -0500147 )
Alexb3dc8592019-06-11 13:20:36 -0500148 _dat['name'] = _if_name
149 _dat['ifs'] = [_if]
Alexe0c5b9e2019-04-23 18:51:23 -0500150
151 _reclass = self._map_network_for_host(
152 node,
153 _if,
154 _reclass,
Alexb3dc8592019-06-11 13:20:36 -0500155 _dat
Alexe0c5b9e2019-04-23 18:51:23 -0500156 )
157
158 return _reclass
159
160 def _map_configured_networks(self):
161 # class uses nodes from self.nodes dict
162 _confs = {}
163
Alex92e07ce2019-05-31 16:00:03 -0500164 # TODO: parse /etc/network/interfaces
165
Alexe0c5b9e2019-04-23 18:51:23 -0500166 return _confs
167
Alex1f90e7b2021-09-03 15:31:28 -0500168 def _map_runtime_networks(self, result):
Alexe0c5b9e2019-04-23 18:51:23 -0500169 # class uses nodes from self.nodes dict
170 _runtime = {}
Alex205546c2020-12-30 19:22:30 -0600171 for key in self.master.nodes.keys():
Alexe0c5b9e2019-04-23 18:51:23 -0500172 # check if we are to work with this node
Alex205546c2020-12-30 19:22:30 -0600173 if not self.master.is_node_available(key):
Alexe0c5b9e2019-04-23 18:51:23 -0500174 continue
Alex205546c2020-12-30 19:22:30 -0600175 # due to much data to be passed from master,
Alexe0c5b9e2019-04-23 18:51:23 -0500176 # it is happening in order
Alex1f90e7b2021-09-03 15:31:28 -0500177 if key in result:
178 _text = result[key]
Alexe0c5b9e2019-04-23 18:51:23 -0500179 if '{' in _text and '}' in _text:
180 _text = _text[_text.find('{'):]
181 else:
182 raise InvalidReturnException(
183 "Non-json object returned: '{}'".format(
184 _text
185 )
186 )
187 _dict = json.loads(_text[_text.find('{'):])
Alex205546c2020-12-30 19:22:30 -0600188 self.master.nodes[key]['routes'] = _dict.pop("routes")
189 self.master.nodes[key]['networks'] = _dict
Alexe0c5b9e2019-04-23 18:51:23 -0500190 else:
Alex205546c2020-12-30 19:22:30 -0600191 self.master.nodes[key]['networks'] = {}
192 self.master.nodes[key]['routes'] = {}
Alexe0c5b9e2019-04-23 18:51:23 -0500193 logger_cli.debug("... {} has {} networks".format(
194 key,
Alex205546c2020-12-30 19:22:30 -0600195 len(self.master.nodes[key]['networks'].keys())
Alexe0c5b9e2019-04-23 18:51:23 -0500196 ))
197 logger_cli.info("-> done collecting networks data")
198
Alex3cdb1bd2021-09-10 15:51:11 -0500199 logger_cli.info("-> mapping runtime network IPs")
Alexe0c5b9e2019-04-23 18:51:23 -0500200 # match interfaces by IP subnets
Alex205546c2020-12-30 19:22:30 -0600201 for host, node_data in self.master.nodes.items():
202 if not self.master.is_node_available(host):
Alexe0c5b9e2019-04-23 18:51:23 -0500203 continue
204
Alex3bc95f62020-03-05 17:00:04 -0600205 for net_name, net_data in node_data['networks'].items():
Alexb3dc8592019-06-11 13:20:36 -0500206 # cut net name
207 _i = net_name.find('@')
208 _name = net_name if _i < 0 else net_name[:_i]
Alexe0c5b9e2019-04-23 18:51:23 -0500209 # get ips and calculate subnets
Alexb3dc8592019-06-11 13:20:36 -0500210 if _name in ['lo']:
Alexe0c5b9e2019-04-23 18:51:23 -0500211 # skip the localhost
212 continue
Alex6b633ec2019-06-06 19:44:34 -0500213 else:
214 # add collected data to interface storage
Alexb3dc8592019-06-11 13:20:36 -0500215 if _name not in self.interfaces[host]:
216 self.interfaces[host][_name] = \
Alex6b633ec2019-06-06 19:44:34 -0500217 deepcopy(_network_item)
Alexb3dc8592019-06-11 13:20:36 -0500218 self.interfaces[host][_name]['runtime'] = \
Alex6b633ec2019-06-06 19:44:34 -0500219 deepcopy(net_data)
220
Alexe0c5b9e2019-04-23 18:51:23 -0500221 # get data and make sure that wide mask goes first
222 _ip4s = sorted(
223 net_data['ipv4'],
224 key=lambda s: s[s.index('/'):]
225 )
226 for _ip_str in _ip4s:
227 # create interface class
228 _if = ipaddress.IPv4Interface(_ip_str)
229 # check if this is a VIP
230 # ...all those will have /32 mask
231 net_data['vip'] = None
232 if _if.network.prefixlen == 32:
233 net_data['vip'] = str(_if.exploded)
234 if 'name' not in net_data:
Alexb3dc8592019-06-11 13:20:36 -0500235 net_data['name'] = _name
Alexe0c5b9e2019-04-23 18:51:23 -0500236 if 'ifs' not in net_data:
237 net_data['ifs'] = [_if]
238 # map it
239 _runtime = self._map_network_for_host(
240 host,
241 _if,
242 _runtime,
243 net_data
244 )
245 else:
246 # data is already there, just add VIP
247 net_data['ifs'].append(_if)
248
Alex1839bbf2019-08-22 17:17:21 -0500249 def process_interface(lvl, interface, tree, res):
250 # get childs for each root
251 # tree row item (<if_name>, [<parents>], [<childs>])
252 if lvl not in tree:
253 # - no level - add it
254 tree[lvl] = {}
255 # there is such interface in this level?
256 if interface not in tree[lvl]:
257 # - IF not present
Alexf3dbe862019-10-07 15:17:04 -0500258 _n = ''
259 if interface not in res:
260 _n = 'unknown IF'
261 _p = None
262 _c = None
263 else:
264 # -- get parents, add
265 _p = res[interface]['lower']
266 # -- get childs, add
267 _c = res[interface]['upper']
268
Alex1839bbf2019-08-22 17:17:21 -0500269 # if None, put empty list
270 _p = _p if _p else []
Alex1839bbf2019-08-22 17:17:21 -0500271 # if None, put empty list
272 _c = _c if _c else []
273 tree[lvl].update({
274 interface: {
Alexf3dbe862019-10-07 15:17:04 -0500275 "note": _n,
Alex1839bbf2019-08-22 17:17:21 -0500276 "parents": _p,
277 "children": _c,
278 "size": len(_p) if len(_p) > len(_c) else len(_c)
279 }
280 })
281 for p_if in tree[lvl][interface]["parents"]:
282 # -- cycle: execute process for next parent, lvl-1
283 process_interface(lvl-1, p_if, tree, res)
284 for c_if in tree[lvl][interface]["children"]:
285 # -- cycle: execute process for next child, lvl+1
286 process_interface(lvl+1, c_if, tree, res)
287 else:
288 # - IF present - exit (been here already)
289 return
290
291 def _put(cNet, cIndex, _list):
Alexf3dbe862019-10-07 15:17:04 -0500292 _added = False
293 _actual_index = -1
294 # Check list len
295 _len = len(_list)
296 if cIndex >= _len:
297 # grow list to meet index
298 _list = _list + [''] * (cIndex - _len + 1)
299 _len = len(_list)
300
301 for _cI in range(cIndex, _len):
Alex1839bbf2019-08-22 17:17:21 -0500302 # add child per index
303 # if space is free
304 if not _list[_cI]:
305 _list[_cI] = cNet
Alexf3dbe862019-10-07 15:17:04 -0500306 _added = True
307 _actual_index = _cI
Alex1839bbf2019-08-22 17:17:21 -0500308 break
Alexf3dbe862019-10-07 15:17:04 -0500309 if not _added:
310 # grow list by one entry
311 _list = _list + [cNet]
312 _actual_index = len(_list) - 1
313 return _actual_index, _list
Alex1839bbf2019-08-22 17:17:21 -0500314
315 # build network hierachy
316 nr = node_data['networks']
317 # walk interface tree
318 for _ifname in node_data['networks']:
319 _tree = {}
320 _level = 0
321 process_interface(_level, _ifname, _tree, nr)
322 # save tree for node/if
323 node_data['networks'][_ifname]['tree'] = _tree
324
325 # debug, print built tree
326 # logger_cli.debug("# '{}'".format(_ifname))
Alex3bc95f62020-03-05 17:00:04 -0600327 lvls = list(_tree.keys())
Alex1839bbf2019-08-22 17:17:21 -0500328 lvls.sort()
329 n = len(lvls)
330 m = max([len(_tree[k].keys()) for k in _tree.keys()])
331 matrix = [["" for i in range(m)] for j in range(n)]
332 x = 0
333 while True:
334 _lv = lvls.pop(0)
335 # get all interfaces on this level
Alex3bc95f62020-03-05 17:00:04 -0600336 nets = iter(_tree[_lv].keys())
Alex1839bbf2019-08-22 17:17:21 -0500337 while True:
338 y = 0
339 # get next interface
Alex3bc95f62020-03-05 17:00:04 -0600340 try:
341 _net = next(nets)
342 except StopIteration:
343 break
Alex1839bbf2019-08-22 17:17:21 -0500344 # all nets
345 _a = [_net]
346 # put current interface if this is only one left
347 if not _tree[_lv][_net]['children']:
348 if _net not in matrix[x]:
Alexf3dbe862019-10-07 15:17:04 -0500349 _, matrix[x] = _put(
350 _net,
351 y,
352 matrix[x]
353 )
Alex1839bbf2019-08-22 17:17:21 -0500354 y += 1
355 else:
356 # get all nets with same child
357 for _c in _tree[_lv][_net]['children']:
358 for _o_net in nets:
359 if _c in _tree[_lv][_o_net]['children']:
360 _a.append(_o_net)
361 # flush collected nets
362 for idx in range(len(_a)):
363 if _a[idx] in matrix[x]:
364 # there is such interface on this level
365 # get index
366 _nI = matrix[x].index(_a[idx])
Alexf3dbe862019-10-07 15:17:04 -0500367 _, matrix[x+1] = _put(
368 _c,
369 _nI,
370 matrix[x+1]
371 )
Alex1839bbf2019-08-22 17:17:21 -0500372 else:
373 # there is no such interface
374 # add it
Alexf3dbe862019-10-07 15:17:04 -0500375 _t, matrix[x] = _put(
376 _a[idx],
377 0,
378 matrix[x]
379 )
380 # also, put child
381 _, matrix[x+1] = _put(
382 _c,
383 _t,
384 matrix[x+1]
385 )
Alex1839bbf2019-08-22 17:17:21 -0500386 # remove collected nets from processing
387 if _a[idx] in nets:
388 nets.remove(_a[idx])
389 y += len(_a)
390 if not nets:
391 x += 1
392 break
393 if not lvls:
394 break
395
396 lines = []
397 _columns = [len(max([i for i in li])) for li in matrix]
398 for idx_y in range(m):
399 line = ""
400 for idx_x in range(n):
Alex9b2c1d12020-03-19 09:32:35 -0500401 _len = _columns[idx_x] if _columns[idx_x] else 1
402 _fmt = "{" + ":{}".format(_len) + "} "
Alex1839bbf2019-08-22 17:17:21 -0500403 line += _fmt.format(matrix[idx_x][idx_y])
404 lines.append(line)
405 node_data['networks'][_ifname]['matrix'] = matrix
406 node_data['networks'][_ifname]['lines'] = lines
Alexe0c5b9e2019-04-23 18:51:23 -0500407 return _runtime
408
Alex1f90e7b2021-09-03 15:31:28 -0500409
410class SaltNetworkMapper(NetworkMapper):
411 def __init__(
412 self,
413 config,
414 errors_class=None,
415 skip_list=None,
416 skip_list_file=None
417 ):
418 self.master = SaltNodes(config)
419 super(SaltNetworkMapper, self).__init__(
420 config,
421 errors_class=errors_class,
422 skip_list=skip_list,
423 skip_list_file=skip_list_file
424 )
425
426 def get_script_output(self):
427 """
428 Get runtime networks by executing script on nodes
429 """
430 logger_cli.info("# Mapping node runtime network data")
431 self.master.prepare_script_on_active_nodes("ifs_data.py")
432 _result = self.master.execute_script_on_active_nodes(
433 "ifs_data.py",
434 args="json"
435 )
436
437 return _result
438
439 def map_networks(self):
Alex3cdb1bd2021-09-10 15:51:11 -0500440 logger_cli.info("-> Mapping reclass networks")
Alex1f90e7b2021-09-03 15:31:28 -0500441 self.map_network(self.RECLASS)
Alex3cdb1bd2021-09-10 15:51:11 -0500442 logger_cli.info("-> Mapping runtime networks")
Alex1f90e7b2021-09-03 15:31:28 -0500443 self.map_network(self.RUNTIME)
444
Alexe0c5b9e2019-04-23 18:51:23 -0500445 def map_network(self, source):
446 # maps target network using given source
447 _networks = None
448
449 if source == self.RECLASS:
450 _networks = self._map_reclass_networks()
451 elif source == self.CONFIG:
452 _networks = self._map_configured_networks()
453 elif source == self.RUNTIME:
Alex1f90e7b2021-09-03 15:31:28 -0500454 _r = self.get_script_output()
455 _networks = self._map_runtime_networks(_r)
Alexe0c5b9e2019-04-23 18:51:23 -0500456
457 self.networks[source] = _networks
458 return _networks
459
Alex3cdb1bd2021-09-10 15:51:11 -0500460 def create_map(self, skip_keywords=None):
Alex836fac82019-08-22 13:36:16 -0500461 """Create all needed elements for map output
Alexe0c5b9e2019-04-23 18:51:23 -0500462
463 :return: none
464 """
465 _runtime = self.networks[self.RUNTIME]
466 _reclass = self.networks[self.RECLASS]
Alex836fac82019-08-22 13:36:16 -0500467
468 # main networks, target vars
469 _map = {}
Alex6b633ec2019-06-06 19:44:34 -0500470 # No matter of proto, at least one IP will be present for the network
Alex836fac82019-08-22 13:36:16 -0500471 # we interested in, since we are to make sure that L3 level
472 # is configured according to reclass model
Alexe0c5b9e2019-04-23 18:51:23 -0500473 for network in _reclass:
474 # shortcuts
475 _net = str(network)
Alex836fac82019-08-22 13:36:16 -0500476 _map[_net] = {}
Alexe0c5b9e2019-04-23 18:51:23 -0500477 if network not in _runtime:
478 # reclass has network that not found in runtime
479 self.errors.add_error(
480 self.errors.NET_NO_RUNTIME_NETWORK,
481 reclass_net=str(network)
482 )
Alex1839bbf2019-08-22 17:17:21 -0500483 logger_cli.warn(
484 "WARN: {}: {}".format(
485 " No runtime network ", str(network)
486 )
487 )
Alexe0c5b9e2019-04-23 18:51:23 -0500488 continue
Alex6b633ec2019-06-06 19:44:34 -0500489 # hostnames
Alexe0c5b9e2019-04-23 18:51:23 -0500490 names = sorted(_runtime[network].keys())
491 for hostname in names:
Alex836fac82019-08-22 13:36:16 -0500492 _notes = []
Alex6b633ec2019-06-06 19:44:34 -0500493 node = hostname.split('.')[0]
Alex205546c2020-12-30 19:22:30 -0600494 if not self.master.is_node_available(hostname, log=False):
Alexe0c5b9e2019-04-23 18:51:23 -0500495 logger_cli.info(
Alex6b633ec2019-06-06 19:44:34 -0500496 " {0:8} {1}".format(node, "node not available")
Alexe0c5b9e2019-04-23 18:51:23 -0500497 )
498 # add non-responsive node erorr
499 self.errors.add_error(
500 self.errors.NET_NODE_NON_RESPONSIVE,
501 host=hostname
502 )
Alex836fac82019-08-22 13:36:16 -0500503 _notes.append(
504 self.errors.get_error_type_text(
505 self.errors.NET_NODE_NON_RESPONSIVE
506 )
507 )
Alexe0c5b9e2019-04-23 18:51:23 -0500508 continue
Alex6b633ec2019-06-06 19:44:34 -0500509 # lookup interface name on node using network CIDR
510 _if_name = _runtime[network][hostname][0]["name"]
Alex836fac82019-08-22 13:36:16 -0500511 _raw = self.interfaces[hostname][_if_name]['runtime']
Alex6b633ec2019-06-06 19:44:34 -0500512 # get proper reclass
513 _r = self.interfaces[hostname][_if_name]['reclass']
Alex6b633ec2019-06-06 19:44:34 -0500514 _if_name_suffix = ""
515 # get the proto value
Alex3b8e5432019-06-11 15:21:59 -0500516 if _r:
517 _if_rc = ""
518 else:
519 self.errors.add_error(
520 self.errors.NET_NODE_UNEXPECTED_IF,
521 host=hostname,
522 if_name=_if_name
523 )
Alex836fac82019-08-22 13:36:16 -0500524 _notes.append(
525 self.errors.get_error_type_text(
526 self.errors.NET_NODE_UNEXPECTED_IF
527 )
528 )
Alex3b8e5432019-06-11 15:21:59 -0500529 _if_rc = "*"
530
Alex6b633ec2019-06-06 19:44:34 -0500531 if "proto" in _r:
532 _proto = _r['proto']
Alexe0c5b9e2019-04-23 18:51:23 -0500533 else:
Alex6b633ec2019-06-06 19:44:34 -0500534 _proto = "-"
Alexe0c5b9e2019-04-23 18:51:23 -0500535
Alex6b633ec2019-06-06 19:44:34 -0500536 if "type" in _r:
537 _if_name_suffix += _r["type"]
538 if "use_interfaces" in _r:
539 _if_name_suffix += "->" + ",".join(_r["use_interfaces"])
540
541 if _if_name_suffix:
542 _if_name_suffix = "({})".format(_if_name_suffix)
543
Alex6b633ec2019-06-06 19:44:34 -0500544 # get gate and routes if proto is static
545 if _proto == 'static':
546 # get the gateway for current net
Alex205546c2020-12-30 19:22:30 -0600547 _routes = self.master.nodes[hostname]['routes']
Alex6b633ec2019-06-06 19:44:34 -0500548 _route = _routes[_net] if _net in _routes else None
Alex6b633ec2019-06-06 19:44:34 -0500549 # get the default gateway
550 if 'default' in _routes:
551 _d_gate = ipaddress.IPv4Address(
552 _routes['default']['gateway']
553 )
554 else:
555 _d_gate = None
Alexb3dc8592019-06-11 13:20:36 -0500556 _d_gate_str = str(_d_gate) if _d_gate else "No default!"
557 # match route with default
558 if not _route:
559 _gate = "?"
560 else:
561 _gate = _route['gateway'] if _route['gateway'] else "-"
Alex6b633ec2019-06-06 19:44:34 -0500562 else:
563 # in case of manual and dhcp, no check possible
564 _gate = "-"
565 _d_gate = "-"
Alex4067f002019-06-11 10:47:16 -0500566 _d_gate_str = "-"
Alex6b633ec2019-06-06 19:44:34 -0500567 # iterate through interfaces
Alexe0c5b9e2019-04-23 18:51:23 -0500568 _a = _runtime[network][hostname]
569 for _host in _a:
570 for _if in _host['ifs']:
Alexe0c5b9e2019-04-23 18:51:23 -0500571 _ip_str = str(_if.exploded)
Alexab232e42019-06-06 19:44:34 -0500572 _gate_error = ""
573 _up_error = ""
574 _mtu_error = ""
Alexe0c5b9e2019-04-23 18:51:23 -0500575
Alexb3dc8592019-06-11 13:20:36 -0500576 # Match gateway
Alexab232e42019-06-06 19:44:34 -0500577 if _proto == 'static':
Alexb3dc8592019-06-11 13:20:36 -0500578 # default reclass gate
Alex6b633ec2019-06-06 19:44:34 -0500579 _r_gate = "-"
580 if "gateway" in _r:
581 _r_gate = _r["gateway"]
Alexb3dc8592019-06-11 13:20:36 -0500582
Alexab232e42019-06-06 19:44:34 -0500583 # if values not match, put *
Alexb3dc8592019-06-11 13:20:36 -0500584 if _gate != _r_gate and _d_gate_str != _r_gate:
585 # if values not match, check if default match
Alex3b8e5432019-06-11 15:21:59 -0500586 self.errors.add_error(
587 self.errors.NET_UNEXPECTED_GATEWAY,
588 host=hostname,
589 if_name=_if_name,
590 ip=_ip_str,
591 gateway=_gate
592 )
Alex836fac82019-08-22 13:36:16 -0500593 _notes.append(
594 self.errors.get_error_type_text(
595 self.errors.NET_UNEXPECTED_GATEWAY
596 )
597 )
Alexab232e42019-06-06 19:44:34 -0500598 _gate_error = "*"
Alexe0c5b9e2019-04-23 18:51:23 -0500599
600 # IF status in reclass
Alex6b633ec2019-06-06 19:44:34 -0500601 _e = "enabled"
Alexab232e42019-06-06 19:44:34 -0500602 if _e not in _r:
Alex3b8e5432019-06-11 15:21:59 -0500603 self.errors.add_error(
604 self.errors.NET_NO_RC_IF_STATUS,
605 host=hostname,
606 if_name=_if_name
607 )
Alex836fac82019-08-22 13:36:16 -0500608 _notes.append(
609 self.errors.get_error_type_text(
610 self.errors.NET_NO_RC_IF_STATUS
611 )
612 )
Alexab232e42019-06-06 19:44:34 -0500613 _up_error = "*"
Alexe0c5b9e2019-04-23 18:51:23 -0500614
Alexe0c5b9e2019-04-23 18:51:23 -0500615 _rc_mtu = _r['mtu'] if 'mtu' in _r else None
Alexab232e42019-06-06 19:44:34 -0500616 _rc_mtu_s = ""
Alexe0c5b9e2019-04-23 18:51:23 -0500617 # check if this is a VIP address
618 # no checks needed if yes.
619 if _host['vip'] != _ip_str:
620 if _rc_mtu:
Alex3b8e5432019-06-11 15:21:59 -0500621 _rc_mtu_s = str(_rc_mtu)
Alexe0c5b9e2019-04-23 18:51:23 -0500622 # if there is an MTU value, match it
623 if _host['mtu'] != _rc_mtu_s:
624 self.errors.add_error(
625 self.errors.NET_MTU_MISMATCH,
626 host=hostname,
Alex6b633ec2019-06-06 19:44:34 -0500627 if_name=_if_name,
Alexe0c5b9e2019-04-23 18:51:23 -0500628 if_cidr=_ip_str,
629 reclass_mtu=_rc_mtu,
630 runtime_mtu=_host['mtu']
631 )
Alex836fac82019-08-22 13:36:16 -0500632 _notes.append(
633 self.errors.get_error_type_text(
634 self.errors.NET_MTU_MISMATCH
635 )
636 )
Alexb3dc8592019-06-11 13:20:36 -0500637 _rc_mtu_s = "/" + _rc_mtu_s
Alexab232e42019-06-06 19:44:34 -0500638 _mtu_error = "*"
639 else:
640 # empty the matched value
641 _rc_mtu_s = ""
Alex3b8e5432019-06-11 15:21:59 -0500642 elif _host['mtu'] != '1500' and \
643 _proto not in ["-", "dhcp"]:
Alexe0c5b9e2019-04-23 18:51:23 -0500644 # there is no MTU value in reclass
645 # and runtime value is not default
646 self.errors.add_error(
647 self.errors.NET_MTU_EMPTY,
648 host=hostname,
Alex6b633ec2019-06-06 19:44:34 -0500649 if_name=_if_name,
Alexe0c5b9e2019-04-23 18:51:23 -0500650 if_cidr=_ip_str,
651 if_mtu=_host['mtu']
652 )
Alex836fac82019-08-22 13:36:16 -0500653 _notes.append(
654 self.errors.get_error_type_text(
655 self.errors.NET_MTU_EMPTY
656 )
657 )
Alexab232e42019-06-06 19:44:34 -0500658 _mtu_error = "*"
Alexe0c5b9e2019-04-23 18:51:23 -0500659 else:
660 # this is a VIP
Alex6b633ec2019-06-06 19:44:34 -0500661 _if_name = " "*7
Alex6b633ec2019-06-06 19:44:34 -0500662 _if_name_suffix = ""
Alexe0c5b9e2019-04-23 18:51:23 -0500663 _ip_str += " VIP"
Alex836fac82019-08-22 13:36:16 -0500664 # Save all data
665 _values = {
666 "interface": _if_name,
667 "interface_error": _if_rc,
668 "interface_note": _if_name_suffix,
Alex1839bbf2019-08-22 17:17:21 -0500669 "interface_map": "\n".join(_host['lines']),
670 "interface_matrix": _host['matrix'],
Alex836fac82019-08-22 13:36:16 -0500671 "ip_address": _ip_str,
672 "address_type": _proto,
673 "rt_mtu": _host['mtu'],
674 "rc_mtu": _rc_mtu_s,
675 "mtu_error": _mtu_error,
676 "status": _host['state'],
677 "status_error": _up_error,
678 "subnet_gateway": _gate,
679 "subnet_gateway_error": _gate_error,
680 "default_gateway": _d_gate_str,
681 "raw_data": _raw,
682 "error_note": " and ".join(_notes)
683 }
684 if node in _map[_net]:
685 # add if to host
686 _map[_net][node].append(_values)
687 else:
688 _map[_net][node] = [_values]
689 _notes = []
690
691 # save map
692 self.map = _map
Alex836fac82019-08-22 13:36:16 -0500693 return
694
695 def print_map(self):
696 """
697 Create text report for CLI
698
699 :return: none
700 """
701 logger_cli.info("# Networks")
702 logger_cli.info(
703 " {0:8} {1:25} {2:25} {3:6} {4:10} {5:10} {6}/{7}".format(
704 "Host",
705 "IF",
706 "IP",
707 "Proto",
708 "MTU",
709 "State",
710 "Gate",
711 "Def.Gate"
712 )
713 )
714 for network in self.map.keys():
715 logger_cli.info("-> {}".format(network))
716 for hostname in self.map[network].keys():
717 node = hostname.split('.')[0]
718 _n = self.map[network][hostname]
719 for _i in _n:
720 # Host IF IP Proto MTU State Gate Def.Gate
721 _text = "{:7} {:17} {:25} {:6} {:10} " \
722 "{:10} {} / {}".format(
723 _i['interface'] + _i['interface_error'],
724 _i['interface_note'],
725 _i['ip_address'],
726 _i['address_type'],
727 _i['rt_mtu'] + _i['rc_mtu'] + _i['mtu_error'],
728 _i['status'] + _i['status_error'],
729 _i['subnet_gateway'] +
730 _i['subnet_gateway_error'],
731 _i['default_gateway']
Alexe0c5b9e2019-04-23 18:51:23 -0500732 )
Alexe0c5b9e2019-04-23 18:51:23 -0500733 logger_cli.info(
Alex836fac82019-08-22 13:36:16 -0500734 " {0:8} {1}".format(
735 node,
736 _text
737 )
Alexe0c5b9e2019-04-23 18:51:23 -0500738 )
Alex836fac82019-08-22 13:36:16 -0500739
740 # logger_cli.info("\n# Other networks")
741 # _other = [n for n in _runtime if n not in _reclass]
742 # for network in _other:
743 # logger_cli.info("-> {}".format(str(network)))
744 # names = sorted(_runtime[network].keys())
745
746 # for hostname in names:
747 # for _n in _runtime[network][hostname]:
748 # _ifs = [str(ifs.ip) for ifs in _n['ifs']]
749 # _text = "{:25} {:25} {:6} {:10} {}".format(
750 # _n['name'],
751 # ", ".join(_ifs),
752 # "-",
753 # _n['mtu'],
754 # _n['state']
755 # )
756 # logger_cli.info(
757 # " {0:8} {1}".format(hostname.split('.')[0], _text)
758 # )
759 # logger_cli.info("\n")
Alex1f90e7b2021-09-03 15:31:28 -0500760 return
Alex205546c2020-12-30 19:22:30 -0600761
762
763class KubeNetworkMapper(NetworkMapper):
764 def __init__(
765 self,
766 config,
767 errors_class=None,
768 skip_list=None,
769 skip_list_file=None
770 ):
771 self.master = KubeNodes(config)
Alex7b0ee9a2021-09-21 17:16:17 -0500772 self.daemonset = None
Alex205546c2020-12-30 19:22:30 -0600773 super(KubeNetworkMapper, self).__init__(
774 config,
775 errors_class=errors_class,
776 skip_list=skip_list,
777 skip_list_file=skip_list_file
778 )
Alex1f90e7b2021-09-03 15:31:28 -0500779
Alex7b0ee9a2021-09-21 17:16:17 -0500780 def get_daemonset(self):
781 if not self.daemonset:
782 _d = self.master.prepare_daemonset("daemonset_template.yaml")
783
784 # wait for daemonset, normally less than 60 sec for all
785 # but still, let us give it 10 second per pod
786 _timeout = self.master.nodes.__len__() * 10
787 if not self.master.wait_for_daemonset(_d, timeout=_timeout):
788 raise KubeException("Daemonset deployment fail")
789 self.daemonset = _d
790 return self.daemonset
791
Alex1f90e7b2021-09-03 15:31:28 -0500792 def get_script_output(self, script, args=None):
793 """
794 Get runtime network by creating DaemonSet with Host network parameter
795 """
796 # prepare daemonset
797 logger_cli.info("-> Preparing daemonset to get node info")
Alex7b0ee9a2021-09-21 17:16:17 -0500798 _daemonset = self.get_daemonset()
Alex1f90e7b2021-09-03 15:31:28 -0500799 logger_cli.info("-> Running script on daemonset")
800 # exec script on all pods in daemonset
Alexb78191f2021-11-02 16:35:46 -0500801 _result = self.master.execute_cmd_on_daemon_set(
Alex1f90e7b2021-09-03 15:31:28 -0500802 _daemonset,
803 script,
Alexb78191f2021-11-02 16:35:46 -0500804 args=args,
805 is_script=True
Alex1f90e7b2021-09-03 15:31:28 -0500806 )
807
808 # delete daemonset
Alex7b0ee9a2021-09-21 17:16:17 -0500809 # TODO: handle daemonset delete properly
810 # self.master.delete_daemonset(_daemonset)
Alex1f90e7b2021-09-03 15:31:28 -0500811
812 return _result
813
814 def map_networks(self):
Alex3cdb1bd2021-09-10 15:51:11 -0500815 logger_cli.info("-> Mapping runtime networks")
Alex1f90e7b2021-09-03 15:31:28 -0500816 self.map_network(self.RUNTIME)
817
818 def map_network(self, source):
Alex7b0ee9a2021-09-21 17:16:17 -0500819 # if network type is mapped - just return it
820 if source in self.networks:
821 return self.networks[source]
Alex1f90e7b2021-09-03 15:31:28 -0500822 # maps target network using given source
823 _networks = None
Alex1f90e7b2021-09-03 15:31:28 -0500824 if source == self.RUNTIME:
825 logger_cli.info("# Mapping node runtime network data")
826 _r = self.get_script_output("ifs_data.py", args="json")
827 _networks = self._map_runtime_networks(_r)
828 else:
829 raise ConfigException(
830 "Network type not supported in 'Kube': '{}'".format(source)
831 )
832
833 self.networks[source] = _networks
834 return _networks
835
Alex3cdb1bd2021-09-10 15:51:11 -0500836 def create_map(self, skip_keywords=None):
Alex1f90e7b2021-09-03 15:31:28 -0500837 """Create all needed elements for map output
838
839 :return: none
840 """
Alex3cdb1bd2021-09-10 15:51:11 -0500841 # shortcut
Alex1f90e7b2021-09-03 15:31:28 -0500842 _runtime = self.networks[self.RUNTIME]
Alex3cdb1bd2021-09-10 15:51:11 -0500843 # networks to skip
844 _net_skip_list = []
Alex1f90e7b2021-09-03 15:31:28 -0500845 # main networks, target vars
846 _map = {}
847 # No matter of proto, at least one IP will be present for the network
848 # we interested in, since we are to make sure that L3 level
849 # is configured according to reclass model
850 for network in _runtime:
851 # shortcuts
852 _net = str(network)
853 _map[_net] = {}
854 # hostnames
855 names = sorted(_runtime[network].keys())
856 for hostname in names:
857 _notes = []
858 node = hostname.split('.')[0]
859 if not self.master.is_node_available(hostname, log=False):
860 logger_cli.info(
861 " {0:8} {1}".format(node, "node not available")
862 )
863 # add non-responsive node erorr
864 self.errors.add_error(
865 self.errors.NET_NODE_NON_RESPONSIVE,
866 host=hostname
867 )
868 _notes.append(
869 self.errors.get_error_type_text(
870 self.errors.NET_NODE_NON_RESPONSIVE
871 )
872 )
873 continue
874 # lookup interface name on node using network CIDR
875 _if_name = _runtime[network][hostname][0]["name"]
876 _raw = self.interfaces[hostname][_if_name]['runtime']
877 _if_name_suffix = ""
878 _a = _runtime[network][hostname]
879 for _host in _a:
880 for _if in _host['ifs']:
881 _ip_str = str(_if.exploded)
Alex3cdb1bd2021-09-10 15:51:11 -0500882 # Make sure we print VIP properly
883 if _host['vip'] == _ip_str:
884 _if_name = " "*7
885 _if_name_suffix = ""
886 _ip_str += " VIP"
Alex1f90e7b2021-09-03 15:31:28 -0500887
888 # Save all data
889 _values = {
890 "interface": _if_name,
891 "interface_note": _if_name_suffix,
892 "interface_map": "\n".join(_host['lines']),
893 "interface_matrix": _host['matrix'],
894 "ip_address": _ip_str,
895 "rt_mtu": _host['mtu'],
896 "status": _host['state'],
Alex3cdb1bd2021-09-10 15:51:11 -0500897 "type": _host['type'],
Alex1f90e7b2021-09-03 15:31:28 -0500898 "raw_data": _raw,
899 }
900 if node in _map[_net]:
901 # add if to host
902 _map[_net][node].append(_values)
903 else:
904 _map[_net][node] = [_values]
905 _notes = []
Alex3cdb1bd2021-09-10 15:51:11 -0500906 # process skips: if key substring found in interface name
907 # then skip the whole network.
908 if any([True for _w in skip_keywords if _w in _if_name]):
909 _net_skip_list.append(_net)
Alex1f90e7b2021-09-03 15:31:28 -0500910
Alex3cdb1bd2021-09-10 15:51:11 -0500911 # remove skipped networks from list
912 _net_skip_list = list(set(_net_skip_list))
913 for _net in _net_skip_list:
914 _map.pop(_net)
Alex1f90e7b2021-09-03 15:31:28 -0500915 # save map
916 self.map = _map
917 return
918
919 def print_map(self):
920 """
921 Create text report for CLI
922
923 :return: none
924 """
925 logger_cli.info("# Networks")
926 logger_cli.info(
Alex3cdb1bd2021-09-10 15:51:11 -0500927 " {0:47} {1:12} {2:25} {3:5} {4:4}".format(
Alex1f90e7b2021-09-03 15:31:28 -0500928 "Host",
929 "IF",
930 "IP",
931 "MTU",
932 "State"
933 )
934 )
935 for network in self.map.keys():
936 logger_cli.info("-> {}".format(network))
937 for hostname in self.map[network].keys():
938 node = hostname.split('.')[0]
939 _n = self.map[network][hostname]
940 for _i in _n:
941 # Host IF IP Proto MTU State Gate Def.Gate
Alex3cdb1bd2021-09-10 15:51:11 -0500942 _text = "{:10} {:2} {:25} {:5} {:4}".format(
Alex1f90e7b2021-09-03 15:31:28 -0500943 _i['interface'],
944 _i['interface_note'],
945 _i['ip_address'],
946 _i['rt_mtu'],
947 _i['status']
948 )
949 logger_cli.info(
Alex3cdb1bd2021-09-10 15:51:11 -0500950 " {0:47} {1}".format(
Alex1f90e7b2021-09-03 15:31:28 -0500951 node,
952 _text
953 )
954 )
955 return