Files
rally/rally/cmd/main.py
Julien Vey b74ea2e4f8 Add rally use command
This adds a new group of commands : 'rally use'
the first implemented is 'rally use deployment <deploy_id>'

As it is not possible in python to export globaly an env var,it saves the
variable in a file at ~/.rally/deployment like :
RALLY_DEPLOYMENT=1234abcd

For each command that takes a deploy_id as parameter, this
parameter is now optional.

The algorithm to find the deploy_id is as follows :
- If the --deploy-id param is set, use it
- If the environ contains the key 'RALLY_DEPLOYMENT', use it
- load the content of ~/.rally/deployment and if it contains the key
RALLY_DEPLOYMENT, use it
- Else, raise an InvalidArgumentException

Note that I have not been able to use the default arg in @cliutils.args
because it is called before the effective method call. Setting
deploy_id=envutils.default_deployment_id() has the same problem

Partially implements blueprint: rally-use-command

Change-Id: I1e62e26d259bd9e81f1c008058ee9ce91cc3290c
2014-01-23 22:14:01 +01:00

327 lines
12 KiB
Python

# Copyright 2013: Mirantis Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
""" CLI interface for Rally. """
from __future__ import print_function
import json
import pprint
import sys
import os
import prettytable
from rally.cmd import cliutils
from rally.cmd import envutils
from rally import db
from rally import exceptions
from rally import fileutils
from rally.openstack.common.gettextutils import _
from rally.orchestrator import api
from rally import processing
class DeploymentCommands(object):
@cliutils.args('--filename', type=str, required=True,
help='A path to the configuration file of the deployment.')
@cliutils.args('--name', type=str, required=True,
help='A name of the deployment.')
def create(self, filename, name):
"""Create a new deployment on the basis of configuration file.
:param filename: a path to the configuration file
:param name: a name of the deployment
"""
with open(filename) as f:
config = json.load(f)
deployment = api.create_deploy(config, name)
self.list(deployment_list=[deployment])
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def recreate(self, deploy_id=None):
"""Destroy and create an existing deployment.
:param deploy_id: a UUID of the deployment
"""
deploy_id = deploy_id or envutils.default_deployment_id()
api.recreate_deploy(deploy_id)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def destroy(self, deploy_id=None):
"""Destroy the deployment.
Release resources that are allocated for the deployment. The
Deployment, related tasks and their results are also deleted.
:param deploy_id: a UUID of the deployment
"""
deploy_id = deploy_id or envutils.default_deployment_id()
api.destroy_deploy(deploy_id)
def list(self, deployment_list=None):
"""Print list of deployments."""
headers = ['uuid', 'created_at', 'name', 'status']
table = prettytable.PrettyTable(headers)
deployment_list = deployment_list or db.deployment_list()
for t in deployment_list:
r = [str(t[column]) for column in headers]
table.add_row(r)
print(table)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def config(self, deploy_id=None):
"""Print on stdout a config of the deployment in JSON format.
:param deploy_id: a UUID of the deployment
"""
deploy_id = deploy_id or envutils.default_deployment_id()
deploy = db.deployment_get(deploy_id)
print(json.dumps(deploy['config']))
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def endpoint(self, deploy_id=None):
"""Print endpoint of the deployment.
:param deploy_id: a UUID of the deployment
"""
deploy_id = deploy_id or envutils.default_deployment_id()
headers = ['auth_url', 'username', 'password', 'tenant_name']
table = prettytable.PrettyTable(headers)
endpoint = db.deployment_get(deploy_id)['endpoint']
table.add_row([endpoint.get(m, '') for m in headers])
print(table)
class TaskCommands(object):
@cliutils.args('--deploy-id', type=str, dest='deploy_id', required=False,
help='UUID of the deployment')
@cliutils.args('--task',
help='Path to the file with full configuration of task')
def start(self, task, deploy_id=None):
"""Run a benchmark task.
:param task: a file with json configration
:param deploy_id: a UUID of a deployment
"""
deploy_id = deploy_id or envutils.default_deployment_id()
with open(task) as task_file:
config_dict = json.load(task_file)
try:
task = api.create_task(deploy_id)
self.list(task_list=[task])
api.start_task(deploy_id, config_dict, task=task)
self.detailed(task_id=task['uuid'])
except exceptions.InvalidArgumentsException:
print(_("Reason: %s") % sys.exc_info()[1])
@cliutils.args('--task-id', type=str, dest='task_id', help='UUID of task')
def abort(self, task_id):
"""Force abort task
:param task_id: Task uuid
"""
api.abort_task(task_id)
@cliutils.args('--task-id', type=str, dest='task_id', help='UUID of task')
def status(self, task_id):
"""Get 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']})
@cliutils.args(
'--task-id', type=str, dest='task_id',
help=('uuid of task, if --task-id is "last" results of most '
'recently created task will be displayed.'))
def detailed(self, task_id):
"""Get detailed information about task
:param task_id: Task uuid
Prints detailed information of task.
"""
if task_id == "last":
task = db.task_get_detailed_last()
task_id = task.uuid
else:
task = db.task_get_detailed(task_id)
if task is None:
print("The task %s can not be found" % task_id)
return
print()
print("=" * 80)
print(_("Task %(task_id)s is %(status)s.")
% {'task_id': task_id, 'status': task['status']})
for result in task["results"]:
key = result["key"]
print("-" * 80)
print()
print("test scenario %s" % key["name"])
print("args position %s" % key["pos"])
print("args values:")
pprint.pprint(key["kw"])
raw = result["data"]["raw"]
times = map(lambda x: x['time'],
filter(lambda r: not r['error'], raw))
table = prettytable.PrettyTable(["max (sec)", "avg (sec)",
"min (sec)", "success/total",
"total times"])
if times:
table.add_row([max(times), sum(times) / len(times), min(times),
float(len(times)) / len(raw), len(raw)])
else:
table.add_row(['n/a', 'n/a', 'n/a', 0, len(raw)])
print(table)
#NOTE(hughsaunders): ssrs=scenario specific results
ssrs = []
for result in raw:
try:
ssrs.append(result['scenario_output']['data'])
except (KeyError, TypeError):
# No SSRs in this result
pass
if ssrs:
sys.stdout.flush()
keys = set()
for ssr in ssrs:
keys.update(ssr.keys())
ssr_table = prettytable.PrettyTable(
["Key", "max", "avg", "min"])
for key in keys:
values = [float(ssr[key]) for ssr in ssrs if key in ssr]
if values:
row = [str(key),
max(values),
sum(values) / len(values),
min(values)]
else:
row = [str(key)] + ['n/a'] * 3
ssr_table.add_row(row)
print("\nScenario Specific Results\n")
print(ssr_table)
for result in raw:
if result['scenario_output']['errors']:
print(result['scenario_output']['errors'])
@cliutils.args('--task-id', type=str, dest='task_id', help='uuid of task')
@cliutils.args('--pretty', type=str, help=('pretty print (pprint) '
'or json print (json)'))
def results(self, task_id, pretty=False):
"""Print raw results of task.
:param task_id: Task uuid
:param pretty: Pretty print (pprint) or not (json)
"""
results = map(lambda x: {"key": x["key"], 'result': x['data']['raw']},
db.task_result_get_all_by_uuid(task_id))
if not pretty or pretty == 'json':
print(json.dumps(results))
elif pretty == 'pprint':
print()
pprint.pprint(results)
print()
else:
print(_("Wrong value for --pretty=%s") % pretty)
def list(self, task_list=None):
"""Print a list of all tasks."""
headers = ['uuid', 'created_at', 'status', 'failed']
table = prettytable.PrettyTable(headers)
task_list = task_list or db.task_list()
for t in task_list:
r = [t['uuid'], str(t['created_at']), t['status'], t['failed']]
table.add_row(r)
print(table)
@cliutils.args('--task-id', type=str, dest='task_id', help='uuid of task')
@cliutils.args('--force', action='store_true', help='force delete')
def delete(self, task_id, force):
"""Delete a specific task and related results.
:param task_id: Task uuid
:param force: Force delete or not
"""
api.delete_task(task_id, force=force)
@cliutils.args('--plot-type', type=str, help='plot type; available types: '
', '.join(processing.PLOTS.keys()))
@cliutils.args('--field-name', type=str, help='field from the task config '
'to aggregate the data on: concurrent/times/...')
@cliutils.args('--task-id', type=str, help='uuid of task')
def plot(self, plot_type, aggregated_field, task_id):
if plot_type in processing.PLOTS:
processing.PLOTS[plot_type](task_id, aggregated_field)
else:
print("Plot type '%s' not supported." % plot_type)
class UseCommands(object):
def deployment(self, deploy_id):
"""Set the RALLY_DEPLOYMENT env var to be used by all CLI commands
:param deploy_id: a UUID of a deployment
"""
print('Using deployment : %s' % deploy_id)
if not os.path.exists(os.path.expanduser('~/.rally/')):
os.makedirs(os.path.expanduser('~/.rally/'))
expanded_path = os.path.expanduser('~/.rally/deployment')
fileutils.update_env_file(expanded_path, 'RALLY_DEPLOYMENT', deploy_id)
def deprecated():
print("\n\n---\n\nopenstack-rally and openstack-rally-manage are "
"deprecated, please use rally and rally-manage\n\n---\n\n")
main()
def main():
categories = {
'task': TaskCommands,
'deployment': DeploymentCommands,
'use': UseCommands,
}
cliutils.run(sys.argv, categories)
if __name__ == '__main__':
main()