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
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

View File

@ -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."""

View File

@ -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:

View File

@ -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 <query>\n\n"
doc += "Possible queries:\n\n" + self._list()
return doc
Usage:
$ rally info find <query>
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 <ScenarioGroupName>\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)

View File

@ -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`.")

View File

@ -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)

View File

@ -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)

View File

@ -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"