Move every rally command to its own module

Move all the currently supported rally commands (i.e. deployment, task,
use) and their equivalent tests that are described as classes in
the 'main.py' module (and 'test_main.py' module), to separate modules in
a new directory. This is done for organizing the code of the rally command
line utility, before it become too big.

Implements: blueprint rally-show-command
Change-Id: I5982f854dfe64604ae112d93fb7ea68f72657b98
This commit is contained in:
Tzanetos Balitsaris 2014-02-12 20:57:45 +00:00
parent ec32cd04ce
commit d674da2222
10 changed files with 777 additions and 658 deletions

View File

View File

@ -0,0 +1,173 @@
# 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.
""" Rally command: deployment """
from __future__ import print_function
import json
import os
import prettytable
import sys
from rally.cmd import cliutils
from rally.cmd.commands import use as _use
from rally.cmd import envutils
from rally import db
from rally import exceptions
from rally.orchestrator import api
from rally import osclients
class DeploymentCommands(object):
@cliutils.args('--name', type=str, required=True,
help='A name of the deployment.')
@cliutils.args('--fromenv', action='store_true',
help='Read environment variables instead of config file')
@cliutils.args('--filename', type=str, required=False,
help='A path to the configuration file of the '
'deployment.')
@cliutils.args('--no-use', action='store_false', dest='use',
help='Don\'t set new deployment as default for'
' future operations')
def create(self, name, fromenv=False, filename=None, use=False):
"""Create a new deployment on the basis of configuration file.
:param fromenv: boolean, read environment instead of config file
:param filename: a path to the configuration file
:param name: a name of the deployment
"""
if fromenv:
required_env_vars = ["OS_USERNAME", "OS_PASSWORD", "OS_AUTH_URL",
"OS_TENANT_NAME"]
unavailable_vars = [v for v in required_env_vars
if v not in os.environ]
if unavailable_vars:
print("The following environment variables are required but "
"not set: %s" % ' '.join(unavailable_vars))
return
config = {
"name": "DummyEngine",
"endpoint": {
"auth_url": os.environ['OS_AUTH_URL'],
"username": os.environ['OS_USERNAME'],
"password": os.environ['OS_PASSWORD'],
"tenant_name": os.environ['OS_TENANT_NAME']
}
}
else:
if not filename:
print("Either --filename or --fromenv is required")
return
with open(filename) as f:
config = json.load(f)
deployment = api.create_deploy(config, name)
self.list(deployment_list=[deployment])
if use:
_use.UseCommands().deployment(deployment['uuid'])
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
@envutils.deploy_id_default
def recreate(self, deploy_id=None):
"""Destroy and create an existing deployment.
:param deploy_id: a UUID of the deployment
"""
api.recreate_deploy(deploy_id)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
@envutils.deploy_id_default
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
"""
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.')
@envutils.deploy_id_default
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 = 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.')
@envutils.deploy_id_default
def endpoint(self, deploy_id=None):
"""Print endpoint of the deployment.
:param deploy_id: a UUID of the deployment
"""
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)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
@envutils.deploy_id_default
def check(self, deploy_id=None):
"""Check the deployment.
Check keystone authentication and list all available services.
:param deploy_id: a UUID of the deployment
"""
headers = ['services', 'type', 'status']
table = prettytable.PrettyTable(headers)
try:
endpoint = db.deployment_get(deploy_id)['endpoint']
clients = osclients.Clients(username=endpoint['username'],
password=endpoint['password'],
tenant_name=endpoint['tenant_name'],
auth_url=endpoint['auth_url'])
client = clients.get_verified_keystone_client()
print("keystone endpoints are valid and following services are "
"available:")
for service in client.service_catalog.get_data():
table.add_row([service['name'], service['type'], 'Available'])
except exceptions.InvalidArgumentsException:
table.add_row(['keystone', 'identity', 'Error'])
print(_("Authentication Issues: %s.")
% sys.exc_info()[1])
print(table)

240
rally/cmd/commands/task.py Normal file
View File

@ -0,0 +1,240 @@
# 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.
""" Rally command: task """
from __future__ import print_function
import collections
import json
import pprint
import prettytable
import sys
from rally.cmd import cliutils
from rally.cmd import envutils
from rally import db
from rally import exceptions
from rally.openstack.common.gettextutils import _
from rally.orchestrator import api
from rally import processing
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')
@envutils.deploy_id_default
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
"""
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.
"""
def _print_atomic_actions_time(raw):
atime_merged = []
for r in raw:
if 'atomic_actions_time' in r:
for a in r['atomic_actions_time']:
atime_merged.append(a)
times_by_action = collections.defaultdict(list)
for at in atime_merged:
times_by_action[at['action']].append(at['duration'])
if times_by_action:
atomic_action_table = prettytable.PrettyTable(
['action', 'max (sec)', 'avg (sec)', 'min (sec)'])
for k, v in times_by_action.iteritems():
atomic_action_table.add_row([k, max(v), sum(v) / len(v),
min(v)])
print(atomic_action_table)
print()
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. Failed: %(failed)s")
% {'task_id': task_id,
'status': task['status'],
'failed': task['failed']
})
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"])
if not result["data"]["validation"]["is_valid"]:
print("-" * 80)
print(result["data"]["validation"]["exc_msg"])
continue
_print_atomic_actions_time(result["data"]["raw"])
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)

53
rally/cmd/commands/use.py Normal file
View File

@ -0,0 +1,53 @@
# 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.
""" Rally command: use """
import os
from rally import db
from rally import fileutils
class UseCommands(object):
def _update_openrc_deployment_file(self, deploy_id):
openrc_path = os.path.expanduser('~/.rally/openrc-%s' % deploy_id)
endpoint = db.deployment_get(deploy_id)['endpoint']
with open(openrc_path, 'w+') as env_file:
env_file.write('export OS_AUTH_URL=%(auth_url)s\n'
'export OS_USERNAME=%(username)s\n'
'export OS_PASSWORD=%(password)s\n'
'export OS_TENANT_NAME=%(tenant_name)s\n'
% endpoint)
expanded_path = os.path.expanduser('~/.rally/openrc')
if os.path.exists(expanded_path):
os.remove(expanded_path)
os.symlink(openrc_path, expanded_path)
def _update_rally_deployment_file(self, deploy_id):
expanded_path = os.path.expanduser('~/.rally/deployment')
fileutils.update_env_file(expanded_path, 'RALLY_DEPLOYMENT', deploy_id)
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/'))
self._update_rally_deployment_file(deploy_id)
self._update_openrc_deployment_file(deploy_id)

View File

@ -17,405 +17,12 @@
from __future__ import print_function
import collections
import json
import os
import pprint
import prettytable
import sys
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 osclients
from rally import processing
class DeploymentCommands(object):
@cliutils.args('--name', type=str, required=True,
help='A name of the deployment.')
@cliutils.args('--fromenv', action='store_true',
help='Read environment variables instead of config file')
@cliutils.args('--filename', type=str, required=False,
help='A path to the configuration file of the '
'deployment.')
@cliutils.args('--no-use', action='store_false', dest='use',
help='Don\'t set new deployment as default for'
' future operations')
def create(self, name, fromenv=False, filename=None, use=False):
"""Create a new deployment on the basis of configuration file.
:param fromenv: boolean, read environment instead of config file
:param filename: a path to the configuration file
:param name: a name of the deployment
"""
if fromenv:
required_env_vars = ["OS_USERNAME", "OS_PASSWORD", "OS_AUTH_URL",
"OS_TENANT_NAME"]
unavailable_vars = [v for v in required_env_vars
if v not in os.environ]
if unavailable_vars:
print("The following environment variables are required but "
"not set: %s" % ' '.join(unavailable_vars))
return
config = {
"name": "DummyEngine",
"endpoint": {
"auth_url": os.environ['OS_AUTH_URL'],
"username": os.environ['OS_USERNAME'],
"password": os.environ['OS_PASSWORD'],
"tenant_name": os.environ['OS_TENANT_NAME']
}
}
else:
if not filename:
print("Either --filename or --fromenv is required")
return
with open(filename) as f:
config = json.load(f)
deployment = api.create_deploy(config, name)
self.list(deployment_list=[deployment])
if use:
UseCommands().deployment(deployment['uuid'])
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
@envutils.deploy_id_default
def recreate(self, deploy_id=None):
"""Destroy and create an existing deployment.
:param deploy_id: a UUID of the deployment
"""
api.recreate_deploy(deploy_id)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
@envutils.deploy_id_default
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
"""
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.')
@envutils.deploy_id_default
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 = 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.')
@envutils.deploy_id_default
def endpoint(self, deploy_id=None):
"""Print endpoint of the deployment.
:param deploy_id: a UUID of the deployment
"""
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)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
@envutils.deploy_id_default
def check(self, deploy_id=None):
"""Check the deployment.
Check keystone authentication and list all available services.
:param deploy_id: a UUID of the deployment
"""
headers = ['services', 'type', 'status']
table = prettytable.PrettyTable(headers)
try:
endpoint = db.deployment_get(deploy_id)['endpoint']
clients = osclients.Clients(username=endpoint['username'],
password=endpoint['password'],
tenant_name=endpoint['tenant_name'],
auth_url=endpoint['auth_url'])
client = clients.get_verified_keystone_client()
print("keystone endpoints are valid and following services are "
"available:")
for service in client.service_catalog.get_data():
table.add_row([service['name'], service['type'], 'Available'])
except exceptions.InvalidArgumentsException:
table.add_row(['keystone', 'identity', 'Error'])
print(_("Authentication Issues: %s.")
% sys.exc_info()[1])
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')
@envutils.deploy_id_default
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
"""
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.
"""
def _print_atomic_actions_time(raw):
atime_merged = []
for r in raw:
if 'atomic_actions_time' in r:
for a in r['atomic_actions_time']:
atime_merged.append(a)
times_by_action = collections.defaultdict(list)
for at in atime_merged:
times_by_action[at['action']].append(at['duration'])
if times_by_action:
atomic_action_table = prettytable.PrettyTable(
['action', 'max (sec)', 'avg (sec)', 'min (sec)'])
for k, v in times_by_action.iteritems():
atomic_action_table.add_row([k, max(v), sum(v) / len(v),
min(v)])
print(atomic_action_table)
print()
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. Failed: %(failed)s")
% {'task_id': task_id,
'status': task['status'],
'failed': task['failed']
})
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"])
if not result["data"]["validation"]["is_valid"]:
print("-" * 80)
print(result["data"]["validation"]["exc_msg"])
continue
_print_atomic_actions_time(result["data"]["raw"])
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 _update_openrc_deployment_file(self, deploy_id):
openrc_path = os.path.expanduser('~/.rally/openrc-%s' % deploy_id)
endpoint = db.deployment_get(deploy_id)['endpoint']
with open(openrc_path, 'w+') as env_file:
env_file.write('export OS_AUTH_URL=%(auth_url)s\n'
'export OS_USERNAME=%(username)s\n'
'export OS_PASSWORD=%(password)s\n'
'export OS_TENANT_NAME=%(tenant_name)s\n'
% endpoint)
expanded_path = os.path.expanduser('~/.rally/openrc')
if os.path.exists(expanded_path):
os.remove(expanded_path)
os.symlink(openrc_path, expanded_path)
def _update_rally_deployment_file(self, deploy_id):
expanded_path = os.path.expanduser('~/.rally/deployment')
fileutils.update_env_file(expanded_path, 'RALLY_DEPLOYMENT', deploy_id)
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/'))
self._update_rally_deployment_file(deploy_id)
self._update_openrc_deployment_file(deploy_id)
from rally.cmd.commands import deployment
from rally.cmd.commands import task
from rally.cmd.commands import use
def deprecated():
@ -426,9 +33,9 @@ def deprecated():
def main():
categories = {
'task': TaskCommands,
'deployment': DeploymentCommands,
'use': UseCommands,
'deployment': deployment.DeploymentCommands,
'task': task.TaskCommands,
'use': use.UseCommands,
}
cliutils.run(sys.argv, categories)

