Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 1 | import ipaddress |
| 2 | import json |
| 3 | |
Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 4 | from cfg_checker.common import logger_cli |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 5 | from cfg_checker.helpers.console_utils import Progress |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 6 | |
| 7 | |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 8 | # This is independent class with a salt.nodes input |
| 9 | class NetworkPinger(object): |
Alex | e9908f7 | 2020-05-19 16:04:53 -0500 | [diff] [blame] | 10 | def __init__( |
| 11 | self, |
Alex | aae5804 | 2020-12-31 11:48:28 -0600 | [diff] [blame] | 12 | mapper, |
Alex | e9908f7 | 2020-05-19 16:04:53 -0500 | [diff] [blame] | 13 | detailed=False, |
Alex | e9908f7 | 2020-05-19 16:04:53 -0500 | [diff] [blame] | 14 | skip_list=None, |
| 15 | skip_list_file=None |
| 16 | ): |
Alex | 9a4ad21 | 2020-10-01 18:04:25 -0500 | [diff] [blame] | 17 | logger_cli.info("# Initializing Pinger") |
Alex | aae5804 | 2020-12-31 11:48:28 -0600 | [diff] [blame] | 18 | self.mapper = mapper |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 19 | # only data |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 20 | self.detailed_summary = detailed |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 21 | self.results = {} |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 22 | |
| 23 | def _collect_node_addresses(self, target_net): |
| 24 | # use reclass model and standard methods |
| 25 | # to create list of nodes with target network |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 26 | _networks = self.mapper.map_network(self.mapper.RUNTIME) |
| 27 | if target_net in _networks: |
| 28 | return _networks[target_net] |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 29 | else: |
| 30 | logger_cli.info( |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 31 | "# Target network of {} not found after mapping".format( |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 32 | target_net.exploded |
| 33 | ) |
| 34 | ) |
| 35 | return None |
| 36 | |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 37 | def _get_packets_data(self, network_cidr_str, mtu): |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 38 | # Conduct actual ping using network CIDR |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 39 | logger_cli.debug("... collecting node pairs") |
Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 40 | _fake_if = ipaddress.IPv4Interface(str(network_cidr_str)) |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 41 | _net = _fake_if.network |
| 42 | # collect nodes and ips from reclass |
| 43 | nodes = self._collect_node_addresses(_net) |
| 44 | # build list of packets to be sent |
| 45 | # source -> target |
| 46 | _count = 0 |
| 47 | _packets = {} |
| 48 | _nodes = sorted(nodes.keys()) |
| 49 | _nodes_total = len(_nodes) |
| 50 | logger_cli.info("-> {} nodes found within subnet of '{}'".format( |
| 51 | _nodes_total, |
| 52 | network_cidr_str |
| 53 | )) |
| 54 | while len(_nodes) > 0: |
| 55 | src_host = _nodes.pop() |
| 56 | src_data = nodes[src_host] |
| 57 | src_if_name = src_data[0]['name'] |
| 58 | src_ips = [str(_if.ip) for _if in src_data[0]['ifs']] |
| 59 | _packets[src_host] = { |
| 60 | "ip": src_ips[0], |
| 61 | "if_name": src_if_name, |
| 62 | "targets": {} |
| 63 | } |
| 64 | |
Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 65 | for tgt_host, tgt_data in nodes.items(): |
Alex | e9547d8 | 2019-06-03 15:22:50 -0500 | [diff] [blame] | 66 | _t = _packets[src_host]["targets"] |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 67 | for tgt_if in tgt_data: |
| 68 | tgt_if_name = tgt_if['name'] |
| 69 | _ip_index = 0 |
| 70 | for tgt_ip in tgt_if['ifs']: |
| 71 | _ip = str(tgt_ip.ip) |
| 72 | if _ip not in src_ips: |
Alex | e9547d8 | 2019-06-03 15:22:50 -0500 | [diff] [blame] | 73 | if tgt_host not in _t: |
| 74 | _t[tgt_host] = [] |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 75 | # Handling mtu and packet size |
| 76 | if mtu == 0: |
| 77 | # Detect MTU |
| 78 | _mtu = int(tgt_if['mtu']) |
| 79 | else: |
| 80 | _mtu = mtu |
| 81 | _packet_size = int(_mtu)-20-8 if _mtu != 0 else 0 |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 82 | _tgt = { |
| 83 | "ip": _ip, |
| 84 | "tgt_host": tgt_host, |
| 85 | "ip_index": _ip_index, |
| 86 | "if_name": tgt_if_name, |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 87 | "mtu": _mtu, |
| 88 | "size": _packet_size |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 89 | } |
Alex | e9547d8 | 2019-06-03 15:22:50 -0500 | [diff] [blame] | 90 | _t[tgt_host].append( |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 91 | _tgt |
| 92 | ) |
| 93 | _count += 1 |
| 94 | _ip_index += 1 |
| 95 | else: |
| 96 | pass |
| 97 | logger_cli.info("-> {} packets to send".format(_count)) |
| 98 | |
Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 99 | 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 | |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 105 | return None, _packets, _nodes_total |
| 106 | else: |
| 107 | return _count, _packets, _nodes_total |
| 108 | |
| 109 | def _process_result(self, src, src_data, result): |
| 110 | # Parse output |
| 111 | try: |
| 112 | _result = json.loads(result) |
| 113 | except (ValueError, TypeError): |
| 114 | self.mapper.errors.add_error( |
| 115 | self.mapper.errors.NET_NODE_NON_RESPONSIVE, |
| 116 | node=src, |
| 117 | response=result |
| 118 | ) |
| 119 | |
| 120 | _msg = "# ERROR: Unexpected return for '{}': '{}'\n".format( |
| 121 | src, |
| 122 | result |
| 123 | ) |
| 124 | |
| 125 | return False, _msg |
| 126 | |
| 127 | # Handle return codes |
| 128 | for tgt_node, _tgt_ips in _result.items(): |
| 129 | for _params in _tgt_ips: |
| 130 | _body = "{}({}) --{}--> {}({}@{})\n".format( |
| 131 | src, |
| 132 | src_data["if_name"], |
| 133 | _params["returncode"], |
| 134 | tgt_node, |
| 135 | _params["if_name"], |
| 136 | _params["ip"] |
| 137 | ) |
| 138 | _stdout = "" |
| 139 | _stderr = "" |
| 140 | if len(_params["stdout"]) > 0: |
| 141 | _stdout = "stdout:\n{}\n".format(_params["stdout"]) |
| 142 | if len(_params["stderr"]) > 0: |
| 143 | _stderr = "stderr:\n{}\n".format(_params["stderr"]) |
| 144 | |
| 145 | if not _params["returncode"]: |
| 146 | # 0 |
| 147 | self.mapper.errors.add_error( |
| 148 | self.mapper.errors.NET_PING_SUCCESS, |
| 149 | ping_path=_body, |
| 150 | stdout=_stdout, |
| 151 | stderr=_stderr |
| 152 | ) |
| 153 | elif _params["returncode"] == 68: |
| 154 | # 68 is a 'can't resove host error' |
| 155 | self.mapper.errors.add_error( |
| 156 | self.mapper.errors.NET_PING_NOT_RESOLVED, |
| 157 | ping_path=_body, |
| 158 | stdout=_stdout, |
| 159 | stderr=_stderr |
| 160 | ) |
| 161 | elif _params["returncode"] > 1: |
| 162 | # >1 is when no actial (any) response |
| 163 | self.mapper.errors.add_error( |
| 164 | self.mapper.errors.NET_PING_ERROR, |
| 165 | ping_path=_body, |
| 166 | stdout=_stdout, |
| 167 | stderr=_stderr |
| 168 | ) |
| 169 | else: |
| 170 | # 1 is for timeouts amd/or packet lost |
| 171 | self.mapper.errors.add_error( |
| 172 | self.mapper.errors.NET_PING_TIMEOUT, |
| 173 | ping_path=_body, |
| 174 | stdout=_stdout, |
| 175 | stderr=_stderr |
| 176 | ) |
| 177 | return True, _result |
| 178 | |
| 179 | def print_summary(self, cidr, data): |
| 180 | # Print summary of ping activity in node-node perspective |
| 181 | logger_cli.info("\n-> {}, {} nodes".format(cidr, len(data))) |
| 182 | # iterate nodes |
| 183 | for _n, _d in data.items(): |
| 184 | # targets |
| 185 | _total = len(_d['targets']) |
| 186 | _fail = [] |
| 187 | _pass = [] |
| 188 | _mtus = [] |
| 189 | for _f, _t in _d['targets'].items(): |
| 190 | # filter data |
| 191 | _fail += [[_f, _l] for _l in _t if _l['returncode']] |
| 192 | _pass += [[_f, _l] for _l in _t if not _l['returncode']] |
| 193 | _mtus += [str(_l['mtu']) for _l in _t] |
| 194 | # summary of passed |
| 195 | # source node |
| 196 | logger_cli.info( |
| 197 | " PASS: {}/{} nodes from {} ({}:{}) with MTU {}".format( |
| 198 | len(_pass), |
| 199 | _total, |
| 200 | _n, |
| 201 | _d['if_name'], |
| 202 | _d['ip'], |
| 203 | ",".join(list(set(_mtus))) |
| 204 | ) |
| 205 | ) |
| 206 | # print fails |
| 207 | for node, _dict in _fail: |
| 208 | logger_cli.info( |
| 209 | "\tFAIL: {} ({}:{}) with {}/{}".format( |
| 210 | node, |
| 211 | _dict['if_name'], |
| 212 | _dict['ip'], |
| 213 | _dict['size'], |
| 214 | _dict['mtu'] |
| 215 | ) |
| 216 | ) |
| 217 | |
| 218 | # logger_cli.info(self.mapper.errors.get_summary(print_zeros=False)) |
| 219 | return |
| 220 | |
| 221 | def print_details(self, cidr, data): |
| 222 | def _print_stds(stdout, stderr): |
| 223 | logger_cli.debug(" stdout:\n") |
| 224 | for line in stdout.splitlines(): |
| 225 | logger_cli.debug(" {}".format(line)) |
| 226 | if not stderr: |
| 227 | logger_cli.debug(" stderr: <empty>") |
| 228 | else: |
| 229 | logger_cli.debug(" stderr:\n") |
| 230 | for line in stderr.splitlines(): |
| 231 | logger_cli.debug(" {}".format(line)) |
| 232 | |
| 233 | logger_cli.info("\n---> {}, {} nodes".format(cidr, len(data))) |
| 234 | # iterate nodes |
| 235 | for _n, _d in data.items(): |
| 236 | # targets |
| 237 | _fail = [] |
| 238 | _pass = [] |
| 239 | for _f, _t in _d['targets'].items(): |
| 240 | # filter data |
| 241 | _fail += [[_f, _l] for _l in _t if _l['returncode']] |
| 242 | _pass += [[_f, _l] for _l in _t if not _l['returncode']] |
| 243 | # summary of passed |
| 244 | # source node |
| 245 | logger_cli.info( |
| 246 | "======= from {} ({}:{})".format( |
| 247 | _n, |
| 248 | _d['if_name'], |
| 249 | _d['ip'] |
| 250 | ) |
| 251 | ) |
| 252 | for node, _dict in _pass: |
| 253 | logger_cli.info( |
| 254 | " + PASS: to {} ({}:{}) with {}/{}".format( |
| 255 | node, |
| 256 | _dict['if_name'], |
| 257 | _dict['ip'], |
| 258 | _dict['size'], |
| 259 | _dict['mtu'] |
| 260 | ) |
| 261 | ) |
| 262 | _print_stds(_dict['stdout'], _dict['stderr']) |
| 263 | # print fails |
| 264 | for node, _dict in _fail: |
| 265 | logger_cli.info( |
| 266 | " - FAIL: to {} ({}:{}) with {}/{}".format( |
| 267 | node, |
| 268 | _dict['if_name'], |
| 269 | _dict['ip'], |
| 270 | _dict['size'], |
| 271 | _dict['mtu'] |
| 272 | ) |
| 273 | ) |
| 274 | _print_stds(_dict['stdout'], _dict['stderr']) |
| 275 | |
| 276 | # Detailed errors |
| 277 | # logger_cli.info( |
| 278 | # "\n{}\n".format( |
| 279 | # self.mapper.errors.get_errors() |
| 280 | # ) |
| 281 | # ) |
| 282 | return |
| 283 | |
| 284 | |
| 285 | class SaltNetworkPinger(NetworkPinger): |
| 286 | def __init__( |
| 287 | self, |
| 288 | mapper, |
| 289 | detailed=False, |
| 290 | skip_list=None, |
| 291 | skip_list_file=None |
| 292 | ): |
| 293 | super(SaltNetworkPinger, self).__init__( |
| 294 | mapper, |
| 295 | detailed=detailed, |
| 296 | skip_list=skip_list, |
| 297 | skip_list_file=skip_list_file |
| 298 | ) |
| 299 | |
| 300 | def ping_nodes(self, network_cidr_str, mtu): |
| 301 | # get packets |
| 302 | _count, _packets, _nodes_total = self._get_packets_data( |
| 303 | network_cidr_str, |
| 304 | mtu |
| 305 | ) |
| 306 | if not _count: |
| 307 | return None |
Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 308 | |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 309 | # do ping of packets |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 310 | logger_cli.info( |
| 311 | "-> Pinging nodes in {}".format(network_cidr_str)) |
Alex | aae5804 | 2020-12-31 11:48:28 -0600 | [diff] [blame] | 312 | self.mapper.master.prepare_script_on_active_nodes("ping.py") |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 313 | _progress = Progress(_count) |
| 314 | _progress_index = 0 |
| 315 | _node_index = 0 |
Alex | 3bc95f6 | 2020-03-05 17:00:04 -0600 | [diff] [blame] | 316 | for src, src_data in _packets.items(): |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 317 | _targets = src_data["targets"] |
| 318 | _node_index += 1 |
| 319 | # create 'targets.json' on source host |
Alex | aae5804 | 2020-12-31 11:48:28 -0600 | [diff] [blame] | 320 | _path = self.mapper.master.prepare_json_on_node( |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 321 | src, |
| 322 | _targets, |
| 323 | "targets.json" |
| 324 | ) |
| 325 | # execute ping.py |
Alex | aae5804 | 2020-12-31 11:48:28 -0600 | [diff] [blame] | 326 | _results = self.mapper.master.execute_script_on_node( |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 327 | src, |
| 328 | "ping.py", |
| 329 | args=[_path] |
| 330 | ) |
| 331 | _progress_index += len(_targets) |
| 332 | # print progress |
| 333 | _progress.write_progress( |
| 334 | _progress_index, |
| 335 | note='/ {}/{} nodes / current {}'.format( |
| 336 | _node_index, |
| 337 | _nodes_total, |
| 338 | src |
| 339 | ) |
| 340 | ) |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 341 | # Parse results back in place |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 342 | _ret_code, _data = self._process_result( |
| 343 | src, src_data, |
| 344 | _results[src] |
| 345 | ) |
| 346 | if not _ret_code: |
| 347 | _progress.clearline() |
| 348 | logger_cli.error(_data) |
| 349 | continue |
| 350 | else: |
| 351 | src_data["targets"] = _data |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 352 | |
Alex | d9fd85e | 2019-05-16 16:58:24 -0500 | [diff] [blame] | 353 | _progress.end() |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 354 | |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 355 | return _packets |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 356 | |
Alex | 92e07ce | 2019-05-31 16:00:03 -0500 | [diff] [blame] | 357 | |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 358 | class KubeNetworkPinger(NetworkPinger): |
| 359 | def __init__( |
| 360 | self, |
| 361 | mapper, |
| 362 | detailed=False, |
| 363 | skip_list=None, |
| 364 | skip_list_file=None |
| 365 | ): |
| 366 | super(KubeNetworkPinger, self).__init__( |
| 367 | mapper, |
| 368 | detailed=detailed, |
| 369 | skip_list=skip_list, |
| 370 | skip_list_file=skip_list_file |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame] | 371 | ) |
Alex | 7b0ee9a | 2021-09-21 17:16:17 -0500 | [diff] [blame^] | 372 | |
| 373 | def ping_nodes(self, network_cidr_str, mtu): |
| 374 | # get packets |
| 375 | _count, _packets, _nodes_total = self._get_packets_data( |
| 376 | network_cidr_str, |
| 377 | mtu |
| 378 | ) |
| 379 | if not _count: |
| 380 | return None |
| 381 | |
| 382 | # do ping of packets |
| 383 | logger_cli.info( |
| 384 | "-> Pinging nodes in {}".format(network_cidr_str)) |
| 385 | _progress = Progress(_count) |
| 386 | _progress_index = 0 |
| 387 | _node_index = 0 |
| 388 | for src, src_data in _packets.items(): |
| 389 | _targets = src_data["targets"] |
| 390 | _node_index += 1 |
| 391 | # create 'targets.json' on source host |
| 392 | _ds = self.mapper.get_daemonset() |
| 393 | _pname = self.mapper.master.get_pod_name_in_daemonset_by_node( |
| 394 | src, |
| 395 | _ds |
| 396 | ) |
| 397 | _path = self.mapper.master.prepare_json_in_pod( |
| 398 | _pname, |
| 399 | _ds.metadata.namespace, |
| 400 | _targets, |
| 401 | "targets.json" |
| 402 | ) |
| 403 | # execute ping.py |
| 404 | _result = self.mapper.master.exec_on_target_pod( |
| 405 | _pname, |
| 406 | "ping.py", |
| 407 | args=[_path] |
| 408 | ) |
| 409 | _progress_index += len(_targets) |
| 410 | # print progress |
| 411 | _progress.write_progress( |
| 412 | _progress_index, |
| 413 | note='/ {}/{} nodes / current {}'.format( |
| 414 | _node_index, |
| 415 | _nodes_total, |
| 416 | src |
| 417 | ) |
| 418 | ) |
| 419 | # Parse results back in place |
| 420 | _ret_code, _data = self._process_result( |
| 421 | src, src_data, |
| 422 | _result |
| 423 | ) |
| 424 | if not _ret_code: |
| 425 | _progress.clearline() |
| 426 | logger_cli.error(_data) |
| 427 | continue |
| 428 | else: |
| 429 | src_data["targets"] = _data |
| 430 | |
| 431 | _progress.end() |
| 432 | |
| 433 | return _packets |