Enhance rally info

* Add "rally info BenchmarkScenario", "rally info DeployEgnine" etc.
* Simplify the default "rally info" output
* Add SLA support

Change-Id: I85259e7cf61b2b435b416b9618064f8ec22fe778
This commit is contained in:
Mikhail Dubov 2014-10-29 14:50:45 +04:00
parent b439b4d127
commit 91acc1c039
8 changed files with 135 additions and 55 deletions

View File

@ -73,8 +73,6 @@ class Scenario(object):
@staticmethod @staticmethod
def get_by_name(name): def get_by_name(name):
"""Returns Scenario class by name.""" """Returns Scenario class by name."""
# TODO(msdubov): support approximate string matching
# (here and in other base classes).
for scenario in utils.itersubclasses(Scenario): for scenario in utils.itersubclasses(Scenario):
if name == scenario.__name__: if name == scenario.__name__:
return scenario return scenario

View File

@ -25,6 +25,7 @@ import jsonschema
import six import six
from rally.benchmark.processing import utils as putils from rally.benchmark.processing import utils as putils
from rally import exceptions
from rally.i18n import _ from rally.i18n import _
from rally import utils from rally import utils
@ -81,6 +82,14 @@ class SLA(object):
'detail': check_result.msg}) 'detail': check_result.msg})
return results 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): class FailureRate(SLA):
"""Failure rate in percents.""" """Failure rate in percents."""

View File

@ -104,19 +104,6 @@ def args(*args, **kwargs):
return _decorator 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): def _methods_of(cls):
"""Get all callable methods of a class that don't start with underscore. """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) descr_pairs = _methods_of(category)
description = "" description = ""
doc = _get_doc(category) doc = category.__doc__
if doc: if doc:
description = doc.strip() description = doc.strip()
if descr_pairs: if descr_pairs:

View File