View File

View File

@ -0,0 +1,126 @@
# 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.
import mock
import os
import uuid
from rally.cmd.commands import deployment
from rally import exceptions
from rally.openstack.common import test
class DeploymentCommandsTestCase(test.BaseTestCase):
def setUp(self):
super(DeploymentCommandsTestCase, self).setUp()
self.deployment = deployment.DeploymentCommands()
@mock.patch('rally.cmd.commands.deployment.api.create_deploy')
@mock.patch('rally.cmd.commands.deployment.open',
mock.mock_open(read_data='{"some": "json"}'),
create=True)
def test_create(self, mock_create):
self.deployment.create('fake_deploy', False, 'path_to_config.json')
mock_create.assert_called_once_with({'some': 'json'}, 'fake_deploy')
@mock.patch.dict(os.environ, {'OS_AUTH_URL': 'fake_auth_url',
'OS_USERNAME': 'fake_username',
'OS_PASSWORD': 'fake_password',
'OS_TENANT_NAME': 'fake_tenant_name'})
@mock.patch('rally.cmd.commands.deployment.api.create_deploy')
def test_createfromenv(self, mock_create):
self.deployment.create('from_env', True)
mock_create.assert_called_once_with(
{
"name": "DummyEngine",
"endpoint": {
"auth_url": 'fake_auth_url',
"username": 'fake_username',
"password": 'fake_password',
"tenant_name": 'fake_tenant_name'
}
},
'from_env'
)
@mock.patch('rally.cmd.commands.deployment.DeploymentCommands.list')
@mock.patch('rally.cmd.commands.use.UseCommands.deployment')
@mock.patch('rally.cmd.commands.deployment.api.create_deploy',
return_value=dict(uuid='uuid'))
@mock.patch('rally.cmd.commands.deployment.open',
mock.mock_open(read_data='{"uuid": "uuid"}'),
create=True)
def test_create_and_use(self, mock_create, mock_use_deployment,
mock_list):
self.deployment.create('fake_deploy', False, 'path_to_config.json',
True)
mock_create.assert_called_once_with({'uuid': 'uuid'}, 'fake_deploy')
mock_use_deployment.assert_called_once_with('uuid')
@mock.patch('rally.cmd.commands.deployment.api.recreate_deploy')
def test_recreate(self, mock_recreate):
deploy_id = str(uuid.uuid4())
self.deployment.recreate(deploy_id)
mock_recreate.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.commands.deployment.envutils.'
'_default_deployment_id')
def test_recreate_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.recreate, None)
@mock.patch('rally.cmd.commands.deployment.api.destroy_deploy')
def test_destroy(self, mock_destroy):
deploy_id = str(uuid.uuid4())
self.deployment.destroy(deploy_id)
mock_destroy.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.commands.deployment.envutils.'
'_default_deployment_id')
def test_destroy_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.destroy, None)
@mock.patch('rally.cmd.commands.deployment.db.deployment_get')
def test_config(self, mock_deployment):
deploy_id = str(uuid.uuid4())
value = {'config': 'config'}
mock_deployment.return_value = value
self.deployment.config(deploy_id)
mock_deployment.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.commands.deployment.envutils.'
'_default_deployment_id')
def test_config_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.config, None)
@mock.patch('rally.cmd.commands.deployment.db.deployment_get')
def test_endpoint(self, mock_deployment):
deploy_id = str(uuid.uuid4())
value = {'endpoint': {}}
mock_deployment.return_value = value
self.deployment.endpoint(deploy_id)
mock_deployment.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.commands.deployment.envutils.'
'_default_deployment_id')
def test_deploy_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.endpoint, None)

