Revert "Remove flavor API"

Story: 2008341
Task: 41242

This reverts commit 27cf71299e.

Change-Id: I036a0423263b1a0332415c6403647ae82c9e34c4
This commit is contained in:
Lingxian Kong 2020-11-12 09:57:13 +13:00
parent dd371f0454
commit c04e299b1d
15 changed files with 429 additions and 13 deletions

View File

@ -63,6 +63,8 @@ openstack.database.v1 =
database_db_create = troveclient.osc.v1.databases:CreateDatabase
database_db_delete = troveclient.osc.v1.databases:DeleteDatabase
database_db_list = troveclient.osc.v1.databases:ListDatabases
database_flavor_list = troveclient.osc.v1.database_flavors:ListDatabaseFlavors
database_flavor_show = troveclient.osc.v1.database_flavors:ShowDatabaseFlavor
database_instance_create = troveclient.osc.v1.database_instances:CreateDatabaseInstance
database_instance_delete = troveclient.osc.v1.database_instances:DeleteDatabaseInstance
database_instance_force_delete = troveclient.osc.v1.database_instances:ForceDeleteDatabaseInstance

View File

@ -21,9 +21,11 @@ from troveclient.v1.accounts import Accounts # noqa
from troveclient.v1.databases import Databases # noqa
from troveclient.v1.diagnostics import DiagnosticsInterrogator # noqa
from troveclient.v1.diagnostics import HwInfoInterrogator # noqa
from troveclient.v1.flavors import Flavors # noqa
from troveclient.v1.hosts import Hosts # noqa
from troveclient.v1.instances import Instances # noqa
from troveclient.v1.management import Management # noqa
from troveclient.v1.management import MgmtFlavors # noqa
from troveclient.v1.management import RootHistory # noqa
from troveclient.v1.root import Root # noqa
from troveclient.v1.storage import StorageInfo # noqa

View File

@ -129,6 +129,16 @@ class InstanceCommands(common.AuthedCommandsBase):
self._pretty_print(self.dbaas.instances.configuration, self.id)
class FlavorsCommands(common.AuthedCommandsBase):
"""Command for listing Flavors."""
params = []
def list(self):
"""List the available flavors."""
self._pretty_list(self.dbaas.flavors.list)
class DatabaseCommands(common.AuthedCommandsBase):
"""Database CRUD operations on an instance."""
@ -426,6 +436,7 @@ class MetadataCommands(common.AuthedCommandsBase):
COMMANDS = {
'auth': common.Auth,
'instance': InstanceCommands,
'flavor': FlavorsCommands,
'database': DatabaseCommands,
'limit': LimitsCommands,
'backup': BackupsCommands,

View File

@ -309,6 +309,7 @@ class Dbaas(object):
from troveclient.v1 import databases
from troveclient.v1 import datastores
from troveclient.v1 import diagnostics
from troveclient.v1 import flavors
from troveclient.v1 import hosts
from troveclient.v1 import instances
from troveclient.v1 import limits
@ -330,6 +331,7 @@ class Dbaas(object):
region_name=region_name)
self.versions = versions.Versions(self)
self.databases = databases.Databases(self)
self.flavors = flavors.Flavors(self)
self.instances = instances.Instances(self)
self.limits = limits.Limits(self)
self.users = users.Users(self)
@ -347,6 +349,7 @@ class Dbaas(object):
self.storage = storage.StorageInfo(self)
self.management = management.Management(self)
self.mgmt_cluster = management.MgmtClusters(self)
self.mgmt_flavor = management.MgmtFlavors(self)
self.accounts = accounts.Accounts(self)
self.diagnostics = diagnostics.DiagnosticsInterrogator(self)
self.hwinfo = diagnostics.HwInfoInterrogator(self)

View File

@ -181,6 +181,31 @@ class StorageCommands(common.AuthedCommandsBase):
self._pretty_list(self.dbaas.storage.index)
class FlavorsCommands(common.AuthedCommandsBase):
"""Commands for managing Flavors."""
params = [
'name',
'ram',
'disk',
'vcpus',
'flavor_id',
'ephemeral',
'swap',
'rxtx_factor',
'service_type'
]
def create(self):
"""Create a new flavor."""
self._require('name', 'ram', 'disk', 'vcpus',
'flavor_id', 'service_type')
self._pretty_print(self.dbaas.mgmt_flavor.create, self.name,
self.ram, self.disk, self.vcpus, self.flavor_id,
self.ephemeral, self.swap, self.rxtx_factor,
self.service_type)
def config_options(oparser):
oparser.add_option("-u", "--url", default="http://localhost:5000/v1.1",
help="Auth API endpoint URL with port and version. \
@ -194,6 +219,7 @@ COMMANDS = {
'root': RootCommands,
'storage': StorageCommands,
'quota': QuotaCommands,
'flavor': FlavorsCommands,
}