@ -50,6 +50,7 @@ Samples:
from __future__ import print_function from __future__ import print_function
from rally.benchmark.scenarios import base as scenario_base 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.cmd import cliutils
from rally import deploy from rally import deploy
from rally.deploy import serverprovider from rally.deploy import serverprovider
@ -60,14 +61,18 @@ from rally import utils
class InfoCommands(object): class InfoCommands(object):
"""This command allows you to get quick doc of some rally entities. """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. server providers.
"""
def __get__doc__(self): Usage:
doc = "Usage:\n\n $ rally info find <query>\n\n" $ rally info find <query>
doc += "Possible queries:\n\n" + self._list()
return doc 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.") @cliutils.args("--query", dest="query", type=str, help="Search query.")
def find(self, query): def find(self, query):
@ -81,8 +86,8 @@ class InfoCommands(object):
if info: if info:
print(info) print(info)
else: else:
print("Failed to find any docs for query: '%s'" % query)
substitutions = self._find_substitution(query) substitutions = self._find_substitution(query)
print("Failed to find any docs for query: '%s'" % query)
if substitutions: if substitutions:
print("Did you mean one of these?\n\t%s" % print("Did you mean one of these?\n\t%s" %
"\n\t".join(substitutions)) "\n\t".join(substitutions))
@ -93,36 +98,51 @@ class InfoCommands(object):
Lists benchmark scenario groups, deploy engines and server providers. Lists benchmark scenario groups, deploy engines and server providers.
""" """
print(self._list()) self.BenchmarkScenarios()
self.SLA()
self.DeployEngines()
self.ServerProviders()
def _list(self): def BenchmarkScenarios(self):
base_classes = {"scenario_groups": scenario_base.Scenario, """List benchmark scenarios available in Rally."""
"deploy_engines": deploy.EngineFactory, scenarios = self._get_descriptions(scenario_base.Scenario)
"server_providers": serverprovider.ProviderFactory} info = self._compose_table("Benchmark scenario groups", scenarios)
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"])
info += (" To get information about benchmark scenarios inside " info += (" To get information about benchmark scenarios inside "
"each scenario group, run:\n" "each scenario group, run:\n"
" $ rally info find <ScenarioGroupName>\n\n") " $ rally info find <ScenarioGroupName>\n\n")
info += self._compose_table("Deploy engines", print(info)
descriptions["deploy_engines"])
info += self._compose_table("Server providers", def SLA(self):
descriptions["server_providers"]) """List server providers available in Rally."""
return info 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): def _find_info(self, query):
return (self._get_scenario_group_info(query) or return (self._get_scenario_group_info(query) or
self._get_scenario_info(query) or self._get_scenario_info(query) or
self._get_sla_info(query) or
self._get_deploy_engine_info(query) or self._get_deploy_engine_info(query) or
self._get_server_provider_info(query)) self._get_server_provider_info(query))
@ -200,6 +220,15 @@ class InfoCommands(object):
except exceptions.NoSuchScenario: except exceptions.NoSuchScenario:
return None 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): def _get_deploy_engine_info(self, query):
try: try:
deploy_engine = deploy.EngineFactory.get_by_name(query) deploy_engine = deploy.EngineFactory.get_by_name(query)

View File

@ -136,6 +136,10 @@ class NoSuchContext(NotFoundException):
msg_fmt = _("There is no benchmark context with name `%(name)s`.") 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): class NoSuchConfigField(NotFoundException):
msg_fmt = _("There is no field in the task config with name `%(name)s`.") msg_fmt = _("There is no field in the task config with name `%(name)s`.")

View File

@ -38,6 +38,9 @@ class InfoTestCase(unittest.TestCase):
def test_find_scenario(self): def test_find_scenario(self):
self.assertIn("(benchmark scenario)", self.rally("info find dummy")) 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): 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")) self.assertIn(marker_string, self.rally("info find ExistingCloud"))
@ -68,18 +71,29 @@ class InfoTestCase(unittest.TestCase):
output = self.rally("info list") output = self.rally("info list")
self.assertIn("Benchmark scenario groups:", output) self.assertIn("Benchmark scenario groups:", output)
self.assertIn("NovaServers", output) self.assertIn("NovaServers", output)
self.assertIn("SLA:", output)
self.assertIn("FailureRate", output)
self.assertIn("Deploy engines:", output) self.assertIn("Deploy engines:", output)
self.assertIn("ExistingCloud", output) self.assertIn("ExistingCloud", output)
self.assertIn("Server providers:", output) self.assertIn("Server providers:", output)
self.assertIn("ExistingServers", output) self.assertIn("ExistingServers", output)
def test_list_shorthand(self): def test_BenchmarkScenarios(self):
try: output = self.rally("info BenchmarkScenarios")
self.rally("info") self.assertIn("Benchmark scenario groups:", output)
except utils.RallyCmdError as e: self.assertIn("NovaServers", output)
self.assertIn("Benchmark scenario groups:", e.output)
self.assertIn("NovaServers", e.output) def test_SLA(self):
self.assertIn("Deploy engines:", e.output) output = self.rally("info SLA")
self.assertIn("ExistingCloud", e.output) self.assertIn("SLA:", output)
self.assertIn("Server providers:", e.output) self.assertIn("FailureRate", output)
self.assertIn("ExistingServers", e.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)

View File

@ -17,6 +17,7 @@ import mock
from rally.benchmark.scenarios import base as scenario_base from rally.benchmark.scenarios import base as scenario_base
from rally.benchmark.scenarios.dummy import dummy from rally.benchmark.scenarios.dummy import dummy
from rally.benchmark.sla import base as sla_base
from rally.cmd.commands import info from rally.cmd.commands import info
from rally import deploy from rally import deploy
from rally.deploy.engines import existing as existing_cloud 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" SCENARIO = "rally.cmd.commands.info.scenario_base.Scenario"
SLA = "rally.cmd.commands.info.sla_base.SLA"
ENGINE = "rally.cmd.commands.info.deploy.EngineFactory" ENGINE = "rally.cmd.commands.info.deploy.EngineFactory"
PROVIDER = "rally.cmd.commands.info.serverprovider.ProviderFactory" PROVIDER = "rally.cmd.commands.info.serverprovider.ProviderFactory"
UTILS = "rally.cmd.commands.info.utils" UTILS = "rally.cmd.commands.info.utils"
@ -61,6 +63,13 @@ class InfoCommandsTestCase(test.TestCase):
mock_get_scenario_by_name.assert_called_once_with(query) mock_get_scenario_by_name.assert_called_once_with(query)
self.assertEqual(1, status) 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", @mock.patch(ENGINE + ".get_by_name",
return_value=existing_cloud.ExistingCloud) return_value=existing_cloud.ExistingCloud)
def test_find_existing_cloud(self, mock_get_by_name): def test_find_existing_cloud(self, mock_get_by_name):
@ -82,6 +91,32 @@ class InfoCommandsTestCase(test.TestCase):
status = self.info.list() status = self.info.list()
mock_itersubclasses.assert_has_calls([ mock_itersubclasses.assert_has_calls([
mock.call(scenario_base.Scenario), mock.call(scenario_base.Scenario),
mock.call(sla_base.SLA),
mock.call(deploy.EngineFactory), mock.call(deploy.EngineFactory),
mock.call(serverprovider.ProviderFactory)]) mock.call(serverprovider.ProviderFactory)])
self.assertEqual(None, status) 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)

View File

@ -6,6 +6,10 @@ _rally()
declare -A SUBCOMMANDS declare -A SUBCOMMANDS
declare -A OPTS declare -A OPTS
OPTS["info_BenchmarkScenarios"]=""
OPTS["info_DeployEngines"]=""
OPTS["info_SLA"]=""
OPTS["info_ServerProviders"]=""
OPTS["info_find"]="--query" OPTS["info_find"]="--query"
OPTS["info_list"]="" OPTS["info_list"]=""
OPTS["use_deployment"]="--uuid --name" OPTS["use_deployment"]="--uuid --name"