Merge "Add detailed description for rally commands"

This commit is contained in:
Jenkins 2014-10-20 10:44:41 +00:00 committed by Gerrit Code Review
commit 65efb6b2df
7 changed files with 201 additions and 39 deletions

View File

@ -15,6 +15,7 @@
from __future__ import print_function
import argparse
import os
import sys
@ -24,12 +25,52 @@ from rally.openstack.common.apiclient import exceptions
from rally.openstack.common import cliutils
from rally.openstack.common.gettextutils import _
from rally.openstack.common import log as logging
from rally import utils
from rally import version
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class CategoryParser(argparse.ArgumentParser):
"""Customized arguments parser
We need this one to override hardcoded behavior.
So, we want to print item's help instead of 'error: too fiew arguments'.
Also, we want not to print positional arguments in help messge.
"""
def format_help(self):
formatter = self._get_formatter()
# usage
formatter.add_usage(self.usage, self._actions,
self._mutually_exclusive_groups)
# description
formatter.add_text(self.description)
# positionals, optionals and user-defined groups
# INFO(oanufriev) _action_groups[0] contains positional arguments.
for action_group in self._action_groups[1:]:
formatter.start_section(action_group.title)
formatter.add_text(action_group.description)
formatter.add_arguments(action_group._group_actions)
formatter.end_section()
# epilog
formatter.add_text(self.epilog)
# determine help from format above
return formatter.format_help()
def error(self, message):
self.print_help(sys.stderr)
sys.exit(2)
def pretty_float_formatter(field, ndigits=None):
"""Create a formatter function for the given float field.
@ -70,7 +111,49 @@ def _methods_of(obj):
return result
def _compose_category_description(category):
descr_pairs = _methods_of(category)
description = ""
if category.__doc__:
description = category.__doc__.strip()
if descr_pairs:
description += "\n\nCommands:\n"
sublen = lambda item: len(item[0])
first_column_len = max(map(sublen, descr_pairs)) + 3
for item in descr_pairs:
name = item[0]
if item[1].__doc__:
doc = utils.parse_docstring(
item[1].__doc__)["short_description"]
else:
doc = ""
name += " " * (first_column_len - len(name))
description += " %s%s\n" % (name, doc)
return description
def _compose_action_description(action_fn):
description = ""
if action_fn.__doc__:
parsed_doc = utils.parse_docstring(action_fn.__doc__)
short = parsed_doc.get("short_description")
long = parsed_doc.get("long_description")
description = "%s\n\n%s" % (short, long) if long else short
return description
def _add_command_parsers(categories, subparsers):
# INFO(oanufriev) This monkey patching makes our custom parser class to be
# used instead of native. This affects all subparsers down from
# 'subparsers' parameter of this function (categories and actions).
subparsers._parser_class = CategoryParser
parser = subparsers.add_parser('version')
parser = subparsers.add_parser('bash-completion')
@ -78,16 +161,20 @@ def _add_command_parsers(categories, subparsers):
for category in categories:
command_object = categories[category]()
parser = subparsers.add_parser(category)
descr = _compose_category_description(categories[category])
parser = subparsers.add_parser(
category, description=descr,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.set_defaults(command_object=command_object)
category_subparsers = parser.add_subparsers(dest='action')
for action, action_fn in _methods_of(command_object):
kwargs = {"help": action_fn.__doc__,
"description": action_fn.__doc__}
parser = category_subparsers.add_parser(action, **kwargs)
descr = _compose_action_description(action_fn)
parser = category_subparsers.add_parser(
action,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=descr, help=descr)
action_kwargs = []
for args, kwargs in getattr(action_fn, 'args', []):
@ -104,7 +191,6 @@ def _add_command_parsers(categories, subparsers):
parser.set_defaults(action_fn=action_fn)
parser.set_defaults(action_kwargs=action_kwargs)
parser.add_argument('action_args', nargs='*')

View File

@ -39,6 +39,7 @@ from rally import utils
class DeploymentCommands(object):
"""Set of commands that allow you to manage deployments."""
@cliutils.args('--name', type=str, required=True,
help='A name of the deployment.')
@ -51,7 +52,30 @@ class DeploymentCommands(object):
help='Don\'t set new deployment as default for'
' future operations')
def create(self, name, fromenv=False, filename=None, do_use=False):
"""Create a new deployment on the basis of configuration file.
"""Create new deployment.
This command will create new deployment record in rally database.
In case of ExistingCloud deployment engine it will use cloud,
represented in config.
In cases when cloud doesn't exists Rally will deploy new one
for you with Devstack or Fuel. For this purposes different deployment
engines are developed.
If you use ExistionCloud deployment engine you can pass deployment
config by environment variables:
OS_USERNAME
OS_PASSWORD
OS_AUTH_URL
OS_TENANT_NAME
All other deployment engines need more complex configuration data, so
it should be stored in configuration file.
You can use physical servers, lxc containers, KVM virtual machines
or virtual machines in OpenStack for deploying the cloud in.
Except physical servers, Rally can create cluster nodes for you.
Interaction with virtualisation software, OpenStack
cloud or physical servers is provided by server providers.
:param fromenv: boolean, read environment instead of config file
:param filename: a path to the configuration file
@ -105,6 +129,9 @@ class DeploymentCommands(object):
def recreate(self, deploy_id=None):
"""Destroy and create an existing deployment.
Unlike 'deployment destroy' command deployment database record will
not be deleted, so deployment's UUID stay same.
:param deploy_id: a UUID of the deployment
"""
api.recreate_deploy(deploy_id)
@ -113,17 +140,19 @@ class DeploymentCommands(object):
help='UUID of a deployment.')
@envutils.with_default_deploy_id
def destroy(self, deploy_id=None):
"""Destroy the deployment.
"""Destroy existing deployment.
Release resources that are allocated for the deployment. The
Deployment, related tasks and their results are also deleted.
This will delete all containers, virtual machines, OpenStack instances
or Fuel clusters created during Rally deployment creation. Also it will
remove deployment record from Rally database.
:param deploy_id: a UUID of the deployment
"""
api.destroy_deploy(deploy_id)
def list(self, deployment_list=None):
"""Print list of deployments."""
"""List existing deployments."""
headers = ['uuid', 'created_at', 'name', 'status', 'active']
current_deploy_id = envutils.get_global('RALLY_DEPLOYMENT')
deployment_list = deployment_list or db.deployment_list()
@ -150,9 +179,9 @@ class DeploymentCommands(object):
help='Output in pretty print format')
@envutils.with_default_deploy_id
def config(self, deploy_id=None, output_json=None, output_pprint=None):
"""Print on stdout a config of the deployment.
"""Display configuration of the deployment.
Output can JSON or Pretty print format.
Output can JSON or Pretty print format.
:param deploy_id: a UUID of the deployment
:param output_json: Output in json format (Default)
@ -174,10 +203,11 @@ class DeploymentCommands(object):
help='UUID of a deployment.')
@envutils.with_default_deploy_id
def endpoint(self, deploy_id=None):
"""Print all endpoints of the deployment.
"""Display all endpoints of the deployment.
:param deploy_id: a UUID of the deployment
"""
headers = ['auth_url', 'username', 'password', 'tenant_name',
'region_name', 'endpoint_type', 'admin_port']
table_rows = []
@ -196,12 +226,11 @@ class DeploymentCommands(object):
help='UUID of a deployment.')
@envutils.with_default_deploy_id
def check(self, deploy_id=None):
"""Check the deployment.
Check keystone authentication and list all available services.
"""Check keystone authentication and list all available services.
:param deploy_id: a UUID of the deployment
"""
headers = ['services', 'type', 'status']
table_rows = []
try:

