diff --git a/watcher/cmd/api.py b/watcher/cmd/api.py index cb86df082..d3730010b 100644 --- a/watcher/cmd/api.py +++ b/watcher/cmd/api.py @@ -21,12 +21,9 @@ import sys from oslo_config import cfg from oslo_log import log as logging -from oslo_reports import guru_meditation_report as gmr -from watcher._i18n import _ +from watcher._i18n import _LI from watcher.common import service -from watcher import version - LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -35,8 +32,6 @@ CONF = cfg.CONF def main(): service.prepare_service(sys.argv) - gmr.TextGuruMeditation.setup_autorun(version) - host, port = cfg.CONF.api.host, cfg.CONF.api.port protocol = "http" if not CONF.api.enable_ssl_api else "https" # Build and start the WSGI app @@ -44,11 +39,11 @@ def main(): 'watcher-api', CONF.api.enable_ssl_api) if host == '0.0.0.0': - LOG.info(_('serving on 0.0.0.0:%(port)s, ' - 'view at %(protocol)s://127.0.0.1:%(port)s') % + LOG.info(_LI('serving on 0.0.0.0:%(port)s, ' + 'view at %(protocol)s://127.0.0.1:%(port)s') % dict(protocol=protocol, port=port)) else: - LOG.info(_('serving on %(protocol)s://%(host)s:%(port)s') % + LOG.info(_LI('serving on %(protocol)s://%(host)s:%(port)s') % dict(protocol=protocol, host=host, port=port)) launcher = service.process_launcher() diff --git a/watcher/cmd/applier.py b/watcher/cmd/applier.py index fd6eb2463..ec45e2d2f 100644 --- a/watcher/cmd/applier.py +++ b/watcher/cmd/applier.py @@ -22,13 +22,11 @@ import sys from oslo_config import cfg from oslo_log import log as logging -from oslo_reports import guru_meditation_report as gmr from oslo_service import service from watcher._i18n import _LI from watcher.applier import manager from watcher.common import service as watcher_service -from watcher import version LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -36,7 +34,6 @@ CONF = cfg.CONF def main(): watcher_service.prepare_service(sys.argv) - gmr.TextGuruMeditation.setup_autorun(version) LOG.info(_LI('Starting Watcher Applier service in PID %s'), os.getpid()) diff --git a/watcher/cmd/decisionengine.py b/watcher/cmd/decisionengine.py index 8bb747496..00c21f8b9 100644 --- a/watcher/cmd/decisionengine.py +++ b/watcher/cmd/decisionengine.py @@ -22,14 +22,12 @@ import sys from oslo_config import cfg from oslo_log import log as logging -from oslo_reports import guru_meditation_report as gmr from oslo_service import service from watcher._i18n import _LI from watcher.common import service as watcher_service from watcher.decision_engine import manager from watcher.decision_engine import sync -from watcher import version LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -37,7 +35,6 @@ CONF = cfg.CONF def main(): watcher_service.prepare_service(sys.argv) - gmr.TextGuruMeditation.setup_autorun(version) LOG.info(_LI('Starting Watcher Decision Engine service in PID %s'), os.getpid()) diff --git a/watcher/common/service.py b/watcher/common/service.py index 1bd0bb06d..54159e266 100644 --- a/watcher/common/service.py +++ b/watcher/common/service.py @@ -22,6 +22,7 @@ from oslo_config import cfg from oslo_log import _options from oslo_log import log import oslo_messaging as om +from oslo_reports import guru_meditation_report as gmr from oslo_reports import opts as gmr_opts from oslo_service import service from oslo_service import wsgi @@ -33,6 +34,8 @@ from watcher.common.messaging.events import event_dispatcher as dispatcher from watcher.common.messaging import messaging_handler from watcher.common import rpc from watcher.objects import base +from watcher import opts +from watcher import version service_opts = [ cfg.IntOpt('periodic_interval', @@ -219,3 +222,6 @@ def prepare_service(argv=(), conf=cfg.CONF): default_log_levels=_DEFAULT_LOG_LEVELS) log.setup(conf, 'python-watcher') conf.log_opt_values(LOG, logging.DEBUG) + + gmr.TextGuruMeditation.register_section(_('Plugins'), opts.show_plugins) + gmr.TextGuruMeditation.setup_autorun(version) diff --git a/watcher/common/utils.py b/watcher/common/utils.py index 5673d3466..c77b4302e 100644 --- a/watcher/common/utils.py +++ b/watcher/common/utils.py @@ -118,6 +118,14 @@ def is_hostname_safe(hostname): :returns: True if valid. False if not. """ - m = '^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$' + m = r'^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$' return (isinstance(hostname, six.string_types) and (re.match(m, hostname) is not None)) + + +def get_cls_import_path(cls): + """Return the import path of a given class""" + module = cls.__module__ + if module is None or module == str.__module__: + return cls.__name__ + return module + '.' + cls.__name__ diff --git a/watcher/opts.py b/watcher/opts.py index 587cf3f38..e280523c4 100644 --- a/watcher/opts.py +++ b/watcher/opts.py @@ -16,6 +16,7 @@ # limitations under the License. from keystoneauth1 import loading as ka_loading +import prettytable as ptable import watcher.api.app from watcher.applier.actions.loading import default as action_loader @@ -23,6 +24,7 @@ from watcher.applier import manager as applier_manager from watcher.applier.workflow_engine.loading import default as \ workflow_engine_loader from watcher.common import clients +from watcher.common import utils from watcher.decision_engine import manager as decision_engine_manger from watcher.decision_engine.planner.loading import default as planner_loader from watcher.decision_engine.planner import manager as planner_manager @@ -73,3 +75,26 @@ def list_plugin_opts(): (plugin_loader.get_entry_name(plugin_name), plugin_opts)) return plugins_opts + + +def _show_plugins_ascii_table(rows): + headers = ["Namespace", "Plugin name", "Import path"] + table = ptable.PrettyTable(field_names=headers) + for row in rows: + table.add_row(row) + return table.get_string() + + +def show_plugins(): + rows = [] + for plugin_loader_cls in PLUGIN_LOADERS: + plugin_loader = plugin_loader_cls() + plugins_map = plugin_loader.list_available() + + rows += [ + (plugin_loader.get_entry_name(plugin_name), + plugin_name, + utils.get_cls_import_path(plugin_cls)) + for plugin_name, plugin_cls in plugins_map.items()] + + return _show_plugins_ascii_table(rows) diff --git a/watcher/tests/test_list_opts.py b/watcher/tests/test_list_opts.py index 967251588..305f2678a 100644 --- a/watcher/tests/test_list_opts.py +++ b/watcher/tests/test_list_opts.py @@ -108,3 +108,38 @@ class TestListOpts(base.TestCase): strategy_opts = result_map['watcher_strategies.STRATEGY_1'] self.assertEqual(['test_opt'], [opt.name for opt in strategy_opts]) + + +class TestPlugins(base.TestCase): + + def test_show_plugins(self): + # Set up the fake Stevedore extensions + fake_extmanager_call = extension.ExtensionManager.make_test_instance( + extensions=[extension.Extension( + name=fake_strategies.FakeDummy1Strategy1.get_name(), + entry_point="%s:%s" % ( + fake_strategies.FakeDummy1Strategy1.__module__, + fake_strategies.FakeDummy1Strategy1.__name__), + plugin=fake_strategies.FakeDummy1Strategy1, + obj=None, + )], + namespace="watcher_strategies", + ) + + def m_list_available(namespace): + if namespace == "watcher_strategies": + return fake_extmanager_call + else: + return extension.ExtensionManager.make_test_instance( + extensions=[], namespace=namespace) + + with mock.patch.object(extension, "ExtensionManager") as m_ext_manager: + with mock.patch.object( + opts, "_show_plugins_ascii_table" + ) as m_show: + m_ext_manager.side_effect = m_list_available + opts.show_plugins() + m_show.assert_called_once_with( + [('watcher_strategies.STRATEGY_1', 'STRATEGY_1', + 'watcher.tests.decision_engine.' + 'fake_strategies.FakeDummy1Strategy1')])