blob: 266727bf50ca4d8de3122e27dc327f32e357a3fe [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):
Alex92e07ce2019-05-31 16:00:03 -050013 def __init__(self, mtu=None, detailed=False, errors_class=None):
Alexe0c5b9e2019-04-23 18:51:23 -050014 logger_cli.info("# Initializing")
15 # all active nodes in the cloud
16 self.target_nodes = salt_master.get_nodes()
17 # default MTU value
18 self.target_mtu = mtu if mtu else 64
19 # only data
20 self.packet_size = int(self.target_mtu) - 20 - 8
21 self.detailed_summary = detailed
22
Alex92e07ce2019-05-31 16:00:03 -050023 if errors_class:
24 self.errors = errors_class
25 else:
26 logger_cli.debug("... init error logs folder")
27 self.errors = NetworkErrors()
28
Alexe0c5b9e2019-04-23 18:51:23 -050029 def _collect_node_addresses(self, target_net):
30 # use reclass model and standard methods
31 # to create list of nodes with target network
Alex92e07ce2019-05-31 16:00:03 -050032 _mapper = NetworkMapper(errors_class=self.errors)
33 _reclass = _mapper.map_network(_mapper.RUNTIME)
Alexe0c5b9e2019-04-23 18:51:23 -050034 if target_net in _reclass:
35 return _reclass[target_net]
36 else:
37 logger_cli.info(
38 "# Target network of {} not found in reclass".format(
39 target_net.exploded
40 )
41 )
42 return None
43
44 def ping_nodes(self, network_cidr_str):
45 # Conduct actual ping using network CIDR
46 logger_cli.info("# Collecting node pairs")
47 _fake_if = ipaddress.IPv4Interface(unicode(network_cidr_str))
48 _net = _fake_if.network
49 # collect nodes and ips from reclass
50 nodes = self._collect_node_addresses(_net)
51 # build list of packets to be sent
52 # source -> target
53 _count = 0
54 _packets = {}
55 _nodes = sorted(nodes.keys())
56 _nodes_total = len(_nodes)
57 logger_cli.info("-> {} nodes found within subnet of '{}'".format(
58 _nodes_total,
59 network_cidr_str
60 ))
61 while len(_nodes) > 0:
62 src_host = _nodes.pop()
63 src_data = nodes[src_host]
64 src_if_name = src_data[0]['name']
65 src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
66 _packets[src_host] = {
67 "ip": src_ips[0],
68 "if_name": src_if_name,
69 "targets": {}
70 }
71
72 for tgt_host, tgt_data in nodes.iteritems():
Alexe9547d82019-06-03 15:22:50 -050073 _t = _packets[src_host]["targets"]
Alexe0c5b9e2019-04-23 18:51:23 -050074 for tgt_if in tgt_data:
75 tgt_if_name = tgt_if['name']
76 _ip_index = 0
77 for tgt_ip in tgt_if['ifs']:
78 _ip = str(tgt_ip.ip)
79 if _ip not in src_ips:
Alexe9547d82019-06-03 15:22:50 -050080 if tgt_host not in _t:
81 _t[tgt_host] = []
Alexe0c5b9e2019-04-23 18:51:23 -050082 _tgt = {
83 "ip": _ip,
84 "tgt_host": tgt_host,
85 "ip_index": _ip_index,
86 "if_name": tgt_if_name,
87 "mtu": self.target_mtu,
88 "size": self.packet_size
89 }
Alexe9547d82019-06-03 15:22:50 -050090 _t[tgt_host].append(
Alexe0c5b9e2019-04-23 18:51:23 -050091 _tgt
92 )
93 _count += 1
94 _ip_index += 1
95 else:
96 pass
97 logger_cli.info("-> {} packets to send".format(_count))
98
Alex92e07ce2019-05-31 16:00:03 -050099 if not _count:
100 logger_cli.warning(
101 "\n# WARNING: No packets to send for '{}', "
102 "check network configuration\n".format(network_cidr_str)
103 )
104
105 return -1
106
Alexe0c5b9e2019-04-23 18:51:23 -0500107 # do ping of packets
108 logger_cli.info("# Pinging nodes: MTU={}".format(self.target_mtu))
109 salt_master.prepare_script_on_active_nodes("ping.py")
Alexe0c5b9e2019-04-23 18:51:23 -0500110 _progress = Progress(_count)
111 _progress_index = 0
112 _node_index = 0
113 for src, src_data in _packets.iteritems():
114 _targets = src_data["targets"]
115 _node_index += 1
116 # create 'targets.json' on source host
117 _path = salt_master.prepare_json_on_node(
118 src,
119 _targets,
120 "targets.json"
121 )
122 # execute ping.py
123 _results = salt_master.execute_script_on_node(
124 src,
125 "ping.py",
126 args=[_path]
127 )
128 _progress_index += len(_targets)
129 # print progress
130 _progress.write_progress(
131 _progress_index,
132 note='/ {}/{} nodes / current {}'.format(
133 _node_index,
134 _nodes_total,
135 src
136 )
137 )
138 # Parse salt output
139 _result = _results[src]
Alexe9547d82019-06-03 15:22:50 -0500140 try:
141 _result = json.loads(_result)
Alexab232e42019-06-06 19:44:34 -0500142 except (ValueError, TypeError):
Alexe9547d82019-06-03 15:22:50 -0500143 _progress.clearline()
144 logger_cli.error(
Alexab232e42019-06-06 19:44:34 -0500145 "# ERROR: Unexpected salt return for '{}': '{}'\n".format(
146 src,
147 _result
148 )
149 )
150 self.errors.add_error(
151 self.errors.NET_NODE_NON_RESPONSIVE,
152 node=src,
153 response=_result
Alexe9547d82019-06-03 15:22:50 -0500154 )
155 continue
Alexe0c5b9e2019-04-23 18:51:23 -0500156 # Handle return codes
157 for tgt_node, _tgt_ips in _result.iteritems():
158 for _params in _tgt_ips:
159 _body = "{}({}) --{}--> {}({}@{})\n".format(
160 src,
161 src_data["if_name"],
162 _params["returncode"],
163 tgt_node,
164 _params["if_name"],
165 _params["ip"]
166 )
167 _stdout = ""
168 _stderr = ""
169 if len(_params["stdout"]) > 0:
170 _stdout = "stdout:\n{}\n".format(_params["stdout"])
171 if len(_params["stderr"]) > 0:
172 _stderr = "stderr:\n{}\n".format(_params["stderr"])
173
Alex92e07ce2019-05-31 16:00:03 -0500174 if not _params["returncode"]:
175 # 0
176 self.errors.add_error(
177 self.errors.NET_PING_SUCCESS,
178 ping_path=_body,
179 stdout=_stdout,
180 stderr=_stderr
181 )
182 elif _params["returncode"] == 68:
183 # 68 is a 'can't resove host error'
184 self.errors.add_error(
185 self.errors.NET_PING_NOT_RESOLVED,
186 ping_path=_body,
187 stdout=_stdout,
188 stderr=_stderr
189 )
190 elif _params["returncode"] > 1:
191 # >1 is when no actial (any) response
192 self.errors.add_error(
193 self.errors.NET_PING_ERROR,
194 ping_path=_body,
195 stdout=_stdout,
196 stderr=_stderr
197 )
Alexe0c5b9e2019-04-23 18:51:23 -0500198 else:
Alex92e07ce2019-05-31 16:00:03 -0500199 # 1 is for timeouts amd/or packet lost
200 self.errors.add_error(
201 self.errors.NET_PING_TIMEOUT,
202 ping_path=_body,
203 stdout=_stdout,
204 stderr=_stderr
205 )
Alexe0c5b9e2019-04-23 18:51:23 -0500206
207 # Parse results back in place
208 src_data["targets"] = _result
209
Alexd9fd85e2019-05-16 16:58:24 -0500210 _progress.end()
Alexe0c5b9e2019-04-23 18:51:23 -0500211
Alex92e07ce2019-05-31 16:00:03 -0500212 return 0
Alexe0c5b9e2019-04-23 18:51:23 -0500213
Alex92e07ce2019-05-31 16:00:03 -0500214 def print_summary(self):
215 logger_cli.info(self.errors.get_summary(print_zeros=False))
216
217 def print_details(self):
218 # Detailed errors
Alexe0c5b9e2019-04-23 18:51:23 -0500219 logger_cli.info(
Alex92e07ce2019-05-31 16:00:03 -0500220 "\n{}\n".format(
221 self.errors.get_errors()
Alexe0c5b9e2019-04-23 18:51:23 -0500222 )
223 )