blob: e53f75f22ea48f3b26090cd4d4a4b5bed8e50eab [file] [log] [blame]
# Author: Alex Savatieiev (osavatieiev@mirantis.com; a.savex@gmail.com)
# Copyright 2019-2022 Mirantis, Inc.
import ipaddress
import json
from cfg_checker.common import logger_cli
from cfg_checker.helpers.console_utils import Progress
# This is independent class with a salt.nodes input
class NetworkPinger(object):
def __init__(
self,
mapper,
detailed=False,
skip_list=None,
skip_list_file=None
):
logger_cli.info("# Initializing Pinger")
self.mapper = mapper
# only data
self.detailed_summary = detailed
self.results = {}
def _collect_node_addresses(self, target_net):
# use reclass model and standard methods
# to create list of nodes with target network
_networks = self.mapper.map_network(self.mapper.RUNTIME)
if target_net in _networks:
return _networks[target_net]
else:
logger_cli.info(
"# Target network of {} not found after mapping".format(
target_net.exploded
)
)
return None
def _get_packets_data(self, network_cidr_str, mtu):
# Conduct actual ping using network CIDR
logger_cli.debug("... collecting node pairs")
_fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
_net = _fake_if.network
# collect nodes and ips from reclass
nodes = self._collect_node_addresses(_net)
# build list of packets to be sent
# source -> target
_count = 0
_packets = {}
_nodes = sorted(nodes.keys())
_nodes_total = len(_nodes)
logger_cli.info("-> {} nodes found within subnet of '{}'".format(
_nodes_total,
network_cidr_str
))
while len(_nodes) > 0:
src_host = _nodes.pop()
src_data = nodes[src_host]
src_if_name = src_data[0]['name']
src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
_packets[src_host] = {
"ip": src_ips[0],
"if_name": src_if_name,
"targets": {}
}
for tgt_host, tgt_data in nodes.items():
_t = _packets[src_host]["targets"]
for tgt_if in tgt_data:
tgt_if_name = tgt_if['name']
_ip_index = 0
for tgt_ip in tgt_if['ifs']:
_ip = str(tgt_ip.ip)
if _ip not in src_ips:
if tgt_host not in _t:
_t[tgt_host] = []
# Handling mtu and packet size
if mtu == 0:
# Detect MTU
_mtu = int(tgt_if['mtu'])
else:
_mtu = mtu
_packet_size = int(_mtu)-20-8 if _mtu != 0 else 0
_tgt = {
"ip": _ip,
"tgt_host": tgt_host,
"ip_index": _ip_index,
"if_name": tgt_if_name,
"mtu": _mtu,
"size": _packet_size
}
_t[tgt_host].append(
_tgt
)
_count += 1
_ip_index += 1
else:
pass
logger_cli.info("-> {} packets to send".format(_count))
if not _count:
logger_cli.warning(
"\n# WARNING: No packets to send for '{}', "
"check network configuration\n".format(network_cidr_str)
)
return None, _packets, _nodes_total
else:
return _count, _packets, _nodes_total
def _process_result(self, src, src_data, result):
# Parse output
try:
_result = json.loads(result)
except (ValueError, TypeError):
self.mapper.errors.add_error(
self.mapper.errors.NET_NODE_NON_RESPONSIVE,
node=src,
response=result
)
_msg = "# ERROR: Unexpected return for '{}': '{}'\n".format(
src,
result
)
return False, _msg
# Handle return codes
for tgt_node, _tgt_ips in _result.items():
for _params in _tgt_ips:
_body = "{}({}) --{}--> {}({}@{})\n".format(
src,
src_data["if_name"],
_params["returncode"],
tgt_node,
_params["if_name"],
_params["ip"]
)
_stdout = ""
_stderr = ""
if len(_params["stdout"]) > 0:
_stdout = "stdout:\n{}\n".format(_params["stdout"])
if len(_params["stderr"]) > 0:
_stderr = "stderr:\n{}\n".format(_params["stderr"])
if not _params["returncode"]:
# 0
self.mapper.errors.add_error(
self.mapper.errors.NET_PING_SUCCESS,
ping_path=_body,
stdout=_stdout,
stderr=_stderr
)
elif _params["returncode"] == 68:
# 68 is a 'can't resove host error'
self.mapper.errors.add_error(
self.mapper.errors.NET_PING_NOT_RESOLVED,
ping_path=_body,
stdout=_stdout,
stderr=_stderr
)
elif _params["returncode"] > 1:
# >1 is when no actial (any) response
self.mapper.errors.add_error(
self.mapper.errors.NET_PING_ERROR,
ping_path=_body,
stdout=_stdout,
stderr=_stderr
)
else:
# 1 is for timeouts amd/or packet lost
self.mapper.errors.add_error(
self.mapper.errors.NET_PING_TIMEOUT,
ping_path=_body,
stdout=_stdout,
stderr=_stderr
)
return True, _result
def print_summary(self, cidr, data):
# Print summary of ping activity in node-node perspective
logger_cli.info("\n-> {}, {} nodes".format(cidr, len(data)))
# iterate nodes
for _n, _d in data.items():
# targets
_total = len(_d['targets'])
_fail = []
_pass = []
_mtus = []
for _f, _t in _d['targets'].items():
# filter data
_fail += [[_f, _l] for _l in _t if _l['returncode']]
_pass += [[_f, _l] for _l in _t if not _l['returncode']]
_mtus += [str(_l['mtu']) for _l in _t]
# summary of passed
# source node
logger_cli.info(
" PASS: {}/{} nodes from {} ({}:{}) with MTU {}".format(
len(_pass),
_total,
_n,
_d['if_name'],
_d['ip'],
",".join(list(set(_mtus)))
)
)
# print fails
for node, _dict in _fail:
logger_cli.info(
"\tFAIL: {} ({}:{}) with {}/{}".format(
node,
_dict['if_name'],
_dict['ip'],
_dict['size'],
_dict['mtu']
)
)
# logger_cli.info(self.mapper.errors.get_summary(print_zeros=False))
return
def print_details(self, cidr, data):
def _print_stds(stdout, stderr):
logger_cli.debug(" stdout:\n")
for line in stdout.splitlines():
logger_cli.debug(" {}".format(line))
if not stderr:
logger_cli.debug(" stderr: <empty>")
else:
logger_cli.debug(" stderr:\n")
for line in stderr.splitlines():
logger_cli.debug(" {}".format(line))
logger_cli.info("\n---> {}, {} nodes".format(cidr, len(data)))
# iterate nodes
for _n, _d in data.items():
# targets
_fail = []
_pass = []
for _f, _t in _d['targets'].items():
# filter data
_fail += [[_f, _l] for _l in _t if _l['returncode']]
_pass += [[_f, _l] for _l in _t if not _l['returncode']]
# summary of passed
# source node
logger_cli.info(
"======= from {} ({}:{})".format(
_n,
_d['if_name'],
_d['ip']
)
)
for node, _dict in _pass:
logger_cli.info(
" + PASS: to {} ({}:{}) with {}/{}".format(
node,
_dict['if_name'],
_dict['ip'],
_dict['size'],
_dict['mtu']
)
)
_print_stds(_dict['stdout'], _dict['stderr'])
# print fails
for node, _dict in _fail:
logger_cli.info(
" - FAIL: to {} ({}:{}) with {}/{}".format(
node,
_dict['if_name'],
_dict['ip'],
_dict['size'],
_dict['mtu']
)
)
_print_stds(_dict['stdout'], _dict['stderr'])
# Detailed errors
# logger_cli.info(
# "\n{}\n".format(
# self.mapper.errors.get_errors()
# )
# )
return
class SaltNetworkPinger(NetworkPinger):
def __init__(
self,
mapper,
detailed=False,
skip_list=None,
skip_list_file=None
):
super(SaltNetworkPinger, self).__init__(
mapper,
detailed=detailed,
skip_list=skip_list,
skip_list_file=skip_list_file
)
def ping_nodes(self, network_cidr_str, mtu):
# get packets
_count, _packets, _nodes_total = self._get_packets_data(
network_cidr_str,
mtu
)
if not _count:
return None
# do ping of packets
logger_cli.info(
"-> Pinging nodes in {}".format(network_cidr_str))
self.mapper.master.prepare_script_on_active_nodes("ping.py")
_progress = Progress(_count)
_progress_index = 0
_node_index = 0
for src, src_data in _packets.items():
_targets = src_data["targets"]
_node_index += 1
# create 'targets.json' on source host
_path = self.mapper.master.prepare_json_on_node(
src,
_targets,
"targets.json"
)
# execute ping.py
_results = self.mapper.master.execute_script_on_node(
src,
"ping.py",
args=[_path]
)
_progress_index += len(_targets)
# print progress
_progress.write_progress(
_progress_index,
note='/ {}/{} nodes / current {}'.format(
_node_index,
_nodes_total,
src
)
)
# Parse results back in place
_ret_code, _data = self._process_result(
src, src_data,
_results[src]
)
if not _ret_code:
_progress.clearline()
logger_cli.error(_data)
continue
else:
src_data["targets"] = _data
_progress.end()
return _packets
class KubeNetworkPinger(NetworkPinger):
def __init__(
self,
mapper,
detailed=False,
skip_list=None,
skip_list_file=None
):
super(KubeNetworkPinger, self).__init__(
mapper,
detailed=detailed,
skip_list=skip_list,
skip_list_file=skip_list_file
)
def ping_nodes(self, network_cidr_str, mtu):
# get packets
_count, _packets, _nodes_total = self._get_packets_data(
network_cidr_str,
mtu
)
if not _count:
return None
# do ping of packets
logger_cli.info(
"-> Pinging nodes in {}".format(network_cidr_str))
_progress = Progress(_count)
_progress_index = 0
_node_index = 0
for src, src_data in _packets.items():
_targets = src_data["targets"]
_node_index += 1
# create 'targets.json' on source host
_ds = self.mapper.get_daemonset()
_pname = self.mapper.master.get_pod_name_in_daemonset_by_node(
src,
_ds
)
_path = self.mapper.master.prepare_json_in_pod(
_pname,
_ds.metadata.namespace,
_targets,
"targets.json"
)
# execute ping.py
_result = self.mapper.master.exec_script_on_target_pod(
_pname,
"ping.py",
args=[_path]
)
_progress_index += len(_targets)
# print progress
_progress.write_progress(
_progress_index,
note='/ {}/{} nodes / current {}'.format(
_node_index,
_nodes_total,
src
)
)
# Parse results back in place
_ret_code, _data = self._process_result(
src, src_data,
_result
)
if not _ret_code:
_progress.clearline()
logger_cli.error(_data)
continue
else:
src_data["targets"] = _data
_progress.end()
return _packets