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/scripts/ping.py b/scripts/ping.py
new file mode 100644
index 0000000..4e2778d
--- /dev/null
+++ b/scripts/ping.py
@@ -0,0 +1,159 @@
+import json
+import os
+import platform
+import sys
+from copy import deepcopy
+from multiprocessing.dummy import Pool
+from subprocess import PIPE, Popen
+
+_os = platform.system()
+_packets = {}
+
+_defaults = {
+    "ip": None,
+    "size": 0,
+    "fragmentation": False,
+    "count": 1,
+    "exit_timeout": 1,
+    "response_timeout": 1,
+    "numeric": True
+}
+
+_help_message = \
+    "Invalid parameters. Use: 'ping.py [PKT_SIZE] <IP>' or 'ping.py n.json'\n"
+
+_template = {
+    "returncode": -1,
+    "stdout": "",
+    "stderr": ""
+}
+
+
+def shell(command):
+    _ps = Popen(
+        " ".join(command),
+        shell=True,
+        stdout=PIPE,
+        stderr=PIPE
+    )
+    _out = _ps.communicate()
+    _err = _out[1]
+    _out = _out[0]
+    return _ps.returncode, _out, _err
+
+
+def write_help():
+    _t = deepcopy(_template)
+    _t["returncode"] = 1
+    _t["stderr"] = _help_message
+    write_outcome(_t)
+
+
+def write_outcome(_params):
+    sys.stdout.write(json.dumps(_params))
+
+
+def do_ping(_params):
+    # Load params and defaults
+    _d = deepcopy(_defaults)
+    for key in _params:
+        _d[key] = _params[key]
+
+    # Build cmd
+    _cmd = ["ping"]
+    if _os == "Darwin":
+        if not _d["fragmentation"]:
+            _cmd.append("-D")
+        if _d["exit_timeout"]:
+            _cmd += ["-t", str(_d["exit_timeout"])]
+    elif _os == "Linux":
+        if not _d["fragmentation"]:
+            _cmd += ["-M", "do"]
+        if _d["exit_timeout"]:
+            _cmd += ["-w", str(_d["exit_timeout"])]
+    else:
+        # Windows or other OS
+        _t = deepcopy(_template)
+        _t["returncode"] = 1
+        _t["stderr"] = \
+            "ping.py: '{}' support not implemented".format(_os)
+        write_outcome(_t)
+        sys.exit(1)
+
+    if _d["size"]:
+        _cmd += ["-s", str(_d["size"])]
+    if _d["count"]:
+        _cmd += ["-c", str(_d["count"])]
+    if _d["numeric"]:
+        _cmd.append("-n")
+    if _d["response_timeout"]:
+        _cmd += ["-W", str(_d["response_timeout"])]
+
+    _cmd.append(_d["ip"])
+    # sys.stdout.write("# {}\n".format(" ".join(_cmd)))
+    _r, _out, _err = shell(_cmd)
+
+    # TODO: parse results, latency, etc
+    _t = deepcopy(_template)
+    _t["returncode"] = _r
+    _t["stdout"] = _out
+    _t["stderr"] = _err
+    _params.update(_t)
+    return _params
+
+
+def load_targets(filename):
+    # load target ips from json
+    with open(filename, "r") as f:
+        j = json.load(f)
+
+    return j
+
+
+if len(sys.argv) < 2:
+    # no params given
+    write_help()
+elif len(sys.argv) < 3:
+    # one param: decide if it json file or IP
+    _arg = sys.argv[1]
+    if os.path.isfile(_arg):
+        _packets = load_targets(_arg)
+        # up to 15 packets at once
+        pool = Pool(15)
+        # prepare threaded map
+        _param_map = []
+        for _node, _data in _packets.iteritems():
+            if isinstance(_data, list):
+                for target in _data:
+                    _param_map.append(target)
+            elif isinstance(_data, dict):
+                _param_map.append(_data)
+            else:
+                _t = deepcopy(_template)
+                _t["returncode"] = 1
+                _t["stderr"] = \
+                    "TypeError: 'list' or 'dict' expected. " \
+                    "Got '{}': '{}'".format(
+                        type(_data).__name__,
+                        _data
+                )
+                _packets[_node] = _t
+        _threaded_out = pool.map(do_ping, _param_map)
+        for _out in _threaded_out:
+            if isinstance(_packets[_out["tgt_host"]], dict):
+                _packets[_out["tgt_host"]] = _out
+            elif isinstance(_packets[_out["tgt_host"]], list):
+                _packets[_out["tgt_host"]][_out["ip_index"]] = _out
+        sys.stdout.write(json.dumps(_packets))
+    else:
+        # IP given
+        _ip = sys.argv[1]
+        write_outcome(do_ping(_ip))
+elif len(sys.argv) < 4:
+    # two params: size and IP
+    _s = sys.argv[1]
+    _ip = sys.argv[2]
+    write_outcome(do_ping(_ip, size=_s))
+else:
+    # too many params given
+    write_help()