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:
parent
ea358057f8
commit
0f4f42b652
@ -45,7 +45,7 @@ msgpack-python==0.4.0
|
||||
munch==2.1.0
|
||||
netaddr==0.7.18
|
||||
netifaces==0.10.4
|
||||
openstacksdk==0.51.0
|
||||
openstacksdk==0.52.0
|
||||
os-service-types==1.7.0
|
||||
os-testr==1.0.0
|
||||
osc-lib==2.2.0
|
||||
|
@ -17,7 +17,8 @@
|
||||
|
||||
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 parseractions
|
||||
from osc_lib.command import command
|
||||
@ -33,10 +34,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
_formatters = {
|
||||
'extra_specs': format_columns.DictColumn,
|
||||
# Unless we finish switch to use SDK resources this need to be doubled this
|
||||
# way
|
||||
'properties': format_columns.DictColumn,
|
||||
'Properties': format_columns.DictColumn
|
||||
'properties': format_columns.DictColumn
|
||||
}
|
||||
|
||||
|
||||
@ -51,29 +49,10 @@ def _get_flavor_columns(item):
|
||||
|
||||
}
|
||||
hidden_columns = ['links', 'location']
|
||||
|
||||
return utils.get_osc_show_columns_for_sdk_resource(
|
||||
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):
|
||||
_description = _("Create new flavor")
|
||||
|
||||
@ -87,9 +66,7 @@ class CreateFlavor(command.ShowOne):
|
||||
parser.add_argument(
|
||||
"--id",
|
||||
metavar="<id>",
|
||||
default='auto',
|
||||
help=_("Unique flavor ID; 'auto' creates a UUID "
|
||||
"(default: auto)")
|
||||
help=_("Unique flavor ID")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ram",
|
||||
@ -170,32 +147,36 @@ class CreateFlavor(command.ShowOne):
|
||||
return parser
|
||||
|
||||
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
|
||||
|
||||
if parsed_args.project and parsed_args.public:
|
||||
msg = _("--project is only allowed with --private")
|
||||
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 compute_client.api_version < api_versions.APIVersion("2.55"):
|
||||
msg = _("--os-compute-api-version 2.55 or later is required")
|
||||
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)
|
||||
|
||||
args = (
|
||||
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
|
||||
)
|
||||
args['description'] = parsed_args.description
|
||||
|
||||
flavor = compute_client.flavors.create(*args)
|
||||
flavor = compute_client.create_flavor(**args)
|
||||
|
||||
if parsed_args.project:
|
||||
try:
|
||||
@ -204,7 +185,7 @@ class CreateFlavor(command.ShowOne):
|
||||
parsed_args.project,
|
||||
parsed_args.project_domain,
|
||||
).id
|
||||
compute_client.flavor_access.add_tenant_access(
|
||||
compute_client.flavor_add_tenant_access(
|
||||
flavor.id, project_id)
|
||||
except Exception as e:
|
||||
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})
|
||||
if parsed_args.property:
|
||||
try:
|
||||
flavor.set_keys(parsed_args.property)
|
||||
flavor = compute_client.create_flavor_extra_specs(
|
||||
flavor, parsed_args.property)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set flavor property: %s"), e)
|
||||
|
||||
flavor_info = flavor._info.copy()
|
||||
flavor_info['properties'] = flavor.get_keys()
|
||||
|
||||
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'])
|
||||
display_columns, columns = _get_flavor_columns(flavor)
|
||||
data = utils.get_dict_properties(flavor, columns,
|
||||
formatters=_formatters)
|
||||
|
||||
return (display_columns, data)
|
||||
|
||||
@ -243,12 +219,12 @@ class DeleteFlavor(command.Command):
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
compute_client = self.app.client_manager.sdk_connection.compute
|
||||
result = 0
|
||||
for f in parsed_args.flavor:
|
||||
try:
|
||||
flavor = _find_flavor(compute_client, f)
|
||||
compute_client.flavors.delete(flavor.id)
|
||||
flavor = compute_client.find_flavor(f, ignore_missing=False)
|
||||
compute_client.delete_flavor(flavor.id)
|
||||
except Exception as e:
|
||||
result += 1
|
||||
LOG.error(_("Failed to delete flavor with name or "
|
||||
@ -307,37 +283,63 @@ class ListFlavor(command.Lister):
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
columns = (
|
||||
"ID",
|
||||
"Name",
|
||||
"RAM",
|
||||
"Disk",
|
||||
"Ephemeral",
|
||||
"VCPUs",
|
||||
"Is Public",
|
||||
)
|
||||
|
||||
compute_client = self.app.client_manager.sdk_connection.compute
|
||||
# is_public is ternary - None means give all flavors,
|
||||
# True is public only and False is private only
|
||||
# By default Nova assumes True and gives admins public flavors
|
||||
# and flavors from their own projects only.
|
||||
is_public = None if parsed_args.all else parsed_args.public
|
||||
|
||||
data = compute_client.flavors.list(is_public=is_public,
|
||||
marker=parsed_args.marker,
|
||||
limit=parsed_args.limit)
|
||||
query_attrs = {
|
||||
'is_public': is_public
|
||||
}
|
||||
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:
|
||||
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",
|
||||
"RXTX Factor",
|
||||
"Properties",
|
||||
)
|
||||
for f in data:
|
||||
f.properties = f.get_keys()
|
||||
|
||||
column_headers = columns
|
||||
|
||||
return (column_headers,
|
||||
(utils.get_item_properties(
|
||||
@ -387,24 +389,42 @@ class SetFlavor(command.Command):
|
||||
return parser
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
key_list = []
|
||||
if parsed_args.no_property:
|
||||
try:
|
||||
for key in flavor.get_keys().keys():
|
||||
key_list.append(key)
|
||||
flavor.unset_keys(key_list)
|
||||
for key in flavor.extra_specs.keys():
|
||||
compute_client.delete_flavor_extra_specs_property(
|
||||
flavor.id, key)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to clear flavor property: %s"), e)
|
||||
result += 1
|
||||
|
||||
if parsed_args.property:
|
||||
try:
|
||||
flavor.set_keys(parsed_args.property)
|
||||
compute_client.create_flavor_extra_specs(
|
||||
flavor.id, parsed_args.property)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to set flavor property: %s"), e)
|
||||
result += 1
|
||||
@ -420,7 +440,7 @@ class SetFlavor(command.Command):
|
||||
parsed_args.project,
|
||||
parsed_args.project_domain,
|
||||
).id
|
||||
compute_client.flavor_access.add_tenant_access(
|
||||
compute_client.flavor_add_tenant_access(
|
||||
flavor.id, project_id)
|
||||
except Exception as 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"
|
||||
" 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):
|
||||
_description = _("Display flavor details")
|
||||
@ -451,35 +464,32 @@ class ShowFlavor(command.ShowOne):
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
compute_client = self.app.client_manager.compute
|
||||
resource_flavor = _find_flavor(compute_client, parsed_args.flavor)
|
||||
compute_client = self.app.client_manager.sdk_connection.compute
|
||||
flavor = compute_client.find_flavor(
|
||||
parsed_args.flavor, get_extra_specs=True, ignore_missing=False)
|
||||
|
||||
access_projects = None
|
||||
# get access projects list of this flavor
|
||||
if not resource_flavor.is_public:
|
||||
if not flavor.is_public:
|
||||
try:
|
||||
flavor_access = compute_client.flavor_access.list(
|
||||
flavor=resource_flavor.id)
|
||||
access_projects = [utils.get_field(access, 'tenant_id')
|
||||
for access in flavor_access]
|
||||
flavor_access = compute_client.get_flavor_access(
|
||||
flavor=flavor.id)
|
||||
access_projects = [
|
||||
utils.get_field(access, 'tenant_id')
|
||||
for access in flavor_access]
|
||||
except Exception as e:
|
||||
msg = _("Failed to get access projects list "
|
||||
"for flavor '%(flavor)s': %(e)s")
|
||||
LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e})
|
||||
|
||||
flavor = resource_flavor._info.copy()
|
||||
flavor.update({
|
||||
'access_project_ids': access_projects
|
||||
})
|
||||
|
||||
flavor['properties'] = resource_flavor.get_keys()
|
||||
# Since we need to inject "access_project_id" into resource - convert
|
||||
# it to dict and treat it respectively
|
||||
flavor = flavor.to_dict()
|
||||
flavor['access_project_ids'] = access_projects
|
||||
|
||||
display_columns, columns = _get_flavor_columns(flavor)
|
||||
data = utils.get_dict_properties(
|
||||
flavor, columns,
|
||||
formatters=_formatters,
|
||||
mixed_case_fields=['OS-FLV-DISABLED:disabled',
|
||||
'OS-FLV-EXT-DATA:ephemeral'])
|
||||
flavor, columns, formatters=_formatters)
|
||||
|
||||
return (display_columns, data)
|
||||
|
||||
@ -512,32 +522,40 @@ class UnsetFlavor(command.Command):
|
||||
return parser
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
if parsed_args.property:
|
||||
try:
|
||||
flavor.unset_keys(parsed_args.property)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to unset flavor property: %s"), e)
|
||||
result += 1
|
||||
for key in parsed_args.property:
|
||||
try:
|
||||
compute_client.delete_flavor_extra_specs_property(
|
||||
flavor.id, key)
|
||||
except sdk_exceptions.SDKException as e:
|
||||
LOG.error(_("Failed to unset flavor property: %s"), e)
|
||||
result += 1
|
||||
|
||||
if parsed_args.project:
|
||||
try:
|
||||
if flavor.is_public:
|
||||
msg = _("Cannot remove access for a public flavor")
|
||||
raise exceptions.CommandError(msg)
|
||||
else:
|
||||
project_id = identity_common.find_project(
|
||||
identity_client,
|
||||
parsed_args.project,
|
||||
parsed_args.project_domain,
|
||||
).id
|
||||
compute_client.flavor_access.remove_tenant_access(
|
||||
flavor.id, project_id)
|
||||
|
||||
project_id = identity_common.find_project(
|
||||
identity_client,
|
||||
parsed_args.project,
|
||||
parsed_args.project_domain,
|
||||
).id
|
||||
compute_client.flavor_remove_tenant_access(
|
||||
flavor.id, project_id)
|
||||
except Exception as e:
|
||||
LOG.error(_("Failed to remove flavor access from project: %s"),
|
||||
e)
|
||||
|
@ -19,6 +19,7 @@ from unittest import mock
|
||||
import uuid
|
||||
|
||||
from novaclient import api_versions
|
||||
from openstack.compute.v2 import flavor as _flavor
|
||||
|
||||
from openstackclient.api import compute_v2
|
||||
from openstackclient.tests.unit import fakes
|
||||
@ -164,7 +165,6 @@ class FakeComputev2Client(object):
|
||||
self.extensions.resource_class = fakes.FakeResource(None, {})
|
||||
|
||||
self.flavors = mock.Mock()
|
||||
self.flavors.resource_class = fakes.FakeResource(None, {})
|
||||
|
||||
self.flavor_access = mock.Mock()
|
||||
self.flavor_access.resource_class = fakes.FakeResource(None, {})
|
||||
@ -777,27 +777,13 @@ class FakeFlavor(object):
|
||||
'os-flavor-access:is_public': True,
|
||||
'description': 'description',
|
||||
'OS-FLV-EXT-DATA:ephemeral': 0,
|
||||
'properties': {'property': 'value'},
|
||||
'extra_specs': {'property': 'value'},
|
||||
}
|
||||
|
||||
# Overwrite default attributes.
|
||||
flavor_info.update(attrs)
|
||||
|
||||
# Set default methods.
|
||||
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']
|
||||
flavor = _flavor.Flavor(**flavor_info)
|
||||
|
||||
return flavor
|
||||
|
||||
|
@ -12,11 +12,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
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 import exceptions
|
||||
|
||||
@ -31,13 +31,19 @@ class TestFlavor(compute_fakes.TestComputev2):
|
||||
def setUp(self):
|
||||
super(TestFlavor, self).setUp()
|
||||
|
||||
# Get a shortcut to the FlavorManager Mock
|
||||
self.flavors_mock = self.app.client_manager.compute.flavors
|
||||
self.flavors_mock.reset_mock()
|
||||
|
||||
# Get a shortcut to the FlavorAccessManager Mock
|
||||
self.flavor_access_mock = self.app.client_manager.compute.flavor_access
|
||||
self.flavor_access_mock.reset_mock()
|
||||
# SDK mock
|
||||
self.app.client_manager.sdk_connection = mock.Mock()
|
||||
self.app.client_manager.sdk_connection.compute = mock.Mock()
|
||||
self.sdk_client = self.app.client_manager.sdk_connection.compute
|
||||
self.sdk_client.flavors = mock.Mock()
|
||||
self.sdk_client.find_flavor = mock.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.reset_mock()
|
||||
@ -48,6 +54,7 @@ class TestFlavorCreate(TestFlavor):
|
||||
flavor = compute_fakes.FakeFlavor.create_one_flavor(
|
||||
attrs={'links': 'flavor-links'})
|
||||
project = identity_fakes.FakeProject.create_one_project()
|
||||
|
||||
columns = (
|
||||
'OS-FLV-DISABLED:disabled',
|
||||
'OS-FLV-EXT-DATA:ephemeral',
|
||||
@ -60,17 +67,32 @@ class TestFlavorCreate(TestFlavor):
|
||||
'ram',
|
||||
'rxtx_factor',
|
||||
'swap',
|
||||
'vcpus',
|
||||
'vcpus'
|
||||
)
|
||||
|
||||
data = (
|
||||
flavor.disabled,
|
||||
flavor.is_disabled,
|
||||
flavor.ephemeral,
|
||||
flavor.description,
|
||||
flavor.disk,
|
||||
flavor.id,
|
||||
flavor.name,
|
||||
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.rxtx_factor,
|
||||
flavor.swap,
|
||||
@ -82,7 +104,7 @@ class TestFlavorCreate(TestFlavor):
|
||||
|
||||
# Return a 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)
|
||||
|
||||
def test_flavor_create_default_options(self):
|
||||
@ -95,20 +117,20 @@ class TestFlavorCreate(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
default_args = (
|
||||
self.flavor.name,
|
||||
256,
|
||||
1,
|
||||
0,
|
||||
'auto',
|
||||
0,
|
||||
0,
|
||||
1.0,
|
||||
True,
|
||||
None,
|
||||
)
|
||||
default_args = {
|
||||
'name': self.flavor.name,
|
||||
'ram': 256,
|
||||
'vcpus': 1,
|
||||
'disk': 0,
|
||||
'id': None,
|
||||
'ephemeral': 0,
|
||||
'swap': 0,
|
||||
'rxtx_factor': 1.0,
|
||||
'is_public': True,
|
||||
}
|
||||
|
||||
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.assertItemEqual(self.data, data)
|
||||
@ -143,29 +165,44 @@ class TestFlavorCreate(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
args = (
|
||||
self.flavor.name,
|
||||
self.flavor.ram,
|
||||
self.flavor.vcpus,
|
||||
self.flavor.disk,
|
||||
self.flavor.id,
|
||||
self.flavor.ephemeral,
|
||||
self.flavor.swap,
|
||||
self.flavor.rxtx_factor,
|
||||
self.flavor.is_public,
|
||||
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()
|
||||
args = {
|
||||
'name': self.flavor.name,
|
||||
'ram': self.flavor.ram,
|
||||
'vcpus': self.flavor.vcpus,
|
||||
'disk': self.flavor.disk,
|
||||
'id': self.flavor.id,
|
||||
'ephemeral': self.flavor.ephemeral,
|
||||
'swap': self.flavor.swap,
|
||||
'rxtx_factor': self.flavor.rxtx_factor,
|
||||
'is_public': self.flavor.is_public,
|
||||
'description': self.flavor.description
|
||||
}
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertItemEqual(self.data, data)
|
||||
props = {'property': 'value'}
|
||||
|
||||
# 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):
|
||||
|
||||
@ -200,33 +237,47 @@ class TestFlavorCreate(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
args = (
|
||||
self.flavor.name,
|
||||
self.flavor.ram,
|
||||
self.flavor.vcpus,
|
||||
self.flavor.disk,
|
||||
'auto',
|
||||
self.flavor.ephemeral,
|
||||
self.flavor.swap,
|
||||
self.flavor.rxtx_factor,
|
||||
self.flavor.is_public,
|
||||
self.flavor.description,
|
||||
)
|
||||
self.app.client_manager.compute.api_version = 2.55
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
args = {
|
||||
'name': self.flavor.name,
|
||||
'ram': self.flavor.ram,
|
||||
'vcpus': self.flavor.vcpus,
|
||||
'disk': self.flavor.disk,
|
||||
'id': 'auto',
|
||||
'ephemeral': self.flavor.ephemeral,
|
||||
'swap': self.flavor.swap,
|
||||
'rxtx_factor': self.flavor.rxtx_factor,
|
||||
'is_public': False,
|
||||
'description': self.flavor.description
|
||||
}
|
||||
|
||||
props = {'key1': 'value1', 'key2': 'value2'}
|
||||
|
||||
# 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)
|
||||
self.flavors_mock.create.assert_called_once_with(*args)
|
||||
self.flavor_access_mock.add_tenant_access.assert_called_with(
|
||||
self.sdk_client.create_flavor.assert_called_once_with(**args)
|
||||
self.sdk_client.flavor_add_tenant_access.assert_called_with(
|
||||
self.flavor.id,
|
||||
self.project.id,
|
||||
)
|
||||
self.flavor.set_keys.assert_called_with(
|
||||
{'key1': 'value1', 'key2': 'value2'})
|
||||
self.flavor.get_keys.assert_called_with()
|
||||
self.sdk_client.create_flavor_extra_specs.assert_called_with(
|
||||
create_flavor, props)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertItemEqual(self.data, data)
|
||||
self.assertItemEqual(cmp_data, data)
|
||||
|
||||
def test_public_flavor_create_with_project(self):
|
||||
arglist = [
|
||||
@ -278,29 +329,28 @@ class TestFlavorCreate(TestFlavor):
|
||||
('name', self.flavor.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.app.client_manager.compute.api_version = 2.55
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
with mock.patch.object(sdk_utils, 'supports_microversion',
|
||||
return_value=True):
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
args = (
|
||||
self.flavor.name,
|
||||
self.flavor.ram,
|
||||
self.flavor.vcpus,
|
||||
self.flavor.disk,
|
||||
self.flavor.id,
|
||||
self.flavor.ephemeral,
|
||||
self.flavor.swap,
|
||||
self.flavor.rxtx_factor,
|
||||
False,
|
||||
'fake description',
|
||||
)
|
||||
args = {
|
||||
'name': self.flavor.name,
|
||||
'ram': self.flavor.ram,
|
||||
'vcpus': self.flavor.vcpus,
|
||||
'disk': self.flavor.disk,
|
||||
'id': self.flavor.id,
|
||||
'ephemeral': self.flavor.ephemeral,
|
||||
'swap': self.flavor.swap,
|
||||
'rxtx_factor': self.flavor.rxtx_factor,
|
||||
'is_public': self.flavor.is_public,
|
||||
'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.assertItemEqual(self.data, data)
|
||||
self.assertItemEqual(self.data_private, data)
|
||||
|
||||
def test_flavor_create_with_description_api_older(self):
|
||||
arglist = [
|
||||
@ -318,10 +368,8 @@ class TestFlavorCreate(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.app.client_manager.compute.api_version = 2.54
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
with mock.patch.object(sdk_utils, 'supports_microversion',
|
||||
return_value=False):
|
||||
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
|
||||
parsed_args)
|
||||
|
||||
@ -333,9 +381,7 @@ class TestFlavorDelete(TestFlavor):
|
||||
def setUp(self):
|
||||
super(TestFlavorDelete, self).setUp()
|
||||
|
||||
self.flavors_mock.get = (
|
||||
compute_fakes.FakeFlavor.get_flavors(self.flavors))
|
||||
self.flavors_mock.delete.return_value = None
|
||||
self.sdk_client.delete_flavor.return_value = None
|
||||
|
||||
self.cmd = flavor.DeleteFlavor(self.app, None)
|
||||
|
||||
@ -348,9 +394,13 @@ class TestFlavorDelete(TestFlavor):
|
||||
]
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_delete_multiple_flavors(self):
|
||||
@ -362,12 +412,17 @@ class TestFlavorDelete(TestFlavor):
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
calls = []
|
||||
for f in self.flavors:
|
||||
calls.append(call(f.id))
|
||||
self.flavors_mock.delete.assert_has_calls(calls)
|
||||
find_calls = [
|
||||
mock.call(i.id, ignore_missing=False) for i in self.flavors
|
||||
]
|
||||
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)
|
||||
|
||||
def test_multi_flavors_delete_with_exception(self):
|
||||
@ -380,11 +435,10 @@ class TestFlavorDelete(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
find_mock_result = [self.flavors[0], exceptions.CommandError]
|
||||
self.flavors_mock.get = (
|
||||
mock.Mock(side_effect=find_mock_result)
|
||||
)
|
||||
self.flavors_mock.find.side_effect = exceptions.NotFound(None)
|
||||
self.sdk_client.find_flavor.side_effect = [
|
||||
self.flavors[0],
|
||||
sdk_exceptions.ResourceNotFound
|
||||
]
|
||||
|
||||
try:
|
||||
self.cmd.take_action(parsed_args)
|
||||
@ -392,15 +446,18 @@ class TestFlavorDelete(TestFlavor):
|
||||
except exceptions.CommandError as e:
|
||||
self.assertEqual('1 of 2 flavors failed to delete.', str(e))
|
||||
|
||||
self.flavors_mock.get.assert_any_call(self.flavors[0].id)
|
||||
self.flavors_mock.get.assert_any_call('unexist_flavor')
|
||||
self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id)
|
||||
find_calls = [
|
||||
mock.call(self.flavors[0].id, ignore_missing=False),
|
||||
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):
|
||||
|
||||
# Return value of self.flavors_mock.list().
|
||||
flavors = compute_fakes.FakeFlavor.create_flavors(count=1)
|
||||
_flavor = compute_fakes.FakeFlavor.create_one_flavor()
|
||||
|
||||
columns = (
|
||||
'ID',
|
||||
@ -418,24 +475,27 @@ class TestFlavorList(TestFlavor):
|
||||
)
|
||||
|
||||
data = ((
|
||||
flavors[0].id,
|
||||
flavors[0].name,
|
||||
flavors[0].ram,
|
||||
flavors[0].disk,
|
||||
flavors[0].ephemeral,
|
||||
flavors[0].vcpus,
|
||||
flavors[0].is_public,
|
||||
), )
|
||||
_flavor.id,
|
||||
_flavor.name,
|
||||
_flavor.ram,
|
||||
_flavor.disk,
|
||||
_flavor.ephemeral,
|
||||
_flavor.vcpus,
|
||||
_flavor.is_public,
|
||||
),)
|
||||
data_long = (data[0] + (
|
||||
flavors[0].swap,
|
||||
flavors[0].rxtx_factor,
|
||||
format_columns.DictColumn(flavors[0].properties)
|
||||
_flavor.swap,
|
||||
_flavor.rxtx_factor,
|
||||
format_columns.DictColumn(_flavor.extra_specs)
|
||||
), )
|
||||
|
||||
def setUp(self):
|
||||
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
|
||||
self.cmd = flavor.ListFlavor(self.app, None)
|
||||
@ -458,16 +518,14 @@ class TestFlavorList(TestFlavor):
|
||||
# Set expected values
|
||||
kwargs = {
|
||||
'is_public': True,
|
||||
'limit': None,
|
||||
'marker': None
|
||||
}
|
||||
|
||||
self.flavors_mock.list.assert_called_with(
|
||||
self.sdk_client.flavors.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
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):
|
||||
arglist = [
|
||||
@ -487,16 +545,14 @@ class TestFlavorList(TestFlavor):
|
||||
# Set expected values
|
||||
kwargs = {
|
||||
'is_public': None,
|
||||
'limit': None,
|
||||
'marker': None
|
||||
}
|
||||
|
||||
self.flavors_mock.list.assert_called_with(
|
||||
self.sdk_client.flavors.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
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):
|
||||
arglist = [
|
||||
@ -516,16 +572,14 @@ class TestFlavorList(TestFlavor):
|
||||
# Set expected values
|
||||
kwargs = {
|
||||
'is_public': False,
|
||||
'limit': None,
|
||||
'marker': None
|
||||
}
|
||||
|
||||
self.flavors_mock.list.assert_called_with(
|
||||
self.sdk_client.flavors.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
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):
|
||||
arglist = [
|
||||
@ -545,16 +599,14 @@ class TestFlavorList(TestFlavor):
|
||||
# Set expected values
|
||||
kwargs = {
|
||||
'is_public': True,
|
||||
'limit': None,
|
||||
'marker': None
|
||||
}
|
||||
|
||||
self.flavors_mock.list.assert_called_with(
|
||||
self.sdk_client.flavors.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertEqual(tuple(self.data), tuple(data))
|
||||
self.assertEqual(self.data, tuple(data))
|
||||
|
||||
def test_flavor_list_long(self):
|
||||
arglist = [
|
||||
@ -574,11 +626,9 @@ class TestFlavorList(TestFlavor):
|
||||
# Set expected values
|
||||
kwargs = {
|
||||
'is_public': True,
|
||||
'limit': None,
|
||||
'marker': None
|
||||
}
|
||||
|
||||
self.flavors_mock.list.assert_called_with(
|
||||
self.sdk_client.flavors.assert_called_with(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
@ -588,7 +638,7 @@ class TestFlavorList(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(
|
||||
attrs={'os-flavor-access:is_public': False})
|
||||
project = identity_fakes.FakeProject.create_one_project()
|
||||
@ -596,8 +646,7 @@ class TestFlavorSet(TestFlavor):
|
||||
def setUp(self):
|
||||
super(TestFlavorSet, self).setUp()
|
||||
|
||||
self.flavors_mock.find.return_value = self.flavor
|
||||
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
|
||||
self.sdk_client.find_flavor.return_value = self.flavor
|
||||
# Return a project
|
||||
self.projects_mock.get.return_value = self.project
|
||||
self.cmd = flavor.SetFlavor(self.app, None)
|
||||
@ -614,9 +663,14 @@ class TestFlavorSet(TestFlavor):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
|
||||
is_public=None)
|
||||
self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'})
|
||||
self.sdk_client.find_flavor.assert_called_with(
|
||||
parsed_args.flavor,
|
||||
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)
|
||||
|
||||
def test_flavor_set_no_property(self):
|
||||
@ -631,9 +685,13 @@ class TestFlavorSet(TestFlavor):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
|
||||
is_public=None)
|
||||
self.flavor.unset_keys.assert_called_with(['property'])
|
||||
self.sdk_client.find_flavor.assert_called_with(
|
||||
parsed_args.flavor,
|
||||
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)
|
||||
|
||||
def test_flavor_set_project(self):
|
||||
@ -649,13 +707,16 @@ class TestFlavorSet(TestFlavor):
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
|
||||
is_public=None)
|
||||
self.flavor_access_mock.add_tenant_access.assert_called_with(
|
||||
self.sdk_client.find_flavor.assert_called_with(
|
||||
parsed_args.flavor,
|
||||
get_extra_specs=True,
|
||||
ignore_missing=False
|
||||
)
|
||||
self.sdk_client.flavor_add_tenant_access.assert_called_with(
|
||||
self.flavor.id,
|
||||
self.project.id,
|
||||
)
|
||||
self.flavor.set_keys.assert_not_called()
|
||||
self.sdk_client.create_flavor_extra_specs.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_flavor_set_no_project(self):
|
||||
@ -681,8 +742,9 @@ class TestFlavorSet(TestFlavor):
|
||||
self.cmd, arglist, verifylist)
|
||||
|
||||
def test_flavor_set_with_unexist_flavor(self):
|
||||
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
|
||||
self.flavors_mock.find.side_effect = exceptions.NotFound(None)
|
||||
self.sdk_client.find_flavor.side_effect = [
|
||||
sdk_exceptions.ResourceNotFound()
|
||||
]
|
||||
|
||||
arglist = [
|
||||
'--project', self.project.id,
|
||||
@ -708,9 +770,12 @@ class TestFlavorSet(TestFlavor):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
|
||||
is_public=None)
|
||||
self.flavor_access_mock.add_tenant_access.assert_not_called()
|
||||
self.sdk_client.find_flavor.assert_called_with(
|
||||
parsed_args.flavor,
|
||||
get_extra_specs=True,
|
||||
ignore_missing=False
|
||||
)
|
||||
self.sdk_client.flavor_add_tenant_access.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_flavor_set_description_api_newer(self):
|
||||
@ -724,11 +789,11 @@ class TestFlavorSet(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.app.client_manager.compute.api_version = 2.55
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
with mock.patch.object(sdk_utils,
|
||||
'supports_microversion',
|
||||
return_value=True):
|
||||
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')
|
||||
self.assertIsNone(result)
|
||||
|
||||
@ -743,9 +808,9 @@ class TestFlavorSet(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.app.client_manager.compute.api_version = 2.54
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
with mock.patch.object(sdk_utils,
|
||||
'supports_microversion',
|
||||
return_value=False):
|
||||
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
|
||||
parsed_args)
|
||||
|
||||
@ -760,11 +825,12 @@ class TestFlavorSet(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.app.client_manager.compute.api_version = 2.55
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
|
||||
with mock.patch.object(sdk_utils,
|
||||
'supports_microversion',
|
||||
return_value=True):
|
||||
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')
|
||||
self.assertIsNone(result)
|
||||
|
||||
@ -779,16 +845,17 @@ class TestFlavorSet(TestFlavor):
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.app.client_manager.compute.api_version = 2.54
|
||||
with mock.patch.object(novaclient.api_versions,
|
||||
'APIVersion',
|
||||
return_value=2.55):
|
||||
|
||||
with mock.patch.object(sdk_utils,
|
||||
'supports_microversion',
|
||||
return_value=False):
|
||||
self.assertRaises(exceptions.CommandError, self.cmd.take_action,
|
||||
parsed_args)
|
||||
|
||||
|
||||
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 = compute_fakes.FakeFlavor.create_one_flavor()
|
||||
|
||||
@ -805,11 +872,11 @@ class TestFlavorShow(TestFlavor):
|
||||
'ram',
|
||||
'rxtx_factor',
|
||||
'swap',
|
||||
'vcpus',
|
||||
'vcpus'
|
||||
)
|
||||
|
||||
data = (
|
||||
flavor.disabled,
|
||||
flavor.is_disabled,
|
||||
flavor.ephemeral,
|
||||
None,
|
||||
flavor.description,
|
||||
@ -817,7 +884,7 @@ class TestFlavorShow(TestFlavor):
|
||||
flavor.id,
|
||||
flavor.name,
|
||||
flavor.is_public,
|
||||
format_columns.DictColumn(flavor.get_keys()),
|
||||
format_columns.DictColumn(flavor.extra_specs),
|
||||
flavor.ram,
|
||||
flavor.rxtx_factor,
|
||||
flavor.swap,
|
||||
@ -828,9 +895,8 @@ class TestFlavorShow(TestFlavor):
|
||||
super(TestFlavorShow, self).setUp()
|
||||
|
||||
# Return value of _find_resource()
|
||||
self.flavors_mock.find.return_value = self.flavor
|
||||
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
|
||||
self.flavor_access_mock.list.return_value = [self.flavor_access]
|
||||
self.sdk_client.find_flavor.return_value = self.flavor
|
||||
self.sdk_client.get_flavor_access.return_value = [self.flavor_access]
|
||||
self.cmd = flavor.ShowFlavor(self.app, None)
|
||||
|
||||
def test_show_no_options(self):
|
||||
@ -862,7 +928,7 @@ class TestFlavorShow(TestFlavor):
|
||||
'os-flavor-access:is_public': False,
|
||||
}
|
||||
)
|
||||
self.flavors_mock.find.return_value = private_flavor
|
||||
self.sdk_client.find_flavor.return_value = private_flavor
|
||||
|
||||
arglist = [
|
||||
private_flavor.name,
|
||||
@ -872,7 +938,7 @@ class TestFlavorShow(TestFlavor):
|
||||
]
|
||||
|
||||
data_with_project = (
|
||||
private_flavor.disabled,
|
||||
private_flavor.is_disabled,
|
||||
private_flavor.ephemeral,
|
||||
[self.flavor_access.tenant_id],
|
||||
private_flavor.description,
|
||||
@ -880,7 +946,7 @@ class TestFlavorShow(TestFlavor):
|
||||
private_flavor.id,
|
||||
private_flavor.name,
|
||||
private_flavor.is_public,
|
||||
format_columns.DictColumn(private_flavor.get_keys()),
|
||||
format_columns.DictColumn(private_flavor.extra_specs),
|
||||
private_flavor.ram,
|
||||
private_flavor.rxtx_factor,
|
||||
private_flavor.swap,
|
||||
@ -891,7 +957,7 @@ class TestFlavorShow(TestFlavor):
|
||||
|
||||
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)
|
||||
self.assertEqual(self.columns, columns)
|
||||
self.assertItemEqual(data_with_project, data)
|
||||
@ -899,7 +965,7 @@ class TestFlavorShow(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(
|
||||
attrs={'os-flavor-access:is_public': False})
|
||||
project = identity_fakes.FakeProject.create_one_project()
|
||||
@ -907,12 +973,13 @@ class TestFlavorUnset(TestFlavor):
|
||||
def setUp(self):
|
||||
super(TestFlavorUnset, self).setUp()
|
||||
|
||||
self.flavors_mock.find.return_value = self.flavor
|
||||
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
|
||||
self.sdk_client.find_flavor.return_value = self.flavor
|
||||
# Return a project
|
||||
self.projects_mock.get.return_value = self.project
|
||||
self.cmd = flavor.UnsetFlavor(self.app, None)
|
||||
|
||||
self.mock_shortcut = self.sdk_client.delete_flavor_extra_specs_property
|
||||
|
||||
def test_flavor_unset_property(self):
|
||||
arglist = [
|
||||
'--property', 'property',
|
||||
@ -925,12 +992,49 @@ class TestFlavorUnset(TestFlavor):
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
|
||||
is_public=None)
|
||||
self.flavor.unset_keys.assert_called_with(['property'])
|
||||
self.flavor_access_mock.remove_tenant_access.assert_not_called()
|
||||
self.sdk_client.find_flavor.assert_called_with(
|
||||
parsed_args.flavor,
|
||||
get_extra_specs=True,
|
||||
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)
|
||||
|
||||
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):
|
||||
arglist = [
|
||||
'--project', self.project.id,
|
||||
@ -945,13 +1049,14 @@ class TestFlavorUnset(TestFlavor):
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertIsNone(result)
|
||||
|
||||
self.flavors_mock.find.assert_called_with(name=parsed_args.flavor,
|
||||
is_public=None)
|
||||
self.flavor_access_mock.remove_tenant_access.assert_called_with(
|
||||
self.sdk_client.find_flavor.assert_called_with(
|
||||
parsed_args.flavor, get_extra_specs=True,
|
||||
ignore_missing=False)
|
||||
self.sdk_client.flavor_remove_tenant_access.assert_called_with(
|
||||
self.flavor.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)
|
||||
|
||||
def test_flavor_unset_no_project(self):
|
||||
@ -977,8 +1082,9 @@ class TestFlavorUnset(TestFlavor):
|
||||
self.cmd, arglist, verifylist)
|
||||
|
||||
def test_flavor_unset_with_unexist_flavor(self):
|
||||
self.flavors_mock.get.side_effect = exceptions.NotFound(None)
|
||||
self.flavors_mock.find.side_effect = exceptions.NotFound(None)
|
||||
self.sdk_client.find_flavor.side_effect = [
|
||||
sdk_exceptions.ResourceNotFound
|
||||
]
|
||||
|
||||
arglist = [
|
||||
'--project', self.project.id,
|
||||
@ -1004,4 +1110,4 @@ class TestFlavorUnset(TestFlavor):
|
||||
result = self.cmd.take_action(parsed_args)
|
||||
self.assertIsNone(result)
|
||||
|
||||
self.flavor_access_mock.remove_tenant_access.assert_not_called()
|
||||
self.sdk_client.flavor_remove_tenant_access.assert_not_called()
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Switch compute.flavor operations from direct API calls (novaclient) to
|
||||
OpenStackSDK.
|
@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
|
||||
cliff>=3.4.0 # Apache-2.0
|
||||
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
|
||||
oslo.i18n>=3.15.3 # Apache-2.0
|
||||
python-keystoneclient>=3.22.0 # Apache-2.0
|
||||
|
Loading…
Reference in New Issue
Block a user