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
This commit is contained in:
Julien Vey
2014-01-22 15:25:40 +01:00
parent e21cd3f42d
commit b74ea2e4f8
6 changed files with 287 additions and 12 deletions

31
rally/cmd/envutils.py Normal file
View File

@@ -0,0 +1,31 @@
# 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 os
from rally import exceptions
from rally import fileutils
def default_deployment_id():
try:
deploy_id = os.environ['RALLY_DEPLOYMENT']
except KeyError:
fileutils.load_env_file(os.path.expanduser('~/.rally/deployment'))
try:
deploy_id = os.environ['RALLY_DEPLOYMENT']
except KeyError:
raise exceptions.InvalidArgumentsException(
"deploy-id argument is missing")
return deploy_id

View File

@@ -21,11 +21,14 @@ 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
@@ -48,18 +51,19 @@ class DeploymentCommands(object):
deployment = api.create_deploy(config, name)
self.list(deployment_list=[deployment])
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=True,
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def recreate(self, deploy_id):
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=True,
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def destroy(self, deploy_id):
def destroy(self, deploy_id=None):
"""Destroy the deployment.
Release resources that are allocated for the deployment. The
@@ -67,6 +71,7 @@ class DeploymentCommands(object):
: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):
@@ -81,23 +86,25 @@ class DeploymentCommands(object):
print(table)
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=True,
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def config(self, deploy_id):
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=True,
@cliutils.args('--deploy-id', dest='deploy_id', type=str, required=False,
help='UUID of a deployment.')
def endpoint(self, deploy_id):
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']
@@ -107,16 +114,17 @@ class DeploymentCommands(object):
class TaskCommands(object):
@cliutils.args('--deploy-id', type=str, dest='deploy_id', required=True,
@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, deploy_id, task):
def start(self, task, deploy_id=None):
"""Run a benchmark task.
:param deploy_id: a UUID of a deployment
: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:
@@ -285,6 +293,20 @@ class TaskCommands(object):
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")
@@ -295,6 +317,7 @@ def main():
categories = {
'task': TaskCommands,
'deployment': DeploymentCommands,
'use': UseCommands,
}
cliutils.run(sys.argv, categories)

72
rally/fileutils.py Normal file
View File

@@ -0,0 +1,72 @@
# 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 os
def _read_env_file(path, except_env=None):
"""Read the environment variable file.
:param path: the path of the file
:param except_env: the environment variable to avoid in the output
:returns: the content of the original file except the line starting with
the except_env parameter
"""
output = []
if os.path.exists(path):
with open(path, 'r') as env_file:
content = env_file.readlines()
print(content)
for line in content:
if except_env is None or \
not line.startswith("%s=" % except_env):
output.append(line)
return output
def load_env_file(path):
"""Load the environment variable file into os.environ.
:param path: the path of the file
"""
if os.path.exists(path):
content = _read_env_file(path)
for line in content:
(key, sep, value) = line.partition("=")
os.environ[key] = value.rstrip()
def _rewrite_env_file(path, initial_content):
"""Rewrite the environment variable file.
:param path: the path of the file
:param initial_content: the original content of the file
"""
with open(path, 'w+') as env_file:
for line in initial_content:
env_file.write(line)
def update_env_file(path, env_key, env_value):
"""Update the environment variable file.
:param path: the path of the file
:param env_key: the key to update
:param env_value: the value of the property to update
"""
output = _read_env_file(path, env_key)
output.append('%s=%s' % (env_key, env_value))
_rewrite_env_file(path, output)

View File

@@ -0,0 +1,38 @@
# 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
from rally.cmd import envutils
from rally import exceptions
from rally.openstack.common import test
class EnvUtilsTestCase(test.BaseTestCase):
@mock.patch.dict(os.environ, values={'RALLY_DEPLOYMENT': 'my_deploy_id'},
clear=True)
def test_get_deployment_id_in_env(self):
deploy_id = envutils.default_deployment_id()
self.assertEqual('my_deploy_id', deploy_id)
@mock.patch.dict(os.environ, values={}, clear=True)
@mock.patch('rally.cmd.envutils.fileutils.load_env_file')
def test_get_deployment_id_with_exception(self, mock_file):
self.assertRaises(exceptions.InvalidArgumentsException,
envutils.default_deployment_id)
mock_file.assert_called_once_with(os.path.expanduser(
'~/.rally/deployment'))

View File

@@ -14,9 +14,11 @@
# under the License.
import mock
import os
import uuid
from rally.cmd import main
from rally import exceptions
from rally.openstack.common import test
@@ -39,10 +41,16 @@ class TaskCommandsTestCase(test.BaseTestCase):
def test_start(self, mock_api, mock_create_task,
mock_task_detailed):
deploy_id = str(uuid.uuid4())
self.task.start(deploy_id, 'path_to_config.json')
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:
@@ -129,8 +137,64 @@ class DeploymentCommandsTestCase(test.BaseTestCase):
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.path.exists')
@mock.patch('rally.cmd.main.fileutils.update_env_file')
def test_deployment(self, mock_file, mock_path):
deploy_id = str(uuid.uuid4())
mock_path.return_value = True
self.use.deployment(deploy_id)
mock_path.assert_called_once_with(os.path.expanduser('~/.rally/'))
mock_file.assert_called_once_with(os.path.expanduser(
'~/.rally/deployment'), 'RALLY_DEPLOYMENT', deploy_id)

47
tests/test_fileutils.py Normal file
View File

@@ -0,0 +1,47 @@
# 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
from rally import fileutils
from tests import test
class FileUtilsTestCase(test.TestCase):
@mock.patch('os.path.exists')
@mock.patch.dict('os.environ', values={}, clear=True)
def test_load_env_vile(self, mock_path):
file_data = ["FAKE_ENV=fake_env\n"]
mock_path.return_value = True
with mock.patch('rally.fileutils.open', mock.mock_open(
read_data=file_data), create=True) as mock_file:
mock_file.return_value.readlines.return_value = file_data
fileutils.load_env_file('path_to_file')
self.assertIn('FAKE_ENV', os.environ)
mock_file.return_value.readlines.assert_called_once_with()
@mock.patch('os.path.exists')
def test_update_env_file(self, mock_path):
file_data = ["FAKE_ENV=old_value\n", "FAKE_ENV2=any\n"]
mock_path.return_value = True
with mock.patch('rally.fileutils.open', mock.mock_open(
read_data=file_data), create=True) as mock_file:
mock_file.return_value.readlines.return_value = file_data
fileutils.update_env_file('path_to_file', 'FAKE_ENV', 'new_value')
calls = [mock.call('FAKE_ENV2=any\n'), mock.call(
'FAKE_ENV=new_value')]
mock_file.return_value.readlines.assert_called_once_with()
mock_file.return_value.write.assert_has_calls(calls)