From 4eebd5678625e5db173f3fa617663e5de71f74de Mon Sep 17 00:00:00 2001 From: Trevor McCasland Date: Wed, 11 Jan 2017 15:36:52 -0600 Subject: [PATCH] Add OpenStackClient plugin and flavor list This change adds database support to the python-openstackclient through a plugin and tests. The support can be demonstrated through the implementation of the trove command flavor-list which is now: openstack database flavor list Use the -v or --debug flag to see the calls being made to the correct flavor list function. ubuntu@ubuntu:~$ openstack database flavor list -v START with options: [u'database', u'flavor', u'list', u'-v'] command: database flavor list -> troveclient.osc.v1.flavors.ListFlavors Using auth plugin: password +-----+-----------+-------+-------+------+-----------+ | id | name | RAM | vCPUs | Disk | ephemeral | +-----+-----------+-------+-------+------+-----------+ | 1 | m1.tiny | 512 | 1 | 1 | 0 | | 2 | m1.small | 2048 | 1 | 20 | 0 | | 3 | m1.medium | 4096 | 2 | 40 | 0 | | 4 | m1.large | 8192 | 4 | 80 | 0 | | 42 | m1.nano | 64 | 1 | 0 | 0 | | 451 | m1.heat | 512 | 1 | 0 | 0 | | 5 | m1.xlarge | 16384 | 8 | 160 | 0 | | 84 | m1.micro | 128 | 1 | 0 | 0 | | c1 | cirros256 | 256 | 1 | 0 | 0 | | d1 | ds512M | 512 | 1 | 5 | 0 | | d2 | ds1G | 1024 | 1 | 10 | 0 | | d3 | ds2G | 2048 | 2 | 10 | 0 | | d4 | ds4G | 4096 | 4 | 20 | 0 | +-----+-----------+-------+-------+------+-----------+ END return value: 0 Change-Id: I308a6c6f3f5ce7dbb814ec0fd8ecb1734a2f137f Partially-Implements: trove-support-in-python-openstackclient --- ...d-flavor-list-to-osc-b8b3a42f3bae3851.yaml | 5 ++ requirements.txt | 1 + setup.cfg | 6 ++ test-requirements.txt | 1 + troveclient/osc/__init__.py | 0 troveclient/osc/plugin.py | 54 ++++++++++++++++ troveclient/osc/v1/__init__.py | 0 troveclient/osc/v1/database_flavors.py | 63 +++++++++++++++++++ troveclient/tests/osc/__init__.py | 0 troveclient/tests/osc/fakes.py | 26 ++++++++ troveclient/tests/osc/utils.py | 62 ++++++++++++++++++ troveclient/tests/osc/v1/__init__.py | 0 troveclient/tests/osc/v1/fakes.py | 31 +++++++++ .../tests/osc/v1/test_database_flavors.py | 41 ++++++++++++ 14 files changed, 290 insertions(+) create mode 100644 releasenotes/notes/add-flavor-list-to-osc-b8b3a42f3bae3851.yaml create mode 100644 troveclient/osc/__init__.py create mode 100644 troveclient/osc/plugin.py create mode 100644 troveclient/osc/v1/__init__.py create mode 100644 troveclient/osc/v1/database_flavors.py create mode 100644 troveclient/tests/osc/__init__.py create mode 100644 troveclient/tests/osc/fakes.py create mode 100644 troveclient/tests/osc/utils.py create mode 100644 troveclient/tests/osc/v1/__init__.py create mode 100644 troveclient/tests/osc/v1/fakes.py create mode 100644 troveclient/tests/osc/v1/test_database_flavors.py diff --git a/releasenotes/notes/add-flavor-list-to-osc-b8b3a42f3bae3851.yaml b/releasenotes/notes/add-flavor-list-to-osc-b8b3a42f3bae3851.yaml new file mode 100644 index 00000000..e631c99a --- /dev/null +++ b/releasenotes/notes/add-flavor-list-to-osc-b8b3a42f3bae3851.yaml @@ -0,0 +1,5 @@ +--- +features: + - The command ``trove flavor-list`` is now available to use in + the python-openstackclient CLI as ``openstack database flavor + list`` diff --git a/requirements.txt b/requirements.txt index cb390d00..2acd86cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ keystoneauth1>=2.16.0 # Apache-2.0 six>=1.9.0 # MIT python-swiftclient>=3.2.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 +osc-lib>=1.2.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index ae4fd889..1bb07a7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,12 @@ packages = console_scripts = trove = troveclient.shell:main +openstack.cli.extension = + database = troveclient.osc.plugin + +openstack.database.v1 = + database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors + [build_sphinx] all_files = 1 source-dir = doc/source diff --git a/test-requirements.txt b/test-requirements.txt index 9ddd4268..4077cceb 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,6 +6,7 @@ coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 +python-openstackclient>=3.3.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD testrepository>=0.0.18 # Apache-2.0/BSD diff --git a/troveclient/osc/__init__.py b/troveclient/osc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troveclient/osc/plugin.py b/troveclient/osc/plugin.py new file mode 100644 index 00000000..263192b9 --- /dev/null +++ b/troveclient/osc/plugin.py @@ -0,0 +1,54 @@ +# 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 logging + +from osc_lib import utils + +LOG = logging.getLogger(__name__) + +DEFAULT_DATABASE_API_VERSION = '1' +API_VERSION_OPTION = 'os_database_api_version' +API_NAME = 'database' +API_VERSIONS = { + '1': 'troveclient.v1.client.Client', +} + + +def make_client(instance): + """Returns a database service client""" + trove_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + LOG.debug('Instantiating database client: %s', trove_client) + client = trove_client( + auth=instance.auth, + session=instance.session + ) + + return client + + +def build_option_parser(parser): + """Hook to add global options""" + parser.add_argument( + '--os-database-api-version', + metavar='', + default=utils.env( + 'OS_DATABASE_API_VERSION', + default=DEFAULT_DATABASE_API_VERSION), + help='Database API version, default=' + + DEFAULT_DATABASE_API_VERSION + + ' (Env: OS_DATABASE_API_VERSION)') + return parser diff --git a/troveclient/osc/v1/__init__.py b/troveclient/osc/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troveclient/osc/v1/database_flavors.py b/troveclient/osc/v1/database_flavors.py new file mode 100644 index 00000000..f9fb3d09 --- /dev/null +++ b/troveclient/osc/v1/database_flavors.py @@ -0,0 +1,63 @@ +# 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. + +"""Database v1 Flavors action implementations""" + +from osc_lib.command import command +from osc_lib import utils + +from troveclient import exceptions +from troveclient.i18n import _ + + +class ListDatabaseFlavors(command.Lister): + + _description = _("List database flavors") + columns = ['ID', 'Name', 'RAM', 'vCPUs', 'Disk', 'Ephemeral'] + + def get_parser(self, prog_name): + parser = super(ListDatabaseFlavors, self).get_parser(prog_name) + parser.add_argument( + '--datastore-type', + dest='datastore_type', + metavar='', + help=_('Type of the datastore. For eg: mysql.') + ) + parser.add_argument( + '--datastore-version-id', + dest='datastore_version_id', + metavar='', + help=_('ID of the datastore version.') + ) + return parser + + def take_action(self, parsed_args): + db_flavors = self.app.client_manager.database.flavors + if parsed_args.datastore_type and parsed_args.datastore_version_id: + flavors = db_flavors.list_datastore_version_associated_flavors( + datastore_type=parsed_args.datastore_type, + datastore_version_id=parsed_args.datastore_version_id) + elif (not parsed_args.datastore_type and not + parsed_args.datastore_version_id): + flavors = db_flavors.list() + else: + raise exceptions.MissingArgs(['datastore-type', + 'datastore-version-id']) + + # Fallback to str_id where necessary. + _flavors = [] + for f in flavors: + if not f.id and hasattr(f, 'str_id'): + f.id = f.str_id + _flavors.append(utils.get_item_properties(f, self.columns)) + + return self.columns, _flavors diff --git a/troveclient/tests/osc/__init__.py b/troveclient/tests/osc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troveclient/tests/osc/fakes.py b/troveclient/tests/osc/fakes.py new file mode 100644 index 00000000..d269ecc7 --- /dev/null +++ b/troveclient/tests/osc/fakes.py @@ -0,0 +1,26 @@ +# 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. +# + + +class FakeStdout(object): + def __init__(self): + self.content = [] + + def write(self, text): + self.content.append(text) + + def make_string(self): + result = '' + for line in self.content: + result = result + line + return result diff --git a/troveclient/tests/osc/utils.py b/troveclient/tests/osc/utils.py new file mode 100644 index 00000000..a3a73597 --- /dev/null +++ b/troveclient/tests/osc/utils.py @@ -0,0 +1,62 @@ +# 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 + +import fixtures +import mock +import sys +import testtools + +from troveclient.tests.osc import fakes + + +class TestCase(testtools.TestCase): + def setUp(self): + testtools.TestCase.setUp(self) + + if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or + os.environ.get("OS_STDOUT_CAPTURE") == "1"): + stdout = self.useFixture(fixtures.StringStream("stdout")).stream + self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout)) + + if (os.environ.get("OS_STDERR_CAPTURE") == "True" or + os.environ.get("OS_STDERR_CAPTURE") == "1"): + stderr = self.useFixture(fixtures.StringStream("stderr")).stream + self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) + + +class TestCommand(TestCase): + """Test cliff command classes""" + + def setUp(self): + super(TestCommand, self).setUp() + # Build up a fake app + self.fake_stdout = fakes.FakeStdout() + self.app = mock.MagicMock() + self.app.stdout = self.fake_stdout + self.app.stdin = sys.stdin + self.app.stderr = sys.stderr + + def check_parser(self, cmd, args, verify_args): + cmd_parser = cmd.get_parser('check_parser') + try: + parsed_args = cmd_parser.parse_args(args) + except SystemExit: + raise Exception("Argument parse failed") + for av in verify_args: + attr, value = av + if attr: + self.assertIn(attr, parsed_args) + self.assertEqual(getattr(parsed_args, attr), value) + return parsed_args diff --git a/troveclient/tests/osc/v1/__init__.py b/troveclient/tests/osc/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troveclient/tests/osc/v1/fakes.py b/troveclient/tests/osc/v1/fakes.py new file mode 100644 index 00000000..0b0267f6 --- /dev/null +++ b/troveclient/tests/osc/v1/fakes.py @@ -0,0 +1,31 @@ +# 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 troveclient.tests import fakes +from troveclient.tests.osc import utils +from troveclient.v1 import flavors + + +class TestDatabasev1(utils.TestCommand): + def setUp(self): + super(TestDatabasev1, self).setUp() + self.app.client_manager.database = mock.MagicMock() + + +class FakeFlavors(object): + fake_flavors = fakes.FakeHTTPClient().get_flavors()[2]['flavors'] + + def get_flavors_1(self): + return flavors.Flavor(None, self.fake_flavors[0]) diff --git a/troveclient/tests/osc/v1/test_database_flavors.py b/troveclient/tests/osc/v1/test_database_flavors.py new file mode 100644 index 00000000..780abae0 --- /dev/null +++ b/troveclient/tests/osc/v1/test_database_flavors.py @@ -0,0 +1,41 @@ +# 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 troveclient.osc.v1 import database_flavors +from troveclient.tests.osc.v1 import fakes + + +class TestFlavors(fakes.TestDatabasev1): + fake_flavors = fakes.FakeFlavors() + + def setUp(self): + super(TestFlavors, self).setUp() + self.mock_client = self.app.client_manager.database + self.flavor_client = self.app.client_manager.database.flavors + + +class TestFlavorList(TestFlavors): + columns = database_flavors.ListDatabaseFlavors.columns + values = (1, 'm1.tiny', 512, '', '', '') + + def setUp(self): + super(TestFlavorList, self).setUp() + self.cmd = database_flavors.ListDatabaseFlavors(self.app, None) + self.data = [self.fake_flavors.get_flavors_1()] + self.flavor_client.list.return_value = self.data + + def test_flavor_list_defaults(self): + parsed_args = self.check_parser(self.cmd, [], []) + columns, values = self.cmd.take_action(parsed_args) + self.flavor_client.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual([self.values], values)