Refactor working with Networks and Pinger class
- Mapper moved to separate module
- Other modules can use Mapper to get desired networks
- salt_master is now a separate single instance
- Updated file handling on salt
- ping.py, an scripted flexible interface to ping command
multithreaded ping execution, 15 at once
- New commands in network: 'ping' and 'list'
- New error when runtime has no network listed in reclass
Fixes:
- Master node code handling
- Unknown node codes detection
- Proper node code search and handling
- File upload procedures updated
- Packages report fix
Change-Id: I5959210aed53b20b04b05ea880218e93239bb661
Related-PROD: PROD-28199
diff --git a/cfg_checker/modules/network/pinger.py b/cfg_checker/modules/network/pinger.py
new file mode 100644
index 0000000..8eea315
--- /dev/null
+++ b/cfg_checker/modules/network/pinger.py
@@ -0,0 +1,180 @@
+import ipaddress
+import json
+
+from cfg_checker.common import logger, logger_cli
+from cfg_checker.helpers.console_utils import Progress
+from cfg_checker.modules.network.mapper import NetworkMapper
+from cfg_checker.nodes import salt_master
+
+
+# ping -s 9072 -M do -n -c 1 -w 1 -W 1 ctl01
+
+
+# This is independent class with a salt.nodes input
+class NetworkPinger(object):
+ def __init__(self, mtu=None, detailed=False):
+ logger_cli.info("# Initializing")
+ # all active nodes in the cloud
+ self.target_nodes = salt_master.get_nodes()
+ # default MTU value
+ self.target_mtu = mtu if mtu else 64
+ # only data
+ self.packet_size = int(self.target_mtu) - 20 - 8
+ self.detailed_summary = detailed
+
+ def _collect_node_addresses(self, target_net):
+ # use reclass model and standard methods
+ # to create list of nodes with target network
+ _mapper = NetworkMapper()
+ _reclass = _mapper.map_network(_mapper.RECLASS)
+ if target_net in _reclass:
+ return _reclass[target_net]
+ else:
+ logger_cli.info(
+ "# Target network of {} not found in reclass".format(
+ target_net.exploded
+ )
+ )
+ return None
+
+ def ping_nodes(self, network_cidr_str):
+ # Conduct actual ping using network CIDR
+ logger_cli.info("# Collecting node pairs")
+ _fake_if = ipaddress.IPv4Interface(unicode(network_cidr_str))
+ _net = _fake_if.network
+ # collect nodes and ips from reclass
+ nodes = self._collect_node_addresses(_net)
+ # build list of packets to be sent
+ # source -> target
+ _count = 0
+ _packets = {}
+ _nodes = sorted(nodes.keys())
+ _nodes_total = len(_nodes)
+ logger_cli.info("-> {} nodes found within subnet of '{}'".format(
+ _nodes_total,
+ network_cidr_str
+ ))
+ while len(_nodes) > 0:
+ src_host = _nodes.pop()
+ src_data = nodes[src_host]
+ src_if_name = src_data[0]['name']
+ src_ips = [str(_if.ip) for _if in src_data[0]['ifs']]
+ _packets[src_host] = {
+ "ip": src_ips[0],
+ "if_name": src_if_name,
+ "targets": {}
+ }
+
+ for tgt_host, tgt_data in nodes.iteritems():
+ for tgt_if in tgt_data:
+ tgt_if_name = tgt_if['name']
+ _ip_index = 0
+ for tgt_ip in tgt_if['ifs']:
+ _ip = str(tgt_ip.ip)
+ if _ip not in src_ips:
+ _packets[src_host]["targets"][tgt_host] = []
+ _tgt = {
+ "ip": _ip,
+ "tgt_host": tgt_host,
+ "ip_index": _ip_index,
+ "if_name": tgt_if_name,
+ "mtu": self.target_mtu,
+ "size": self.packet_size
+ }
+ _packets[src_host]["targets"][tgt_host].append(
+ _tgt
+ )
+ _count += 1
+ _ip_index += 1
+ else:
+ pass
+ logger_cli.info("-> {} packets to send".format(_count))
+
+ # do ping of packets
+ logger_cli.info("# Pinging nodes: MTU={}".format(self.target_mtu))
+ salt_master.prepare_script_on_active_nodes("ping.py")
+ _errors = []
+ _success = []
+ _progress = Progress(_count)
+ _progress_index = 0
+ _node_index = 0
+ for src, src_data in _packets.iteritems():
+ _targets = src_data["targets"]
+ _node_index += 1
+ # create 'targets.json' on source host
+ _path = salt_master.prepare_json_on_node(
+ src,
+ _targets,
+ "targets.json"
+ )
+ # execute ping.py
+ _results = salt_master.execute_script_on_node(
+ src,
+ "ping.py",
+ args=[_path]
+ )
+ _progress_index += len(_targets)
+ # print progress
+ _progress.write_progress(
+ _progress_index,
+ note='/ {}/{} nodes / current {}'.format(
+ _node_index,
+ _nodes_total,
+ src
+ )
+ )
+ # Parse salt output
+ _result = _results[src]
+ _result = json.loads(_result)
+ # Handle return codes
+ for tgt_node, _tgt_ips in _result.iteritems():
+ for _params in _tgt_ips:
+ _body = "{}({}) --{}--> {}({}@{})\n".format(
+ src,
+ src_data["if_name"],
+ _params["returncode"],
+ tgt_node,
+ _params["if_name"],
+ _params["ip"]
+ )
+ _stdout = ""
+ _stderr = ""
+ if len(_params["stdout"]) > 0:
+ _stdout = "stdout:\n{}\n".format(_params["stdout"])
+ if len(_params["stderr"]) > 0:
+ _stderr = "stderr:\n{}\n".format(_params["stderr"])
+
+ if _params["returncode"]:
+ _errors.append("FAIL: {}{}{}".format(
+ _body,
+ _stdout,
+ _stderr
+ ))
+ else:
+ _success.append("PASS: {}{}{}".format(
+ _body,
+ _stdout,
+ _stderr
+ ))
+
+ # Parse results back in place
+ src_data["targets"] = _result
+
+ _progress.newline()
+
+ if self.detailed_summary:
+ logger_cli.info("\n{:=^8s}".format("PASS"))
+ logger_cli.info("\n".join(_success))
+ else:
+ logger.info("\n{:=^8s}".format("PASS"))
+ logger.info("\n".join(_success))
+ if len(_errors) > 0:
+ logger_cli.info("\n{:=^8s}".format("FAIL"))
+ logger_cli.info("\n".join(_errors))
+
+ logger_cli.info(
+ "# {} failed, {} passed".format(
+ len(_errors),
+ len(_success)
+ )
+ )