View File

@ -0,0 +1,121 @@
# 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.
import mock
import uuid
from rally.cmd.commands import task
from rally import exceptions
from rally.openstack.common import test
class TaskCommandsTestCase(test.BaseTestCase):
def setUp(self):
super(TaskCommandsTestCase, self).setUp()
self.task = task.TaskCommands()
@mock.patch('rally.cmd.commands.task.TaskCommands.detailed')
@mock.patch('rally.orchestrator.api.create_task',
return_value=dict(uuid='fc1a9bbe-1ead-4740-92b5-0feecf421634',
created_at='2014-01-14 09:14:45.395822',
status='init',
failed=False))
@mock.patch('rally.cmd.commands.task.api.start_task')
@mock.patch('rally.cmd.commands.task.open',
mock.mock_open(read_data='{"some": "json"}'),
create=True)
def test_start(self, mock_api, mock_create_task,
mock_task_detailed):
deploy_id = str(uuid.uuid4())
self.task.start('path_to_config.json', deploy_id)
mock_api.assert_called_once_with(deploy_id, {u'some': u'json'},
task=mock_create_task.return_value)
@mock.patch('rally.cmd.commands.task.envutils._default_deployment_id')
def test_start_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.task.start, 'path_to_config.json', None)
def test_abort(self):
test_uuid = str(uuid.uuid4())
with mock.patch("rally.cmd.commands.task.api") as mock_api:
mock_api.abort_task = mock.MagicMock()
self.task.abort(test_uuid)
task.api.abort_task.assert_called_once_with(test_uuid)
def test_status(self):
test_uuid = str(uuid.uuid4())
value = {'task_id': "task", "status": "status"}
with mock.patch("rally.cmd.commands.task.db") as mock_db:
mock_db.task_get = mock.MagicMock(return_value=value)
self.task.status(test_uuid)
mock_db.task_get.assert_called_once_with(test_uuid)
@mock.patch('rally.cmd.commands.task.db')
def test_detailed(self, mock_db):
test_uuid = str(uuid.uuid4())
value = {'task_id': "task",
"status": "status",
"results": [],
"failed": False
}
mock_db.task_get_detailed = mock.MagicMock(return_value=value)
self.task.detailed(test_uuid)
mock_db.task_get_detailed.assert_called_once_with(test_uuid)
@mock.patch('rally.cmd.commands.task.db')
def test_detailed_wrong_id(self, mock_db):
test_uuid = str(uuid.uuid4())
mock_db.task_get_detailed = mock.MagicMock(return_value=None)
self.task.detailed(test_uuid)
mock_db.task_get_detailed.assert_called_once_with(test_uuid)
@mock.patch('rally.cmd.commands.task.db')
def test_results(self, mock_db):
test_uuid = str(uuid.uuid4())
value = [
{'key': 'key', 'data': {'raw': 'raw'}}
]
mock_db.task_result_get_all_by_uuid.return_value = value
self.task.results(test_uuid)
mock_db.task_result_get_all_by_uuid.assert_called_once_with(test_uuid)
def test_list(self):
db_response = [
{'uuid': 'a', 'created_at': 'b', 'status': 'c', 'failed': True}
]
with mock.patch("rally.cmd.commands.task.db") as mock_db:
mock_db.task_list = mock.MagicMock(return_value=db_response)
self.task.list()
mock_db.task_list.assert_called_once_with()
def test_delete(self):
task_uuid = str(uuid.uuid4())
force = False
with mock.patch("rally.cmd.commands.task.api") as mock_api:
mock_api.delete_task = mock.Mock()
self.task.delete(task_uuid, force)
mock_api.delete_task.assert_called_once_with(task_uuid,
force=force)
def test_plot(self):
test_uuid = str(uuid.uuid4())
mock_plot = mock.Mock()
PLOTS = {"aggregated": mock_plot}
with mock.patch("rally.cmd.commands.task.processing.PLOTS", new=PLOTS):
self.task.plot("aggregated", "concurrent", test_uuid)
mock_plot.assert_called_once_with(test_uuid, "concurrent")

