blob: 075012cdd90479845ff13d3e23a59f13cea4ebb9 [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
Alex92e07ce2019-05-31 16:00:03 -05006from cfg_checker.modules.network.network_errors import NetworkErrors
Alexe0c5b9e2019-04-23 18:51:23 -05007
8
Alexe0c5b9e2019-04-23 18:51:23 -05009# This is independent class with a salt.nodes input
10class NetworkPinger(object):
Alexe9908f72020-05-19 16:04:53 -050011 def __init__(
12 self,
Alexaae58042020-12-31 11:48:28 -060013 mapper,
Alexe9908f72020-05-19 16:04:53 -050014 mtu=None,
15 detailed=False,
16 errors_class=None,
17 skip_list=None,
18 skip_list_file=None
19 ):
Alex9a4ad212020-10-01 18:04:25 -050020 logger_cli.info("# Initializing Pinger")
Alexaae58042020-12-31 11:48:28 -060021 self.mapper = mapper
Alexe0c5b9e2019-04-23 18:51:23 -050022 # default MTU value
23 self.target_mtu = mtu if mtu else 64
24 # only data
25 self.packet_size = int(self.target_mtu) - 20 - 8
26 self.detailed_summary = detailed
27
Alex92e07ce2019-05-31 16:00:03 -050028 if errors_class:
29 self.errors = errors_class
30 else:
31 logger_cli.debug("... init error logs folder")
32 self.errors = NetworkErrors()
33
Alexe0c5b9e2019-04-23 18:51:23 -050034 def _collect_node_addresses(self, target_net):
35 # use reclass model and standard methods
36 # to create list of nodes with target network
Alexaae58042020-12-31 11:48:28 -060037 _reclass = self.mapper.map_network(self.mapper.RUNTIME)
Alexe0c5b9e2019-04-23 18:51:23 -050038 if target_net in _reclass:
39 return _reclass[target_net]
40 else:
41 logger_cli.info(
42 "# Target network of {} not found in reclass".format(
43 target_net.exploded
44 )
45 )
46 return None
47
48 def ping_nodes(self, network_cidr_str):
49 # Conduct actual ping using network CIDR
50 logger_cli.info("# Collecting node pairs")
Alex3bc95f62020-03-05 17:00:04 -060051 _fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
Alexe0c5b9e2019-04-23 18:51:23 -050052 _net = _fake_if.network
53 # collect nodes and ips from reclass
54 nodes = self._collect_node_addresses(_net)
55 # build list of packets to be sent
56 # source -> target
57 _count = 0
58 _packets = {}
59 _nodes = sorted(nodes.keys())
60 _nodes_total = len(_nodes)
61 logger_cli.info("-> {} nodes found within subnet of '{}'".format(
62 _nodes_total,
63 network_cidr_str
64 ))
65 while len(_nodes) > 0:
66 src_host = _nodes.pop()
67 src_data = nodes[src_host]
68 src_if_name = src_data[0]['name']
69 src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
70 _packets[src_host] = {
71 "ip": src_ips[0],
72 "if_name": src_if_name,
73 "targets": {}
74 }
75
Alex3bc95f62020-03-05 17:00:04 -060076 for tgt_host, tgt_data in nodes.items():
Alexe9547d82019-06-03 15:22:50 -050077 _t = _packets[src_host]["targets"]
Alexe0c5b9e2019-04-23 18:51:23 -050078 for tgt_if in tgt_data:
79 tgt_if_name = tgt_if['name']
80 _ip_index = 0
81 for tgt_ip in tgt_if['ifs']:
82 _ip = str(tgt_ip.ip)
83 if _ip not in src_ips:
Alexe9547d82019-06-03 15:22:50 -050084 if tgt_host not in _t:
85 _t[tgt_host] = []
Alexe0c5b9e2019-04-23 18:51:23 -050086 _tgt = {
87 "ip": _ip,
88 "tgt_host": tgt_host,
89 "ip_index": _ip_index,
90 "if_name": tgt_if_name,
91 "mtu": self.target_mtu,
92 "size": self.packet_size
93 }
Alexe9547d82019-06-03 15:22:50 -050094 _t[tgt_host].append(
Alexe0c5b9e2019-04-23 18:51:23 -050095 _tgt
96 )
97 _count += 1
98 _ip_index += 1
99 else:
100 pass
101 logger_cli.info("-> {} packets to send".format(_count))
102
Alex92e07ce2019-05-31 16:00:03 -0500103 if not _count:
104 logger_cli.warning(
105 "\n# WARNING: No packets to send for '{}', "
106 "check network configuration\n".format(network_cidr_str)
107 )
108
109 return -1
110
Alexe0c5b9e2019-04-23 18:51:23 -0500111 # do ping of packets
112 logger_cli.info("# Pinging nodes: MTU={}".format(self.target_mtu))
Alexaae58042020-12-31 11:48:28 -0600113 self.mapper.master.prepare_script_on_active_nodes("ping.py")
Alexe0c5b9e2019-04-23 18:51:23 -0500114 _progress = Progress(_count)
115 _progress_index = 0
116 _node_index = 0
Alex3bc95f62020-03-05 17:00:04 -0600117 for src, src_data in _packets.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500118 _targets = src_data["targets"]
119 _node_index += 1
120 # create 'targets.json' on source host
Alexaae58042020-12-31 11:48:28 -0600121 _path = self.mapper.master.prepare_json_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500122 src,
123 _targets,
124 "targets.json"
125 )
126 # execute ping.py
Alexaae58042020-12-31 11:48:28 -0600127 _results = self.mapper.master.execute_script_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500128 src,
129 "ping.py",
130 args=[_path]
131 )
132 _progress_index += len(_targets)
133 # print progress
134 _progress.write_progress(
135 _progress_index,
136 note='/ {}/{} nodes / current {}'.format(
137 _node_index,
138 _nodes_total,
139 src
140 )
141 )
142 # Parse salt output
143 _result = _results[src]
Alexe9547d82019-06-03 15:22:50 -0500144 try:
145 _result = json.loads(_result)
Alexab232e42019-06-06 19:44:34 -0500146 except (ValueError, TypeError):
Alexe9547d82019-06-03 15:22:50 -0500147 _progress.clearline()
148 logger_cli.error(
Alexab232e42019-06-06 19:44:34 -0500149 "# ERROR: Unexpected salt return for '{}': '{}'\n".format(
150 src,
151 _result
152 )
153 )
154 self.errors.add_error(
155 self.errors.NET_NODE_NON_RESPONSIVE,
156 node=src,
157 response=_result
Alexe9547d82019-06-03 15:22:50 -0500158 )
159 continue
Alexe0c5b9e2019-04-23 18:51:23 -0500160 # Handle return codes
Alex3bc95f62020-03-05 17:00:04 -0600161 for tgt_node, _tgt_ips in _result.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500162 for _params in _tgt_ips:
163 _body = "{}({}) --{}--> {}({}@{})\n".format(
164 src,
165 src_data["if_name"],
166 _params["returncode"],
167 tgt_node,
168 _params["if_name"],
169 _params["ip"]
170 )
171 _stdout = ""
172 _stderr = ""
173 if len(_params["stdout"]) > 0:
174 _stdout = "stdout:\n{}\n".format(_params["stdout"])
175 if len(_params["stderr"]) > 0:
176 _stderr = "stderr:\n{}\n".format(_params["stderr"])
177
Alex92e07ce2019-05-31 16:00:03 -0500178 if not _params["returncode"]:
179 # 0
180 self.errors.add_error(
181 self.errors.NET_PING_SUCCESS,
182 ping_path=_body,
183 stdout=_stdout,
184 stderr=_stderr
185 )
186 elif _params["returncode"] == 68:
187 # 68 is a 'can't resove host error'
188 self.errors.add_error(
189 self.errors.NET_PING_NOT_RESOLVED,
190 ping_path=_body,
191 stdout=_stdout,
192 stderr=_stderr
193 )
194 elif _params["returncode"] > 1:
195 # >1 is when no actial (any) response
196 self.errors.add_error(
197 self.errors.NET_PING_ERROR,
198 ping_path=_body,
199 stdout=_stdout,
200 stderr=_stderr
201 )
Alexe0c5b9e2019-04-23 18:51:23 -0500202 else:
Alex92e07ce2019-05-31 16:00:03 -0500203 # 1 is for timeouts amd/or packet lost
204 self.errors.add_error(
205 self.errors.NET_PING_TIMEOUT,
206 ping_path=_body,
207 stdout=_stdout,
208 stderr=_stderr
209 )
Alexe0c5b9e2019-04-23 18:51:23 -0500210
211 # Parse results back in place
212 src_data["targets"] = _result
213
Alexd9fd85e2019-05-16 16:58:24 -0500214 _progress.end()
Alexe0c5b9e2019-04-23 18:51:23 -0500215
Alex92e07ce2019-05-31 16:00:03 -0500216 return 0
Alexe0c5b9e2019-04-23 18:51:23 -0500217
Alex92e07ce2019-05-31 16:00:03 -0500218 def print_summary(self):
219 logger_cli.info(self.errors.get_summary(print_zeros=False))
220
221 def print_details(self):
222 # Detailed errors
Alexe0c5b9e2019-04-23 18:51:23 -0500223 logger_cli.info(
Alex92e07ce2019-05-31 16:00:03 -0500224 "\n{}\n".format(
225 self.errors.get_errors()
Alexe0c5b9e2019-04-23 18:51:23 -0500226 )
227 )