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:
31
rally/cmd/envutils.py
Normal file
31
rally/cmd/envutils.py
Normal 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
|
@@ -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
72
rally/fileutils.py
Normal 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)
|
38
tests/cmd/test_envutils.py
Normal file
38
tests/cmd/test_envutils.py
Normal 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'))
|
@@ -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
47
tests/test_fileutils.py
Normal 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)
|
Reference in New Issue
Block a user