Unified command execution and unit tests

- All arguments inits moved to own clases
- Added unified way to execute commands
- Unit test structure and very basic tests
- Command line script to test coverage
- Argument parsers moved to corresponding commands
- Automatic parsers and command mapping

Change-Id: Id099d14702d9590729583dfd9574bd57022efac5
Related-PROD: PROD-28199
diff --git a/cfg_checker/modules/network/__init__.py b/cfg_checker/modules/network/__init__.py
index 78df6c6..075dccf 100644
--- a/cfg_checker/modules/network/__init__.py
+++ b/cfg_checker/modules/network/__init__.py
@@ -3,6 +3,37 @@
 
 import checker
 
+command_help = "Network infrastructure checks and reports"
+
+
+def init_parser(_parser):
+    # network subparser
+    net_subparsers = _parser.add_subparsers(dest='type')
+
+    net_check_parser = net_subparsers.add_parser(
+        'check',
+        help="Do network check and print the result"
+    )
+
+    net_check_parser.add_argument(
+        '--detailed',
+        action="store_true", default=False,
+        help="Print error details after summary"
+    )
+
+    net_report_parser = net_subparsers.add_parser(
+        'report',
+        help="Generate network check report"
+    )
+
+    net_report_parser.add_argument(
+        '--html',
+        metavar='network_html_filename',
+        help="HTML filename to save report"
+    )
+
+    return _parser
+
 
 def _prepare_check():
     _checker_class = checker.NetworkChecker()
@@ -11,7 +42,16 @@
     return _checker_class
 
 
+def _prepare_map():
+    _map_class = None
+
+    return _map_class
+
+
 def do_check(args):
+    # Net Checks
+    # should not print map, etc...
+    # Just bare summary and errors
     logger_cli.info("# Network check to console")
     netChecker = _prepare_check()
     netChecker.print_network_report()
@@ -28,7 +68,11 @@
 
 
 def do_report(args):
-    logger_cli.info("# Network report")
+    # Network Report
+    # should generate Static HTML page
+    # with node/network map and values
+
+    logger_cli.info("# Network report (check, node map")
 
     _filename = args_utils.get_arg(args, 'html')
 
@@ -36,3 +80,35 @@
     netChecker.create_html_report(_filename)
 
     return
+
+
+def do_map(args):
+    # Network Map
+    # Should generate network map to console or HTML
+    logger_cli.info("# Network report")
+
+    _type, _filename = args_utils.get_network_map_type_and_filename(args)
+    # networkMap = _prepare_map
+
+    # TODO: Create map class to generate network map
+
+    return
+
+
+def do_ping(args):
+    # Network pinger
+    # Checks if selected nodes are pingable
+    # with a desireble parameters: MTU, Frame, etc
+
+    # TODO: Simple ping based on parameters
+
+    return
+
+
+def do_trace(args):
+    # Network packet tracer
+    # Check if packet is delivered to proper network host
+
+    # TODO: Packet tracer
+
+    return
diff --git a/cfg_checker/modules/network/checker.py b/cfg_checker/modules/network/checker.py
index 48e7acb..7e6a239 100644
--- a/cfg_checker/modules/network/checker.py
+++ b/cfg_checker/modules/network/checker.py
@@ -115,6 +115,11 @@
                 for _ip_str in _ip4s:
                     # create interface class
                     _if = ipaddress.IPv4Interface(_ip_str)
+                    # check if this is a VIP
+                    # ...all those will have /32 mask
+                    net_data['vip'] = None
+                    if _if.network.prefixlen == 32:
+                        net_data['vip'] = str(_if.exploded)
                     if 'name' not in net_data:
                         net_data['name'] = net_name
                     if 'ifs' not in net_data:
@@ -303,47 +308,44 @@
 
                         _name = _host['name']
                         _rc_mtu = _r['mtu'] if 'mtu' in _r else None
-
-                        # Check if this is a VIP
-                        if _if.network.prefixlen == 32:
-                            _name = " "*20
-                            _ip_str += " VIP"
-                            _rc_mtu = "(-)"
-                            _enabled = "(-)"
-                            _r_gate = "-"
-
-                        # Check if this is a default MTU
-                        elif _host['mtu'] == '1500':
-                            # reclass is empty if MTU is untended to be 1500
-                            _rc_mtu = "(-)"
-                        elif _rc_mtu:
-                            _rc_mtu_s = str(_rc_mtu)
-                            # if there is an MTU value, match it
-                            if _host['mtu'] != _rc_mtu_s:
+                        _rc_mtu_s = str(_rc_mtu) if _rc_mtu else '(-)'
+                        # check if this is a VIP address
+                        # no checks needed if yes.
+                        if _host['vip'] != _ip_str:
+                            if _rc_mtu:
+                                # if there is an MTU value, match it
+                                if _host['mtu'] != _rc_mtu_s:
+                                    self.errors.add_error(
+                                        self.errors.NET_MTU_MISMATCH,
+                                        host=hostname,
+                                        if_name=_name,
+                                        if_cidr=_ip_str,
+                                        reclass_mtu=_rc_mtu,
+                                        runtime_mtu=_host['mtu']
+                                    )
+                            elif _host['mtu'] != '1500':
+                                # there is no MTU value in reclass
+                                # and runtime value is not default
                                 self.errors.add_error(
-                                    self.errors.NET_MTU_MISMATCH,
+                                    self.errors.NET_MTU_EMPTY,
                                     host=hostname,
                                     if_name=_name,
                                     if_cidr=_ip_str,
-                                    reclass_mtu=_rc_mtu,
-                                    runtime_mtu=_host['mtu']
+                                    if_mtu=_host['mtu']
                                 )
                         else:
-                            # there is no MTU value in reclass
-                            self.errors.add_error(
-                                self.errors.NET_MTU_EMPTY,
-                                host=hostname,
-                                if_name=_name,
-                                if_cidr=_ip_str,
-                                if_mtu=_host['mtu']
-                            )
+                            # this is a VIP
+                            _name = " "*20
+                            _ip_str += " VIP"
+                            _enabled = "(-)"
+                            _r_gate = "-"
 
                         _text = "{0:25} {1:19} {2:5}{3:10} {4:4}{5:10} " \
                                 "{6} / {7} / {8}".format(
                                     _name,
                                     _ip_str,
                                     _host['mtu'],
-                                    "("+_rc_mtu_s+")" if _rc_mtu else "(No!)",
+                                    _rc_mtu_s,
                                     _host['state'],
                                     _enabled,
                                     _gate,
diff --git a/cfg_checker/modules/packages/__init__.py b/cfg_checker/modules/packages/__init__.py
index 5e717d6..a4a81bd 100644
--- a/cfg_checker/modules/packages/__init__.py
+++ b/cfg_checker/modules/packages/__init__.py
@@ -2,6 +2,35 @@
 
 import checker
 
+command_help = "Package versions check (Candidate vs Installed)"
+
+
+def init_parser(_parser):
+    # packages subparser
+    pkg_subparsers = _parser.add_subparsers(dest='type')
+
+    pkg_report_parser = pkg_subparsers.add_parser(
+        'report',
+        help="Report package versions to HTML file"
+    )
+    pkg_report_parser.add_argument(
+        '--full',
+        action="store_true", default=False,
+        help="HTML report will have all of the packages, not just errors"
+    )
+    pkg_report_parser.add_argument(
+        '--html',
+        metavar='packages_html_filename',
+        help="HTML filename to save report"
+    )
+    pkg_report_parser.add_argument(
+        '--csv',
+        metavar='packages_csv_filename',
+        help="CSV filename to save report"
+    )
+
+    return _parser
+
 
 def do_report(args):
     """Create package versions report, HTML
@@ -9,7 +38,7 @@
     :args: - parser arguments
     :return: - no return value
     """
-    _type, _filename = args_utils.get_report_type_and_filename(args)
+    _type, _filename = args_utils.get_package_report_type_and_filename(args)
 
     # init connection to salt and collect minion data
     pChecker = checker.CloudPackageChecker()
diff --git a/cfg_checker/modules/reclass/__init__.py b/cfg_checker/modules/reclass/__init__.py
index adae6df..4b8b667 100644
--- a/cfg_checker/modules/reclass/__init__.py
+++ b/cfg_checker/modules/reclass/__init__.py
@@ -8,6 +8,45 @@
 
 import validator
 
+command_help = "Reclass related checks and reports"
+
+
+def init_parser(_parser):
+    # reclass subparsers
+    reclass_subparsers = _parser.add_subparsers(dest='type')
+    reclass_list_parser = reclass_subparsers.add_parser(
+        'list',
+        help="List models available to compare"
+    )
+    reclass_list_parser.add_argument(
+        "-p",
+        "--models-path",
+        default="/srv/salt/",
+        help="Global path to search models in"
+    )
+
+    reclass_diff_parser = reclass_subparsers.add_parser(
+        'diff',
+        help="List models available to compare"
+    )
+    reclass_diff_parser.add_argument(
+        "--model1",
+        required=True,
+        help="Model A <path>. Model name is the folder name"
+    )
+    reclass_diff_parser.add_argument(
+        "--model2",
+        required=True,
+        help="Model B <path>. Model name is the folder name"
+    )
+    reclass_diff_parser.add_argument(
+        '--html',
+        metavar='reclass_html_filename',
+        help="HTML filename to save report"
+    )
+
+    return _parser
+
 
 def do_list(args):
     logger_cli.info("# Reclass list")