View File

@ -51,6 +51,11 @@ 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
server providers.
"""
@cliutils.args("--query", dest="query", type=str, help="Search query.")
def find(self, query):
@ -58,6 +63,7 @@ class InfoCommands(object):
:param query: search query.
"""
info = self._find_info(query)
if info:

View File

@ -29,6 +29,11 @@ from rally import utils
class ShowCommands(object):
"""Show resources.
Set of commands that allow you to view resourses, provided by OpenStack
cloud represented by deployment.
"""
def _get_endpoints(self, deploy_id):
deployment = db.deployment_get(deploy_id)
@ -41,10 +46,11 @@ class ShowCommands(object):
help='the UUID of a deployment')
@envutils.with_default_deploy_id
def images(self, deploy_id=None):
"""Show the images that are available in a deployment.
"""Display available images.
:param deploy_id: the UUID of a deployment
"""
headers = ['UUID', 'Name', 'Size (B)']
mixed_case_fields = ['UUID', 'Name']
float_cols = ["Size (B)"]
@ -74,10 +80,11 @@ class ShowCommands(object):
help='the UUID of a deployment')
@envutils.with_default_deploy_id
def flavors(self, deploy_id=None):
"""Show the flavors that are available in a deployment.
"""Display available flavors.
:param deploy_id: the UUID of a deployment
"""
headers = ['ID', 'Name', 'vCPUs', 'RAM (MB)', 'Swap (MB)', 'Disk (GB)']
mixed_case_fields = ['ID', 'Name', 'vCPUs']
float_cols = ['RAM (MB)', 'Swap (MB)', 'Disk (GB)']
@ -107,6 +114,8 @@ class ShowCommands(object):
help='the UUID of a deployment')
@envutils.with_default_deploy_id
def networks(self, deploy_id=None):
"""Display configured networks."""
headers = ['ID', 'Label', 'CIDR']
mixed_case_fields = ['ID', 'Label', 'CIDR']
table_rows = []
@ -129,6 +138,8 @@ class ShowCommands(object):
help='the UUID of a deployment')
@envutils.with_default_deploy_id
def secgroups(self, deploy_id=None):
"""Display security groups."""
headers = ['ID', 'Name', 'Description']
mixed_case_fields = ['ID', 'Name', 'Description']
table_rows = []
@ -154,6 +165,8 @@ class ShowCommands(object):
help='the UUID of a deployment')
@envutils.with_default_deploy_id
def keypairs(self, deploy_id=None):
"""Display available ssh keypairs."""
headers = ['Name', 'Fingerprint']
mixed_case_fields = ['Name', 'Fingerprint']
table_rows = []

