diff --git a/rally/benchmark/scenarios/base.py b/rally/benchmark/scenarios/base.py index 5e02b5fb68..d26b83de4d 100644 --- a/rally/benchmark/scenarios/base.py +++ b/rally/benchmark/scenarios/base.py @@ -176,8 +176,11 @@ class Scenario(object): :param method_name: method name :returns: True if the method is a benchmark scenario, False otherwise """ - return (hasattr(cls, method_name) and - Scenario.meta(cls, "is_scenario", method_name, default=False)) + try: + getattr(cls, method_name) + except Exception: + return False + return Scenario.meta(cls, "is_scenario", method_name, default=False) def context(self): """Returns the context of the current benchmark scenario.""" diff --git a/rally/benchmark/sla/base.py b/rally/benchmark/sla/base.py index c26e213303..9581fdb774 100644 --- a/rally/benchmark/sla/base.py +++ b/rally/benchmark/sla/base.py @@ -84,15 +84,15 @@ class SLA(object): @staticmethod def get_by_name(name): - """Returns SLA by name.""" + """Returns SLA by name or config option name.""" for sla in utils.itersubclasses(SLA): - if name == sla.__name__: + if name == sla.__name__ or name == sla.OPTION_NAME: return sla raise exceptions.NoSuchSLA(name=name) class FailureRateDeprecated(SLA): - """Failure rate in percents.""" + """[Deprecated] Failure rate in percents.""" OPTION_NAME = "max_failure_percent" CONFIG_SCHEMA = {"type": "number", "minimum": 0.0, "maximum": 100.0} diff --git a/rally/cmd/cliutils.py b/rally/cmd/cliutils.py index efaffafbae..e98f39c14b 100644 --- a/rally/cmd/cliutils.py +++ b/rally/cmd/cliutils.py @@ -296,7 +296,7 @@ def run(argv, categories): validate_deprecated_args(argv, fn) ret = fn(*fn_args, **fn_kwargs) return(ret) - except IOError as e: + except (IOError, TypeError) as e: if CONF.debug: raise print(e) diff --git a/rally/cmd/commands/info.py b/rally/cmd/commands/info.py index 31f3400e1f..3931a2fd29 100644 --- a/rally/cmd/commands/info.py +++ b/rally/cmd/commands/info.py @@ -67,10 +67,11 @@ class InfoCommands(object): Usage: $ rally info find - To see lists of entities you can query docs for, type one of the following: + To get information about main concepts of Rally as well as to list entities + you can query docs for, type one of the following: $ rally info BenchmarkScenarios $ rally info SLA - $ rally info DeployEngines + $ rally info DeploymentEngines $ rally info ServerProviders """ @@ -103,43 +104,154 @@ class InfoCommands(object): """ self.BenchmarkScenarios() self.SLA() - self.DeployEngines() + self.DeploymentEngines() self.ServerProviders() 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") + """Get information about benchmark scenarios available in Rally.""" + def scenarios_filter(scenario_cls): + return any(scenario_base.Scenario.is_scenario(scenario_cls, m) + for m in dir(scenario_cls)) + scenarios = self._get_descriptions(scenario_base.Scenario, + scenarios_filter) + info = (self._make_header("Rally - Benchmark scenarios") + + "\n\n" + "Benchmark scenarios are what Rally actually uses to test " + "the performance of an OpenStack deployment.\nEach Benchmark " + "scenario implements a sequence of atomic operations " + "(server calls) to simulate\ninteresing user/operator/" + "client activity in some typical use case, usually that of " + "a specific OpenStack\nproject. Iterative execution of this " + "sequence produces some kind of load on the target cloud.\n" + "Benchmark scenarios play the role of building blocks in " + "benchmark task configuration files." + "\n\n" + "Scenarios in Rally are put together in groups. Each " + "scenario group is concentrated on some specific \nOpenStack " + 'functionality. For example, the "NovaServers" scenario ' + "group contains scenarios that employ\nseveral basic " + "operations available in Nova." + "\n\n" + + self._compose_table("List of Benchmark scenario groups", + scenarios) + + "To get information about benchmark scenarios inside " + "each scenario group, run:\n" + " $ rally info find \n\n") print(info) def SLA(self): - """List server providers available in Rally.""" + """Get information about SLA available in Rally.""" sla = self._get_descriptions(sla_base.SLA) - info = self._compose_table("SLA", sla) + # NOTE(msdubov): Add config option names to the "Name" column + for i in range(len(sla)): + description = sla[i] + sla_cls = sla_base.SLA.get_by_name(description[0]) + sla[i] = (sla_cls.OPTION_NAME, description[1]) + info = (self._make_header("Rally - SLA checks " + "(Service-Level Agreements)") + + "\n\n" + "SLA in Rally enable quick and easy checks of " + "whether the results of a particular\nbenchmark task have " + "passed certain success criteria." + "\n\n" + "SLA checks can be configured in the 'sla' section of " + "benchmark task configuration\nfiles, used to launch new " + "tasks by the 'rally task start ' command.\n" + "For each SLA check you would like to use, you should put " + "its name as a key and the\ntarget check parameter as an " + "assosiated value, e.g.:\n\n" + " sla:\n" + " max_seconds_per_iteration: 4\n" + " max_failure_percent: 1" + "\n\n" + + self._compose_table("List of SLA checks", sla) + + "To get information about specific SLA checks, run:\n" + " $ rally info find \n") + print(info) + + def DeploymentEngines(self): + """Get information about deploy engines available in Rally.""" + engines = self._get_descriptions(deploy.EngineFactory) + info = (self._make_header("Rally - Deployment engines") + + "\n\n" + "Rally is an OpenStack benchmarking system. Before starting " + "benchmarking with Rally,\nyou obviously have either to " + "deploy a new OpenStack cloud or to register an existing\n" + "one in Rally. Deployment engines in Rally are essentially " + "plugins that control the\nprocess of deploying some " + "OpenStack distribution, say, with DevStack or FUEL, and\n" + "register these deployments in Rally before any benchmarking " + "procedures against them\ncan take place." + "\n\n" + "A typical use case in Rally would be when you first " + "register a deployment using the\n'rally deployment create' " + "command and then reference this deployment by uuid " + "when\nstarting a benchmark task with 'rally task start'. " + "The 'rally deployment create'\ncommand awaits a deployment " + "configuration file as its parameter. This file may look " + "like:\n" + "{\n" + ' "type": "ExistingCloud",\n' + ' "auth_url": "http://example.net:5000/v2.0/",\n' + ' "admin": { },\n' + " ...\n" + "}" + "\n\n" + + self._compose_table("List of Deployment engines", engines) + + "To get information about specific Deployment engines, run:\n" + " $ rally info find \n") 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) + """Get information about deploy engines available in Rally.""" + # NOTE(msdubov): This alias should be removed as soon as we rename + # DeployEngines to DeploymentEngines (which is more + # grammatically correct). + self.DeploymentEngines() def ServerProviders(self): - """List server providers available in Rally.""" + """Get information about server providers available in Rally.""" providers = self._get_descriptions(serverprovider.ProviderFactory) - info = self._compose_table("Server providers", providers) + info = (self._make_header("Rally - Server providers") + + "\n\n" + "Rally is an OpenStack benchmarking system. Before starting " + "benchmarking with Rally,\nyou obviously have either to " + "deploy a new OpenStack cloud or to register an existing\n" + "one in Rally with one of the Deployment engines. These " + "deployment engines, in turn,\nmay need Server " + "providers to manage virtual machines used for " + "OpenStack deployment\nand its following benchmarking. The " + "key feature of server providers is that they\nprovide a " + "unified interface for interacting with different " + "virtualization\ntechnologies (LXS, Virsh etc.)." + "\n\n" + "Server providers are usually referenced in deployment " + "configuration files\npassed to the 'rally deployment create'" + " command, e.g.:\n" + "{\n" + ' "type": "DevstackEngine",\n' + ' "provider": {\n' + ' "type": "ExistingServers",\n' + ' "credentials": [{"user": "root", "host": "10.2.0.8"}]\n' + " }\n" + "}" + "\n\n" + + self._compose_table("List of Server providers", providers) + + "To get information about specific Server providers, run:\n" + " $ rally info find \n") print(info) - def _get_descriptions(self, base_cls): + def _get_descriptions(self, base_cls, subclass_filter=None): descriptions = [] - for entity in utils.itersubclasses(base_cls): + subclasses = utils.itersubclasses(base_cls) + if subclass_filter: + subclasses = filter(subclass_filter, subclasses) + for entity in subclasses: name = entity.__name__ doc = utils.parse_docstring(entity.__doc__) description = doc["short_description"] or "" descriptions.append((name, description)) + descriptions.sort(key=lambda d: d[0]) return descriptions def _find_info(self, query): @@ -171,30 +283,23 @@ class InfoCommands(object): def _get_scenario_group_info(self, query): try: scenario_group = scenario_base.Scenario.get_by_name(query) - info = ("%s (benchmark scenario group).\n\n" % - scenario_group.__name__) + if not any(scenario_base.Scenario.is_scenario(scenario_group, m) + for m in dir(scenario_group)): + return None + info = self._make_header("%s (benchmark scenario group)" % + scenario_group.__name__) + info += "\n\n" info += utils.format_docstring(scenario_group.__doc__) - info += "\nBenchmark scenarios:\n" scenarios = scenario_group.list_benchmark_scenarios() - first_column_len = max(map(len, scenarios)) + cliutils.MARGIN - second_column_len = len("Description") + cliutils.MARGIN - table = "" + descriptions = [] for scenario_name in scenarios: cls, method_name = scenario_name.split(".") if hasattr(scenario_group, method_name): scenario = getattr(scenario_group, method_name) doc = utils.parse_docstring(scenario.__doc__) descr = doc["short_description"] or "" - second_column_len = max(second_column_len, - len(descr) + cliutils.MARGIN) - table += " " + scenario_name - table += " " * (first_column_len - len(scenario_name)) - table += descr + "\n" - info += "-" * (first_column_len + second_column_len + 1) + "\n" - info += (" Name" + " " * (first_column_len - len("Name")) + - "Description\n") - info += "-" * (first_column_len + second_column_len + 1) + "\n" - info += table + descriptions.append((scenario_name, descr)) + info += self._compose_table("Benchmark scenarios", descriptions) return info except exceptions.NoSuchScenario: return None @@ -203,10 +308,12 @@ class InfoCommands(object): try: scenario = scenario_base.Scenario.get_scenario_by_name(query) scenario_group_name = utils.get_method_class(scenario).__name__ - info = ("%(scenario_group)s.%(scenario_name)s " - "(benchmark scenario).\n\n" % - {"scenario_group": scenario_group_name, - "scenario_name": scenario.__name__}) + header = ("%(scenario_group)s.%(scenario_name)s " + "(benchmark scenario)" % + {"scenario_group": scenario_group_name, + "scenario_name": scenario.__name__}) + info = self._make_header(header) + info += "\n\n" doc = utils.parse_docstring(scenario.__doc__) if not doc["short_description"]: return None @@ -226,8 +333,10 @@ class InfoCommands(object): 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__) + header = "%s (SLA)" % sla.OPTION_NAME + info = self._make_header(header) + info += "\n\n" + info += utils.format_docstring(sla.__doc__) + "\n" return info except exceptions.NoSuchSLA: return None @@ -235,7 +344,9 @@ class InfoCommands(object): def _get_deploy_engine_info(self, query): try: deploy_engine = deploy.EngineFactory.get_by_name(query) - info = "%s (deploy engine).\n\n" % deploy_engine.__name__ + header = "%s (deploy engine)" % deploy_engine.__name__ + info = self._make_header(header) + info += "\n\n" info += utils.format_docstring(deploy_engine.__doc__) return info except exceptions.NoSuchEngine: @@ -244,14 +355,22 @@ class InfoCommands(object): def _get_server_provider_info(self, query): try: server_provider = serverprovider.ProviderFactory.get_by_name(query) - info = "%s (server provider).\n\n" % server_provider.__name__ + header = "%s (server provider)" % server_provider.__name__ + info = self._make_header(header) + info += "\n\n" info += utils.format_docstring(server_provider.__doc__) return info except exceptions.NoSuchVMProvider: return None + def _make_header(self, string): + header = "-" * (len(string) + 2) + "\n" + header += " " + string + " \n" + header += "-" * (len(string) + 2) + return header + def _compose_table(self, title, descriptions): - table = title + ":\n" + table = " " + title + ":\n" len0 = lambda x: len(x[0]) len1 = lambda x: len(x[1]) first_column_len = max(map(len0, descriptions)) + cliutils.MARGIN @@ -264,5 +383,6 @@ class InfoCommands(object): table += " " + name table += " " * (first_column_len - len(name)) table += descr + "\n" + table += "-" * (first_column_len + second_column_len + 1) + "\n" table += "\n" - return table \ No newline at end of file + return table diff --git a/rally/utils.py b/rally/utils.py index 5bc604f563..224145b6df 100644 --- a/rally/utils.py +++ b/rally/utils.py @@ -258,30 +258,41 @@ def parse_docstring(docstring): """ if docstring: - docstring_lines = docstrings.prepare_docstring(docstring) - docstring_lines = filter(lambda line: line != "", docstring_lines) + lines = docstrings.prepare_docstring(docstring) + lines = filter(lambda line: line != "", lines) else: - docstring_lines = [] + lines = [] - if docstring_lines: + if lines: + short_description = lines[0] - short_description = docstring_lines[0] - - param_lines_start = first_index(docstring_lines, - lambda line: line.startswith(":param") - or line.startswith(":returns")) - if param_lines_start: - long_description = "\n".join(docstring_lines[1:param_lines_start]) + param_start = first_index(lines, lambda l: l.startswith(":param")) + returns_start = first_index(lines, lambda l: l.startswith(":returns")) + if param_start or returns_start: + description_end = param_start or returns_start + long_description = "\n".join(lines[1:description_end]) else: - long_description = "\n".join(docstring_lines[1:]) + long_description = "\n".join(lines[1:]) if not long_description: long_description = None + param_lines = [] + if param_start: + current_line = lines[param_start] + current_line_index = param_start + 1 + while current_line_index < (returns_start or len(lines)): + if lines[current_line_index].startswith(":param"): + param_lines.append(current_line) + current_line = lines[current_line_index] + else: + continuation_line = lines[current_line_index].strip() + current_line += " " + continuation_line + current_line_index += 1 + param_lines.append(current_line) params = [] param_regex = re.compile("^:param (?P\w+): (?P.*)$") - for param_line in filter(lambda line: line.startswith(":param"), - docstring_lines): + for param_line in param_lines: match = param_regex.match(param_line) if match: params.append({ @@ -290,11 +301,10 @@ def parse_docstring(docstring): }) returns = None - returns_line = filter(lambda line: line.startswith(":returns"), - docstring_lines) - if returns_line: + if returns_start: + returns_line = " ".join([l.strip() for l in lines[returns_start:]]) returns_regex = re.compile("^:returns: (?P.*)$") - match = returns_regex.match(returns_line[0]) + match = returns_regex.match(returns_line) if match: returns = match.group("doc") diff --git a/tests/functional/test_cli_info.py b/tests/functional/test_cli_info.py index 5c75dcfae9..df379450cd 100644 --- a/tests/functional/test_cli_info.py +++ b/tests/functional/test_cli_info.py @@ -32,21 +32,28 @@ class InfoTestCase(unittest.TestCase): self.assertIn("Dummy.dummy_random_fail_in_atomic", output) def test_find_scenario_group_base_class(self): - output = self.rally("info find CeilometerScenario") - self.assertIn("(benchmark scenario group)", output) + # NOTE(msdubov): We shouldn't display info about base scenario classes + # containing no end-user scenarios + self.assertRaises(utils.RallyCmdError, self.rally, + ("info find CeilometerScenario")) 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")) + expected = "failure_rate (SLA)" + self.assertIn(expected, self.rally("info find failure_rate")) + + def test_find_sla_by_class_name(self): + expected = "failure_rate (SLA)" + self.assertIn(expected, self.rally("info find FailureRate")) def test_find_deployment_engine(self): - marker_string = "ExistingCloud (deploy engine)." + marker_string = "ExistingCloud (deploy engine)" self.assertIn(marker_string, self.rally("info find ExistingCloud")) def test_find_server_provider(self): - marker_string = "ExistingServers (server provider)." + marker_string = "ExistingServers (server provider)" self.assertIn(marker_string, self.rally("info find ExistingServers")) def test_find_fails(self): @@ -54,21 +61,29 @@ class InfoTestCase(unittest.TestCase): ("info find NonExistingStuff")) def test_find_misspelling_typos(self): - marker_string = "ExistingServers (server provider)." + marker_string = "ExistingServers (server provider)" self.assertIn(marker_string, self.rally("info find ExistinfServert")) def test_find_misspelling_truncated(self): marker_string = ("NovaServers.boot_and_delete_server " - "(benchmark scenario).") + "(benchmark scenario)") self.assertIn(marker_string, self.rally("info find boot_and_delete")) + def test_find_misspelling_truncated_many_substitutions(self): + try: + self.rally("info find Nova") + except utils.RallyCmdError as e: + self.assertIn("NovaServers", e.output) + self.assertIn("NovaServers.boot_and_delete_server", e.output) + self.assertIn("NovaServers.snapshot_server", e.output) + def test_list(self): 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("SLA checks:", output) + self.assertIn("failure_rate", output) + self.assertIn("Deployment engines:", output) self.assertIn("ExistingCloud", output) self.assertIn("Server providers:", output) self.assertIn("ExistingServers", output) @@ -77,15 +92,16 @@ class InfoTestCase(unittest.TestCase): output = self.rally("info BenchmarkScenarios") self.assertIn("Benchmark scenario groups:", output) self.assertIn("NovaServers", output) + self.assertNotIn("NovaScenario", output) def test_SLA(self): output = self.rally("info SLA") - self.assertIn("SLA:", output) - self.assertIn("FailureRate", output) + self.assertIn("SLA checks:", output) + self.assertIn("failure_rate", output) - def test_DeployEngines(self): - output = self.rally("info DeployEngines") - self.assertIn("Deploy engines:", output) + def test_DeploymentEngines(self): + output = self.rally("info DeploymentEngines") + self.assertIn("Deployment engines:", output) self.assertIn("ExistingCloud", output) def test_ServerProviders(self): diff --git a/tests/unit/benchmark/sla/test_base.py b/tests/unit/benchmark/sla/test_base.py index 570453f4ed..205364b5d9 100644 --- a/tests/unit/benchmark/sla/test_base.py +++ b/tests/unit/benchmark/sla/test_base.py @@ -32,6 +32,13 @@ class TestCriterion(base.SLA): class BaseSLATestCase(test.TestCase): + def test_get_by_name(self): + self.assertEqual(base.FailureRate, base.SLA.get_by_name("FailureRate")) + + def test_get_by_name_by_config_option(self): + self.assertEqual(base.FailureRate, + base.SLA.get_by_name("failure_rate")) + def test_validate(self): cnf = {"test_criterion": 42} base.SLA.validate(cnf) diff --git a/tests/unit/cmd/commands/test_info.py b/tests/unit/cmd/commands/test_info.py index 13e77d3775..940571838c 100644 --- a/tests/unit/cmd/commands/test_info.py +++ b/tests/unit/cmd/commands/test_info.py @@ -32,6 +32,7 @@ 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" +COMMANDS = "rally.cmd.commands.info.InfoCommands" class InfoCommandsTestCase(test.TestCase): @@ -65,6 +66,13 @@ class InfoCommandsTestCase(test.TestCase): @mock.patch(SLA + ".get_by_name", return_value=sla_base.FailureRate) def test_find_failure_rate_sla(self, mock_get_by_name): + query = "failure_rate" + status = self.info.find(query) + mock_get_by_name.assert_called_once_with(query) + self.assertIsNone(status) + + @mock.patch(SLA + ".get_by_name", return_value=sla_base.FailureRate) + def test_find_failure_rate_sla_by_class_name(self, mock_get_by_name): query = "FailureRate" status = self.info.find(query) mock_get_by_name.assert_called_once_with(query) @@ -86,37 +94,41 @@ class InfoCommandsTestCase(test.TestCase): mock_get_by_name.assert_called_once_with(query) self.assertIsNone(status) - @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) - def test_list(self, mock_itersubclasses): + @mock.patch(COMMANDS + ".ServerProviders") + @mock.patch(COMMANDS + ".DeploymentEngines") + @mock.patch(COMMANDS + ".SLA") + @mock.patch(COMMANDS + ".BenchmarkScenarios") + def test_list(self, mock_BenchmarkScenarios, mock_SLA, + mock_DeploymentEngines, mock_ServerProviders): 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)]) + mock_BenchmarkScenarios.assert_called_once_with() + mock_SLA.assert_called_once_with() + mock_DeploymentEngines.assert_called_once_with() + mock_ServerProviders.assert_called_once_with() self.assertIsNone(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) + mock_itersubclasses.assert_called_with(scenario_base.Scenario) self.assertIsNone(status) - @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) + @mock.patch(UTILS + ".itersubclasses", return_value=[sla_base.FailureRate]) def test_SLA(self, mock_itersubclasses): status = self.info.SLA() - mock_itersubclasses.assert_called_once_with(sla_base.SLA) + mock_itersubclasses.assert_called_with(sla_base.SLA) self.assertIsNone(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) + @mock.patch(UTILS + ".itersubclasses", + return_value=[existing_cloud.ExistingCloud]) + def test_DeploymentEngines(self, mock_itersubclasses): + status = self.info.DeploymentEngines() + mock_itersubclasses.assert_called_with(deploy.EngineFactory) self.assertIsNone(status) - @mock.patch(UTILS + ".itersubclasses", return_value=[dummy.Dummy]) + @mock.patch(UTILS + ".itersubclasses", + return_value=[existing_servers.ExistingServers]) def test_ServerProviders(self, mock_itersubclasses): status = self.info.ServerProviders() - mock_itersubclasses.assert_called_once_with( - serverprovider.ProviderFactory) + mock_itersubclasses.assert_called_with(serverprovider.ProviderFactory) self.assertIsNone(status) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 033a89987f..989d464e58 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -254,8 +254,10 @@ line- description. :param p1: Param 1 description. -:param p2: Param 2 description. -:returns: Return value description. +:param p2: Param 2 + description. +:returns: Return value + description. """ dct = utils.parse_docstring(docstring) @@ -272,7 +274,8 @@ description. docstring = """One-line description. :param p1: Param 1 description. -:param p2: Param 2 description. +:param p2: Param 2 + description. """ dct = utils.parse_docstring(docstring) @@ -292,7 +295,8 @@ Multi- line- description. -:returns: Return value description. +:returns: Return value + description. """ dct = utils.parse_docstring(docstring) diff --git a/tools/rally.bash_completion b/tools/rally.bash_completion index eae07cdfa1..dfc69550dd 100644 --- a/tools/rally.bash_completion +++ b/tools/rally.bash_completion @@ -8,6 +8,7 @@ _rally() OPTS["info_BenchmarkScenarios"]="" OPTS["info_DeployEngines"]="" + OPTS["info_DeploymentEngines"]="" OPTS["info_SLA"]="" OPTS["info_ServerProviders"]="" OPTS["info_find"]="--query"