diff options
Diffstat (limited to 'openstackclient')
80 files changed, 5569 insertions, 1707 deletions
diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 8092abd0..67c79230 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -17,9 +17,9 @@ import io import logging import os import sys +import urllib from osc_lib import utils -from six.moves import urllib from openstackclient.api import api diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 11de986b..643cb4e4 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -161,6 +161,13 @@ class BaseQuota(object): raise return quota._info + def _network_quota_to_dict(self, network_quota): + if type(network_quota) is not dict: + dict_quota = network_quota.to_dict() + else: + dict_quota = network_quota + return {k: v for k, v in dict_quota.items() if v is not None} + def get_network_quota(self, parsed_args): quota_class = ( parsed_args.quota_class if 'quota_class' in parsed_args else False) @@ -174,13 +181,11 @@ class BaseQuota(object): client = self.app.client_manager.network if default: network_quota = client.get_quota_default(project) - if type(network_quota) is not dict: - network_quota = network_quota.to_dict() + network_quota = self._network_quota_to_dict(network_quota) else: network_quota = client.get_quota(project, details=detail) - if type(network_quota) is not dict: - network_quota = network_quota.to_dict() + network_quota = self._network_quota_to_dict(network_quota) if detail: # NOTE(slaweq): Neutron returns values with key "used" but # Nova for example returns same data with key "in_use" diff --git a/openstackclient/common/sdk_utils.py b/openstackclient/common/sdk_utils.py index 9f085617..af9c74f9 100644 --- a/openstackclient/common/sdk_utils.py +++ b/openstackclient/common/sdk_utils.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six - def get_osc_show_columns_for_sdk_resource( sdk_resource, @@ -44,7 +42,7 @@ def get_osc_show_columns_for_sdk_resource( for col_name in invisible_columns: if col_name in display_columns: display_columns.remove(col_name) - for sdk_attr, osc_attr in six.iteritems(osc_column_map): + for sdk_attr, osc_attr in osc_column_map.items(): if sdk_attr in display_columns: attr_map[osc_attr] = sdk_attr display_columns.remove(sdk_attr) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 3feb99ec..15fb0f9c 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -28,7 +28,12 @@ LOG = logging.getLogger(__name__) class CreateAgent(command.ShowOne): - _description = _("Create compute agent") + """Create compute agent. + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) @@ -80,7 +85,12 @@ class CreateAgent(command.ShowOne): class DeleteAgent(command.Command): - _description = _("Delete compute agent(s)") + """Delete compute agent(s). + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) @@ -111,7 +121,12 @@ class DeleteAgent(command.Command): class ListAgent(command.Lister): - _description = _("List compute agents") + """List compute agents. + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) @@ -141,7 +156,12 @@ class ListAgent(command.Lister): class SetAgent(command.Command): - _description = _("Set compute agent properties") + """Set compute agent properties. + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 599659a3..8b70f426 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -18,6 +18,7 @@ import logging +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 @@ -30,6 +31,25 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +_aggregate_formatters = { + 'Hosts': format_columns.ListColumn, + 'Metadata': format_columns.DictColumn, + 'hosts': format_columns.ListColumn, + 'metadata': format_columns.DictColumn, +} + + +def _get_aggregate_columns(item): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = { + 'metadata': 'properties', + } + hidden_columns = ['links', 'location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class AddAggregateHost(command.ShowOne): _description = _("Add host to aggregate") @@ -48,26 +68,18 @@ class AddAggregateHost(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 - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) - data = compute_client.aggregates.add_host(aggregate, parsed_args.host) - - info = {} - info.update(data._info) - - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - info.update( - { - 'hosts': format_columns.ListColumn(info.pop('hosts')), - 'properties': format_columns.DictColumn(info.pop('metadata')), - }, - ) - return zip(*sorted(info.items())) + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) + + aggregate = compute_client.add_host_to_aggregate( + aggregate.id, parsed_args.host) + + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class CreateAggregate(command.ShowOne): @@ -95,36 +107,25 @@ class CreateAggregate(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 - info = {} - data = compute_client.aggregates.create( - parsed_args.name, - parsed_args.zone, - ) - info.update(data._info) + attrs = {'name': parsed_args.name} + + if parsed_args.zone: + attrs['availability_zone'] = parsed_args.zone + + aggregate = compute_client.create_aggregate(**attrs) if parsed_args.property: - info.update(compute_client.aggregates.set_metadata( - data, + aggregate = compute_client.set_aggregate_metadata( + aggregate.id, parsed_args.property, - )._info) - - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - hosts = None - properties = None - if 'hosts' in info.keys(): - hosts = format_columns.ListColumn(info.pop('hosts')) - if 'metadata' in info.keys(): - properties = format_columns.DictColumn(info.pop('metadata')) - info.update( - { - 'hosts': hosts, - 'properties': properties, - }, - ) - return zip(*sorted(info.items())) + ) + + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class DeleteAggregate(command.Command): @@ -141,13 +142,14 @@ class DeleteAggregate(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 a in parsed_args.aggregate: try: - data = utils.find_resource( - compute_client.aggregates, a) - compute_client.aggregates.delete(data.id) + aggregate = compute_client.find_aggregate( + a, ignore_missing=False) + compute_client.delete_aggregate( + aggregate.id, ignore_missing=False) except Exception as e: result += 1 LOG.error(_("Failed to delete aggregate with name or " @@ -175,15 +177,15 @@ class ListAggregate(command.Lister): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - data = compute_client.aggregates.list() + aggregates = list(compute_client.aggregates()) if parsed_args.long: # Remove availability_zone from metadata because Nova doesn't - for d in data: - if 'availability_zone' in d.metadata: - d.metadata.pop('availability_zone') + for aggregate in aggregates: + if 'availability_zone' in aggregate.metadata: + aggregate.metadata.pop('availability_zone') # This is the easiest way to change column headers column_headers = ( "ID", @@ -204,14 +206,11 @@ class ListAggregate(command.Lister): "Availability Zone", ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={ - 'Hosts': format_columns.ListColumn, - 'Metadata': format_columns.DictColumn, - }, - ) for s in data)) + data = ( + utils.get_item_properties( + s, columns, formatters=_aggregate_formatters + ) for s in aggregates) + return (column_headers, data) class RemoveAggregateHost(command.ShowOne): @@ -232,29 +231,18 @@ class RemoveAggregateHost(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 - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) - data = compute_client.aggregates.remove_host( - aggregate, - parsed_args.host, - ) + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) - info = {} - info.update(data._info) + aggregate = compute_client.remove_host_from_aggregate( + aggregate.id, parsed_args.host) - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - info.update( - { - 'hosts': format_columns.ListColumn(info.pop('hosts')), - 'properties': format_columns.DictColumn(info.pop('metadata')), - }, - ) - return zip(*sorted(info.items())) + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class SetAggregate(command.Command): @@ -296,11 +284,9 @@ class SetAggregate(command.Command): def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) + compute_client = self.app.client_manager.sdk_connection.compute + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) kwargs = {} if parsed_args.name: @@ -308,18 +294,12 @@ class SetAggregate(command.Command): if parsed_args.zone: kwargs['availability_zone'] = parsed_args.zone if kwargs: - compute_client.aggregates.update( - aggregate, - kwargs - ) + compute_client.update_aggregate(aggregate.id, **kwargs) set_property = {} if parsed_args.no_property: - # NOTE(RuiChen): "availability_zone" is removed from response of - # aggregate show and create commands, don't see it - # anywhere, so pop it, avoid the unexpected server - # exception(can't unset the availability zone from - # aggregate metadata in nova). + # NOTE(RuiChen): "availability_zone" can not be unset from + # properties. It is already excluded from show and create output. set_property.update({key: None for key in aggregate.metadata.keys() if key != 'availability_zone'}) @@ -327,8 +307,8 @@ class SetAggregate(command.Command): set_property.update(parsed_args.property) if set_property: - compute_client.aggregates.set_metadata( - aggregate, + compute_client.set_aggregate_metadata( + aggregate.id, set_property ) @@ -347,31 +327,18 @@ class ShowAggregate(command.ShowOne): def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) + compute_client = self.app.client_manager.sdk_connection.compute + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) + # Remove availability_zone from metadata because Nova doesn't - if 'availability_zone' in data.metadata: - data.metadata.pop('availability_zone') - - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - data._info.update( - { - 'hosts': format_columns.ListColumn( - data._info.pop('hosts') - ), - 'properties': format_columns.DictColumn( - data._info.pop('metadata') - ), - }, - ) + if 'availability_zone' in aggregate.metadata: + aggregate.metadata.pop('availability_zone') - info = {} - info.update(data._info) - return zip(*sorted(info.items())) + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class UnsetAggregate(command.Command): @@ -394,14 +361,56 @@ class UnsetAggregate(command.Command): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate) + compute_client = self.app.client_manager.sdk_connection.compute + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) unset_property = {} if parsed_args.property: unset_property.update({key: None for key in parsed_args.property}) if unset_property: - compute_client.aggregates.set_metadata(aggregate, - unset_property) + compute_client.set_aggregate_metadata( + aggregate, unset_property) + + +class CacheImageForAggregate(command.Command): + _description = _("Request image caching for aggregate") + # NOTE(gtema): According to stephenfin and dansmith there is no and will + # not be anything to return. + + def get_parser(self, prog_name): + parser = super(CacheImageForAggregate, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='<aggregate>', + help=_("Aggregate (name or ID)") + ) + parser.add_argument( + 'image', + metavar='<image>', + nargs='+', + help=_("Image ID to request caching for aggregate (name or ID). " + "May be specified multiple times.") + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.sdk_connection.compute + + if not sdk_utils.supports_microversion(compute_client, '2.81'): + msg = _( + 'This operation requires server support for ' + 'API microversion 2.81' + ) + raise exceptions.CommandError(msg) + + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) + + images = [] + for img in parsed_args.image: + image = self.app.client_manager.sdk_connection.image.find_image( + img, ignore_missing=False) + images.append(image.id) + + compute_client.aggregate_precache_images(aggregate.id, images) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 110b21b8..0ab5c8a2 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -22,6 +22,15 @@ from osc_lib import utils from openstackclient.i18n import _ +def _get_console_columns(item): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = {} + hidden_columns = ['id', 'links', 'location', 'name'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class ShowConsoleLog(command.Command): _description = _("Show server's console output") @@ -44,19 +53,18 @@ class ShowConsoleLog(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 - server = utils.find_resource( - compute_client.servers, - parsed_args.server, + server = compute_client.find_server( + name_or_id=parsed_args.server, + ignore_missing=False ) - length = parsed_args.lines - if length: - # NOTE(dtroyer): get_console_output() appears to shortchange the - # output by one line - length += 1 - data = server.get_console_output(length=length) + output = compute_client.get_server_console_output( + server.id, length=parsed_args.lines) + data = None + if output: + data = output.get('output', None) if data and data[-1] != '\n': data += '\n' @@ -120,21 +128,15 @@ class ShowConsoleURL(command.ShowOne): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - server = utils.find_resource( - compute_client.servers, + compute_client = self.app.client_manager.sdk_connection.compute + server = compute_client.find_server( parsed_args.server, - ) + ignore_missing=False) + + data = compute_client.create_console(server.id, + console_type=parsed_args.url_type) + + display_columns, columns = _get_console_columns(data) + data = utils.get_dict_properties(data, columns) - data = server.get_console_url(parsed_args.url_type) - if not data: - return ({}, {}) - - info = {} - # NOTE(Rui Chen): Return 'remote_console' in compute microversion API - # 2.6 and later, return 'console' in compute - # microversion API from 2.0 to 2.5, do compatibility - # handle for different microversion API. - console_data = data.get('remote_console', data.get('console')) - info.update(console_data) - return zip(*sorted(info.items())) + return (display_columns, data) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 42649db5..8477e8ef 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -17,7 +17,9 @@ 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 from osc_lib import exceptions @@ -30,22 +32,25 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) -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 +_formatters = { + 'extra_specs': format_columns.DictColumn, + 'properties': format_columns.DictColumn +} + + +def _get_flavor_columns(item): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = { + 'extra_specs': 'properties', + 'ephemeral': 'OS-FLV-EXT-DATA:ephemeral', + 'is_disabled': 'OS-FLV-DISABLED:disabled', + 'is_public': 'os-flavor-access:is_public' + + } + hidden_columns = ['links', 'location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) class CreateFlavor(command.ShowOne): @@ -61,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", @@ -144,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: @@ -178,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 " @@ -186,15 +193,16 @@ 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.pop("links") - flavor_info['properties'] = utils.format_dict(flavor.get_keys()) + display_columns, columns = _get_flavor_columns(flavor) + data = utils.get_dict_properties(flavor, columns, + formatters=_formatters) - return zip(*sorted(flavor_info.items())) + return (display_columns, data) class DeleteFlavor(command.Command): @@ -211,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 " @@ -275,41 +283,67 @@ class ListFlavor(command.Lister): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + 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 + + 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 += ( + "swap", + "rxtx_factor", + "extra_specs", + ) + + column_headers = ( "ID", "Name", "RAM", "Disk", "Ephemeral", "VCPUs", - "Is Public", + "Is Public" ) - - # 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) - if parsed_args.long: - columns = columns + ( + 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( - s, columns, formatters={'Properties': utils.format_dict}, + s, columns, formatters=_formatters, ) for s in data)) @@ -355,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 @@ -388,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) @@ -398,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=parsed_args.flavor, - description=parsed_args.description) - class ShowFlavor(command.ShowOne): _description = _("Display flavor details") @@ -419,34 +464,34 @@ 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) - projects = [utils.get_field(access, 'tenant_id') - for access in flavor_access] - # TODO(Huanxuan Ao): This format case can be removed after - # patch https://review.opendev.org/#/c/330223/ merged. - access_projects = utils.format_list(projects) + 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.pop("links", None) + # 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 - flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) + display_columns, columns = _get_flavor_columns(flavor) + data = utils.get_dict_properties( + flavor, columns, formatters=_formatters) - return zip(*sorted(flavor.items())) + return (display_columns, data) class UnsetFlavor(command.Command): @@ -477,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) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 2b365ceb..19e30bff 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -20,16 +20,31 @@ import logging import os import sys +from openstack import utils as sdk_utils from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +def _get_keypair_columns(item, hide_pub_key=False, hide_priv_key=False): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = {} + hidden_columns = ['links', 'location'] + if hide_pub_key: + hidden_columns.append('public_key') + if hide_priv_key: + hidden_columns.append('private_key') + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class CreateKeypair(command.ShowOne): _description = _("Create new public or private key for server ssh access") @@ -53,10 +68,33 @@ class CreateKeypair(command.ShowOne): help=_("Filename for private key to save. If not used, " "print private key in console.") ) + parser.add_argument( + '--type', + metavar='<type>', + choices=['ssh', 'x509'], + help=_( + "Keypair type. Can be ssh or x509. " + "(Supported by API versions '2.2' - '2.latest')" + ), + ) + parser.add_argument( + '--user', + metavar='<user>', + help=_( + 'The owner of the keypair. (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) 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 + + kwargs = { + 'name': parsed_args.name + } public_key = parsed_args.public_key if public_key: @@ -70,17 +108,40 @@ class CreateKeypair(command.ShowOne): "exception": e} ) - keypair = compute_client.keypairs.create( - parsed_args.name, - public_key=public_key, - ) + kwargs['public_key'] = public_key + + if parsed_args.type: + if not sdk_utils.supports_microversion(compute_client, '2.2'): + msg = _( + '--os-compute-api-version 2.2 or greater is required to ' + 'support the --type option' + ) + raise exceptions.CommandError(msg) + + kwargs['key_type'] = parsed_args.type + + if parsed_args.user: + if not sdk_utils.supports_microversion(compute_client, '2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + + keypair = compute_client.create_keypair(**kwargs) private_key = parsed_args.private_key # Save private key into specified file if private_key: try: with io.open( - os.path.expanduser(parsed_args.private_key), 'w+' + os.path.expanduser(parsed_args.private_key), 'w+' ) as p: p.write(keypair.private_key) except IOError as e: @@ -93,14 +154,12 @@ class CreateKeypair(command.ShowOne): # NOTE(dtroyer): how do we want to handle the display of the private # key when it needs to be communicated back to the user # For now, duplicate nova keypair-add command output - info = {} if public_key or private_key: - info.update(keypair._info) - if 'public_key' in info: - del info['public_key'] - if 'private_key' in info: - del info['private_key'] - return zip(*sorted(info.items())) + display_columns, columns = _get_keypair_columns( + keypair, hide_pub_key=True, hide_priv_key=True) + data = utils.get_item_properties(keypair, columns) + + return (display_columns, data) else: sys.stdout.write(keypair.private_key) return ({}, {}) @@ -117,16 +176,42 @@ class DeleteKeypair(command.Command): nargs='+', help=_("Name of key(s) to delete (name only)") ) + parser.add_argument( + '--user', + metavar='<user>', + help=_( + 'The owner of the keypair. (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) 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 + + kwargs = {} result = 0 + + if parsed_args.user: + if not sdk_utils.supports_microversion(compute_client, '2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + for n in parsed_args.name: try: - data = utils.find_resource( - compute_client.keypairs, n) - compute_client.keypairs.delete(data.name) + compute_client.delete_keypair( + n, **kwargs, ignore_missing=False) except Exception as e: result += 1 LOG.error(_("Failed to delete key with name " @@ -142,18 +227,85 @@ class DeleteKeypair(command.Command): class ListKeypair(command.Lister): _description = _("List key fingerprints") + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + user_group = parser.add_mutually_exclusive_group() + user_group.add_argument( + '--user', + metavar='<user>', + help=_( + 'Show keypairs for another user (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) + user_group.add_argument( + '--project', + metavar='<project>', + help=_( + 'Show keypairs for all users associated with project ' + '(admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_project_domain_option_to_parser(parser) + 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: + if not sdk_utils.supports_microversion(compute_client, '2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) + + # NOTE(stephenfin): This is done client side because nova doesn't + # currently support doing so server-side. If this is slow, we can + # think about spinning up a threadpool or similar. + project = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + users = identity_client.users.list(tenant_id=project) + + data = [] + for user in users: + data.extend(compute_client.keypairs(user_id=user.id)) + elif parsed_args.user: + if not sdk_utils.supports_microversion(compute_client, '2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + user = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ) + + data = compute_client.keypairs(user_id=user.id) + else: + data = compute_client.keypairs() + columns = ( "Name", "Fingerprint" ) - data = compute_client.keypairs.list() - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data)) + if sdk_utils.supports_microversion(compute_client, '2.2'): + columns += ("Type", ) + + return ( + columns, + (utils.get_item_properties(s, columns) for s in data), + ) class ShowKeypair(command.ShowOne): @@ -172,20 +324,45 @@ class ShowKeypair(command.ShowOne): default=False, help=_("Show only bare public key paired with the generated key") ) + parser.add_argument( + '--user', + metavar='<user>', + help=_( + 'The owner of the keypair. (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - keypair = utils.find_resource(compute_client.keypairs, - parsed_args.name) + compute_client = self.app.client_manager.sdk_connection.compute + identity_client = self.app.client_manager.identity + + kwargs = {} + + if parsed_args.user: + if not sdk_utils.supports_microversion(compute_client, '2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + + keypair = compute_client.find_keypair( + parsed_args.name, **kwargs, ignore_missing=False) - info = {} - info.update(keypair._info) if not parsed_args.public_key: - del info['public_key'] - return zip(*sorted(info.items())) + display_columns, columns = _get_keypair_columns( + keypair, hide_pub_key=True) + data = utils.get_item_properties(keypair, columns) + return (display_columns, data) else: - # NOTE(dtroyer): a way to get the public key in a similar form - # as the private key in the create command sys.stdout.write(keypair.public_key) return ({}, {}) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966..24c09ed1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,15 +21,15 @@ import io import logging import os +import iso8601 from novaclient import api_versions from novaclient.v2 import servers from openstack import exceptions as sdk_exceptions +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import timeutils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -38,6 +38,8 @@ from openstackclient.network import common as network_common LOG = logging.getLogger(__name__) +IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)' + def _format_servers_list_networks(networks): """Return a formatted string of a server's networks @@ -94,7 +96,7 @@ def _get_ip_address(addresses, address_type, ip_address_family): for network in addresses: for addy in addresses[network]: # Case where it is list of strings - if isinstance(addy, six.string_types): + if isinstance(addy, str): if new_address_type == 'fixed': return addresses[network][0] else: @@ -147,6 +149,12 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): info['image'] = "%s (%s)" % (image.name, image_id) except Exception: info['image'] = image_id + else: + # NOTE(melwitt): An server booted from a volume will have no image + # associated with it. We fill in the image with "N/A (booted from + # volume)" to help users who want to be able to grep for + # boot-from-volume servers when using the CLI. + info['image'] = IMAGE_STRING_FOR_BFV # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) @@ -166,14 +174,14 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): if 'os-extended-volumes:volumes_attached' in info: info.update( { - 'volumes_attached': utils.format_list_of_dicts( + 'volumes_attached': format_columns.ListDictColumn( info.pop('os-extended-volumes:volumes_attached')) } ) if 'security_groups' in info: info.update( { - 'security_groups': utils.format_list_of_dicts( + 'security_groups': format_columns.ListDictColumn( info.pop('security_groups')) } ) @@ -182,9 +190,14 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): info['addresses'] = _format_servers_list_networks(server.networks) # Map 'metadata' field to 'properties' - info.update( - {'properties': utils.format_dict(info.pop('metadata'))} - ) + if not info['metadata']: + info.update( + {'properties': utils.format_dict(info.pop('metadata'))} + ) + else: + info.update( + {'properties': format_columns.DictColumn(info.pop('metadata'))} + ) # Migrate tenant_id to project_id naming if 'tenant_id' in info: @@ -223,6 +236,14 @@ class AddFixedIP(command.Command): metavar="<ip-address>", help=_("Requested fixed IP address"), ) + parser.add_argument( + '--tag', + metavar='<tag>', + help=_( + 'Tag for the attached interface. ' + '(supported by --os-compute-api-version 2.52 or above)' + ) + ) return parser def take_action(self, parsed_args): @@ -233,11 +254,23 @@ class AddFixedIP(command.Command): network = compute_client.api.network_find(parsed_args.network) - server.interface_attach( - port_id=None, - net_id=network['id'], - fixed_ip=parsed_args.fixed_ip_address, - ) + kwargs = { + 'port_id': None, + 'net_id': network['id'], + 'fixed_ip': parsed_args.fixed_ip_address, + } + + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion('2.49'): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + kwargs['tag'] = parsed_args.tag + + server.interface_attach(**kwargs) class AddFloatingIP(network_common.NetworkAndComputeCommand): @@ -279,6 +312,10 @@ class AddFloatingIP(network_common.NetworkAndComputeCommand): parsed_args.server, ) ports = list(client.ports(device_id=server.id)) + if not ports: + msg = _('No attached ports found to associate floating IP with') + raise exceptions.CommandError(msg) + # If the fixed IP address was specified, we need to find the # corresponding port. if parsed_args.fixed_ip_address: @@ -341,6 +378,14 @@ class AddPort(command.Command): metavar="<port>", help=_("Port to add to the server (name or ID)"), ) + parser.add_argument( + '--tag', + metavar='<tag>', + help=_( + "Tag for the attached interface. " + "(Supported by API versions '2.49' - '2.latest')" + ) + ) return parser def take_action(self, parsed_args): @@ -356,7 +401,22 @@ class AddPort(command.Command): else: port_id = parsed_args.port - server.interface_attach(port_id=port_id, net_id=None, fixed_ip=None) + kwargs = { + 'port_id': port_id, + 'net_id': None, + 'fixed_ip': None, + } + + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion("2.49"): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + kwargs['tag'] = parsed_args.tag + + server.interface_attach(**kwargs) class AddNetwork(command.Command): @@ -374,6 +434,14 @@ class AddNetwork(command.Command): metavar="<network>", help=_("Network to add to the server (name or ID)"), ) + parser.add_argument( + '--tag', + metavar='<tag>', + help=_( + 'Tag for the attached interface. ' + '(supported by --os-compute-api-version 2.49 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -389,7 +457,23 @@ class AddNetwork(command.Command): else: net_id = parsed_args.network - server.interface_attach(port_id=None, net_id=net_id, fixed_ip=None) + kwargs = { + 'port_id': None, + 'net_id': net_id, + 'fixed_ip': None, + } + + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion('2.49'): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + kwargs['tag'] = parsed_args.tag + + server.interface_attach(**kwargs) class AddServerSecurityGroup(command.Command): @@ -446,20 +530,32 @@ class AddServerVolume(command.Command): metavar='<device>', help=_('Server internal device name for volume'), ) + parser.add_argument( + '--tag', + metavar='<tag>', + help=_( + "Tag for the attached volume. " + "(Supported by API versions '2.49' - '2.latest')" + ), + ) termination_group = parser.add_mutually_exclusive_group() termination_group.add_argument( '--enable-delete-on-termination', action='store_true', - help=_("Specify if the attached volume should be deleted when " - "the server is destroyed. (Supported with " - "``--os-compute-api-version`` 2.79 or greater.)"), + help=_( + "Specify if the attached volume should be deleted when the " + "server is destroyed. " + "(Supported by API versions '2.79' - '2.latest')" + ), ) termination_group.add_argument( '--disable-delete-on-termination', action='store_true', - help=_("Specify if the attached volume should not be deleted " - "when the server is destroyed. (Supported with " - "``--os-compute-api-version`` 2.79 or greater.)"), + help=_( + "Specify if the attached volume should not be deleted when " + "the server is destroyed. " + "(Supported by API versions '2.79' - '2.latest')" + ), ) return parser @@ -476,28 +572,38 @@ class AddServerVolume(command.Command): parsed_args.volume, ) - support_set_delete_on_termination = (compute_client.api_version >= - api_versions.APIVersion('2.79')) - - if not support_set_delete_on_termination: - if parsed_args.enable_delete_on_termination: - msg = _('--os-compute-api-version 2.79 or greater ' - 'is required to support the ' - '--enable-delete-on-termination option.') - raise exceptions.CommandError(msg) - if parsed_args.disable_delete_on_termination: - msg = _('--os-compute-api-version 2.79 or greater ' - 'is required to support the ' - '--disable-delete-on-termination option.') - raise exceptions.CommandError(msg) - kwargs = { "device": parsed_args.device } + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion('2.49'): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + kwargs['tag'] = parsed_args.tag + if parsed_args.enable_delete_on_termination: + if compute_client.api_version < api_versions.APIVersion('2.79'): + msg = _( + '--os-compute-api-version 2.79 or greater is required to ' + 'support the --enable-delete-on-termination option.' + ) + raise exceptions.CommandError(msg) + kwargs['delete_on_termination'] = True + if parsed_args.disable_delete_on_termination: + if compute_client.api_version < api_versions.APIVersion('2.79'): + msg = _( + '--os-compute-api-version 2.79 or greater is required to ' + 'support the --disable-delete-on-termination option.' + ) + raise exceptions.CommandError(msg) + kwargs['delete_on_termination'] = False compute_client.volumes.create_server_volume( @@ -534,13 +640,20 @@ class CreateServer(command.ShowOne): disk_group.add_argument( '--volume', metavar='<volume>', - help=_('Create server using this volume as the boot disk (name ' - 'or ID).\n' - 'This option automatically creates a block device mapping ' - 'with a boot index of 0. On many hypervisors (libvirt/kvm ' - 'for example) this will be device vda. Do not create a ' - 'duplicate mapping using --block-device-mapping for this ' - 'volume.'), + help=_( + 'Create server using this volume as the boot disk (name or ID)' + '\n' + 'This option automatically creates a block device mapping ' + 'with a boot index of 0. On many hypervisors (libvirt/kvm ' + 'for example) this will be device vda. Do not create a ' + 'duplicate mapping using --block-device-mapping for this ' + 'volume.' + ), + ) + parser.add_argument( + '--password', + metavar='<password>', + help=_("Set the password to this server"), ) parser.add_argument( '--flavor', @@ -553,28 +666,34 @@ class CreateServer(command.ShowOne): metavar='<security-group>', action='append', default=[], - help=_('Security group to assign to this server (name or ID) ' - '(repeat option to set multiple groups)'), + help=_( + 'Security group to assign to this server (name or ID) ' + '(repeat option to set multiple groups)' + ), ) parser.add_argument( '--key-name', metavar='<key-name>', - help=_('Keypair to inject into this server (optional extension)'), + help=_('Keypair to inject into this server'), ) parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, - help=_('Set a property on this server ' - '(repeat option to set multiple values)'), + help=_( + 'Set a property on this server ' + '(repeat option to set multiple values)' + ), ) parser.add_argument( '--file', metavar='<dest-filename=source-filename>', action='append', default=[], - help=_('File to inject into image before boot ' - '(repeat option to set multiple files)'), + help=_( + 'File to inject into image before boot ' + '(repeat option to set multiple files)' + ), ) parser.add_argument( '--user-data', @@ -584,40 +703,53 @@ class CreateServer(command.ShowOne): parser.add_argument( '--description', metavar='<description>', - help=_('Set description for the server (supported by ' - '--os-compute-api-version 2.19 or above)'), + help=_( + 'Set description for the server ' + '(supported by --os-compute-api-version 2.19 or above)' + ), ) parser.add_argument( '--availability-zone', metavar='<zone-name>', - help=_('Select an availability zone for the server'), + help=_('Select an availability zone for the server. ' + 'Host and node are optional parameters. ' + 'Availability zone in the format ' + '<zone-name>:<host-name>:<node-name>, ' + '<zone-name>::<node-name>, <zone-name>:<host-name> ' + 'or <zone-name>'), ) parser.add_argument( '--host', metavar='<host>', - help=_('Requested host to create servers. Admin only ' - 'by default. (supported by --os-compute-api-version 2.74 ' - 'or above)'), + help=_( + 'Requested host to create servers. ' + '(admin only) ' + '(supported by --os-compute-api-version 2.74 or above)' + ), ) parser.add_argument( '--hypervisor-hostname', metavar='<hypervisor-hostname>', - help=_('Requested hypervisor hostname to create servers. Admin ' - 'only by default. (supported by --os-compute-api-version ' - '2.74 or above)'), + help=_( + 'Requested hypervisor hostname to create servers. ' + '(admin only) ' + '(supported by --os-compute-api-version 2.74 or above)' + ), ) parser.add_argument( '--boot-from-volume', metavar='<volume-size>', type=int, - help=_('When used in conjunction with the ``--image`` or ' - '``--image-property`` option, this option automatically ' - 'creates a block device mapping with a boot index of 0 ' - 'and tells the compute service to create a volume of the ' - 'given size (in GB) from the specified image and use it ' - 'as the root disk of the server. The root volume will not ' - 'be deleted when the server is deleted. This option is ' - 'mutually exclusive with the ``--volume`` option.') + help=_( + 'When used in conjunction with the ``--image`` or ' + '``--image-property`` option, this option automatically ' + 'creates a block device mapping with a boot index of 0 ' + 'and tells the compute service to create a volume of the ' + 'given size (in GB) from the specified image and use it ' + 'as the root disk of the server. The root volume will not ' + 'be deleted when the server is deleted. This option is ' + 'mutually exclusive with the ``--volume`` option.' + ) ) parser.add_argument( '--block-device-mapping', @@ -627,37 +759,40 @@ class CreateServer(command.ShowOne): # NOTE(RuiChen): Add '\n' at the end of line to put each item in # the separated line, avoid the help message looks # messy, see _SmartHelpFormatter in cliff. - help=_('Create a block device on the server.\n' - 'Block device mapping in the format\n' - '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n' - '<dev-name>: block device name, like: vdb, xvdc ' - '(required)\n' - '<id>: Name or ID of the volume, volume snapshot or image ' - '(required)\n' - '<type>: volume, snapshot or image; default: volume ' - '(optional)\n' - '<size(GB)>: volume size if create from image or snapshot ' - '(optional)\n' - '<delete-on-terminate>: true or false; default: false ' - '(optional)\n' - '(optional extension)'), + help=_( + 'Create a block device on the server.\n' + 'Block device mapping in the format\n' + '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n' + '<dev-name>: block device name, like: vdb, xvdc ' + '(required)\n' + '<id>: Name or ID of the volume, volume snapshot or image ' + '(required)\n' + '<type>: volume, snapshot or image; default: volume ' + '(optional)\n' + '<size(GB)>: volume size if create from image or snapshot ' + '(optional)\n' + '<delete-on-terminate>: true or false; default: false ' + '(optional)\n' + ), ) parser.add_argument( '--nic', metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," "port-id=port-uuid,auto,none>", action='append', - help=_("Create a NIC on the server. " - "Specify option multiple times to create multiple NICs. " - "Either net-id or port-id must be provided, but not both. " - "net-id: attach NIC to network with this UUID, " - "port-id: attach NIC to port with this UUID, " - "v4-fixed-ip: IPv4 fixed address for NIC (optional), " - "v6-fixed-ip: IPv6 fixed address for NIC (optional), " - "none: (v2.37+) no network is attached, " - "auto: (v2.37+) the compute service will automatically " - "allocate a network. Specifying a --nic of auto or none " - "cannot be used with any other --nic value."), + help=_( + "Create a NIC on the server. " + "Specify option multiple times to create multiple NICs. " + "Either net-id or port-id must be provided, but not both. " + "net-id: attach NIC to network with this UUID, " + "port-id: attach NIC to port with this UUID, " + "v4-fixed-ip: IPv4 fixed address for NIC (optional), " + "v6-fixed-ip: IPv6 fixed address for NIC (optional), " + "none: (v2.37+) no network is attached, " + "auto: (v2.37+) the compute service will automatically " + "allocate a network. Specifying a --nic of auto or none " + "cannot be used with any other --nic value." + ), ) parser.add_argument( '--network', @@ -665,13 +800,15 @@ class CreateServer(command.ShowOne): action='append', dest='nic', type=_prefix_checked_value('net-id='), - help=_("Create a NIC on the server and connect it to network. " - "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic net-id=<network>' " - "parameter that provides simple syntax for the standard " - "use case of connecting a new server to a given network. " - "For more advanced use cases, refer to the '--nic' " - "parameter."), + help=_( + "Create a NIC on the server and connect it to network. " + "Specify option multiple times to create multiple NICs. " + "This is a wrapper for the '--nic net-id=<network>' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given network. " + "For more advanced use cases, refer to the '--nic' " + "parameter." + ), ) parser.add_argument( '--port', @@ -679,12 +816,14 @@ class CreateServer(command.ShowOne): action='append', dest='nic', type=_prefix_checked_value('port-id='), - help=_("Create a NIC on the server and connect it to port. " - "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic port-id=<port>' " - "parameter that provides simple syntax for the standard " - "use case of connecting a new server to a given port. For " - "more advanced use cases, refer to the '--nic' parameter."), + help=_( + "Create a NIC on the server and connect it to port. " + "Specify option multiple times to create multiple NICs. " + "This is a wrapper for the '--nic port-id=<port>' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given port. For " + "more advanced use cases, refer to the '--nic' parameter." + ), ) parser.add_argument( '--hint', @@ -693,12 +832,30 @@ class CreateServer(command.ShowOne): default={}, help=_('Hints for the scheduler (optional extension)'), ) - parser.add_argument( + config_drive_group = parser.add_mutually_exclusive_group() + config_drive_group.add_argument( + '--use-config-drive', + action='store_true', + dest='config_drive', + help=_("Enable config drive."), + ) + config_drive_group.add_argument( + '--no-config-drive', + action='store_false', + dest='config_drive', + help=_("Disable config drive."), + ) + # TODO(stephenfin): Drop support in the next major version bump after + # Victoria + config_drive_group.add_argument( '--config-drive', metavar='<config-drive-volume>|True', default=False, - help=_('Use specified volume as the config drive, ' - 'or \'True\' to use an ephemeral drive'), + help=_( + "**Deprecated** Use specified volume as the config drive, " + "or 'True' to use an ephemeral drive. Replaced by " + "'--use-config-drive'." + ), ) parser.add_argument( '--min', @@ -719,6 +876,18 @@ class CreateServer(command.ShowOne): action='store_true', help=_('Wait for build to complete'), ) + parser.add_argument( + '--tag', + metavar='<tag>', + action='append', + default=[], + dest='tags', + help=_( + 'Tags for the server. ' + 'Specify multiple times to add multiple tags. ' + '(supported by --os-compute-api-version 2.52 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -741,57 +910,75 @@ class CreateServer(command.ShowOne): if not image and parsed_args.image_property: def emit_duplicated_warning(img, image_property): img_uuid_list = [str(image.id) for image in img] - LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n' - 'Using image: %(chosen_one)s') % - {'img_uuid_list': img_uuid_list, - 'chosen_one': img_uuid_list[0]}) + LOG.warning( + 'Multiple matching images: %(img_uuid_list)s\n' + 'Using image: %(chosen_one)s', + { + 'img_uuid_list': img_uuid_list, + 'chosen_one': img_uuid_list[0], + }) def _match_image(image_api, wanted_properties): image_list = image_api.images() images_matched = [] for img in image_list: img_dict = {} + # exclude any unhashable entries - for key, value in img.items(): + img_dict_items = list(img.items()) + if img.properties: + img_dict_items.extend(list(img.properties.items())) + for key, value in img_dict_items: try: set([key, value]) except TypeError: + if key != 'properties': + LOG.debug( + 'Skipped the \'%s\' attribute. ' + 'That cannot be compared. ' + '(image: %s, value: %s)', + key, img.id, value, + ) pass else: img_dict[key] = value - if all(k in img_dict and img_dict[k] == v - for k, v in wanted_properties.items()): + + if all( + k in img_dict and img_dict[k] == v + for k, v in wanted_properties.items() + ): images_matched.append(img) - else: - return [] + return images_matched images = _match_image(image_client, parsed_args.image_property) if len(images) > 1: - emit_duplicated_warning(images, - parsed_args.image_property) + emit_duplicated_warning(images, parsed_args.image_property) if images: image = images[0] else: - raise exceptions.CommandError(_("No images match the " - "property expected by " - "--image-property")) + msg = _( + 'No images match the property expected by ' + '--image-property' + ) + raise exceptions.CommandError(msg) # Lookup parsed_args.volume volume = None if parsed_args.volume: # --volume and --boot-from-volume are mutually exclusive. if parsed_args.boot_from_volume: - raise exceptions.CommandError( - _('--volume is not allowed with --boot-from-volume')) + msg = _('--volume is not allowed with --boot-from-volume') + raise exceptions.CommandError(msg) + volume = utils.find_resource( volume_client.volumes, parsed_args.volume, ).id # Lookup parsed_args.flavor - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + flavor = utils.find_resource( + compute_client.flavors, parsed_args.flavor) files = {} for f in parsed_args.file: @@ -801,16 +988,17 @@ class CreateServer(command.ShowOne): except IOError as e: msg = _("Can't open '%(source)s': %(exception)s") raise exceptions.CommandError( - msg % {"source": src, - "exception": e} + msg % {'source': src, 'exception': e} ) if parsed_args.min > parsed_args.max: msg = _("min instances should be <= max instances") raise exceptions.CommandError(msg) + if parsed_args.min < 1: msg = _("min instances should be > 0") raise exceptions.CommandError(msg) + if parsed_args.max < 1: msg = _("max instances should be > 0") raise exceptions.CommandError(msg) @@ -822,8 +1010,7 @@ class CreateServer(command.ShowOne): except IOError as e: msg = _("Can't open '%(data)s': %(exception)s") raise exceptions.CommandError( - msg % {"data": parsed_args.user_data, - "exception": e} + msg % {'data': parsed_args.user_data, 'exception': e} ) if parsed_args.description: @@ -834,11 +1021,12 @@ class CreateServer(command.ShowOne): block_device_mapping_v2 = [] if volume: - block_device_mapping_v2 = [{'uuid': volume, - 'boot_index': '0', - 'source_type': 'volume', - 'destination_type': 'volume' - }] + block_device_mapping_v2 = [{ + 'uuid': volume, + 'boot_index': '0', + 'source_type': 'volume', + 'destination_type': 'volume' + }] elif parsed_args.boot_from_volume: # Tell nova to create a root volume from the image provided. block_device_mapping_v2 = [{ @@ -854,18 +1042,21 @@ class CreateServer(command.ShowOne): boot_args = [parsed_args.server_name, image, flavor] # Handle block device by device name order, like: vdb -> vdc -> vdd - for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)): + for dev_name in sorted(parsed_args.block_device_mapping): dev_map = parsed_args.block_device_mapping[dev_name] dev_map = dev_map.split(':') if dev_map[0]: mapping = {'device_name': dev_name} + # 1. decide source and destination type if (len(dev_map) > 1 and dev_map[1] in ('volume', 'snapshot', 'image')): mapping['source_type'] = dev_map[1] else: mapping['source_type'] = 'volume' + mapping['destination_type'] = 'volume' + # 2. check target exist, update target uuid according by # source type if mapping['source_type'] == 'volume': @@ -891,14 +1082,18 @@ class CreateServer(command.ShowOne): image_id = image_client.find_image(dev_map[0], ignore_missing=False).id mapping['uuid'] = image_id + # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] + if len(dev_map) > 3 and dev_map[3]: mapping['delete_on_termination'] = dev_map[3] else: - msg = _("Volume, volume snapshot or image (name or ID) must " - "be specified if --block-device-mapping is specified") + msg = _( + 'Volume, volume snapshot or image (name or ID) must ' + 'be specified if --block-device-mapping is specified' + ) raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) @@ -912,22 +1107,32 @@ class CreateServer(command.ShowOne): auto_or_none = True nics.append(nic_str) else: - nic_info = {"net-id": "", "v4-fixed-ip": "", - "v6-fixed-ip": "", "port-id": ""} + nic_info = { + 'net-id': '', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + 'port-id': '', + } for kv_str in nic_str.split(","): k, sep, v = kv_str.partition("=") if k in nic_info and v: nic_info[k] = v else: - msg = (_("Invalid nic argument '%s'. Nic arguments " - "must be of the form --nic <net-id=net-uuid" - ",v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," - "port-id=port-uuid>.")) + msg = _( + "Invalid nic argument '%s'. Nic arguments " + "must be of the form --nic <net-id=net-uuid" + ",v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," + "port-id=port-uuid>." + ) raise exceptions.CommandError(msg % k) + if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): - msg = _("either network or port should be specified " - "but not both") + msg = _( + 'Either network or port should be specified ' + 'but not both' + ) raise exceptions.CommandError(msg) + if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network if nic_info["net-id"]: @@ -944,17 +1149,22 @@ class CreateServer(command.ShowOne): nic_info["net-id"] )['id'] if nic_info["port-id"]: - msg = _("can't create server with port specified " - "since network endpoint not enabled") + msg = _( + "Can't create server with port specified " + "since network endpoint not enabled" + ) raise exceptions.CommandError(msg) + nics.append(nic_info) if nics: if auto_or_none: if len(nics) > 1: - msg = _('Specifying a --nic of auto or none cannot ' - 'be used with any other --nic, --network ' - 'or --port value.') + msg = _( + 'Specifying a --nic of auto or none cannot ' + 'be used with any other --nic, --network ' + 'or --port value.' + ) raise exceptions.CommandError(msg) nics = nics[0] else: @@ -991,16 +1201,19 @@ class CreateServer(command.ShowOne): else: hints[key] = values - # What does a non-boolean value for config-drive do? - # --config-drive argument is either a volume id or - # 'True' (or '1') to use an ephemeral volume - if str(parsed_args.config_drive).lower() in ("true", "1"): - config_drive = True - elif str(parsed_args.config_drive).lower() in ("false", "0", - "", "none"): - config_drive = None + if isinstance(parsed_args.config_drive, bool): + # NOTE(stephenfin): The API doesn't accept False as a value :'( + config_drive = parsed_args.config_drive or None else: - config_drive = parsed_args.config_drive + # TODO(stephenfin): Remove when we drop support for + # '--config-drive' + if str(parsed_args.config_drive).lower() in ("true", "1"): + config_drive = True + elif str(parsed_args.config_drive).lower() in ("false", "0", + "", "none"): + config_drive = None + else: + config_drive = parsed_args.config_drive boot_kwargs = dict( meta=parsed_args.property, @@ -1012,6 +1225,7 @@ class CreateServer(command.ShowOne): userdata=userdata, key_name=parsed_args.key_name, availability_zone=parsed_args.availability_zone, + admin_pass=parsed_args.password, block_device_mapping_v2=block_device_mapping_v2, nics=nics, scheduler_hints=hints, @@ -1020,18 +1234,34 @@ class CreateServer(command.ShowOne): if parsed_args.description: boot_kwargs['description'] = parsed_args.description + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.52'): + msg = _( + '--os-compute-api-version 2.52 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + boot_kwargs['tags'] = parsed_args.tags + if parsed_args.host: if compute_client.api_version < api_versions.APIVersion("2.74"): - msg = _("Specifying --host is not supported for " - "--os-compute-api-version less than 2.74") + msg = _( + '--os-compute-api-version 2.74 or greater is required to ' + 'support the --host option' + ) raise exceptions.CommandError(msg) + boot_kwargs['host'] = parsed_args.host if parsed_args.hypervisor_hostname: if compute_client.api_version < api_versions.APIVersion("2.74"): - msg = _("Specifying --hypervisor-hostname is not supported " - "for --os-compute-api-version less than 2.74") + msg = _( + '--os-compute-api-version 2.74 or greater is required to ' + 'support the --hypervisor-hostname option' + ) raise exceptions.CommandError(msg) + boot_kwargs['hypervisor_hostname'] = ( parsed_args.hypervisor_hostname) @@ -1057,8 +1287,7 @@ class CreateServer(command.ShowOne): ): self.app.stdout.write('\n') else: - LOG.error(_('Error creating server: %s'), - parsed_args.server_name) + LOG.error('Error creating server: %s', parsed_args.server_name) self.app.stdout.write(_('Error creating server\n')) raise SystemExit @@ -1142,6 +1371,13 @@ class ListServer(command.Lister): def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) parser.add_argument( + '--availability-zone', + metavar='<availability-zone>', + help=_('Only return instances that match the availability zone. ' + 'Note that this option will be ignored for non-admin users ' + 'when using ``--os-compute-api-version`` prior to 2.83.'), + ) + parser.add_argument( '--reservation-id', metavar='<reservation-id>', help=_('Only return instances that match the reservation'), @@ -1287,6 +1523,30 @@ class ListServer(command.Lister): help=_('Only display unlocked servers. ' 'Requires ``--os-compute-api-version`` 2.73 or greater.'), ) + parser.add_argument( + '--tags', + metavar='<tag>', + action='append', + default=[], + dest='tags', + help=_( + 'Only list servers with the specified tag. ' + 'Specify multiple times to filter on multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) + parser.add_argument( + '--not-tags', + metavar='<tag>', + action='append', + default=[], + dest='not_tags', + help=_( + 'Only list servers without the specified tag. ' + 'Specify multiple times to filter on multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1326,6 +1586,7 @@ class ListServer(command.Lister): ignore_missing=False).id search_opts = { + 'availability_zone': parsed_args.availability_zone, 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, 'ip6': parsed_args.ip6, @@ -1342,6 +1603,27 @@ class ListServer(command.Lister): 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } + + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + search_opts['tags'] = parsed_args.tags + + if parsed_args.not_tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --not-tag option' + ) + raise exceptions.CommandError(msg) + + search_opts['not-tags'] = parsed_args.not_tags + support_locked = (compute_client.api_version >= api_versions.APIVersion('2.73')) if not support_locked and (parsed_args.locked or parsed_args.unlocked): @@ -1362,8 +1644,8 @@ class ListServer(command.Lister): raise exceptions.CommandError(msg) try: - timeutils.parse_isotime(search_opts['changes-before']) - except ValueError: + iso8601.parse_date(search_opts['changes-before']) + except (TypeError, iso8601.ParseError): raise exceptions.CommandError( _('Invalid changes-before value: %s') % search_opts['changes-before'] @@ -1371,8 +1653,8 @@ class ListServer(command.Lister): if search_opts['changes-since']: try: - timeutils.parse_isotime(search_opts['changes-since']) - except ValueError: + iso8601.parse_date(search_opts['changes-since']) + except (TypeError, iso8601.ParseError): raise exceptions.CommandError( _('Invalid changes-since value: %s') % search_opts['changes-since'] @@ -1446,6 +1728,27 @@ class ListServer(command.Lister): marker_id = None + # support for additional columns + if parsed_args.columns: + # convert tuple to list to edit them + column_headers = list(column_headers) + columns = list(columns) + + for c in parsed_args.columns: + if c in ('Project ID', 'project_id'): + columns.append('tenant_id') + column_headers.append('Project ID') + if c in ('User ID', 'user_id'): + columns.append('user_id') + column_headers.append('User ID') + if c in ('Created At', 'created_at'): + columns.append('created_at') + column_headers.append('Created At') + + # convert back to tuple + column_headers = tuple(column_headers) + columns = tuple(columns) + if parsed_args.marker: # Check if both "--marker" and "--deleted" are used. # In that scenario a lookup is not needed as the marker @@ -1520,8 +1823,12 @@ class ListServer(command.Lister): s.image_name = image.name s.image_id = s.image['id'] else: - s.image_name = '' - s.image_id = '' + # NOTE(melwitt): An server booted from a volume will have no + # image associated with it. We fill in the Image Name and ID + # with "N/A (booted from volume)" to help users who want to be + # able to grep for boot-from-volume servers when using the CLI. + s.image_name = IMAGE_STRING_FOR_BFV + s.image_id = IMAGE_STRING_FOR_BFV if 'id' in s.flavor: flavor = flavors.get(s.flavor['id']) if flavor: @@ -1768,6 +2075,284 @@ revert to release the new server and restart the old one.""") raise SystemExit +class ListMigration(command.Lister): + _description = _("""List server migrations""") + + def get_parser(self, prog_name): + parser = super(ListMigration, self).get_parser(prog_name) + parser.add_argument( + '--server', + metavar='<server>', + help=_( + 'Filter migrations by server (name or ID)' + ) + ) + parser.add_argument( + '--host', + metavar='<host>', + help=_( + 'Filter migrations by source or destination host' + ), + ) + parser.add_argument( + '--status', + metavar='<status>', + help=_('Filter migrations by status') + ) + parser.add_argument( + '--type', + metavar='<type>', + choices=[ + 'evacuation', 'live-migration', 'cold-migration', 'resize', + ], + help=_('Filter migrations by type'), + ) + parser.add_argument( + '--marker', + metavar='<marker>', + help=_( + "The last migration of the previous page; displays list " + "of migrations after 'marker'. Note that the marker is " + "the migration UUID. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--limit', + metavar='<limit>', + type=int, + help=_( + "Maximum number of migrations to display. Note that there " + "is a configurable max limit on the server, and the limit " + "that is used will be the minimum of what is requested " + "here and what is configured in the server. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--changes-since', + dest='changes_since', + metavar='<changes-since>', + help=_( + "List only migrations changed later or equal to a certain " + "point of time. The provided time should be an ISO 8061 " + "formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.59 or above)" + ), + ) + parser.add_argument( + '--changes-before', + dest='changes_before', + metavar='<changes-before>', + help=_( + "List only migrations changed earlier or equal to a " + "certain point of time. The provided time should be an ISO " + "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.66 or above)" + ), + ) + parser.add_argument( + '--project', + metavar='<project>', + dest='project_id', + help=_( + "Filter migrations by project (ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), + ) + parser.add_argument( + '--user', + metavar='<user>', + dest='user_id', + help=_( + "Filter migrations by user (ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), + ) + return parser + + def print_migrations(self, parsed_args, compute_client, migrations): + columns = [ + 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Created At', 'Updated At', + ] + + # Insert migrations UUID after ID + if compute_client.api_version >= api_versions.APIVersion("2.59"): + columns.insert(0, "UUID") + + if compute_client.api_version >= api_versions.APIVersion("2.23"): + columns.insert(0, "Id") + columns.insert(len(columns) - 2, "Type") + + if compute_client.api_version >= api_versions.APIVersion("2.80"): + if parsed_args.project_id: + columns.insert(len(columns) - 2, "Project") + if parsed_args.user_id: + columns.insert(len(columns) - 2, "User") + + return ( + columns, + (utils.get_item_properties(mig, columns) for mig in migrations), + ) + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + search_opts = { + 'host': parsed_args.host, + 'status': parsed_args.status, + } + + if parsed_args.server: + search_opts['instance_uuid'] = utils.find_resource( + compute_client.servers, + parsed_args.server, + ).id + + if parsed_args.type: + migration_type = parsed_args.type + # we're using an alias because the default value is confusing + if migration_type == 'cold-migration': + migration_type = 'migration' + search_opts['migration_type'] = migration_type + + if parsed_args.marker: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --marker option' + ) + raise exceptions.CommandError(msg) + search_opts['marker'] = parsed_args.marker + + if parsed_args.limit: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --limit option' + ) + raise exceptions.CommandError(msg) + search_opts['limit'] = parsed_args.limit + + if parsed_args.changes_since: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --changes-since option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_since'] = parsed_args.changes_since + + if parsed_args.changes_before: + if compute_client.api_version < api_versions.APIVersion('2.66'): + msg = _( + '--os-compute-api-version 2.66 or greater is required to ' + 'support the --changes-before option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_before'] = parsed_args.changes_before + + if parsed_args.project_id: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) + search_opts['project_id'] = parsed_args.project_id + + if parsed_args.user_id: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + search_opts['user_id'] = parsed_args.user_id + + migrations = compute_client.migrations.list(**search_opts) + + return self.print_migrations(parsed_args, compute_client, migrations) + + +class AbortMigration(command.Command): + """Cancel an ongoing live migration. + + This command requires ``--os-compute-api-version`` 2.24 or greater. + """ + + def get_parser(self, prog_name): + parser = super(AbortMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='<migration>', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration abort command' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + compute_client.server_migrations.live_migration_abort( + server.id, parsed_args.migration) + + +class ForceCompleteMigration(command.Command): + """Force an ongoing live migration to complete. + + This command requires ``--os-compute-api-version`` 2.22 or greater. + """ + + def get_parser(self, prog_name): + parser = super(ForceCompleteMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='<migration>', + help=_('Migration (ID)') + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.22'): + msg = _( + '--os-compute-api-version 2.22 or greater is required to ' + 'support the server migration force complete command' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + compute_client.server_migrations.live_migrate_force_complete( + server.id, parsed_args.migration) + + class PauseServer(command.Command): _description = _("Pause server(s)") @@ -1863,47 +2448,57 @@ class RebuildServer(command.ShowOne): parser.add_argument( '--image', metavar='<image>', - help=_('Recreate server from the specified image (name or ID).' - ' Defaults to the currently used one.'), + help=_( + 'Recreate server from the specified image (name or ID).' + 'Defaults to the currently used one.' + ), ) parser.add_argument( '--password', metavar='<password>', - help=_("Set the password on the rebuilt instance"), + help=_('Set a password on the rebuilt server'), ) parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, - help=_('Set a property on the rebuilt instance ' - '(repeat option to set multiple values)'), + help=_( + 'Set a new property on the rebuilt server ' + '(repeat option to set multiple values)' + ), ) parser.add_argument( '--description', metavar='<description>', - help=_('New description for the server (supported by ' - '--os-compute-api-version 2.19 or above'), - ) - parser.add_argument( - '--wait', - action='store_true', - help=_('Wait for rebuild to complete'), + help=_( + 'Set a new description on the rebuilt server ' + '(supported by --os-compute-api-version 2.19 or above)' + ), ) key_group = parser.add_mutually_exclusive_group() key_group.add_argument( '--key-name', metavar='<key-name>', - help=_("Set the key name of key pair on the rebuilt instance." - " Cannot be specified with the '--key-unset' option." - " (Supported by API versions '2.54' - '2.latest')"), + help=_( + 'Set the key name of key pair on the rebuilt server. ' + 'Cannot be specified with the --key-unset option. ' + '(supported by --os-compute-api-version 2.54 or above)' + ), ) key_group.add_argument( '--key-unset', action='store_true', default=False, - help=_("Unset the key name of key pair on the rebuilt instance." - " Cannot be specified with the '--key-name' option." - " (Supported by API versions '2.54' - '2.latest')"), + help=_( + 'Unset the key name of key pair on the rebuilt server. ' + 'Cannot be specified with the --key-name option. ' + '(supported by --os-compute-api-version 2.54 or above)' + ), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for rebuild to complete'), ) return parser @@ -1929,24 +2524,38 @@ class RebuildServer(command.ShowOne): image = image_client.get_image(image_id) kwargs = {} + if parsed_args.property: kwargs['meta'] = parsed_args.property + if parsed_args.description: if server.api_version < api_versions.APIVersion("2.19"): - msg = _("Description is not supported for " - "--os-compute-api-version less than 2.19") + msg = _( + '--os-compute-api-version 2.19 or greater is required to ' + 'support the --description option' + ) raise exceptions.CommandError(msg) + kwargs['description'] = parsed_args.description - if parsed_args.key_name or parsed_args.key_unset: + if parsed_args.key_name: if compute_client.api_version < api_versions.APIVersion('2.54'): - msg = _('--os-compute-api-version 2.54 or later is required') + msg = _( + '--os-compute-api-version 2.54 or greater is required to ' + 'support the --key-name option' + ) raise exceptions.CommandError(msg) - if parsed_args.key_unset: - kwargs['key_name'] = None - if parsed_args.key_name: kwargs['key_name'] = parsed_args.key_name + elif parsed_args.key_unset: + if compute_client.api_version < api_versions.APIVersion('2.54'): + msg = _( + '--os-compute-api-version 2.54 or greater is required to ' + 'support the --no-key-name option' + ) + raise exceptions.CommandError(msg) + + kwargs['key_name'] = None server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: @@ -1957,13 +2566,124 @@ class RebuildServer(command.ShowOne): ): self.app.stdout.write(_('Complete\n')) else: - LOG.error(_('Error rebuilding server: %s'), - server.id) + LOG.error(_('Error rebuilding server: %s'), server.id) self.app.stdout.write(_('Error rebuilding server\n')) raise SystemExit - details = _prep_server_detail(compute_client, image_client, server, - refresh=False) + details = _prep_server_detail( + compute_client, image_client, server, refresh=False) + return zip(*sorted(details.items())) + + +class EvacuateServer(command.ShowOne): + _description = _("""Evacuate a server to a different host. + +This command is used to recreate a server after the host it was on has failed. +It can only be used if the compute service that manages the server is down. +This command should only be used by an admin after they have confirmed that the +instance is not running on the failed host. + +If the server instance was created with an ephemeral root disk on non-shared +storage the server will be rebuilt using the original glance image preserving +the ports and any attached data volumes. + +If the server uses boot for volume or has its root disk on shared storage the +root disk will be preserved and reused for the evacuated instance on the new +host.""") + + def get_parser(self, prog_name): + parser = super(EvacuateServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server (name or ID)'), + ) + + parser.add_argument( + '--wait', action='store_true', + help=_('Wait for evacuation to complete'), + ) + parser.add_argument( + '--host', metavar='<host>', default=None, + help=_( + 'Set the preferred host on which to rebuild the evacuated ' + 'server. The host will be validated by the scheduler. ' + '(supported by --os-compute-api-version 2.29 or above)' + ), + ) + shared_storage_group = parser.add_mutually_exclusive_group() + shared_storage_group.add_argument( + '--password', metavar='<password>', default=None, + help=_( + 'Set the password on the evacuated instance. This option is ' + 'mutually exclusive with the --shared-storage option' + ), + ) + shared_storage_group.add_argument( + '--shared-storage', action='store_true', dest='shared_storage', + help=_( + 'Indicate that the instance is on shared storage. ' + 'This will be auto-calculated with ' + '--os-compute-api-version 2.14 and greater and should not ' + 'be used with later microversions. This option is mutually ' + 'exclusive with the --password option' + ), + ) + return parser + + def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + + compute_client = self.app.client_manager.compute + image_client = self.app.client_manager.image + + if parsed_args.host: + if compute_client.api_version < api_versions.APIVersion('2.29'): + msg = _( + '--os-compute-api-version 2.29 or later is required ' + 'to specify a preferred host.' + ) + raise exceptions.CommandError(msg) + + if parsed_args.shared_storage: + if compute_client.api_version > api_versions.APIVersion('2.13'): + msg = _( + '--os-compute-api-version 2.13 or earlier is required ' + 'to specify shared-storage.' + ) + raise exceptions.CommandError(msg) + + kwargs = { + 'host': parsed_args.host, + 'password': parsed_args.password, + } + + if compute_client.api_version <= api_versions.APIVersion('2.13'): + kwargs['on_shared_storage'] = parsed_args.shared_storage + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + server = server.evacuate(**kwargs) + + if parsed_args.wait: + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + self.app.stdout.write(_('Complete\n')) + else: + LOG.error(_('Error evacuating server: %s'), server.id) + self.app.stdout.write(_('Error evacuating server\n')) + raise SystemExit + + details = _prep_server_detail( + compute_client, image_client, server, refresh=False) return zip(*sorted(details.items())) @@ -2433,6 +3153,18 @@ class SetServer(command.Command): help=_('New server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) + parser.add_argument( + '--tag', + metavar='<tag>', + action='append', + default=[], + dest='tags', + help=_( + 'Tag for the server. ' + 'Specify multiple times to add multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -2471,6 +3203,17 @@ class SetServer(command.Command): raise exceptions.CommandError(msg) server.update(description=parsed_args.description) + if parsed_args.tags: + if server.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + for tag in parsed_args.tags: + server.add_tag(tag=tag) + class ShelveServer(command.Command): _description = _("Shelve server(s)") @@ -2530,7 +3273,6 @@ class ShowServer(command.ShowOne): data = _prep_server_detail(compute_client, self.app.client_manager.image, server, refresh=False) - return zip(*sorted(data.items())) @@ -2813,7 +3555,7 @@ class UnrescueServer(command.Command): class UnsetServer(command.Command): - _description = _("Unset server properties") + _description = _("Unset server properties and tags") def get_parser(self, prog_name): parser = super(UnsetServer, self).get_parser(prog_name) @@ -2837,6 +3579,18 @@ class UnsetServer(command.Command): help=_('Unset server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) + parser.add_argument( + '--tag', + metavar='<tag>', + action='append', + default=[], + dest='tags', + help=_( + 'Tag to remove from the server. ' + 'Specify multiple times to remove multiple tags. ' + '(supported by --os-compute-api-version 2.26 or later' + ), + ) return parser def take_action(self, parsed_args): @@ -2862,6 +3616,17 @@ class UnsetServer(command.Command): description="", ) + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + for tag in parsed_args.tags: + compute_client.servers.delete_tag(server, tag=tag) + class UnshelveServer(command.Command): _description = _("Unshelve server(s)") diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index a5d43fc6..b1b821b2 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -15,10 +15,11 @@ """Compute v2 Server action implementations""" +import importlib + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import importutils from openstackclient.i18n import _ @@ -119,7 +120,7 @@ class CreateServerBackup(command.ShowOne): info['properties'] = utils.format_dict(info.get('properties', {})) else: # Get the right image module to format the output - image_module = importutils.import_module( + image_module = importlib.import_module( self.IMAGE_API_VERSIONS[ self.app.client_manager._api_version['image'] ] diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index c49a552f..a3363244 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -17,6 +17,7 @@ import logging +from novaclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -55,21 +56,40 @@ class CreateServerGroup(command.ShowOne): parser.add_argument( '--policy', metavar='<policy>', + choices=[ + 'affinity', + 'anti-affinity', + 'soft-affinity', + 'soft-anti-affinity', + ], default='affinity', - help=_("Add a policy to <name> " - "('affinity' or 'anti-affinity', " - "defaults to 'affinity'). Specify --os-compute-api-version " - "2.15 or higher for the 'soft-affinity' or " - "'soft-anti-affinity' policy.") + help=_( + "Add a policy to <name> " + "Specify --os-compute-api-version 2.15 or higher for the " + "'soft-affinity' or 'soft-anti-affinity' policy." + ) ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute info = {} + + if parsed_args.policy in ('soft-affinity', 'soft-anti-affinity'): + if compute_client.api_version < api_versions.APIVersion('2.15'): + msg = _( + '--os-compute-api-version 2.15 or greater is required to ' + 'support the %s policy' + ) + raise exceptions.CommandError(msg % parsed_args.policy) + + policy_arg = {'policies': [parsed_args.policy]} + if compute_client.api_version >= api_versions.APIVersion("2.64"): + policy_arg = {'policy': parsed_args.policy} + server_group = compute_client.server_groups.create( - name=parsed_args.name, - policies=[parsed_args.policy]) + name=parsed_args.name, **policy_arg) + info.update(server_group._info) columns = _get_columns(info) @@ -136,11 +156,15 @@ class ListServerGroup(command.Lister): compute_client = self.app.client_manager.compute data = compute_client.server_groups.list(parsed_args.all_projects) + policy_key = 'Policies' + if compute_client.api_version >= api_versions.APIVersion("2.64"): + policy_key = 'Policy' + if parsed_args.long: column_headers = columns = ( 'ID', 'Name', - 'Policies', + policy_key, 'Members', 'Project Id', 'User Id', @@ -149,7 +173,7 @@ class ListServerGroup(command.Lister): column_headers = columns = ( 'ID', 'Name', - 'Policies', + policy_key, ) return (column_headers, diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index fea87af8..c12bc2b3 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -15,12 +15,12 @@ """Compute v2 Server action implementations""" +import importlib import logging from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import importutils from openstackclient.i18n import _ @@ -99,7 +99,7 @@ class CreateServerImage(command.ShowOne): info['properties'] = utils.format_dict(info.get('properties', {})) else: # Get the right image module to format the output - image_module = importutils.import_module( + image_module = importlib.import_module( self.IMAGE_API_VERSIONS[ self.app.client_manager._api_version['image'] ] diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index e70d87d2..a75db4f8 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -207,7 +207,7 @@ def _find_identity_resource(identity_client_manager, name_or_id, name_or_id, **kwargs) if identity_resource is not None: return identity_resource - except exceptions.Forbidden: + except (exceptions.Forbidden, identity_exc.Forbidden): pass return resource_type(None, {'id': name_or_id, 'name': name_or_id}) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index ccedbf33..05d0e9ae 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -91,7 +91,7 @@ class ShowCatalog(command.ShowOne): for service in auth_ref.service_catalog.catalog: if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): - data = service + data = service.copy() data['endpoints'] = EndpointsColumn(data['endpoints']) if 'endpoints_links' in data: data.pop('endpoints_links') diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py index 65e78be1..ffda04f9 100644 --- a/openstackclient/identity/v3/access_rule.py +++ b/openstackclient/identity/v3/access_rule.py @@ -20,7 +20,6 @@ import logging from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -115,4 +114,4 @@ class ShowAccessRule(command.ShowOne): access_rule._info.pop('links', None) - return zip(*sorted(six.iteritems(access_rule._info))) + return zip(*sorted(access_rule._info.items())) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 2b2d9d11..7307cea0 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -143,10 +143,32 @@ class DeleteIdentityProvider(command.Command): class ListIdentityProvider(command.Lister): _description = _("List identity providers") + def get_parser(self, prog_name): + parser = super(ListIdentityProvider, self).get_parser(prog_name) + parser.add_argument( + '--id', + metavar='<id>', + help=_('The Identity Providers’ ID attribute'), + ) + parser.add_argument( + '--enabled', + dest='enabled', + action='store_true', + help=_('The Identity Providers that are enabled will be returned'), + ) + return parser + def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Domain ID', 'Description') identity_client = self.app.client_manager.identity - data = identity_client.federation.identity_providers.list() + + kwargs = {} + if parsed_args.id: + kwargs['id'] = parsed_args.id + if parsed_args.enabled: + kwargs['enabled'] = True + + data = identity_client.federation.identity_providers.list(**kwargs) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 980ebf11..a674564f 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -64,59 +64,61 @@ def _add_identity_and_resource_options_to_parser(parser): def _process_identity_and_resource_options(parsed_args, - identity_client_manager): + identity_client_manager, + validate_actor_existence=True): + + def _find_user(): + try: + return common.find_user( + identity_client_manager, + parsed_args.user, + parsed_args.user_domain + ).id + except exceptions.CommandError: + if not validate_actor_existence: + return parsed_args.user + raise + + def _find_group(): + try: + return common.find_group( + identity_client_manager, + parsed_args.group, + parsed_args.group_domain + ).id + except exceptions.CommandError: + if not validate_actor_existence: + return parsed_args.group + raise + kwargs = {} if parsed_args.user and parsed_args.system: - kwargs['user'] = common.find_user( - identity_client_manager, - parsed_args.user, - parsed_args.user_domain, - ).id + kwargs['user'] = _find_user() kwargs['system'] = parsed_args.system elif parsed_args.user and parsed_args.domain: - kwargs['user'] = common.find_user( - identity_client_manager, - parsed_args.user, - parsed_args.user_domain, - ).id + kwargs['user'] = _find_user() kwargs['domain'] = common.find_domain( identity_client_manager, parsed_args.domain, ).id elif parsed_args.user and parsed_args.project: - kwargs['user'] = common.find_user( - identity_client_manager, - parsed_args.user, - parsed_args.user_domain, - ).id + kwargs['user'] = _find_user() kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, parsed_args.project_domain, ).id elif parsed_args.group and parsed_args.system: - kwargs['group'] = common.find_group( - identity_client_manager, - parsed_args.group, - parsed_args.group_domain, - ).id + kwargs['group'] = _find_group() kwargs['system'] = parsed_args.system elif parsed_args.group and parsed_args.domain: - kwargs['group'] = common.find_group( - identity_client_manager, - parsed_args.group, - parsed_args.group_domain, - ).id + kwargs['group'] = _find_group() kwargs['domain'] = common.find_domain( identity_client_manager, parsed_args.domain, ).id elif parsed_args.group and parsed_args.project: - kwargs['group'] = common.find_group( - identity_client_manager, - parsed_args.group, - parsed_args.group_domain, - ).id + kwargs['group'] = _find_group() kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, @@ -340,7 +342,9 @@ class RemoveRole(command.Command): ) kwargs = _process_identity_and_resource_options( - parsed_args, self.app.client_manager.identity) + parsed_args, self.app.client_manager.identity, + validate_actor_existence=False + ) identity_client.roles.revoke(role.id, **kwargs) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index cf1d6817..64aa3fcd 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -478,7 +478,11 @@ class SaveImage(command.Command): image_client = self.app.client_manager.image image = image_client.find_image(parsed_args.image) - image_client.download_image(image.id, output=parsed_args.file) + output_file = parsed_args.file + if output_file is None: + output_file = getattr(sys.stdout, "buffer", sys.stdout) + + image_client.download_image(image.id, stream=True, output=output_file) class SetImage(command.Command): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 029f57a3..58d92f51 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -354,7 +354,7 @@ class CreateImage(command.ShowOne): # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line - kwargs = {} + kwargs = {'allow_duplicates': True} copy_attrs = ('name', 'id', 'container_format', 'disk_format', 'min_disk', 'min_ram', 'tags', 'visibility') @@ -803,7 +803,11 @@ class SaveImage(command.Command): image_client = self.app.client_manager.image image = image_client.find_image(parsed_args.image) - image_client.download_image(image.id, output=parsed_args.file) + output_file = parsed_args.file + if output_file is None: + output_file = getattr(sys.stdout, "buffer", sys.stdout) + + image_client.download_image(image.id, stream=True, output=output_file) class SetImage(command.Command): diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index e68628b3..47ffbe77 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -18,7 +18,6 @@ import logging import openstack.exceptions from osc_lib.command import command from osc_lib import exceptions -import six from openstackclient.i18n import _ @@ -54,8 +53,7 @@ def check_missing_extension_if_error(client_manager, attrs): raise -@six.add_metaclass(abc.ABCMeta) -class NetDetectionMixin(object): +class NetDetectionMixin(metaclass=abc.ABCMeta): """Convenience methods for nova-network vs. neutron decisions. A live environment detects which network type it is running and creates its @@ -166,8 +164,8 @@ class NetDetectionMixin(object): pass -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeCommand(NetDetectionMixin, command.Command): +class NetworkAndComputeCommand(NetDetectionMixin, command.Command, + metaclass=abc.ABCMeta): """Network and Compute Command Command class for commands that support implementation via @@ -178,8 +176,8 @@ class NetworkAndComputeCommand(NetDetectionMixin, command.Command): pass -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeDelete(NetworkAndComputeCommand): +class NetworkAndComputeDelete(NetworkAndComputeCommand, + metaclass=abc.ABCMeta): """Network and Compute Delete Delete class for commands that support implementation via @@ -222,8 +220,8 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand): raise exceptions.CommandError(msg) -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeLister(NetDetectionMixin, command.Lister): +class NetworkAndComputeLister(NetDetectionMixin, command.Lister, + metaclass=abc.ABCMeta): """Network and Compute Lister Lister class for commands that support implementation via @@ -234,8 +232,8 @@ class NetworkAndComputeLister(NetDetectionMixin, command.Lister): pass -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne): +class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne, + metaclass=abc.ABCMeta): """Network and Compute ShowOne ShowOne class for commands that support implementation via @@ -255,5 +253,5 @@ class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne): except openstack.exceptions.HttpException as exc: msg = _("Error while executing command: %s") % exc.message if exc.details: - msg += ", " + six.text_type(exc.details) + msg += ", " + str(exc.details) raise exceptions.CommandError(msg) diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 49ff9e1b..1cf0395f 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -46,6 +46,10 @@ def _get_attrs(client_manager, parsed_args): attrs['direction'] = 'egress' if parsed_args.remote_ip_prefix is not None: attrs['remote_ip_prefix'] = parsed_args.remote_ip_prefix + if parsed_args.source_ip_prefix is not None: + attrs['source_ip_prefix'] = parsed_args.source_ip_prefix + if parsed_args.destination_ip_prefix is not None: + attrs['destination_ip_prefix'] = parsed_args.destination_ip_prefix if parsed_args.meter is not None: attrs['metering_label_id'] = parsed_args.meter if parsed_args.project is not None: @@ -97,10 +101,22 @@ class CreateMeterRule(command.ShowOne): parser.add_argument( '--remote-ip-prefix', metavar='<remote-ip-prefix>', - required=True, + required=False, help=_('The remote IP prefix to associate with this rule'), ) parser.add_argument( + '--source-ip-prefix', + metavar='<remote-ip-prefix>', + required=False, + help=_('The source IP prefix to associate with this rule'), + ) + parser.add_argument( + '--destination-ip-prefix', + metavar='<remote-ip-prefix>', + required=False, + help=_('The destination IP prefix to associate with this rule'), + ) + parser.add_argument( 'meter', metavar='<meter>', help=_('Label to associate with this metering rule (name or ID)'), @@ -168,12 +184,16 @@ class ListMeterRule(command.Lister): 'excluded', 'direction', 'remote_ip_prefix', + 'source_ip_prefix', + 'destination_ip_prefix', ) column_headers = ( 'ID', 'Excluded', 'Direction', 'Remote IP Prefix', + 'Source IP Prefix', + 'Destination IP Prefix', ) data = client.metering_label_rules() return (column_headers, diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a21324ae..cb77759e 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -158,6 +158,16 @@ def _get_attrs(client_manager, parsed_args): parsed_args.disable_uplink_status_propagation): attrs['propagate_uplink_status'] = False + if ('numa_policy_required' in parsed_args and + parsed_args.numa_policy_required): + attrs['numa_affinity_policy'] = 'required' + elif ('numa_policy_preferred' in parsed_args and + parsed_args.numa_policy_preferred): + attrs['numa_affinity_policy'] = 'preferred' + elif ('numa_policy_legacy' in parsed_args and + parsed_args.numa_policy_legacy): + attrs['numa_affinity_policy'] = 'legacy' + return attrs @@ -265,6 +275,22 @@ def _add_updatable_args(parser): help=_("Set DNS name for this port " "(requires DNS integration extension)") ) + numa_affinity_policy_group = parser.add_mutually_exclusive_group() + numa_affinity_policy_group.add_argument( + '--numa-policy-required', + action='store_true', + help=_("NUMA affinity policy required to schedule this port") + ) + numa_affinity_policy_group.add_argument( + '--numa-policy-preferred', + action='store_true', + help=_("NUMA affinity policy preferred to schedule this port") + ) + numa_affinity_policy_group.add_argument( + '--numa-policy-legacy', + action='store_true', + help=_("NUMA affinity policy using legacy mode to schedule this port") + ) # TODO(abhiraut): Use the SDK resource mapped attribute names once the @@ -454,12 +480,23 @@ class CreatePort(command.ShowOne): if parsed_args.qos_policy: attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id + + set_tags_in_post = bool( + client.find_extension('tag-ports-during-bulk-creation')) + if set_tags_in_post: + if parsed_args.no_tag: + attrs['tags'] = [] + if parsed_args.tags: + attrs['tags'] = list(set(parsed_args.tags)) + with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_port(**attrs) - # tags cannot be set when created, so tags need to be set later. - _tag.update_tags_for_set(client, obj, parsed_args) + if not set_tags_in_post: + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -628,7 +665,7 @@ class ListPort(command.Lister): _tag.get_tag_filtering_args(parsed_args, filters) - data = network_client.ports(**filters) + data = network_client.ports(fields=columns, **filters) headers, attrs = utils.calculate_header_and_attrs( column_headers, columns, parsed_args) @@ -904,6 +941,11 @@ class UnsetPort(command.Command): action='store_true', help=_("Clear existing information of data plane status") ) + parser.add_argument( + '--numa-policy', + action='store_true', + help=_("Clear existing NUMA affinity policy") + ) _tag.add_tag_option_to_parser_for_unset(parser, _('port')) @@ -959,6 +1001,8 @@ class UnsetPort(command.Command): attrs['qos_policy_id'] = None if parsed_args.data_plane_status: attrs['data_plane_status'] = None + if parsed_args.numa_policy: + attrs['numa_affinity_policy'] = None if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 1fbd97ab..a7703933 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -474,7 +474,7 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): action='store_true', default=False, help=self.enhance_help_neutron( - _("List additional fields in output")) + _("**Deprecated** This argument is no longer needed")) ) return parser @@ -504,15 +504,19 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): 'Ethertype', 'IP Range', 'Port Range', + 'Direction', + 'Remote Security Group', ) - if parsed_args.long: - column_headers = column_headers + ('Direction',) - column_headers = column_headers + ('Remote Security Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) return column_headers def take_action_network(self, client, parsed_args): + if parsed_args.long: + self.log.warning(_( + "The --long option has been deprecated and is no longer needed" + )) + column_headers = self._get_column_headers(parsed_args) columns = ( 'id', @@ -520,10 +524,9 @@ class ListSecurityGroupRule(common.NetworkAndComputeLister): 'ether_type', 'remote_ip_prefix', 'port_range', + 'direction', + 'remote_group_id', ) - if parsed_args.long: - columns = columns + ('direction',) - columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. query = {} diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 755af24d..bc88e1f1 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -16,13 +16,11 @@ """Command-line interface to the OpenStack APIs""" -import locale import sys from osc_lib.api import auth from osc_lib.command import commandmanager from osc_lib import shell -import six import openstackclient from openstackclient.common import clientmanager @@ -143,12 +141,6 @@ class OpenStackShell(shell.OpenStackShell): def main(argv=None): if argv is None: argv = sys.argv[1:] - if six.PY2: - # Emulate Py3, decode argv into Unicode based on locale so that - # commands always see arguments as text instead of binary data - encoding = locale.getpreferredencoding() - if encoding: - argv = map(lambda arg: arg.decode(encoding), argv) return OpenStackShell().run(argv) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 08e9390e..3542a827 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -19,10 +19,6 @@ from tempest.lib import exceptions import testtools -COMMON_DIR = os.path.dirname(os.path.abspath(__file__)) -FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..')) -ROOT_DIR = os.path.normpath(os.path.join(FUNCTIONAL_DIR, '..')) -EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples') ADMIN_CLOUD = os.environ.get('OS_ADMIN_CLOUD', 'devstack-admin') diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 4c2fc0e3..9c057460 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -165,47 +165,3 @@ class QuotaTests(base.TestCase): # returned attributes self.assertTrue(cmd_output["key-pairs"] >= 0) self.assertTrue(cmd_output["snapshots"] >= 0) - - def test_quota_set_force(self): - """Test to set instance value by force """ - json_output = json.loads(self.openstack( - 'quota list -f json --detail --compute' - )) - in_use = limit = None - for j in json_output: - if j["Resource"] == "instances": - in_use = j["In Use"] - limit = j["Limit"] - - # Reduce count of in_use - in_use = in_use - 1 - # cannot have negative instances limit - if in_use < 0: - in_use = 0 - - # set the limit by force now - self.openstack( - 'quota set ' + self.PROJECT_NAME + - '--instances ' + str(in_use) + ' --force' - ) - cmd_output = json.loads(self.openstack( - 'quota show -f json ' + self.PROJECT_NAME - )) - self.assertIsNotNone(cmd_output) - self.assertEqual( - in_use, - cmd_output["instances"] - ) - - # Set instances limit to original limit now - self.openstack( - 'quota set ' + self.PROJECT_NAME + '--instances ' + str(limit) - ) - cmd_output = json.loads(self.openstack( - 'quota show -f json ' + self.PROJECT_NAME - )) - self.assertIsNotNone(cmd_output) - self.assertEqual( - limit, - cmd_output["instances"] - ) diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py deleted file mode 100644 index 25d8c868..00000000 --- a/openstackclient/tests/functional/compute/v2/test_agent.py +++ /dev/null @@ -1,196 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import hashlib -import json - -from openstackclient.tests.functional import base - - -class ComputeAgentTests(base.TestCase): - """Functional tests for compute agent.""" - - # Generate two different md5hash - MD5HASH1 = hashlib.md5() - MD5HASH1.update('agent_1'.encode('utf-8')) - MD5HASH1 = MD5HASH1.hexdigest() - MD5HASH2 = hashlib.md5() - MD5HASH2.update('agent_2'.encode('utf-8')) - MD5HASH2 = MD5HASH2.hexdigest() - - def test_compute_agent_delete(self): - """Test compute agent create, delete multiple""" - os1 = "os_1" - arch1 = "x86_64" - ver1 = "v1" - url1 = "http://localhost" - md5hash1 = self.MD5HASH1 - hyper1 = "kvm" - cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd1 - )) - agent_id1 = str(cmd_output["agent_id"]) - - os2 = "os_2" - arch2 = "x86" - ver2 = "v2" - url2 = "http://openstack" - md5hash2 = self.MD5HASH2 - hyper2 = "xen" - cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd2 - )) - agent_id2 = str(cmd_output["agent_id"]) - - # Test compute agent delete - del_output = self.openstack( - 'compute agent delete ' + - agent_id1 + ' ' + agent_id2 - ) - self.assertOutput('', del_output) - - def test_compute_agent_list(self): - """Test compute agent create and list""" - os1 = "os_1" - arch1 = "x86_64" - ver1 = "v1" - url1 = "http://localhost" - md5hash1 = self.MD5HASH1 - hyper1 = "kvm" - cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd1 - )) - agent_id1 = str(cmd_output["agent_id"]) - self.addCleanup(self.openstack, 'compute agent delete ' + agent_id1) - - os2 = "os_2" - arch2 = "x86" - ver2 = "v2" - url2 = "http://openstack" - md5hash2 = self.MD5HASH2 - hyper2 = "xen" - cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd2 - )) - agent_id2 = str(cmd_output["agent_id"]) - self.addCleanup(self.openstack, 'compute agent delete ' + agent_id2) - - # Test compute agent list - cmd_output = json.loads(self.openstack( - 'compute agent list -f json' - )) - - hypervisors = [x["Hypervisor"] for x in cmd_output] - self.assertIn(hyper1, hypervisors) - self.assertIn(hyper2, hypervisors) - - os = [x['OS'] for x in cmd_output] - self.assertIn(os1, os) - self.assertIn(os2, os) - - archs = [x['Architecture'] for x in cmd_output] - self.assertIn(arch1, archs) - self.assertIn(arch2, archs) - - versions = [x['Version'] for x in cmd_output] - self.assertIn(ver1, versions) - self.assertIn(ver2, versions) - - md5hashes = [x['Md5Hash'] for x in cmd_output] - self.assertIn(md5hash1, md5hashes) - self.assertIn(md5hash2, md5hashes) - - urls = [x['URL'] for x in cmd_output] - self.assertIn(url1, urls) - self.assertIn(url2, urls) - - # Test compute agent list --hypervisor - cmd_output = json.loads(self.openstack( - 'compute agent list -f json ' + - '--hypervisor kvm' - )) - - hypervisors = [x["Hypervisor"] for x in cmd_output] - self.assertIn(hyper1, hypervisors) - self.assertNotIn(hyper2, hypervisors) - - os = [x['OS'] for x in cmd_output] - self.assertIn(os1, os) - self.assertNotIn(os2, os) - - archs = [x['Architecture'] for x in cmd_output] - self.assertIn(arch1, archs) - self.assertNotIn(arch2, archs) - - versions = [x['Version'] for x in cmd_output] - self.assertIn(ver1, versions) - self.assertNotIn(ver2, versions) - - md5hashes = [x['Md5Hash'] for x in cmd_output] - self.assertIn(md5hash1, md5hashes) - self.assertNotIn(md5hash2, md5hashes) - - urls = [x['URL'] for x in cmd_output] - self.assertIn(url1, urls) - self.assertNotIn(url2, urls) - - def test_compute_agent_set(self): - """Test compute agent set""" - os1 = "os_1" - arch1 = "x86_64" - ver1 = "v1" - ver2 = "v2" - url1 = "http://localhost" - url2 = "http://openstack" - md5hash1 = self.MD5HASH1 - md5hash2 = self.MD5HASH2 - hyper1 = "kvm" - cmd = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd - )) - agent_id = str(cmd_output["agent_id"]) - self.assertEqual(ver1, cmd_output["version"]) - self.assertEqual(url1, cmd_output["url"]) - self.assertEqual(md5hash1, cmd_output["md5hash"]) - - self.addCleanup(self.openstack, 'compute agent delete ' + agent_id) - - raw_output = self.openstack( - 'compute agent set ' + - agent_id + ' ' + - '--agent-version ' + ver2 + ' ' + - '--url ' + url2 + ' ' + - '--md5hash ' + md5hash2 - ) - self.assertOutput('', raw_output) - - cmd_output = json.loads(self.openstack( - 'compute agent list -f json' - )) - self.assertEqual(ver2, cmd_output[0]["Version"]) - self.assertEqual(url2, cmd_output[0]["URL"]) - self.assertEqual(md5hash2, cmd_output[0]["Md5Hash"]) diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py index c274adf2..162d4287 100644 --- a/openstackclient/tests/functional/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -115,8 +115,8 @@ class FlavorTests(base.TestCase): self.assertFalse( cmd_output["os-flavor-access:is_public"], ) - self.assertEqual( - "a='b2', b='d2'", + self.assertDictEqual( + {"a": "b2", "b": "d2"}, cmd_output["properties"], ) @@ -133,12 +133,18 @@ class FlavorTests(base.TestCase): "flavor list -f json " + "--long" )) - col_name = [x["Name"] for x in cmd_output] - col_properties = [x['Properties'] for x in cmd_output] - self.assertIn(name1, col_name) - self.assertIn("a='b', c='d'", col_properties) - self.assertNotIn(name2, col_name) - self.assertNotIn("b2', b='d2'", col_properties) + # We have list of complex json objects + # Iterate through the list setting flags + found_expected = False + for rec in cmd_output: + if rec['Name'] == name1: + found_expected = True + self.assertEqual('b', rec['Properties']['a']) + self.assertEqual('d', rec['Properties']['c']) + elif rec['Name'] == name2: + # We should have not seen private flavor + self.assertFalse(True) + self.assertTrue(found_expected) # Test list --public cmd_output = json.loads(self.openstack( @@ -201,8 +207,8 @@ class FlavorTests(base.TestCase): self.assertFalse( cmd_output["os-flavor-access:is_public"], ) - self.assertEqual( - "a='first', b='second'", + self.assertDictEqual( + {"a": "first", "b": "second"}, cmd_output["properties"], ) @@ -223,9 +229,14 @@ class FlavorTests(base.TestCase): cmd_output["id"], ) self.assertEqual( - "a='third and 10', b='second', g='fourth'", - cmd_output['properties'], - ) + 'third and 10', + cmd_output['properties']['a']) + self.assertEqual( + 'second', + cmd_output['properties']['b']) + self.assertEqual( + 'fourth', + cmd_output['properties']['g']) raw_output = self.openstack( "flavor unset " + @@ -238,7 +249,5 @@ class FlavorTests(base.TestCase): "flavor show -f json " + name1 )) - self.assertEqual( - "a='third and 10', g='fourth'", - cmd_output["properties"], - ) + + self.assertNotIn('b', cmd_output['properties']) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6e080e9b..44d9c61f 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -16,6 +16,7 @@ import uuid from tempest.lib import exceptions +from openstackclient.compute.v2 import server as v2_server from openstackclient.tests.functional.compute.v2 import common from openstackclient.tests.functional.volume.v2 import common as volume_common @@ -230,7 +231,7 @@ class ServerTests(common.ComputeTestCase): )) # Really, shouldn't this be a list? self.assertEqual( - "a='b', c='d'", + {'a': 'b', 'c': 'd'}, cmd_output['properties'], ) @@ -244,7 +245,7 @@ class ServerTests(common.ComputeTestCase): name )) self.assertEqual( - "c='d'", + {'c': 'd'}, cmd_output['properties'], ) @@ -509,6 +510,20 @@ class ServerTests(common.ComputeTestCase): server['name'], ) + # check that image indicates server "booted from volume" + self.assertEqual( + v2_server.IMAGE_STRING_FOR_BFV, + server['image'], + ) + # check server list too + servers = json.loads(self.openstack( + 'server list -f json' + )) + self.assertEqual( + v2_server.IMAGE_STRING_FOR_BFV, + servers[0]['Image'] + ) + # check volumes cmd_output = json.loads(self.openstack( 'volume show -f json ' + @@ -619,8 +634,8 @@ class ServerTests(common.ComputeTestCase): server_name )) volumes_attached = cmd_output['volumes_attached'] - self.assertTrue(volumes_attached.startswith('id=')) - attached_volume_id = volumes_attached.replace('id=', '') + self.assertIsNotNone(volumes_attached) + attached_volume_id = volumes_attached[0]["id"] # check the volume that attached on server cmd_output = json.loads(self.openstack( @@ -699,8 +714,8 @@ class ServerTests(common.ComputeTestCase): server_name )) volumes_attached = cmd_output['volumes_attached'] - self.assertTrue(volumes_attached.startswith('id=')) - attached_volume_id = volumes_attached.replace('id=', '') + self.assertIsNotNone(volumes_attached) + attached_volume_id = volumes_attached[0]["id"] # check the volume that attached on server cmd_output = json.loads(self.openstack( @@ -773,19 +788,21 @@ class ServerTests(common.ComputeTestCase): server_name )) volumes_attached = cmd_output['volumes_attached'] - self.assertTrue(volumes_attached.startswith('id=')) - attached_volume_id = volumes_attached.replace('id=', '') - # Don't leak the volume when the test exits. - self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id) + self.assertIsNotNone(volumes_attached) + attached_volume_id = volumes_attached[0]["id"] + for vol in volumes_attached: + self.assertIsNotNone(vol['id']) + # Don't leak the volume when the test exits. + self.addCleanup(self.openstack, 'volume delete ' + vol['id']) # Since the server is volume-backed the GET /servers/{server_id} - # response will have image=''. - self.assertEqual('', cmd_output['image']) + # response will have image='N/A (booted from volume)'. + self.assertEqual(v2_server.IMAGE_STRING_FOR_BFV, cmd_output['image']) # check the volume that attached on server cmd_output = json.loads(self.openstack( 'volume show -f json ' + - attached_volume_id + volumes_attached[0]["id"] )) # The volume size should be what we specified on the command line. self.assertEqual(1, int(cmd_output['size'])) @@ -879,14 +896,21 @@ class ServerTests(common.ComputeTestCase): self.assertIsNotNone(server['id']) self.assertEqual(server_name, server['name']) - self.assertIn(str(security_group1['id']), server['security_groups']) - self.assertIn(str(security_group2['id']), server['security_groups']) + sec_grp = "" + for sec in server['security_groups']: + sec_grp += sec['name'] + self.assertIn(str(security_group1['id']), sec_grp) + self.assertIn(str(security_group2['id']), sec_grp) self.wait_for_status(server_name, 'ACTIVE') server = json.loads(self.openstack( 'server show -f json ' + server_name )) - self.assertIn(sg_name1, server['security_groups']) - self.assertIn(sg_name2, server['security_groups']) + # check if security group exists in list + sec_grp = "" + for sec in server['security_groups']: + sec_grp += sec['name'] + self.assertIn(sg_name1, sec_grp) + self.assertIn(sg_name2, sec_grp) def test_server_create_with_empty_network_option_latest(self): """Test server create with empty network option in nova 2.latest.""" diff --git a/openstackclient/tests/functional/examples/__init__.py b/openstackclient/tests/functional/examples/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/openstackclient/tests/functional/examples/__init__.py +++ /dev/null diff --git a/openstackclient/tests/functional/examples/test_examples.py b/openstackclient/tests/functional/examples/test_examples.py deleted file mode 100644 index 031f036a..00000000 --- a/openstackclient/tests/functional/examples/test_examples.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstackclient.tests.functional import base - - -class ExampleTests(base.TestCase): - """Functional tests for running examples.""" - - def test_common(self): - # NOTE(stevemar): If an examples has a non-zero return - # code, then execute will raise an error by default. - base.execute('python', base.EXAMPLE_DIR + '/common.py --debug') - - def test_object_api(self): - base.execute('python', base.EXAMPLE_DIR + '/object_api.py --debug') - - def test_osc_lib(self): - base.execute('python', base.EXAMPLE_DIR + '/osc-lib.py --debug') diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 6504c5b0..8771359c 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -1087,3 +1087,26 @@ class TestQuotaShow(TestQuota): identity_fakes.project_id, details=False ) self.assertNotCalled(self.network.get_quota_default) + + def test_network_quota_show_remove_empty(self): + arglist = [ + self.projects[0].name, + ] + verifylist = [ + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # First check that all regular values are returned + result = self.cmd.get_network_quota(parsed_args) + self.assertEqual(len(network_fakes.QUOTA), len(result)) + + # set 1 of the values to None, and verify it is not returned + orig_get_quota = self.network.get_quota + network_quotas = copy.copy(network_fakes.QUOTA) + network_quotas['healthmonitor'] = None + self.network.get_quota = mock.Mock(return_value=network_quotas) + result = self.cmd.get_network_quota(parsed_args) + self.assertEqual(len(network_fakes.QUOTA) - 1, len(result)) + # Go back to default mock + self.network.get_quota = orig_get_quota diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 6e12f735..d3d037a9 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -14,10 +14,12 @@ # import copy +import random 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 @@ -163,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, {}) @@ -195,9 +196,15 @@ class FakeComputev2Client(object): self.server_groups = mock.Mock() self.server_groups.resource_class = fakes.FakeResource(None, {}) + self.server_migrations = mock.Mock() + self.server_migrations.resource_class = fakes.FakeResource(None, {}) + self.instance_action = mock.Mock() self.instance_action.resource_class = fakes.FakeResource(None, {}) + self.migrations = mock.Mock() + self.migrations.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -770,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 @@ -877,6 +870,7 @@ class FakeKeypair(object): # Set default attributes. keypair_info = { 'name': 'keypair-name-' + uuid.uuid4().hex, + 'type': 'ssh', 'fingerprint': 'dummy', 'public_key': 'dummy', 'user_id': 'user' @@ -1244,7 +1238,7 @@ class FakeServerGroup(object): """Fake one server group""" @staticmethod - def create_one_server_group(attrs=None): + def _create_one_server_group(attrs=None): """Create a fake server group :param Dictionary attrs: @@ -1261,7 +1255,6 @@ class FakeServerGroup(object): 'members': [], 'metadata': {}, 'name': 'server-group-name-' + uuid.uuid4().hex, - 'policies': [], 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, } @@ -1274,6 +1267,38 @@ class FakeServerGroup(object): loaded=True) return server_group + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + attrs.setdefault('policies', ['policy1', 'policy2']) + return FakeServerGroup._create_one_server_group(attrs) + + +class FakeServerGroupV264(object): + """Fake one server group fo API >= 2.64""" + + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + attrs.setdefault('policy', 'policy1') + return FakeServerGroup._create_one_server_group(attrs) + class FakeUsage(object): """Fake one or more usage.""" @@ -1539,3 +1564,89 @@ class FakeRateLimit(object): self.remain = remain self.unit = unit self.next_available = next_available + + +class FakeServerMigration(object): + """Fake one or more server migrations.""" + + @staticmethod + def create_one_server_migration(attrs=None, methods=None): + """Create a fake server migration. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, type, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + migration_info = { + "dest_host": "10.0.2.15", + "status": "migrating", + "type": "migration", + "updated_at": "2017-01-31T08:03:25.000000", + "created_at": "2017-01-31T08:03:21.000000", + "dest_compute": "compute-" + uuid.uuid4().hex, + "id": random.randint(1, 999), + "source_node": "node-" + uuid.uuid4().hex, + "server": uuid.uuid4().hex, + "dest_node": "node-" + uuid.uuid4().hex, + "source_compute": "compute-" + uuid.uuid4().hex, + "uuid": uuid.uuid4().hex, + "old_instance_type_id": uuid.uuid4().hex, + "new_instance_type_id": uuid.uuid4().hex, + "project": uuid.uuid4().hex, + "user": uuid.uuid4().hex + } + + # Overwrite default attributes. + migration_info.update(attrs) + + migration = fakes.FakeResource(info=copy.deepcopy(migration_info), + methods=methods, + loaded=True) + return migration + + @staticmethod + def create_server_migrations(attrs=None, methods=None, count=2): + """Create multiple fake server migrations. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of server migrations to fake + :return: + A list of FakeResource objects faking the server migrations + """ + migrations = [] + for i in range(0, count): + migrations.append( + FakeServerMigration.create_one_server_migration( + attrs, methods)) + + return migrations + + @staticmethod + def get_server_migrations(migrations=None, count=2): + """Get an iterable MagicMock object with a list of faked migrations. + + If server migrations list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List migrations: + A list of FakeResource objects faking server migrations + :param int count: + The number of server migrations to fake + :return: + An iterable Mock object with side_effect set to a list of faked + server migrations + """ + if migrations is None: + migrations = FakeServerMigration.create_server_migrations(count) + return mock.Mock(side_effect=migrations) diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index cd0c1525..e12edd0f 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -16,12 +16,14 @@ from unittest import mock from unittest.mock import call +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 -from osc_lib import utils from openstackclient.compute.v2 import aggregate from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes class TestAggregate(compute_fakes.TestComputev2): @@ -48,8 +50,17 @@ class TestAggregate(compute_fakes.TestComputev2): super(TestAggregate, self).setUp() # Get a shortcut to the AggregateManager Mock - self.aggregate_mock = self.app.client_manager.compute.aggregates - self.aggregate_mock.reset_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.aggregates = mock.Mock() + self.sdk_client.find_aggregate = mock.Mock() + self.sdk_client.create_aggregate = mock.Mock() + self.sdk_client.update_aggregate = mock.Mock() + self.sdk_client.update_aggregate = mock.Mock() + self.sdk_client.set_aggregate_metadata = mock.Mock() + self.sdk_client.add_host_to_aggregate = mock.Mock() + self.sdk_client.remove_host_from_aggregate = mock.Mock() class TestAggregateAddHost(TestAggregate): @@ -57,8 +68,8 @@ class TestAggregateAddHost(TestAggregate): def setUp(self): super(TestAggregateAddHost, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag - self.aggregate_mock.add_host.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag + self.sdk_client.add_host_to_aggregate.return_value = self.fake_ag self.cmd = aggregate.AddAggregateHost(self.app, None) def test_aggregate_add_host(self): @@ -72,11 +83,12 @@ class TestAggregateAddHost(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.add_host.assert_called_once_with(self.fake_ag, - parsed_args.host) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.add_host_to_aggregate.assert_called_once_with( + self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestAggregateCreate(TestAggregate): @@ -84,8 +96,8 @@ class TestAggregateCreate(TestAggregate): def setUp(self): super(TestAggregateCreate, self).setUp() - self.aggregate_mock.create.return_value = self.fake_ag - self.aggregate_mock.set_metadata.return_value = self.fake_ag + self.sdk_client.create_aggregate.return_value = self.fake_ag + self.sdk_client.set_aggregate_metadata.return_value = self.fake_ag self.cmd = aggregate.CreateAggregate(self.app, None) def test_aggregate_create(self): @@ -97,10 +109,10 @@ class TestAggregateCreate(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.create.assert_called_once_with(parsed_args.name, - None) + self.sdk_client.create_aggregate.assert_called_once_with( + name=parsed_args.name) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_aggregate_create_with_zone(self): arglist = [ @@ -114,10 +126,10 @@ class TestAggregateCreate(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.create.assert_called_once_with(parsed_args.name, - parsed_args.zone) + self.sdk_client.create_aggregate.assert_called_once_with( + name=parsed_args.name, availability_zone=parsed_args.zone) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_aggregate_create_with_property(self): arglist = [ @@ -131,12 +143,12 @@ class TestAggregateCreate(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.create.assert_called_once_with(parsed_args.name, - None) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, parsed_args.property) + self.sdk_client.create_aggregate.assert_called_once_with( + name=parsed_args.name) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, parsed_args.property) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestAggregateDelete(TestAggregate): @@ -146,7 +158,7 @@ class TestAggregateDelete(TestAggregate): def setUp(self): super(TestAggregateDelete, self).setUp() - self.aggregate_mock.get = ( + self.sdk_client.find_aggregate = ( compute_fakes.FakeAggregate.get_aggregates(self.fake_ags)) self.cmd = aggregate.DeleteAggregate(self.app, None) @@ -158,10 +170,11 @@ class TestAggregateDelete(TestAggregate): ('aggregate', [self.fake_ags[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id) - self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id) - self.assertIsNone(result) + self.cmd.take_action(parsed_args) + self.sdk_client.find_aggregate.assert_called_once_with( + self.fake_ags[0].id, ignore_missing=False) + self.sdk_client.delete_aggregate.assert_called_once_with( + self.fake_ags[0].id, ignore_missing=False) def test_delete_multiple_aggregates(self): arglist = [] @@ -172,13 +185,13 @@ class TestAggregateDelete(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) calls = [] for a in self.fake_ags: - calls.append(call(a.id)) - self.aggregate_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) + calls.append(call(a.id, ignore_missing=False)) + self.sdk_client.find_aggregate.assert_has_calls(calls) + self.sdk_client.delete_aggregate.assert_has_calls(calls) def test_delete_multiple_agggregates_with_exception(self): arglist = [ @@ -191,23 +204,21 @@ class TestAggregateDelete(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.fake_ags[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 aggregates failed to delete.', - str(e)) - - find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id) - find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate') + self.sdk_client.find_aggregate.side_effect = [ + self.fake_ags[0], sdk_exceptions.NotFoundException] + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 aggregates failed to delete.', + str(e)) - self.assertEqual(2, find_mock.call_count) - self.aggregate_mock.delete.assert_called_once_with( - self.fake_ags[0].id - ) + calls = [] + for a in arglist: + calls.append(call(a, ignore_missing=False)) + self.sdk_client.find_aggregate.assert_has_calls(calls) + self.sdk_client.delete_aggregate.assert_called_with( + self.fake_ags[0].id, ignore_missing=False) class TestAggregateList(TestAggregate): @@ -245,7 +256,7 @@ class TestAggregateList(TestAggregate): def setUp(self): super(TestAggregateList, self).setUp() - self.aggregate_mock.list.return_value = [self.fake_ag] + self.sdk_client.aggregates.return_value = [self.fake_ag] self.cmd = aggregate.ListAggregate(self.app, None) def test_aggregate_list(self): @@ -254,7 +265,7 @@ class TestAggregateList(TestAggregate): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns, columns) - self.assertItemEqual(self.list_data, tuple(data)) + self.assertItemsEqual(self.list_data, tuple(data)) def test_aggregate_list_with_long(self): arglist = [ @@ -267,7 +278,7 @@ class TestAggregateList(TestAggregate): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns_long, columns) - self.assertListItemEqual(self.list_data_long, tuple(data)) + self.assertItemsEqual(self.list_data_long, tuple(data)) class TestAggregateRemoveHost(TestAggregate): @@ -275,11 +286,11 @@ class TestAggregateRemoveHost(TestAggregate): def setUp(self): super(TestAggregateRemoveHost, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag - self.aggregate_mock.remove_host.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag + self.sdk_client.remove_host_from_aggregate.return_value = self.fake_ag self.cmd = aggregate.RemoveAggregateHost(self.app, None) - def test_aggregate_add_host(self): + def test_aggregate_remove_host(self): arglist = [ 'ag1', 'host1', @@ -290,11 +301,12 @@ class TestAggregateRemoveHost(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.remove_host.assert_called_once_with( - self.fake_ag, parsed_args.host) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.remove_host_from_aggregate.assert_called_once_with( + self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestAggregateSet(TestAggregate): @@ -302,7 +314,7 @@ class TestAggregateSet(TestAggregate): def setUp(self): super(TestAggregateSet, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag self.cmd = aggregate.SetAggregate(self.app, None) def test_aggregate_set_no_option(self): @@ -315,9 +327,10 @@ class TestAggregateSet(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) def test_aggregate_set_with_name(self): @@ -332,10 +345,11 @@ class TestAggregateSet(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.update.assert_called_once_with( - self.fake_ag, {'name': parsed_args.name}) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.update_aggregate.assert_called_once_with( + self.fake_ag.id, name=parsed_args.name) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) def test_aggregate_set_with_zone(self): @@ -350,10 +364,11 @@ class TestAggregateSet(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.update.assert_called_once_with( - self.fake_ag, {'availability_zone': parsed_args.zone}) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.update_aggregate.assert_called_once_with( + self.fake_ag.id, availability_zone=parsed_args.zone) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) def test_aggregate_set_with_property(self): @@ -369,10 +384,11 @@ class TestAggregateSet(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, parsed_args.property) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, parsed_args.property) self.assertIsNone(result) def test_aggregate_set_with_no_property_and_property(self): @@ -388,10 +404,11 @@ class TestAggregateSet(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, {'key1': None, 'key2': 'value2'}) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, {'key1': None, 'key2': 'value2'}) self.assertIsNone(result) def test_aggregate_set_with_no_property(self): @@ -405,10 +422,11 @@ class TestAggregateSet(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, {'key1': None}) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, {'key1': None}) self.assertIsNone(result) def test_aggregate_set_with_zone_and_no_property(self): @@ -424,11 +442,12 @@ class TestAggregateSet(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.update.assert_called_once_with( - self.fake_ag, {'availability_zone': parsed_args.zone}) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, {'key1': None}) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.update_aggregate.assert_called_once_with( + self.fake_ag.id, availability_zone=parsed_args.zone) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, {'key1': None}) self.assertIsNone(result) @@ -457,7 +476,7 @@ class TestAggregateShow(TestAggregate): def setUp(self): super(TestAggregateShow, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag self.cmd = aggregate.ShowAggregate(self.app, None) def test_aggregate_show(self): @@ -469,10 +488,11 @@ class TestAggregateShow(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, tuple(data)) + self.assertItemsEqual(self.data, tuple(data)) class TestAggregateUnset(TestAggregate): @@ -480,7 +500,7 @@ class TestAggregateUnset(TestAggregate): def setUp(self): super(TestAggregateUnset, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag self.cmd = aggregate.UnsetAggregate(self.app, None) def test_aggregate_unset(self): @@ -495,7 +515,7 @@ class TestAggregateUnset(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.set_metadata.assert_called_once_with( + self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag, {'unset_key': None}) self.assertIsNone(result) @@ -512,7 +532,7 @@ class TestAggregateUnset(TestAggregate): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.set_metadata.assert_called_once_with( + self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag, {'unset_key1': None, 'unset_key2': None}) self.assertIsNone(result) @@ -526,5 +546,72 @@ class TestAggregateUnset(TestAggregate): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) + + +class TestAggregateCacheImage(TestAggregate): + + images = image_fakes.FakeImage.create_images(count=2) + + def setUp(self): + super(TestAggregateCacheImage, self).setUp() + + self.sdk_client.find_aggregate.return_value = self.fake_ag + self.find_image_mock = mock.Mock(side_effect=self.images) + self.app.client_manager.sdk_connection.image.find_image = \ + self.find_image_mock + + self.cmd = aggregate.CacheImageForAggregate(self.app, None) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_aggregate_not_supported(self, sm_mock): + arglist = [ + 'ag1', + 'im1' + ] + verifylist = [ + ('aggregate', 'ag1'), + ('image', ['im1']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_aggregate_add_single_image(self, sm_mock): + arglist = [ + 'ag1', + 'im1' + ] + verifylist = [ + ('aggregate', 'ag1'), + ('image', ['im1']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.aggregate_precache_images.assert_called_once_with( + self.fake_ag.id, [self.images[0].id]) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_aggregate_add_multiple_images(self, sm_mock): + arglist = [ + 'ag1', + 'im1', + 'im2', + ] + verifylist = [ + ('aggregate', 'ag1'), + ('image', ['im1', 'im2']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.aggregate_precache_images.assert_called_once_with( + self.fake_ag.id, [self.images[0].id, self.images[1].id]) diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index 99a14f04..db9603c9 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -17,29 +17,103 @@ from unittest import mock from openstackclient.compute.v2 import console from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils class TestConsole(compute_fakes.TestComputev2): def setUp(self): super(TestConsole, self).setUp() - self.servers_mock = self.app.client_manager.compute.servers - self.servers_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.find_server = mock.Mock() + self.sdk_client.get_server_console_output = mock.Mock() + + +class TestConsoleLog(TestConsole): + _server = compute_fakes.FakeServer.create_one_server() + + def setUp(self): + super(TestConsoleLog, self).setUp() + + self.sdk_client.find_server.return_value = self._server + + self.cmd = console.ShowConsoleLog(self.app, None) + + def test_show_no_args(self): + arglist = [ + ] + verifylist = [ + ] + self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_show(self): + arglist = [ + 'fake_server' + ] + verifylist = [ + ('server', 'fake_server') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + output = { + 'output': '1st line\n2nd line\n' + } + self.sdk_client.get_server_console_output.return_value = output + self.cmd.take_action(parsed_args) + + self.sdk_client.find_server.assert_called_with( + name_or_id='fake_server', ignore_missing=False) + self.sdk_client.get_server_console_output.assert_called_with( + self._server.id, + length=None + ) + stdout = self.app.stdout.content + self.assertEqual(stdout[0], output['output']) + + def test_show_lines(self): + arglist = [ + 'fake_server', + '--lines', '15' + ] + verifylist = [ + ('server', 'fake_server'), + ('lines', 15) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + output = { + 'output': '1st line\n2nd line' + } + self.sdk_client.get_server_console_output.return_value = output + self.cmd.take_action(parsed_args) + + self.sdk_client.find_server.assert_called_with( + name_or_id='fake_server', ignore_missing=False) + self.sdk_client.get_server_console_output.assert_called_with( + self._server.id, + length=15 + ) class TestConsoleUrlShow(TestConsole): + _server = compute_fakes.FakeServer.create_one_server() def setUp(self): super(TestConsoleUrlShow, self).setUp() - fake_console_data = {'remote_console': {'url': 'http://localhost', - 'protocol': 'fake_protocol', - 'type': 'fake_type'}} - methods = { - 'get_console_url': fake_console_data - } - self.fake_server = compute_fakes.FakeServer.create_one_server( - methods=methods) - self.servers_mock.get.return_value = self.fake_server + self.sdk_client.find_server.return_value = self._server + fake_console_data = {'url': 'http://localhost', + 'protocol': 'fake_protocol', + 'type': 'fake_type'} + self.sdk_client.create_console = mock.Mock( + return_value=fake_console_data) self.columns = ( 'protocol', @@ -47,9 +121,9 @@ class TestConsoleUrlShow(TestConsole): 'url', ) self.data = ( - fake_console_data['remote_console']['protocol'], - fake_console_data['remote_console']['type'], - fake_console_data['remote_console']['url'] + fake_console_data['protocol'], + fake_console_data['type'], + fake_console_data['url'] ) self.cmd = console.ShowConsoleURL(self.app, None) @@ -64,7 +138,9 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('novnc') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -79,7 +155,9 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('novnc') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -94,7 +172,9 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('xvpvnc') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='xvpvnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -109,41 +189,12 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with( - 'spice-html5') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='spice-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_console_url_show_compatible(self): - methods = { - 'get_console_url': {'console': {'url': 'http://localhost', - 'type': 'fake_type'}}, - } - old_fake_server = compute_fakes.FakeServer.create_one_server( - methods=methods) - old_columns = ( - 'type', - 'url', - ) - old_data = ( - methods['get_console_url']['console']['type'], - methods['get_console_url']['console']['url'] - ) - arglist = [ - 'foo_vm', - ] - verifylist = [ - ('url_type', 'novnc'), - ('server', 'foo_vm'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(self.servers_mock, 'get', - return_value=old_fake_server): - columns, data = self.cmd.take_action(parsed_args) - old_fake_server.get_console_url.assert_called_once_with('novnc') - self.assertEqual(old_columns, columns) - self.assertEqual(old_data, data) - def test_console_url_show_with_rdp(self): arglist = [ '--rdp', @@ -155,8 +206,9 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with( - 'rdp-html5') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='rdp-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -171,8 +223,9 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with( - 'serial') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='serial') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -187,6 +240,8 @@ class TestConsoleUrlShow(TestConsole): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('webmks') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='webmks') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index fe7ce174..4c4882ec 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -12,13 +12,13 @@ # 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 -from osc_lib import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -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, - utils.format_dict(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,23 +117,23 @@ 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.assertEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_flavor_create_all_options(self): @@ -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): + 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 + } + + 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.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.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.assertEqual(self.data, data) + self.assertItemsEqual(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.assertEqual(self.data, data) + self.assertItemsEqual(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.assertEqual(self.data, data) + self.assertItemsEqual(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, - u'property=\'value\'' + _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,21 +626,19 @@ 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_long, columns) - self.assertEqual(tuple(self.data_long), tuple(data)) + self.assertItemsEqual(self.data_long, tuple(data)) 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,16 +808,54 @@ 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) + + def test_flavor_set_description_using_name_api_newer(self): + arglist = [ + '--description', 'description', + self.flavor.name, + ] + verifylist = [ + ('description', 'description'), + ('flavor', 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(sdk_utils, + 'supports_microversion', + return_value=True): + result = self.cmd.take_action(parsed_args) + self.sdk_client.update_flavor.assert_called_with( + flavor=self.flavor.id, description='description') + self.assertIsNone(result) + + def test_flavor_set_description_using_name_api_older(self): + arglist = [ + '--description', 'description', + self.flavor.name, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.name), + ] + 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', + 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() @@ -769,11 +872,11 @@ class TestFlavorShow(TestFlavor): 'ram', 'rxtx_factor', 'swap', - 'vcpus', + 'vcpus' ) data = ( - flavor.disabled, + flavor.is_disabled, flavor.ephemeral, None, flavor.description, @@ -781,7 +884,7 @@ class TestFlavorShow(TestFlavor): flavor.id, flavor.name, flavor.is_public, - utils.format_dict(flavor.get_keys()), + format_columns.DictColumn(flavor.extra_specs), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -792,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): @@ -818,7 +920,7 @@ class TestFlavorShow(TestFlavor): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_private_flavor_show(self): private_flavor = compute_fakes.FakeFlavor.create_one_flavor( @@ -826,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, @@ -836,15 +938,15 @@ class TestFlavorShow(TestFlavor): ] data_with_project = ( - private_flavor.disabled, + private_flavor.is_disabled, private_flavor.ephemeral, - self.flavor_access.tenant_id, + [self.flavor_access.tenant_id], private_flavor.description, private_flavor.disk, private_flavor.id, private_flavor.name, private_flavor.is_public, - utils.format_dict(private_flavor.get_keys()), + format_columns.DictColumn(private_flavor.extra_specs), private_flavor.ram, private_flavor.rxtx_factor, private_flavor.swap, @@ -855,15 +957,15 @@ 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.assertEqual(data_with_project, data) + self.assertItemsEqual(data_with_project, data) 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() @@ -871,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', @@ -889,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, @@ -909,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): @@ -941,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, @@ -968,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() diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 1f3f56f9..5a17808f 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -13,15 +13,19 @@ # under the License. # +import copy from unittest import mock from unittest.mock import call import uuid +from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib import exceptions -from osc_lib import utils from openstackclient.compute.v2 import keypair from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit import utils as tests_utils @@ -30,9 +34,22 @@ class TestKeypair(compute_fakes.TestComputev2): def setUp(self): super(TestKeypair, self).setUp() - # Get a shortcut to the KeypairManager Mock - self.keypairs_mock = self.app.client_manager.compute.keypairs - self.keypairs_mock.reset_mock() + # Initialize the user mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + 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.keypairs = mock.Mock() + self.sdk_client.create_keypair = mock.Mock() + self.sdk_client.delete_keypair = mock.Mock() + self.sdk_client.find_keypair = mock.Mock() class TestKeypairCreate(TestKeypair): @@ -45,18 +62,20 @@ class TestKeypairCreate(TestKeypair): self.columns = ( 'fingerprint', 'name', + 'type', 'user_id' ) self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) # Get the command object to test self.cmd = keypair.CreateKeypair(self.app, None) - self.keypairs_mock.create.return_value = self.keypair + self.sdk_client.create_keypair.return_value = self.keypair def test_key_pair_create_no_options(self): @@ -70,9 +89,8 @@ class TestKeypairCreate(TestKeypair): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( - self.keypair.name, - public_key=None + self.sdk_client.create_keypair.assert_called_with( + name=self.keypair.name ) self.assertEqual({}, columns) @@ -82,11 +100,12 @@ class TestKeypairCreate(TestKeypair): # overwrite the setup one because we want to omit private_key self.keypair = compute_fakes.FakeKeypair.create_one_keypair( no_pri=True) - self.keypairs_mock.create.return_value = self.keypair + self.sdk_client.create_keypair.return_value = self.keypair self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) @@ -96,7 +115,7 @@ class TestKeypairCreate(TestKeypair): ] verifylist = [ ('public_key', self.keypair.public_key), - ('name', self.keypair.name) + ('name', self.keypair.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -108,9 +127,9 @@ class TestKeypairCreate(TestKeypair): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( - self.keypair.name, - public_key=self.keypair.public_key + self.sdk_client.create_keypair.assert_called_with( + name=self.keypair.name, + public_key=self.keypair.public_key, ) self.assertEqual(self.columns, columns) @@ -124,7 +143,7 @@ class TestKeypairCreate(TestKeypair): ] verifylist = [ ('private_key', tmp_pk_file), - ('name', self.keypair.name) + ('name', self.keypair.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -135,9 +154,8 @@ class TestKeypairCreate(TestKeypair): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( - self.keypair.name, - public_key=None + self.sdk_client.create_keypair.assert_called_with( + name=self.keypair.name, ) mock_open.assert_called_once_with(tmp_pk_file, 'w+') @@ -146,6 +164,116 @@ class TestKeypairCreate(TestKeypair): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_create_with_key_type(self, sm_mock): + for key_type in ['x509', 'ssh']: + self.keypair = compute_fakes.FakeKeypair.create_one_keypair( + no_pri=True) + self.sdk_client.create_keypair.return_value = self.keypair + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.type, + self.keypair.user_id, + ) + arglist = [ + '--public-key', self.keypair.public_key, + self.keypair.name, + '--type', key_type, + ] + verifylist = [ + ('public_key', self.keypair.public_key), + ('name', self.keypair.name), + ('type', key_type), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + m_file.read.return_value = 'dummy' + columns, data = self.cmd.take_action(parsed_args) + + self.sdk_client.create_keypair.assert_called_with( + name=self.keypair.name, + public_key=self.keypair.public_key, + key_type=key_type, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_create_with_key_type_pre_v22(self, sm_mock): + for key_type in ['x509', 'ssh']: + arglist = [ + '--public-key', self.keypair.public_key, + self.keypair.name, + '--type', 'ssh', + ] + verifylist = [ + ('public_key', self.keypair.public_key), + ('name', self.keypair.name), + ('type', 'ssh'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + m_file.read.return_value = 'dummy' + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.2 or greater is required', + str(ex)) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_key_pair_create_with_user(self, sm_mock): + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.sdk_client.create_keypair.assert_called_with( + name=self.keypair.name, + user_id=identity_fakes.user_id, + ) + + self.assertEqual({}, columns) + self.assertEqual({}, data) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_key_pair_create_with_user_pre_v210(self, sm_mock): + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + class TestKeypairDelete(TestKeypair): @@ -154,10 +282,6 @@ class TestKeypairDelete(TestKeypair): def setUp(self): super(TestKeypairDelete, self).setUp() - self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs( - self.keypairs) - self.keypairs_mock.delete.return_value = None - self.cmd = keypair.DeleteKeypair(self.app, None) def test_keypair_delete(self): @@ -173,7 +297,8 @@ class TestKeypairDelete(TestKeypair): ret = self.cmd.take_action(parsed_args) self.assertIsNone(ret) - self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name) + self.sdk_client.delete_keypair.assert_called_with( + self.keypairs[0].name, ignore_missing=False) def test_delete_multiple_keypairs(self): arglist = [] @@ -188,8 +313,8 @@ class TestKeypairDelete(TestKeypair): calls = [] for k in self.keypairs: - calls.append(call(k.name)) - self.keypairs_mock.delete.assert_has_calls(calls) + calls.append(call(k.name, ignore_missing=False)) + self.sdk_client.delete_keypair.assert_has_calls(calls) self.assertIsNone(result) def test_delete_multiple_keypairs_with_exception(self): @@ -203,52 +328,81 @@ class TestKeypairDelete(TestKeypair): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.keypairs[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 keys failed to delete.', str(e)) - - find_mock.assert_any_call( - self.keypairs_mock, self.keypairs[0].name) - find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair') - - self.assertEqual(2, find_mock.call_count) - self.keypairs_mock.delete.assert_called_once_with( - self.keypairs[0].name - ) + self.sdk_client.delete_keypair.side_effect = [ + None, exceptions.CommandError] + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 keys failed to delete.', str(e)) + calls = [] + for k in arglist: + calls.append(call(k, ignore_missing=False)) + self.sdk_client.delete_keypair.assert_has_calls(calls) -class TestKeypairList(TestKeypair): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_delete_with_user(self, sm_mock): + arglist = [ + '--user', identity_fakes.user_name, + self.keypairs[0].name + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', [self.keypairs[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # Return value of self.keypairs_mock.list(). - keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1) + ret = self.cmd.take_action(parsed_args) + + self.assertIsNone(ret) + self.sdk_client.delete_keypair.assert_called_with( + self.keypairs[0].name, + user_id=identity_fakes.user_id, + ignore_missing=False + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_delete_with_user_pre_v210(self, sm_mock): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.9') + + arglist = [ + '--user', identity_fakes.user_name, + self.keypairs[0].name + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', [self.keypairs[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns = ( - "Name", - "Fingerprint" - ) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) - data = (( - keypairs[0].name, - keypairs[0].fingerprint - ), ) + +class TestKeypairList(TestKeypair): + + # Return value of self.sdk_client.keypairs(). + keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1) def setUp(self): super(TestKeypairList, self).setUp() - self.keypairs_mock.list.return_value = self.keypairs + self.sdk_client.keypairs.return_value = self.keypairs # Get the command object to test self.cmd = keypair.ListKeypair(self.app, None) - def test_keypair_list_no_options(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_list_no_options(self, sm_mock): arglist = [] - verifylist = [ - ] + verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -259,10 +413,161 @@ class TestKeypairList(TestKeypair): # Set expected values - self.keypairs_mock.list.assert_called_with() + self.sdk_client.keypairs.assert_called_with() - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(('Name', 'Fingerprint'), columns) + self.assertEqual( + ((self.keypairs[0].name, self.keypairs[0].fingerprint), ), + tuple(data) + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_list_v22(self, sm_mock): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + + self.sdk_client.keypairs.assert_called_with() + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_list_with_user(self, sm_mock): + + users_mock = self.app.client_manager.identity.users + users_mock.reset_mock() + users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + users_mock.get.assert_called_with(identity_fakes.user_name) + self.sdk_client.keypairs.assert_called_with( + user_id=identity_fakes.user_id, + ) + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_list_with_user_pre_v210(self, sm_mock): + + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_list_with_project(self, sm_mock): + + projects_mock = self.app.client_manager.identity.tenants + projects_mock.reset_mock() + projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + users_mock = self.app.client_manager.identity.users + users_mock.reset_mock() + users_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ), + ] + + arglist = ['--project', identity_fakes.project_name] + verifylist = [('project', identity_fakes.project_name)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + projects_mock.get.assert_called_with(identity_fakes.project_name) + users_mock.list.assert_called_with(tenant_id=identity_fakes.project_id) + self.sdk_client.keypairs.assert_called_with( + user_id=identity_fakes.user_id, + ) + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_list_with_project_pre_v210(self, sm_mock): + + arglist = ['--project', identity_fakes.project_name] + verifylist = [('project', identity_fakes.project_name)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + + def test_keypair_list_conflicting_user_options(self): + + arglist = [ + '--user', identity_fakes.user_name, + '--project', identity_fakes.project_name, + ] + + self.assertRaises( + tests_utils.ParserException, + self.check_parser, self.cmd, arglist, None) class TestKeypairShow(TestKeypair): @@ -272,23 +577,25 @@ class TestKeypairShow(TestKeypair): def setUp(self): super(TestKeypairShow, self).setUp() - self.keypairs_mock.get.return_value = self.keypair + self.sdk_client.find_keypair.return_value = self.keypair self.cmd = keypair.ShowKeypair(self.app, None) self.columns = ( "fingerprint", "name", + "type", "user_id" ) self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) - def test_show_no_options(self): + def test_keypair_show_no_options(self): arglist = [] verifylist = [] @@ -301,11 +608,12 @@ class TestKeypairShow(TestKeypair): # overwrite the setup one because we want to omit private_key self.keypair = compute_fakes.FakeKeypair.create_one_keypair( no_pri=True) - self.keypairs_mock.get.return_value = self.keypair + self.sdk_client.find_keypair.return_value = self.keypair self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) @@ -315,11 +623,15 @@ class TestKeypairShow(TestKeypair): verifylist = [ ('name', self.keypair.name) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + self.sdk_client.find_keypair.assert_called_with( + self.keypair.name, + ignore_missing=False + ) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -340,3 +652,60 @@ class TestKeypairShow(TestKeypair): self.assertEqual({}, columns) self.assertEqual({}, data) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_show_with_user(self, sm_mock): + + # overwrite the setup one because we want to omit private_key + self.keypair = compute_fakes.FakeKeypair.create_one_keypair( + no_pri=True) + self.sdk_client.find_keypair.return_value = self.keypair + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.type, + self.keypair.user_id + ) + + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.users_mock.get.assert_called_with(identity_fakes.user_name) + self.sdk_client.find_keypair.assert_called_with( + self.keypair.name, + ignore_missing=False, + user_id=identity_fakes.user_id + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_show_with_user_pre_v210(self, sm_mock): + + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c5..dfb8df30 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -19,12 +19,11 @@ import getpass from unittest import mock from unittest.mock import call +import iso8601 from novaclient import api_versions from openstack import exceptions as sdk_exceptions from osc_lib import exceptions from osc_lib import utils as common_utils -from oslo_utils import timeutils -import six from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -43,10 +42,19 @@ class TestServer(compute_fakes.TestComputev2): self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() - # Get a shortcut to the compute client volumeManager Mock + # Get a shortcut to the compute client ServerMigrationsManager Mock + self.server_migrations_mock = \ + self.app.client_manager.compute.server_migrations + self.server_migrations_mock.reset_mock() + + # Get a shortcut to the compute client VolumeManager mock self.servers_volumes_mock = self.app.client_manager.compute.volumes self.servers_volumes_mock.reset_mock() + # Get a shortcut to the compute client MigrationManager mock + self.migrations_mock = self.app.client_manager.compute.migrations + self.migrations_mock.reset_mock() + # Get a shortcut to the compute client FlavorManager Mock self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() @@ -178,6 +186,72 @@ class TestServerAddFixedIP(TestServer): extralist = ['--fixed-ip-address', '5.6.7.8'] self._test_server_add_fixed_ip(extralist, '5.6.7.8') + def test_server_add_fixed_ip_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + with mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_find' + ) as net_mock: + net_mock.return_value = network + + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + servers[0].interface_attach.assert_called_once_with( + port_id=None, + net_id=network['id'], + fixed_ip='5.6.7.8', + tag='tag1' + ) + self.assertIsNone(result) + + def test_server_add_fixed_ip_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + with mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_find' + ) as net_mock: + net_mock.return_value = network + + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + @mock.patch( 'openstackclient.api.compute_v2.APIv2.floating_ip_add' @@ -250,7 +324,7 @@ class TestServerAddFloatingIPNetwork( # Get the command object to test self.cmd = server.AddFloatingIP(self.app, self.namespace) - def test_server_add_floating_ip_default(self): + def test_server_add_floating_ip(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -285,8 +359,41 @@ class TestServerAddFloatingIPNetwork( **attrs ) - def test_server_add_floating_ip_default_no_external_gateway(self, - success=False): + def test_server_add_floating_ip_no_ports(self): + server = compute_fakes.FakeServer.create_one_server() + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + + self.servers_mock.get.return_value = server + self.network.find_ip = mock.Mock(return_value=floating_ip) + self.network.ports = mock.Mock(return_value=[]) + + arglist = [ + server.id, + floating_ip['floating_ip_address'], + ] + verifylist = [ + ('server', server.id), + ('ip_address', floating_ip['floating_ip_address']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'No attached ports found to associate floating IP with', + str(ex)) + + self.network.find_ip.assert_called_once_with( + floating_ip['floating_ip_address'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=server.id, + ) + + def test_server_add_floating_ip_no_external_gateway(self, success=False): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -339,11 +446,10 @@ class TestServerAddFloatingIPNetwork( **attrs ) - def test_server_add_floating_ip_default_one_external_gateway(self): - self.test_server_add_floating_ip_default_no_external_gateway( - success=True) + def test_server_add_floating_ip_one_external_gateway(self): + self.test_server_add_floating_ip_no_external_gateway(success=True) - def test_server_add_floating_ip_fixed(self): + def test_server_add_floating_ip_with_fixed_ip(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -385,7 +491,7 @@ class TestServerAddFloatingIPNetwork( **attrs ) - def test_server_add_floating_ip_fixed_no_port_found(self): + def test_server_add_floating_ip_with_fixed_ip_no_port_found(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -466,6 +572,59 @@ class TestServerAddPort(TestServer): self._test_server_add_port('fake-port') self.find_port.assert_not_called() + def test_server_add_port_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + self.find_port.return_value.id = 'fake-port' + arglist = [ + servers[0].id, + 'fake-port', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('port', 'fake-port'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + servers[0].interface_attach.assert_called_once_with( + port_id='fake-port', + net_id=None, + fixed_ip=None, + tag='tag1') + + def test_server_add_port_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + self.find_port.return_value.id = 'fake-port' + arglist = [ + servers[0].id, + 'fake-port', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('port', 'fake-port'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + class TestServerVolume(TestServer): @@ -503,8 +662,57 @@ class TestServerVolume(TestServer): servers[0].id, self.volume.id, device='/dev/sdb') self.assertIsNone(result) + def test_server_add_volume_with_tag(self): + # requires API 2.49 or later + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + arglist = [ + '--device', '/dev/sdb', + '--tag', 'foo', + servers[0].id, + self.volume.id, + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('device', '/dev/sdb'), + ('tag', 'foo'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.create_server_volume.assert_called_once_with( + servers[0].id, self.volume.id, device='/dev/sdb', tag='foo') + self.assertIsNone(result) + + def test_server_add_volume_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + arglist = [ + servers[0].id, + self.volume.id, + '--tag', 'foo', + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('tag', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) -class TestServerVolumeV279(TestServerVolume): + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) def test_server_add_volume_with_enable_delete_on_termination(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -561,7 +769,8 @@ class TestServerVolumeV279(TestServerVolume): self.assertIsNone(result) def test_server_add_volume_with_enable_delete_on_termination_pre_v279( - self): + self, + ): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.78') @@ -585,7 +794,8 @@ class TestServerVolumeV279(TestServerVolume): str(ex)) def test_server_add_volume_with_disable_delete_on_termination_pre_v279( - self): + self, + ): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.78') @@ -609,7 +819,8 @@ class TestServerVolumeV279(TestServerVolume): str(ex)) def test_server_add_volume_with_disable_and_enable_delete_on_termination( - self): + self, + ): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.79') @@ -682,6 +893,62 @@ class TestServerAddNetwork(TestServer): self._test_server_add_network('fake-network') self.find_network.assert_not_called() + def test_server_add_network_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + self.find_network.return_value.id = 'fake-network' + + arglist = [ + servers[0].id, + 'fake-network', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', 'fake-network'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + servers[0].interface_attach.assert_called_once_with( + port_id=None, + net_id='fake-network', + fixed_ip=None, + tag='tag1' + ) + + def test_server_add_network_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + self.find_network.return_value.id = 'fake-network' + + arglist = [ + servers[0].id, + 'fake-network', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', 'fake-network'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + @mock.patch( 'openstackclient.api.compute_v2.APIv2.security_group_find' @@ -832,6 +1099,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -857,6 +1125,8 @@ class TestServerCreate(TestServer): '--key-name', 'keyname', '--property', 'Beta=b', '--security-group', 'securitygroup', + '--use-config-drive', + '--password', 'passw0rd', '--hint', 'a=b', '--hint', 'a=c', self.new_server.name, @@ -868,7 +1138,8 @@ class TestServerCreate(TestServer): ('property', {'Beta': 'b'}), ('security_group', ['securitygroup']), ('hint', {'a': ['b', 'c']}), - ('config_drive', False), + ('config_drive', True), + ('password', 'passw0rd'), ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -897,10 +1168,11 @@ class TestServerCreate(TestServer): userdata=None, key_name='keyname', availability_zone=None, + admin_pass='passw0rd', block_device_mapping_v2=[], nics=[], scheduler_hints={'a': ['b', 'c']}, - config_drive=None, + config_drive=True, ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( @@ -983,6 +1255,7 @@ class TestServerCreate(TestServer): userdata=None, key_name='keyname', availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1069,6 +1342,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[{'net-id': 'net1_uuid', 'v4-fixed-ip': '', @@ -1133,6 +1407,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -1182,6 +1457,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -1227,6 +1503,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', scheduler_hints={}, @@ -1392,6 +1669,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1442,6 +1720,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1497,6 +1776,7 @@ class TestServerCreate(TestServer): userdata=mock_file, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1543,6 +1823,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vda', 'uuid': self.volume.id, @@ -1595,6 +1876,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vdf', 'uuid': self.volume.id, @@ -1646,6 +1928,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vdf', 'uuid': self.volume.id, @@ -1699,6 +1982,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vde', 'uuid': self.volume.id, @@ -1754,6 +2038,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vds', 'uuid': self.snapshot.id, @@ -1809,6 +2094,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[ { 'device_name': 'vdb', @@ -1907,7 +2193,7 @@ class TestServerCreate(TestServer): self.cmd.take_action, parsed_args) # Assert it is the error we expect. self.assertIn('--volume is not allowed with --boot-from-volume', - six.text_type(ex)) + str(ex)) def test_server_create_image_property(self): arglist = [ @@ -1945,6 +2231,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', meta=None, @@ -2000,6 +2287,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', meta=None, @@ -2048,6 +2336,66 @@ class TestServerCreate(TestServer): self.cmd.take_action, parsed_args) + def test_server_create_image_property_with_image_list(self): + arglist = [ + '--image-property', + 'owner_specified.openstack.object=image/cirros', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + + verifylist = [ + ('image_property', + {'owner_specified.openstack.object': 'image/cirros'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('server_name', self.new_server.name), + ] + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'properties': { + 'owner_specified.openstack.object': 'image/cirros' + } + } + + target_image = image_fakes.FakeImage.create_one_image(image_info) + another_image = image_fakes.FakeImage.create_one_image({}) + self.images_mock.return_value = [target_image, another_image] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + admin_pass=None, + block_device_mapping_v2=[], + nics='none', + meta=None, + scheduler_hints={}, + config_drive=None, + ) + + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + target_image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_invalid_hint(self): # Not a key-value pair arglist = [ @@ -2110,6 +2458,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2155,6 +2504,87 @@ class TestServerCreate(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.52') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--tag', 'tag1', + '--tag', 'tag2', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('tags', ['tag1', 'tag2']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'block_device_mapping_v2': [], + 'admin_pass': None, + 'nics': 'auto', + 'scheduler_hints': {}, + 'config_drive': None, + 'tags': ['tag1', 'tag2'], + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_tag_pre_v252(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.51') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--tag', 'tag1', + '--tag', 'tag2', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('tags', ['tag1', 'tag2']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.52 or greater is required', + str(ex)) + def test_server_create_with_host_v274(self): # Explicit host is supported for nova api version 2.74 or above @@ -2194,6 +2624,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2279,6 +2710,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2366,6 +2798,7 @@ class TestServerCreate(TestServer): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2529,6 +2962,7 @@ class TestServerList(TestServer): super(TestServerList, self).setUp() self.search_opts = { + 'availability_zone': None, 'reservation_id': None, 'ip': None, 'ip6': None, @@ -2609,7 +3043,7 @@ class TestServerList(TestServer): s.status, server._format_servers_list_networks(s.networks), # Image will be an empty string if boot-from-volume - self.image.name if s.image else s.image, + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, )) self.data_long.append(( @@ -2622,8 +3056,8 @@ class TestServerList(TestServer): ), server._format_servers_list_networks(s.networks), # Image will be an empty string if boot-from-volume - self.image.name if s.image else s.image, - s.image['id'] if s.image else s.image, + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, s.flavor['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), @@ -2636,7 +3070,7 @@ class TestServerList(TestServer): s.status, server._format_servers_list_networks(s.networks), # Image will be an empty string if boot-from-volume - s.image['id'] if s.image else s.image, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, s.flavor['id'] )) @@ -2696,6 +3130,25 @@ class TestServerList(TestServer): self.assertEqual(self.columns_long, columns) self.assertEqual(tuple(self.data_long), tuple(data)) + def test_server_list_column_option(self): + arglist = [ + '-c', 'Project ID', + '-c', 'User ID', + '-c', 'Created At', + '--long' + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertIn('Project ID', columns) + self.assertIn('User ID', columns) + self.assertIn('Created At', columns) + def test_server_list_no_name_lookup_option(self): arglist = [ '--no-name-lookup', @@ -2886,7 +3339,7 @@ class TestServerList(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) - @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): arglist = [ @@ -2924,12 +3377,13 @@ class TestServerList(TestServer): self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' self.search_opts['deleted'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) - @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_v266_with_invalid_changes_before( self, mock_parse_isotime): self.app.client_manager.compute.api_version = ( @@ -3016,6 +3470,92 @@ class TestServerList(TestServer): 'UNKNOWN', '', '', '') self.assertEqual(expected_row, partial_server) + def test_server_list_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['tags'] = ['tag1', 'tag2'] + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_tag_pre_v225(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + + def test_server_list_with_not_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--not-tag', 'tag1', + '--not-tag', 'tag2', + ] + verifylist = [ + ('not_tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['not-tags'] = ['tag1', 'tag2'] + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_not_tag_pre_v226(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--not-tag', 'tag1', + '--not-tag', 'tag2', + ] + verifylist = [ + ('not_tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerLock(TestServer): @@ -3229,7 +3769,7 @@ class TestServerMigrate(TestServer): # Make sure it's the error we expect. self.assertIn('--os-compute-api-version 2.56 or greater is required ' 'to use --host without --live-migration.', - six.text_type(ex)) + str(ex)) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.live_migrate) @@ -3264,7 +3804,7 @@ class TestServerMigrate(TestServer): # A warning should have been logged for using --live. mock_warning.assert_called_once() self.assertIn('The --live option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) def test_server_live_migrate_host_pre_2_30(self): # Tests that the --host option is not supported for --live-migration @@ -3287,7 +3827,7 @@ class TestServerMigrate(TestServer): # Make sure it's the error we expect. self.assertIn('--os-compute-api-version 2.30 or greater is required ' - 'when using --host', six.text_type(ex)) + 'when using --host', str(ex)) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.live_migrate) @@ -3377,7 +3917,7 @@ class TestServerMigrate(TestServer): # A warning should have been logged for using --live. mock_warning.assert_called_once() self.assertIn('The --live option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) def test_server_live_migrate_live_and_host_mutex(self): # Tests specifying both the --live and --host options which are in a @@ -3523,6 +4063,603 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.live_migrate) +class TestListMigration(TestServer): + """Test fetch all migrations.""" + + MIGRATION_COLUMNS = [ + 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigration, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server + + self.migrations = compute_fakes.FakeServerMigration\ + .create_server_migrations(count=3) + self.migrations_mock.list.return_value = self.migrations + + self.data = (common_utils.get_item_properties( + s, self.MIGRATION_COLUMNS) for s in self.migrations) + + # Get the command object to test + self.cmd = server.ListMigration(self.app, None) + + def test_server_migration_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': None, + 'host': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list(self): + arglist = [ + '--server', 'server1', + '--host', 'host1', + '--status', 'migrating', + '--type', 'cold-migration', + ] + verifylist = [ + ('server', 'server1'), + ('host', 'host1'), + ('status', 'migrating'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': 'host1', + 'instance_uuid': self.server.id, + 'migration_type': 'migration', + } + + self.servers_mock.get.assert_called_with('server1') + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV223(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV223, self).setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + def test_server_migration_list(self): + arglist = [ + '--status', 'migrating' + ] + verifylist = [ + ('status', 'migrating') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert(0, "Id") + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, 'Type') + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV259(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV259, self).setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + def test_server_migration_list(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'changes_since': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list_with_limit_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--limit', '1' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_list_with_marker_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--marker', 'test_kp' + ] + verifylist = [ + ('status', 'migrating'), + ('marker', 'test_kp') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + def test_server_migration_list_with_changes_since_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) + + +class TestListMigrationV266(TestListMigration): + """Test fetch all migrations by changes-before. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV266, self).setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.66') + + def test_server_migration_list_with_changes_before(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migration_list_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.65') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.66 or greater is required', + str(ex)) + + +class TestListMigrationV280(TestListMigration): + """Test fetch all migrations by user-id and/or project-id. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV280, self).setUp() + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.80') + + def test_server_migration_list_with_project(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + + def test_get_migrations_with_project_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + def test_server_migration_list_with_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('User') + + def test_get_migrations_with_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + def test_server_migration_list_with_project_and_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'host': None, + 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', + 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + self.MIGRATION_COLUMNS.remove('User') + + def test_get_migrations_with_project_and_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) + + +class TestServerMigrationAbort(TestServer): + + def setUp(self): + super(TestServerMigrationAbort, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.AbortMigration(self.app, None) + + def test_migration_abort(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migration_abort.assert_called_with( + self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_abort_pre_v224(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + +class TestServerMigrationForceComplete(TestServer): + + def setUp(self): + super(TestServerMigrationForceComplete, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.ForceCompleteMigration(self.app, None) + + def test_migration_force_complete(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.22') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migrate_force_complete\ + .assert_called_with(self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_force_complete_pre_v222(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.21') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.22 or greater is required', + str(ex)) + + class TestServerPause(TestServer): def setUp(self): @@ -3861,6 +4998,167 @@ class TestServerRebuild(TestServer): self.cmd, arglist, verifylist) +class TestEvacuateServer(TestServer): + + def setUp(self): + super(TestEvacuateServer, self).setUp() + # Return value for utils.find_resource for image + self.image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = self.image + + # Fake the rebuilt new server. + attrs = { + 'image': { + 'id': self.image.id + }, + 'networks': {}, + 'adminPass': 'passw0rd', + } + new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs) + + # Fake the server to be rebuilt. The IDs of them should be the same. + attrs['id'] = new_server.id + methods = { + 'evacuate': new_server, + } + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods + ) + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + self.cmd = server.EvacuateServer(self.app, None) + + def _test_evacuate(self, args, verify_args, evac_args): + parsed_args = self.check_parser(self.cmd, args, verify_args) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.evacuate.assert_called_with(**evac_args) + + def test_evacuate(self): + args = [ + self.server.id, + ] + verify_args = [ + ('server', self.server.id), + ] + evac_args = { + 'host': None, 'on_shared_storage': False, 'password': None, + } + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_with_password(self): + args = [ + self.server.id, + '--password', 'password', + ] + verify_args = [ + ('server', self.server.id), + ('password', 'password'), + ] + evac_args = { + 'host': None, 'on_shared_storage': False, 'password': 'password', + } + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_with_host(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.29') + + host = 'target-host' + args = [ + self.server.id, + '--host', 'target-host', + ] + verify_args = [ + ('server', self.server.id), + ('host', 'target-host'), + ] + evac_args = {'host': host, 'password': None} + + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_with_host_pre_v229(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.28') + + args = [ + self.server.id, + '--host', 'target-host', + ] + verify_args = [ + ('server', self.server.id), + ('host', 'target-host'), + ] + parsed_args = self.check_parser(self.cmd, args, verify_args) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_evacuate_without_share_storage(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.13') + + args = [ + self.server.id, + '--shared-storage' + ] + verify_args = [ + ('server', self.server.id), + ('shared_storage', True), + ] + evac_args = { + 'host': None, 'on_shared_storage': True, 'password': None, + } + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_without_share_storage_post_v213(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.14') + + args = [ + self.server.id, + '--shared-storage' + ] + verify_args = [ + ('server', self.server.id), + ('shared_storage', True), + ] + parsed_args = self.check_parser(self.cmd, args, verify_args) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_evacuate_with_wait_ok(self, mock_wait_for_status): + args = [ + self.server.id, + '--wait', + ] + verify_args = [ + ('server', self.server.id), + ('wait', True), + ] + evac_args = { + 'host': None, 'on_shared_storage': False, 'password': None, + } + self._test_evacuate(args, verify_args, evac_args) + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.server.id, + callback=mock.ANY, + ) + + class TestServerRemoveFixedIP(TestServer): def setUp(self): @@ -4293,7 +5591,7 @@ class TestServerResize(TestServer): # A warning should have been logged for using --confirm. mock_warning.assert_called_once() self.assertIn('The --confirm option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) def test_server_resize_revert(self): arglist = [ @@ -4318,7 +5616,7 @@ class TestServerResize(TestServer): # A warning should have been logged for using --revert. mock_warning.assert_called_once() self.assertIn('The --revert option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_resize_with_wait_ok(self, mock_wait_for_status): @@ -4520,6 +5818,8 @@ class TestServerSet(TestServer): 'update': None, 'reset_state': None, 'change_password': None, + 'add_tag': None, + 'set_tags': None, } self.fake_servers = self.setup_servers_mock(2) @@ -4660,6 +5960,50 @@ class TestServerSet(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_set_with_tag(self): + self.fake_servers[0].api_version = api_versions.APIVersion('2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.fake_servers[0].add_tag.assert_has_calls([ + mock.call(tag='tag1'), + mock.call(tag='tag2'), + ]) + self.assertIsNone(result) + + def test_server_set_with_tag_pre_v226(self): + self.fake_servers[0].api_version = api_versions.APIVersion('2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerShelve(TestServer): @@ -4985,6 +6329,52 @@ class TestServerUnset(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_unset_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.delete_tag.assert_has_calls([ + mock.call(self.fake_server, tag='tag1'), + mock.call(self.fake_server, tag='tag2'), + ]) + + def test_server_unset_with_tag_pre_v226(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerUnshelve(TestServer): @@ -5166,6 +6556,8 @@ class TestServerGeneral(TestServer): 'tenant_id': u'tenant-id-xxx', 'networks': {u'public': [u'10.20.30.40', u'2001:db8::f']}, 'links': u'http://xxx.yyy.com', + 'properties': '', + 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}], } _server = compute_fakes.FakeServer.create_one_server(attrs=server_info) find_resource.side_effect = [_server, _flavor] @@ -5182,6 +6574,7 @@ class TestServerGeneral(TestServer): 'properties': '', 'OS-EXT-STS:power_state': server._format_servers_list_power_state( getattr(_server, 'OS-EXT-STS:power_state')), + 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}], } # Call _prep_server_detail(). diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 5cdc2080..753db9cd 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -139,7 +139,7 @@ class TestServerBackupCreate(TestServerBackup): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) def test_server_backup_create_options(self): servers = self.setup_servers_mock(count=1) @@ -173,7 +173,7 @@ class TestServerBackupCreate(TestServerBackup): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_backup_wait_fail(self, mock_wait_for_status): @@ -269,4 +269,4 @@ class TestServerBackupCreate(TestServerBackup): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 9cd876ea..bf0ea0ba 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -15,6 +15,7 @@ from unittest import mock +from novaclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -53,6 +54,33 @@ class TestServerGroup(compute_fakes.TestComputev2): self.server_groups_mock.reset_mock() +class TestServerGroupV264(TestServerGroup): + + fake_server_group = \ + compute_fakes.FakeServerGroupV264.create_one_server_group() + + columns = ( + 'id', + 'members', + 'name', + 'policy', + 'project_id', + 'user_id', + ) + + data = ( + fake_server_group.id, + utils.format_list(fake_server_group.members), + fake_server_group.name, + fake_server_group.policy, + fake_server_group.project_id, + fake_server_group.user_id, + ) + + def setUp(self): + super(TestServerGroupV264, self).setUp() + + class TestServerGroupCreate(TestServerGroup): def setUp(self): @@ -63,6 +91,28 @@ class TestServerGroupCreate(TestServerGroup): def test_server_group_create(self): arglist = [ + '--policy', 'anti-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', 'anti-affinity'), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=[parsed_args.policy], + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_with_soft_policies(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.15') + + arglist = [ '--policy', 'soft-anti-affinity', 'affinity_group', ] @@ -80,6 +130,49 @@ class TestServerGroupCreate(TestServerGroup): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_server_group_create_with_soft_policies_pre_v215(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.14') + + arglist = [ + '--policy', 'soft-anti-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', 'soft-anti-affinity'), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.15 or greater is required', + str(ex)) + + def test_server_group_create_v264(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.64') + + arglist = [ + '--policy', 'soft-anti-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', 'soft-anti-affinity'), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policy=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestServerGroupDelete(TestServerGroup): @@ -230,6 +323,76 @@ class TestServerGroupList(TestServerGroup): self.assertEqual(self.list_data_long, tuple(data)) +class TestServerGroupListV264(TestServerGroupV264): + + list_columns = ( + 'ID', + 'Name', + 'Policy', + ) + + list_columns_long = ( + 'ID', + 'Name', + 'Policy', + 'Members', + 'Project Id', + 'User Id', + ) + + list_data = (( + TestServerGroupV264.fake_server_group.id, + TestServerGroupV264.fake_server_group.name, + TestServerGroupV264.fake_server_group.policy, + ),) + + list_data_long = (( + TestServerGroupV264.fake_server_group.id, + TestServerGroupV264.fake_server_group.name, + TestServerGroupV264.fake_server_group.policy, + utils.format_list(TestServerGroupV264.fake_server_group.members), + TestServerGroupV264.fake_server_group.project_id, + TestServerGroupV264.fake_server_group.user_id, + ),) + + def setUp(self): + super(TestServerGroupListV264, self).setUp() + + self.server_groups_mock.list.return_value = [self.fake_server_group] + self.cmd = server_group.ListServerGroup(self.app, None) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.64') + + def test_server_group_list(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(False) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_server_group_list_with_all_projects_and_long(self): + arglist = [ + '--all-projects', + '--long', + ] + verifylist = [ + ('all_projects', True), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(True) + + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) + + class TestServerGroupShow(TestServerGroup): def setUp(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 1cec5b68..06f6017c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -133,7 +133,7 @@ class TestServerImageCreate(TestServerImage): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) def test_server_image_create_options(self): servers = self.setup_servers_mock(count=1) @@ -161,7 +161,7 @@ class TestServerImageCreate(TestServerImage): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_create_image_wait_fail(self, mock_wait_for_status): @@ -229,4 +229,4 @@ class TestServerImageCreate(TestServerImage): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 7a036833..87e54747 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -18,7 +18,6 @@ from unittest.mock import call from novaclient import api_versions from osc_lib import exceptions -import six from openstackclient.compute.v2 import service from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -502,7 +501,7 @@ class TestServiceSet(TestService): self.cmd._find_service_by_host_and_binary, self.service_mock, 'fake-host', 'nova-compute') self.assertIn('Compute service for host "fake-host" and binary ' - '"nova-compute" not found.', six.text_type(ex)) + '"nova-compute" not found.', str(ex)) def test_service_set_find_service_by_host_and_binary_many_results(self): # Tests that more than one compute service is found by host and binary. @@ -512,4 +511,4 @@ class TestServiceSet(TestService): self.service_mock, 'fake-host', 'nova-compute') self.assertIn('Multiple compute services found for host "fake-host" ' 'and binary "nova-compute". Unable to proceed.', - six.text_type(ex)) + str(ex)) diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index e5476f06..00e0c129 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -19,7 +19,6 @@ from unittest import mock from keystoneauth1 import fixture import requests -import six AUTH_TOKEN = "foobar" @@ -253,7 +252,7 @@ class FakeResponse(requests.Response): self.headers.update(headers) self._content = json.dumps(data) - if not isinstance(self._content, six.binary_type): + if not isinstance(self._content, bytes): self._content = self._content.encode() diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index 17355074..e2c56ba1 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -71,9 +71,10 @@ class TestCatalogList(TestCatalog): datalist = (( 'supernova', 'compute', - catalog.EndpointsColumn(self.service_catalog['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): attr = { @@ -113,9 +114,10 @@ class TestCatalogList(TestCatalog): datalist = (( 'supernova', 'compute', - catalog.EndpointsColumn(service_catalog['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -150,16 +152,17 @@ class TestCatalogShow(TestCatalog): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - catalog.EndpointsColumn(self.service_catalog['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), self.service_catalog.id, 'supernova', 'compute', ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) class TestFormatColumns(TestCatalog): - def test_endpoints_column_human_readabale(self): + def test_endpoints_column_human_readable(self): col = catalog.EndpointsColumn(self.service_catalog['endpoints']) self.assertEqual( 'one\n publicURL: https://public.one.example.com\n ' diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index cd8c825d..766d5dab 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -643,7 +643,7 @@ class TestProjectShow(TestProject): self.fake_proj_show.name, format_columns.DictColumn({}), ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) class TestProjectUnset(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index 4308b05d..dd300478 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -482,7 +482,7 @@ class TestUserList(TestUser): self.users_mock.list.assert_called_with(tenant_id=None) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -502,7 +502,7 @@ class TestUserList(TestUser): self.users_mock.list.assert_called_with(tenant_id=project_id) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -531,7 +531,7 @@ class TestUserList(TestUser): self.fake_user_l.email, True, ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestUserSet(TestUser): @@ -819,4 +819,4 @@ class TestUserShow(TestUser): self.fake_user.name, self.fake_project.id, ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 3630ccb6..97ce48f6 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -91,9 +91,10 @@ class TestCatalogList(TestCatalog): datalist = (( 'supernova', 'compute', - catalog.EndpointsColumn(self.fake_service['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -128,12 +129,13 @@ class TestCatalogShow(TestCatalog): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - catalog.EndpointsColumn(self.fake_service['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), 'qwertyuiop', 'supernova', 'compute', ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) class TestFormatColumns(TestCatalog): diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index a419a9bc..5aff2b1b 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -89,7 +89,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -117,7 +117,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -145,7 +145,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -174,7 +174,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -207,7 +207,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -250,7 +250,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) def test_create_identity_provider_domain_name(self): arglist = [ @@ -278,7 +278,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_domain_id(self): arglist = [ @@ -306,7 +306,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) class TestIdentityProviderDelete(TestIdentityProvider): @@ -382,7 +382,62 @@ class TestIdentityProviderList(TestIdentityProvider): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) + + def test_identity_provider_list_ID_option(self): + arglist = ['--id', + identity_fakes.idp_id] + verifylist = [ + ('id', identity_fakes.idp_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'id': identity_fakes.idp_id + } + self.identity_providers_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Enabled', 'Domain ID', 'Description') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.idp_id, + True, + identity_fakes.domain_id, + identity_fakes.idp_description, + ), ) + self.assertItemsEqual(datalist, tuple(data)) + + def test_identity_provider_list_enabled_option(self): + arglist = ['--enabled'] + verifylist = [ + ('enabled', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'enabled': True + } + self.identity_providers_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Enabled', 'Domain ID', 'Description') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.idp_id, + True, + identity_fakes.domain_id, + identity_fakes.idp_description, + ), ) + self.assertItemsEqual(datalist, tuple(data)) class TestIdentityProviderSet(TestIdentityProvider): @@ -667,4 +722,4 @@ class TestIdentityProviderShow(TestIdentityProvider): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 544da7c1..774b2c2b 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -19,6 +19,7 @@ from unittest import mock from osc_lib import exceptions from osc_lib import utils +from openstackclient.identity import common from openstackclient.identity.v3 import role from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -102,6 +103,40 @@ class TestRoleAdd(TestRole): # Get the command object to test self.cmd = role.AddRole(self.app, None) + def test_role_add_user_system(self): + arglist = [ + '--user', identity_fakes.user_name, + '--system', 'all', + identity_fakes.role_name, + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_name), + ('group', None), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.grant(role, user=, group=, domain=, project=) + self.roles_mock.grant.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_add_user_domain(self): arglist = [ '--user', identity_fakes.user_name, @@ -168,6 +203,40 @@ class TestRoleAdd(TestRole): ) self.assertIsNone(result) + def test_role_add_group_system(self): + arglist = [ + '--group', identity_fakes.group_name, + '--system', 'all', + identity_fakes.role_name, + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_name), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.grant(role, user=, group=, domain=, project=) + self.roles_mock.grant.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_add_group_domain(self): arglist = [ '--group', identity_fakes.group_name, @@ -744,6 +813,81 @@ class TestRoleRemove(TestRole): # Get the command object to test self.cmd = role.RemoveRole(self.app, None) + def test_role_remove_user_system(self): + arglist = [ + '--user', identity_fakes.user_name, + '--system', 'all', + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_name), + ('group', None), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + + @mock.patch.object(common, 'find_user') + def test_role_remove_non_existent_user_system(self, find_mock): + # Simulate the user not being in keystone, the client should gracefully + # handle this exception and send the request to remove the role since + # keystone supports removing role assignments with non-existent actors + # (e.g., users or groups). + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--user', identity_fakes.user_id, + '--system', 'all', + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_id), + ('group', None), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_user_domain(self): arglist = [ '--user', identity_fakes.user_name, @@ -777,6 +921,46 @@ class TestRoleRemove(TestRole): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_user') + def test_role_remove_non_existent_user_domain(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--user', identity_fakes.user_id, + '--domain', identity_fakes.domain_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_id), + ('group', None), + ('system', None), + ('domain', identity_fakes.domain_name), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'domain': identity_fakes.domain_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_user_project(self): arglist = [ '--user', identity_fakes.user_name, @@ -810,6 +994,121 @@ class TestRoleRemove(TestRole): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_user') + def test_role_remove_non_existent_user_project(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--user', identity_fakes.user_id, + '--project', identity_fakes.project_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_id), + ('group', None), + ('system', None), + ('domain', None), + ('project', identity_fakes.project_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'project': identity_fakes.project_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + + def test_role_remove_group_system(self): + arglist = [ + '--group', identity_fakes.group_name, + '--system', 'all', + identity_fakes.role_name, + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_name), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + + @mock.patch.object(common, 'find_group') + def test_role_remove_non_existent_group_system(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--group', identity_fakes.group_id, + '--system', 'all', + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_id), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_group_domain(self): arglist = [ '--group', identity_fakes.group_name, @@ -844,6 +1143,46 @@ class TestRoleRemove(TestRole): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_group') + def test_role_remove_non_existent_group_domain(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--group', identity_fakes.group_id, + '--domain', identity_fakes.domain_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_id), + ('system', None), + ('domain', identity_fakes.domain_name), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'domain': identity_fakes.domain_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_group_project(self): arglist = [ '--group', identity_fakes.group_name, @@ -877,6 +1216,46 @@ class TestRoleRemove(TestRole): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_group') + def test_role_remove_non_existent_group_project(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--group', identity_fakes.group_id, + '--project', identity_fakes.project_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_id), + ('system', None), + ('domain', None), + ('project', identity_fakes.project_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'project': identity_fakes.project_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_domain_role_on_group_domain(self): self.roles_mock.get.return_value = fakes.FakeResource( None, diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 2f190a7a..db64983c 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -100,7 +100,7 @@ class TestImageCreate(TestImage): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) @mock.patch('sys.stdin', side_effect=[None]) def test_image_reserve_options(self, raw_input): @@ -149,7 +149,7 @@ class TestImageCreate(TestImage): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): @@ -205,7 +205,7 @@ class TestImageCreate(TestImage): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestImageDelete(TestImage): @@ -386,7 +386,7 @@ class TestImageList(TestImage): format_columns.DictColumn( {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -737,7 +737,7 @@ class TestImageShow(TestImage): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_image_show_human_readable(self): arglist = [ diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 310f6b76..b72e9835 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -100,6 +100,7 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, ) @@ -110,7 +111,7 @@ class TestImageCreate(TestImage): self.assertEqual( self.expected_columns, columns) - self.assertItemEqual( + self.assertItemsEqual( self.expected_data, data) @@ -152,6 +153,7 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format='ovf', disk_format='ami', min_disk=10, @@ -164,7 +166,7 @@ class TestImageCreate(TestImage): self.assertEqual( self.expected_columns, columns) - self.assertItemEqual( + self.assertItemsEqual( self.expected_data, data) @@ -239,6 +241,7 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, is_protected=self.new_image.is_protected, @@ -246,13 +249,13 @@ class TestImageCreate(TestImage): Alpha='1', Beta='2', tags=self.new_image.tags, - filename=imagefile.name + filename=imagefile.name, ) self.assertEqual( self.expected_columns, columns) - self.assertItemEqual( + self.assertItemsEqual( self.expected_data, data) @@ -288,6 +291,7 @@ class TestImageCreate(TestImage): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, use_import=True @@ -509,7 +513,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -533,7 +537,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -557,7 +561,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_community_option(self): arglist = [ @@ -605,7 +609,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_shared_member_status_option(self): arglist = [ @@ -693,7 +697,7 @@ class TestImageList(TestImage): self._image.owner_id, format_columns.ListColumn(self._image.tags), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -721,7 +725,7 @@ class TestImageList(TestImage): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -743,7 +747,7 @@ class TestImageList(TestImage): str, ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): ret_limit = 1 @@ -1468,7 +1472,7 @@ class TestImageShow(TestImage): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_image_show_human_readable(self): self.client.find_image.return_value = self.new_image @@ -1614,7 +1618,7 @@ class TestImageSave(TestImage): verifylist = [ ('file', '/path/to/file'), - ('image', self.image.id) + ('image', self.image.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1622,6 +1626,7 @@ class TestImageSave(TestImage): self.client.download_image.assert_called_once_with( self.image.id, + stream=True, output='/path/to/file') diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cef0a11c..2db83d3b 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -634,6 +634,7 @@ class FakePort(object): 'mac_address': 'fa:16:3e:a9:4e:72', 'name': 'port-name-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, + 'numa_affinity_policy': 'required', 'port_security_enabled': True, 'security_group_ids': [], 'status': 'ACTIVE', @@ -641,7 +642,7 @@ class FakePort(object): 'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tags': [], - 'uplink_status_propagation': False, + 'propagate_uplink_status': False, } # Overwrite default attributes. @@ -661,8 +662,8 @@ class FakePort(object): port.project_id = port_attrs['tenant_id'] port.security_group_ids = port_attrs['security_group_ids'] port.qos_policy_id = port_attrs['qos_policy_id'] - port.uplink_status_propagation = port_attrs[ - 'uplink_status_propagation'] + port.propagate_uplink_status = port_attrs[ + 'propagate_uplink_status'] return port @@ -1590,6 +1591,8 @@ class FakeNetworkMeterRule(object): 'excluded': False, 'metering_label_id': 'meter-label-id-' + uuid.uuid4().hex, 'remote_ip_prefix': '10.0.0.0/24', + 'source_ip_prefix': '8.8.8.8/32', + 'destination_ip_prefix': '10.0.0.0/24', 'tenant_id': 'project-id-' + uuid.uuid4().hex, } diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index 9a712704..ade57837 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -75,7 +75,7 @@ class TestListIPAvailability(TestIPAvailability): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_ip_version(self): arglist = [ @@ -93,7 +93,7 @@ class TestListIPAvailability(TestIPAvailability): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_project(self): arglist = [ @@ -113,7 +113,7 @@ class TestListIPAvailability(TestIPAvailability): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestShowIPAvailability(TestIPAvailability): @@ -176,4 +176,4 @@ class TestShowIPAvailability(TestIPAvailability): self._ip_availability.network_name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 5f8eed67..e29b72c7 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -146,7 +146,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -211,7 +211,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_other_options(self): arglist = [ @@ -238,7 +238,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._network.name] @@ -270,7 +270,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -385,7 +385,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ @@ -577,7 +577,7 @@ class TestListNetwork(TestNetwork): self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_external(self): arglist = [ @@ -598,7 +598,7 @@ class TestListNetwork(TestNetwork): **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_internal(self): arglist = [ @@ -615,7 +615,7 @@ class TestListNetwork(TestNetwork): **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_long(self): arglist = [ @@ -634,7 +634,7 @@ class TestListNetwork(TestNetwork): self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_list_name(self): test_name = "fakename" @@ -653,7 +653,7 @@ class TestListNetwork(TestNetwork): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_enable(self): arglist = [ @@ -671,7 +671,7 @@ class TestListNetwork(TestNetwork): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_disable(self): arglist = [ @@ -689,7 +689,7 @@ class TestListNetwork(TestNetwork): **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -708,7 +708,7 @@ class TestListNetwork(TestNetwork): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -727,7 +727,7 @@ class TestListNetwork(TestNetwork): self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -744,7 +744,7 @@ class TestListNetwork(TestNetwork): **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_no_share(self): arglist = [ @@ -761,7 +761,7 @@ class TestListNetwork(TestNetwork): **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_status(self): choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] @@ -780,7 +780,7 @@ class TestListNetwork(TestNetwork): **{'status': test_status} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_provider_network_type(self): network_type = self._network[0].provider_network_type @@ -798,7 +798,7 @@ class TestListNetwork(TestNetwork): 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_provider_physical_network(self): physical_network = self._network[0].provider_physical_network @@ -816,7 +816,7 @@ class TestListNetwork(TestNetwork): 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_provider_segment(self): segmentation_id = self._network[0].provider_segmentation_id @@ -834,7 +834,7 @@ class TestListNetwork(TestNetwork): 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_dhcp_agent(self): arglist = [ @@ -853,7 +853,7 @@ class TestListNetwork(TestNetwork): *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(list(data), list(self.data)) + self.assertItemsEqual(list(data), list(self.data)) def test_list_with_tag_options(self): arglist = [ @@ -878,7 +878,7 @@ class TestListNetwork(TestNetwork): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestSetNetwork(TestNetwork): @@ -1111,7 +1111,7 @@ class TestShowNetwork(TestNetwork): self._network.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 3181ee78..fceac68e 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -246,7 +246,7 @@ class TestListNetworkAgent(TestNetworkAgent): self.network.agents.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_agent_type(self): arglist = [ @@ -263,7 +263,7 @@ class TestListNetworkAgent(TestNetworkAgent): 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_host(self): arglist = [ @@ -280,7 +280,7 @@ class TestListNetworkAgent(TestNetworkAgent): 'host': self.network_agents[0].host, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_networks(self): arglist = [ @@ -298,7 +298,7 @@ class TestListNetworkAgent(TestNetworkAgent): self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_routers(self): arglist = [ @@ -318,7 +318,7 @@ class TestListNetworkAgent(TestNetworkAgent): *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_routers_with_long_option(self): arglist = [ @@ -343,7 +343,7 @@ class TestListNetworkAgent(TestNetworkAgent): router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertListItemEqual(router_agent_data, list(data)) + self.assertItemsEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -571,4 +571,4 @@ class TestShowNetworkAgent(TestNetworkAgent): self.network.get_agent.assert_called_once_with( self._network_agent.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(list(self.data), list(data)) + self.assertItemsEqual(list(self.data), list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py index 8f8922c0..e9224fa6 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py @@ -42,20 +42,24 @@ class TestCreateMeterRule(TestMeterRule): ) columns = ( + 'destination_ip_prefix', 'direction', 'excluded', 'id', 'metering_label_id', 'project_id', 'remote_ip_prefix', + 'source_ip_prefix', ) data = ( + new_rule.destination_ip_prefix, new_rule.direction, new_rule.excluded, new_rule.id, new_rule.metering_label_id, new_rule.project_id, new_rule.remote_ip_prefix, + new_rule.source_ip_prefix, ) def setUp(self): @@ -228,6 +232,8 @@ class TestListMeterRule(TestMeterRule): 'Excluded', 'Direction', 'Remote IP Prefix', + 'Source IP Prefix', + 'Destination IP Prefix' ) data = [] @@ -238,6 +244,8 @@ class TestListMeterRule(TestMeterRule): rule.excluded, rule.direction, rule.remote_ip_prefix, + rule.source_ip_prefix, + rule.destination_ip_prefix )) def setUp(self): @@ -270,21 +278,25 @@ class TestShowMeterRule(TestMeterRule): ) columns = ( + 'destination_ip_prefix', 'direction', 'excluded', 'id', 'metering_label_id', 'project_id', 'remote_ip_prefix', + 'source_ip_prefix', ) data = ( + new_rule.destination_ip_prefix, new_rule.direction, new_rule.excluded, new_rule.id, new_rule.metering_label_id, new_rule.project_id, new_rule.remote_ip_prefix, + new_rule.source_ip_prefix, ) def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 87aea61f..e21f9d01 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -26,6 +26,10 @@ from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils +LIST_FIELDS_TO_RETRIEVE = ('id', 'name', 'mac_address', 'fixed_ips', 'status') +LIST_FIELDS_TO_RETRIEVE_LONG = ('security_group_ids', 'device_owner', 'tags') + + class TestPort(network_fakes.TestNetworkV2): def setUp(self): @@ -59,14 +63,15 @@ class TestPort(network_fakes.TestNetworkV2): 'mac_address', 'name', 'network_id', + 'numa_affinity_policy', 'port_security_enabled', 'project_id', + 'propagate_uplink_status', 'qos_network_policy_id', 'qos_policy_id', 'security_group_ids', 'status', 'tags', - 'uplink_status_propagation', ) data = ( @@ -90,14 +95,15 @@ class TestPort(network_fakes.TestNetworkV2): fake_port.mac_address, fake_port.name, fake_port.network_id, + fake_port.numa_affinity_policy, fake_port.port_security_enabled, fake_port.project_id, + fake_port.propagate_uplink_status, fake_port.qos_network_policy_id, fake_port.qos_policy_id, format_columns.ListColumn(fake_port.security_group_ids), fake_port.status, format_columns.ListColumn(fake_port.tags), - fake_port.uplink_status_propagation, ) return columns, data @@ -119,6 +125,7 @@ class TestCreatePort(TestPort): self.network.find_network = mock.Mock(return_value=fake_net) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + self.network.find_extension = mock.Mock(return_value=[]) # Get the command object to test self.cmd = port.CreatePort(self.app, self.namespace) @@ -144,7 +151,7 @@ class TestCreatePort(TestPort): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -202,7 +209,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -253,7 +260,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -282,7 +289,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -308,7 +315,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -338,7 +345,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -364,7 +371,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_no_fixed_ips(self): arglist = [ @@ -390,7 +397,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -420,7 +427,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -456,7 +463,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -484,7 +491,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -534,7 +541,7 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) - def _test_create_with_tag(self, add_tags=True): + def _test_create_with_tag(self, add_tags=True, add_tags_in_post=True): arglist = [ '--network', self._port.network_id, 'test-port', @@ -553,28 +560,59 @@ class TestCreatePort(TestPort): else: verifylist.append(('no_tag', True)) + self.network.find_extension = mock.Mock(return_value=add_tags_in_post) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_port.assert_called_once_with( - admin_state_up=True, - network_id=self._port.network_id, - name='test-port' - ) - if add_tags: - self.network.set_tags.assert_called_once_with( - self._port, - tests_utils.CompareBySet(['red', 'blue'])) + args = { + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + } + if add_tags_in_post: + if add_tags: + args['tags'] = sorted(['red', 'blue']) + else: + args['tags'] = [] + self.network.create_port.assert_called_once() + # Now we need to verify if arguments to call create_port are as + # expected, + # But we can't simply use assert_called_once_with() method because + # duplicates from 'tags' are removed with + # list(set(parsed_args.tags)) and that don't quarantee order of + # tags list which is used to call create_port(). + create_port_call_kwargs = self.network.create_port.call_args[1] + create_port_call_kwargs['tags'] = sorted( + create_port_call_kwargs['tags']) + self.assertDictEqual(args, create_port_call_kwargs) else: - self.assertFalse(self.network.set_tags.called) + self.network.create_port.assert_called_once_with( + admin_state_up=True, + network_id=self._port.network_id, + name='test-port' + ) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._port, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): - self._test_create_with_tag(add_tags=True) + self._test_create_with_tag(add_tags=True, add_tags_in_post=True) def test_create_with_no_tag(self): - self._test_create_with_tag(add_tags=False) + self._test_create_with_tag(add_tags=False, add_tags_in_post=True) + + def test_create_with_tags_using_put(self): + self._test_create_with_tag(add_tags=True, add_tags_in_post=False) + + def test_create_with_no_tag_using_put(self): + self._test_create_with_tag(add_tags=False, add_tags_in_post=False) def _test_create_with_uplink_status_propagation(self, enable=True): arglist = [ @@ -605,7 +643,7 @@ class TestCreatePort(TestPort): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): self._test_create_with_uplink_status_propagation(enable=True) @@ -655,6 +693,50 @@ class TestCreatePort(TestPort): 'name': 'test-port', }) + def _test_create_with_numa_affinity_policy(self, policy=None): + arglist = [ + '--network', self._port.network_id, + 'test-port', + ] + if policy: + arglist += ['--numa-policy-%s' % policy] + + numa_affinity_policy = None if not policy else policy + verifylist = [ + ('network', self._port.network_id,), + ('name', 'test-port'), + ] + if policy: + verifylist.append(('numa_policy_%s' % policy, True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + create_args = { + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + } + if numa_affinity_policy: + create_args['numa_affinity_policy'] = numa_affinity_policy + self.network.create_port.assert_called_once_with(**create_args) + + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + + def test_create_with_numa_affinity_policy_required(self): + self._test_create_with_numa_affinity_policy(policy='required') + + def test_create_with_numa_affinity_policy_preferred(self): + self._test_create_with_numa_affinity_policy(policy='preferred') + + def test_create_with_numa_affinity_policy_legacy(self): + self._test_create_with_numa_affinity_policy(policy='legacy') + + def test_create_with_numa_affinity_policy_null(self): + self._test_create_with_numa_affinity_policy() + class TestDeletePort(TestPort): @@ -805,9 +887,10 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) - self.network.ports.assert_called_once_with() + self.network.ports.assert_called_once_with( + fields=LIST_FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_router_opt(self): arglist = [ @@ -823,10 +906,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'device_id': 'fake-router-id' + 'device_id': 'fake-router-id', + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) @mock.patch.object(utils, 'find_resource') def test_port_list_with_server_option(self, mock_find): @@ -843,10 +927,11 @@ class TestListPort(TestPort): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with( - device_id=fake_server.id) + device_id=fake_server.id, + fields=LIST_FIELDS_TO_RETRIEVE) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_device_id_opt(self): arglist = [ @@ -862,10 +947,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'device_id': self._ports[0].device_id + 'device_id': self._ports[0].device_id, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_device_owner_opt(self): arglist = [ @@ -881,10 +967,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'device_owner': self._ports[0].device_owner + 'device_owner': self._ports[0].device_owner, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_all_opt(self): arglist = [ @@ -909,10 +996,11 @@ class TestListPort(TestPort): 'device_owner': self._ports[0].device_owner, 'device_id': 'fake-router-id', 'network_id': 'fake-network-id', - 'mac_address': self._ports[0].mac_address + 'mac_address': self._ports[0].mac_address, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_mac_address_opt(self): arglist = [ @@ -928,10 +1016,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'mac_address': self._ports[0].mac_address + 'mac_address': self._ports[0].mac_address, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address(self): ip_address = self._ports[0].fixed_ips[0]['ip_address'] @@ -947,9 +1036,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'fixed_ips': ['ip_address=%s' % ip_address]}) + 'fixed_ips': ['ip_address=%s' % ip_address], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address_substr(self): ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1] @@ -965,9 +1056,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]}) + 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -985,9 +1078,11 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'fixed_ips': ['subnet_id=%s' % subnet_id]}) + 'fixed_ips': ['subnet_id=%s' % subnet_id], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opts(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1009,9 +1104,11 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['subnet_id=%s' % subnet_id, - 'ip_address=%s' % ip_address]}) + 'ip_address=%s' % ip_address], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ips(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1025,17 +1122,21 @@ class TestListPort(TestPort): {'ip-address': ip_address}]) ] - self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( - {'id': subnet_id}) + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet({ + 'id': subnet_id, + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['subnet_id=%s' % subnet_id, - 'ip_address=%s' % ip_address]}) + 'ip_address=%s' % ip_address], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_port_with_long(self): arglist = [ @@ -1050,9 +1151,10 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) - self.network.ports.assert_called_once_with() + self.network.ports.assert_called_once_with( + fields=LIST_FIELDS_TO_RETRIEVE + LIST_FIELDS_TO_RETRIEVE_LONG) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_port_list_host(self): arglist = [ @@ -1064,11 +1166,14 @@ class TestListPort(TestPort): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'binding:host_id': 'foobar'} + filters = { + 'binding:host_id': 'foobar', + 'fields': LIST_FIELDS_TO_RETRIEVE, + } self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -1082,11 +1187,15 @@ class TestListPort(TestPort): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = { + 'tenant_id': project.id, + 'project_id': project.id, + 'fields': LIST_FIELDS_TO_RETRIEVE, + } self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -1102,11 +1211,15 @@ class TestListPort(TestPort): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = { + 'tenant_id': project.id, + 'project_id': project.id, + 'fields': LIST_FIELDS_TO_RETRIEVE, + } self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1128,10 +1241,11 @@ class TestListPort(TestPort): **{'tags': 'red,blue', 'any_tags': 'red,green', 'not_tags': 'orange,yellow', - 'not_any_tags': 'black,white'} + 'not_any_tags': 'black,white', + 'fields': LIST_FIELDS_TO_RETRIEVE} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestSetPort(TestPort): @@ -1668,6 +1782,32 @@ class TestSetPort(TestPort): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) + def _test_create_with_numa_affinity_policy(self, policy): + arglist = [ + '--numa-policy-%s' % policy, + self._port.id, + ] + verifylist = [ + ('numa_policy_%s' % policy, True), + ('port', self._port.id,) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.network.update_port.assert_called_once_with( + self._port, **{'numa_affinity_policy': policy}) + + def test_create_with_numa_affinity_policy_required(self): + self._test_create_with_numa_affinity_policy('required') + + def test_create_with_numa_affinity_policy_preferred(self): + self._test_create_with_numa_affinity_policy('preferred') + + def test_create_with_numa_affinity_policy_legacy(self): + self._test_create_with_numa_affinity_policy('legacy') + class TestShowPort(TestPort): @@ -1705,7 +1845,7 @@ class TestShowPort(TestPort): self._port.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetPort(TestPort): @@ -1923,3 +2063,26 @@ class TestUnsetPort(TestPort): def test_unset_with_all_tag(self): self._test_unset_tags(with_tags=False) + + def test_unset_numa_affinity_policy(self): + _fake_port = network_fakes.FakePort.create_one_port( + {'numa_affinity_policy': 'required'}) + self.network.find_port = mock.Mock(return_value=_fake_port) + arglist = [ + '--numa-policy', + _fake_port.name, + ] + verifylist = [ + ('numa_policy', True), + ('port', _fake_port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'numa_affinity_policy': None, + } + + self.network.update_port.assert_called_once_with(_fake_port, **attrs) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 09b4957c..323c9198 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -184,7 +184,7 @@ class TestCreateRouter(TestRouter): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_ha_options(self, option, ha): arglist = [ @@ -208,7 +208,7 @@ class TestCreateRouter(TestRouter): 'ha': ha, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_ha_option(self): self._test_create_with_ha_options('--ha', True) @@ -237,7 +237,7 @@ class TestCreateRouter(TestRouter): 'distributed': distributed, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_distributed_option(self): self._test_create_with_distributed_options('--distributed', True) @@ -268,7 +268,7 @@ class TestCreateRouter(TestRouter): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self.new_router.name] @@ -301,7 +301,7 @@ class TestCreateRouter(TestRouter): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -494,7 +494,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_no_ha_no_distributed(self): _routers = network_fakes.FakeRouter.create_routers({ @@ -531,7 +531,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_router_list_long_no_az(self): arglist = [ @@ -552,7 +552,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long_no_az, columns) - self.assertListItemEqual(self.data_long_no_az, list(data)) + self.assertItemsEqual(self.data_long_no_az, list(data)) def test_list_name(self): test_name = "fakename" @@ -570,7 +570,7 @@ class TestListRouter(TestRouter): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_enable(self): arglist = [ @@ -587,7 +587,7 @@ class TestListRouter(TestRouter): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_disable(self): arglist = [ @@ -605,7 +605,7 @@ class TestListRouter(TestRouter): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -623,7 +623,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -643,7 +643,7 @@ class TestListRouter(TestRouter): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_agents_no_args(self): arglist = [ @@ -671,7 +671,7 @@ class TestListRouter(TestRouter): self.network.agent_hosted_routers( *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -696,7 +696,7 @@ class TestListRouter(TestRouter): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestRemovePortFromRouter(TestRouter): @@ -1403,7 +1403,7 @@ class TestShowRouter(TestRouter): 'device_id': self._router.id }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_show_no_ha_no_distributed(self): _router = network_fakes.FakeRouter.create_one_router({ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index b4ddcf80..837c9b21 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -88,7 +88,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): self._security_group['name'], ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_security_group_create_all_options(self, sg_mock): sg_mock.return_value = self._security_group @@ -109,7 +109,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): self._security_group['description'], ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) @mock.patch( @@ -255,7 +255,7 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): kwargs = {'search_opts': {'all_tenants': False}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_all_projects(self, sg_mock): sg_mock.return_value = self._security_groups @@ -272,7 +272,7 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): kwargs = {'search_opts': {'all_tenants': True}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) - self.assertListItemEqual(self.data_all_projects, list(data)) + self.assertItemsEqual(self.data_all_projects, list(data)) @mock.patch( @@ -401,4 +401,4 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 7c1d7fb6..fe377785 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -96,7 +96,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name': self._security_group.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -124,7 +124,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._security_group.name] @@ -155,7 +155,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -293,7 +293,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -309,7 +309,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -329,7 +329,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -351,7 +351,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -539,7 +539,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.find_security_group.assert_called_once_with( self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 5720e305..b7e38afb 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -362,6 +362,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): 'Ethertype', 'IP Range', 'Port Range', + 'Direction', 'Remote Security Group', ) expected_columns_no_group = \ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 0a9522b0..01411611 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -870,7 +870,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rules = [_security_group_rule_tcp, _security_group_rule_icmp] - expected_columns_with_group_and_long = ( + expected_columns_with_group = ( 'ID', 'IP Protocol', 'Ethertype', @@ -885,14 +885,15 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'Ethertype', 'IP Range', 'Port Range', + 'Direction', 'Remote Security Group', 'Security Group', ) - expected_data_with_group_and_long = [] + expected_data_with_group = [] expected_data_no_group = [] for _security_group_rule in _security_group_rules: - expected_data_with_group_and_long.append(( + expected_data_with_group.append(( _security_group_rule.id, _security_group_rule.protocol, _security_group_rule.ether_type, @@ -909,6 +910,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), + _security_group_rule.direction, _security_group_rule.remote_group_id, _security_group_rule.security_group_id, )) @@ -935,14 +937,12 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) - def test_list_with_group_and_long(self): + def test_list_with_group(self): self._security_group_rule_tcp.port_range_min = 80 arglist = [ - '--long', self._security_group.id, ] verifylist = [ - ('long', True), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -952,8 +952,8 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.security_group_rules.assert_called_once_with(**{ 'security_group_id': self._security_group.id, }) - self.assertEqual(self.expected_columns_with_group_and_long, columns) - self.assertEqual(self.expected_data_with_group_and_long, list(data)) + self.assertEqual(self.expected_columns_with_group, columns) + self.assertEqual(self.expected_data_with_group, list(data)) def test_list_with_ignored_options(self): self._security_group_rule_tcp.port_range_min = 80 diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 47d0c6b4..1b4bfdad 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -255,7 +255,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. @@ -317,7 +317,7 @@ class TestCreateSubnet(TestSubnet): 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data_subnet_pool, data) + self.assertItemsEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. @@ -390,7 +390,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data_ipv6, data) + self.assertItemsEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. @@ -424,7 +424,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. @@ -458,7 +458,7 @@ class TestCreateSubnet(TestSubnet): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_dns(self, publish_dns=True): arglist = [ @@ -490,7 +490,7 @@ class TestCreateSubnet(TestSubnet): dns_publish_fixed_ip=publish_dns, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_dns(self): self._test_create_with_dns(publish_dns=True) @@ -535,7 +535,7 @@ class TestCreateSubnet(TestSubnet): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -691,7 +691,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_long(self): arglist = [ @@ -706,7 +706,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_subnet_list_ip_version(self): arglist = [ @@ -722,7 +722,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_dhcp(self): arglist = [ @@ -738,7 +738,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_no_dhcp(self): arglist = [ @@ -754,7 +754,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_service_type(self): arglist = [ @@ -769,7 +769,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -787,7 +787,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_service_type_multiple(self): arglist = [ @@ -805,7 +805,7 @@ class TestListSubnet(TestSubnet): 'network:floatingip_agent_gateway']} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -825,7 +825,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_network(self): network = network_fakes.FakeNetwork.create_one_network() @@ -843,7 +843,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -861,7 +861,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -879,7 +879,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -897,7 +897,7 @@ class TestListSubnet(TestSubnet): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1244,7 +1244,7 @@ class TestShowSubnet(TestSubnet): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetSubnet(TestSubnet): diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index eb454646..243fc76d 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -133,7 +133,7 @@ class TestCreateSubnetPool(TestSubnetPool): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_prefixlen_options(self): arglist = [ @@ -163,7 +163,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_len_negative(self): arglist = [ @@ -201,7 +201,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_address_scope_option(self): arglist = [ @@ -224,7 +224,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_default_and_shared_options(self): arglist = [ @@ -250,7 +250,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'shared': True, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_description(self): arglist = [ @@ -273,7 +273,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'description': self._subnet_pool.description, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_default_quota(self): arglist = [ @@ -294,7 +294,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'default_quota': 10, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -328,7 +328,7 @@ class TestCreateSubnetPool(TestSubnetPool): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -476,7 +476,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_long(self): arglist = [ @@ -491,7 +491,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_subnet_pool_list_no_share(self): arglist = [ @@ -507,7 +507,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_share(self): arglist = [ @@ -523,7 +523,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_no_default(self): arglist = [ @@ -539,7 +539,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_default(self): arglist = [ @@ -555,7 +555,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -573,7 +573,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -593,7 +593,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -611,7 +611,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -629,7 +629,7 @@ class TestListSubnetPool(TestSubnetPool): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -654,7 +654,7 @@ class TestListSubnetPool(TestSubnetPool): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestSetSubnetPool(TestSubnetPool): @@ -1008,7 +1008,7 @@ class TestShowSubnetPool(TestSubnetPool): ignore_missing=False ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetSubnetPool(TestSubnetPool): diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index 0ed791a5..1808d5b7 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -14,7 +14,6 @@ # from keystoneauth1 import session -import six from openstackclient.api import object_store_v1 as object_store from openstackclient.tests.unit import utils @@ -68,7 +67,7 @@ OBJECT = { 'last_modified': object_modified_1, } -object_1_content = six.b('object 1 content') +object_1_content = b'object 1 content' OBJECT_2 = { 'name': object_name_2, diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index dd587142..7e88409f 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -12,11 +12,11 @@ # import copy +import io from unittest import mock from osc_lib import exceptions from requests_mock.contrib import fixture -import six from openstackclient.object.v1 import object as object_cmds from openstackclient.tests.unit.object.v1 import fakes as object_fakes @@ -241,9 +241,9 @@ class TestObjectSave(TestObjectAll): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - class FakeStdout(six.BytesIO): + class FakeStdout(io.BytesIO): def __init__(self): - six.BytesIO.__init__(self) + io.BytesIO.__init__(self) self.context_manager_calls = [] def __enter__(self): diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 94f4f44d..366c364e 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -13,12 +13,12 @@ # under the License. # +import importlib import os import sys from unittest import mock from osc_lib.tests import utils as osc_lib_test_utils -from oslo_utils import importutils import wrapt from openstackclient import shell @@ -151,12 +151,13 @@ class TestShell(osc_lib_test_utils.TestShell): super(TestShell, self).setUp() # TODO(dtroyer): remove this once the shell_class_patch patch is # released in osc-lib - self.shell_class = importutils.import_class(self.shell_class_name) + mod_str, _sep, class_str = self.shell_class_name.rpartition('.') + self.shell_class = getattr(importlib.import_module(mod_str), class_str) def _assert_admin_token_auth(self, cmd_options, default_args): with mock.patch( - self.shell_class_name + ".initialize_app", - self.app, + self.shell_class_name + ".initialize_app", + self.app, ): _shell = osc_lib_test_utils.make_shell( shell_class=self.shell_class, diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 4f1bc46a..39cb5614 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -14,11 +14,10 @@ # under the License. # +from io import StringIO import os -from cliff import columns as cliff_columns import fixtures -from six.moves import StringIO import testtools from openstackclient.tests.unit import fakes @@ -85,18 +84,3 @@ class TestCommand(TestCase): self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args - - def assertListItemEqual(self, expected, actual): - self.assertEqual(len(expected), len(actual)) - for item_expected, item_actual in zip(expected, actual): - self.assertItemEqual(item_expected, item_actual) - - def assertItemEqual(self, expected, actual): - self.assertEqual(len(expected), len(actual)) - for col_expected, col_actual in zip(expected, actual): - if isinstance(col_expected, cliff_columns.FormattableColumn): - self.assertIsInstance(col_actual, col_expected.__class__) - self.assertEqual(col_expected.human_readable(), - col_actual.human_readable()) - else: - self.assertEqual(col_expected, col_actual) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 83c533b6..5500438b 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -109,7 +109,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_qos_create_with_consumer(self): arglist = [ @@ -129,7 +129,7 @@ class TestQosCreate(TestQos): {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_qos_create_with_properties(self): arglist = [ @@ -155,7 +155,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -350,7 +350,7 @@ class TestQosList(TestQos): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -377,7 +377,7 @@ class TestQosList(TestQos): format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertListItemEqual(ex_data, list(data)) + self.assertItemsEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -454,7 +454,7 @@ class TestQosShow(TestQos): self.qos_spec.name, format_columns.DictColumn(self.qos_spec.specs), ) - self.assertItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 8bee5747..f1d46914 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -78,7 +78,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_create_with_encryption(self): encryption_info = { @@ -139,7 +139,7 @@ class TestTypeCreate(TestType): body, ) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -270,7 +270,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -284,7 +284,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -328,7 +328,7 @@ class TestTypeList(TestType): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with() self.assertEqual(encryption_columns, columns) - self.assertListItemEqual(encryption_data, list(data)) + self.assertItemsEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -469,7 +469,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -513,7 +513,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeUnset(TestType): diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 25cdf92a..704a66da 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -135,7 +135,7 @@ class TestVolumeCreate(TestVolume): None, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -179,7 +179,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -226,7 +226,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -273,7 +273,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -314,7 +314,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -357,7 +357,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -400,7 +400,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_source(self): self.volumes_mock.get.return_value = self.new_volume @@ -430,7 +430,7 @@ class TestVolumeCreate(TestVolume): None, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -468,7 +468,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -510,7 +510,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -562,7 +562,7 @@ class TestVolumeCreate(TestVolume): self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -765,7 +765,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -782,7 +782,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -799,7 +799,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -816,7 +816,7 @@ class TestVolumeList(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -856,7 +856,7 @@ class TestVolumeList(TestVolume): volume.AttachmentsColumn(self._volume.attachments), format_columns.DictColumn(self._volume.metadata), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_with_limit(self): arglist = [ @@ -881,7 +881,7 @@ class TestVolumeList(TestVolume): 'all_tenants': False, } ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1272,7 +1272,7 @@ class TestVolumeShow(TestVolume): self.volumes_mock.get.assert_called_with(self._volume.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_show_backward_compatibility(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index 20aadcd3..a7131550 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -100,7 +100,7 @@ class TestBackupCreate(TestBackup): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ @@ -124,7 +124,7 @@ class TestBackupCreate(TestBackup): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestBackupDelete(TestBackup): @@ -277,7 +277,7 @@ class TestBackupList(TestBackup): search_opts=search_opts, ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -309,7 +309,7 @@ class TestBackupList(TestBackup): search_opts=search_opts, ) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -391,4 +391,4 @@ class TestBackupShow(TestBackup): self.backups_mock.get.assert_called_with(self.backup.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index c3bd71e3..6bb6c029 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -251,7 +251,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -279,7 +279,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -307,7 +307,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -463,7 +463,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -480,7 +480,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -497,7 +497,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -705,4 +705,4 @@ class TestConsistencyGroupShow(TestConsistencyGroup): self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 073ec570..bc4cee8b 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -112,7 +112,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -133,7 +133,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -159,7 +159,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestQosDelete(TestQos): @@ -342,7 +342,7 @@ class TestQosList(TestQos): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -369,7 +369,7 @@ class TestQosList(TestQos): format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertListItemEqual(ex_data, list(data)) + self.assertItemsEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -449,7 +449,7 @@ class TestQosShow(TestQos): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, tuple(data)) + self.assertItemsEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index f13d0851..000464c5 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -93,7 +93,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -119,7 +119,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -196,7 +196,7 @@ class TestTypeCreate(TestType): body, ) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -330,7 +330,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -348,7 +348,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -365,7 +365,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=False) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -383,7 +383,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data_with_default_type, list(data)) + self.assertItemsEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -427,7 +427,7 @@ class TestTypeList(TestType): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(encryption_columns, columns) - self.assertListItemEqual(encryption_data, list(data)) + self.assertItemsEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -713,7 +713,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -746,7 +746,7 @@ class TestTypeShow(TestType): private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemEqual(private_type_data, data) + self.assertItemsEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -778,7 +778,7 @@ class TestTypeShow(TestType): private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemEqual(private_type_data, data) + self.assertItemsEqual(private_type_data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -824,7 +824,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeUnset(TestType): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 4e204ad1..b9fe4e83 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -136,7 +136,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -182,7 +182,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -218,7 +218,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -256,7 +256,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -294,7 +294,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -331,7 +331,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -369,7 +369,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -411,7 +411,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -463,7 +463,7 @@ class TestVolumeCreate(TestVolume): self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -680,7 +680,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -720,7 +720,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -762,7 +762,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -801,7 +801,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -843,7 +843,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -883,7 +883,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -923,7 +923,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -963,7 +963,7 @@ class TestVolumeList(TestVolume): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1017,7 +1017,7 @@ class TestVolumeList(TestVolume): volume.AttachmentsColumn(self.mock_volume.attachments), format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1056,7 +1056,7 @@ class TestVolumeList(TestVolume): 'name': None, 'all_tenants': False, } ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1450,7 +1450,7 @@ class TestVolumeShow(TestVolume): volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertItemEqual( + self.assertItemsEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py index db188660..d9ac2c96 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backend.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py @@ -65,7 +65,7 @@ class TestShowVolumeCapability(volume_fakes.TestVolume): # confirming if all expected values are present in the result. for cap in data: - self.assertTrue(cap[0] in capabilities) + self.assertIn(cap[0], capabilities) # checking if proper call was made to get capabilities self.capability_mock.get.assert_called_with( diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 4e1f7ee1..13513ed8 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -314,7 +314,7 @@ class TestBackupList(TestBackup): limit=None, ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +353,7 @@ class TestBackupList(TestBackup): limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 1fbfaaee..64e8b9f3 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -29,6 +29,7 @@ API_VERSIONS = { "1": "cinderclient.v1.client.Client", "2": "cinderclient.v2.client.Client", "3": "cinderclient.v3.client.Client", + "3.42": "cinderclient.v3.client.Client", } @@ -47,14 +48,19 @@ def make_client(instance): except Exception: del API_VERSIONS['1'] - if instance._api_version[API_NAME] == '1': + version = instance._api_version[API_NAME] + from cinderclient import api_versions + # convert to APIVersion object + version = api_versions.get_api_version(version) + + if version.ver_major == '1': # Monkey patch for v1 cinderclient volumes.Volume.NAME_ATTR = 'display_name' volume_snapshots.Snapshot.NAME_ATTR = 'display_name' volume_client = utils.get_client_class( API_NAME, - instance._api_version[API_NAME], + version.ver_major, API_VERSIONS ) LOG.debug('Instantiating volume client: %s', volume_client) @@ -76,6 +82,7 @@ def make_client(instance): http_log_debug=http_log_debug, region_name=instance.region_name, endpoint_override=endpoint_override, + api_version=version, **kwargs ) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 1e0cb183..cab0b2f4 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -605,14 +605,16 @@ class SetVolume(command.Command): result = 0 if parsed_args.size: try: - if volume.status != 'available': - msg = (_("Volume is in %s state, it must be available " - "before size can be extended") % volume.status) - raise exceptions.CommandError(msg) if parsed_args.size <= volume.size: msg = (_("New size must be greater than %s GB") % volume.size) raise exceptions.CommandError(msg) + if volume.status != 'available' and \ + not volume_client.api_version.matches('3.42'): + + msg = (_("Volume is in %s state, it must be available " + "before size can be extended") % volume.status) + raise exceptions.CommandError(msg) volume_client.volumes.extend(volume.id, parsed_args.size) except Exception as e: LOG.error(_("Failed to set volume size: %s"), e) |
