blob: 17f8597e62e835f38c18def65b44e1644fd4ec3f [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
6from cfg_checker.modules.network.mapper import NetworkMapper
Alex92e07ce2019-05-31 16:00:03 -05007from cfg_checker.modules.network.network_errors import NetworkErrors
Alex9a4ad212020-10-01 18:04:25 -05008from cfg_checker.nodes import SaltNodes
Alexe0c5b9e2019-04-23 18:51:23 -05009
10
Alexe0c5b9e2019-04-23 18:51:23 -050011# This is independent class with a salt.nodes input
12class NetworkPinger(object):
Alexe9908f72020-05-19 16:04:53 -050013 def __init__(
14 self,
Alex9a4ad212020-10-01 18:04:25 -050015 config,
Alexe9908f72020-05-19 16:04:53 -050016 mtu=None,
17 detailed=False,
18 errors_class=None,
19 skip_list=None,
20 skip_list_file=None
21 ):
Alex9a4ad212020-10-01 18:04:25 -050022 logger_cli.info("# Initializing Pinger")
23 self.salt_master = SaltNodes(config)
Alexe0c5b9e2019-04-23 18:51:23 -050024 # all active nodes in the cloud
Alex9a4ad212020-10-01 18:04:25 -050025 self.target_nodes = self.salt_master.get_nodes(
Alexe9908f72020-05-19 16:04:53 -050026 skip_list=skip_list,
27 skip_list_file=skip_list_file
28 )
29
Alexe0c5b9e2019-04-23 18:51:23 -050030 # default MTU value
31 self.target_mtu = mtu if mtu else 64
32 # only data
33 self.packet_size = int(self.target_mtu) - 20 - 8
34 self.detailed_summary = detailed
35
Alex92e07ce2019-05-31 16:00:03 -050036 if errors_class:
37 self.errors = errors_class
38 else:
39 logger_cli.debug("... init error logs folder")
40 self.errors = NetworkErrors()
41
Alexe0c5b9e2019-04-23 18:51:23 -050042 def _collect_node_addresses(self, target_net):
43 # use reclass model and standard methods
44 # to create list of nodes with target network
Alex92e07ce2019-05-31 16:00:03 -050045 _mapper = NetworkMapper(errors_class=self.errors)
46 _reclass = _mapper.map_network(_mapper.RUNTIME)
Alexe0c5b9e2019-04-23 18:51:23 -050047 if target_net in _reclass:
48 return _reclass[target_net]
49 else:
50 logger_cli.info(
51 "# Target network of {} not found in reclass".format(
52 target_net.exploded
53 )
54 )
55 return None
56
57 def ping_nodes(self, network_cidr_str):
58 # Conduct actual ping using network CIDR
59 logger_cli.info("# Collecting node pairs")
Alex3bc95f62020-03-05 17:00:04 -060060 _fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
Alexe0c5b9e2019-04-23 18:51:23 -050061 _net = _fake_if.network
62 # collect nodes and ips from reclass
63 nodes = self._collect_node_addresses(_net)
64 # build list of packets to be sent
65 # source -> target
66 _count = 0
67 _packets = {}
68 _nodes = sorted(nodes.keys())
69 _nodes_total = len(_nodes)
70 logger_cli.info("-> {} nodes found within subnet of '{}'".format(
71 _nodes_total,
72 network_cidr_str
73 ))
74 while len(_nodes) > 0:
75 src_host = _nodes.pop()
76 src_data = nodes[src_host]
77 src_if_name = src_data[0]['name']
78 src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
79 _packets[src_host] = {
80 "ip": src_ips[0],
81 "if_name": src_if_name,
82 "targets": {}
83 }
84
Alex3bc95f62020-03-05 17:00:04 -060085 for tgt_host, tgt_data in nodes.items():
Alexe9547d82019-06-03 15:22:50 -050086 _t = _packets[src_host]["targets"]
Alexe0c5b9e2019-04-23 18:51:23 -050087 for tgt_if in tgt_data:
88 tgt_if_name = tgt_if['name']
89 _ip_index = 0
90 for tgt_ip in tgt_if['ifs']:
91 _ip = str(tgt_ip.ip)
92 if _ip not in src_ips:
Alexe9547d82019-06-03 15:22:50 -050093 if tgt_host not in _t:
94 _t[tgt_host] = []
Alexe0c5b9e2019-04-23 18:51:23 -050095 _tgt = {
96 "ip": _ip,
97 "tgt_host": tgt_host,
98 "ip_index": _ip_index,
99 "if_name": tgt_if_name,
100 "mtu": self.target_mtu,
101 "size": self.packet_size
102 }
Alexe9547d82019-06-03 15:22:50 -0500103 _t[tgt_host].append(
Alexe0c5b9e2019-04-23 18:51:23 -0500104 _tgt
105 )
106 _count += 1
107 _ip_index += 1
108 else:
109 pass
110 logger_cli.info("-> {} packets to send".format(_count))
111
Alex92e07ce2019-05-31 16:00:03 -0500112 if not _count:
113 logger_cli.warning(
114 "\n# WARNING: No packets to send for '{}', "
115 "check network configuration\n".format(network_cidr_str)
116 )
117
118 return -1
119
Alexe0c5b9e2019-04-23 18:51:23 -0500120 # do ping of packets
121 logger_cli.info("# Pinging nodes: MTU={}".format(self.target_mtu))
Alex9a4ad212020-10-01 18:04:25 -0500122 self.salt_master.prepare_script_on_active_nodes("ping.py")
Alexe0c5b9e2019-04-23 18:51:23 -0500123 _progress = Progress(_count)
124 _progress_index = 0
125 _node_index = 0
Alex3bc95f62020-03-05 17:00:04 -0600126 for src, src_data in _packets.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500127 _targets = src_data["targets"]
128 _node_index += 1
129 # create 'targets.json' on source host
Alex9a4ad212020-10-01 18:04:25 -0500130 _path = self.salt_master.prepare_json_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500131 src,
132 _targets,
133 "targets.json"
134 )
135 # execute ping.py
Alex9a4ad212020-10-01 18:04:25 -0500136 _results = self.salt_master.execute_script_on_node(
Alexe0c5b9e2019-04-23 18:51:23 -0500137 src,
138 "ping.py",
139 args=[_path]
140 )
141 _progress_index += len(_targets)
142 # print progress
143 _progress.write_progress(
144 _progress_index,
145 note='/ {}/{} nodes / current {}'.format(
146 _node_index,
147 _nodes_total,
148 src
149 )
150 )
151 # Parse salt output
152 _result = _results[src]
Alexe9547d82019-06-03 15:22:50 -0500153 try:
154 _result = json.loads(_result)
Alexab232e42019-06-06 19:44:34 -0500155 except (ValueError, TypeError):
Alexe9547d82019-06-03 15:22:50 -0500156 _progress.clearline()
157 logger_cli.error(
Alexab232e42019-06-06 19:44:34 -0500158 "# ERROR: Unexpected salt return for '{}': '{}'\n".format(
159 src,
160 _result
161 )
162 )
163 self.errors.add_error(
164 self.errors.NET_NODE_NON_RESPONSIVE,
165 node=src,
166 response=_result
Alexe9547d82019-06-03 15:22:50 -0500167 )
168 continue
Alexe0c5b9e2019-04-23 18:51:23 -0500169 # Handle return codes
Alex3bc95f62020-03-05 17:00:04 -0600170 for tgt_node, _tgt_ips in _result.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500171 for _params in _tgt_ips:
172 _body = "{}({}) --{}--> {}({}@{})\n".format(
173 src,
174 src_data["if_name"],
175 _params["returncode"],
176 tgt_node,
177 _params["if_name"],
178 _params["ip"]
179 )
180 _stdout = ""
181 _stderr = ""
182 if len(_params["stdout"]) > 0:
183 _stdout = "stdout:\n{}\n".format(_params["stdout"])
184 if len(_params["stderr"]) > 0:
185 _stderr = "stderr:\n{}\n".format(_params["stderr"])
186
Alex92e07ce2019-05-31 16:00:03 -0500187 if not _params["returncode"]:
188 # 0
189 self.errors.add_error(
190 self.errors.NET_PING_SUCCESS,
191 ping_path=_body,
192 stdout=_stdout,
193 stderr=_stderr
194 )
195 elif _params["returncode"] == 68:
196 # 68 is a 'can't resove host error'
197 self.errors.add_error(
198 self.errors.NET_PING_NOT_RESOLVED,
199 ping_path=_body,
200 stdout=_stdout,
201 stderr=_stderr
202 )
203 elif _params["returncode"] > 1:
204 # >1 is when no actial (any) response
205 self.errors.add_error(
206 self.errors.NET_PING_ERROR,
207 ping_path=_body,
208 stdout=_stdout,
209 stderr=_stderr
210 )
Alexe0c5b9e2019-04-23 18:51:23 -0500211 else:
Alex92e07ce2019-05-31 16:00:03 -0500212 # 1 is for timeouts amd/or packet lost
213 self.errors.add_error(
214 self.errors.NET_PING_TIMEOUT,
215 ping_path=_body,
216 stdout=_stdout,
217 stderr=_stderr
218 )
Alexe0c5b9e2019-04-23 18:51:23 -0500219
220 # Parse results back in place
221 src_data["targets"] = _result
222
Alexd9fd85e2019-05-16 16:58:24 -0500223 _progress.end()
Alexe0c5b9e2019-04-23 18:51:23 -0500224
Alex92e07ce2019-05-31 16:00:03 -0500225 return 0
Alexe0c5b9e2019-04-23 18:51:23 -0500226
Alex92e07ce2019-05-31 16:00:03 -0500227 def print_summary(self):
228 logger_cli.info(self.errors.get_summary(print_zeros=False))
229
230 def print_details(self):
231 # Detailed errors
Alexe0c5b9e2019-04-23 18:51:23 -0500232 logger_cli.info(
Alex92e07ce2019-05-31 16:00:03 -0500233 "\n{}\n".format(
234 self.errors.get_errors()
Alexe0c5b9e2019-04-23 18:51:23 -0500235 )
236 )