View File

@ -38,6 +38,10 @@ from rally import utils as rutils
class TaskCommands(object):
"""Task management.
Set of commands that allow you to manage benchmarking tasks and results.
"""
@cliutils.args('--deploy-id', type=str, dest='deploy_id', required=False,
help='UUID of the deployment')
@ -45,7 +49,10 @@ class TaskCommands(object):
help='Path to the file with full configuration of task')
@envutils.with_default_deploy_id
def validate(self, task, deploy_id=None):
"""Validate a task file.
"""Validate a task configuration file.
This will check that task configuration file has valid syntax and
all required options of scenarios, contexts, SLA and runners are set.
:param task: a file with yaml/json configration
:param deploy_id: a UUID of a deployment
@ -71,7 +78,7 @@ class TaskCommands(object):
help='Don\'t set new task as default for future operations')
@envutils.with_default_deploy_id
def start(self, task, deploy_id=None, tag=None, do_use=False):
"""Run a benchmark task.
"""Start benchmark task.
:param task: a file with yaml/json configration
:param deploy_id: a UUID of a deployment
@ -99,20 +106,22 @@ class TaskCommands(object):
@cliutils.args('--uuid', type=str, dest='task_id', help='UUID of task')
@envutils.with_default_task_id
def abort(self, task_id=None):
"""Force abort task
"""Abort started benchmarking task.
:param task_id: Task uuid
"""
api.abort_task(task_id)
@cliutils.args('--uuid', type=str, dest='task_id', help='UUID of task')
@envutils.with_default_task_id
def status(self, task_id=None):
"""Get status of task
"""Display current status of task.
:param task_id: Task uuid
Returns current status of task
"""
task = db.task_get(task_id)
print(_("Task %(task_id)s is %(status)s.")
% {'task_id': task_id, 'status': task['status']})
@ -126,12 +135,13 @@ class TaskCommands(object):
help='print detailed results for each iteration')
@envutils.with_default_task_id
def detailed(self, task_id=None, iterations_data=False):
"""Get detailed information about task
"""Display results table.
:param task_id: Task uuid
:param iterations_data: print detailed results for each iteration
Prints detailed information of task.
"""
def _print_iterations_data(raw_data):
headers = ["iteration", "full duration"]
float_cols = ["full duration"]
@ -300,12 +310,15 @@ class TaskCommands(object):
help=('Output in json format(default)'))
@envutils.with_default_task_id
def results(self, task_id=None, output_pprint=None, output_json=None):
"""Print raw results of task.
"""Diplay raw task results.
This will produce a lot of output data about every iteration.
:param task_id: Task uuid
:param output_pprint: Output in pretty print format
:param output_json: Output in json format (Default)
"""
results = map(lambda x: {"key": x["key"], 'result': x['data']['raw'],
"sla": x["data"]["sla"]},
db.task_result_get_all_by_uuid(task_id))
@ -325,7 +338,8 @@ class TaskCommands(object):
return(1)
def list(self, task_list=None):
"""Print a list of all tasks."""
"""List all tasks, started and finished."""
headers = ['uuid', 'created_at', 'status', 'failed', 'tag']
task_list = task_list or db.task_list()
if task_list:
@ -380,11 +394,12 @@ class TaskCommands(object):
help='uuid of task or a list of task uuids')
@envutils.with_default_task_id
def delete(self, task_id=None, force=False):
"""Delete a specific task and related results.
"""Delete task and its results.
:param task_id: Task uuid or a list of task uuids
:param force: Force delete or not
"""
if isinstance(task_id, list):
for tid in task_id:
api.delete_task(tid, force=force)
@ -397,7 +412,7 @@ class TaskCommands(object):
help="output in json format")
@envutils.with_default_task_id
def sla_check(self, task_id=None, tojson=False):
"""Check if task was succeded according to SLA.
"""Display SLA check results table.
:param task_id: Task uuid.
:returns: Number of failed criteria.

View File

@ -24,6 +24,11 @@ from rally import fileutils
class UseCommands(object):
"""Set of commands that allow you to set an active deployment and task.
Active deployment and task allow you not to specify deployment UUID and
task UUID in the commands requiring this parameter.
"""
def _update_openrc_deployment_file(self, deploy_id, endpoint):
openrc_path = os.path.expanduser('~/.rally/openrc-%s' % deploy_id)
@ -55,10 +60,11 @@ class UseCommands(object):
@cliutils.args('--name', type=str, dest='name', required=False,
help='Name of the deployment')
def deployment(self, deploy_id=None, name=None):
"""Set the RALLY_DEPLOYMENT env var to be used by all CLI commands
"""Set active deployment.
:param deploy_id: a UUID of a deployment
"""
if not (name or deploy_id):
print('You should specify --name or --uuid of deployment')
return 1
@ -99,14 +105,9 @@ class UseCommands(object):
@cliutils.args('--uuid', type=str, dest='task_id', required=False,
help='UUID of the task')
def task(self, task_id):
"""Set the RALLY_TASK env var.
"""Set active task.
Is used to allow the user not to specify a task UUID in the command
requiring this parameter.
If the task uuid specified in parameter by the user does not exist,
a TaskNotFound will be raised by task_get().
:param task_id: a UUID of a task
:param task_id: a UUID of task
"""
print('Using task: %s' % task_id)
self._ensure_rally_configuration_dir_exists()

View File

@ -34,6 +34,11 @@ from rally.verification.verifiers.tempest import json2html
class VerifyCommands(object):
"""Test cloud with Tempest
Set of commands that allow you to perform Tempest tests of
OpenStack live cloud.
"""
@cliutils.args("--deploy-id", dest="deploy_id", type=str, required=False,
help="UUID of a deployment.")
@ -48,13 +53,14 @@ class VerifyCommands(object):
@envutils.with_default_deploy_id
def start(self, deploy_id=None, set_name="smoke", regex=None,
tempest_config=None):
"""Start running tempest tests against a live cloud cluster.
"""Start set of tests.
:param deploy_id: a UUID of a deployment
:param set_name: Name of tempest test set
:param regex: Regular expression of test
:param tempest_config: User specified Tempest config file location
"""
if regex:
set_name = "full"
if set_name not in consts.TEMPEST_TEST_SETS:
@ -65,7 +71,8 @@ class VerifyCommands(object):
api.verify(deploy_id, set_name, regex, tempest_config)
def list(self):
"""Print a result list of verifications."""
"""Display all verifications table, started and finished."""
fields = ['UUID', 'Deployment UUID', 'Set name', 'Tests', 'Failures',
'Created at', 'Status']
verifications = db.verification_list()
@ -89,7 +96,7 @@ class VerifyCommands(object):
help='If specified, output will be saved to given file')
def results(self, verification_uuid, output_file=None, output_html=None,
output_json=None, output_pprint=None):
"""Print raw results of verification.
"""Get raw results of the verification.
:param verification_uuid: Verification UUID
:param output_file: If specified, output will be saved to given file
@ -99,6 +106,7 @@ class VerifyCommands(object):
:param output_pprint: Save results in pprint format to the
specified file
"""
try:
results = db.verification_result_get(verification_uuid)['data']
except exceptions.NotFoundException as e:
@ -132,6 +140,8 @@ class VerifyCommands(object):
@cliutils.args('--detailed', dest='detailed', action='store_true',
required=False, help='Prints traceback of failed tests')
def show(self, verification_uuid, sort_by='name', detailed=False):
"""Display results table of the verification."""
try:
sortby_index = ('name', 'duration').index(sort_by)
except ValueError:
@ -182,4 +192,6 @@ class VerifyCommands(object):
@cliutils.args('--sort-by', dest='sort_by', type=str, required=False,
help='Tests can be sorted by "name" or "duration"')
def detailed(self, verification_uuid, sort_by='name'):
"""Display results table of verification with detailed errors."""
self.show(verification_uuid, sort_by, True)