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
This commit is contained in:
@@ -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``
|
||||||
@@ -12,3 +12,4 @@ keystoneauth1>=2.16.0 # Apache-2.0
|
|||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
python-swiftclient>=3.2.0 # Apache-2.0
|
python-swiftclient>=3.2.0 # Apache-2.0
|
||||||
python-mistralclient>=2.0.0 # Apache-2.0
|
python-mistralclient>=2.0.0 # Apache-2.0
|
||||||
|
osc-lib>=1.2.0 # Apache-2.0
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ packages =
|
|||||||
console_scripts =
|
console_scripts =
|
||||||
trove = troveclient.shell:main
|
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]
|
[build_sphinx]
|
||||||
all_files = 1
|
all_files = 1
|
||||||
source-dir = doc/source
|
source-dir = doc/source
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ coverage>=4.0 # Apache-2.0
|
|||||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
oslosphinx>=4.7.0 # Apache-2.0
|
||||||
oslotest>=1.10.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
|
requests-mock>=1.1 # Apache-2.0
|
||||||
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
||||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||||
|
|||||||
0
troveclient/osc/__init__.py
Normal file
0
troveclient/osc/__init__.py
Normal file
54
troveclient/osc/plugin.py
Normal file
54
troveclient/osc/plugin.py
Normal file
@@ -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='<database-api-version>',
|
||||||
|
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
|
||||||
0
troveclient/osc/v1/__init__.py
Normal file
0
troveclient/osc/v1/__init__.py
Normal file
63
troveclient/osc/v1/database_flavors.py
Normal file
63
troveclient/osc/v1/database_flavors.py
Normal file
@@ -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='<datastore-type>',
|
||||||
|
help=_('Type of the datastore. For eg: mysql.')
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--datastore-version-id',
|
||||||
|
dest='datastore_version_id',
|
||||||
|
metavar='<datastore-version-id>',
|
||||||
|
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
|
||||||
0
troveclient/tests/osc/__init__.py
Normal file
0
troveclient/tests/osc/__init__.py
Normal file
26
troveclient/tests/osc/fakes.py
Normal file
26
troveclient/tests/osc/fakes.py
Normal file
@@ -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
|
||||||
62
troveclient/tests/osc/utils.py
Normal file
62
troveclient/tests/osc/utils.py
Normal file
@@ -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
|
||||||
0
troveclient/tests/osc/v1/__init__.py
Normal file
0
troveclient/tests/osc/v1/__init__.py
Normal file
31
troveclient/tests/osc/v1/fakes.py
Normal file
31
troveclient/tests/osc/v1/fakes.py
Normal file
@@ -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])
|
||||||
41
troveclient/tests/osc/v1/test_database_flavors.py
Normal file
41
troveclient/tests/osc/v1/test_database_flavors.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user