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
Change-Id: I10bc973776595779b563b84548d46367bcd0886f
Related-PROD: PROD-28199
diff --git a/cfg_checker/cfg_check.py b/cfg_checker/cfg_check.py
index 00e40d2..22125f6 100644
--- a/cfg_checker/cfg_check.py
+++ b/cfg_checker/cfg_check.py
@@ -1,38 +1,17 @@
-import argparse
import os
import sys
-import traceback
from logging import DEBUG, INFO
+from cfg_checker.cli.command import execute_command
+from cfg_checker.cli.network import init_network_parser
+from cfg_checker.cli.package import init_package_parser
+from cfg_checker.cli.reclass import init_reclass_parser
from cfg_checker.common import config, logger, logger_cli
-from cfg_checker.common.exception import CheckerException
-
+from cfg_checker.helpers.args_utils import MyParser
pkg_dir = os.path.dirname(__file__)
pkg_dir = os.path.normpath(pkg_dir)
-commands = {
- 'packages': ['report'],
- 'network': ['check', 'report'],
- 'reclass': ['list', 'diff']
-}
-
-
-class MyParser(argparse.ArgumentParser):
- def error(self, message):
- sys.stderr.write('Error: {0}\n\n'.format(message))
- self.print_help()
-
-
-def help_message():
- print"""
- Please, use following examples to generate info reports:\n
- cfg_checker packages report\n
- cfg_checker network check\n
- cfg_checker network report\n
- """
- return
-
def config_check_entrypoint():
"""
@@ -44,19 +23,6 @@
# Main entrypoint
parser = MyParser(prog="# Mirantis Cloud configuration checker")
- # Parsers (each parser can have own arguments)
- # - subparsers (command)
- # |- pkg_parser
- # | - pkg_subparsers (type)
- # | - pkg_report_parser (default func - pkg_check)
- # |- net_parser
- # | - net_subparsers (type)
- # | - net_check_parser (default func - net_check)
- # | - net_report_parser (default func - net_report)
- # - reclass_parser
- # - reclass_list (default func - reclass_list)
- # - reclass_compare (default func - reclass_diff)
-
parser.add_argument(
"-d",
"--debug",
@@ -70,105 +36,34 @@
help="Use sudo for getting salt creds"
)
subparsers = parser.add_subparsers(dest='command')
- # packages
+
+ # package
pkg_parser = subparsers.add_parser(
'packages',
help="Package versions check (Candidate vs Installed)"
)
- pkg_subparsers = pkg_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"
- )
+ init_package_parser(pkg_parser)
# networking
net_parser = subparsers.add_parser(
'network',
help="Network infrastructure checks and reports"
)
- net_subparsers = net_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"
- )
+ init_network_parser(net_parser)
# reclass
reclass_parser = subparsers.add_parser(
'reclass',
help="Reclass related checks and reports"
)
- reclass_subparsers = reclass_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"
- )
+ init_reclass_parser(reclass_parser)
# parse arguments
try:
args = parser.parse_args()
except TypeError:
logger_cli.info("\n# Please, check arguments")
- return
+ sys.exit(0)
# Pass externally configured values
config.ssh_uses_sudo = args.sudo
@@ -179,51 +74,11 @@
else:
logger_cli.setLevel(INFO)
- # Validate the commands
- # check command
- if args.command not in commands:
- logger_cli.info("\n# Please, type a command listed above")
- return
- elif args.type not in commands[args.command]:
- # check type
- logger_cli.info(
- "\n# Please, select '{}' command type listed above".format(
- args.command
- )
- )
- return
- else:
- # form function name to call
- _method_name = "do_" + args.type
- _target_module = __import__(
- "cfg_checker.modules."+args.command,
- fromlist=[""]
- )
- _method = getattr(_target_module, _method_name)
-
# Execute the command
- result = _method(args)
-
+ result = execute_command(args, args.command)
logger.debug(result)
-
-
-def cli_main():
- try:
- config_check_entrypoint()
- except CheckerException as e:
- logger_cli.error("\nERROR: {}".format(
- e.message
- ))
-
- exc_type, exc_value, exc_traceback = sys.exc_info()
- logger_cli.debug("\n{}".format(
- "".join(traceback.format_exception(
- exc_type,
- exc_value,
- exc_traceback
- ))
- ))
+ sys.exit(result)
if __name__ == '__main__':
- cli_main()
+ config_check_entrypoint()
diff --git a/cfg_checker/cli/command.py b/cfg_checker/cli/command.py
new file mode 100644
index 0000000..0a9c2a2
--- /dev/null
+++ b/cfg_checker/cli/command.py
@@ -0,0 +1,59 @@
+import sys
+import traceback
+
+from cfg_checker.common import logger_cli
+from cfg_checker.common.exception import CheckerException
+
+# TODO: auto-create types for each command
+package_command_types = ['report']
+network_command_types = ['check', 'report']
+reclass_command_types = ['list', 'diff']
+
+commands = {
+ 'packages': package_command_types,
+ 'network': network_command_types,
+ 'reclass': reclass_command_types
+}
+
+
+def execute_command(args, command):
+ # Validate the commands
+ # check command
+ if command not in commands:
+ logger_cli.info("\n# Please, type a command listed above")
+ return 0
+ elif args.type not in commands[command]:
+ # check type
+ logger_cli.info(
+ "\n# Please, select '{}' command type listed above".format(
+ command
+ )
+ )
+ return 0
+ else:
+ # form function name to call
+ _method_name = "do_" + args.type
+ _target_module = __import__(
+ "cfg_checker.modules."+command,
+ fromlist=[""]
+ )
+ _method = getattr(_target_module, _method_name)
+
+ # Execute the command
+ try:
+ _method(args)
+ return 0
+ except CheckerException as e:
+ logger_cli.error("\nERROR: {}".format(
+ e.message
+ ))
+
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ logger_cli.debug("\n{}".format(
+ "".join(traceback.format_exception(
+ exc_type,
+ exc_value,
+ exc_traceback
+ ))
+ ))
+ return 1
diff --git a/cfg_checker/cli/network.py b/cfg_checker/cli/network.py
index 4d6d0c4..b53311d 100644
--- a/cfg_checker/cli/network.py
+++ b/cfg_checker/cli/network.py
@@ -1,14 +1,59 @@
-from cfg_checker.modules.network.checker import NetworkChecker
+import sys
+
+from cfg_checker.common import config, logger, logger_cli
+from cfg_checker.helpers.args_utils import MyParser
+
+from command import execute_command
+
+
+def init_network_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 cli_network():
+ net_parser = MyParser("# Mirantis Cloud Network checker")
+ init_network_parser(net_parser)
+
+ # parse arguments
+ try:
+ args = net_parser.parse_args()
+ except TypeError:
+ logger_cli.info("\n# Please, check arguments")
+ sys.exit(0)
+
+ # force use of sudo
+ config.ssh_uses_sudo = True
+
+ # Execute the command
+ result = execute_command(args, 'network')
+ logger.debug(result)
+ sys.exit(result)
+
if __name__ == '__main__':
- # init connection to salt and collect minion data
- cl = NetworkChecker()
-
- # collect data on installed packages
- cl.collect_network_info()
-
- # diff installed and candidates
- # cl.collect_packages()
-
- # report it
- cl.create_html_report("./pkg_versions.html")
+ cli_network()
diff --git a/cfg_checker/cli/package.py b/cfg_checker/cli/package.py
index 74166be..5fe7e0b 100644
--- a/cfg_checker/cli/package.py
+++ b/cfg_checker/cli/package.py
@@ -1,15 +1,57 @@
-from cfg_checker.modules.packages.checker import CloudPackageChecker
+import sys
+
+from cfg_checker.common import config, logger, logger_cli
+from cfg_checker.helpers.args_utils import MyParser
+
+from command import execute_command
+
+
+def init_package_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 cli_package():
+ pkg_parser = MyParser("# Mirantis Cloud Package checker")
+ init_package_parser(pkg_parser)
+
+ # parse arguments
+ try:
+ args = pkg_parser.parse_args()
+ except TypeError:
+ logger_cli.info("\n# Please, check arguments")
+ sys.exit(0)
+
+ # force use of sudo
+ config.ssh_uses_sudo = True
+
+ # Execute the command
+ result = execute_command(args, 'packages')
+ logger.debug(result)
+ sys.exit(result)
if __name__ == '__main__':
- # init connection to salt and collect minion data
- cl = CloudPackageChecker()
-
- # collect data on installed packages
- cl.collect_installed_packages()
-
- # diff installed and candidates
- # cl.collect_packages()
-
- # report it
- cl.create_html_report("./pkg_versions.html")
+ cli_package()
diff --git a/cfg_checker/cli/reclass.py b/cfg_checker/cli/reclass.py
index 2959bf3..fc5961a 100644
--- a/cfg_checker/cli/reclass.py
+++ b/cfg_checker/cli/reclass.py
@@ -1,6 +1,67 @@
-from cfg_checker.modules.reclass.comparer import ModelComparer
+import sys
+
+from cfg_checker.common import config, logger, logger_cli
+from cfg_checker.helpers.args_utils import MyParser
+
+from command import execute_command
+
+
+def init_reclass_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 cli_reclass():
+ net_parser = MyParser("# Mirantis Cloud Reclass comparer")
+ init_reclass_parser(net_parser)
+
+ # parse arguments
+ try:
+ args = net_parser.parse_args()
+ except TypeError:
+ logger_cli.info("\n# Please, check arguments")
+ sys.exit(0)
+
+ # force use of sudo
+ config.ssh_uses_sudo = True
+
+ # Execute the command
+ result = execute_command(args, 'reclass')
+ logger.debug(result)
+ sys.exit(result)
+
if __name__ == "__main__":
- # Execute the comparison using argv params
-
- pass
+ cli_reclass()
diff --git a/cfg_checker/helpers/args_utils.py b/cfg_checker/helpers/args_utils.py
index f8453e4..daf55b1 100644
--- a/cfg_checker/helpers/args_utils.py
+++ b/cfg_checker/helpers/args_utils.py
@@ -1,8 +1,16 @@
+import argparse
import os
+import sys
from cfg_checker.common.exception import ConfigException
+class MyParser(argparse.ArgumentParser):
+ def error(self, message):
+ sys.stderr.write('Error: {0}\n\n'.format(message))
+ self.print_help()
+
+
def get_arg(args, str_arg):
_attr = getattr(args, str_arg)
if _attr:
diff --git a/cfg_checker/modules/packages/versions.py b/cfg_checker/modules/packages/versions.py
index 9352dd6..9737d80 100644
--- a/cfg_checker/modules/packages/versions.py
+++ b/cfg_checker/modules/packages/versions.py
@@ -1,7 +1,8 @@
import csv
import os
-from cfg_checker.common import config, const, logger_cli, pkg_dir
+from cfg_checker.common import config, const, logger_cli
+from cfg_checker.common.settings import pkg_dir
class PkgVersions(object):
diff --git a/cover.sh b/cover.sh
new file mode 100644
index 0000000..49c73e5
--- /dev/null
+++ b/cover.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+coverage run --source cfg_checker setup.py test && coverage report
diff --git a/requirements-test.txt b/requirements-test.txt
new file mode 100644
index 0000000..9cd0e0a
--- /dev/null
+++ b/requirements-test.txt
@@ -0,0 +1,3 @@
+six
+pyyaml
+coverage
diff --git a/setup.py b/setup.py
index 42ca20f..248ceb2 100644
--- a/setup.py
+++ b/setup.py
@@ -23,10 +23,10 @@
entry_points = {
"console_scripts": [
- "mcp-checker = cfg_checker.cfg_check:cli_main",
- "package-report = cfg_checker.cli.package",
- "network-check = cfg_checker.cli.network",
- "reclass-compare = cfg_checker.cli.reclass"
+ "mcp-checker = cfg_checker.cfg_check:config_check_entrypoint",
+ "mcp-checker-package = cfg_checker.cli.package:cli_package",
+ "mcp-checker-network = cfg_checker.cli.network:cli_network",
+ "mcp-checker-reclass = cfg_checker.cli.reclass:cli_reclass"
]
}
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/test_base.py b/tests/test_base.py
new file mode 100644
index 0000000..c4a627f
--- /dev/null
+++ b/tests/test_base.py
@@ -0,0 +1,29 @@
+import contextlib
+import io
+import sys
+import unittest
+
+
+class CfgCheckerTestBase(unittest.TestCase):
+ dummy_base_var = 0
+
+ def _safe_import_module(self, _str):
+ _import_msg = ""
+ _module = None
+
+ try:
+ _module = __import__(_str)
+ except ImportError as e:
+ _import_msg = e.message
+
+ return _import_msg, _module
+
+ @contextlib.contextmanager
+ def redirect_output(self):
+ save_stdout = sys.stdout
+ save_stderr = sys.stderr
+ sys.stdout = io.BytesIO()
+ sys.stderr = io.BytesIO()
+ yield
+ sys.stdout = save_stdout
+ sys.stderr = save_stderr
diff --git a/tests/test_entrypoints.py b/tests/test_entrypoints.py
new file mode 100644
index 0000000..0b87e6d
--- /dev/null
+++ b/tests/test_entrypoints.py
@@ -0,0 +1,99 @@
+from test_base import CfgCheckerTestBase
+
+
+class TestEntrypoints(CfgCheckerTestBase):
+ def test_entry_mcp_checker(self):
+ _module_name = 'cfg_checker.cfg_check'
+ with self.redirect_output():
+ _msg, _m = self._safe_import_module(_module_name)
+
+ self.assertEqual(
+ len(_msg),
+ 0,
+ "Error importing '{}': {}".format(
+ _module_name,
+ _msg
+ )
+ )
+
+ with self.redirect_output():
+ with self.assertRaises(SystemExit) as ep:
+ _m.cfg_check.config_check_entrypoint()
+
+ self.assertEqual(
+ ep.exception.code,
+ 0,
+ "mcp-checker has non-zero exit-code"
+ )
+
+ def test_entry_packages(self):
+ _module_name = 'cfg_checker.cli.package'
+ with self.redirect_output():
+ _msg, _m = self._safe_import_module(_module_name)
+
+ self.assertEqual(
+ len(_msg),
+ 0,
+ "Error importing '{}': {}".format(
+ _module_name,
+ _msg
+ )
+ )
+
+ with self.redirect_output():
+ with self.assertRaises(SystemExit) as ep:
+ _m.cli.package.cli_package()
+
+ self.assertEqual(
+ ep.exception.code,
+ 0,
+ "packages has non-zero exit code"
+ )
+
+ def test_entry_network(self):
+ _module_name = 'cfg_checker.cli.network'
+ with self.redirect_output():
+ _msg, _m = self._safe_import_module(_module_name)
+
+ self.assertEqual(
+ len(_msg),
+ 0,
+ "Error importing '{}': {}".format(
+ _module_name,
+ _msg
+ )
+ )
+
+ with self.redirect_output():
+ with self.assertRaises(SystemExit) as ep:
+ _m.cli.network.cli_network()
+
+ self.assertEqual(
+ ep.exception.code,
+ 0,
+ "network has non-zero exit-code"
+ )
+
+ def test_entry_reclass(self):
+ _module_name = 'cfg_checker.cli.reclass'
+ with self.redirect_output():
+ _msg, _m = self._safe_import_module(_module_name)
+
+ self.assertEqual(
+ len(_msg),
+ 0,
+ "Error importing '{}': {}".format(
+ _module_name,
+ _msg
+ )
+ )
+
+ with self.redirect_output():
+ with self.assertRaises(SystemExit) as ep:
+ _m.cli.reclass.cli_reclass()
+
+ self.assertEqual(
+ ep.exception.code,
+ 0,
+ "reclass has non-zero exit-code"
+ )