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)
+            )
+        )