Switch compute flavors from novaclient/direct to SDK

Let's switch flavors from novaclient or direct API requests onto using
SDK. Microversion agreement comes out of the box.

SDK normalizes property names, while OSC uses server side names. In
order not to break OSC users continue using server-side names.

Depends-On: https://review.opendev.org/#/c/762989/
Change-Id: I62b2ed8488ee4ac9c42051311bcfb455506ddd90
This commit is contained in:
Artem Goncharov 2020-09-05 22:20:07 +02:00 committed by Stephen Finucane
parent ea358057f8
commit 0f4f42b652
6 changed files with 455 additions and 341 deletions

View File

@ -45,7 +45,7 @@ msgpack-python==0.4.0
munch==2.1.0 munch==2.1.0
netaddr==0.7.18 netaddr==0.7.18
netifaces==0.10.4 netifaces==0.10.4
openstacksdk==0.51.0 openstacksdk==0.52.0
os-service-types==1.7.0 os-service-types==1.7.0
os-testr==1.0.0 os-testr==1.0.0
osc-lib==2.2.0 osc-lib==2.2.0

View File

@ -17,7 +17,8 @@
import logging import logging
from novaclient import api_versions from openstack import exceptions as sdk_exceptions
from openstack import utils as sdk_utils
from osc_lib.cli import format_columns from osc_lib.cli import format_columns
from osc_lib.cli import parseractions from osc_lib.cli import parseractions
from osc_lib.command import command from osc_lib.command import command
@ -33,10 +34,7 @@ LOG = logging.getLogger(__name__)
_formatters = { _formatters = {
'extra_specs': format_columns.DictColumn, 'extra_specs': format_columns.DictColumn,
# Unless we finish switch to use SDK resources this need to be doubled this 'properties': format_columns.DictColumn
# way
'properties': format_columns.DictColumn,
'Properties': format_columns.DictColumn
} }
@ -51,29 +49,10 @@ def _get_flavor_columns(item):
} }
hidden_columns = ['links', 'location'] hidden_columns = ['links', 'location']
return utils.get_osc_show_columns_for_sdk_resource( return utils.get_osc_show_columns_for_sdk_resource(
item, column_map, hidden_columns) item, column_map, hidden_columns)
def _find_flavor(compute_client, flavor):
try:
return compute_client.flavors.get(flavor)
except Exception as ex:
if type(ex).__name__ == 'NotFound':
pass
else:
raise
try:
return compute_client.flavors.find(name=flavor, is_public=None)
except Exception as ex:
if type(ex).__name__ == 'NotFound':
msg = _("No flavor with a name or ID of '%s' exists.") % flavor
raise exceptions.CommandError(msg)
else:
raise
class CreateFlavor(command.ShowOne): class CreateFlavor(command.ShowOne):
_description = _("Create new flavor") _description = _("Create new flavor")
@ -87,9 +66,7 @@ class CreateFlavor(command.ShowOne):
parser.add_argument( parser.add_argument(
"--id", "--id",
metavar="<id>", metavar="<id>",
default='auto', help=_("Unique flavor ID")
help=_("Unique flavor ID; 'auto' creates a UUID "
"(default: auto)")
) )
parser.add_argument( parser.add_argument(
"--ram", "--ram",
@ -170,32 +147,36 @@ class CreateFlavor(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.identity
if parsed_args.project and parsed_args.public: if parsed_args.project and parsed_args.public:
msg = _("--project is only allowed with --private") msg = _("--project is only allowed with --private")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
args = {
'name': parsed_args.name,
'ram': parsed_args.ram,
'vcpus': parsed_args.vcpus,
'disk': parsed_args.disk,
'id': parsed_args.id,
'ephemeral': parsed_args.ephemeral,
'swap': parsed_args.swap,
'rxtx_factor': parsed_args.rxtx_factor,
'is_public': parsed_args.public,
}
if parsed_args.description: if parsed_args.description:
if compute_client.api_version < api_versions.APIVersion("2.55"): if not sdk_utils.supports_microversion(compute_client, '2.55'):
msg = _("--os-compute-api-version 2.55 or later is required") msg = _(
'The --description parameter requires server support for '
'API microversion 2.55'
)
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
args = ( args['description'] = parsed_args.description
parsed_args.name,
parsed_args.ram,
parsed_args.vcpus,
parsed_args.disk,
parsed_args.id,
parsed_args.ephemeral,
parsed_args.swap,
parsed_args.rxtx_factor,
parsed_args.public,
parsed_args.description
)
flavor = compute_client.flavors.create(*args) flavor = compute_client.create_flavor(**args)
if parsed_args.project: if parsed_args.project:
try: try:
@ -204,7 +185,7 @@ class CreateFlavor(command.ShowOne):
parsed_args.project, parsed_args.project,
parsed_args.project_domain, parsed_args.project_domain,
).id ).id
compute_client.flavor_access.add_tenant_access( compute_client.flavor_add_tenant_access(
flavor.id, project_id) flavor.id, project_id)
except Exception as e: except Exception as e:
msg = _("Failed to add project %(project)s access to " msg = _("Failed to add project %(project)s access to "
@ -212,19 +193,14 @@ class CreateFlavor(command.ShowOne):
LOG.error(msg, {'project': parsed_args.project, 'e': e}) LOG.error(msg, {'project': parsed_args.project, 'e': e})
if parsed_args.property: if parsed_args.property:
try: try:
flavor.set_keys(parsed_args.property) flavor = compute_client.create_flavor_extra_specs(
flavor, parsed_args.property)
except Exception as e: except Exception as e:
LOG.error(_("Failed to set flavor property: %s"), e) LOG.error(_("Failed to set flavor property: %s"), e)
flavor_info = flavor._info.copy() display_columns, columns = _get_flavor_columns(flavor)
flavor_info['properties'] = flavor.get_keys() data = utils.get_dict_properties(flavor, columns,
formatters=_formatters)
display_columns, columns = _get_flavor_columns(flavor_info)
data = utils.get_dict_properties(
flavor_info, columns,
formatters=_formatters,
mixed_case_fields=['OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral'])
return (display_columns, data) return (display_columns, data)
@ -243,12 +219,12 @@ class DeleteFlavor(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
result = 0 result = 0
for f in parsed_args.flavor: for f in parsed_args.flavor:
try: try:
flavor = _find_flavor(compute_client, f) flavor = compute_client.find_flavor(f, ignore_missing=False)
compute_client.flavors.delete(flavor.id) compute_client.delete_flavor(flavor.id)
except Exception as e: except Exception as e:
result += 1 result += 1
LOG.error(_("Failed to delete flavor with name or " LOG.error(_("Failed to delete flavor with name or "
@ -307,37 +283,63 @@ class ListFlavor(command.Lister):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
columns = (
"ID",
"Name",
"RAM",
"Disk",
"Ephemeral",
"VCPUs",
"Is Public",
)
# is_public is ternary - None means give all flavors, # is_public is ternary - None means give all flavors,
# True is public only and False is private only # True is public only and False is private only
# By default Nova assumes True and gives admins public flavors # By default Nova assumes True and gives admins public flavors
# and flavors from their own projects only. # and flavors from their own projects only.
is_public = None if parsed_args.all else parsed_args.public is_public = None if parsed_args.all else parsed_args.public
data = compute_client.flavors.list(is_public=is_public, query_attrs = {
marker=parsed_args.marker, 'is_public': is_public
limit=parsed_args.limit) }
if parsed_args.marker:
query_attrs['marker'] = parsed_args.marker
if parsed_args.limit:
query_attrs['limit'] = parsed_args.limit
if parsed_args.limit or parsed_args.marker:
# User passed explicit pagination request, switch off SDK
# pagination
query_attrs['paginated'] = False
data = list(compute_client.flavors(**query_attrs))
# Even if server supports 2.61 some policy might stop it sending us
# extra_specs. So try to fetch them if they are absent
for f in data:
if not f.extra_specs:
compute_client.fetch_flavor_extra_specs(f)
columns = (
"id",
"name",
"ram",
"disk",
"ephemeral",
"vcpus",
"is_public"
)
if parsed_args.long: if parsed_args.long:
columns = columns + ( columns += (
"swap",
"rxtx_factor",
"extra_specs",
)
column_headers = (
"ID",
"Name",
"RAM",
"Disk",
"Ephemeral",
"VCPUs",
"Is Public"
)
if parsed_args.long:
column_headers += (
"Swap", "Swap",
"RXTX Factor", "RXTX Factor",
"Properties", "Properties",
) )
for f in data:
f.properties = f.get_keys()
column_headers = columns
return (column_headers, return (column_headers,
(utils.get_item_properties( (utils.get_item_properties(
@ -387,24 +389,42 @@ class SetFlavor(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.identity
flavor = _find_flavor(compute_client, parsed_args.flavor) try:
flavor = compute_client.find_flavor(
parsed_args.flavor,
get_extra_specs=True,
ignore_missing=False)
except sdk_exceptions.ResourceNotFound as e:
raise exceptions.CommandError(e.message)
if parsed_args.description:
if not sdk_utils.supports_microversion(compute_client, '2.55'):
msg = _(
'The --description parameter requires server support for '
'API microversion 2.55'
)
raise exceptions.CommandError(msg)
compute_client.update_flavor(
flavor=flavor.id, description=parsed_args.description)
result = 0 result = 0
key_list = []
if parsed_args.no_property: if parsed_args.no_property:
try: try:
for key in flavor.get_keys().keys(): for key in flavor.extra_specs.keys():
key_list.append(key) compute_client.delete_flavor_extra_specs_property(
flavor.unset_keys(key_list) flavor.id, key)
except Exception as e: except Exception as e:
LOG.error(_("Failed to clear flavor property: %s"), e) LOG.error(_("Failed to clear flavor property: %s"), e)
result += 1 result += 1
if parsed_args.property: if parsed_args.property:
try: try:
flavor.set_keys(parsed_args.property) compute_client.create_flavor_extra_specs(
flavor.id, parsed_args.property)
except Exception as e: except Exception as e:
LOG.error(_("Failed to set flavor property: %s"), e) LOG.error(_("Failed to set flavor property: %s"), e)
result += 1 result += 1
@ -420,7 +440,7 @@ class SetFlavor(command.Command):
parsed_args.project, parsed_args.project,
parsed_args.project_domain, parsed_args.project_domain,
).id ).id
compute_client.flavor_access.add_tenant_access( compute_client.flavor_add_tenant_access(
flavor.id, project_id) flavor.id, project_id)
except Exception as e: except Exception as e:
LOG.error(_("Failed to set flavor access to project: %s"), e) LOG.error(_("Failed to set flavor access to project: %s"), e)
@ -430,13 +450,6 @@ class SetFlavor(command.Command):
raise exceptions.CommandError(_("Command Failed: One or more of" raise exceptions.CommandError(_("Command Failed: One or more of"
" the operations failed")) " the operations failed"))
if parsed_args.description:
if compute_client.api_version < api_versions.APIVersion("2.55"):
msg = _("--os-compute-api-version 2.55 or later is required")
raise exceptions.CommandError(msg)
compute_client.flavors.update(flavor=flavor.id,
description=parsed_args.description)
class ShowFlavor(command.ShowOne): class ShowFlavor(command.ShowOne):
_description = _("Display flavor details") _description = _("Display flavor details")
@ -451,35 +464,32 @@ class ShowFlavor(command.ShowOne):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
resource_flavor = _find_flavor(compute_client, parsed_args.flavor) flavor = compute_client.find_flavor(
parsed_args.flavor, get_extra_specs=True, ignore_missing=False)
access_projects = None access_projects = None
# get access projects list of this flavor # get access projects list of this flavor
if not resource_flavor.is_public: if not flavor.is_public:
try: try:
flavor_access = compute_client.flavor_access.list( flavor_access = compute_client.get_flavor_access(
flavor=resource_flavor.id) flavor=flavor.id)
access_projects = [utils.get_field(access, 'tenant_id') access_projects = [
for access in flavor_access] utils.get_field(access, 'tenant_id')
for access in flavor_access]
except Exception as e: except Exception as e:
msg = _("Failed to get access projects list " msg = _("Failed to get access projects list "
"for flavor '%(flavor)s': %(e)s") "for flavor '%(flavor)s': %(e)s")
LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e}) LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e})
flavor = resource_flavor._info.copy() # Since we need to inject "access_project_id" into resource - convert
flavor.update({ # it to dict and treat it respectively
'access_project_ids': access_projects flavor = flavor.to_dict()
}) flavor['access_project_ids'] = access_projects
flavor['properties'] = resource_flavor.get_keys()
display_columns, columns = _get_flavor_columns(flavor) display_columns, columns = _get_flavor_columns(flavor)
data = utils.get_dict_properties( data = utils.get_dict_properties(
flavor, columns, flavor, columns, formatters=_formatters)
formatters=_formatters,
mixed_case_fields=['OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral'])
return (display_columns, data) return (display_columns, data)
@ -512,32 +522,40 @@ class UnsetFlavor(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
compute_client = self.app.client_manager.compute compute_client = self.app.client_manager.sdk_connection.compute
identity_client = self.app.client_manager.identity identity_client = self.app.client_manager.identity
flavor = _find_flavor(compute_client, parsed_args.flavor) try:
flavor = compute_client.find_flavor(
parsed_args.flavor,
get_extra_specs=True,
ignore_missing=False)
except sdk_exceptions.ResourceNotFound as e:
raise exceptions.CommandError(_(e.message))
result = 0 result = 0
if parsed_args.property: if parsed_args.property:
try: for key in parsed_args.property:
flavor.unset_keys(parsed_args.property) try:
except Exception as e: compute_client.delete_flavor_extra_specs_property(
LOG.error(_("Failed to unset flavor property: %s"), e) flavor.id, key)
result += 1 except sdk_exceptions.SDKException as e:
LOG.error(_("Failed to unset flavor property: %s"), e)
result += 1
if parsed_args.project: if parsed_args.project:
try: try:
if flavor.is_public: if flavor.is_public:
msg = _("Cannot remove access for a public flavor") msg = _("Cannot remove access for a public flavor")
raise exceptions.CommandError(msg) raise exceptions.CommandError(msg)
else:
project_id = identity_common.find_project( project_id = identity_common.find_project(
identity_client, identity_client,
parsed_args.project, parsed_args.project,
parsed_args.project_domain, parsed_args.project_domain,
).id ).id
compute_client.flavor_access.remove_tenant_access( compute_client.flavor_remove_tenant_access(
flavor.id, project_id) flavor.id, project_id)
except Exception as e: except Exception as e:
LOG.error(_("Failed to remove flavor access from project: %s"), LOG.error(_("Failed to remove flavor access from project: %s"),
e) e)

View File

@ -19,6 +19,7 @@ from unittest import mock
import uuid import uuid
from novaclient import api_versions from novaclient import api_versions
from openstack.compute.v2 import flavor as _flavor
from openstackclient.api import compute_v2 from openstackclient.api import compute_v2
from openstackclient.tests.unit import fakes from openstackclient.tests.unit import fakes
@ -164,7 +165,6 @@ class FakeComputev2Client(object):
self.extensions.resource_class = fakes.FakeResource(None, {}) self.extensions.resource_class = fakes.FakeResource(None, {})
self.flavors = mock.Mock() self.flavors = mock.Mock()
self.flavors.resource_class = fakes.FakeResource(None, {})
self.flavor_access = mock.Mock() self.flavor_access = mock.Mock()
self.flavor_access.resource_class = fakes.FakeResource(None, {}) self.flavor_access.resource_class = fakes.FakeResource(None, {})
@ -777,27 +777,13 @@ class FakeFlavor(object):
'os-flavor-access:is_public': True, 'os-flavor-access:is_public': True,
'description': 'description', 'description': 'description',
'OS-FLV-EXT-DATA:ephemeral': 0, 'OS-FLV-EXT-DATA:ephemeral': 0,
'properties': {'property': 'value'}, 'extra_specs': {'property': 'value'},
} }
# Overwrite default attributes. # Overwrite default attributes.
flavor_info.update(attrs) flavor_info.update(attrs)
# Set default methods. flavor = _flavor.Flavor(**flavor_info)
flavor_methods = {
'set_keys': None,
'unset_keys': None,
'get_keys': {'property': 'value'},
}
flavor = fakes.FakeResource(info=copy.deepcopy(flavor_info),
methods=flavor_methods,
loaded=True)
# Set attributes with special mappings in nova client.
flavor.disabled = flavor_info['OS-FLV-DISABLED:disabled']
flavor.is_public = flavor_info['os-flavor-access:is_public']
flavor.ephemeral = flavor_info['OS-FLV-EXT-DATA:ephemeral']
return flavor return flavor

View File

@ -12,11 +12,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# #
from unittest import mock from unittest import mock
from unittest.mock import call
import novaclient from openstack.compute.v2 import flavor as _flavor
from openstack import exceptions as sdk_exceptions
from openstack import utils as sdk_utils
from osc_lib.cli import format_columns from osc_lib.cli import format_columns
from osc_lib import exceptions from osc_lib import exceptions
@ -31,13 +31,19 @@ class TestFlavor(compute_fakes.TestComputev2):
def setUp(self): def setUp(self):
super(TestFlavor, self).setUp() super(TestFlavor, self).setUp()
# Get a shortcut to the FlavorManager Mock # SDK mock
self.flavors_mock = self.app.client_manager.compute.flavors self.app.client_manager.sdk_connection = mock.Mock()
self.flavors_mock.reset_mock() self.app.client_manager.sdk_connection.compute = mock.Mock()
self.sdk_client = self.app.client_manager.sdk_connection.compute
# Get a shortcut to the FlavorAccessManager Mock self.sdk_client.flavors = mock.Mock()
self.flavor_access_mock = self.app.client_manager.compute.flavor_access self.sdk_client.find_flavor = mock.Mock()
self.flavor_access_mock.reset_mock() self.sdk_client.delete_flavor = mock.Mock()
self.sdk_client.update_flavor = mock.Mock()
self.sdk_client.flavor_add_tenant_access = mock.Mock()
self.sdk_client.flavor_remove_tenant_access = mock.Mock()
self.sdk_client.create_flavor_extra_specs = mock.Mock()
self.sdk_client.update_flavor_extra_specs_property = mock.Mock()
self.sdk_client.delete_flavor_extra_specs_property = mock.Mock()
self.projects_mock = self.app.client_manager.identity.projects self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock() self.projects_mock.reset_mock()
@ -48,6 +54,7 @@ class TestFlavorCreate(TestFlavor):
flavor = compute_fakes.FakeFlavor.create_one_flavor( flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'links': 'flavor-links'}) attrs={'links': 'flavor-links'})
project = identity_fakes.FakeProject.create_one_project() project = identity_fakes.FakeProject.create_one_project()
columns = ( columns = (
'OS-FLV-DISABLED:disabled', 'OS-FLV-DISABLED:disabled',
'OS-FLV-EXT-DATA:ephemeral', 'OS-FLV-EXT-DATA:ephemeral',
@ -60,17 +67,32 @@ class TestFlavorCreate(TestFlavor):
'ram', 'ram',
'rxtx_factor', 'rxtx_factor',
'swap', 'swap',
'vcpus', 'vcpus'
) )
data = ( data = (
flavor.disabled, flavor.is_disabled,
flavor.ephemeral, flavor.ephemeral,
flavor.description, flavor.description,
flavor.disk, flavor.disk,
flavor.id, flavor.id,
flavor.name, flavor.name,
flavor.is_public, flavor.is_public,
format_columns.DictColumn(flavor.properties), format_columns.DictColumn(flavor.extra_specs),
flavor.ram,
flavor.rxtx_factor,
flavor.swap,
flavor.vcpus,
)
data_private = (
flavor.is_disabled,
flavor.ephemeral,
flavor.description,
flavor.disk,
flavor.id,
flavor.name,
False,
format_columns.DictColumn(flavor.extra_specs),
flavor.ram, flavor.ram,
flavor.rxtx_factor, flavor.rxtx_factor,
flavor.swap, flavor.swap,
@ -82,7 +104,7 @@ class TestFlavorCreate(TestFlavor):
# Return a project # Return a project
self.projects_mock.get.return_value = self.project self.projects_mock.get.return_value = self.project
self.flavors_mock.create.return_value = self.flavor self.sdk_client.create_flavor.return_value = self.flavor
self.cmd = flavor.CreateFlavor(self.app, None) self.cmd = flavor.CreateFlavor(self.app, None)
def test_flavor_create_default_options(self): def test_flavor_create_default_options(self):
@ -95,20 +117,20 @@ class TestFlavorCreate(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
default_args = ( default_args = {
self.flavor.name, 'name': self.flavor.name,
256, 'ram': 256,
1, 'vcpus': 1,
0, 'disk': 0,
'auto', 'id': None,
0, 'ephemeral': 0,
0, 'swap': 0,
1.0, 'rxtx_factor': 1.0,
True, 'is_public': True,
None, }
)
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*default_args) self.sdk_client.create_flavor.assert_called_once_with(**default_args)
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(self.data, data) self.assertItemEqual(self.data, data)
@ -143,29 +165,44 @@ class TestFlavorCreate(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
args = ( args = {
self.flavor.name, 'name': self.flavor.name,
self.flavor.ram, 'ram': self.flavor.ram,
self.flavor.vcpus, 'vcpus': self.flavor.vcpus,
self.flavor.disk, 'disk': self.flavor.disk,
self.flavor.id, 'id': self.flavor.id,
self.flavor.ephemeral, 'ephemeral': self.flavor.ephemeral,
self.flavor.swap, 'swap': self.flavor.swap,
self.flavor.rxtx_factor, 'rxtx_factor': self.flavor.rxtx_factor,
self.flavor.is_public, 'is_public': self.flavor.is_public,
self.flavor.description, 'description': self.flavor.description
) }
self.app.client_manager.compute.api_version = 2.55
with mock.patch.object(novaclient.api_versions,
'APIVersion',
return_value=2.55):
columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args)
self.flavor.set_keys.assert_called_once_with({'property': 'value'})
self.flavor.get_keys.assert_called_once_with()
self.assertEqual(self.columns, columns) props = {'property': 'value'}
self.assertItemEqual(self.data, data)
# SDK updates the flavor object instance. In order to make the
# verification clear and preciese let's create new flavor and change
# expected props this way
create_flavor = _flavor.Flavor(**self.flavor)
expected_flavor = _flavor.Flavor(**self.flavor)
expected_flavor.extra_specs = props
# convert expected data tuple to list to be able to modify it
cmp_data = list(self.data)
cmp_data[7] = format_columns.DictColumn(props)
self.sdk_client.create_flavor.return_value = create_flavor
self.sdk_client.create_flavor_extra_specs.return_value = \
expected_flavor
with mock.patch.object(sdk_utils, 'supports_microversion',
return_value=True):
columns, data = self.cmd.take_action(parsed_args)
self.sdk_client.create_flavor.assert_called_once_with(**args)
self.sdk_client.create_flavor_extra_specs.assert_called_once_with(
create_flavor, props)
self.sdk_client.get_flavor_access.assert_not_called()
self.assertEqual(self.columns, columns)
self.assertItemEqual(tuple(cmp_data), data)
def test_flavor_create_other_options(self): def test_flavor_create_other_options(self):
@ -200,33 +237,47 @@ class TestFlavorCreate(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
args = ( args = {
self.flavor.name, 'name': self.flavor.name,
self.flavor.ram, 'ram': self.flavor.ram,
self.flavor.vcpus, 'vcpus': self.flavor.vcpus,
self.flavor.disk, 'disk': self.flavor.disk,
'auto', 'id': 'auto',
self.flavor.ephemeral, 'ephemeral': self.flavor.ephemeral,
self.flavor.swap, 'swap': self.flavor.swap,
self.flavor.rxtx_factor, 'rxtx_factor': self.flavor.rxtx_factor,
self.flavor.is_public, 'is_public': False,
self.flavor.description, 'description': self.flavor.description
) }
self.app.client_manager.compute.api_version = 2.55
with mock.patch.object(novaclient.api_versions, props = {'key1': 'value1', 'key2': 'value2'}
'APIVersion',
return_value=2.55): # SDK updates the flavor object instance. In order to make the
# verification clear and preciese let's create new flavor and change
# expected props this way
create_flavor = _flavor.Flavor(**self.flavor)
expected_flavor = _flavor.Flavor(**self.flavor)
expected_flavor.extra_specs = props
expected_flavor.is_public = False
# convert expected data tuple to list to be able to modify it
cmp_data = list(self.data_private)
cmp_data[7] = format_columns.DictColumn(props)
self.sdk_client.create_flavor.return_value = create_flavor
self.sdk_client.create_flavor_extra_specs.return_value = \
expected_flavor
with mock.patch.object(sdk_utils, 'supports_microversion',
return_value=True):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.flavors_mock.create.assert_called_once_with(*args) self.sdk_client.create_flavor.assert_called_once_with(**args)
self.flavor_access_mock.add_tenant_access.assert_called_with( self.sdk_client.flavor_add_tenant_access.assert_called_with(
self.flavor.id, self.flavor.id,
self.project.id, self.project.id,
) )
self.flavor.set_keys.assert_called_with( self.sdk_client.create_flavor_extra_specs.assert_called_with(
{'key1': 'value1', 'key2': 'value2'}) create_flavor, props)
self.flavor.get_keys.assert_called_with()
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(self.data, data) self.assertItemEqual(cmp_data, data)
def test_public_flavor_create_with_project(self): def test_public_flavor_create_with_project(self):
arglist = [ arglist = [
@ -278,29 +329,28 @@ class TestFlavorCreate(TestFlavor):
('name', self.flavor.name), ('name', self.flavor.name),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.app.client_manager.compute.api_version = 2.55 with mock.patch.object(sdk_utils, 'supports_microversion',
with mock.patch.object(novaclient.api_versions, return_value=True):
'APIVersion',
return_value=2.55):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
args = ( args = {
self.flavor.name, 'name': self.flavor.name,
self.flavor.ram, 'ram': self.flavor.ram,
self.flavor.vcpus, 'vcpus': self.flavor.vcpus,
self.flavor.disk, 'disk': self.flavor.disk,
self.flavor.id, 'id': self.flavor.id,
self.flavor.ephemeral, 'ephemeral': self.flavor.ephemeral,
self.flavor.swap, 'swap': self.flavor.swap,
self.flavor.rxtx_factor, 'rxtx_factor': self.flavor.rxtx_factor,
False, 'is_public': self.flavor.is_public,
'fake description', 'description': 'fake description'
) }
self.flavors_mock.create.assert_called_once_with(*args) self.sdk_client.create_flavor.assert_called_once_with(**args)
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(self.data, data) self.assertItemEqual(self.data_private, data)
def test_flavor_create_with_description_api_older(self): def test_flavor_create_with_description_api_older(self):
arglist = [ arglist = [
@ -318,10 +368,8 @@ class TestFlavorCreate(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.app.client_manager.compute.api_version = 2.54 with mock.patch.object(sdk_utils, 'supports_microversion',
with mock.patch.object(novaclient.api_versions, return_value=False):
'APIVersion',
return_value=2.55):
self.assertRaises(exceptions.CommandError, self.cmd.take_action, self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args) parsed_args)
@ -333,9 +381,7 @@ class TestFlavorDelete(TestFlavor):
def setUp(self): def setUp(self):
super(TestFlavorDelete, self).setUp() super(TestFlavorDelete, self).setUp()
self.flavors_mock.get = ( self.sdk_client.delete_flavor.return_value = None
compute_fakes.FakeFlavor.get_flavors(self.flavors))
self.flavors_mock.delete.return_value = None
self.cmd = flavor.DeleteFlavor(self.app, None) self.cmd = flavor.DeleteFlavor(self.app, None)
@ -348,9 +394,13 @@ class TestFlavorDelete(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.sdk_client.find_flavor.return_value = self.flavors[0]
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.delete.assert_called_with(self.flavors[0].id) self.sdk_client.find_flavor.assert_called_with(self.flavors[0].id,
ignore_missing=False)
self.sdk_client.delete_flavor.assert_called_with(self.flavors[0].id)
self.assertIsNone(result) self.assertIsNone(result)
def test_delete_multiple_flavors(self): def test_delete_multiple_flavors(self):
@ -362,12 +412,17 @@ class TestFlavorDelete(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.sdk_client.find_flavor.side_effect = self.flavors
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
calls = [] find_calls = [
for f in self.flavors: mock.call(i.id, ignore_missing=False) for i in self.flavors
calls.append(call(f.id)) ]
self.flavors_mock.delete.assert_has_calls(calls) delete_calls = [mock.call(i.id) for i in self.flavors]
self.sdk_client.find_flavor.assert_has_calls(find_calls)
self.sdk_client.delete_flavor.assert_has_calls(delete_calls)
self.assertIsNone(result) self.assertIsNone(result)
def test_multi_flavors_delete_with_exception(self): def test_multi_flavors_delete_with_exception(self):
@ -380,11 +435,10 @@ class TestFlavorDelete(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
find_mock_result = [self.flavors[0], exceptions.CommandError] self.sdk_client.find_flavor.side_effect = [
self.flavors_mock.get = ( self.flavors[0],
mock.Mock(side_effect=find_mock_result) sdk_exceptions.ResourceNotFound
) ]
self.flavors_mock.find.side_effect = exceptions.NotFound(None)
try: try:
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
@ -392,15 +446,18 @@ class TestFlavorDelete(TestFlavor):
except exceptions.CommandError as e: except exceptions.CommandError as e:
self.assertEqual('1 of 2 flavors failed to delete.', str(e)) self.assertEqual('1 of 2 flavors failed to delete.', str(e))
self.flavors_mock.get.assert_any_call(self.flavors[0].id) find_calls = [
self.flavors_mock.get.assert_any_call('unexist_flavor') mock.call(self.flavors[0].id, ignore_missing=False),
self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id) mock.call('unexist_flavor', ignore_missing=False),
]
delete_calls = [mock.call(self.flavors[0].id)]
self.sdk_client.find_flavor.assert_has_calls(find_calls)
self.sdk_client.delete_flavor.assert_has_calls(delete_calls)
class TestFlavorList(TestFlavor): class TestFlavorList(TestFlavor):
# Return value of self.flavors_mock.list(). _flavor = compute_fakes.FakeFlavor.create_one_flavor()
flavors = compute_fakes.FakeFlavor.create_flavors(count=1)
columns = ( columns = (
'ID', 'ID',
@ -418,24 +475,27 @@ class TestFlavorList(TestFlavor):
) )
data = (( data = ((
flavors[0].id, _flavor.id,
flavors[0].name, _flavor.name,
flavors[0].ram, _flavor.ram,
flavors[0].disk, _flavor.disk,
flavors[0].ephemeral, _flavor.ephemeral,
flavors[0].vcpus, _flavor.vcpus,
flavors[0].is_public, _flavor.is_public,
), ) ),)
data_long = (data[0] + ( data_long = (data[0] + (
flavors[0].swap, _flavor.swap,
flavors[0].rxtx_factor, _flavor.rxtx_factor,
format_columns.DictColumn(flavors[0].properties) format_columns.DictColumn(_flavor.extra_specs)
), ) ), )
def setUp(self): def setUp(self):
super(TestFlavorList, self).setUp() super(TestFlavorList, self).setUp()
self.flavors_mock.list.return_value = self.flavors self.api_mock = mock.Mock()
self.api_mock.side_effect = [[self._flavor], [], ]
self.sdk_client.flavors = self.api_mock
# Get the command object to test # Get the command object to test
self.cmd = flavor.ListFlavor(self.app, None) self.cmd = flavor.ListFlavor(self.app, None)
@ -458,16 +518,14 @@ class TestFlavorList(TestFlavor):
# Set expected values # Set expected values
kwargs = { kwargs = {
'is_public': True, 'is_public': True,
'limit': None,
'marker': None
} }
self.flavors_mock.list.assert_called_with( self.sdk_client.flavors.assert_called_with(
**kwargs **kwargs
) )
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data)) self.assertEqual(self.data, tuple(data))
def test_flavor_list_all_flavors(self): def test_flavor_list_all_flavors(self):
arglist = [ arglist = [
@ -487,16 +545,14 @@ class TestFlavorList(TestFlavor):
# Set expected values # Set expected values
kwargs = { kwargs = {
'is_public': None, 'is_public': None,
'limit': None,
'marker': None
} }
self.flavors_mock.list.assert_called_with( self.sdk_client.flavors.assert_called_with(
**kwargs **kwargs
) )
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data)) self.assertEqual(self.data, tuple(data))
def test_flavor_list_private_flavors(self): def test_flavor_list_private_flavors(self):
arglist = [ arglist = [
@ -516,16 +572,14 @@ class TestFlavorList(TestFlavor):
# Set expected values # Set expected values
kwargs = { kwargs = {
'is_public': False, 'is_public': False,
'limit': None,
'marker': None
} }
self.flavors_mock.list.assert_called_with( self.sdk_client.flavors.assert_called_with(
**kwargs **kwargs
) )
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data)) self.assertEqual(self.data, tuple(data))
def test_flavor_list_public_flavors(self): def test_flavor_list_public_flavors(self):
arglist = [ arglist = [
@ -545,16 +599,14 @@ class TestFlavorList(TestFlavor):
# Set expected values # Set expected values
kwargs = { kwargs = {
'is_public': True, 'is_public': True,
'limit': None,
'marker': None
} }
self.flavors_mock.list.assert_called_with( self.sdk_client.flavors.assert_called_with(
**kwargs **kwargs
) )
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data)) self.assertEqual(self.data, tuple(data))
def test_flavor_list_long(self): def test_flavor_list_long(self):
arglist = [ arglist = [
@ -574,11 +626,9 @@ class TestFlavorList(TestFlavor):
# Set expected values # Set expected values
kwargs = { kwargs = {
'is_public': True, 'is_public': True,
'limit': None,
'marker': None
} }
self.flavors_mock.list.assert_called_with( self.sdk_client.flavors.assert_called_with(
**kwargs **kwargs
) )
@ -588,7 +638,7 @@ class TestFlavorList(TestFlavor):
class TestFlavorSet(TestFlavor): class TestFlavorSet(TestFlavor):
# Return value of self.flavors_mock.find(). # Return value of self.sdk_client.find_flavor().
flavor = compute_fakes.FakeFlavor.create_one_flavor( flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'os-flavor-access:is_public': False}) attrs={'os-flavor-access:is_public': False})
project = identity_fakes.FakeProject.create_one_project() project = identity_fakes.FakeProject.create_one_project()
@ -596,8 +646,7 @@ class TestFlavorSet(TestFlavor):
def setUp(self): def setUp(self):
super(TestFlavorSet, self).setUp() super(TestFlavorSet, self).setUp()
self.flavors_mock.find.return_value = self.flavor self.sdk_client.find_flavor.return_value = self.flavor
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
# Return a project # Return a project
self.projects_mock.get.return_value = self.project self.projects_mock.get.return_value = self.project
self.cmd = flavor.SetFlavor(self.app, None) self.cmd = flavor.SetFlavor(self.app, None)
@ -614,9 +663,14 @@ class TestFlavorSet(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, self.sdk_client.find_flavor.assert_called_with(
is_public=None) parsed_args.flavor,
self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'}) get_extra_specs=True,
ignore_missing=False
)
self.sdk_client.create_flavor_extra_specs.assert_called_with(
self.flavor.id,
{'FOO': '"B A R"'})
self.assertIsNone(result) self.assertIsNone(result)
def test_flavor_set_no_property(self): def test_flavor_set_no_property(self):
@ -631,9 +685,13 @@ class TestFlavorSet(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, self.sdk_client.find_flavor.assert_called_with(
is_public=None) parsed_args.flavor,
self.flavor.unset_keys.assert_called_with(['property']) get_extra_specs=True,
ignore_missing=False
)
self.sdk_client.delete_flavor_extra_specs_property.assert_called_with(
self.flavor.id, 'property')
self.assertIsNone(result) self.assertIsNone(result)
def test_flavor_set_project(self): def test_flavor_set_project(self):
@ -649,13 +707,16 @@ class TestFlavorSet(TestFlavor):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, self.sdk_client.find_flavor.assert_called_with(
is_public=None) parsed_args.flavor,
self.flavor_access_mock.add_tenant_access.assert_called_with( get_extra_specs=True,
ignore_missing=False
)
self.sdk_client.flavor_add_tenant_access.assert_called_with(
self.flavor.id, self.flavor.id,
self.project.id, self.project.id,
) )
self.flavor.set_keys.assert_not_called() self.sdk_client.create_flavor_extra_specs.assert_not_called()
self.assertIsNone(result) self.assertIsNone(result)
def test_flavor_set_no_project(self): def test_flavor_set_no_project(self):
@ -681,8 +742,9 @@ class TestFlavorSet(TestFlavor):
self.cmd, arglist, verifylist) self.cmd, arglist, verifylist)
def test_flavor_set_with_unexist_flavor(self): def test_flavor_set_with_unexist_flavor(self):
self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.sdk_client.find_flavor.side_effect = [
self.flavors_mock.find.side_effect = exceptions.NotFound(None) sdk_exceptions.ResourceNotFound()
]
arglist = [ arglist = [
'--project', self.project.id, '--project', self.project.id,
@ -708,9 +770,12 @@ class TestFlavorSet(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, self.sdk_client.find_flavor.assert_called_with(
is_public=None) parsed_args.flavor,
self.flavor_access_mock.add_tenant_access.assert_not_called() get_extra_specs=True,
ignore_missing=False
)
self.sdk_client.flavor_add_tenant_access.assert_not_called()
self.assertIsNone(result) self.assertIsNone(result)
def test_flavor_set_description_api_newer(self): def test_flavor_set_description_api_newer(self):
@ -724,11 +789,11 @@ class TestFlavorSet(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.app.client_manager.compute.api_version = 2.55 self.app.client_manager.compute.api_version = 2.55
with mock.patch.object(novaclient.api_versions, with mock.patch.object(sdk_utils,
'APIVersion', 'supports_microversion',
return_value=2.55): return_value=True):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.update.assert_called_with( self.sdk_client.update_flavor.assert_called_with(
flavor=self.flavor.id, description='description') flavor=self.flavor.id, description='description')
self.assertIsNone(result) self.assertIsNone(result)
@ -743,9 +808,9 @@ class TestFlavorSet(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.app.client_manager.compute.api_version = 2.54 self.app.client_manager.compute.api_version = 2.54
with mock.patch.object(novaclient.api_versions, with mock.patch.object(sdk_utils,
'APIVersion', 'supports_microversion',
return_value=2.55): return_value=False):
self.assertRaises(exceptions.CommandError, self.cmd.take_action, self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args) parsed_args)
@ -760,11 +825,12 @@ class TestFlavorSet(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.app.client_manager.compute.api_version = 2.55 self.app.client_manager.compute.api_version = 2.55
with mock.patch.object(novaclient.api_versions,
'APIVersion', with mock.patch.object(sdk_utils,
return_value=2.55): 'supports_microversion',
return_value=True):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.update.assert_called_with( self.sdk_client.update_flavor.assert_called_with(
flavor=self.flavor.id, description='description') flavor=self.flavor.id, description='description')
self.assertIsNone(result) self.assertIsNone(result)
@ -779,16 +845,17 @@ class TestFlavorSet(TestFlavor):
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.app.client_manager.compute.api_version = 2.54 self.app.client_manager.compute.api_version = 2.54
with mock.patch.object(novaclient.api_versions,
'APIVersion', with mock.patch.object(sdk_utils,
return_value=2.55): 'supports_microversion',
return_value=False):
self.assertRaises(exceptions.CommandError, self.cmd.take_action, self.assertRaises(exceptions.CommandError, self.cmd.take_action,
parsed_args) parsed_args)
class TestFlavorShow(TestFlavor): class TestFlavorShow(TestFlavor):
# Return value of self.flavors_mock.find(). # Return value of self.sdk_client.find_flavor().
flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access() flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access()
flavor = compute_fakes.FakeFlavor.create_one_flavor() flavor = compute_fakes.FakeFlavor.create_one_flavor()
@ -805,11 +872,11 @@ class TestFlavorShow(TestFlavor):
'ram', 'ram',
'rxtx_factor', 'rxtx_factor',
'swap', 'swap',
'vcpus', 'vcpus'
) )
data = ( data = (
flavor.disabled, flavor.is_disabled,
flavor.ephemeral, flavor.ephemeral,
None, None,
flavor.description, flavor.description,
@ -817,7 +884,7 @@ class TestFlavorShow(TestFlavor):
flavor.id, flavor.id,
flavor.name, flavor.name,
flavor.is_public, flavor.is_public,
format_columns.DictColumn(flavor.get_keys()), format_columns.DictColumn(flavor.extra_specs),
flavor.ram, flavor.ram,
flavor.rxtx_factor, flavor.rxtx_factor,
flavor.swap, flavor.swap,
@ -828,9 +895,8 @@ class TestFlavorShow(TestFlavor):
super(TestFlavorShow, self).setUp() super(TestFlavorShow, self).setUp()
# Return value of _find_resource() # Return value of _find_resource()
self.flavors_mock.find.return_value = self.flavor self.sdk_client.find_flavor.return_value = self.flavor
self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.sdk_client.get_flavor_access.return_value = [self.flavor_access]
self.flavor_access_mock.list.return_value = [self.flavor_access]
self.cmd = flavor.ShowFlavor(self.app, None) self.cmd = flavor.ShowFlavor(self.app, None)
def test_show_no_options(self): def test_show_no_options(self):
@ -862,7 +928,7 @@ class TestFlavorShow(TestFlavor):
'os-flavor-access:is_public': False, 'os-flavor-access:is_public': False,
} }
) )
self.flavors_mock.find.return_value = private_flavor self.sdk_client.find_flavor.return_value = private_flavor
arglist = [ arglist = [
private_flavor.name, private_flavor.name,
@ -872,7 +938,7 @@ class TestFlavorShow(TestFlavor):
] ]
data_with_project = ( data_with_project = (
private_flavor.disabled, private_flavor.is_disabled,
private_flavor.ephemeral, private_flavor.ephemeral,
[self.flavor_access.tenant_id], [self.flavor_access.tenant_id],
private_flavor.description, private_flavor.description,
@ -880,7 +946,7 @@ class TestFlavorShow(TestFlavor):
private_flavor.id, private_flavor.id,
private_flavor.name, private_flavor.name,
private_flavor.is_public, private_flavor.is_public,
format_columns.DictColumn(private_flavor.get_keys()), format_columns.DictColumn(private_flavor.extra_specs),
private_flavor.ram, private_flavor.ram,
private_flavor.rxtx_factor, private_flavor.rxtx_factor,
private_flavor.swap, private_flavor.swap,
@ -891,7 +957,7 @@ class TestFlavorShow(TestFlavor):
columns, data = self.cmd.take_action(parsed_args) columns, data = self.cmd.take_action(parsed_args)
self.flavor_access_mock.list.assert_called_with( self.sdk_client.get_flavor_access.assert_called_with(
flavor=private_flavor.id) flavor=private_flavor.id)
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertItemEqual(data_with_project, data) self.assertItemEqual(data_with_project, data)
@ -899,7 +965,7 @@ class TestFlavorShow(TestFlavor):
class TestFlavorUnset(TestFlavor): class TestFlavorUnset(TestFlavor):
# Return value of self.flavors_mock.find(). # Return value of self.sdk_client.find_flavor().
flavor = compute_fakes.FakeFlavor.create_one_flavor( flavor = compute_fakes.FakeFlavor.create_one_flavor(
attrs={'os-flavor-access:is_public': False}) attrs={'os-flavor-access:is_public': False})
project = identity_fakes.FakeProject.create_one_project() project = identity_fakes.FakeProject.create_one_project()
@ -907,12 +973,13 @@ class TestFlavorUnset(TestFlavor):
def setUp(self): def setUp(self):
super(TestFlavorUnset, self).setUp() super(TestFlavorUnset, self).setUp()
self.flavors_mock.find.return_value = self.flavor self.sdk_client.find_flavor.return_value = self.flavor
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
# Return a project # Return a project
self.projects_mock.get.return_value = self.project self.projects_mock.get.return_value = self.project
self.cmd = flavor.UnsetFlavor(self.app, None) self.cmd = flavor.UnsetFlavor(self.app, None)
self.mock_shortcut = self.sdk_client.delete_flavor_extra_specs_property
def test_flavor_unset_property(self): def test_flavor_unset_property(self):
arglist = [ arglist = [
'--property', 'property', '--property', 'property',
@ -925,12 +992,49 @@ class TestFlavorUnset(TestFlavor):
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, self.sdk_client.find_flavor.assert_called_with(
is_public=None) parsed_args.flavor,
self.flavor.unset_keys.assert_called_with(['property']) get_extra_specs=True,
self.flavor_access_mock.remove_tenant_access.assert_not_called() ignore_missing=False)
self.mock_shortcut.assert_called_with(
self.flavor.id, 'property')
self.sdk_client.flavor_remove_tenant_access.assert_not_called()
self.assertIsNone(result) self.assertIsNone(result)
def test_flavor_unset_properties(self):
arglist = [
'--property', 'property1',
'--property', 'property2',
'baremetal'
]
verifylist = [
('property', ['property1', 'property2']),
('flavor', 'baremetal'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.sdk_client.find_flavor.assert_called_with(
parsed_args.flavor,
get_extra_specs=True,
ignore_missing=False)
calls = [
mock.call(self.flavor.id, 'property1'),
mock.call(self.flavor.id, 'property2')
]
self.mock_shortcut.assert_has_calls(
calls)
# A bit tricky way to ensure we do not unset other properties
calls.append(mock.call(self.flavor.id, 'property'))
self.assertRaises(
AssertionError,
self.mock_shortcut.assert_has_calls,
calls
)
self.sdk_client.flavor_remove_tenant_access.assert_not_called()
def test_flavor_unset_project(self): def test_flavor_unset_project(self):
arglist = [ arglist = [
'--project', self.project.id, '--project', self.project.id,
@ -945,13 +1049,14 @@ class TestFlavorUnset(TestFlavor):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.assertIsNone(result) self.assertIsNone(result)
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, self.sdk_client.find_flavor.assert_called_with(
is_public=None) parsed_args.flavor, get_extra_specs=True,
self.flavor_access_mock.remove_tenant_access.assert_called_with( ignore_missing=False)
self.sdk_client.flavor_remove_tenant_access.assert_called_with(
self.flavor.id, self.flavor.id,
self.project.id, self.project.id,
) )
self.flavor.unset_keys.assert_not_called() self.sdk_client.delete_flavor_extra_specs_proerty.assert_not_called()
self.assertIsNone(result) self.assertIsNone(result)
def test_flavor_unset_no_project(self): def test_flavor_unset_no_project(self):
@ -977,8 +1082,9 @@ class TestFlavorUnset(TestFlavor):
self.cmd, arglist, verifylist) self.cmd, arglist, verifylist)
def test_flavor_unset_with_unexist_flavor(self): def test_flavor_unset_with_unexist_flavor(self):
self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.sdk_client.find_flavor.side_effect = [
self.flavors_mock.find.side_effect = exceptions.NotFound(None) sdk_exceptions.ResourceNotFound
]
arglist = [ arglist = [
'--project', self.project.id, '--project', self.project.id,
@ -1004,4 +1110,4 @@ class TestFlavorUnset(TestFlavor):
result = self.cmd.take_action(parsed_args) result = self.cmd.take_action(parsed_args)
self.assertIsNone(result) self.assertIsNone(result)
self.flavor_access_mock.remove_tenant_access.assert_not_called() self.sdk_client.flavor_remove_tenant_access.assert_not_called()

View File

@ -0,0 +1,4 @@
---
features:
- Switch compute.flavor operations from direct API calls (novaclient) to
OpenStackSDK.

View File

@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
cliff>=3.4.0 # Apache-2.0 cliff>=3.4.0 # Apache-2.0
iso8601>=0.1.11 # MIT iso8601>=0.1.11 # MIT
openstacksdk>=0.51.0 # Apache-2.0 openstacksdk>=0.52.0 # Apache-2.0
osc-lib>=2.2.0 # Apache-2.0 osc-lib>=2.2.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0
python-keystoneclient>=3.22.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0