blob: 5b12a94e673af3963adf1416a94bf30db99c5339 [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
Alexe0c5b9e2019-04-23 18:51:23 -05008from cfg_checker.nodes import salt_master
9
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,
15 mtu=None,
16 detailed=False,
17 errors_class=None,
18 skip_list=None,
19 skip_list_file=None
20 ):
Alexe0c5b9e2019-04-23 18:51:23 -050021 logger_cli.info("# Initializing")
22 # all active nodes in the cloud
Alexe9908f72020-05-19 16:04:53 -050023 self.target_nodes = salt_master.get_nodes(
24 skip_list=skip_list,
25 skip_list_file=skip_list_file
26 )
27
Alexe0c5b9e2019-04-23 18:51:23 -050028 # default MTU value
29 self.target_mtu = mtu if mtu else 64
30 # only data
31 self.packet_size = int(self.target_mtu) - 20 - 8
32 self.detailed_summary = detailed
33
Alex92e07ce2019-05-31 16:00:03 -050034 if errors_class:
35 self.errors = errors_class
36 else:
37 logger_cli.debug("... init error logs folder")
38 self.errors = NetworkErrors()
39
Alexe0c5b9e2019-04-23 18:51:23 -050040 def _collect_node_addresses(self, target_net):
41 # use reclass model and standard methods
42 # to create list of nodes with target network
Alex92e07ce2019-05-31 16:00:03 -050043 _mapper = NetworkMapper(errors_class=self.errors)
44 _reclass = _mapper.map_network(_mapper.RUNTIME)
Alexe0c5b9e2019-04-23 18:51:23 -050045 if target_net in _reclass:
46 return _reclass[target_net]
47 else:
48 logger_cli.info(
49 "# Target network of {} not found in reclass".format(
50 target_net.exploded
51 )
52 )
53 return None
54
55 def ping_nodes(self, network_cidr_str):
56 # Conduct actual ping using network CIDR
57 logger_cli.info("# Collecting node pairs")
Alex3bc95f62020-03-05 17:00:04 -060058 _fake_if = ipaddress.IPv4Interface(str(network_cidr_str))
Alexe0c5b9e2019-04-23 18:51:23 -050059 _net = _fake_if.network
60 # collect nodes and ips from reclass
61 nodes = self._collect_node_addresses(_net)
62 # build list of packets to be sent
63 # source -> target
64 _count = 0
65 _packets = {}
66 _nodes = sorted(nodes.keys())
67 _nodes_total = len(_nodes)
68 logger_cli.info("-> {} nodes found within subnet of '{}'".format(
69 _nodes_total,
70 network_cidr_str
71 ))
72 while len(_nodes) > 0:
73 src_host = _nodes.pop()
74 src_data = nodes[src_host]
75 src_if_name = src_data[0]['name']
76 src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
77 _packets[src_host] = {
78 "ip": src_ips[0],
79 "if_name": src_if_name,
80 "targets": {}
81 }
82
Alex3bc95f62020-03-05 17:00:04 -060083 for tgt_host, tgt_data in nodes.items():
Alexe9547d82019-06-03 15:22:50 -050084 _t = _packets[src_host]["targets"]
Alexe0c5b9e2019-04-23 18:51:23 -050085 for tgt_if in tgt_data:
86 tgt_if_name = tgt_if['name']
87 _ip_index = 0
88 for tgt_ip in tgt_if['ifs']:
89 _ip = str(tgt_ip.ip)
90 if _ip not in src_ips:
Alexe9547d82019-06-03 15:22:50 -050091 if tgt_host not in _t:
92 _t[tgt_host] = []
Alexe0c5b9e2019-04-23 18:51:23 -050093 _tgt = {
94 "ip": _ip,
95 "tgt_host": tgt_host,
96 "ip_index": _ip_index,
97 "if_name": tgt_if_name,
98 "mtu": self.target_mtu,
99 "size": self.packet_size
100 }
Alexe9547d82019-06-03 15:22:50 -0500101 _t[tgt_host].append(
Alexe0c5b9e2019-04-23 18:51:23 -0500102 _tgt
103 )
104 _count += 1
105 _ip_index += 1
106 else:
107 pass
108 logger_cli.info("-> {} packets to send".format(_count))
109
Alex92e07ce2019-05-31 16:00:03 -0500110 if not _count:
111 logger_cli.warning(
112 "\n# WARNING: No packets to send for '{}', "
113 "check network configuration\n".format(network_cidr_str)
114 )
115
116 return -1
117
Alexe0c5b9e2019-04-23 18:51:23 -0500118 # do ping of packets
119 logger_cli.info("# Pinging nodes: MTU={}".format(self.target_mtu))
120 salt_master.prepare_script_on_active_nodes("ping.py")
Alexe0c5b9e2019-04-23 18:51:23 -0500121 _progress = Progress(_count)
122 _progress_index = 0
123 _node_index = 0
Alex3bc95f62020-03-05 17:00:04 -0600124 for src, src_data in _packets.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500125 _targets = src_data["targets"]
126 _node_index += 1
127 # create 'targets.json' on source host
128 _path = salt_master.prepare_json_on_node(
129 src,
130 _targets,
131 "targets.json"
132 )
133 # execute ping.py
134 _results = salt_master.execute_script_on_node(
135 src,
136 "ping.py",
137 args=[_path]
138 )
139 _progress_index += len(_targets)
140 # print progress
141 _progress.write_progress(
142 _progress_index,
143 note='/ {}/{} nodes / current {}'.format(
144 _node_index,
145 _nodes_total,
146 src
147 )
148 )
149 # Parse salt output
150 _result = _results[src]
Alexe9547d82019-06-03 15:22:50 -0500151 try:
152 _result = json.loads(_result)
Alexab232e42019-06-06 19:44:34 -0500153 except (ValueError, TypeError):
Alexe9547d82019-06-03 15:22:50 -0500154 _progress.clearline()
155 logger_cli.error(
Alexab232e42019-06-06 19:44:34 -0500156 "# ERROR: Unexpected salt return for '{}': '{}'\n".format(
157 src,
158 _result
159 )
160 )
161 self.errors.add_error(
162 self.errors.NET_NODE_NON_RESPONSIVE,
163 node=src,
164 response=_result
Alexe9547d82019-06-03 15:22:50 -0500165 )
166 continue
Alexe0c5b9e2019-04-23 18:51:23 -0500167 # Handle return codes
Alex3bc95f62020-03-05 17:00:04 -0600168 for tgt_node, _tgt_ips in _result.items():
Alexe0c5b9e2019-04-23 18:51:23 -0500169 for _params in _tgt_ips:
170 _body = "{}({}) --{}--> {}({}@{})\n".format(
171 src,
172 src_data["if_name"],
173 _params["returncode"],
174 tgt_node,
175 _params["if_name"],
176 _params["ip"]
177 )
178 _stdout = ""
179 _stderr = ""
180 if len(_params["stdout"]) > 0:
181 _stdout = "stdout:\n{}\n".format(_params["stdout"])
182 if len(_params["stderr"]) > 0:
183 _stderr = "stderr:\n{}\n".format(_params["stderr"])
184
Alex92e07ce2019-05-31 16:00:03 -0500185 if not _params["returncode"]:
186 # 0
187 self.errors.add_error(
188 self.errors.NET_PING_SUCCESS,
189 ping_path=_body,
190 stdout=_stdout,
191 stderr=_stderr
192 )
193 elif _params["returncode"] == 68:
194 # 68 is a 'can't resove host error'
195 self.errors.add_error(
196 self.errors.NET_PING_NOT_RESOLVED,
197 ping_path=_body,
198 stdout=_stdout,
199 stderr=_stderr
200 )
201 elif _params["returncode"] > 1:
202 # >1 is when no actial (any) response
203 self.errors.add_error(
204 self.errors.NET_PING_ERROR,
205 ping_path=_body,
206 stdout=_stdout,
207 stderr=_stderr
208 )
Alexe0c5b9e2019-04-23 18:51:23 -0500209 else:
Alex92e07ce2019-05-31 16:00:03 -0500210 # 1 is for timeouts amd/or packet lost
211 self.errors.add_error(
212 self.errors.NET_PING_TIMEOUT,
213 ping_path=_body,
214 stdout=_stdout,
215 stderr=_stderr
216 )
Alexe0c5b9e2019-04-23 18:51:23 -0500217
218 # Parse results back in place
219 src_data["targets"] = _result
220
Alexd9fd85e2019-05-16 16:58:24 -0500221 _progress.end()
Alexe0c5b9e2019-04-23 18:51:23 -0500222
Alex92e07ce2019-05-31 16:00:03 -0500223 return 0
Alexe0c5b9e2019-04-23 18:51:23 -0500224
Alex92e07ce2019-05-31 16:00:03 -0500225 def print_summary(self):
226 logger_cli.info(self.errors.get_summary(print_zeros=False))
227
228 def print_details(self):
229 # Detailed errors
Alexe0c5b9e2019-04-23 18:51:23 -0500230 logger_cli.info(
Alex92e07ce2019-05-31 16:00:03 -0500231 "\n{}\n".format(
232 self.errors.get_errors()
Alexe0c5b9e2019-04-23 18:51:23 -0500233 )
234 )