View File

@ -0,0 +1,97 @@
# 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
import six
from troveclient import exceptions
from troveclient.i18n import _
def set_attributes_for_print_detail(flavor):
info = flavor._info.copy()
# Get rid of those ugly links
if info.get('links'):
del(info['links'])
# Fallback to str_id for flavors, where necessary
if hasattr(flavor, 'str_id'):
info['id'] = flavor.id
del(info['str_id'])
return info
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=parsed_args.datastore_type,
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
class ShowDatabaseFlavor(command.ShowOne):
_description = _("Shows details of a database flavor")
def get_parser(self, prog_name):
parser = super(ShowDatabaseFlavor, self).get_parser(prog_name)
parser.add_argument(
'flavor',
metavar='<flavor>',
help=_('ID or name of the flavor'),
)
return parser
def take_action(self, parsed_args):
db_flavors = self.app.client_manager.database.flavors
flavor = utils.find_resource(db_flavors,
parsed_args.flavor)
flavor = set_attributes_for_print_detail(flavor)
return zip(*sorted(six.iteritems(flavor)))

View File

@ -244,7 +244,8 @@ class CreateDatabaseInstance(command.ShowOne):
'--flavor',
metavar='<flavor>',
type=str,
help=_("A flavor ID."),
help=_("Flavor to create the instance (name or ID). Flavor is not "
"required when creating replica instances."),
)
parser.add_argument(
'--size',
@ -373,8 +374,16 @@ class CreateDatabaseInstance(command.ShowOne):
db_instances = database.instances
if not parsed_args.replica_of and not parsed_args.flavor:
raise exceptions.CommandError(
_("Please specify a flavor"))
raise exceptions.CommandError(_("Please specify a flavor"))
if parsed_args.replica_of and parsed_args.flavor:
print("Warning: Flavor is ignored for creating replica.")
if not parsed_args.replica_of:
flavor_id = osc_utils.find_resource(
database.flavors, parsed_args.flavor).id
else:
flavor_id = None
volume = None
if parsed_args.size is not None and parsed_args.size <= 0:
@ -441,7 +450,7 @@ class CreateDatabaseInstance(command.ShowOne):
instance = db_instances.create(
parsed_args.name,
flavor_id=parsed_args.flavor,
flavor_id=flavor_id,
volume=volume,
databases=databases,
users=users,

View File

@ -20,6 +20,7 @@ from troveclient.v1 import clusters
from troveclient.v1 import configurations
from troveclient.v1 import databases
from troveclient.v1 import datastores
from troveclient.v1 import flavors
from troveclient.v1 import instances
from troveclient.v1 import limits
from troveclient.v1 import modules
@ -33,6 +34,13 @@ class TestDatabasev1(utils.TestCommand):
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])
class FakeBackups(object):
fake_backups = fakes.FakeHTTPClient().get_backups()[2]['backups']

View File

@ -0,0 +1,78 @@
# 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
self.flavor_client.list_datastore_version_associated_flavors. \
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)
def test_flavor_list_with_optional_args(self):
args = ['--datastore-type', 'mysql',
'--datastore-version-id', '5.6']
parsed_args = self.check_parser(self.cmd, args, [])
list_flavor_dict = {'datastore': 'mysql',
'version_id': '5.6'}
columns, values = self.cmd.take_action(parsed_args)
self.flavor_client.list_datastore_version_associated_flavors. \
assert_called_once_with(**list_flavor_dict)
self.assertEqual(self.columns, columns)
self.assertEqual([self.values], values)
class TestFlavorShow(TestFlavors):
values = (1, 'm1.tiny', 512)
def setUp(self):
super(TestFlavorShow, self).setUp()
self.cmd = database_flavors.ShowDatabaseFlavor(self.app, None)
self.data = self.fake_flavors.get_flavors_1()
self.flavor_client.get.return_value = self.data
self.columns = (
'id',
'name',
'ram',
)
def test_flavor_show_defaults(self):
args = ['m1.tiny']
parsed_args = self.check_parser(self.cmd, args, [])
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
self.assertEqual(self.values, data)

View File

