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