diff --git a/requirements.txt b/requirements.txt index 227cf33d..744dd492 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,12 @@ pbr<2.0,>=1.3 Babel>=1.3 +cliff>=1.14.0 # Apache-2.0 netaddr>=0.7.12 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.9.0 # Apache-2.0 python-keystoneclient>=1.6.0 +python-openstackclient>=1.5.0 requests>=2.5.2 six>=1.9.0 PrettyTable<0.8,>=0.7 diff --git a/saharaclient/osc/__init__.py b/saharaclient/osc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/saharaclient/osc/plugin.py b/saharaclient/osc/plugin.py new file mode 100644 index 00000000..9329703b --- /dev/null +++ b/saharaclient/osc/plugin.py @@ -0,0 +1,57 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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. + +from openstackclient.common import utils +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) + +DEFAULT_DATA_PROCESSING_API_VERSION = "1.1" +API_VERSION_OPTION = "os_data_processing_api_version" +API_NAME = "data_processing" +API_VERSIONS = { + "1.1": "saharaclient.api.client.Client" +} + + +def make_client(instance): + data_processing_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + LOG.debug('Instantiating data-processing client: %s', + data_processing_client) + + client = data_processing_client( + session=instance.session, + region_name=instance._region_name, + cacert=instance._cacert, + insecure=instance._insecure + ) + return client + + +def build_option_parser(parser): + """Hook to add global options.""" + parser.add_argument( + "--os-data-processing-api-version", + metavar="", + default=utils.env( + 'OS_DATA_PROCESSING_API_VERSION', + default=DEFAULT_DATA_PROCESSING_API_VERSION), + help=("Data processing API version, default=" + + DEFAULT_DATA_PROCESSING_API_VERSION + + ' (Env: OS_DATA_PROCESSING_API_VERSION)')) + return parser diff --git a/saharaclient/osc/v1/__init__.py b/saharaclient/osc/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/saharaclient/osc/v1/plugins.py b/saharaclient/osc/v1/plugins.py new file mode 100644 index 00000000..3a992372 --- /dev/null +++ b/saharaclient/osc/v1/plugins.py @@ -0,0 +1,135 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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. + +from os import path + +from cliff import command +from cliff import lister +from cliff import show +from openstackclient.common import utils +from oslo_log import log as logging +from oslo_serialization import jsonutils + + +class ListPlugins(lister.Lister): + """Lists plugins""" + + log = logging.getLogger(__name__ + ".ListPlugins") + + def get_parser(self, prog_name): + parser = super(ListPlugins, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + client = self.app.client_manager.data_processing + data = client.plugins.list() + + if parsed_args.long: + columns = ('name', 'title', 'versions', 'description') + column_headers = [c.capitalize() for c in columns] + + else: + columns = ('name', 'versions') + column_headers = [c.capitalize() for c in columns] + + return ( + column_headers, + (utils.get_item_properties( + s, + columns, + formatters={ + 'versions': utils.format_list + }, + ) for s in data) + ) + + +class ShowPlugin(show.ShowOne): + """Display plugin details""" + + log = logging.getLogger(__name__ + ".ShowPlugin") + + def get_parser(self, prog_name): + parser = super(ShowPlugin, self).get_parser(prog_name) + parser.add_argument( + "plugin", + metavar="", + help="Name of the plugin to display", + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + client = self.app.client_manager.data_processing + + data = client.plugins.get(parsed_args.plugin).to_dict() + data['versions'] = utils.format_list(data['versions']) + + return self.dict2columns(data) + + +class GetPluginConfigs(command.Command): + """Get plugin configs""" + + log = logging.getLogger(__name__ + ".GetPluginConfigs") + + def get_parser(self, prog_name): + parser = super(GetPluginConfigs, self).get_parser(prog_name) + parser.add_argument( + "plugin", + metavar="", + help="Name of the plugin to provide config information about", + ) + parser.add_argument( + "version", + metavar="", + help="Version of the plugin to provide config information about", + ) + parser.add_argument( + '--file', + metavar="", + help='Destination file (defaults to plugin name)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + client = self.app.client_manager.data_processing + + if not parsed_args.file: + parsed_args.file = parsed_args.plugin + + data = client.plugins.get_version_details( + parsed_args.plugin, parsed_args.version).to_dict() + + if path.exists(parsed_args.file): + self.log.error('File "%s" already exists. Chose another one with ' + '--file argument.' % parsed_args.file) + else: + with open(parsed_args.file, 'w') as f: + jsonutils.dump(data, f, indent=4) + self.log.info( + '"%(plugin)s" plugin configs was saved in "%(file)s"' + 'file' % {'plugin': parsed_args.plugin, + 'file': parsed_args.file}) diff --git a/saharaclient/osc/v1/utils.py b/saharaclient/osc/v1/utils.py new file mode 100644 index 00000000..1108a748 --- /dev/null +++ b/saharaclient/osc/v1/utils.py @@ -0,0 +1,43 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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. + +from openstackclient.common import exceptions +from openstackclient.common import utils + + +def get_resource(manager, name_or_id): + resource = utils.find_resource(manager, name_or_id) + if isinstance(resource, list): + if not resource: + msg = "No %s with a name or ID of '%s' exists." % \ + (manager.resource_class.__name__.lower(), name_or_id) + raise exceptions.CommandError(msg) + if len(resource) > 1: + msg = "More than one %s exists with the name '%s'." % \ + (manager.resource_class.__name__.lower(), name_or_id) + raise exceptions.CommandError(msg) + return resource[0] + + else: + return resource + + +def prepare_data(data, fields): + new_data = {} + for f in fields: + if f in data: + new_data[f.replace('_', ' ').capitalize()] = data[f] + + return new_data diff --git a/saharaclient/tests/unit/osc/__init__.py b/saharaclient/tests/unit/osc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/saharaclient/tests/unit/osc/test_plugin.py b/saharaclient/tests/unit/osc/test_plugin.py new file mode 100644 index 00000000..7d057f47 --- /dev/null +++ b/saharaclient/tests/unit/osc/test_plugin.py @@ -0,0 +1,38 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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 + +from saharaclient.osc import plugin +from saharaclient.tests.unit import base + + +class TestDataProcessingPlugin(base.BaseTestCase): + + @mock.patch("saharaclient.api.client.Client") + def test_make_client(self, p_client): + + instance = mock.Mock() + instance._api_version = {"data_processing": '1.1'} + instance.session = 'session' + instance._region_name = 'region_name' + instance._cacert = 'cacert' + instance._insecure = 'insecure' + + plugin.make_client(instance) + p_client.assert_called_with(session='session', + region_name='region_name', + cacert='cacert', + insecure='insecure') diff --git a/saharaclient/tests/unit/osc/v1/__init__.py b/saharaclient/tests/unit/osc/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/saharaclient/tests/unit/osc/v1/fakes.py b/saharaclient/tests/unit/osc/v1/fakes.py new file mode 100644 index 00000000..d8062a3e --- /dev/null +++ b/saharaclient/tests/unit/osc/v1/fakes.py @@ -0,0 +1,26 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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 +from openstackclient.tests import utils + + +class TestDataProcessing(utils.TestCommand): + + def setUp(self): + super(TestDataProcessing, self).setUp() + + self.app.client_manager.data_processing = mock.Mock() diff --git a/saharaclient/tests/unit/osc/v1/test_plugins.py b/saharaclient/tests/unit/osc/v1/test_plugins.py new file mode 100644 index 00000000..1f222ebd --- /dev/null +++ b/saharaclient/tests/unit/osc/v1/test_plugins.py @@ -0,0 +1,160 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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 + +from saharaclient.api import plugins as api_plugins +from saharaclient.osc.v1 import plugins as osc_plugins +from saharaclient.tests.unit.osc.v1 import fakes + + +PLUGIN_INFO = {'name': 'fake', + 'title': 'Fake Plugin', + 'versions': ['0.1', '0.2'], + 'description': 'Plugin for tests'} + + +class TestPlugins(fakes.TestDataProcessing): + def setUp(self): + super(TestPlugins, self).setUp() + self.plugins_mock = self.app.client_manager.data_processing.plugins + self.plugins_mock.reset_mock() + + +class TestListPlugins(TestPlugins): + def setUp(self): + super(TestListPlugins, self).setUp() + self.plugins_mock.list.return_value = [api_plugins.Plugin( + None, PLUGIN_INFO)] + + # Command to test + self.cmd = osc_plugins.ListPlugins(self.app, None) + + def test_plugins_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Check that columns are correct + expected_columns = ['Name', 'Versions'] + self.assertEqual(expected_columns, columns) + + # Check that data is correct + expected_data = [('fake', '0.1, 0.2')] + self.assertEqual(expected_data, list(data)) + + def test_plugins_list_long(self): + arglist = ['--long'] + verifylist = [('long', True)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Check that columns are correct + expected_columns = ['Name', 'Title', 'Versions', 'Description'] + self.assertEqual(expected_columns, columns) + + # Check that data is correct + expected_data = [('fake', 'Fake Plugin', '0.1, 0.2', + 'Plugin for tests')] + self.assertEqual(expected_data, list(data)) + + +class TestShowPlugin(TestPlugins): + def setUp(self): + super(TestShowPlugin, self).setUp() + self.plugins_mock.get.return_value = api_plugins.Plugin( + None, PLUGIN_INFO) + + # Command to test + self.cmd = osc_plugins.ShowPlugin(self.app, None) + + def test_plugin_show(self): + arglist = ['fake'] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Check that correct arguments was passed + self.plugins_mock.get.assert_called_once_with('fake') + + # Check that columns are correct + expected_columns = ('description', 'name', 'title', 'versions') + self.assertEqual(expected_columns, columns) + + # Check that data is correct + expected_data = ('Plugin for tests', 'fake', 'Fake Plugin', '0.1, 0.2') + self.assertEqual(expected_data, data) + + +class TestGetPluginConfigs(TestPlugins): + def setUp(self): + super(TestGetPluginConfigs, self).setUp() + self.plugins_mock.get_version_details.return_value = ( + api_plugins.Plugin(None, PLUGIN_INFO)) + + # Command to test + self.cmd = osc_plugins.GetPluginConfigs(self.app, None) + + @mock.patch('oslo_serialization.jsonutils.dump') + def test_get_plugin_configs_default_file(self, p_dump): + m_open = mock.mock_open() + with mock.patch('six.moves.builtins.open', m_open, create=True): + arglist = ['fake', '0.1'] + verifylist = [('plugin', 'fake'), ('version', '0.1')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Check that correct arguments was passed + self.plugins_mock.get_version_details.assert_called_once_with( + 'fake', '0.1') + + args_to_dump = p_dump.call_args[0] + # Check that the right data will be saved + + self.assertEqual(PLUGIN_INFO, args_to_dump[0]) + # Check that data will be saved to the right file + self.assertEqual('fake', m_open.call_args[0][0]) + + @mock.patch('oslo_serialization.jsonutils.dump') + def test_get_plugin_configs_specified_file(self, p_dump): + m_open = mock.mock_open() + with mock.patch('six.moves.builtins.open', m_open): + arglist = ['fake', '0.1', '--file', 'testfile'] + verifylist = [('plugin', 'fake'), ('version', '0.1'), + ('file', 'testfile')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Check that correct arguments was passed + self.plugins_mock.get_version_details.assert_called_once_with( + 'fake', '0.1') + + args_to_dump = p_dump.call_args[0] + # Check that the right data will be saved + + self.assertEqual(PLUGIN_INFO, args_to_dump[0]) + # Check that data will be saved to the right file + self.assertEqual('testfile', m_open.call_args[0][0]) diff --git a/saharaclient/tests/unit/osc/v1/test_utils.py b/saharaclient/tests/unit/osc/v1/test_utils.py new file mode 100644 index 00000000..f7d3cf0e --- /dev/null +++ b/saharaclient/tests/unit/osc/v1/test_utils.py @@ -0,0 +1,76 @@ +# Copyright (c) 2015 Mirantis Inc. +# +# 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. + +from openstackclient.common import exceptions + +from saharaclient.osc.v1 import utils +from saharaclient.tests.unit import base + + +class TestUtils(base.BaseTestCase): + def test_prepare_data(self): + data = {'id': '123', 'name_of_res': 'name', 'description': 'descr'} + + fields = ['id', 'name_of_res', 'description'] + expected_data = {'Description': 'descr', 'Id': '123', + 'Name of res': 'name'} + self.assertEqual(expected_data, utils.prepare_data(data, fields)) + + fields = ['id', 'name_of_res'] + expected_data = {'Id': '123', 'Name of res': 'name'} + self.assertEqual(expected_data, utils.prepare_data(data, fields)) + + fields = ['name_of_res'] + expected_data = {'Name of res': 'name'} + self.assertEqual(expected_data, utils.prepare_data(data, fields)) + + def test_get_resource_id(self): + class TestResource(object): + def __init__(self, id): + self.id = id + + class TestManager(object): + + resource_class = TestResource + + def get(self, id): + if id == 'id': + return TestResource('from_id') + else: + raise + + def find(self, name): + if name == 'name': + return [TestResource('from_name')] + if name == 'null': + return [] + if name == 'mult': + return [TestResource('1'), TestResource('2')] + + # check case when resource id is passed + self.assertEqual('from_id', utils.get_resource( + TestManager(), 'id').id) + + # check case when resource name is passed + self.assertEqual('from_name', utils.get_resource( + TestManager(), 'name').id) + + # check that error is raised when resource doesn't exists + self.assertRaises(exceptions.CommandError, utils.get_resource, + TestManager(), 'null') + + # check that error is raised when multiple resources choice + self.assertRaises(exceptions.CommandError, utils.get_resource, + TestManager(), 'mult') diff --git a/setup.cfg b/setup.cfg index caf595d3..972154a5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,13 +25,21 @@ classifier = setup-hooks = pbr.hooks.setup_hook [files] -packages = +packages = saharaclient [entry_points] console_scripts = sahara = saharaclient.shell:main +openstack.cli.extension = + data_processing = saharaclient.osc.plugin + +openstack.data_processing.v1 = + dataprocessing_plugin_list = saharaclient.osc.v1.plugins:ListPlugins + dataprocessing_plugin_show = saharaclient.osc.v1.plugins:ShowPlugin + dataprocessing_plugin_configs_get = saharaclient.osc.v1.plugins:GetPluginConfigs + [build_sphinx] all_files = 1 build-dir = doc/build