diff --git a/rally/benchmark/scenarios/base.py b/rally/benchmark/scenarios/base.py index 2327b863b5..5e02b5fb68 100644 --- a/rally/benchmark/scenarios/base.py +++ b/rally/benchmark/scenarios/base.py @@ -73,8 +73,6 @@ class Scenario(object): @staticmethod def get_by_name(name): """Returns Scenario class by name.""" - # TODO(msdubov): support approximate string matching - # (here and in other base classes). for scenario in utils.itersubclasses(Scenario): if name == scenario.__name__: return scenario diff --git a/rally/benchmark/sla/base.py b/rally/benchmark/sla/base.py index a0421f38cf..9cc3bfcfa1 100644 --- a/rally/benchmark/sla/base.py +++ b/rally/benchmark/sla/base.py @@ -25,6 +25,7 @@ import jsonschema import six from rally.benchmark.processing import utils as putils +from rally import exceptions from rally.i18n import _ from rally import utils @@ -81,6 +82,14 @@ class SLA(object): 'detail': check_result.msg}) return results + @staticmethod + def get_by_name(name): + """Returns SLA by name.""" + for sla in utils.itersubclasses(SLA): + if name == sla.__name__: + return sla + raise exceptions.NoSuchSLA(name=name) + class FailureRate(SLA): """Failure rate in percents.""" diff --git a/rally/cmd/cliutils.py b/rally/cmd/cliutils.py index ace08ff042..3b07d16eca 100644 --- a/rally/cmd/cliutils.py +++ b/rally/cmd/cliutils.py @@ -104,19 +104,6 @@ def args(*args, **kwargs): return _decorator -def _get_doc(cls): - """Get the dynamic docstring of a class. - - Return the usual docstring stored in __doc__ if no dynamic one exists. - - :returns: docstring - """ - if hasattr(cls, "__get__doc__"): - return cls().__get__doc__() - else: - return cls.__doc__ - - def _methods_of(cls): """Get all callable methods of a class that don't start with underscore. @@ -132,7 +119,7 @@ def _compose_category_description(category): descr_pairs = _methods_of(category) description = "" - doc = _get_doc(category) + doc = category.__doc__ if doc: description = doc.strip() if descr_pairs: diff --git a/rally/cmd/commands/info.py b/rally/cmd/commands/info.py index bfbcd81104..b78d823ae0 100644 --- a/rally/cmd/commands/info.py +++ b/rally/cmd/commands/info.py @@ -50,6 +50,7 @@ Samples: from __future__ import print_function from rally.benchmark.scenarios import base as scenario_base +from rally.benchmark.sla import base as sla_base from rally.cmd import cliutils from rally import deploy from rally.deploy import serverprovider @@ -60,14 +61,18 @@ from rally import utils class InfoCommands(object): """This command allows you to get quick doc of some rally entities. - Available for scenario groups, scenarios, deployment engines and + Available for scenario groups, scenarios, SLA, deploy engines and server providers. - """ - def __get__doc__(self): - doc = "Usage:\n\n $ rally info find \n\n" - doc += "Possible queries:\n\n" + self._list() - return doc + Usage: + $ rally info find + + To see lists of entities you can query docs for, type one of the following: + $ rally info BenchmarkScenarios + $ rally info SLA + $ rally info DeployEngines + $ rally info ServerProviders + """ @cliutils.args("--query", dest="query", type=str, help="Search query.") def find(self, query): @@ -81,8 +86,8 @@ class InfoCommands(object): if info: print(info) else: - print("Failed to find any docs for query: '%s'" % query) substitutions = self._find_substitution(query) + print("Failed to find any docs for query: '%s'" % query) if substitutions: print("Did you mean one of these?\n\t%s" % "\n\t".join(substitutions)) @@ -93,36 +98,51 @@ class InfoCommands(object): Lists benchmark scenario groups, deploy engines and server providers. """ - print(self._list()) + self.BenchmarkScenarios() + self.SLA() + self.DeployEngines() + self.ServerProviders() - def _list(self): - base_classes = {"scenario_groups": scenario_base.Scenario, - "deploy_engines": deploy.EngineFactory, - "server_providers": serverprovider.ProviderFactory} - descriptions = {"scenario_groups": [], - "deploy_engines": [], - "server_providers": []} - for entity_type in base_classes: - for entity in utils.itersubclasses(base_classes[entity_type]): - name = entity.__name__ - doc = utils.parse_docstring(entity.__doc__) - description = doc["short_description"] or "" - descriptions[entity_type].append((name, description)) - - info = self._compose_table("Benchmark scenario groups", - descriptions["scenario_groups"]) + def BenchmarkScenarios(self): + """List benchmark scenarios available in Rally.""" + scenarios = self._get_descriptions(scenario_base.Scenario) + info = self._compose_table("Benchmark scenario groups", scenarios) info += (" To get information about benchmark scenarios inside " "each scenario group, run:\n" " $ rally info find \n\n") - info += self._compose_table("Deploy engines", - descriptions["deploy_engines"]) - info += self._compose_table("Server providers", - descriptions["server_providers"]) - return info + print(info) + + def SLA(self): + """List server providers available in Rally.""" + sla = self._get_descriptions(sla_base.SLA) + info = self._compose_table("SLA", sla) + print(info) + + def DeployEngines(self): + """List deploy engines available in Rally.""" + engines = self._get_descriptions(deploy.EngineFactory) + info = self._compose_table("Deploy engines", engines) + print(info) + + def ServerProviders(self): + """List server providers available in Rally.""" + providers = self._get_descriptions(serverprovider.ProviderFactory) + info = self._compose_table("Server providers", providers) + print(info) + + def _get_descriptions(self, base_cls): + descriptions = [] + for entity in utils.itersubclasses(base_cls): + name = entity.__name__ + doc = utils.parse_docstring(entity.__doc__) + description = doc["short_description"] or "" + descriptions.append((name, description)) + return descriptions def _find_info(self, query): return (self._get_scenario_group_info(query) or self._get_scenario_info(query) or + self._get_sla_info(query) or self._get_deploy_engine_info(query) or self._get_server_provider_info(query)) @@ -200,6 +220,15 @@ class InfoCommands(object): except exceptions.NoSuchScenario: return None + def _get_sla_info(self, query): + try: + sla = sla_base.SLA.get_by_name(query) + info = "%s (SLA).\n\n" % sla.__name__ + info += utils.format_docstring(sla.__doc__) + return info + except exceptions.NoSuchSLA: + return None + def _get_deploy_engine_info(self, query): try: deploy_engine = deploy.EngineFactory.get_by_name(query) diff --git a/rally/exceptions.py b/rally/exceptions.py index 9e27e4a4c0..ead0648edb 100644 --- a/rally/exceptions.py +++ b/rally/exceptions.py @@ -136,6 +136,10 @@ class NoSuchContext(NotFoundException): msg_fmt = _("There is no benchmark context with name `%(name)s`.") +class NoSuchSLA(NotFoundException): + msg_fmt = _("There is no SLA with name `%(name)s`.") + + class NoSuchConfigField(NotFoundException): msg_fmt = _("There is no field in the task config with name `%(name)s`.") diff --git a/tests/functional/test_cli_info.py b/tests/functional/test_cli_info.py index 8e0aa45068..f006b0f03f 100644 --- a/tests/functional/test_cli_info.py +++ b/tests/functional/test_cli_info.py @@ -38,6 +38,9 @@ class InfoTestCase(unittest.TestCase): def test_find_scenario(self): self.assertIn("(benchmark scenario)", self.rally("info find dummy")) + def test_find_sla(self): + self.assertIn("(SLA)", self.rally("info find FailureRate")) + def test_find_deployment_engine(self): marker_string = "ExistingCloud (deploy engine)." self.assertIn(marker_string, self.rally("info find ExistingCloud")) @@ -68,18 +71,29 @@ class InfoTestCase(unittest.TestCase): output = self.rally("info list") self.assertIn("Benchmark scenario groups:", output) self.assertIn("NovaServers", output) + self.assertIn("SLA:", output) + self.assertIn("FailureRate", output) self.assertIn("Deploy engines:", output) self.assertIn("ExistingCloud", output) self.assertIn("Server providers:", output) self.assertIn("ExistingServers", output) - def test_list_shorthand(self): - try: - self.rally("info") - except utils.RallyCmdError as e: - self.assertIn("Benchmark scenario groups:", e.output) - self.assertIn("NovaServers", e.output) - self.assertIn("Deploy engines:", e.output) - self.assertIn("ExistingCloud", e.output) - self.assertIn("Server providers:", e.output) - self.assertIn("ExistingServers", e.output) + def test_BenchmarkScenarios(self): + output = self.rally("info BenchmarkScenarios") + self.assertIn("Benchmark scenario groups:", output) + self.assertIn("NovaServers", output) + + def test_SLA(self): + output = self.rally("info SLA") + self.assertIn("SLA:", output) + self.assertIn("FailureRate", output) + + def test_DeployEngines(self): + output = self.rally("info DeployEngines") + self.assertIn("Deploy engines:", output) + self.assertIn("ExistingCloud", output) + + def test_ServerProviders(self): + output = self.rally("info ServerProviders") + self.assertIn("Server providers:", output) + self.assertIn("ExistingServers", output) diff --git a/tests/unit/cmd/commands/test_info.py b/tests/unit/cmd/commands/test_info.py index 9bee35a340..2fcb8b7d8d 100644 --- a/tests/unit/cmd/commands/test_info.py +++ b/tests/unit/cmd/commands/test_info.py @@ -17,6 +17,7 @@ import mock from rally.benchmark.scenarios import base as scenario_base from rally.benchmark.scenarios.dummy import dummy +from rally.benchmark.sla import base as sla_base from rally.cmd.commands import info from rally import deploy from rally.deploy.engines import existing as existing_cloud @@ -27,6 +28,7 @@ from tests.unit import test SCENARIO = "rally.cmd.commands.info.scenario_base.Scenario" +SLA = "rally.cmd.commands.info.sla_base.SLA" ENGINE = "rally.cmd.commands.info.deploy.EngineFactory" PROVIDER = "rally.cmd.commands.info.serverprovider.ProviderFactory" UTILS = "rally.cmd.commands.info.utils" @@ -61,6 +63,13 @@ class InfoCommandsTestCase(test.TestCase): mock_get_scenario_by_name.assert_called_once_with(query) self.assertEqual(1, status) + @mock.patch(SLA + ".get_by_name", return_value=sla_base.FailureRate) + def test_find_failure_rate_sla(self, mock_get_by_name): + query = "FailureRate" + status = self.info.find(query) + mock_get_by_name.assert_called_once_with(query) + self.assertEqual(None, status) + @mock.patch(ENGINE + ".get_by_name", return_value=existing_cloud.ExistingCloud) def test_find_existing_cloud(self, mock_get_by_name): @@ -82,6 +91,32 @@ class InfoCommandsTestCase(test.TestCase): status = self.info.list() mock_itersubclasses.assert_has_calls([ mock.call(scenario_base.Scenario), + mock.call(sla_base.SLA), mock.call(deploy.EngineFactory), mock.call(serverprovider.ProviderFactory)]) self.assertEqual(None, status) + + @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) + def test_BenchmarkScenarios(self, mock_itersubclasses): + status = self.info.BenchmarkScenarios() + mock_itersubclasses.assert_called_once_with(scenario_base.Scenario) + self.assertEqual(None, status) + + @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) + def test_SLA(self, mock_itersubclasses): + status = self.info.SLA() + mock_itersubclasses.assert_called_once_with(sla_base.SLA) + self.assertEqual(None, status) + + @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) + def test_DeployEngines(self, mock_itersubclasses): + status = self.info.DeployEngines() + mock_itersubclasses.assert_called_once_with(deploy.EngineFactory) + self.assertEqual(None, status) + + @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) + def test_ServerProviders(self, mock_itersubclasses): + status = self.info.ServerProviders() + mock_itersubclasses.assert_called_once_with( + serverprovider.ProviderFactory) + self.assertEqual(None, status) diff --git a/tools/rally.bash_completion b/tools/rally.bash_completion index af84cae65e..c050ab5eb3 100644 --- a/tools/rally.bash_completion +++ b/tools/rally.bash_completion @@ -6,6 +6,10 @@ _rally() declare -A SUBCOMMANDS declare -A OPTS + OPTS["info_BenchmarkScenarios"]="" + OPTS["info_DeployEngines"]="" + OPTS["info_SLA"]="" + OPTS["info_ServerProviders"]="" OPTS["info_find"]="--query" OPTS["info_list"]="" OPTS["use_deployment"]="--uuid --name"