blob: e53f75f22ea48f3b26090cd4d4a4b5bed8e50eab [file] [log] [blame]
Alex0989ecf2022-03-29 13:43:21 -05001# Author: Alex Savatieiev (osavatieiev@mirantis.com; a.savex@gmail.com)
2# Copyright 2019-2022 Mirantis, Inc.
Alexe0c5b9e2019-04-23 18:51:23 -05003import ipaddress
4import json
5
Alex92e07ce2019-05-31 16:00:03 -05006from cfg_checker.common import logger_cli
Alexe0c5b9e2019-04-23 18:51:23 -05007from cfg_checker.helpers.console_utils import Progress
Alexe0c5b9e2019-04-23 18:51:23 -05008
9
Alexe0c5b9e2019-04-23 18:51:23 -050010# This is independent class with a salt.nodes input
11class NetworkPinger(object):
Alexe9908f72020-05-19 16:04:53 -050012 def __init__(
13 self,
Alexaae58042020-12-31 11:48:28 -060014 mapper,
Alexe9908f72020-05-19 16:04:53 -050015 detailed=False,
Alexe9908f72020-05-19 16:04:53 -050016 skip_list=None,
17 skip_list_file=None
18 ):
Alex9a4ad212020-10-01 18:04:25 -050019 logger_cli.info("# Initializing Pinger")
Alexaae58042020-12-31 11:48:28 -060020 self.mapper = mapper
Alexe0c5b9e2019-04-23 18:51:23 -050021 # only data
Alexe0c5b9e2019-04-23 18:51:23 -050022 self.detailed_summary = detailed
Alex7b0ee9a2021-09-21 17:16:17 -050023 self.results = {}
Alexe0c5b9e2019-04-23 18:51:23 -050024
25 def _collect_node_addresses(self, target_net):
26 # use reclass model and standard methods
27 # to create list of nodes with target network
Alex7b0ee9a2021-09-21 17:16:17 -050028 _networks = self.mapper.map_network(self.mapper.RUNTIME)
29 if target_net in _networks:
30 return _networks[target_net]
Alexe0c5b9e2019-04-23 18:51:23 -050031 else:
32 logger_cli.info(
Alex7b0ee9a2021-09-21 17:16:17 -050033 "# Target network of {} not found after mapping".format(
Alexe0c5b9e2019-04-23 18:51:23 -050034 target_net.exploded
35 )
36 )
37 return None
38
Alex7b0ee9a2021-09-21 17:16:17 -050039 def _get_packets_data(self, network_cidr_str, mtu):
Alexe0c5b9e2019-04-23 18:51:23 -050040 # Conduct actual ping using network CIDR
Alex7b0ee9a2021-09-21 17:16:17 -050041 logger_cli.debug("... collecting node pairs")
Alex3bc95f62020-03-05 17:00:04 -060042 _fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
Alexe0c5b9e2019-04-23 18:51:23 -050043 _net = _fake_if.network
44 # collect nodes and ips from reclass
45 nodes = self._collect_node_addresses(_net)
46 # build list of packets to be sent
47 # source -> target
48 _count = 0
49 _packets = {}
50 _nodes = sorted(nodes.keys())
51 _nodes_total = len(_nodes)
52 logger_cli.info("-> {} nodes found within subnet of '{}'".format(
53 _nodes_total,
54 network_cidr_str
55 ))
56 while len(_nodes) > 0:
57 src_host = _nodes.pop()
58 src_data = nodes[src_host]
59 src_if_name = src_data[0]['name']
60 src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
61 _packets[src_host] = {
62 "ip": src_ips[0],
63 "if_name": src_if_name,
64 "targets": {}
65 }
66
Alex3bc95f62020-03-05 17:00:04 -060067 for tgt_host, tgt_data in nodes.items():
Alexe9547d82019-06-03 15:22:50 -050068 _t = _packets[src_host]["targets"]
Alexe0c5b9e2019-04-23 18:51:23 -050069 for tgt_if in tgt_data:
70 tgt_if_name = tgt_if['name']
71 _ip_index = 0
72 for tgt_ip in tgt_if['ifs']:
73 _ip = str(tgt_ip.ip)
74 if _ip not in src_ips:
Alexe9547d82019-06-03 15:22:50 -050075 if tgt_host not in _t:
76 _t[tgt_host] = []
Alex7b0ee9a2021-09-21 17:16:17 -050077 # Handling mtu and packet size
78 if mtu == 0:
79 # Detect MTU
80 _mtu = int(tgt_if['mtu'])
81 else:
82 _mtu = mtu
83 _packet_size = int(_mtu)-20-8 if _mtu != 0 else 0
Alexe0c5b9e2019-04-23 18:51:23 -050084 _tgt = {
85 "ip": _ip,
86 "tgt_host": tgt_host,
87 "ip_index": _ip_index,
88 "if_name": tgt_if_name,
Alex7b0ee9a2021-09-21 17:16:17 -050089 "mtu": _mtu,
90 "size": _packet_size
Alexe0c5b9e2019-04-23 18:51:23 -050091 }
Alexe9547d82019-06-03 15:22:50 -050092 _t[tgt_host].append(
Alexe0c5b9e2019-04-23 18:51:23 -050093 _tgt
94 )
95 _count += 1
96 _ip_index += 1
97 else:
98 pass
99 logger_cli.info("-> {} packets to send".format(_count))
100
Alex92e07ce2019-05-31 16:00:03 -0500101 if not _count:
102 logger_cli.warning(
103 "\n# WARNING: No packets to send for '{}', "
104 "check network configuration\n".format(network_cidr_str)
105 )
106
Alex7b0ee9a2021-09-21 17:16:17 -0500107 return None, _packets, _nodes_total
108 else:
109 return _count, _packets, _nodes_total
110
111 def _process_result(self, src, src_data, result):
112 # Parse output
113 try:
114 _result = json.loads(result)
115 except (ValueError, TypeError):
116 self.mapper.errors.add_error(
117 self.mapper.errors.NET_NODE_NON_RESPONSIVE,
118 node=src,
119 response=result
120 )
121
122 _msg = "# ERROR: Unexpected return for '{}': '{}'\n".format(
123 src,
124 result
125 )
126
127 return False, _msg
128
129 # Handle return codes
130 for tgt_node, _tgt_ips in _result.items():
131 for _params in _tgt_ips:
132 _body = "{}({}) --{}--> {}({}@{})\n".format(
133 src,
134 src_data["if_name"],
135 _params["returncode"],
136 tgt_node,
137 _params["if_name"],
138 _params["ip"]
139 )
140 _stdout = ""
141 _stderr = ""
142 if len(_params["stdout"]) > 0:
143 _stdout = "stdout:\n{}\n".format(_params["stdout"])
144 if len(_params["stderr"]) > 0:
145 _stderr = "stderr:\n{}\n".format(_params["stderr"])
146
147 if not _params["returncode"]:
148 # 0
149 self.mapper.errors.add_error(
150 self.mapper.errors.NET_PING_SUCCESS,
151 ping_path=_body,
152 stdout=_stdout,
153 stderr=_stderr
154 )
155 elif _params["returncode"] == 68:
156 # 68 is a 'can't resove host error'
157 self.mapper.errors.add_error(
158 self.mapper.errors.NET_PING_NOT_RESOLVED,
159 ping_path=_body,
160 stdout=_stdout,
161 stderr=_stderr
162 )
163 elif _params["returncode"] > 1:
164 # >1 is when no actial (any) response
165 self.mapper.errors.add_error(
166 self.mapper.errors.NET_PING_ERROR,
167 ping_path=_body,
168 stdout=_stdout,
169 stderr=_stderr
170 )
171 else:
172 # 1 is for timeouts amd/or packet lost
173 self.mapper.errors.add_error(
174 self.mapper.errors.NET_PING_TIMEOUT,
175 ping_path=_body,
176 stdout=_stdout,
177 stderr=_stderr
178 )
179 return True, _result
180
181 def print_summary(self, cidr, data):
182 # Print summary of ping activity in node-node perspective
183 logger_cli.info("\n-> {}, {} nodes".format(cidr, len(data)))
184 # iterate nodes
185 for _n, _d in data.items():
186 # targets
187 _total = len(_d['targets'])
188 _fail = []
189 _pass = []
190 _mtus = []
191 for _f, _t in _d['targets'].items():
192 # filter data
193 _fail += [[_f, _l] for _l in _t if _l['returncode']]
194 _pass += [[_f, _l] for _l in _t if not _l['returncode']]
195 _mtus += [str(_l['mtu']) for _l in _t]
196 # summary of passed
197 # source node
198 logger_cli.info(
199 " PASS: {}/{} nodes from {} ({}:{}) with MTU {}".format(
200 len(_pass),
201 _total,
202 _n,
203 _d['if_name'],
204 _d['ip'],
205 ",".join(list(set(_mtus)))
206 )
207 )
208 # print fails
209 for node, _dict in _fail:
210 logger_cli.info(
211 "\tFAIL: {} ({}:{}) with {}/{}".format(
212 node,
213 _dict['if_name'],
214 _dict['ip'],
215 _dict['size'],
216 _dict['mtu']
217 )
218 )
219
220 # logger_cli.info(self.mapper.errors.get_summary(print_zeros=False))
221 return
222
223 def print_details(self, cidr, data):
224 def _print_stds(stdout, stderr):
225 logger_cli.debug(" stdout:\n")
226 for line in stdout.splitlines():
227 logger_cli.debug(" {}".format(line))
228 if not stderr:
229 logger_cli.debug(" stderr: <empty>")
230 else:
231 logger_cli.debug(" stderr:\n")
232 for line in stderr.splitlines():
233 logger_cli.debug(" {}".format(line))
234
235 logger_cli.info("\n---> {}, {} nodes".format(cidr, len(data)))
236 # iterate nodes
237 for _n, _d in data.items():
238 # targets
239 _fail = []
240 _pass = []
241 for _f, _t in _d['targets'].items():
242 # filter data
243 _fail += [[_f, _l] for _l in _t if _l['returncode']]
244 _pass += [[_f, _l] for _l in _t if not _l['returncode']]
245 # summary of passed
246 # source node
247 logger_cli.info(
248 "======= from {} ({}:{})".format(
249 _n,
250 _d['if_name'],
251 _d['ip']
252 )
253 )
254 for node, _dict in _pass:
255 logger_cli.info(
256 " + PASS: to {} ({}:{}) with {}/{}".format(
257 node,
258 _dict['if_name'],
259 _dict['ip'],
260 _dict['size'],
261 _dict['mtu']
262 )
263 )
264 _print_stds(_dict['stdout'], _dict['stderr'])
265 # print fails
266 for node, _dict in _fail:
267 logger_cli.info(
268 " - FAIL: to {} ({}:{}) with {}/{}".format(
269 node,
270 _dict['if_name'],
271 _dict['ip'],
272 _dict['size'],
273 _dict['mtu']
274 )
275 )
276 _print_stds(_dict['stdout'], _dict['stderr'])
277
278 # Detailed errors
279 # logger_cli.info(
280 # "\n{}\n".format(
281 # self.mapper.errors.get_errors()
282 # )
283 # )
284 return
285
286
287class SaltNetworkPinger(NetworkPinger):
288 def __init__(
289 self,
290 mapper,
291 detailed=False,
292 skip_list=None,
293 skip_list_file=None
294 ):
295 super(SaltNetworkPinger, self).__init__(
296 mapper,
297 detailed=detailed,
298 skip_list=skip_list,
299 skip_list_file=skip_list_file
300 )
301
302 def ping_nodes(self, network_cidr_str, mtu):
303 # get packets
304 _count, _packets, _nodes_total = self._get_packets_data(
305 network_cidr_str,
306 mtu
307 )
308 if not _count:
309 return None
Alex92e07ce2019-05-31 16:00:03 -0500310
Alexe0c5b9e2019-04-23 18:51:23 -0500311 # do ping of packets
Alex7b0ee9a2021-09-21 17:16:17 -0500312 logger_cli.info(
313 "-> Pinging nodes in {}".format(network_cidr_str))
Alexaae58042020-12-31 11:48:28 -0600314 self.mapper.master.prepare_script_on_active_nodes("ping.py")
Alexe0c5b9e2019-04-23 18:51:23 -0500315 _progress = Progress(_count)
316 _progress_index = 0
317 _node_index = 0
Alex3bc95f62020-03-05 17:00:04 -0600318 for src, src_data in _packets.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500319 _targets = src_data["targets"]
320 _node_index += 1
321 # create 'targets.json' on source host
Alexaae58042020-12-31 11:48:28 -0600322 _path = self.mapper.master.prepare_json_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500323 src,
324 _targets,
325 "targets.json"
326 )
327 # execute ping.py
Alexaae58042020-12-31 11:48:28 -0600328 _results = self.mapper.master.execute_script_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500329 src,
330 "ping.py",
331 args=[_path]
332 )
333 _progress_index += len(_targets)
334 # print progress
335 _progress.write_progress(
336 _progress_index,
337 note='/ {}/{} nodes / current {}'.format(
338 _node_index,
339 _nodes_total,
340 src
341 )
342 )
Alexe0c5b9e2019-04-23 18:51:23 -0500343 # Parse results back in place
Alex7b0ee9a2021-09-21 17:16:17 -0500344 _ret_code, _data = self._process_result(
345 src, src_data,
346 _results[src]
347 )
348 if not _ret_code:
349 _progress.clearline()
350 logger_cli.error(_data)
351 continue
352 else:
353 src_data["targets"] = _data
Alexe0c5b9e2019-04-23 18:51:23 -0500354
Alexd9fd85e2019-05-16 16:58:24 -0500355 _progress.end()
Alexe0c5b9e2019-04-23 18:51:23 -0500356
Alex7b0ee9a2021-09-21 17:16:17 -0500357 return _packets
Alexe0c5b9e2019-04-23 18:51:23 -0500358
Alex92e07ce2019-05-31 16:00:03 -0500359
Alex7b0ee9a2021-09-21 17:16:17 -0500360class KubeNetworkPinger(NetworkPinger):
361 def __init__(
362 self,
363 mapper,
364 detailed=False,
365 skip_list=None,
366 skip_list_file=None
367 ):
368 super(KubeNetworkPinger, self).__init__(
369 mapper,
370 detailed=detailed,
371 skip_list=skip_list,
372 skip_list_file=skip_list_file
Alexe0c5b9e2019-04-23 18:51:23 -0500373 )
Alex7b0ee9a2021-09-21 17:16:17 -0500374
375 def ping_nodes(self, network_cidr_str, mtu):
376 # get packets
377 _count, _packets, _nodes_total = self._get_packets_data(
378 network_cidr_str,
379 mtu
380 )
381 if not _count:
382 return None
383
384 # do ping of packets
385 logger_cli.info(
386 "-> Pinging nodes in {}".format(network_cidr_str))
387 _progress = Progress(_count)
388 _progress_index = 0
389 _node_index = 0
390 for src, src_data in _packets.items():
391 _targets = src_data["targets"]
392 _node_index += 1
393 # create 'targets.json' on source host
394 _ds = self.mapper.get_daemonset()
395 _pname = self.mapper.master.get_pod_name_in_daemonset_by_node(
396 src,
397 _ds
398 )
399 _path = self.mapper.master.prepare_json_in_pod(
400 _pname,
401 _ds.metadata.namespace,
402 _targets,
403 "targets.json"
404 )
405 # execute ping.py
Alexdcb792f2021-10-04 14:24:21 -0500406 _result = self.mapper.master.exec_script_on_target_pod(
Alex7b0ee9a2021-09-21 17:16:17 -0500407 _pname,
408 "ping.py",
409 args=[_path]
410 )
411 _progress_index += len(_targets)
412 # print progress
413 _progress.write_progress(
414 _progress_index,
415 note='/ {}/{} nodes / current {}'.format(
416 _node_index,
417 _nodes_total,
418 src
419 )
420 )
421 # Parse results back in place
422 _ret_code, _data = self._process_result(
423 src, src_data,
424 _result
425 )
426 if not _ret_code:
427 _progress.clearline()
428 logger_cli.error(_data)
429 continue
430 else:
431 src_data["targets"] = _data
432
433 _progress.end()
434
435 return _packets