View File

@ -0,0 +1,58 @@
# 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.
import mock
import os
import uuid
from rally.cmd.commands import use
from rally.openstack.common import test
class UseCommandsTestCase(test.BaseTestCase):
def setUp(self):
super(UseCommandsTestCase, self).setUp()
self.use = use.UseCommands()
@mock.patch('os.remove')
@mock.patch('os.symlink')
@mock.patch('rally.cmd.commands.use.db.deployment_get')
@mock.patch('os.path.exists')
@mock.patch('rally.cmd.commands.use.fileutils.update_env_file')
def test_deployment(self, mock_env, mock_path, mock_deployment,
mock_symlink, mock_remove):
deploy_id = str(uuid.uuid4())
endpoint = {'endpoint': {'auth_url': 'fake_auth_url',
'username': 'fake_username',
'password': 'fake_password',
'tenant_name': 'fake_tenant_name'}}
mock_deployment.return_value = endpoint
mock_path.return_value = True
with mock.patch('rally.cmd.commands.use.open', mock.mock_open(),
create=True) as mock_file:
self.use.deployment(deploy_id)
self.assertEqual(2, mock_path.call_count)
mock_env.assert_called_once_with(os.path.expanduser(
'~/.rally/deployment'), 'RALLY_DEPLOYMENT', deploy_id)
mock_file.return_value.write.assert_called_once_with(
'export OS_AUTH_URL=fake_auth_url\n'
'export OS_USERNAME=fake_username\n'
'export OS_PASSWORD=fake_password\n'
'export OS_TENANT_NAME=fake_tenant_name\n')
mock_symlink.assert_called_once_with(
os.path.expanduser('~/.rally/openrc-%s' % deploy_id),
os.path.expanduser('~/.rally/openrc'))
mock_remove.assert_called_once_with(os.path.expanduser(
'~/.rally/openrc'))

