blob: 30881da687d25fb5aba8b98e5eaaca1892b7c2cb [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 mtu=None,
14 detailed=False,
Alexe9908f72020-05-19 16:04:53 -050015 skip_list=None,
16 skip_list_file=None
17 ):
Alex9a4ad212020-10-01 18:04:25 -050018 logger_cli.info("# Initializing Pinger")
Alexaae58042020-12-31 11:48:28 -060019 self.mapper = mapper
Alexe0c5b9e2019-04-23 18:51:23 -050020 # default MTU value
21 self.target_mtu = mtu if mtu else 64
22 # only data
23 self.packet_size = int(self.target_mtu) - 20 - 8
24 self.detailed_summary = detailed
25
26 def _collect_node_addresses(self, target_net):
27 # use reclass model and standard methods
28 # to create list of nodes with target network
Alexaae58042020-12-31 11:48:28 -060029 _reclass = self.mapper.map_network(self.mapper.RUNTIME)
Alexe0c5b9e2019-04-23 18:51:23 -050030 if target_net in _reclass:
31 return _reclass[target_net]
32 else:
33 logger_cli.info(
34 "# Target network of {} not found in reclass".format(
35 target_net.exploded
36 )
37 )
38 return None
39
40 def ping_nodes(self, network_cidr_str):
41 # Conduct actual ping using network CIDR
42 logger_cli.info("# Collecting node pairs")
Alex3bc95f62020-03-05 17:00:04 -060043 _fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
Alexe0c5b9e2019-04-23 18:51:23 -050044 _net = _fake_if.network
45 # collect nodes and ips from reclass
46 nodes = self._collect_node_addresses(_net)
47 # build list of packets to be sent
48 # source -> target
49 _count = 0
50 _packets = {}
51 _nodes = sorted(nodes.keys())
52 _nodes_total = len(_nodes)
53 logger_cli.info("-> {} nodes found within subnet of '{}'".format(
54 _nodes_total,
55 network_cidr_str
56 ))
57 while len(_nodes) > 0:
58 src_host = _nodes.pop()
59 src_data = nodes[src_host]
60 src_if_name = src_data[0]['name']
61 src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
62 _packets[src_host] = {
63 "ip": src_ips[0],
64 "if_name": src_if_name,
65 "targets": {}
66 }
67
Alex3bc95f62020-03-05 17:00:04 -060068 for tgt_host, tgt_data in nodes.items():
Alexe9547d82019-06-03 15:22:50 -050069 _t = _packets[src_host]["targets"]
Alexe0c5b9e2019-04-23 18:51:23 -050070 for tgt_if in tgt_data:
71 tgt_if_name = tgt_if['name']
72 _ip_index = 0
73 for tgt_ip in tgt_if['ifs']:
74 _ip = str(tgt_ip.ip)
75 if _ip not in src_ips:
Alexe9547d82019-06-03 15:22:50 -050076 if tgt_host not in _t:
77 _t[tgt_host] = []
Alexe0c5b9e2019-04-23 18:51:23 -050078 _tgt = {
79 "ip": _ip,
80 "tgt_host": tgt_host,
81 "ip_index": _ip_index,
82 "if_name": tgt_if_name,
83 "mtu": self.target_mtu,
84 "size": self.packet_size
85 }
Alexe9547d82019-06-03 15:22:50 -050086 _t[tgt_host].append(
Alexe0c5b9e2019-04-23 18:51:23 -050087 _tgt
88 )
89 _count += 1
90 _ip_index += 1
91 else:
92 pass
93 logger_cli.info("-> {} packets to send".format(_count))
94
Alex92e07ce2019-05-31 16:00:03 -050095 if not _count:
96 logger_cli.warning(
97 "\n# WARNING: No packets to send for '{}', "
98 "check network configuration\n".format(network_cidr_str)
99 )
100
101 return -1
102
Alexe0c5b9e2019-04-23 18:51:23 -0500103 # do ping of packets
104 logger_cli.info("# Pinging nodes: MTU={}".format(self.target_mtu))
Alexaae58042020-12-31 11:48:28 -0600105 self.mapper.master.prepare_script_on_active_nodes("ping.py")
Alexe0c5b9e2019-04-23 18:51:23 -0500106 _progress = Progress(_count)
107 _progress_index = 0
108 _node_index = 0
Alex3bc95f62020-03-05 17:00:04 -0600109 for src, src_data in _packets.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500110 _targets = src_data["targets"]
111 _node_index += 1
112 # create 'targets.json' on source host
Alexaae58042020-12-31 11:48:28 -0600113 _path = self.mapper.master.prepare_json_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500114 src,
115 _targets,
116 "targets.json"
117 )
118 # execute ping.py
Alexaae58042020-12-31 11:48:28 -0600119 _results = self.mapper.master.execute_script_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500120 src,
121 "ping.py",
122 args=[_path]
123 )
124 _progress_index += len(_targets)
125 # print progress
126 _progress.write_progress(
127 _progress_index,
128 note='/ {}/{} nodes / current {}'.format(
129 _node_index,
130 _nodes_total,
131 src
132 )
133 )
134 # Parse salt output
135 _result = _results[src]
Alexe9547d82019-06-03 15:22:50 -0500136 try:
137 _result = json.loads(_result)
Alexab232e42019-06-06 19:44:34 -0500138 except (ValueError, TypeError):
Alexe9547d82019-06-03 15:22:50 -0500139 _progress.clearline()
140 logger_cli.error(
Alexab232e42019-06-06 19:44:34 -0500141 "# ERROR: Unexpected salt return for '{}': '{}'\n".format(
142 src,
143 _result
144 )
145 )
Alexf0c628c2020-12-31 11:56:45 -0600146 self.mapper.errors.add_error(
147 self.mapper.errors.NET_NODE_NON_RESPONSIVE,
Alexab232e42019-06-06 19:44:34 -0500148 node=src,
149 response=_result
Alexe9547d82019-06-03 15:22:50 -0500150 )
151 continue
Alexe0c5b9e2019-04-23 18:51:23 -0500152 # Handle return codes
Alex3bc95f62020-03-05 17:00:04 -0600153 for tgt_node, _tgt_ips in _result.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500154 for _params in _tgt_ips:
155 _body = "{}({}) --{}--> {}({}@{})\n".format(
156 src,
157 src_data["if_name"],
158 _params["returncode"],
159 tgt_node,
160 _params["if_name"],
161 _params["ip"]
162 )
163 _stdout = ""
164 _stderr = ""
165 if len(_params["stdout"]) > 0:
166 _stdout = "stdout:\n{}\n".format(_params["stdout"])
167 if len(_params["stderr"]) > 0:
168 _stderr = "stderr:\n{}\n".format(_params["stderr"])
169
Alex92e07ce2019-05-31 16:00:03 -0500170 if not _params["returncode"]:
171 # 0
Alexf0c628c2020-12-31 11:56:45 -0600172 self.mapper.errors.add_error(
173 self.mapper.errors.NET_PING_SUCCESS,
Alex92e07ce2019-05-31 16:00:03 -0500174 ping_path=_body,
175 stdout=_stdout,
176 stderr=_stderr
177 )
178 elif _params["returncode"] == 68:
179 # 68 is a 'can't resove host error'
Alexf0c628c2020-12-31 11:56:45 -0600180 self.mapper.errors.add_error(
181 self.mapper.errors.NET_PING_NOT_RESOLVED,
Alex92e07ce2019-05-31 16:00:03 -0500182 ping_path=_body,
183 stdout=_stdout,
184 stderr=_stderr
185 )
186 elif _params["returncode"] > 1:
187 # >1 is when no actial (any) response
Alexf0c628c2020-12-31 11:56:45 -0600188 self.mapper.errors.add_error(
189 self.mapper.errors.NET_PING_ERROR,
Alex92e07ce2019-05-31 16:00:03 -0500190 ping_path=_body,
191 stdout=_stdout,
192 stderr=_stderr
193 )
Alexe0c5b9e2019-04-23 18:51:23 -0500194 else:
Alex92e07ce2019-05-31 16:00:03 -0500195 # 1 is for timeouts amd/or packet lost
Alexf0c628c2020-12-31 11:56:45 -0600196 self.mapper.errors.add_error(
197 self.mapper.errors.NET_PING_TIMEOUT,
Alex92e07ce2019-05-31 16:00:03 -0500198 ping_path=_body,
199 stdout=_stdout,
200 stderr=_stderr
201 )
Alexe0c5b9e2019-04-23 18:51:23 -0500202
203 # Parse results back in place
204 src_data["targets"] = _result
205
Alexd9fd85e2019-05-16 16:58:24 -0500206 _progress.end()
Alexe0c5b9e2019-04-23 18:51:23 -0500207
Alex92e07ce2019-05-31 16:00:03 -0500208 return 0
Alexe0c5b9e2019-04-23 18:51:23 -0500209
Alex92e07ce2019-05-31 16:00:03 -0500210 def print_summary(self):
Alexf0c628c2020-12-31 11:56:45 -0600211 logger_cli.info(self.mapper.errors.get_summary(print_zeros=False))
Alex92e07ce2019-05-31 16:00:03 -0500212
213 def print_details(self):
214 # Detailed errors
Alexe0c5b9e2019-04-23 18:51:23 -0500215 logger_cli.info(
Alex92e07ce2019-05-31 16:00:03 -0500216 "\n{}\n".format(
Alexf0c628c2020-12-31 11:56:45 -0600217 self.mapper.errors.get_errors()
Alexe0c5b9e2019-04-23 18:51:23 -0500218 )
219 )