Merge "Add detailed description for rally commands"
This commit is contained in:
commit
65efb6b2df
@ -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='*')
|
||||
|
||||
|
||||
|
@ -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,7 +179,7 @@ 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.
|
||||
|
||||
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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 = []
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user