View File

@ -1,259 +0,0 @@
# 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.
import mock
import os
import uuid
from rally.cmd import main
from rally import exceptions
from rally.openstack.common import test
class TaskCommandsTestCase(test.BaseTestCase):
def setUp(self):
super(TaskCommandsTestCase, self).setUp()
self.task = main.TaskCommands()
@mock.patch('rally.cmd.main.TaskCommands.detailed')
@mock.patch('rally.orchestrator.api.create_task',
return_value=dict(uuid='fc1a9bbe-1ead-4740-92b5-0feecf421634',
created_at='2014-01-14 09:14:45.395822',
status='init',
failed=False))
@mock.patch('rally.cmd.main.api.start_task')
@mock.patch('rally.cmd.main.open',
mock.mock_open(read_data='{"some": "json"}'),
create=True)
def test_start(self, mock_api, mock_create_task,
mock_task_detailed):
deploy_id = str(uuid.uuid4())
self.task.start('path_to_config.json', deploy_id)
mock_api.assert_called_once_with(deploy_id, {u'some': u'json'},
task=mock_create_task.return_value)
@mock.patch('rally.cmd.main.envutils._default_deployment_id')
def test_start_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.task.start, 'path_to_config.json', None)
def test_abort(self):
test_uuid = str(uuid.uuid4())
with mock.patch("rally.cmd.main.api") as mock_api:
mock_api.abort_task = mock.MagicMock()
self.task.abort(test_uuid)
main.api.abort_task.assert_called_once_with(test_uuid)
def test_status(self):
test_uuid = str(uuid.uuid4())
value = {'task_id': "task", "status": "status"}
with mock.patch("rally.cmd.main.db") as mock_db:
mock_db.task_get = mock.MagicMock(return_value=value)
self.task.status(test_uuid)
mock_db.task_get.assert_called_once_with(test_uuid)
@mock.patch('rally.cmd.main.db')
def test_detailed(self, mock_db):
test_uuid = str(uuid.uuid4())
value = {'task_id': "task",
"status": "status",
"results": [],
"failed": False
}
mock_db.task_get_detailed = mock.MagicMock(return_value=value)
self.task.detailed(test_uuid)
mock_db.task_get_detailed.assert_called_once_with(test_uuid)
@mock.patch('rally.cmd.main.db')
def test_detailed_wrong_id(self, mock_db):
test_uuid = str(uuid.uuid4())
mock_db.task_get_detailed = mock.MagicMock(return_value=None)
self.task.detailed(test_uuid)
mock_db.task_get_detailed.assert_called_once_with(test_uuid)
@mock.patch('rally.cmd.main.db')
def test_results(self, mock_db):
test_uuid = str(uuid.uuid4())
value = [
{'key': 'key', 'data': {'raw': 'raw'}}
]
mock_db.task_result_get_all_by_uuid.return_value = value
self.task.results(test_uuid)
mock_db.task_result_get_all_by_uuid.assert_called_once_with(test_uuid)
def test_list(self):
db_response = [
{'uuid': 'a', 'created_at': 'b', 'status': 'c', 'failed': True}
]
with mock.patch("rally.cmd.main.db") as mock_db:
mock_db.task_list = mock.MagicMock(return_value=db_response)
self.task.list()
mock_db.task_list.assert_called_once_with()
def test_delete(self):
task_uuid = str(uuid.uuid4())
force = False
with mock.patch("rally.cmd.main.api") as mock_api:
mock_api.delete_task = mock.Mock()
self.task.delete(task_uuid, force)
mock_api.delete_task.assert_called_once_with(task_uuid,
force=force)
def test_plot(self):
test_uuid = str(uuid.uuid4())
mock_plot = mock.Mock()
PLOTS = {"aggregated": mock_plot}
with mock.patch("rally.cmd.main.processing.PLOTS", new=PLOTS):
self.task.plot("aggregated", "concurrent", test_uuid)
mock_plot.assert_called_once_with(test_uuid, "concurrent")
class DeploymentCommandsTestCase(test.BaseTestCase):
def setUp(self):
super(DeploymentCommandsTestCase, self).setUp()
self.deployment = main.DeploymentCommands()
@mock.patch('rally.cmd.main.api.create_deploy')
@mock.patch('rally.cmd.main.open',
mock.mock_open(read_data='{"some": "json"}'),
create=True)
def test_create(self, mock_create):
self.deployment.create('fake_deploy', False, 'path_to_config.json')
mock_create.assert_called_once_with({'some': 'json'}, 'fake_deploy')
@mock.patch.dict(os.environ, {'OS_AUTH_URL': 'fake_auth_url',
'OS_USERNAME': 'fake_username',
'OS_PASSWORD': 'fake_password',
'OS_TENANT_NAME': 'fake_tenant_name'})
@mock.patch('rally.cmd.main.api.create_deploy')
def test_createfromenv(self, mock_create):
self.deployment.create('from_env', True)
mock_create.assert_called_once_with(
{
"name": "DummyEngine",
"endpoint": {
"auth_url": 'fake_auth_url',
"username": 'fake_username',
"password": 'fake_password',
"tenant_name": 'fake_tenant_name'
}
},
'from_env'
)
@mock.patch('rally.cmd.main.DeploymentCommands.list')
@mock.patch('rally.cmd.main.UseCommands.deployment')
@mock.patch('rally.cmd.main.api.create_deploy',
return_value=dict(uuid='uuid'))
@mock.patch('rally.cmd.main.open',
mock.mock_open(read_data='{"uuid": "uuid"}'),
create=True)
def test_create_and_use(self, mock_create, mock_use_deployment,
mock_list):
self.deployment.create('fake_deploy', False, 'path_to_config.json',
True)
mock_create.assert_called_once_with({'uuid': 'uuid'}, 'fake_deploy')
mock_use_deployment.assert_called_once_with('uuid')
@mock.patch('rally.cmd.main.api.recreate_deploy')
def test_recreate(self, mock_recreate):
deploy_id = str(uuid.uuid4())
self.deployment.recreate(deploy_id)
mock_recreate.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.main.envutils._default_deployment_id')
def test_recreate_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.recreate, None)
@mock.patch('rally.cmd.main.api.destroy_deploy')
def test_destroy(self, mock_destroy):
deploy_id = str(uuid.uuid4())
self.deployment.destroy(deploy_id)
mock_destroy.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.main.envutils._default_deployment_id')
def test_destroy_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.destroy, None)
@mock.patch('rally.cmd.main.db.deployment_get')
def test_config(self, mock_deployment):
deploy_id = str(uuid.uuid4())
value = {'config': 'config'}
mock_deployment.return_value = value
self.deployment.config(deploy_id)
mock_deployment.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.main.envutils._default_deployment_id')
def test_config_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.config, None)
@mock.patch('rally.cmd.main.db.deployment_get')
def test_endpoint(self, mock_deployment):
deploy_id = str(uuid.uuid4())
value = {'endpoint': {}}
mock_deployment.return_value = value
self.deployment.endpoint(deploy_id)
mock_deployment.assert_called_once_with(deploy_id)
@mock.patch('rally.cmd.main.envutils._default_deployment_id')
def test_deploy_no_deploy_id(self, mock_default):
mock_default.side_effect = exceptions.InvalidArgumentsException
self.assertRaises(exceptions.InvalidArgumentsException,
self.deployment.endpoint, None)
class UseCommandsTestCase(test.BaseTestCase):
def setUp(self):
super(UseCommandsTestCase, self).setUp()
self.use = main.UseCommands()
@mock.patch('os.remove')
@mock.patch('os.symlink')
@mock.patch('rally.cmd.main.db.deployment_get')
@mock.patch('os.path.exists')
@mock.patch('rally.cmd.main.fileutils.update_env_file')
def test_deployment(self, mock_env, mock_path, mock_deployment,
mock_symlink, mock_remove):
deploy_id = str(uuid.uuid4())
endpoint = {'endpoint': {'auth_url': 'fake_auth_url',
'username': 'fake_username',
'password': 'fake_password',
'tenant_name': 'fake_tenant_name'}}
mock_deployment.return_value = endpoint
mock_path.return_value = True
with mock.patch('rally.cmd.main.open', mock.mock_open(),
create=True) as mock_file:
self.use.deployment(deploy_id)
self.assertEqual(2, mock_path.call_count)
mock_env.assert_called_once_with(os.path.expanduser(
'~/.rally/deployment'), 'RALLY_DEPLOYMENT', deploy_id)
mock_file.return_value.write.assert_called_once_with(
'export OS_AUTH_URL=fake_auth_url\n'
'export OS_USERNAME=fake_username\n'
'export OS_PASSWORD=fake_password\n'
'export OS_TENANT_NAME=fake_tenant_name\n')
mock_symlink.assert_called_once_with(
os.path.expanduser('~/.rally/openrc-%s' % deploy_id),
os.path.expanduser('~/.rally/openrc'))
mock_remove.assert_called_once_with(os.path.expanduser(
'~/.rally/openrc'))