@ -222,7 +222,6 @@ class TestDatabaseInstanceCreate(TestInstances):
@mock.patch.object(utils, 'find_resource')
def test_instance_create(self, mock_find):
mock_find.id.side_effect = ['test', 'mod_id']
args = ['test-name', '--flavor', '103',
'--size', '1',
'--databases', 'db1', 'db2',
@ -256,7 +255,8 @@ class TestDatabaseInstanceCreate(TestInstances):
self.assertEqual(self.columns, columns)
self.assertEqual(self.values, data)
def test_instance_create_without_allowed_cidrs(self):
@mock.patch.object(utils, 'find_resource')
def test_instance_create_without_allowed_cidrs(self, mock_find):
resp = {
"id": "a1fea1cf-18ad-48ab-bdfd-fce99a4b834e",
"name": "test-mysql",
@ -338,7 +338,10 @@ class TestDatabaseInstanceCreate(TestInstances):
self.assertEqual(expected_columns, columns)
self.assertEqual(expected_values, data)
def test_instance_create_nic_param(self):
@mock.patch.object(utils, 'find_resource')
def test_instance_create_nic_param(self, mock_find):
fake_id = self.random_uuid()
mock_find.return_value.id = fake_id
args = [
'test-mysql',
'--flavor', 'a48ea749-7ee3-4003-8aae-eb4e79773e2d',
@ -352,7 +355,7 @@ class TestDatabaseInstanceCreate(TestInstances):
self.instance_client.create.assert_called_once_with(
'test-mysql',
flavor_id='a48ea749-7ee3-4003-8aae-eb4e79773e2d',
flavor_id=fake_id,
volume={"size": 1, "type": None},
databases=[],
users=[],

View File

@ -165,6 +165,38 @@ class ManagementTest(testtools.TestCase):
self.assertEqual({'reset-task-status': {}}, self.body_)
class MgmtFlavorsTest(testtools.TestCase):
def setUp(self):
super(MgmtFlavorsTest, self).setUp()
self.orig__init = management.MgmtFlavors.__init__
management.MgmtFlavors.__init__ = mock.Mock(return_value=None)
self.flavors = management.MgmtFlavors()
self.flavors.api = mock.Mock()
self.flavors.api.client = mock.Mock()
self.flavors.resource_class = mock.Mock(return_value="flavor-1")
self.orig_base_getid = base.getid
base.getid = mock.Mock(return_value="flavor1")
def tearDown(self):
super(MgmtFlavorsTest, self).tearDown()
management.MgmtFlavors.__init__ = self.orig__init
base.getid = self.orig_base_getid
def test_create(self):
def side_effect_func(path, body, inst):
return path, body, inst
self.flavors._create = mock.Mock(side_effect=side_effect_func)
p, b, i = self.flavors.create("test-name", 1024, 30, 2, 1)
self.assertEqual("/mgmt/flavors", p)
self.assertEqual("flavor", i)
self.assertEqual("test-name", b["flavor"]["name"])
self.assertEqual(1024, b["flavor"]["ram"])
self.assertEqual(2, b["flavor"]["vcpu"])
self.assertEqual(1, b["flavor"]["flavor_id"])
class MgmtDatastoreVersionsTest(testtools.TestCase):
def setUp(self):

View File

@ -21,6 +21,7 @@ from troveclient.v1 import clusters
from troveclient.v1 import configurations
from troveclient.v1 import databases
from troveclient.v1 import datastores
from troveclient.v1 import flavors
from troveclient.v1 import instances
from troveclient.v1 import limits
from troveclient.v1 import management
@ -64,6 +65,7 @@ class Client(object):
# self.limits = limits.LimitsManager(self)
# extensions
self.flavors = flavors.Flavors(self)
self.volume_types = volume_types.VolumeTypes(self)
self.users = users.Users(self)
self.databases = databases.Databases(self)

58
troveclient/v1/flavors.py Normal file
View File

@ -0,0 +1,58 @@
# Copyright 2011 OpenStack Foundation
# Copyright 2013 Rackspace Hosting
# 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.
from troveclient import base
class Flavor(base.Resource):
"""A Flavor is an Instance type, specifying other things, like RAM size."""
def __init__(self, manager, info, loaded=False):
super(Flavor, self).__init__(manager, info, loaded)
if self.id is None and self.str_id is not None:
self.id = self.str_id
def __repr__(self):
return "<Flavor: %s>" % self.name
class Flavors(base.ManagerWithFind):
"""Manage :class:`Flavor` resources."""
resource_class = Flavor
def list(self):
"""Get a list of all flavors.
:rtype: list of :class:`Flavor`.
"""
return self._list("/flavors", "flavors")
def list_datastore_version_associated_flavors(self, datastore,
version_id):
"""Get a list of all flavors for the specified datastore type
and datastore version .
:rtype: list of :class:`Flavor`.
"""
return self._list("/datastores/%s/versions/%s/flavors" %
(datastore, version_id),
"flavors")
def get(self, flavor):
"""Get a specific flavor.
:rtype: :class:`Flavor`
"""
return self._get("/flavors/%s" % base.getid(flavor),
"flavor")

View File

@ -21,6 +21,7 @@ from troveclient import common
from troveclient.v1 import clusters
from troveclient.v1 import configurations
from troveclient.v1 import datastores
from troveclient.v1 import flavors
from troveclient.v1 import instances
@ -149,6 +150,44 @@ class MgmtClusters(base.ManagerWithFind):
self._action(cluster_id, body)
class MgmtFlavors(base.ManagerWithFind):
"""Manage :class:`Flavor` resources."""
resource_class = flavors.Flavor
def __repr__(self):
return "<Flavors Manager at %s>" % id(self)
# Appease the abc gods
def list(self):
pass
def create(self, name, ram, disk, vcpus,
flavorid="auto", ephemeral=None, swap=None, rxtx_factor=None,
service_type=None):
"""Create a new flavor."""
body = {"flavor": {
"flavor_id": flavorid,
"name": name,
"ram": ram,
"disk": disk,
"vcpu": vcpus,
"ephemeral": 0,
"swap": 0,
"rxtx_factor": "1.0",
"is_public": "True"
}}
if ephemeral:
body["flavor"]["ephemeral"] = ephemeral
if swap:
body["flavor"]["swap"] = swap
if rxtx_factor:
body["flavor"]["rxtx_factor"] = rxtx_factor
if service_type:
body["flavor"]["service_type"] = service_type
return self._create("/mgmt/flavors", body, "flavor")
class MgmtConfigurationParameters(configurations.ConfigurationParameters):
def create(self, version, name, restart_required, data_type,
max_size=None, min_size=None):

View File

@ -187,6 +187,11 @@ def _find_cluster(cs, cluster):
return utils.find_resource(cs.clusters, cluster)
def _find_flavor(cs, flavor):
"""Get a flavor by ID."""
return utils.find_resource(cs.flavors, flavor)
def _find_volume_type(cs, volume_type):
"""Get a volume type by ID."""
return utils.find_resource(cs.volume_types, volume_type)
@ -217,6 +222,46 @@ def _find_configuration(cs, configuration):
return utils.find_resource(cs.configurations, configuration)
# Flavor related calls
@utils.arg('--datastore_type', metavar='<datastore_type>',
default=None,
help=_('Type of the datastore. For eg: mysql.'))
@utils.arg("--datastore_version_id", metavar="<datastore_version_id>",
default=None, help=_("ID of the datastore version."))
@utils.service_type('database')
def do_flavor_list(cs, args):
"""Lists available flavors."""
if args.datastore_type and args.datastore_version_id:
flavors = cs.flavors.list_datastore_version_associated_flavors(
args.datastore_type, args.datastore_version_id)
elif not args.datastore_type and not args.datastore_version_id:
flavors = cs.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(f)
utils.print_list(_flavors, ['id', 'name', 'ram', 'vcpus', 'disk',
'ephemeral'],
labels={'ram': 'RAM', 'vcpus': 'vCPUs', 'disk': 'Disk'},
order_by='ram')
@utils.arg('flavor', metavar='<flavor>', type=str,
help=_('ID or name of the flavor.'))
@utils.service_type('database')
def do_flavor_show(cs, args):
"""Shows details of a flavor."""
flavor = _find_flavor(cs, args.flavor)
_print_object(flavor)
# Volume type related calls
@utils.arg('--datastore_type', metavar='<datastore_type>',
default=None,
@ -508,7 +553,7 @@ def do_update(cs, args):
@utils.arg('flavor',
metavar='<flavor>',
type=str,
help=_('A flavor ID.'))
help=_('A flavor name or ID.'))
@utils.arg('--databases', metavar='<database>',
help=_('Optional list of databases.'),
nargs="+", default=[])
@ -576,7 +621,7 @@ def do_update(cs, args):
@utils.service_type('database')
def do_create(cs, args):
"""Creates a new instance."""
flavor_id = args.flavor
flavor_id = _find_flavor(cs, args.flavor).id
volume = None
if args.size is not None and args.size <= 0:
raise exceptions.ValidationError(
@ -639,7 +684,8 @@ def _validate_nic_info(nic_info, nic_str):
def _get_flavor(cs, opts_str):
flavor_id, opts_str = _strip_option(opts_str, 'flavor', True)
flavor_name, opts_str = _strip_option(opts_str, 'flavor', True)
flavor_id = _find_flavor(cs, flavor_name).id
return str(flavor_id), opts_str
@ -881,12 +927,12 @@ def do_cluster_create(cs, args):
@utils.arg('flavor',
metavar='<flavor>',
type=str,
help=_('New flavor ID for the instance.'))
help=_('New flavor of the instance.'))
@utils.service_type('database')
def do_resize_instance(cs, args):
"""Resizes an instance with a new flavor."""
instance = _find_instance(cs, args.instance)
flavor_id = args.flavor
flavor_id = _find_flavor(cs, args.flavor).id
cs.instances.resize_instance(instance, flavor_id)