diff options
Diffstat (limited to 'openstackclient')
91 files changed, 5308 insertions, 1016 deletions
diff --git a/openstackclient/__init__.py b/openstackclient/__init__.py index 89deee32..e6e7f7c0 100644 --- a/openstackclient/__init__.py +++ b/openstackclient/__init__.py @@ -11,10 +11,10 @@ # under the License. # -__all__ = ['__version__'] - import pbr.version +__all__ = ['__version__'] + version_info = pbr.version.VersionInfo('python-openstackclient') try: __version__ = version_info.version_string() diff --git a/openstackclient/api/image_v2.py b/openstackclient/api/image_v2.py index c3628121..d0163189 100644 --- a/openstackclient/api/image_v2.py +++ b/openstackclient/api/image_v2.py @@ -31,6 +31,7 @@ class APIv2(image_v1.APIv1): detailed=False, public=False, private=False, + community=False, shared=False, **filter ): @@ -44,25 +45,29 @@ class APIv2(image_v1.APIv1): Return public images if True :param private: Return private images if True + :param community: + Return commuity images if True :param shared: Return shared images if True - If public, private and shared are all True or all False then all - images are returned. All arguments False is equivalent to no filter - and all images are returned. All arguments True is a filter that - includes all public, private and shared images which is the same set - as all images. + If public, private, community and shared are all True or all False + then all images are returned. All arguments False is equivalent to no + filter and all images are returned. All arguments True is a filter + that includes all public, private, community and shared images which + is the same set as all images. http://docs.openstack.org/api/openstack-image-service/2.0/content/list-images.html """ - if not public and not private and not shared: + if not public and not private and not community and not shared: # No filtering for all False filter.pop('visibility', None) elif public: filter['visibility'] = 'public' elif private: filter['visibility'] = 'private' + elif community: + filter['visibility'] = 'community' elif shared: filter['visibility'] = 'shared' diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 89781052..aa1045e4 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -125,6 +125,25 @@ class ClientManager(clientmanager.ClientManager): # use Network API by default return self.is_service_available('network') is not False + def is_compute_endpoint_enabled(self): + """Check if Compute endpoint is enabled""" + + return self.is_service_available('compute') is not False + + def is_volume_endpoint_enabled(self, volume_client): + """Check if volume endpoint is enabled""" + # NOTE(jcross): Cinder did some interesting things with their service + # name so we need to figure out which version to look + # for when calling is_service_available() + volume_version = volume_client.api_version.ver_major + if self.is_service_available( + "volumev%s" % volume_version) is not False: + return True + elif self.is_service_available('volume') is not False: + return True + else: + return False + # Plugin Support diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 139f43ab..71206618 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -75,8 +75,10 @@ class ListExtension(command.Lister): # by default we want to show everything, unless the # user specifies one or more of the APIs to show # for now, only identity and compute are supported. - show_all = (not parsed_args.identity and not parsed_args.compute - and not parsed_args.volume and not parsed_args.network) + show_all = (not parsed_args.identity and + not parsed_args.compute and + not parsed_args.volume and + not parsed_args.network) if parsed_args.identity or show_all: identity_client = self.app.client_manager.identity diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 957f1d02..19db35d7 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -83,24 +83,34 @@ class ShowLimits(command.Lister): project_id = utils.find_resource(identity_client.projects, parsed_args.project).id - compute_limits = compute_client.limits.get(parsed_args.is_reserved, - tenant_id=project_id) - volume_limits = volume_client.limits.get() + compute_limits = None + volume_limits = None + if self.app.client_manager.is_compute_endpoint_enabled(): + compute_limits = compute_client.limits.get(parsed_args.is_reserved, + tenant_id=project_id) + + if self.app.client_manager.is_volume_endpoint_enabled(volume_client): + volume_limits = volume_client.limits.get() + + data = [] if parsed_args.is_absolute: - compute_limits = compute_limits.absolute - volume_limits = volume_limits.absolute + if compute_limits: + data.append(compute_limits.absolute) + if volume_limits: + data.append(volume_limits.absolute) columns = ["Name", "Value"] return (columns, (utils.get_item_properties(s, columns) - for s in itertools.chain(compute_limits, volume_limits))) + for s in itertools.chain(*data))) elif parsed_args.is_rate: - compute_limits = compute_limits.rate - volume_limits = volume_limits.rate + if compute_limits: + data.append(compute_limits.rate) + if volume_limits: + data.append(volume_limits.rate) columns = ["Verb", "URI", "Value", "Remain", "Unit", "Next Available"] return (columns, (utils.get_item_properties(s, columns) - for s in itertools.chain(compute_limits, volume_limits))) - + for s in itertools.chain(*data))) else: - return ({}, {}) + return {}, {} diff --git a/openstackclient/common/project_purge.py b/openstackclient/common/project_purge.py index 5b1d0072..76ed4563 100644 --- a/openstackclient/common/project_purge.py +++ b/openstackclient/common/project_purge.py @@ -85,7 +85,7 @@ class ProjectPurge(command.Command): # servers try: compute_client = self.app.client_manager.compute - search_opts = {'tenant_id': project_id} + search_opts = {'tenant_id': project_id, 'all_tenants': True} data = compute_client.servers.list(search_opts=search_opts) self.delete_objects( compute_client.servers.delete, data, 'server', dry_run) @@ -110,7 +110,7 @@ class ProjectPurge(command.Command): # volumes, snapshots, backups volume_client = self.app.client_manager.volume - search_opts = {'project_id': project_id} + search_opts = {'project_id': project_id, 'all_tenants': True} try: data = volume_client.volume_snapshots.list(search_opts=search_opts) self.delete_objects( diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py new file mode 100644 index 00000000..6a93d300 --- /dev/null +++ b/openstackclient/common/versions.py @@ -0,0 +1,114 @@ +# Copyright 2018 Red Hat, Inc. +# +# 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. + +"""Versions Action Implementation""" + +import os_service_types +from osc_lib.command import command + +from openstackclient.i18n import _ + + +class ShowVersions(command.Lister): + _description = _("Show available versions of services") + + def get_parser(self, prog_name): + parser = super(ShowVersions, self).get_parser(prog_name) + interface_group = parser.add_mutually_exclusive_group() + interface_group.add_argument( + "--all-interfaces", + dest="is_all_interfaces", + action="store_true", + default=False, + help=_("Show values for all interfaces"), + ) + interface_group.add_argument( + '--interface', + default='public', + metavar='<interface>', + help=_('Show versions for a specific interface.'), + ) + parser.add_argument( + '--region-name', + metavar='<region_name>', + help=_('Show versions for a specific region.'), + ) + parser.add_argument( + '--service', + metavar='<region_name>', + help=_('Show versions for a specific service.'), + ) + parser.add_argument( + '--status', + metavar='<region_name>', + help=_('Show versions for a specific status.' + ' [Valid values are SUPPORTED, CURRENT,' + ' DEPRECATED, EXPERIMENTAL]'), + ) + return parser + + def take_action(self, parsed_args): + + interface = parsed_args.interface + if parsed_args.is_all_interfaces: + interface = None + + session = self.app.client_manager.session + version_data = session.get_all_version_data( + interface=interface, + region_name=parsed_args.region_name) + + columns = [ + "Region Name", + "Service Type", + "Version", + "Status", + "Endpoint", + "Min Microversion", + "Max Microversion", + ] + + status = parsed_args.status + if status: + status = status.upper() + + service = parsed_args.service + if service: + # Normalize service type argument to official type + service_type_manager = os_service_types.ServiceTypes() + service = service_type_manager.get_service_type(service) + + versions = [] + for region_name, interfaces in version_data.items(): + for interface, services in interfaces.items(): + for service_type, service_versions in services.items(): + if service and service != service_type: + # TODO(mordred) Once there is a version of + # keystoneauth that can do this filtering + # before making all the discovery calls, switch + # to that. + continue + for data in service_versions: + if status and status != data['status']: + continue + versions.append(( + region_name, + service_type, + data['version'], + data['status'], + data['url'], + data['min_microversion'], + data['max_microversion'], + )) + return (columns, versions) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 25f92108..b2f7288f 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -15,8 +15,6 @@ """Compute v2 Console action implementations""" -import sys - from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils @@ -60,7 +58,10 @@ class ShowConsoleLog(command.Command): length += 1 data = server.get_console_output(length=length) - sys.stdout.write(data) + + if data and data[-1] != '\n': + data += '\n' + self.app.stdout.write(data) class ShowConsoleURL(command.ShowOne): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 0f5dd742..2cc5f1e8 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -17,6 +17,7 @@ import logging +from novaclient import api_versions from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -134,6 +135,12 @@ class CreateFlavor(command.ShowOne): help=_("Allow <project> to access private flavor (name or ID) " "(Must be used with --private option)"), ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Description for the flavor.(Supported by API versions " + "'2.55' - '2.latest'") + ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -145,6 +152,11 @@ class CreateFlavor(command.ShowOne): msg = _("--project is only allowed with --private") raise exceptions.CommandError(msg) + 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) + args = ( parsed_args.name, parsed_args.ram, @@ -154,7 +166,8 @@ class CreateFlavor(command.ShowOne): parsed_args.ephemeral, parsed_args.swap, parsed_args.rxtx_factor, - parsed_args.public + parsed_args.public, + parsed_args.description ) flavor = compute_client.flavors.create(*args) @@ -332,6 +345,12 @@ class SetFlavor(command.Command): help=_('Set flavor access to project (name or ID) ' '(admin only)'), ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Set description for the flavor.(Supported by API " + "versions '2.55' - '2.latest'") + ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -380,6 +399,13 @@ 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") diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 9fdfd927..07c92a8c 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -97,7 +97,7 @@ class SetHost(command.Command): compute_client.api.host_set( parsed_args.host, - kwargs + **kwargs ) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3341fbfe..4f672428 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -20,7 +20,6 @@ import getpass import io import logging import os -import sys from novaclient import api_versions from novaclient.v2 import servers @@ -122,17 +121,21 @@ def _prefix_checked_value(prefix): return func -def _prep_server_detail(compute_client, image_client, server): +def _prep_server_detail(compute_client, image_client, server, refresh=True): """Prepare the detailed server dict for printing :param compute_client: a compute client instance + :param image_client: an image client instance :param server: a Server resource + :param refresh: Flag indicating if ``server`` is already the latest version + or if it needs to be refreshed, for example when showing + the latest details of a server after creating it. :rtype: a dict of server details """ - info = server._info.copy() - - server = utils.find_resource(compute_client.servers, info['id']) - info.update(server._info) + info = server.to_dict() + if refresh: + server = utils.find_resource(compute_client.servers, info['id']) + info.update(server.to_dict()) # Convert the image blob to a name image_info = info.get('image', {}) @@ -146,12 +149,18 @@ def _prep_server_detail(compute_client, image_client, server): # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) - flavor_id = flavor_info.get('id', '') - try: - flavor = utils.find_resource(compute_client.flavors, flavor_id) - info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) - except Exception: - info['flavor'] = flavor_id + # Microversion 2.47 puts the embedded flavor into the server response + # body but omits the id, so if not present we just expose the flavor + # dict in the server output. + if 'id' in flavor_info: + flavor_id = flavor_info.get('id', '') + try: + flavor = utils.find_resource(compute_client.flavors, flavor_id) + info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + except Exception: + info['flavor'] = flavor_id + else: + info['flavor'] = utils.format_dict(flavor_info) if 'os-extended-volumes:volumes_attached' in info: info.update( @@ -180,7 +189,7 @@ def _prep_server_detail(compute_client, image_client, server): if 'tenant_id' in info: info['project_id'] = info.pop('tenant_id') - # Map power state num to meanful string + # Map power state num to meaningful string if 'OS-EXT-STS:power_state' in info: info['OS-EXT-STS:power_state'] = _format_servers_list_power_state( info['OS-EXT-STS:power_state']) @@ -191,12 +200,6 @@ def _prep_server_detail(compute_client, image_client, server): return info -def _show_progress(progress): - if progress: - sys.stdout.write('\rProgress: %s' % progress) - sys.stdout.flush() - - class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") @@ -444,6 +447,12 @@ class CreateServer(command.ShowOne): help=_('Create server boot disk from this image (name or ID)'), ) disk_group.add_argument( + '--image-property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help=_("Image property to be matched"), + ) + disk_group.add_argument( '--volume', metavar='<volume>', help=_('Create server using this volume as the boot disk (name ' @@ -599,6 +608,12 @@ class CreateServer(command.ShowOne): 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 volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image @@ -611,6 +626,45 @@ class CreateServer(command.ShowOne): parsed_args.image, ) + 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]}) + + def _match_image(image_api, wanted_properties): + image_list = image_api.image_list() + images_matched = [] + for img in image_list: + img_dict = {} + # exclude any unhashable entries + for key, value in img.items(): + try: + set([key, value]) + except TypeError: + pass + else: + img_dict[key] = value + 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.api, parsed_args.image_property) + if len(images) > 1: + 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")) + # Lookup parsed_args.volume volume = None if parsed_args.volume: @@ -838,11 +892,11 @@ class CreateServer(command.ShowOne): server.id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: LOG.error(_('Error creating server: %s'), parsed_args.server_name) - sys.stdout.write(_('Error creating server\n')) + self.app.stdout.write(_('Error creating server\n')) raise SystemExit details = _prep_server_detail(compute_client, image_client, server) @@ -896,6 +950,12 @@ class DeleteServer(command.Command): 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 for server in parsed_args.server: server_obj = utils.find_resource( @@ -907,11 +967,11 @@ class DeleteServer(command.Command): server_obj.id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: LOG.error(_('Error deleting server: %s'), server_obj.id) - sys.stdout.write(_('Error deleting server\n')) + self.app.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -1172,7 +1232,7 @@ class ListServer(command.Lister): # Create a dict that maps image_id to image object. # Needed so that we can display the "Image Name" column. # "Image Name" is not crucial, so we swallow any exceptions. - if not parsed_args.no_name_lookup: + if data and not parsed_args.no_name_lookup: try: images_list = self.app.client_manager.image.images.list() for i in images_list: @@ -1184,9 +1244,9 @@ class ListServer(command.Lister): # Create a dict that maps flavor_id to flavor object. # Needed so that we can display the "Flavor Name" column. # "Flavor Name" is not crucial, so we swallow any exceptions. - if not parsed_args.no_name_lookup: + if data and not parsed_args.no_name_lookup: try: - flavors_list = compute_client.flavors.list() + flavors_list = compute_client.flavors.list(is_public=None) for i in flavors_list: flavors[i.id] = i except Exception: @@ -1209,6 +1269,10 @@ class ListServer(command.Lister): s.flavor_name = flavor.name s.flavor_id = s.flavor['id'] else: + # TODO(mriedem): Fix this for microversion >= 2.47 where the + # flavor is embedded in the server response without the id. + # We likely need to drop the Flavor ID column in that case if + # --long is specified. s.flavor_name = '' s.flavor_id = '' @@ -1314,6 +1378,11 @@ class MigrateServer(command.Command): 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 server = utils.find_resource( @@ -1321,11 +1390,13 @@ class MigrateServer(command.Command): parsed_args.server, ) if parsed_args.live: - server.live_migrate( - host=parsed_args.live, - block_migration=parsed_args.block_migration, - disk_over_commit=parsed_args.disk_overcommit, - ) + kwargs = { + 'host': parsed_args.live, + 'block_migration': parsed_args.block_migration + } + if compute_client.api_version < api_versions.APIVersion('2.25'): + kwargs['disk_over_commit'] = parsed_args.disk_overcommit + server.live_migrate(**kwargs) else: if parsed_args.block_migration or parsed_args.disk_overcommit: raise exceptions.CommandError("--live must be specified if " @@ -1337,13 +1408,14 @@ class MigrateServer(command.Command): if utils.wait_for_status( compute_client.servers.get, server.id, + success_status=['active', 'verify_resize'], callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error migrating server: %s'), server.id) - sys.stdout.write(_('Error migrating server\n')) + self.app.stdout.write(_('Error migrating server\n')) raise SystemExit @@ -1404,6 +1476,12 @@ class RebootServer(command.Command): 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 server = utils.find_resource( compute_client.servers, parsed_args.server) @@ -1415,11 +1493,11 @@ class RebootServer(command.Command): server.id, callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error rebooting server: %s'), server.id) - sys.stdout.write(_('Error rebooting server\n')) + self.app.stdout.write(_('Error rebooting server\n')) raise SystemExit @@ -1445,6 +1523,13 @@ class RebuildServer(command.ShowOne): help=_("Set the password on the rebuilt instance"), ) parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help=_('Set a property on the rebuilt instance ' + '(repeat option to set multiple values)'), + ) + parser.add_argument( '--wait', action='store_true', help=_('Wait for rebuild to complete'), @@ -1452,6 +1537,12 @@ class RebuildServer(command.ShowOne): 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 @@ -1459,24 +1550,30 @@ class RebuildServer(command.ShowOne): compute_client.servers, parsed_args.server) # If parsed_args.image is not set, default to the currently used one. - image_id = parsed_args.image or server._info.get('image', {}).get('id') + image_id = parsed_args.image or server.to_dict().get( + 'image', {}).get('id') image = utils.find_resource(image_client.images, image_id) - server = server.rebuild(image, parsed_args.password) + kwargs = {} + if parsed_args.property: + kwargs['meta'] = parsed_args.property + + server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: if utils.wait_for_status( compute_client.servers.get, server.id, callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error rebuilding server: %s'), server.id) - sys.stdout.write(_('Error rebuilding server\n')) + self.app.stdout.write(_('Error rebuilding server\n')) raise SystemExit - details = _prep_server_detail(compute_client, image_client, server) + details = _prep_server_detail(compute_client, image_client, server, + refresh=False) return zip(*sorted(six.iteritems(details))) @@ -1758,6 +1855,11 @@ the new server and restart the old one.""") 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 server = utils.find_resource( compute_client.servers, @@ -1776,11 +1878,11 @@ the new server and restart the old one.""") success_status=['active', 'verify_resize'], callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error resizing server: %s'), server.id) - sys.stdout.write(_('Error resizing server\n')) + self.app.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: compute_client.servers.confirm_resize(server) @@ -1921,7 +2023,9 @@ class ShelveServer(command.Command): class ShowServer(command.ShowOne): - _description = _("Show server details") + _description = _( + "Show server details. Specify ``--os-compute-api-version 2.47`` " + "or higher to see the embedded flavor information for the server.") def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) @@ -1946,11 +2050,14 @@ class ShowServer(command.ShowOne): if parsed_args.diagnostics: (resp, data) = server.diagnostics() if not resp.status_code == 200: - sys.stderr.write(_("Error retrieving diagnostics data\n")) + self.app.stderr.write(_( + "Error retrieving diagnostics data\n" + )) return ({}, {}) else: data = _prep_server_detail(compute_client, - self.app.client_manager.image, server) + self.app.client_manager.image, server, + refresh=False) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index ddcf9101..a79f5f70 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -15,8 +15,6 @@ """Compute v2 Server action implementations""" -import sys - from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,12 +24,6 @@ import six from openstackclient.i18n import _ -def _show_progress(progress): - if progress: - sys.stderr.write('\rProgress: %s' % progress) - sys.stderr.flush() - - class CreateServerBackup(command.ShowOne): _description = _("Create a server backup image") @@ -74,6 +66,12 @@ class CreateServerBackup(command.ShowOne): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stderr.write('\rProgress: %s' % progress) + self.app.stderr.flush() + compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -114,7 +112,7 @@ class CreateServerBackup(command.ShowOne): image.id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: msg = _('Error creating server backup: %s') % parsed_args.name raise exceptions.CommandError(msg) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index c66e0674..3bc5d94a 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -16,7 +16,6 @@ """Compute v2 Server action implementations""" import logging -import sys from osc_lib.command import command from osc_lib import exceptions @@ -30,12 +29,6 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -def _show_progress(progress): - if progress: - sys.stdout.write('\rProgress: %s' % progress) - sys.stdout.flush() - - class CreateServerImage(command.ShowOne): _description = _("Create a new server disk image from an existing server") @@ -64,6 +57,12 @@ class CreateServerImage(command.ShowOne): 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 server = utils.find_resource( @@ -92,7 +91,7 @@ class CreateServerImage(command.ShowOne): image_id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: LOG.error(_('Error creating server image: %s'), parsed_args.server) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 7331d29d..18e6d9d9 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.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 @@ -192,18 +193,23 @@ class SetService(command.Command): result += 1 force_down = None - try: - if parsed_args.down: - force_down = True - if parsed_args.up: - force_down = False - if force_down is not None: + if parsed_args.down: + force_down = True + if parsed_args.up: + force_down = False + if force_down is not None: + if compute_client.api_version < api_versions.APIVersion( + '2.11'): + msg = _('--os-compute-api-version 2.11 or later is ' + 'required') + raise exceptions.CommandError(msg) + try: cs.force_down(parsed_args.host, parsed_args.service, force_down=force_down) - except Exception: - state = "down" if force_down else "up" - LOG.error("Failed to set service state to %s", state) - result += 1 + except Exception: + state = "down" if force_down else "up" + LOG.error("Failed to set service state to %s", state) + result += 1 if result > 0: msg = _("Compute service %(service)s of host %(host)s failed to " diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 3edcffe4..4320bf90 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -16,7 +16,6 @@ """Usage action implementations""" import datetime -import sys from osc_lib.command import command from osc_lib import utils @@ -96,7 +95,7 @@ class ListUsage(command.Lister): pass if parsed_args.formatter == 'table' and len(usage_list) > 0: - sys.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { + self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { "start": start.strftime(dateformat), "end": end.strftime(dateformat), }) @@ -168,8 +167,9 @@ class ShowUsage(command.ShowOne): usage = compute_client.usage.get(project, start, end) if parsed_args.formatter == 'table': - sys.stdout.write(_("Usage from %(start)s to %(end)s on " - "project %(project)s: \n") % { + self.app.stdout.write(_( + "Usage from %(start)s to %(end)s on project %(project)s: \n" + ) % { "start": start.strftime(dateformat), "end": end.strftime(dateformat), "project": project, diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 3229240e..858b5036 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -199,7 +199,7 @@ class ListEndpoint(command.Lister): metavar='<project>', help=_('Project to list filters (name or ID)'), ) - common.add_project_domain_option_to_parser(list_group) + common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 39c8547c..02eeadd6 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -16,7 +16,6 @@ """Group action implementations""" import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -122,7 +121,7 @@ class CheckUserInGroup(command.Command): 'user': parsed_args.user, 'group': parsed_args.group, } - sys.stderr.write(msg) + self.app.stderr.write(msg) else: raise e else: @@ -130,7 +129,7 @@ class CheckUserInGroup(command.Command): 'user': parsed_args.user, 'group': parsed_args.group, } - sys.stdout.write(msg) + self.app.stdout.write(msg) class CreateGroup(command.ShowOne): diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py index c7623389..4e3df88a 100644 --- a/openstackclient/identity/v3/implied_role.py +++ b/openstackclient/identity/v3/implied_role.py @@ -71,7 +71,7 @@ class CreateImpliedRole(command.ShowOne): identity_client = self.app.client_manager.identity (prior_role_id, implied_role_id) = _get_role_ids( identity_client, parsed_args) - response = identity_client.roles.create_implied( + response = identity_client.inference_rules.create( prior_role_id, implied_role_id) response._info.pop('links', None) return zip(*sorted([(k, v['id']) @@ -101,7 +101,7 @@ class DeleteImpliedRole(command.Command): identity_client = self.app.client_manager.identity (prior_role_id, implied_role_id) = _get_role_ids( identity_client, parsed_args) - identity_client.roles.delete_implied( + identity_client.inference_rules.delete( prior_role_id, implied_role_id) @@ -125,5 +125,5 @@ class ListImpliedRole(command.Lister): implies['name']) identity_client = self.app.client_manager.identity - response = identity_client.roles.list_inference_roles() + response = identity_client.inference_rules.list_inference_roles() return (self._COLUMNS, _list_implied(response)) diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py new file mode 100644 index 00000000..c6f1cb1f --- /dev/null +++ b/openstackclient/identity/v3/limit.py @@ -0,0 +1,238 @@ +# 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. +# + +"""Limits action implementations.""" + +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 as common_utils + +LOG = logging.getLogger(__name__) + + +class CreateLimit(command.ShowOne): + _description = _("Create a limit") + + def get_parser(self, prog_name): + parser = super(CreateLimit, self).get_parser(prog_name) + parser.add_argument( + '--description', + metavar='<description>', + help=_('Description of the limit'), + ) + parser.add_argument( + '--region', + metavar='<region>', + help=_('Region for the limit to affect.'), + ) + parser.add_argument( + '--project', + metavar='<project>', + required=True, + help=_('Project to associate the resource limit to'), + ) + parser.add_argument( + '--service', + metavar='<service>', + required=True, + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-limit', + metavar='<resource-limit>', + required=True, + type=int, + help=_('The resource limit for the project to assume'), + ) + parser.add_argument( + 'resource_name', + metavar='<resource-name>', + help=_('The name of the resource to limit'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + project = common_utils.find_project( + identity_client, parsed_args.project + ) + service = common_utils.find_service( + identity_client, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + limit = identity_client.limits.create( + project, + service, + parsed_args.resource_name, + parsed_args.resource_limit, + description=parsed_args.description, + region=region + ) + + limit._info.pop('links', None) + return zip(*sorted(six.iteritems(limit._info))) + + +class ListLimit(command.Lister): + _description = _("List limits") + + def get_parser(self, prog_name): + parser = super(ListLimit, self).get_parser(prog_name) + parser.add_argument( + '--service', + metavar='<service>', + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-name', + metavar='<resource-name>', + dest='resource_name', + help=_('The name of the resource to limit'), + ) + parser.add_argument( + '--region', + metavar='<region>', + help=_('Region for the registered limit to affect.'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = None + if parsed_args.service: + service = common_utils.find_service( + identity_client, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + limits = identity_client.limits.list( + service=service, + resource_name=parsed_args.resource_name, + region=region + ) + + columns = ( + 'ID', 'Project ID', 'Service ID', 'Resource Name', + 'Resource Limit', 'Description', 'Region ID' + ) + return ( + columns, + (utils.get_item_properties(s, columns) for s in limits), + ) + + +class ShowLimit(command.ShowOne): + _description = _("Display limit details") + + def get_parser(self, prog_name): + parser = super(ShowLimit, self).get_parser(prog_name) + parser.add_argument( + 'limit_id', + metavar='<limit-id>', + help=_('Limit to display (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + limit = identity_client.limits.get(parsed_args.limit_id) + limit._info.pop('links', None) + return zip(*sorted(six.iteritems(limit._info))) + + +class SetLimit(command.ShowOne): + _description = _("Update information about a limit") + + def get_parser(self, prog_name): + parser = super(SetLimit, self).get_parser(prog_name) + parser.add_argument( + 'limit_id', + metavar='<limit-id>', + help=_('Limit to update (ID)'), + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_('Description of the limit'), + ) + parser.add_argument( + '--resource-limit', + metavar='<resource-limit>', + dest='resource_limit', + type=int, + help=_('The resource limit for the project to assume'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + limit = identity_client.limits.update( + parsed_args.limit_id, + description=parsed_args.description, + resource_limit=parsed_args.resource_limit + ) + + limit._info.pop('links', None) + + return zip(*sorted(six.iteritems(limit._info))) + + +class DeleteLimit(command.Command): + _description = _("Delete a limit") + + def get_parser(self, prog_name): + parser = super(DeleteLimit, self).get_parser(prog_name) + parser.add_argument( + 'limit_id', + metavar='<limit-id>', + nargs="+", + help=_('Limit to delete (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for limit_id in parsed_args.limit_id: + try: + identity_client.limits.delete(limit_id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete limit with ID " + "'%(id)s': %(e)s"), + {'id': limit_id, 'e': e}) + + if errors > 0: + total = len(parsed_args.limit_id) + msg = (_("%(errors)s of %(total)s limits failed to " + "delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py new file mode 100644 index 00000000..72e07297 --- /dev/null +++ b/openstackclient/identity/v3/registered_limit.py @@ -0,0 +1,260 @@ +# 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. +# + +"""Registered limits action implementations.""" + +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 as common_utils + +LOG = logging.getLogger(__name__) + + +class CreateRegisteredLimit(command.ShowOne): + _description = _("Create a registered limit") + + def get_parser(self, prog_name): + parser = super(CreateRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + '--description', + metavar='<description>', + help=_('Description of the registered limit'), + ) + parser.add_argument( + '--region', + metavar='<region>', + help=_('Region for the registered limit to affect'), + ) + parser.add_argument( + '--service', + metavar='<service>', + required=True, + help=_('Service responsible for the resource to limit (required)'), + ) + parser.add_argument( + '--default-limit', + type=int, + metavar='<default-limit>', + required=True, + help=_('The default limit for the resources to assume (required)'), + ) + parser.add_argument( + 'resource_name', + metavar='<resource-name>', + help=_('The name of the resource to limit'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = utils.find_resource( + identity_client.services, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + registered_limit = identity_client.registered_limits.create( + service, + parsed_args.resource_name, + parsed_args.default_limit, + description=parsed_args.description, + region=region + ) + + registered_limit._info.pop('links', None) + return zip(*sorted(six.iteritems(registered_limit._info))) + + +class DeleteRegisteredLimit(command.Command): + _description = _("Delete a registered limit") + + def get_parser(self, prog_name): + parser = super(DeleteRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + 'registered_limit_id', + metavar='<registered-limit-id>', + nargs="+", + help=_('Registered limit to delete (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for registered_limit_id in parsed_args.registered_limit_id: + try: + identity_client.registered_limits.delete(registered_limit_id) + except Exception as e: + errors += 1 + from pprint import pprint + pprint(type(e)) + LOG.error(_("Failed to delete registered limit with ID " + "'%(id)s': %(e)s"), + {'id': registered_limit_id, 'e': e}) + + if errors > 0: + total = len(parsed_args.registered_limit_id) + msg = (_("%(errors)s of %(total)s registered limits failed to " + "delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListRegisteredLimit(command.Lister): + _description = _("List registered limits") + + def get_parser(self, prog_name): + parser = super(ListRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + '--service', + metavar='<service>', + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-name', + metavar='<resource-name>', + dest='resource_name', + help=_('The name of the resource to limit'), + ) + parser.add_argument( + '--region', + metavar='<region>', + help=_('Region for the limit to affect.'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = None + if parsed_args.service: + service = common_utils.find_service( + identity_client, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + registered_limits = identity_client.registered_limits.list( + service=service, + resource_name=parsed_args.resource_name, + region=region + ) + + columns = ( + 'ID', 'Service ID', 'Resource Name', 'Default Limit', + 'Description', 'Region ID' + ) + return ( + columns, + (utils.get_item_properties(s, columns) for s in registered_limits), + ) + + +class SetRegisteredLimit(command.ShowOne): + _description = _("Update information about a registered limit") + + def get_parser(self, prog_name): + parser = super(SetRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + 'registered_limit_id', + metavar='<registered-limit-id>', + help=_('Registered limit to update (ID)'), + ) + parser.add_argument( + '--service', + metavar='<service>', + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-name', + metavar='<resource-name>', + help=_('The name of the resource to limit'), + ) + parser.add_argument( + '--default-limit', + metavar='<default-limit>', + type=int, + help=_('The default limit for the resources to assume'), + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_('Description of the registered limit'), + ) + parser.add_argument( + '--region', + metavar='<region>', + help=_('Region for the registered limit to affect.'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = None + if parsed_args.service: + service = common_utils.find_service( + identity_client, parsed_args.service + ) + + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + registered_limit = identity_client.registered_limits.update( + parsed_args.registered_limit_id, + service=service, + resource_name=parsed_args.resource_name, + default_limit=parsed_args.default_limit, + description=parsed_args.description, + region=region + ) + + registered_limit._info.pop('links', None) + return zip(*sorted(six.iteritems(registered_limit._info))) + + +class ShowRegisteredLimit(command.ShowOne): + _description = _("Display registered limit details") + + def get_parser(self, prog_name): + parser = super(ShowRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + 'registered_limit_id', + metavar='<registered-limit-id>', + help=_('Registered limit to display (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + registered_limit = identity_client.registered_limits.get( + parsed_args.registered_limit_id + ) + registered_limit._info.pop('links', None) + return zip(*sorted(six.iteritems(registered_limit._info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 1bbf5f07..58a76f8a 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -31,13 +31,18 @@ LOG = logging.getLogger(__name__) def _add_identity_and_resource_options_to_parser(parser): - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( + system_or_domain_or_project = parser.add_mutually_exclusive_group() + system_or_domain_or_project.add_argument( + '--system', + metavar='<system>', + help=_('Include <system> (all)'), + ) + system_or_domain_or_project.add_argument( '--domain', metavar='<domain>', help=_('Include <domain> (name or ID)'), ) - domain_or_project.add_argument( + system_or_domain_or_project.add_argument( '--project', metavar='<project>', help=_('Include <project> (name or ID)'), @@ -62,7 +67,14 @@ def _add_identity_and_resource_options_to_parser(parser): def _process_identity_and_resource_options(parsed_args, identity_client_manager): kwargs = {} - if parsed_args.user and parsed_args.domain: + 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['system'] = parsed_args.system + elif parsed_args.user and parsed_args.domain: kwargs['user'] = common.find_user( identity_client_manager, parsed_args.user, @@ -83,6 +95,13 @@ def _process_identity_and_resource_options(parsed_args, 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['system'] = parsed_args.system elif parsed_args.group and parsed_args.domain: kwargs['group'] = common.find_group( identity_client_manager, @@ -109,8 +128,8 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): - _description = _("Adds a role assignment to a user or group on a domain " - "or project") + _description = _("Adds a role assignment to a user or group on the " + "system, a domain, or a project") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -126,8 +145,8 @@ class AddRole(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.user and not parsed_args.domain - and not parsed_args.group and not parsed_args.project): + if (not parsed_args.user and not parsed_args.domain and + not parsed_args.group and not parsed_args.project): msg = _("Role not added, incorrect set of arguments " "provided. See openstack --help for more details") raise exceptions.CommandError(msg) @@ -381,7 +400,7 @@ class ListRole(command.Lister): class RemoveRole(command.Command): - _description = _("Removes a role assignment from domain/project : " + _description = _("Removes a role assignment from system/domain/project : " "user/group") def get_parser(self, prog_name): @@ -399,8 +418,8 @@ class RemoveRole(command.Command): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.user and not parsed_args.domain - and not parsed_args.group and not parsed_args.project): + if (not parsed_args.user and not parsed_args.domain and + not parsed_args.group and not parsed_args.project): msg = _("Incorrect set of arguments provided. " "See openstack --help for more details") raise exceptions.CommandError(msg) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index a362adb0..9c2f3d24 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -55,17 +55,22 @@ class ListRoleAssignment(command.Lister): help=_('Group to filter (name or ID)'), ) common.add_group_domain_option_to_parser(parser) - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( + system_or_domain_or_project = parser.add_mutually_exclusive_group() + system_or_domain_or_project.add_argument( '--domain', metavar='<domain>', help=_('Domain to filter (name or ID)'), ) - domain_or_project.add_argument( + system_or_domain_or_project.add_argument( '--project', metavar='<project>', help=_('Project to filter (name or ID)'), ) + system_or_domain_or_project.add_argument( + '--system', + metavar='<system>', + help=_('Filter based on system role assignments'), + ) common.add_project_domain_option_to_parser(parser) common.add_inherited_option_to_parser(parser) parser.add_argument( @@ -85,7 +90,8 @@ class ListRoleAssignment(command.Lister): def _as_tuple(self, assignment): return (assignment.role, assignment.user, assignment.group, - assignment.project, assignment.domain, assignment.inherited) + assignment.project, assignment.domain, assignment.system, + assignment.inherited) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -117,6 +123,10 @@ class ListRoleAssignment(command.Lister): auth_ref.user_id ) + system = None + if parsed_args.system: + system = parsed_args.system + domain = None if parsed_args.domain: domain = common.find_domain( @@ -149,7 +159,9 @@ class ListRoleAssignment(command.Lister): include_names = True if parsed_args.names else False effective = True if parsed_args.effective else False - columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + columns = ( + 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited' + ) inherited_to = 'projects' if parsed_args.inherited else None data = identity_client.role_assignments.list( @@ -157,6 +169,7 @@ class ListRoleAssignment(command.Lister): user=user, group=group, project=project, + system=system, role=role, effective=effective, os_inherit_extension_inherited_to=inherited_to, @@ -174,14 +187,24 @@ class ListRoleAssignment(command.Lister): else: setattr(assignment, 'project', scope['project']['id']) assignment.domain = '' + assignment.system = '' elif 'domain' in scope: if include_names: setattr(assignment, 'domain', scope['domain']['name']) else: setattr(assignment, 'domain', scope['domain']['id']) assignment.project = '' - + assignment.system = '' + elif 'system' in scope: + # NOTE(lbragstad): If, or when, keystone supports role + # assignments on subsets of a system, this will have to evolve + # to handle that case instead of hardcoding to the entire + # system. + setattr(assignment, 'system', 'all') + assignment.domain = '' + assignment.project = '' else: + assignment.system = '' assignment.domain = '' assignment.project = '' diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index effb9e35..1933ecad 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -192,6 +192,12 @@ class IssueToken(command.ShowOne): data['user_id'] = auth_ref.user_id if auth_ref.domain_id: data['domain_id'] = auth_ref.domain_id + if auth_ref.system_scoped: + # NOTE(lbragstad): This could change in the future when, or if, + # keystone supports the ability to scope to a subset of the entire + # deployment system. When that happens, this will have to relay + # scope information and IDs like we do for projects and domains. + data['system'] = 'all' return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 7a8e67bf..7ecaa3ef 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -21,11 +21,6 @@ import logging import os import sys -if os.name == "nt": - import msvcrt -else: - msvcrt = None - from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions from osc_lib.command import command @@ -35,6 +30,11 @@ import six from openstackclient.api import utils as api_utils from openstackclient.i18n import _ +if os.name == "nt": + import msvcrt +else: + msvcrt = None + CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] DEFAULT_CONTAINER_FORMAT = 'bare' diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7e6a7aa1..1e67692a 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,10 +16,11 @@ """Image V2 Action Implementations""" import argparse +from base64 import b64encode import logging -import sys from glanceclient.common import utils as gc_utils +from openstack.image import image_signer from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -184,6 +185,22 @@ class CreateImage(command.ShowOne): help=_("Force image creation if volume is in use " "(only meaningful with --volume)"), ) + parser.add_argument( + '--sign-key-path', + metavar="<sign-key-path>", + default=[], + help=_("Sign the image using the specified private key. " + "Only use in combination with --sign-cert-id") + ) + parser.add_argument( + '--sign-cert-id', + metavar="<sign-cert-id>", + default=[], + help=_("The specified certificate UUID is a reference to " + "the certificate in the key manager that corresponds " + "to the public key and is used for signature validation. " + "Only use in combination with --sign-key-path") + ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", @@ -336,6 +353,46 @@ class CreateImage(command.ShowOne): parsed_args.project_domain, ).id + # sign an image using a given local private key file + if parsed_args.sign_key_path or parsed_args.sign_cert_id: + if not parsed_args.file: + msg = (_("signing an image requires the --file option, " + "passing files via stdin when signing is not " + "supported.")) + raise exceptions.CommandError(msg) + if (len(parsed_args.sign_key_path) < 1 or + len(parsed_args.sign_cert_id) < 1): + msg = (_("'sign-key-path' and 'sign-cert-id' must both be " + "specified when attempting to sign an image.")) + raise exceptions.CommandError(msg) + else: + sign_key_path = parsed_args.sign_key_path + sign_cert_id = parsed_args.sign_cert_id + signer = image_signer.ImageSigner() + try: + pw = utils.get_password( + self.app.stdin, + prompt=("Please enter private key password, leave " + "empty if none: "), + confirm=False) + if not pw or len(pw) < 1: + pw = None + signer.load_private_key( + sign_key_path, + password=pw) + except Exception: + msg = (_("Error during sign operation: private key could " + "not be loaded.")) + raise exceptions.CommandError(msg) + + signature = signer.generate_signature(fp) + signature_b64 = b64encode(signature) + kwargs['img_signature'] = signature_b64 + kwargs['img_signature_certificate_uuid'] = sign_cert_id + kwargs['img_signature_hash_method'] = signer.hash_method + if signer.padding_method: + kwargs['img_signature_key_type'] = signer.padding_method + # If a volume is specified. if parsed_args.volume: volume_client = self.app.client_manager.volume @@ -441,6 +498,13 @@ class ListImage(command.Lister): help=_("List only private images"), ) public_group.add_argument( + "--community", + dest="community", + action="store_true", + default=False, + help=_("List only community images"), + ) + public_group.add_argument( "--shared", dest="shared", action="store_true", @@ -466,6 +530,12 @@ class ListImage(command.Lister): help=_("Filter images based on status.") ) parser.add_argument( + '--tag', + metavar='<tag>', + default=None, + help=_('Filter images based on tag.'), + ) + parser.add_argument( '--long', action='store_true', default=False, @@ -511,6 +581,8 @@ class ListImage(command.Lister): kwargs['public'] = True if parsed_args.private: kwargs['private'] = True + if parsed_args.community: + kwargs['community'] = True if parsed_args.shared: kwargs['shared'] = True if parsed_args.limit: @@ -522,6 +594,8 @@ class ListImage(command.Lister): kwargs['name'] = parsed_args.name if parsed_args.status: kwargs['status'] = parsed_args.status + if parsed_args.tag: + kwargs['tag'] = parsed_args.tag if parsed_args.long: columns = ( 'ID', @@ -578,7 +652,7 @@ class ListImage(command.Lister): property_field='properties', ) - data = utils.sort_items(data, parsed_args.sort) + data = utils.sort_items(data, parsed_args.sort, str) return ( column_headers, @@ -592,6 +666,39 @@ class ListImage(command.Lister): ) +class ListImageProjects(command.Lister): + _description = _("List projects associated with image") + + def get_parser(self, prog_name): + parser = super(ListImageProjects, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="<image>", + help=_("Image (name or ID)"), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + columns = ( + "Image ID", + "Member ID", + "Status" + ) + + image_id = utils.find_resource( + image_client.images, + parsed_args.image).id + + data = image_client.image_members.list(image_id) + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + class RemoveProjectImage(command.Command): _description = _("Disassociate project with image") @@ -653,7 +760,7 @@ class SaveImage(command.Command): if data.wrapped is None: msg = _('Image %s has no data.') % image.id LOG.error(msg) - sys.stdout.write(msg + '\n') + self.app.stdout.write(msg + '\n') raise SystemExit gc_utils.save_image(data, parsed_args.file) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 5183cbda..39936fde 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -13,16 +13,6 @@ import logging -from openstack import connection - - -# NOTE(dtroyer): Attempt an import to detect if the SDK installed is new -# enough to not use Profile. If so, use that. -try: - from openstack.config import loader as config # noqa - profile = None -except ImportError: - from openstack import profile from osc_lib import utils from openstackclient.i18n import _ @@ -41,37 +31,17 @@ API_VERSIONS = { def make_client(instance): """Returns a network proxy""" - if getattr(instance, "sdk_connection", None) is None: - if profile is None: - # If the installed OpenStackSDK is new enough to not require a - # Profile obejct and osc-lib is not new enough to have created - # it for us, make an SDK Connection. - # NOTE(dtroyer): This can be removed when this bit is in the - # released osc-lib in requirements.txt. - conn = connection.Connection( - config=instance._cli_options, - session=instance.session, - ) - else: - # Fall back to the original Connection creation - prof = profile.Profile() - prof.set_region(API_NAME, instance.region_name) - prof.set_version(API_NAME, instance._api_version[API_NAME]) - prof.set_interface(API_NAME, instance.interface) - conn = connection.Connection( - authenticator=instance.session.auth, - verify=instance.session.verify, - cert=instance.session.cert, - profile=prof, - ) - - instance.sdk_connection = conn - - conn = instance.sdk_connection - LOG.debug('Connection: %s', conn) - LOG.debug('Network client initialized using OpenStack SDK: %s', - conn.network) - return conn.network + # NOTE(dtroyer): As of osc-lib 1.8.0 and OpenStackSDK 0.10.0 the + # old Profile interface and separate client creation + # for each API that uses the SDK is unnecessary. This + # callback remains as a remnant of the original plugin + # interface and to avoid the code churn of changing all + # of the existing references. + LOG.debug( + 'Network client initialized using OpenStack SDK: %s', + instance.sdk_connection.network, + ) + return instance.sdk_connection.network def build_option_parser(parser): diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 37bf1406..d22b2caa 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,6 +12,7 @@ # import abc +import contextlib import logging import openstack.exceptions @@ -24,6 +25,30 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +_required_opt_extensions_map = { + 'allowed_address_pairs': 'allowed-address-pairs', + 'dns_domain': 'dns-integration', + 'dns_name': 'dns-integration', + 'extra_dhcp_opts': 'extra_dhcp_opt', + 'qos_policy_id': 'qos', + 'security_groups': 'security-groups', +} + + +@contextlib.contextmanager +def check_missing_extension_if_error(client_manager, attrs): + # If specified option requires extension, then try to + # find out if it exists. If it does not exist, + # then an exception with the appropriate message + # will be thrown from within client.find_extension + try: + yield + except openstack.exceptions.HttpException: + for opt, ext in _required_opt_extensions_map.items(): + if opt in attrs: + client_manager.find_extension(ext, ignore_missing=False) + raise + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeCommand(command.Command): diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 4d07d9da..e1ec8274 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,8 +13,6 @@ """IP Floating action implementations""" -import logging - from osc_lib.command import command from osc_lib import utils @@ -22,6 +20,12 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag + + +_formatters = { + 'port_details': utils.format_dict, +} def _get_network_columns(item): @@ -81,6 +85,12 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id + if parsed_args.dns_domain: + attrs['dns_domain'] = parsed_args.dns_domain + + if parsed_args.dns_name: + attrs['dns_name'] = parsed_args.dns_name + return attrs @@ -138,12 +148,32 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): metavar='<project>', help=_("Owner's project (name or ID)") ) + parser.add_argument( + '--dns-domain', + metavar='<dns-domain>', + dest='dns_domain', + help=_("Set DNS domain for this floating IP") + ) + parser.add_argument( + '--dns-name', + metavar='<dns-name>', + dest='dns_name', + help=_("Set DNS name for this floating IP") + ) + identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('floating IP')) return parser def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) - obj = client.create_ip(**attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + obj = client.create_ip(**attrs) + + # 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_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) @@ -155,30 +185,6 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): return (columns, data) -class CreateIPFloating(CreateFloatingIP): - _description = _("Create floating IP") - - # TODO(tangchen): Remove this class and ``ip floating create`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip create" instead.')) - return super(CreateIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip create" instead.')) - return super(CreateIPFloating, self).take_action_compute( - client, parsed_args) - - class DeleteFloatingIP(common.NetworkAndComputeDelete): _description = _("Delete floating IP(s)") @@ -206,30 +212,6 @@ class DeleteFloatingIP(common.NetworkAndComputeDelete): client.api.floating_ip_delete(self.r) -class DeleteIPFloating(DeleteFloatingIP): - _description = _("Delete floating IP(s)") - - # TODO(tangchen): Remove this class and ``ip floating delete`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip delete" instead.')) - return super(DeleteIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip delete" instead.')) - return super(DeleteIPFloating, self).take_action_compute( - client, parsed_args) - - class ListFloatingIP(common.NetworkAndComputeLister): # TODO(songminglong): Use SDK resource mapped attribute names once # the OSC minimum requirements include SDK 1.0 @@ -280,6 +262,7 @@ class ListFloatingIP(common.NetworkAndComputeLister): help=_("List floating IP(s) according to " "given router (name or ID)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('floating IP')) return parser @@ -308,11 +291,17 @@ class ListFloatingIP(common.NetworkAndComputeLister): 'router_id', 'status', 'description', + 'tags', + 'dns_name', + 'dns_domain', ) headers = headers + ( 'Router', 'Status', 'Description', + 'Tags', + 'DNS Name', + 'DNS Domain', ) query = {} @@ -342,6 +331,8 @@ class ListFloatingIP(common.NetworkAndComputeLister): ignore_missing=False) query['router_id'] = router.id + _tag.get_tag_filtering_args(parsed_args, query) + data = client.ips(**query) return (headers, @@ -375,30 +366,6 @@ class ListFloatingIP(common.NetworkAndComputeLister): ) for s in data)) -class ListIPFloating(ListFloatingIP): - _description = _("List floating IP(s)") - - # TODO(tangchen): Remove this class and ``ip floating list`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip list" instead.')) - return super(ListIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip list" instead.')) - return super(ListIPFloating, self).take_action_compute( - client, parsed_args) - - class SetFloatingIP(command.Command): _description = _("Set floating IP Properties") @@ -407,11 +374,10 @@ class SetFloatingIP(command.Command): parser.add_argument( 'floating_ip', metavar='<floating-ip>', - help=_("Floating IP to associate (IP address or ID)")) + help=_("Floating IP to modify (IP address or ID)")) parser.add_argument( '--port', metavar='<port>', - required=True, help=_("Associate the floating IP with port (name or ID)")), parser.add_argument( '--fixed-ip-address', @@ -431,6 +397,9 @@ class SetFloatingIP(command.Command): action='store_true', help=_("Remove the QoS policy attached to the floating IP") ) + + _tag.add_tag_option_to_parser_for_set(parser, _('floating IP')) + return parser def take_action(self, parsed_args): @@ -440,9 +409,11 @@ class SetFloatingIP(command.Command): parsed_args.floating_ip, ignore_missing=False, ) - port = client.find_port(parsed_args.port, - ignore_missing=False) - attrs['port_id'] = port.id + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['port_id'] = port.id + if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address @@ -453,7 +424,11 @@ class SetFloatingIP(command.Command): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None - client.update_ip(obj, **attrs) + if attrs: + client.update_ip(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowFloatingIP(common.NetworkAndComputeShowOne): @@ -473,7 +448,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): ignore_missing=False, ) display_columns, columns = _get_network_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): @@ -483,30 +458,6 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): return (columns, data) -class ShowIPFloating(ShowFloatingIP): - _description = _("Display floating IP details") - - # TODO(tangchen): Remove this class and ``ip floating show`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip show" instead.')) - return super(ShowIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip show" instead.')) - return super(ShowIPFloating, self).take_action_compute( - client, parsed_args) - - class UnsetFloatingIP(command.Command): _description = _("Unset floating IP Properties") @@ -528,6 +479,8 @@ class UnsetFloatingIP(command.Command): default=False, help=_("Remove the QoS policy attached to the floating IP") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('floating IP')) + return parser def take_action(self, parsed_args): @@ -544,3 +497,6 @@ class UnsetFloatingIP(command.Command): if attrs: client.update_ip(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py index ebb15da8..32852004 100644 --- a/openstackclient/network/v2/floating_ip_pool.py +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -13,7 +13,6 @@ """Floating IP Pool action implementations""" -import logging from osc_lib import exceptions from osc_lib import utils @@ -40,27 +39,3 @@ class ListFloatingIPPool(common.NetworkAndComputeLister): (utils.get_dict_properties( s, columns, ) for s in data)) - - -class ListIPFloatingPool(ListFloatingIPPool): - _description = _("List pools of floating IP addresses") - - # TODO(tangchen): Remove this class and ``ip floating pool list`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip pool list" instead.')) - return super(ListIPFloatingPool, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip pool list" instead.')) - return super(ListIPFloatingPool, self).take_action_compute( - client, parsed_args) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4c1725c5..0fdf62c9 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -107,6 +107,10 @@ def _get_attrs_network(client_manager, parsed_args): if parsed_args.description: attrs['description'] = parsed_args.description + # set mtu + if parsed_args.mtu: + attrs['mtu'] = parsed_args.mtu + # update_external_network_options if parsed_args.internal: attrs['router:external'] = False @@ -130,6 +134,9 @@ def _get_attrs_network(client_manager, parsed_args): attrs['qos_policy_id'] = _qos_policy.id if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None + # Update DNS network options + if parsed_args.dns_domain: + attrs['dns_domain'] = parsed_args.dns_domain return attrs @@ -167,6 +174,13 @@ def _add_additional_network_options(parser): dest='segmentation_id', help=_("VLAN ID for VLAN networks or Tunnel ID for " "GENEVE/GRE/VXLAN networks")) + parser.add_argument( + '--dns-domain', + metavar='<dns-domain>', + dest='dns_domain', + help=_("Set DNS domain for this network " + "(requires DNS integration extension)") + ) # TODO(sindhu): Use the SDK resource mapped attribute names once the @@ -217,6 +231,11 @@ class CreateNetwork(common.NetworkAndComputeShowOne): metavar='<description>', help=_("Set network description") ) + parser.add_argument( + '--mtu', + metavar='<mtu>', + help=_("Set network mtu") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--availability-zone-hint', @@ -299,8 +318,10 @@ class CreateNetwork(common.NetworkAndComputeShowOne): attrs['vlan_transparent'] = True if parsed_args.no_transparent_vlan: attrs['vlan_transparent'] = False + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + obj = client.create_network(**attrs) - obj = client.create_network(**attrs) # 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_network(obj) @@ -619,6 +640,11 @@ class SetNetwork(command.Command): metavar="<description", help=_("Set network description") ) + parser.add_argument( + '--mtu', + metavar="<mtu", + help=_("Set network mtu") + ) port_security_group = parser.add_mutually_exclusive_group() port_security_group.add_argument( '--enable-port-security', @@ -676,7 +702,9 @@ class SetNetwork(command.Command): attrs = _get_attrs_network(self.app.client_manager, parsed_args) if attrs: - client.update_network(obj, **attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + client.update_network(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_set(client, obj, parsed_args) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index f50e58b3..28c5600a 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -29,11 +29,11 @@ RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' MANDATORY_PARAMETERS = { RULE_TYPE_MINIMUM_BANDWIDTH: {'min_kbps', 'direction'}, RULE_TYPE_DSCP_MARKING: {'dscp_mark'}, - RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps', 'max_burst_kbps'}} + RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps'}} OPTIONAL_PARAMETERS = { RULE_TYPE_MINIMUM_BANDWIDTH: set(), RULE_TYPE_DSCP_MARKING: set(), - RULE_TYPE_BANDWIDTH_LIMIT: {'direction'}} + RULE_TYPE_BANDWIDTH_LIMIT: {'direction', 'max_burst_kbps'}} DIRECTION_EGRESS = 'egress' DIRECTION_INGRESS = 'ingress' DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, @@ -62,11 +62,11 @@ def _check_type_parameters(attrs, type, is_create): notreq_params -= type_params if is_create and None in map(attrs.get, req_params): msg = (_('"Create" rule command for type "%(rule_type)s" requires ' - 'arguments %(args)s') % + 'arguments: %(args)s') % {'rule_type': type, 'args': ", ".join(sorted(req_params))}) raise exceptions.CommandError(msg) if set(attrs.keys()) & notreq_params: - msg = (_('Rule type "%(rule_type)s" only requires arguments %(args)s') + msg = (_('Rule type "%(rule_type)s" only requires arguments: %(args)s') % {'rule_type': type, 'args': ", ".join(sorted(type_params))}) raise exceptions.CommandError(msg) @@ -141,7 +141,10 @@ def _add_rule_arguments(parser): dest='max_burst_kbits', metavar='<max-burst-kbits>', type=int, - help=_('Maximum burst in kilobits, 0 means automatic') + help=_('Maximum burst in kilobits, 0 or not specified means ' + 'automatic, which is 80%% of the bandwidth limit, which works ' + 'for typical TCP traffic. For details check the QoS user ' + 'workflow.') ) parser.add_argument( '--dscp-mark', diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f13ee7b9..0d276d9d 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -25,6 +25,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils from openstackclient.network.v2 import _tag @@ -215,6 +216,9 @@ def _prepare_filter_fixed_ips(client_manager, parsed_args): if 'ip-address' in ip_spec: ips.append('ip_address=%s' % ip_spec['ip-address']) + + if 'ip-substring' in ip_spec: + ips.append('ip_address_substr=%s' % ip_spec['ip-substring']) return ips @@ -255,7 +259,7 @@ def _add_updatable_args(parser): 'normal', 'baremetal', 'virtio-forwarder'], help=_("VNIC type for this port (direct | direct-physical | " "macvtap | normal | baremetal | virtio-forwarder, " - " default: normal)") + "default: normal)") ) # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not # remove before 3.x release or Mar 2017. @@ -278,8 +282,8 @@ def _add_updatable_args(parser): ) parser.add_argument( '--dns-name', - metavar='dns-name', - help=_("Set DNS name to this port " + metavar='<dns-name>', + help=_("Set DNS name for this port " "(requires DNS integration extension)") ) @@ -434,7 +438,10 @@ 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 - obj = client.create_port(**attrs) + 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) display_columns, columns = _get_columns(obj) @@ -531,11 +538,13 @@ class ListPort(command.Lister): identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--fixed-ip', - metavar='subnet=<subnet>,ip-address=<ip-address>', + metavar=('subnet=<subnet>,ip-address=<ip-address>,' + 'ip-substring=<ip-substring>'), action=parseractions.MultiKeyValueAction, - optional_keys=['subnet', 'ip-address'], + optional_keys=['subnet', 'ip-address', 'ip-substring'], help=_("Desired IP and/or subnet for filtering ports " - "(name or ID): subnet=<subnet>,ip-address=<ip-address> " + "(name or ID): subnet=<subnet>,ip-address=<ip-address>," + "ip-substring=<ip-substring> " "(repeat option to set multiple fixed IP addresses)"), ) _tag.add_tag_filtering_option_to_parser(parser, _('ports')) @@ -660,7 +669,7 @@ class SetPort(command.Command): parser.add_argument( '--no-binding-profile', action='store_true', - help=_("Clear existing information of binding:profile." + help=_("Clear existing information of binding:profile. " "Specify both --binding-profile and --no-binding-profile " "to overwrite the current binding:profile information.") ) @@ -714,9 +723,9 @@ class SetPort(command.Command): '--no-allowed-address', dest='no_allowed_address_pair', action='store_true', - help=_("Clear existing allowed-address pairs associated" - "with this port." - "(Specify both --allowed-address and --no-allowed-address" + help=_("Clear existing allowed-address pairs associated " + "with this port. " + "(Specify both --allowed-address and --no-allowed-address " "to overwrite the current allowed-address pairs)") ) parser.add_argument( @@ -785,7 +794,9 @@ class SetPort(command.Command): attrs['data_plane_status'] = parsed_args.data_plane_status if attrs: - client.update_port(obj, **attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + client.update_port(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_set(client, obj, parsed_args) @@ -832,7 +843,7 @@ class UnsetPort(command.Command): '--binding-profile', metavar='<binding-profile-key>', action='append', - help=_("Desired key which should be removed from binding:profile" + help=_("Desired key which should be removed from binding:profile " "(repeat option to unset multiple binding:profile data)")) parser.add_argument( '--security-group', @@ -856,8 +867,8 @@ class UnsetPort(command.Command): required_keys=['ip-address'], optional_keys=['mac-address'], help=_("Desired allowed-address pair which should be removed " - "from this port: ip-address=<ip-address> " - "[,mac-address=<mac-address>] (repeat option to set " + "from this port: ip-address=<ip-address>" + "[,mac-address=<mac-address>] (repeat option to unset " "multiple allowed-address pairs)") ) parser.add_argument( diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index caf3236a..746452e4 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -86,8 +86,8 @@ def _get_attrs(client_manager, parsed_args): attrs['distributed'] = False if parsed_args.distributed: attrs['distributed'] = True - if ('availability_zone_hints' in parsed_args - and parsed_args.availability_zone_hints is not None): + if ('availability_zone_hints' in parsed_args and + parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints if parsed_args.description is not None: attrs['description'] = parsed_args.description @@ -563,7 +563,7 @@ class SetRouter(command.Command): metavar='subnet=<subnet>,ip-address=<ip-address>', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help=_("Desired IP and/or subnet (name or ID)" + help=_("Desired IP and/or subnet (name or ID) " "on external gateway: " "subnet=<subnet>,ip-address=<ip-address> " "(repeat option to set multiple fixed IP addresses)") @@ -611,10 +611,10 @@ class SetRouter(command.Command): elif parsed_args.no_route or parsed_args.clear_routes: attrs['routes'] = [] if (parsed_args.disable_snat or parsed_args.enable_snat or - parsed_args.fixed_ip) and not parsed_args.external_gateway: - msg = (_("You must specify '--external-gateway' in order" - "to update the SNAT or fixed-ip values")) - raise exceptions.CommandError(msg) + parsed_args.fixed_ip) and not parsed_args.external_gateway: + msg = (_("You must specify '--external-gateway' in order " + "to update the SNAT or fixed-ip values")) + raise exceptions.CommandError(msg) if parsed_args.external_gateway: gateway_info = {} network = client.find_network( diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 75af3587..ed6c8d7c 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -15,6 +15,7 @@ import argparse +from osc_lib.command import command from osc_lib import utils import six @@ -23,6 +24,7 @@ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils +from openstackclient.network.v2 import _tag def _format_network_security_group_rules(sg_rules): @@ -106,6 +108,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('security group')) return parser def _get_description(self, parsed_args): @@ -130,6 +133,8 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): # Create the security group and display the results. obj = client.create_security_group(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, property_columns = _get_columns(obj) data = utils.get_item_properties( obj, @@ -198,6 +203,7 @@ class ListSecurityGroup(common.NetworkAndComputeLister): "(name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_filtering_option_to_parser(parser, _('security group')) return parser def update_parser_compute(self, parser): @@ -220,19 +226,23 @@ class ListSecurityGroup(common.NetworkAndComputeLister): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id + + _tag.get_tag_filtering_args(parsed_args, filters) data = client.security_groups(**filters) columns = ( "ID", "Name", "Description", - "Project ID" + "Project ID", + "tags" ) column_headers = ( "ID", "Name", "Description", - "Project" + "Project", + "Tags" ) return (column_headers, (utils.get_item_properties( @@ -282,6 +292,10 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): ) return parser + def update_parser_network(self, parser): + _tag.add_tag_option_to_parser_for_set(parser, _('security group')) + return parser + def take_action_network(self, client, parsed_args): obj = client.find_security_group(parsed_args.group, ignore_missing=False) @@ -295,6 +309,9 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): # the update. client.update_security_group(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) + def take_action_compute(self, client, parsed_args): data = client.api.security_group_find(parsed_args.group) @@ -344,3 +361,25 @@ class ShowSecurityGroup(common.NetworkAndComputeShowOne): formatters=_formatters_compute ) return (display_columns, data) + + +class UnsetSecurityGroup(command.Command): + _description = _("Unset security group properties") + + def get_parser(self, prog_name): + parser = super(UnsetSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar="<group>", + help=_("Security group to modify (name or ID)") + ) + _tag.add_tag_option_to_parser_for_unset(parser, _('security group')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_security_group(parsed_args.group, + ignore_missing=False) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2c71e1e0..d252a7a5 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -167,6 +167,7 @@ def convert_entries_to_gateway(entries): def _get_attrs(client_manager, parsed_args, is_create=True): attrs = {} + client = client_manager.network if 'name' in parsed_args and parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -179,7 +180,6 @@ def _get_attrs(client_manager, parsed_args, is_create=True): parsed_args.project_domain, ).id attrs['tenant_id'] = project_id - client = client_manager.network attrs['network_id'] = client.find_network(parsed_args.network, ignore_missing=False).id if parsed_args.subnet_pool is not None: @@ -200,10 +200,10 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode if parsed_args.ipv6_address_mode is not None: attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode - if parsed_args.network_segment is not None: - attrs['segment_id'] = client.find_segment( - parsed_args.network_segment, ignore_missing=False).id + if parsed_args.network_segment is not None: + attrs['segment_id'] = client.find_segment( + parsed_args.network_segment, ignore_missing=False).id if 'gateway' in parsed_args and parsed_args.gateway is not None: gateway = parsed_args.gateway.lower() @@ -247,6 +247,7 @@ class CreateSubnet(command.ShowOne): parser = super(CreateSubnet, self).get_parser(prog_name) parser.add_argument( 'name', + metavar='<name>', help=_("New subnet name") ) parser.add_argument( @@ -412,7 +413,7 @@ class ListSubnet(command.Lister): choices=[4, 6], metavar='<ip-version>', dest='ip_version', - help=_("List only subnets of given IP version in output." + help=_("List only subnets of given IP version in output. " "Allowed values for IP version are 4 and 6."), ) dhcp_enable_group = parser.add_mutually_exclusive_group() @@ -559,6 +560,14 @@ class SetSubnet(command.Command): "e.g.: --gateway 192.168.9.1, --gateway none.") ) parser.add_argument( + '--network-segment', + metavar='<network-segment>', + help=_("Network segment to associate with this subnet (name or " + "ID). It is only allowed to set the segment if the current " + "value is `None`, the network must also have only one " + "segment and only one subnet can exist on the network.") + ) + parser.add_argument( '--description', metavar='<description>', help=_("Set subnet description") @@ -581,7 +590,7 @@ class SetSubnet(command.Command): if not parsed_args.no_host_route: attrs['host_routes'] += obj.host_routes elif parsed_args.no_host_route: - attrs['host_routes'] = '' + attrs['host_routes'] = [] if 'allocation_pools' in attrs: if not parsed_args.no_allocation_pool: attrs['allocation_pools'] += obj.allocation_pools diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a5839868..81765ca1 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -191,8 +191,9 @@ class CreateSubnetPool(command.ShowOne): '--default-quota', type=int, metavar='<num-ip-addresses>', - help=_("Set default quota for subnet pool as the number of" - "IP addresses allowed in a subnet")), + help=_("Set default per-project quota for this subnet pool " + "as the number of IP addresses that can be allocated " + "from the subnet pool")), _tag.add_tag_option_to_parser_for_create(parser, _('subnet pool')) return parser @@ -389,8 +390,9 @@ class SetSubnetPool(command.Command): '--default-quota', type=int, metavar='<num-ip-addresses>', - help=_("Set default quota for subnet pool as the number of" - "IP addresses allowed in a subnet")), + help=_("Set default per-project quota for this subnet pool " + "as the number of IP addresses that can be allocated " + "from the subnet pool")), _tag.add_tag_option_to_parser_for_set(parser, _('subnet pool')) return parser diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py index d589f19c..41aabb7f 100644 --- a/openstackclient/tests/functional/common/test_module.py +++ b/openstackclient/tests/functional/common/test_module.py @@ -46,7 +46,7 @@ class ModuleTest(base.TestCase): class CommandTest(base.TestCase): """Functional tests for openstackclient command list.""" GROUPS = [ - 'openstack.volume.v2', + 'openstack.volume.v3', 'openstack.network.v2', 'openstack.image.v2', 'openstack.identity.v3', diff --git a/openstackclient/tests/functional/common/test_versions.py b/openstackclient/tests/functional/common/test_versions.py new file mode 100644 index 00000000..adc74ebc --- /dev/null +++ b/openstackclient/tests/functional/common/test_versions.py @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from openstackclient.tests.functional import base + + +class VersionsTests(base.TestCase): + """Functional tests for versions.""" + + def test_versions_show(self): + # TODO(mordred) Make this better. The trick is knowing what in the + # payload to test for. + cmd_output = json.loads(self.openstack( + 'versions show -f json' + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + "Region Name", + cmd_output[0], + ) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index cf9d2bc0..71026757 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -11,6 +11,7 @@ # under the License. import json +import time import uuid from openstackclient.tests.functional import base @@ -51,6 +52,23 @@ class AggregateTests(base.TestCase): cmd_output['availability_zone'] ) + # Loop a few times since this is timing-sensitive + # Just hard-code it for now, since there is no pause and it is + # racy we shouldn't have to wait too long, a minute seems reasonable + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + + name2 + )) + if cmd_output['name'] != name2: + # Hang out for a bit and try again + print('retrying aggregate check') + wait_time += 10 + time.sleep(10) + else: + break + del_output = self.openstack( 'aggregate delete ' + name1 + ' ' + diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 13fdfa06..3cb72d9f 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -11,6 +11,7 @@ # under the License. import json +import time import uuid from tempest.lib import exceptions @@ -255,10 +256,24 @@ class ServerTests(common.ComputeTestCase): floating_ip ) self.assertEqual("", raw_output) - cmd_output = json.loads(self.openstack( - 'server show -f json ' + - name - )) + + # Loop a few times since this is timing-sensitive + # Just hard-code it for now, since there is no pause and it is + # racy we shouldn't have to wait too long, a minute seems reasonable + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + if floating_ip not in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying floating IP check') + wait_time += 10 + time.sleep(10) + else: + break + self.assertIn( floating_ip, cmd_output['addresses'], @@ -272,6 +287,23 @@ class ServerTests(common.ComputeTestCase): ) self.assertEqual("", raw_output) + # Loop a few times since this is timing-sensitive + # Just hard-code it for now, since there is no pause and it is + # racy we shouldn't have to wait too long, a minute seems reasonable + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + if floating_ip in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying floating IP check') + wait_time += 10 + time.sleep(10) + else: + break + cmd_output = json.loads(self.openstack( 'server show -f json ' + name diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 33cb5d86..58468bc7 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -52,6 +52,17 @@ class IdentityTests(base.TestCase): 'id', 'relay_state_prefix', 'sp_url'] SERVICE_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description', 'Auth URL'] + IMPLIED_ROLE_LIST_HEADERS = ['Prior Role ID', 'Prior Role Name', + 'Implied Role ID', 'Implied Role Name'] + REGISTERED_LIMIT_FIELDS = ['id', 'service_id', 'resource_name', + 'default_limit', 'description', 'region_id'] + REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name', + 'Default Limit', 'Description', + 'Region ID'] + LIMIT_FIELDS = ['id', 'project_id', 'service_id', 'resource_name', + 'resource_limit', 'description', 'region_id'] + LIMIT_LIST_HEADERS = ['ID', 'Project ID', 'Service ID', 'Resource Name', + 'Resource Limit', 'Description', 'Region ID'] @classmethod def setUpClass(cls): @@ -149,6 +160,17 @@ class IdentityTests(base.TestCase): self.assertEqual(role_name, role['name']) return role_name + def _create_dummy_implied_role(self, add_clean_up=True): + role_name = self._create_dummy_role(add_clean_up) + implied_role_name = self._create_dummy_role(add_clean_up) + self.openstack( + 'implied role create ' + '--implied-role %(implied_role)s ' + '%(role)s' % {'implied_role': implied_role_name, + 'role': role_name}) + + return implied_role_name, role_name + def _create_dummy_group(self, add_clean_up=True): group_name = data_utils.rand_name('TestGroup') description = data_utils.rand_name('description') @@ -306,3 +328,74 @@ class IdentityTests(base.TestCase): items = self.parse_show(raw_output) self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS) return service_provider + + def _create_dummy_registered_limit(self, add_clean_up=True): + service_name = self._create_dummy_service() + resource_name = data_utils.rand_name('resource_name') + params = { + 'service_name': service_name, + 'default_limit': 10, + 'resource_name': resource_name + } + raw_output = self.openstack( + 'registered limit create' + ' --service %(service_name)s' + ' --default-limit %(default_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + registered_limit_id = self._extract_value_from_items('id', items) + + if add_clean_up: + self.addCleanup( + self.openstack, + 'registered limit delete %s' % registered_limit_id + ) + + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + return registered_limit_id + + def _extract_value_from_items(self, key, items): + for d in items: + for k, v in d.iteritems(): + if k == key: + return v + + def _create_dummy_limit(self, add_clean_up=True): + registered_limit_id = self._create_dummy_registered_limit() + + raw_output = self.openstack( + 'registered limit show %s' % registered_limit_id + ) + items = self.parse_show(raw_output) + resource_name = self._extract_value_from_items('resource_name', items) + service_id = self._extract_value_from_items('service_id', items) + resource_limit = 15 + + project_name = self._create_dummy_project() + raw_output = self.openstack('project show %s' % project_name) + items = self.parse_show(raw_output) + project_id = self._extract_value_from_items('id', items) + + params = { + 'project_id': project_id, + 'service_id': service_id, + 'resource_name': resource_name, + 'resource_limit': resource_limit + } + + raw_output = self.openstack( + 'limit create' + ' --project %(project_id)s' + ' --service %(service_id)s' + ' --resource-limit %(resource_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + + if add_clean_up: + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + return limit_id diff --git a/openstackclient/tests/functional/identity/v3/test_limit.py b/openstackclient/tests/functional/identity/v3/test_limit.py new file mode 100644 index 00000000..03bcb06e --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_limit.py @@ -0,0 +1,192 @@ +# 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 tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class LimitTestCase(common.IdentityTests): + + def test_limit_create_with_service_name(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + 'registered limit show %s' % registered_limit_id + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + raw_output = self.openstack('service show %s' % service_id) + items = self.parse_show(raw_output) + service_name = self._extract_value_from_items('name', items) + + project_name = self._create_dummy_project() + raw_output = self.openstack('project show %s' % project_name) + items = self.parse_show(raw_output) + project_id = self._extract_value_from_items('id', items) + + params = { + 'project_id': project_id, + 'service_name': service_name, + 'resource_name': resource_name, + 'resource_limit': 15 + } + raw_output = self.openstack( + 'limit create' + ' --project %(project_id)s' + ' --service %(service_name)s' + ' --resource-limit %(resource_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_create_with_project_name(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + 'registered limit show %s' % registered_limit_id + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + raw_output = self.openstack('service show %s' % service_id) + items = self.parse_show(raw_output) + service_name = self._extract_value_from_items('name', items) + + project_name = self._create_dummy_project() + + params = { + 'project_name': project_name, + 'service_name': service_name, + 'resource_name': resource_name, + 'resource_limit': 15 + } + raw_output = self.openstack( + 'limit create' + ' --project %(project_name)s' + ' --service %(service_name)s' + ' --resource-limit %(resource_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + registered_limit_id = self._create_dummy_registered_limit() + + def test_limit_create_with_service_id(self): + self._create_dummy_limit() + + def test_limit_create_with_project_id(self): + self._create_dummy_limit() + + def test_limit_create_with_options(self): + registered_limit_id = self._create_dummy_registered_limit() + region_id = self._create_dummy_region() + + params = { + 'region_id': region_id, + 'registered_limit_id': registered_limit_id + } + + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --region %(region_id)s' % params + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + project_name = self._create_dummy_project() + raw_output = self.openstack('project show %s' % project_name) + items = self.parse_show(raw_output) + project_id = self._extract_value_from_items('id', items) + description = data_utils.arbitrary_string() + + params = { + 'project_id': project_id, + 'service_id': service_id, + 'resource_name': resource_name, + 'resource_limit': 15, + 'region_id': region_id, + 'description': description + } + raw_output = self.openstack( + 'limit create' + ' --project %(project_id)s' + ' --service %(service_id)s' + ' --resource-limit %(resource_limit)s' + ' --region %(region_id)s' + ' --description %(description)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_show(self): + limit_id = self._create_dummy_limit() + raw_output = self.openstack('limit show %s' % limit_id) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_set_description(self): + limit_id = self._create_dummy_limit() + + params = { + 'description': data_utils.arbitrary_string(), + 'limit_id': limit_id + } + + raw_output = self.openstack( + 'limit set' + ' --description %(description)s' + ' %(limit_id)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_set_resource_limit(self): + limit_id = self._create_dummy_limit() + + params = { + 'resource_limit': 5, + 'limit_id': limit_id + } + + raw_output = self.openstack( + 'limit set' + ' --resource-limit %(resource_limit)s' + ' %(limit_id)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_list(self): + self._create_dummy_limit() + raw_output = self.openstack('limit list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.LIMIT_LIST_HEADERS) + + def test_limit_delete(self): + limit_id = self._create_dummy_limit(add_clean_up=False) + raw_output = self.openstack('limit delete %s' % limit_id) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/identity/v3/test_registered_limit.py b/openstackclient/tests/functional/identity/v3/test_registered_limit.py new file mode 100644 index 00000000..09e90ce2 --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_registered_limit.py @@ -0,0 +1,184 @@ +# 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 tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class RegisteredLimitTestCase(common.IdentityTests): + + def test_registered_limit_create_with_service_name(self): + self._create_dummy_registered_limit() + + def test_registered_limit_create_with_service_id(self): + service_name = self._create_dummy_service() + raw_output = self.openstack( + 'service show' + ' %(service_name)s' % {'service_name': service_name} + ) + service_items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('id', service_items) + + raw_output = self.openstack( + 'registered limit create' + ' --service %(service_id)s' + ' --default-limit %(default_limit)s' + ' %(resource_name)s' % { + 'service_id': service_id, + 'default_limit': 10, + 'resource_name': 'cores' + } + ) + items = self.parse_show(raw_output) + registered_limit_id = self._extract_value_from_items('id', items) + self.addCleanup( + self.openstack, + 'registered limit delete' + ' %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_create_with_options(self): + service_name = self._create_dummy_service() + region_id = self._create_dummy_region() + params = { + 'service_name': service_name, + 'resource_name': 'cores', + 'default_limit': 10, + 'description': 'default limit for cores', + 'region_id': region_id + } + + raw_output = self.openstack( + 'registered limit create' + ' --description \'%(description)s\'' + ' --region %(region_id)s' + ' --service %(service_name)s' + ' --default-limit %(default_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + registered_limit_id = self._extract_value_from_items('id', items) + self.addCleanup( + self.openstack, + 'registered limit delete %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_show(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + 'registered limit show %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_region_id(self): + region_id = self._create_dummy_region() + registered_limit_id = self._create_dummy_registered_limit() + + params = { + 'registered_limit_id': registered_limit_id, + 'region_id': region_id + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --region %(region_id)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_description(self): + registered_limit_id = self._create_dummy_registered_limit() + params = { + 'registered_limit_id': registered_limit_id, + 'description': 'updated description' + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --description \'%(description)s\'' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_service(self): + registered_limit_id = self._create_dummy_registered_limit() + service_name = self._create_dummy_service() + params = { + 'registered_limit_id': registered_limit_id, + 'service': service_name + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --service %(service)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_default_limit(self): + registered_limit_id = self._create_dummy_registered_limit() + params = { + 'registered_limit_id': registered_limit_id, + 'default_limit': 20 + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --default-limit %(default_limit)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_resource_name(self): + registered_limit_id = self._create_dummy_registered_limit() + resource_name = data_utils.rand_name('resource_name') + params = { + 'registered_limit_id': registered_limit_id, + 'resource_name': resource_name + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --resource-name %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_list(self): + self._create_dummy_registered_limit() + raw_output = self.openstack('registered limit list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.REGISTERED_LIMIT_LIST_HEADERS) + + def test_registered_limit_delete(self): + registered_limit_id = self._create_dummy_registered_limit( + add_clean_up=False + ) + raw_output = self.openstack( + 'registered limit delete' + ' %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py index ab8af9c0..fb9e0614 100644 --- a/openstackclient/tests/functional/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -143,3 +143,28 @@ class RoleTests(common.IdentityTests): 'role': role_name}) self.assertEqual(0, len(add_raw_output)) self.assertEqual(0, len(remove_raw_output)) + + def test_implied_role_list(self): + self._create_dummy_implied_role() + raw_output = self.openstack('implied role list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.IMPLIED_ROLE_LIST_HEADERS) + self.assertEqual(3, len(items)) + + def test_implied_role_create(self): + role_name = self._create_dummy_role() + implied_role_name = self._create_dummy_role() + self.openstack( + 'implied role create ' + '--implied-role %(implied_role)s ' + '%(role)s' % {'implied_role': implied_role_name, + 'role': role_name}) + + def test_implied_role_delete(self): + implied_role_name, role_name = self._create_dummy_implied_role() + raw_output = self.openstack( + 'implied role delete ' + '--implied-role %(implied_role)s ' + '%(role)s' % {'implied_role': implied_role_name, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/image/base.py b/openstackclient/tests/functional/image/base.py new file mode 100644 index 00000000..4b2ab64b --- /dev/null +++ b/openstackclient/tests/functional/image/base.py @@ -0,0 +1,24 @@ +# 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 BaseImageTests(base.TestCase): + """Functional tests for Image commands""" + + @classmethod + def setUpClass(cls): + super(BaseImageTests, cls).setUpClass() + # TODO(dtroyer): maybe do image API discovery here to determine + # what is available, it isn't in the service catalog + cls.haz_v1_api = False diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index fa073f99..30490bf5 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -15,50 +15,47 @@ import uuid import fixtures -from openstackclient.tests.functional import base +from openstackclient.tests.functional.image import base -class ImageTests(base.TestCase): - """Functional tests for image. """ +class ImageTests(base.BaseImageTests): + """Functional tests for Image commands""" - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex + def setUp(self): + super(ImageTests, self).setUp() + if not self.haz_v1_api: + self.skipTest('No Image v1 API present') - @classmethod - def setUpClass(cls): - super(ImageTests, cls).setUpClass() - json_output = json.loads(cls.openstack( + self.name = uuid.uuid4().hex + json_output = json.loads(self.openstack( '--os-image-api-version 1 ' 'image create -f json ' + - cls.NAME + self.name )) - cls.image_id = json_output["id"] - cls.assertOutput(cls.NAME, json_output['name']) + self.image_id = json_output["id"] + self.assertOutput(self.name, json_output['name']) + + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IMAGE_API_VERSION', '1' + ) + self.useFixture(ver_fixture) - @classmethod - def tearDownClass(cls): + def tearDown(self): try: - cls.openstack( + self.openstack( '--os-image-api-version 1 ' 'image delete ' + - cls.image_id + self.image_id ) finally: - super(ImageTests, cls).tearDownClass() - - def setUp(self): - super(ImageTests, self).setUp() - ver_fixture = fixtures.EnvironmentVariable( - 'OS_IMAGE_API_VERSION', '1' - ) - self.useFixture(ver_fixture) + super(ImageTests, self).tearDown() def test_image_list(self): json_output = json.loads(self.openstack( 'image list -f json ' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) @@ -72,11 +69,11 @@ class ImageTests(base.TestCase): '--min-ram 5 ' + '--disk-format qcow2 ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertEqual( 4, @@ -100,11 +97,11 @@ class ImageTests(base.TestCase): '--property a=b ' + '--property c=d ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertEqual( "a='b', c='d'", diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 278ba5b9..3185c3bd 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -16,59 +16,55 @@ import uuid import fixtures # from glanceclient import exc as image_exceptions -from openstackclient.tests.functional import base +from openstackclient.tests.functional.image import base -class ImageTests(base.TestCase): - """Functional tests for image. """ +class ImageTests(base.BaseImageTests): + """Functional tests for Image commands""" - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex + def setUp(self): + super(ImageTests, self).setUp() - @classmethod - def setUpClass(cls): - super(ImageTests, cls).setUpClass() - json_output = json.loads(cls.openstack( + self.name = uuid.uuid4().hex + self.image_tag = 'my_tag' + json_output = json.loads(self.openstack( '--os-image-api-version 2 ' - 'image create -f json ' + - cls.NAME + 'image create -f json --tag {tag} {name}'.format( + tag=self.image_tag, name=self.name) )) - cls.image_id = json_output["id"] - cls.assertOutput(cls.NAME, json_output['name']) + self.image_id = json_output["id"] + self.assertOutput(self.name, json_output['name']) + + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IMAGE_API_VERSION', '2' + ) + self.useFixture(ver_fixture) - @classmethod - def tearDownClass(cls): + def tearDown(self): try: - cls.openstack( + self.openstack( '--os-image-api-version 2 ' 'image delete ' + - cls.image_id + self.image_id ) finally: - super(ImageTests, cls).tearDownClass() - - def setUp(self): - super(ImageTests, self).setUp() - ver_fixture = fixtures.EnvironmentVariable( - 'OS_IMAGE_API_VERSION', '2' - ) - self.useFixture(ver_fixture) + super(ImageTests, self).tearDown() def test_image_list(self): json_output = json.loads(self.openstack( 'image list -f json ' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) def test_image_list_with_name_filter(self): json_output = json.loads(self.openstack( - 'image list --name ' + self.NAME + ' -f json' + 'image list --name ' + self.name + ' -f json' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) @@ -81,6 +77,16 @@ class ImageTests(base.TestCase): [img['Status'] for img in json_output] ) + def test_image_list_with_tag_filter(self): + json_output = json.loads(self.openstack( + 'image list --tag ' + self.image_tag + ' --long -f json' + )) + for taglist in [img['Tags'].split(', ') for img in json_output]: + self.assertIn( + self.image_tag, + taglist + ) + def test_image_attributes(self): """Test set, unset, show on attributes, tags and properties""" @@ -90,11 +96,11 @@ class ImageTests(base.TestCase): '--min-disk 4 ' + '--min-ram 5 ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertEqual( 4, @@ -115,59 +121,63 @@ class ImageTests(base.TestCase): '--property a=b ' + '--property c=d ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) - self.assertEqual( - "a='b', c='d'", - json_output["properties"], - ) + # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of + # new artributes in the returned data + self.assertIn("a='b'", json_output["properties"]) + self.assertIn("c='d'", json_output["properties"]) self.openstack( 'image unset ' + '--property a ' + '--property c ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) - self.assertNotIn( - 'properties', - json_output, - ) + # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of + # new artributes in the returned data + self.assertNotIn("a='b'", json_output["properties"]) + self.assertNotIn("c='d'", json_output["properties"]) # Test tags + self.assertNotIn( + '01', + json_output["tags"].split(', ') + ) self.openstack( 'image set ' + '--tag 01 ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) - self.assertEqual( + self.assertIn( '01', - json_output["tags"], + json_output["tags"].split(', ') ) self.openstack( 'image unset ' + '--tag 01 ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) - self.assertEqual( - '', - json_output["tags"], + self.assertNotIn( + '01', + json_output["tags"].split(', ') ) def test_image_set_rename(self): @@ -207,7 +217,7 @@ class ImageTests(base.TestCase): json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) # NOTE(dtroyer): Until OSC supports --shared flags in create and set # we can not properly test membership. Sometimes the @@ -215,47 +225,47 @@ class ImageTests(base.TestCase): if json_output["visibility"] == 'shared': self.openstack( 'image add project ' + - self.NAME + ' ' + + self.name + ' ' + my_project_id ) # self.addCleanup( # self.openstack, # 'image remove project ' + - # self.NAME + ' ' + + # self.name + ' ' + # my_project_id # ) self.openstack( 'image set ' + '--accept ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image list -f json ' + '--shared' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) self.openstack( 'image set ' + '--reject ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image list -f json ' + '--shared' )) # self.assertNotIn( - # self.NAME, + # self.name, # [img['Name'] for img in json_output] # ) self.openstack( 'image remove project ' + - self.NAME + ' ' + + self.name + ' ' + my_project_id ) @@ -265,11 +275,11 @@ class ImageTests(base.TestCase): # image_exceptions.HTTPForbidden, # self.openstack, # 'image add project ' + - # self.NAME + ' ' + + # self.name + ' ' + # my_project_id # ) # self.openstack( # 'image set ' + # '--share ' + - # self.NAME + # self.name # ) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 40fb382a..9cef135f 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -209,7 +209,7 @@ class NetworkTests(common.NetworkTagTests): cmd_output["description"], ) - del_output = self.openstack('network delete ' + name1 + ' ' + name2) + del_output = self.openstack('network delete %s %s' % (name1, name2)) self.assertOutput('', del_output) def test_network_list(self): @@ -224,7 +224,7 @@ class NetworkTests(common.NetworkTagTests): network_options + name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) + self.addCleanup(self.openstack, 'network delete %s' % name1) self.assertIsNotNone(cmd_output["id"]) if self.haz_network: self.assertEqual( @@ -264,10 +264,8 @@ class NetworkTests(common.NetworkTagTests): else: network_options = '--subnet 4.5.6.7/28 ' cmd_output = json.loads(self.openstack( - 'network create -f json ' + - '--share ' + - network_options + - name2 + 'network create -f json --share %s%s' % + (network_options, name2) )) self.addCleanup(self.openstack, 'network delete ' + name2) self.assertIsNotNone(cmd_output["id"]) @@ -313,8 +311,7 @@ class NetworkTests(common.NetworkTagTests): # Test list --long if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--long" + "network list -f json --long" )) col_name = [x["Name"] for x in cmd_output] self.assertIn(name1, col_name) @@ -323,9 +320,7 @@ class NetworkTests(common.NetworkTagTests): # Test list --long --enable if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--enable " + - "--long" + "network list -f json --enable --long" )) col_name = [x["Name"] for x in cmd_output] self.assertIn(name1, col_name) @@ -334,9 +329,7 @@ class NetworkTests(common.NetworkTagTests): # Test list --long --disable if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--disable " + - "--long" + "network list -f json --disable --long" )) col_name = [x["Name"] for x in cmd_output] self.assertNotIn(name1, col_name) @@ -345,8 +338,7 @@ class NetworkTests(common.NetworkTagTests): # Test list --share if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--share " + "network list -f json --share " )) col_name = [x["Name"] for x in cmd_output] self.assertNotIn(name1, col_name) @@ -355,8 +347,7 @@ class NetworkTests(common.NetworkTagTests): # Test list --no-share if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--no-share " + "network list -f json --no-share " )) col_name = [x["Name"] for x in cmd_output] self.assertIn(name1, col_name) @@ -368,12 +359,10 @@ class NetworkTests(common.NetworkTagTests): name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( - 'network create -f json ' + - '--description aaaa ' + - name1 + 'network create -f json --description aaaa %s' % name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) + self.addCleanup(self.openstack, 'network delete %s' % name1) # Get network ID network_id = cmd_output1['id'] @@ -386,20 +375,19 @@ class NetworkTests(common.NetworkTagTests): # Add Agent to Network self.openstack( - 'network agent add network --dhcp ' - + agent_id + ' ' + network_id + 'network agent add network --dhcp %s %s' % (agent_id, network_id) ) # Test network list --agent cmd_output3 = json.loads(self.openstack( - 'network list -f json --agent ' + agent_id + 'network list -f json --agent %s' % agent_id )) # Cleanup # Remove Agent from Network self.openstack( - 'network agent remove network --dhcp ' - + agent_id + ' ' + network_id + 'network agent remove network --dhcp %s %s' % + (agent_id, network_id) ) # Assert @@ -415,16 +403,16 @@ class NetworkTests(common.NetworkTagTests): name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'network create -f json ' + - '--description aaaa ' + - '--enable ' + - '--no-share ' + - '--internal ' + - '--no-default ' + - '--enable-port-security ' + + 'network create -f json ' + '--description aaaa ' + '--enable ' + '--no-share ' + '--internal ' + '--no-default ' + '--enable-port-security %s' % name )) - self.addCleanup(self.openstack, 'network delete ' + name) + self.addCleanup(self.openstack, 'network delete %s' % name) self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'aaaa', @@ -453,12 +441,12 @@ class NetworkTests(common.NetworkTagTests): ) raw_output = self.openstack( - 'network set ' + - '--description cccc ' + - '--disable ' + - '--share ' + - '--external ' + - '--disable-port-security ' + + 'network set ' + '--description cccc ' + '--disable ' + '--share ' + '--external ' + '--disable-port-security %s' % name ) self.assertOutput('', raw_output) diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 0c74ea1d..86769e0c 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -42,8 +42,7 @@ class NetworkAgentTests(common.NetworkTests): # agent show cmd_output = json.loads(self.openstack( - 'network agent show -f json ' + - agent_ids[0] + 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( agent_ids[0], @@ -52,15 +51,12 @@ class NetworkAgentTests(common.NetworkTests): # agent set raw_output = self.openstack( - 'network agent set ' + - '--disable ' + - agent_ids[0] + 'network agent set --disable %s' % agent_ids[0] ) self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( - 'network agent show -f json ' + - agent_ids[0] + 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( "DOWN", @@ -68,15 +64,12 @@ class NetworkAgentTests(common.NetworkTests): ) raw_output = self.openstack( - 'network agent set ' + - '--enable ' + - agent_ids[0] + 'network agent set --enable %s' % agent_ids[0] ) self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( - 'network agent show -f json ' + - agent_ids[0] + 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( "UP", @@ -98,12 +91,10 @@ class NetworkAgentListTests(common.NetworkTests): name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( - 'network create -f json ' + - '--description aaaa ' + - name1 + 'network create -f json --description aaaa %s' % name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) + self.addCleanup(self.openstack, 'network delete %s' % name1) # Get network ID network_id = cmd_output1['id'] @@ -116,20 +107,20 @@ class NetworkAgentListTests(common.NetworkTests): # Add Agent to Network self.openstack( - 'network agent add network --dhcp ' - + agent_id + ' ' + network_id + 'network agent add network --dhcp %s %s' % + (agent_id, network_id) ) # Test network agent list --network cmd_output3 = json.loads(self.openstack( - 'network agent list -f json --network ' + network_id + 'network agent list -f json --network %s' % network_id )) # Cleanup # Remove Agent from Network self.openstack( - 'network agent remove network --dhcp ' - + agent_id + ' ' + network_id + 'network agent remove network --dhcp %s %s' % + (agent_id, network_id) ) # Assert @@ -142,9 +133,9 @@ class NetworkAgentListTests(common.NetworkTests): """Add agent to router, list agents on router, delete.""" name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'router create -f json ' + name)) + 'router create -f json %s' % name)) - self.addCleanup(self.openstack, 'router delete ' + name) + self.addCleanup(self.openstack, 'router delete %s' % name) # Get router ID router_id = cmd_output['id'] # Get l3 agent id @@ -157,19 +148,19 @@ class NetworkAgentListTests(common.NetworkTests): # Add router to agent self.openstack( - 'network agent add router --l3 ' + agent_id + ' ' + router_id) + 'network agent add router --l3 %s %s' % (agent_id, router_id)) # Test router list --agent cmd_output = json.loads(self.openstack( - 'network agent list -f json --router ' + router_id)) + 'network agent list -f json --router %s' % router_id)) agent_ids = [x['ID'] for x in cmd_output] self.assertIn(agent_id, agent_ids) # Remove router from agent self.openstack( - 'network agent remove router --l3 ' + agent_id + ' ' + router_id) + 'network agent remove router --l3 %s %s' % (agent_id, router_id)) cmd_output = json.loads(self.openstack( - 'network agent list -f json --router ' + router_id)) + 'network agent list -f json --router %s' % router_id)) agent_ids = [x['ID'] for x in cmd_output] self.assertNotIn(agent_id, agent_ids) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py index 47e7b440..ba3de2cd 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -39,13 +39,13 @@ class NetworkFlavorTests(common.NetworkTests): # Create Service Flavor cmd_output2 = json.loads(self.openstack( 'network flavor profile create -f json --description ' - + 'fakedescription' + ' --enable --metainfo ' + 'Extrainfo' + 'fakedescription --enable --metainfo Extrainfo' )) service_profile_id = cmd_output2.get('id') - self.addCleanup(self.openstack, 'network flavor delete ' + + self.addCleanup(self.openstack, 'network flavor delete %s' % flavor_id) - self.addCleanup(self.openstack, 'network flavor profile delete ' + + self.addCleanup(self.openstack, 'network flavor profile delete %s' % service_profile_id) # Add flavor to service profile self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 770abe94..98e588e8 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -28,62 +28,60 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): if not self.haz_network: self.skipTest("No Network service present") - self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex self.openstack( - 'network qos policy create ' + - self.QOS_POLICY_NAME + 'network qos policy create %s' % self.QOS_POLICY_NAME ) self.addCleanup(self.openstack, - 'network qos policy delete ' + self.QOS_POLICY_NAME) + 'network qos policy delete %s' % self.QOS_POLICY_NAME) cmd_output = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type minimum-bandwidth ' + - '--min-kbps 2800 ' + - '--egress ' + + 'network qos rule create -f json ' + '--type minimum-bandwidth ' + '--min-kbps 2800 ' + '--egress %s' % self.QOS_POLICY_NAME )) self.RULE_ID = cmd_output['id'] self.addCleanup(self.openstack, - 'network qos rule delete ' + - self.QOS_POLICY_NAME + ' ' + - self.RULE_ID) + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) self.assertTrue(self.RULE_ID) def test_qos_rule_create_delete(self): # This is to check the output of qos rule delete policy_name = uuid.uuid4().hex - self.openstack('network qos policy create -f json ' + policy_name) + self.openstack('network qos policy create -f json %s' % policy_name) self.addCleanup(self.openstack, - 'network qos policy delete ' + policy_name) + 'network qos policy delete %s' % policy_name) rule = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type minimum-bandwidth ' + - '--min-kbps 2800 ' + - '--egress ' + policy_name + 'network qos rule create -f json ' + '--type minimum-bandwidth ' + '--min-kbps 2800 ' + '--egress %s' % policy_name )) raw_output = self.openstack( - 'network qos rule delete ' + - policy_name + ' ' + rule['id']) + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) self.assertEqual('', raw_output) def test_qos_rule_list(self): cmd_output = json.loads(self.openstack( - 'network qos rule list -f json ' + self.QOS_POLICY_NAME)) + 'network qos rule list -f json %s' % self.QOS_POLICY_NAME)) self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --min-kbps 7500 ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.openstack('network qos rule set --min-kbps 7500 %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(7500, cmd_output['min_kbps']) @@ -96,58 +94,57 @@ class NetworkQosRuleTestsDSCPMarking(common.NetworkTests): if not self.haz_network: self.skipTest("No Network service present") - self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex self.openstack( - 'network qos policy create ' + - self.QOS_POLICY_NAME + 'network qos policy create %s' % self.QOS_POLICY_NAME ) self.addCleanup(self.openstack, - 'network qos policy delete ' + self.QOS_POLICY_NAME) + 'network qos policy delete %s' % self.QOS_POLICY_NAME) cmd_output = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type dscp-marking ' + - '--dscp-mark 8 ' + + 'network qos rule create -f json ' + '--type dscp-marking ' + '--dscp-mark 8 %s' % self.QOS_POLICY_NAME )) self.RULE_ID = cmd_output['id'] self.addCleanup(self.openstack, - 'network qos rule delete ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) self.assertTrue(self.RULE_ID) def test_qos_rule_create_delete(self): # This is to check the output of qos rule delete policy_name = uuid.uuid4().hex - self.openstack('network qos policy create -f json ' + policy_name) + self.openstack('network qos policy create -f json %s' % policy_name) self.addCleanup(self.openstack, - 'network qos policy delete ' + policy_name) + 'network qos policy delete %s' % policy_name) rule = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type dscp-marking ' + - '--dscp-mark 8 ' + policy_name + 'network qos rule create -f json ' + '--type dscp-marking ' + '--dscp-mark 8 %s' % policy_name )) raw_output = self.openstack( - 'network qos rule delete ' + - policy_name + ' ' + rule['id']) + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) self.assertEqual('', raw_output) def test_qos_rule_list(self): cmd_output = json.loads(self.openstack( - 'network qos rule list -f json ' + self.QOS_POLICY_NAME)) + 'network qos rule list -f json %s' % self.QOS_POLICY_NAME)) self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --dscp-mark 32 ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.openstack('network qos rule set --dscp-mark 32 %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(32, cmd_output['dscp_mark']) @@ -160,65 +157,63 @@ class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests): if not self.haz_network: self.skipTest("No Network service present") - self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex self.openstack( - 'network qos policy create ' + - self.QOS_POLICY_NAME + 'network qos policy create %s' % self.QOS_POLICY_NAME ) self.addCleanup(self.openstack, - 'network qos policy delete ' + self.QOS_POLICY_NAME) + 'network qos policy delete %s' % self.QOS_POLICY_NAME) cmd_output = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type bandwidth-limit ' + - '--max-kbps 10000 ' + - '--max-burst-kbits 1400 ' + - '--egress ' + + 'network qos rule create -f json ' + '--type bandwidth-limit ' + '--max-kbps 10000 ' + '--egress %s' % self.QOS_POLICY_NAME )) self.RULE_ID = cmd_output['id'] self.addCleanup(self.openstack, - 'network qos rule delete ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) self.assertTrue(self.RULE_ID) def test_qos_rule_create_delete(self): # This is to check the output of qos rule delete policy_name = uuid.uuid4().hex - self.openstack('network qos policy create -f json ' + policy_name) + self.openstack('network qos policy create -f json %s' % policy_name) self.addCleanup(self.openstack, - 'network qos policy delete ' + policy_name) + 'network qos policy delete %s' % policy_name) rule = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type bandwidth-limit ' + - '--max-kbps 10000 ' + - '--max-burst-kbits 1400 ' + - '--egress ' + policy_name + 'network qos rule create -f json ' + '--type bandwidth-limit ' + '--max-kbps 10000 ' + '--max-burst-kbits 1400 ' + '--egress %s' % policy_name )) raw_output = self.openstack( - 'network qos rule delete ' + - policy_name + ' ' + rule['id']) + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) self.assertEqual('', raw_output) def test_qos_rule_list(self): cmd_output = json.loads(self.openstack( - 'network qos rule list -f json ' - + self.QOS_POLICY_NAME)) + 'network qos rule list -f json %s' % + self.QOS_POLICY_NAME)) self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --max-kbps 15000 ' + - '--max-burst-kbits 1800 ' + - '--ingress ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.openstack('network qos rule set --max-kbps 15000 ' + '--max-burst-kbits 1800 ' + '--ingress %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(15000, cmd_output['max_kbps']) self.assertEqual(1800, cmd_output['max_burst_kbps']) self.assertEqual('ingress', cmd_output['direction']) diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index 8940273f..6ffb11cf 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -113,9 +113,20 @@ class NetworkSegmentTests(common.NetworkTests): self.openstack, 'network segment delete ' + name ) - self.assertIsNone( - json_output["description"], - ) + + extension_output = json.loads(self.openstack( + "extension list -f json " + )) + ext_alias = [x["Alias"] for x in extension_output] + if "standard-attr-segment" in ext_alias: + self.assertEqual( + '', + json_output["description"], + ) + else: + self.assertIsNone( + json_output["description"], + ) new_description = 'new_description' cmd_output = self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 7357c0ed..e3067d90 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -33,7 +33,7 @@ class PortTests(common.NetworkTagTests): # Create a network for the port tests cls.openstack( - 'network create ' + cls.NETWORK_NAME + 'network create %s' % cls.NETWORK_NAME ) @classmethod @@ -41,7 +41,7 @@ class PortTests(common.NetworkTagTests): try: if cls.haz_network: raw_output = cls.openstack( - 'network delete ' + cls.NETWORK_NAME + 'network delete %s' % cls.NETWORK_NAME ) cls.assertOutput('', raw_output) finally: @@ -56,8 +56,8 @@ class PortTests(common.NetworkTagTests): def test_port_delete(self): """Test create, delete multiple""" json_output = json.loads(self.openstack( - 'port create -f json --network ' + - self.NETWORK_NAME + ' ' + self.NAME + 'port create -f json --network %s %s' % + (self.NETWORK_NAME, self.NAME) )) id1 = json_output.get('id') self.assertIsNotNone(id1) @@ -65,8 +65,8 @@ class PortTests(common.NetworkTagTests): self.assertEqual(self.NAME, json_output.get('name')) json_output = json.loads(self.openstack( - 'port create -f json --network ' + self.NETWORK_NAME + ' ' + - self.NAME + 'x' + 'port create -f json --network %s %sx' % + (self.NETWORK_NAME, self.NAME) )) id2 = json_output.get('id') self.assertIsNotNone(id2) @@ -74,31 +74,31 @@ class PortTests(common.NetworkTagTests): self.assertEqual(self.NAME + 'x', json_output.get('name')) # Clean up after ourselves - raw_output = self.openstack('port delete ' + id1 + ' ' + id2) + raw_output = self.openstack('port delete %s %s' % (id1, id2)) self.assertOutput('', raw_output) def test_port_list(self): """Test create defaults, list, delete""" json_output = json.loads(self.openstack( - 'port create -f json --network ' + self.NETWORK_NAME + ' ' + - self.NAME + 'port create -f json --network %s %s' % + (self.NETWORK_NAME, self.NAME) )) id1 = json_output.get('id') self.assertIsNotNone(id1) mac1 = json_output.get('mac_address') self.assertIsNotNone(mac1) - self.addCleanup(self.openstack, 'port delete ' + id1) + self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(self.NAME, json_output.get('name')) json_output = json.loads(self.openstack( - 'port create -f json --network ' + self.NETWORK_NAME + ' ' + - self.NAME + 'x' + 'port create -f json --network %s %sx' % + (self.NETWORK_NAME, self.NAME) )) id2 = json_output.get('id') self.assertIsNotNone(id2) mac2 = json_output.get('mac_address') self.assertIsNotNone(mac2) - self.addCleanup(self.openstack, 'port delete ' + id2) + self.addCleanup(self.openstack, 'port delete %s' % id2) self.assertEqual(self.NAME + 'x', json_output.get('name')) # Test list @@ -122,7 +122,7 @@ class PortTests(common.NetworkTagTests): # Test list --mac-address json_output = json.loads(self.openstack( - 'port list -f json --mac-address ' + mac2 + 'port list -f json --mac-address %s' % mac2 )) item_map = {item.get('ID'): item.get('MAC Address') for item in json_output} @@ -145,26 +145,26 @@ class PortTests(common.NetworkTagTests): """Test create, set, show, delete""" name = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'port create -f json ' + - '--network ' + self.NETWORK_NAME + ' ' + - '--description xyzpdq ' + - '--disable ' + - name + 'port create -f json ' + '--network %s ' + '--description xyzpdq ' + '--disable %s' % + (self.NETWORK_NAME, name) )) id1 = json_output.get('id') - self.addCleanup(self.openstack, 'port delete ' + id1) + self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) self.assertEqual('DOWN', json_output.get('admin_state_up')) raw_output = self.openstack( - 'port set ' + '--enable ' + + 'port set --enable %s' % name ) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) sg_id = json_output.get('security_group_ids') @@ -174,30 +174,30 @@ class PortTests(common.NetworkTagTests): self.assertIsNotNone(json_output.get('mac_address')) raw_output = self.openstack( - 'port unset --security-group ' + sg_id + ' ' + id1) + 'port unset --security-group %s %s' % (sg_id, id1)) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) self.assertEqual('', json_output.get('security_group_ids')) def test_port_admin_set(self): """Test create, set (as admin), show, delete""" json_output = json.loads(self.openstack( - 'port create -f json ' + - '--network ' + self.NETWORK_NAME + ' ' + self.NAME + 'port create -f json ' + '--network %s %s' % (self.NETWORK_NAME, self.NAME) )) id_ = json_output.get('id') - self.addCleanup(self.openstack, 'port delete ' + id_) + self.addCleanup(self.openstack, 'port delete %s' % id_) raw_output = self.openstack( '--os-username admin ' - + 'port set --mac-address 11:22:33:44:55:66 ' - + self.NAME) + 'port set --mac-address 11:22:33:44:55:66 %s' % + self.NAME) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + self.NAME + 'port show -f json %s' % self.NAME )) self.assertEqual(json_output.get('mac_address'), '11:22:33:44:55:66') @@ -205,41 +205,41 @@ class PortTests(common.NetworkTagTests): """Test create, set, show, delete""" sg_name1 = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'security group create -f json ' + + 'security group create -f json %s' % sg_name1 )) sg_id1 = json_output.get('id') - self.addCleanup(self.openstack, 'security group delete ' + sg_id1) + self.addCleanup(self.openstack, 'security group delete %s' % sg_id1) sg_name2 = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'security group create -f json ' + + 'security group create -f json %s' % sg_name2 )) sg_id2 = json_output.get('id') - self.addCleanup(self.openstack, 'security group delete ' + sg_id2) + self.addCleanup(self.openstack, 'security group delete %s' % sg_id2) name = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'port create -f json ' + - '--network ' + self.NETWORK_NAME + ' ' + - '--security-group ' + sg_name1 + ' ' + - name + 'port create -f json ' + '--network %s ' + '--security-group %s %s' % + (self.NETWORK_NAME, sg_name1, name) )) id1 = json_output.get('id') - self.addCleanup(self.openstack, 'port delete ' + id1) + self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) self.assertEqual(sg_id1, json_output.get('security_group_ids')) raw_output = self.openstack( - 'port set ' + - '--security-group ' + sg_name2 + ' ' + - name + 'port set ' + '--security-group %s %s' % + (sg_name2, name) ) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) self.assertEqual(name, json_output.get('name')) self.assertIn( @@ -254,11 +254,11 @@ class PortTests(common.NetworkTagTests): ) raw_output = self.openstack( - 'port unset --security-group ' + sg_id1 + ' ' + id1) + 'port unset --security-group %s %s' % (sg_id1, id1)) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) self.assertEqual( # TODO(dtroyer): output formatters should do this on JSON! diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py index d1a73c54..226ef8ad 100644 --- a/openstackclient/tests/functional/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -53,39 +53,39 @@ class ObjectTests(common.ObjectStoreTests): self.openstack('container save ' + self.CONTAINER_NAME) # TODO(stevemar): Assert returned fields - raw_output = self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + object_file) + raw_output = self.openstack('object create %s %s' % + (self.CONTAINER_NAME, object_file)) items = self.parse_listing(raw_output) self.assert_show_fields(items, OBJECT_FIELDS) - raw_output = self.openstack('object list ' + self.CONTAINER_NAME) + raw_output = self.openstack('object list %s' % self.CONTAINER_NAME) items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_LIST_HEADERS) - self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + object_file) + self.openstack('object save %s %s' % + (self.CONTAINER_NAME, object_file)) # TODO(stevemar): Assert returned fields tmp_file = 'tmp.txt' self.addCleanup(os.remove, tmp_file) - self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + object_file + ' --file ' + tmp_file) + self.openstack('object save %s %s --file %s' % + (self.CONTAINER_NAME, object_file, tmp_file)) # TODO(stevemar): Assert returned fields - raw_output = self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + object_file + ' --file -') + raw_output = self.openstack('object save %s %s --file -' % + (self.CONTAINER_NAME, object_file)) self.assertEqual(raw_output, 'test content') - self.openstack('object show ' + self.CONTAINER_NAME - + ' ' + object_file) + self.openstack('object show %s %s' % + (self.CONTAINER_NAME, object_file)) # TODO(stevemar): Assert returned fields - raw_output = self.openstack('object delete ' + self.CONTAINER_NAME - + ' ' + object_file) + raw_output = self.openstack('object delete %s %s' % + (self.CONTAINER_NAME, object_file)) self.assertEqual(0, len(raw_output)) - self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + object_file) - raw_output = self.openstack('container delete -r ' + + self.openstack('object create %s %s' % + (self.CONTAINER_NAME, object_file)) + raw_output = self.openstack('container delete -r %s' % self.CONTAINER_NAME) self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index 73191fc9..0399e6cc 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -29,20 +29,13 @@ class TransferRequestTests(common.BaseVolumeTests): 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) - cmd_output = json.loads(cls.openstack( - 'volume transfer request create -f json ' + - cls.VOLUME_NAME + - ' --name ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) + cls.wait_for_status("volume", cls.VOLUME_NAME, "available") @classmethod def tearDownClass(cls): try: - raw_output_transfer = cls.openstack( - 'volume transfer request delete ' + cls.NAME) raw_output_volume = cls.openstack( 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_transfer) cls.assertOutput('', raw_output_volume) finally: super(TransferRequestTests, cls).tearDownClass() @@ -79,12 +72,28 @@ class TransferRequestTests(common.BaseVolumeTests): 'volume delete ' + volume_name) self.assertEqual('', raw_output) - def test_volume_transfer_request_list(self): + def test_volume_transfer_request_list_show(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'volume transfer request list -f json')) - self.assertIn(self.NAME, [req['Name'] for req in cmd_output]) + 'volume transfer request create -f json ' + + ' --name ' + name + ' ' + + self.VOLUME_NAME + )) + self.addCleanup( + self.openstack, + 'volume transfer request delete ' + name + ) + self.assertOutput(name, cmd_output['name']) + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) - def test_volume_transfer_request_show(self): cmd_output = json.loads(self.openstack( - 'volume transfer request show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + 'volume transfer request list -f json' + )) + self.assertIn(name, [req['Name'] for req in cmd_output]) + + cmd_output = json.loads(self.openstack( + 'volume transfer request show -f json ' + + name + )) + self.assertEqual(name, cmd_output['name']) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index 74e14070..eb9d7f64 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -20,70 +20,100 @@ from openstackclient.tests.functional.volume.v1 import common class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ - NAME = uuid.uuid4().hex - - @classmethod - def setUpClass(cls): - super(VolumeTypeTests, cls).setUpClass() - cmd_output = json.loads(cls.openstack( - 'volume type create -f json ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) - - @classmethod - def tearDownClass(cls): - try: - raw_output = cls.openstack('volume type delete ' + cls.NAME) - cls.assertOutput('', raw_output) - finally: - super(VolumeTypeTests, cls).tearDownClass() - - def test_volume_type_list(self): + def test_volume_type_create_list(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + + name, + ) + self.assertEqual(name, cmd_output['name']) + + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual(self.NAME, cmd_output['name']) + cmd_output = json.loads(self.openstack('volume type list -f json')) self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) - def test_volume_type_show(self): cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + 'volume type list -f json --default' + )) + self.assertEqual(1, len(cmd_output)) + self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) def test_volume_type_set_unset_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) - cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) + # TODO(amotoki): properties output should be machine-readable self.assertEqual("a='b', c='d'", cmd_output['properties']) - raw_output = self.openstack('volume type unset --property a ' - + self.NAME) + raw_output = self.openstack( + 'volume type unset --property a %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) - cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( - 'volume type unset --property a --property c ' + self.NAME) + 'volume type unset --property a --property c %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("", cmd_output['properties']) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex vol_type2 = uuid.uuid4().hex - self.openstack('volume type create ' + vol_type1) + self.openstack('volume type create %s' % vol_type1) time.sleep(5) - self.openstack('volume type create ' + vol_type2) + self.openstack('volume type create %s' % vol_type2) time.sleep(5) cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) raw_output = self.openstack(cmd) @@ -140,8 +170,21 @@ class VolumeTypeTests(common.BaseVolumeTests): '--encryption-control-location front-end ' + self.NAME) self.assertEqual('', raw_output) + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", @@ -150,10 +193,12 @@ class VolumeTypeTests(common.BaseVolumeTests): self.assertIn(attr, cmd_output['encryption']) # test unset encryption type raw_output = self.openstack( - 'volume type unset --encryption-type ' + self.NAME) + 'volume type unset --encryption-type ' + name + ) self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) self.assertEqual('', cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 888f12b1..646becc1 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -125,7 +125,6 @@ class QosTests(common.BaseVolumeTests): def test_volume_qos_asso_disasso(self): """Tests associate and disassociate qos with volume type""" vol_type1 = uuid.uuid4().hex - vol_type2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume type create -f json ' + vol_type1 @@ -134,6 +133,9 @@ class QosTests(common.BaseVolumeTests): vol_type1, cmd_output['name'] ) + self.addCleanup(self.openstack, 'volume type delete ' + vol_type1) + + vol_type2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume type create -f json ' + vol_type2 @@ -142,7 +144,6 @@ class QosTests(common.BaseVolumeTests): vol_type2, cmd_output['name'] ) - self.addCleanup(self.openstack, 'volume type delete ' + vol_type1) self.addCleanup(self.openstack, 'volume type delete ' + vol_type2) name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index 33495af6..33d8ce77 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -21,29 +21,24 @@ class TransferRequestTests(common.BaseVolumeTests): NAME = uuid.uuid4().hex VOLUME_NAME = uuid.uuid4().hex + API_VERSION = '2' @classmethod def setUpClass(cls): super(TransferRequestTests, cls).setUpClass() cmd_output = json.loads(cls.openstack( + '--os-volume-api-version ' + cls.API_VERSION + ' ' + 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) - cmd_output = json.loads(cls.openstack( - 'volume transfer request create -f json ' + - cls.VOLUME_NAME + - ' --name ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) + cls.wait_for_status("volume", cls.VOLUME_NAME, "available") @classmethod def tearDownClass(cls): try: - raw_output_transfer = cls.openstack( - 'volume transfer request delete ' + cls.NAME) raw_output_volume = cls.openstack( 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_transfer) cls.assertOutput('', raw_output_volume) finally: super(TransferRequestTests, cls).tearDownClass() @@ -80,12 +75,31 @@ class TransferRequestTests(common.BaseVolumeTests): 'volume delete ' + volume_name) self.assertEqual('', raw_output) - def test_volume_transfer_request_list(self): + def test_volume_transfer_request_list_show(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'volume transfer request list -f json')) - self.assertIn(self.NAME, [req['Name'] for req in cmd_output]) + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request create -f json ' + + ' --name ' + name + ' ' + + self.VOLUME_NAME + )) + self.addCleanup( + self.openstack, + 'volume transfer request delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) + + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request list -f json' + )) + self.assertIn(name, [req['Name'] for req in cmd_output]) - def test_volume_transfer_request_show(self): cmd_output = json.loads(self.openstack( - 'volume transfer request show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request show -f json ' + + name + )) + self.assertEqual(name, cmd_output['name']) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 99630e6b..d8dd5bd6 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -20,84 +20,121 @@ from openstackclient.tests.functional.volume.v2 import common class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ - NAME = uuid.uuid4().hex - - @classmethod - def setUpClass(cls): - super(VolumeTypeTests, cls).setUpClass() - cmd_output = json.loads(cls.openstack( - 'volume type create -f json --private ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) - - @classmethod - def tearDownClass(cls): - try: - raw_output = cls.openstack('volume type delete ' + cls.NAME) - cls.assertOutput('', raw_output) - finally: - super(VolumeTypeTests, cls).tearDownClass() - - def test_volume_type_list(self): + def test_volume_type_create_list(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual(name, cmd_output['name']) + cmd_output = json.loads(self.openstack('volume type list -f json')) - self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) + self.assertIn(name, [t['Name'] for t in cmd_output]) - def test_volume_type_list_default(self): cmd_output = json.loads(self.openstack( - 'volume type list -f json --default')) + 'volume type list -f json --default' + )) self.assertEqual(1, len(cmd_output)) self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) - def test_volume_type_show(self): + def test_volume_type_set_unset_properties(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) - def test_volume_type_set_unset_properties(self): raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) # TODO(amotoki): properties output should be machine-readable self.assertEqual("a='b', c='d'", cmd_output['properties']) - raw_output = self.openstack('volume type unset --property a ' - + self.NAME) + raw_output = self.openstack( + 'volume type unset --property a %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( - 'volume type unset --property a --property c ' + self.NAME) + 'volume type unset --property a --property c %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("", cmd_output['properties']) def test_volume_type_set_unset_project(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --project admin ' + self.NAME) + 'volume type set --project admin %s' % name + ) self.assertEqual("", raw_output) raw_output = self.openstack( - 'volume type unset --project admin ' + self.NAME) + 'volume type unset --project admin %s' % name + ) self.assertEqual("", raw_output) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex vol_type2 = uuid.uuid4().hex - self.openstack('volume type create ' + vol_type1) + self.openstack('volume type create %s' % vol_type1) time.sleep(5) - self.openstack('volume type create ' + vol_type2) + self.openstack('volume type create %s' % vol_type2) time.sleep(5) cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) raw_output = self.openstack(cmd) @@ -108,6 +145,7 @@ class VolumeTypeTests(common.BaseVolumeTests): # these to new test format when beef up all tests for # volume tye commands. def test_encryption_type(self): + name = uuid.uuid4().hex encryption_type = uuid.uuid4().hex # test create new encryption type cmd_output = json.loads(self.openstack( @@ -162,16 +200,28 @@ class VolumeTypeTests(common.BaseVolumeTests): for attr in expected: self.assertIn(attr, cmd_output['encryption']) # test set new encryption type + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( 'volume type set ' '--encryption-provider LuksEncryptor ' '--encryption-cipher aes-xts-plain64 ' '--encryption-key-size 128 ' '--encryption-control-location front-end ' + - self.NAME) + name) self.assertEqual('', raw_output) + cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", @@ -180,10 +230,12 @@ class VolumeTypeTests(common.BaseVolumeTests): self.assertIn(attr, cmd_output['encryption']) # test unset encryption type raw_output = self.openstack( - 'volume type unset --encryption-type ' + self.NAME) + 'volume type unset --encryption-type ' + name + ) self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) self.assertEqual('', cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py index b3253237..f16dfafa 100644 --- a/openstackclient/tests/functional/volume/v3/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py @@ -17,3 +17,5 @@ from openstackclient.tests.functional.volume.v3 import common class TransferRequestTests(common.BaseVolumeTests, v2.TransferRequestTests): """Functional tests for transfer request. """ + + API_VERSION = '3' diff --git a/openstackclient/tests/unit/common/test_limits.py b/openstackclient/tests/unit/common/test_limits.py new file mode 100644 index 00000000..d73db2cb --- /dev/null +++ b/openstackclient/tests/unit/common/test_limits.py @@ -0,0 +1,125 @@ +# 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.common import limits +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes + + +class TestComputeLimits(compute_fakes.TestComputev2): + + absolute_columns = [ + 'Name', + 'Value', + ] + + rate_columns = [ + "Verb", + "URI", + "Value", + "Remain", + "Unit", + "Next Available" + ] + + def setUp(self): + super(TestComputeLimits, self).setUp() + self.app.client_manager.volume_endpoint_enabled = False + self.compute = self.app.client_manager.compute + + self.fake_limits = compute_fakes.FakeLimits() + self.compute.limits.get.return_value = self.fake_limits + + def test_compute_show_absolute(self): + arglist = ['--absolute'] + verifylist = [('is_absolute', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.absolute_limits() + + self.assertEqual(self.absolute_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(19, len(ret_limits)) + + def test_compute_show_rate(self): + arglist = ['--rate'] + verifylist = [('is_rate', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.rate_limits() + + self.assertEqual(self.rate_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(3, len(ret_limits)) + + +class TestVolumeLimits(volume_fakes.TestVolume): + absolute_columns = [ + 'Name', + 'Value', + ] + + rate_columns = [ + "Verb", + "URI", + "Value", + "Remain", + "Unit", + "Next Available" + ] + + def setUp(self): + super(TestVolumeLimits, self).setUp() + self.app.client_manager.compute_endpoint_enabled = False + self.volume = self.app.client_manager.volume + + self.fake_limits = volume_fakes.FakeLimits() + self.volume.limits.get.return_value = self.fake_limits + + def test_volume_show_absolute(self): + arglist = ['--absolute'] + verifylist = [('is_absolute', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.absolute_limits() + + self.assertEqual(self.absolute_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(10, len(ret_limits)) + + def test_volume_show_rate(self): + arglist = ['--rate'] + verifylist = [('is_rate', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.rate_limits() + + self.assertEqual(self.rate_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(3, len(ret_limits)) diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py index 2385eae8..6e8ce188 100644 --- a/openstackclient/tests/unit/common/test_project_purge.py +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -117,10 +117,11 @@ class TestProjectPurge(TestProjectPurgeInit): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -152,10 +153,11 @@ class TestProjectPurge(TestProjectPurgeInit): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_not_called() self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -187,10 +189,11 @@ class TestProjectPurge(TestProjectPurgeInit): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_not_called() self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -223,10 +226,11 @@ class TestProjectPurge(TestProjectPurgeInit): self.projects_mock.get.assert_not_called() self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -259,10 +263,11 @@ class TestProjectPurge(TestProjectPurgeInit): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -295,10 +300,11 @@ class TestProjectPurge(TestProjectPurgeInit): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 1d97cecb..234bbd9b 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -150,6 +150,9 @@ class FakeComputev2Client(object): self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) + self.servers = mock.Mock() self.servers.resource_class = fakes.FakeResource(None, {}) @@ -765,6 +768,7 @@ class FakeFlavor(object): 'rxtx_factor': 1.0, 'OS-FLV-DISABLED:disabled': False, 'os-flavor-access:is_public': True, + 'description': 'description', 'OS-FLV-EXT-DATA:ephemeral': 0, 'properties': {'property': 'value'}, } @@ -1395,3 +1399,110 @@ class FakeQuota(object): quota.project_id = quota_attrs['id'] return quota + + +class FakeLimits(object): + """Fake limits""" + + def __init__(self, absolute_attrs=None, rate_attrs=None): + self.absolute_limits_attrs = { + 'maxServerMeta': 128, + 'maxTotalInstances': 10, + 'maxPersonality': 5, + 'totalServerGroupsUsed': 0, + 'maxImageMeta': 128, + 'maxPersonalitySize': 10240, + 'maxTotalRAMSize': 51200, + 'maxServerGroups': 10, + 'maxSecurityGroupRules': 20, + 'maxTotalKeypairs': 100, + 'totalCoresUsed': 0, + 'totalRAMUsed': 0, + 'maxSecurityGroups': 10, + 'totalFloatingIpsUsed': 0, + 'totalInstancesUsed': 0, + 'maxServerGroupMembers': 10, + 'maxTotalFloatingIps': 10, + 'totalSecurityGroupsUsed': 0, + 'maxTotalCores': 20, + } + absolute_attrs = absolute_attrs or {} + self.absolute_limits_attrs.update(absolute_attrs) + + self.rate_limits_attrs = [{ + "uri": "*", + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 10, + "verb": "PUT", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 100, + "verb": "DELETE", + "remaining": 100, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + } + ] + }] + + @property + def absolute(self): + for (name, value) in self.absolute_limits_attrs.items(): + yield FakeAbsoluteLimit(name, value) + + def absolute_limits(self): + reference_data = [] + for (name, value) in self.absolute_limits_attrs.items(): + reference_data.append((name, value)) + return reference_data + + @property + def rate(self): + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + yield FakeRateLimit(rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + def rate_limits(self): + reference_data = [] + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + reference_data.append((rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available'])) + return reference_data + + +class FakeAbsoluteLimit(object): + """Data model that represents an absolute limit""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class FakeRateLimit(object): + """Data model that represents a flattened view of a single rate limit""" + + def __init__(self, verb, uri, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 4cdbb25b..a112fc1f 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -16,6 +16,7 @@ import mock from mock import call +import novaclient from osc_lib import exceptions from osc_lib import utils @@ -50,6 +51,7 @@ class TestFlavorCreate(TestFlavor): columns = ( 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', + 'description', 'disk', 'id', 'name', @@ -63,6 +65,7 @@ class TestFlavorCreate(TestFlavor): data = ( flavor.disabled, flavor.ephemeral, + flavor.description, flavor.disk, flavor.id, flavor.name, @@ -101,7 +104,8 @@ class TestFlavorCreate(TestFlavor): 0, 0, 1.0, - True + True, + None, ) columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*default_args) @@ -120,6 +124,7 @@ class TestFlavorCreate(TestFlavor): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--public', + '--description', str(self.flavor.description), '--property', 'property=value', self.flavor.name, ] @@ -132,6 +137,7 @@ class TestFlavorCreate(TestFlavor): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', True), + ('description', self.flavor.description), ('property', {'property': 'value'}), ('name', self.flavor.name), ] @@ -147,8 +153,13 @@ class TestFlavorCreate(TestFlavor): self.flavor.swap, self.flavor.rxtx_factor, self.flavor.is_public, + self.flavor.description, ) - columns, data = self.cmd.take_action(parsed_args) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) self.flavor.set_keys.assert_called_once_with({'property': 'value'}) self.flavor.get_keys.assert_called_once_with() @@ -168,6 +179,7 @@ class TestFlavorCreate(TestFlavor): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', + '--description', str(self.flavor.description), '--project', self.project.id, '--property', 'key1=value1', '--property', 'key2=value2', @@ -181,6 +193,7 @@ class TestFlavorCreate(TestFlavor): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), + ('description', 'description'), ('project', self.project.id), ('property', {'key1': 'value1', 'key2': 'value2'}), ('name', self.flavor.name), @@ -197,8 +210,13 @@ class TestFlavorCreate(TestFlavor): self.flavor.swap, self.flavor.rxtx_factor, self.flavor.is_public, + self.flavor.description, ) - columns, data = self.cmd.take_action(parsed_args) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) self.flavor_access_mock.add_tenant_access.assert_called_with( self.flavor.id, @@ -234,6 +252,79 @@ class TestFlavorCreate(TestFlavor): arglist, verifylist) + def test_flavor_create_with_description_api_newer(self): + arglist = [ + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--disk', str(self.flavor.disk), + '--ephemeral', str(self.flavor.ephemeral), + '--swap', str(self.flavor.swap), + '--vcpus', str(self.flavor.vcpus), + '--rxtx-factor', str(self.flavor.rxtx_factor), + '--private', + '--description', 'fake description', + self.flavor.name, + ] + verifylist = [ + ('id', self.flavor.id), + ('ram', self.flavor.ram), + ('disk', self.flavor.disk), + ('ephemeral', self.flavor.ephemeral), + ('swap', self.flavor.swap), + ('vcpus', self.flavor.vcpus), + ('rxtx_factor', self.flavor.rxtx_factor), + ('public', False), + ('description', 'fake description'), + ('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): + 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', + ) + + self.flavors_mock.create.assert_called_once_with(*args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_with_description_api_older(self): + arglist = [ + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--vcpus', str(self.flavor.vcpus), + '--description', 'description', + self.flavor.name, + ] + verifylist = [ + ('ram', self.flavor.ram), + ('vcpus', self.flavor.vcpus), + ('description', 'description'), + ('name', 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(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorDelete(TestFlavor): @@ -622,6 +713,42 @@ class TestFlavorSet(TestFlavor): self.flavor_access_mock.add_tenant_access.assert_not_called() self.assertIsNone(result) + def test_flavor_set_description_api_newer(self): + arglist = [ + '--description', 'description', + self.flavor.id, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.id), + ] + 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): + result = self.cmd.take_action(parsed_args) + self.flavors_mock.update.assert_called_with( + flavor=self.flavor.id, description='description') + self.assertIsNone(result) + + def test_flavor_set_description_api_older(self): + arglist = [ + '--description', 'description', + self.flavor.id, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.id), + ] + 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): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorShow(TestFlavor): @@ -633,6 +760,7 @@ class TestFlavorShow(TestFlavor): 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', 'access_project_ids', + 'description', 'disk', 'id', 'name', @@ -648,6 +776,7 @@ class TestFlavorShow(TestFlavor): flavor.disabled, flavor.ephemeral, None, + flavor.description, flavor.disk, flavor.id, flavor.name, @@ -710,6 +839,7 @@ class TestFlavorShow(TestFlavor): private_flavor.disabled, private_flavor.ephemeral, self.flavor_access.tenant_id, + private_flavor.description, private_flavor.disk, private_flavor.id, private_flavor.name, diff --git a/openstackclient/tests/unit/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py index 329095de..244da413 100644 --- a/openstackclient/tests/unit/compute/v2/test_host.py +++ b/openstackclient/tests/unit/compute/v2/test_host.py @@ -111,8 +111,7 @@ class TestHostSet(TestHost): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - body = {} - h_mock.assert_called_with(self.host['host'], body) + h_mock.assert_called_with(self.host['host']) def test_host_set(self, h_mock): h_mock.return_value = self.host @@ -133,8 +132,8 @@ class TestHostSet(TestHost): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - body = {'status': 'enable', 'maintenance_mode': 'disable'} - h_mock.assert_called_with(self.host['host'], body) + h_mock.assert_called_with(self.host['host'], status='enable', + maintenance_mode='disable') @mock.patch( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8f82e7cd..44e433d8 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1075,7 +1075,7 @@ class TestServerCreate(TestServer): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.new_server.id, - callback=server._show_progress, + callback=mock.ANY, ) kwargs = dict( @@ -1125,7 +1125,7 @@ class TestServerCreate(TestServer): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.new_server.id, - callback=server._show_progress, + callback=mock.ANY, ) kwargs = dict( @@ -1578,6 +1578,164 @@ class TestServerCreate(TestServer): self.cmd.take_action, parsed_args) + def test_server_create_image_property(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + 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, + 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, + image_info, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_image_property_multi(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--image-property', 'hw_disk_bus=ide', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + 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, + 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, + image_info, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_image_property_missed(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--image-property', 'hw_disk_bus=virtio', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu', + 'hw_disk_bus': 'virtio'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): @@ -1644,7 +1802,7 @@ class TestServerDelete(TestServer): mock_wait_for_delete.assert_called_once_with( self.servers_mock, servers[0].id, - callback=server._show_progress + callback=mock.ANY, ) self.assertIsNone(result) @@ -1666,7 +1824,7 @@ class TestServerDelete(TestServer): mock_wait_for_delete.assert_called_once_with( self.servers_mock, servers[0].id, - callback=server._show_progress + callback=mock.ANY, ) @@ -1839,6 +1997,25 @@ class TestServerList(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_no_servers(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ('deleted', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.servers_mock.list.return_value = [] + self.data = () + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(0, self.images_mock.list.call_count) + self.assertEqual(0, self.flavors_mock.list.call_count) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_long_option(self): arglist = [ '--long', @@ -2080,6 +2257,9 @@ class TestServerMigrate(TestServer): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2101,6 +2281,9 @@ class TestServerMigrate(TestServer): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2122,6 +2305,9 @@ class TestServerMigrate(TestServer): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2144,6 +2330,9 @@ class TestServerMigrate(TestServer): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2153,6 +2342,28 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) + def test_server_live_migrate_225(self): + arglist = [ + '--live', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('block_migration', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.25') + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_migrate_with_wait(self, mock_wait_for_status): arglist = [ @@ -2223,17 +2434,17 @@ class TestServerRebuild(TestServer): self.images_mock.get.return_value = self.image # Fake the rebuilt new server. - new_server = compute_fakes.FakeServer.create_one_server() - - # Fake the server to be rebuilt. The IDs of them should be the same. attrs = { - 'id': new_server.id, '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 = { 'rebuild': new_server, } @@ -2302,7 +2513,7 @@ class TestServerRebuild(TestServer): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress, + callback=mock.ANY, # **kwargs ) @@ -2327,13 +2538,34 @@ class TestServerRebuild(TestServer): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress + callback=mock.ANY, ) self.servers_mock.get.assert_called_with(self.server.id) self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) + def test_rebuild_with_property(self): + arglist = [ + self.server.id, + '--property', 'key1=value1', + '--property', 'key2=value2' + ] + expected_property = {'key1': 'value1', 'key2': 'value2'} + verifylist = [ + ('server', self.server.id), + ('property', expected_property) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.images_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, meta=expected_property) + class TestServerRemoveFixedIP(TestServer): @@ -2813,7 +3045,7 @@ class TestServerResize(TestServer): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress, + callback=mock.ANY, **kwargs ) @@ -2853,7 +3085,7 @@ class TestServerResize(TestServer): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress, + callback=mock.ANY, **kwargs ) @@ -3109,6 +3341,33 @@ class TestServerShow(TestServer): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_show_embedded_flavor(self): + # Tests using --os-compute-api-version >= 2.47 where the flavor + # details are embedded in the server response body excluding the id. + arglist = [ + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.server.info['flavor'] = { + 'ephemeral': 0, + 'ram': 512, + 'original_name': 'm1.tiny', + 'vcpus': 1, + 'extra_specs': {}, + 'swap': 0, + 'disk': 1 + } + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + # Since the flavor details are in a dict we can't be sure of the + # ordering so just assert that one of the keys is in the output. + self.assertIn('original_name', data[2]) + def test_show_diagnostics(self): arglist = [ '--diagnostics', diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 8403efc9..bd299123 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -15,7 +15,7 @@ import mock from mock import call - +from novaclient import api_versions from osc_lib import exceptions from openstackclient.compute.v2 import service @@ -340,6 +340,8 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( self.service.host, self.service.binary, force_down=False) @@ -359,6 +361,8 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( self.service.host, self.service.binary, force_down=True) @@ -380,6 +384,8 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.enable.assert_called_once_with( self.service.host, self.service.binary) @@ -402,6 +408,8 @@ class TestServiceSet(TestService): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') with mock.patch.object(self.service_mock, 'enable', side_effect=Exception()): self.assertRaises(exceptions.CommandError, diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 65c76b3e..bca457e4 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -106,6 +106,7 @@ class FakeApp(object): def __init__(self, _stdout, _log): self.stdout = _stdout self.client_manager = None + self.api_version = {} self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr @@ -140,6 +141,8 @@ class FakeClientManager(object): self.auth_ref = None self.auth_plugin_name = None self.network_endpoint_enabled = True + self.compute_endpoint_enabled = True + self.volume_endpoint_enabled = True def get_configuration(self): return { @@ -155,6 +158,12 @@ class FakeClientManager(object): def is_network_endpoint_enabled(self): return self.network_endpoint_enabled + def is_compute_endpoint_enabled(self): + return self.compute_endpoint_enabled + + def is_volume_endpoint_enabled(self, client): + return self.volume_endpoint_enabled + class FakeModule(object): diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 77928792..27ee9fd0 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -486,6 +486,50 @@ APP_CRED_OPTIONS = { 'secret': app_cred_secret } +registered_limit_id = 'registered-limit-id' +registered_limit_default_limit = 10 +registered_limit_description = 'default limit of foobars' +registered_limit_resource_name = 'foobars' +REGISTERED_LIMIT = { + 'id': registered_limit_id, + 'default_limit': registered_limit_default_limit, + 'resource_name': registered_limit_resource_name, + 'service_id': service_id, + 'description': None, + 'region_id': None +} +REGISTERED_LIMIT_OPTIONS = { + 'id': registered_limit_id, + 'default_limit': registered_limit_default_limit, + 'resource_name': registered_limit_resource_name, + 'service_id': service_id, + 'description': registered_limit_description, + 'region_id': region_id +} + +limit_id = 'limit-id' +limit_resource_limit = 15 +limit_description = 'limit of foobars' +limit_resource_name = 'foobars' +LIMIT = { + 'id': limit_id, + 'project_id': project_id, + 'resource_limit': limit_resource_limit, + 'resource_name': limit_resource_name, + 'service_id': service_id, + 'description': None, + 'region_id': None +} +LIMIT_OPTIONS = { + 'id': limit_id, + 'project_id': project_id, + 'resource_limit': limit_resource_limit, + 'resource_name': limit_resource_name, + 'service_id': service_id, + 'description': limit_description, + 'region_id': region_id +} + def fake_auth_ref(fake_token, fake_service=None): """Create an auth_ref using keystoneauth's fixtures""" @@ -576,6 +620,12 @@ class FakeIdentityv3Client(object): self.application_credentials = mock.Mock() self.application_credentials.resource_class = fakes.FakeResource(None, {}) + self.inference_rules = mock.Mock() + self.inference_rules.resource_class = fakes.FakeResource(None, {}) + self.registered_limits = mock.Mock() + self.registered_limits.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index bfe930d6..62dcf58d 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -439,6 +439,47 @@ class TestEndpointList(TestEndpoint): ) self.assertEqual(datalist, tuple(data)) + def test_endpoint_list_project_with_project_domain(self): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + + self.ep_filter_mock.list_endpoints_for_project.return_value = [ + self.endpoint + ] + self.projects_mock.get.return_value = project + + arglist = [ + '--project', project.name, + '--project-domain', domain.name + ] + verifylist = [ + ('project', project.name), + ('project_domain', domain.name), + ] + 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) + self.ep_filter_mock.list_endpoints_for_project.assert_called_with( + project=project.id + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, + True, + self.endpoint.interface, + self.endpoint.url, + ), + ) + self.assertEqual(datalist, tuple(data)) + class TestEndpointSet(TestEndpoint): diff --git a/openstackclient/tests/unit/identity/v3/test_implied_role.py b/openstackclient/tests/unit/identity/v3/test_implied_role.py index 08273f73..74968129 100644 --- a/openstackclient/tests/unit/identity/v3/test_implied_role.py +++ b/openstackclient/tests/unit/identity/v3/test_implied_role.py @@ -25,26 +25,32 @@ class TestRole(identity_fakes.TestIdentityv3): def setUp(self): super(TestRole, self).setUp() + identity_client = self.app.client_manager.identity + # Get a shortcut to the UserManager Mock - self.users_mock = self.app.client_manager.identity.users + self.users_mock = identity_client.users self.users_mock.reset_mock() # Get a shortcut to the UserManager Mock - self.groups_mock = self.app.client_manager.identity.groups + self.groups_mock = identity_client.groups self.groups_mock.reset_mock() # Get a shortcut to the DomainManager Mock - self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock = identity_client.domains self.domains_mock.reset_mock() # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock = identity_client.projects self.projects_mock.reset_mock() # Get a shortcut to the RoleManager Mock - self.roles_mock = self.app.client_manager.identity.roles + self.roles_mock = identity_client.roles self.roles_mock.reset_mock() + # Get a shortcut to the InferenceRuleManager Mock + self.inference_rules_mock = identity_client.inference_rules + self.inference_rules_mock.reset_mock() + def _is_inheritance_testcase(self): return False @@ -67,12 +73,13 @@ class TestImpliedRoleCreate(TestRole): ), ] - self.roles_mock.create_implied.return_value = fakes.FakeResource( + fake_resource = fakes.FakeResource( None, {'prior_role': copy.deepcopy(identity_fakes.ROLES[0]), 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, loaded=True, ) + self.inference_rules_mock.create.return_value = fake_resource self.cmd = implied_role.CreateImpliedRole(self.app, None) @@ -93,8 +100,8 @@ class TestImpliedRoleCreate(TestRole): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # RoleManager.create_implied(prior, implied) - self.roles_mock.create_implied.assert_called_with( + # InferenceRuleManager.create(prior, implied) + self.inference_rules_mock.create.assert_called_with( identity_fakes.ROLES[0]['id'], identity_fakes.ROLES[1]['id'] ) @@ -126,12 +133,13 @@ class TestImpliedRoleDelete(TestRole): ), ] - self.roles_mock.delete_implied.return_value = fakes.FakeResource( + fake_resource = fakes.FakeResource( None, {'prior-role': copy.deepcopy(identity_fakes.ROLES[0]), 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, loaded=True, ) + self.inference_rules_mock.delete.return_value = fake_resource self.cmd = implied_role.DeleteImpliedRole(self.app, None) @@ -147,7 +155,7 @@ class TestImpliedRoleDelete(TestRole): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.roles_mock.delete_implied.assert_called_with( + self.inference_rules_mock.delete.assert_called_with( identity_fakes.ROLES[0]['id'], identity_fakes.ROLES[1]['id'] ) @@ -158,7 +166,7 @@ class TestImpliedRoleList(TestRole): def setUp(self): super(TestImpliedRoleList, self).setUp() - self.roles_mock.list_inference_roles.return_value = ( + self.inference_rules_mock.list_inference_roles.return_value = ( identity_fakes.FakeImpliedRoleResponse.create_list()) self.cmd = implied_role.ListImpliedRole(self.app, None) @@ -168,7 +176,7 @@ class TestImpliedRoleList(TestRole): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.roles_mock.list_inference_roles.assert_called_with() + self.inference_rules_mock.list_inference_roles.assert_called_with() collist = ['Prior Role ID', 'Prior Role Name', 'Implied Role ID', 'Implied Role Name'] diff --git a/openstackclient/tests/unit/identity/v3/test_limit.py b/openstackclient/tests/unit/identity/v3/test_limit.py new file mode 100644 index 00000000..44c0358d --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_limit.py @@ -0,0 +1,382 @@ +# 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 copy + +from keystoneauth1.exceptions import http as ksa_exceptions +from osc_lib import exceptions + +from openstackclient.identity.v3 import limit +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestLimit(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestLimit, self).setUp() + + identity_manager = self.app.client_manager.identity + + self.limit_mock = identity_manager.limits + + self.services_mock = identity_manager.services + self.services_mock.reset_mock() + + self.projects_mock = identity_manager.projects + self.projects_mock.reset_mock() + + self.regions_mock = identity_manager.regions + self.regions_mock.reset_mock() + + +class TestLimitCreate(TestLimit): + + def setUp(self): + super(TestLimitCreate, self).setUp() + + self.service = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True + ) + self.services_mock.get.return_value = self.service + + self.project = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True + ) + self.projects_mock.get.return_value = self.project + + self.region = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGION), + loaded=True + ) + self.regions_mock.get.return_value = self.region + + self.cmd = limit.CreateLimit(self.app, None) + + def test_limit_create_without_options(self): + self.limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT), + loaded=True + ) + + resource_limit = 15 + arglist = [ + '--project', identity_fakes.project_id, + '--service', identity_fakes.service_id, + '--resource-limit', str(resource_limit), + identity_fakes.limit_resource_name + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('service', identity_fakes.service_id), + ('resource_name', identity_fakes.limit_resource_name), + ('resource_limit', resource_limit) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'description': None, 'region': None} + self.limit_mock.create.assert_called_with( + self.project, + self.service, + identity_fakes.limit_resource_name, + resource_limit, + **kwargs + ) + + collist = ('description', 'id', 'project_id', 'region_id', + 'resource_limit', 'resource_name', 'service_id') + self.assertEqual(collist, columns) + datalist = ( + None, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_limit_create_with_options(self): + self.limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT_OPTIONS), + loaded=True + ) + + resource_limit = 15 + arglist = [ + '--project', identity_fakes.project_id, + '--service', identity_fakes.service_id, + '--resource-limit', str(resource_limit), + '--region', identity_fakes.region_id, + '--description', identity_fakes.limit_description, + identity_fakes.limit_resource_name + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('service', identity_fakes.service_id), + ('resource_name', identity_fakes.limit_resource_name), + ('resource_limit', resource_limit), + ('region', identity_fakes.region_id), + ('description', identity_fakes.limit_description) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'description': identity_fakes.limit_description, + 'region': self.region + } + self.limit_mock.create.assert_called_with( + self.project, + self.service, + identity_fakes.limit_resource_name, + resource_limit, + **kwargs + ) + + collist = ('description', 'id', 'project_id', 'region_id', + 'resource_limit', 'resource_name', 'service_id') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.limit_description, + identity_fakes.limit_id, + identity_fakes.project_id, + identity_fakes.region_id, + resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestLimitDelete(TestLimit): + + def setUp(self): + super(TestLimitDelete, self).setUp() + self.cmd = limit.DeleteLimit(self.app, None) + + def test_limit_delete(self): + self.limit_mock.delete.return_value = None + + arglist = [identity_fakes.limit_id] + verifylist = [ + ('limit_id', [identity_fakes.limit_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.limit_mock.delete.assert_called_with( + identity_fakes.limit_id + ) + self.assertIsNone(result) + + def test_limit_delete_with_exception(self): + return_value = ksa_exceptions.NotFound() + self.limit_mock.delete.side_effect = return_value + + arglist = ['fake-limit-id'] + verifylist = [ + ('limit_id', ['fake-limit-id']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 1 limits failed to delete.', str(e) + ) + + +class TestLimitShow(TestLimit): + + def setUp(self): + super(TestLimitShow, self).setUp() + + self.limit_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT), + loaded=True + ) + + self.cmd = limit.ShowLimit(self.app, None) + + def test_limit_show(self): + arglist = [identity_fakes.limit_id] + verifylist = [('limit_id', identity_fakes.limit_id)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.get.assert_called_with(identity_fakes.limit_id) + + collist = ( + 'description', 'id', 'project_id', 'region_id', 'resource_limit', + 'resource_name', 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + None, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + identity_fakes.limit_resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestLimitSet(TestLimit): + + def setUp(self): + super(TestLimitSet, self).setUp() + self.cmd = limit.SetLimit(self.app, None) + + def test_limit_set_description(self): + limit = copy.deepcopy(identity_fakes.LIMIT) + limit['description'] = identity_fakes.limit_description + self.limit_mock.update.return_value = fakes.FakeResource( + None, limit, loaded=True + ) + + arglist = [ + '--description', identity_fakes.limit_description, + identity_fakes.limit_id + ] + verifylist = [ + ('description', identity_fakes.limit_description), + ('limit_id', identity_fakes.limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.update.assert_called_with( + identity_fakes.limit_id, + description=identity_fakes.limit_description, + resource_limit=None + ) + + collist = ( + 'description', 'id', 'project_id', 'region_id', 'resource_limit', + 'resource_name', 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.limit_description, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + identity_fakes.limit_resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_limit_set_resource_limit(self): + resource_limit = 20 + limit = copy.deepcopy(identity_fakes.LIMIT) + limit['resource_limit'] = resource_limit + self.limit_mock.update.return_value = fakes.FakeResource( + None, limit, loaded=True + ) + + arglist = [ + '--resource-limit', str(resource_limit), + identity_fakes.limit_id + ] + verifylist = [ + ('resource_limit', resource_limit), + ('limit_id', identity_fakes.limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.update.assert_called_with( + identity_fakes.limit_id, + description=None, + resource_limit=resource_limit + ) + + collist = ( + 'description', 'id', 'project_id', 'region_id', 'resource_limit', + 'resource_name', 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + None, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestLimitList(TestLimit): + + def setUp(self): + super(TestLimitList, self).setUp() + + self.limit_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT), + loaded=True + ) + ] + + self.cmd = limit.ListLimit(self.app, None) + + def test_limit_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.list.assert_called_with( + service=None, resource_name=None, region=None + ) + + collist = ( + 'ID', 'Project ID', 'Service ID', 'Resource Name', + 'Resource Limit', 'Description', 'Region ID' + ) + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.limit_id, + identity_fakes.project_id, + identity_fakes.service_id, + identity_fakes.limit_resource_name, + identity_fakes.limit_resource_limit, + None, + None + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/unit/identity/v3/test_registered_limit.py b/openstackclient/tests/unit/identity/v3/test_registered_limit.py new file mode 100644 index 00000000..262ca4f9 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_registered_limit.py @@ -0,0 +1,510 @@ +# 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 copy + +from keystoneauth1.exceptions import http as ksa_exceptions +from osc_lib import exceptions + +from openstackclient.identity.v3 import registered_limit +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestRegisteredLimit(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestRegisteredLimit, self).setUp() + + identity_manager = self.app.client_manager.identity + self.registered_limit_mock = identity_manager.registered_limits + + self.services_mock = identity_manager.services + self.services_mock.reset_mock() + + self.regions_mock = identity_manager.regions + self.regions_mock.reset_mock() + + +class TestRegisteredLimitCreate(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitCreate, self).setUp() + + self.service = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True + ) + self.services_mock.get.return_value = self.service + + self.region = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGION), + loaded=True + ) + self.regions_mock.get.return_value = self.region + + self.cmd = registered_limit.CreateRegisteredLimit(self.app, None) + + def test_registered_limit_create_without_options(self): + self.registered_limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT), + loaded=True + ) + + resource_name = identity_fakes.registered_limit_resource_name + default_limit = identity_fakes.registered_limit_default_limit + arglist = [ + '--service', identity_fakes.service_id, + '--default-limit', '10', + resource_name, + ] + + verifylist = [ + ('service', identity_fakes.service_id), + ('default_limit', default_limit), + ('resource_name', resource_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'description': None, 'region': None} + self.registered_limit_mock.create.assert_called_with( + self.service, resource_name, default_limit, **kwargs + ) + + collist = ('default_limit', 'description', 'id', 'region_id', + 'resource_name', 'service_id') + + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_create_with_options(self): + self.registered_limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT_OPTIONS), + loaded=True + ) + + resource_name = identity_fakes.registered_limit_resource_name + default_limit = identity_fakes.registered_limit_default_limit + description = identity_fakes.registered_limit_description + arglist = [ + '--region', identity_fakes.region_id, + '--description', description, + '--service', identity_fakes.service_id, + '--default-limit', '10', + resource_name + ] + + verifylist = [ + ('region', identity_fakes.region_id), + ('description', description), + ('service', identity_fakes.service_id), + ('default_limit', default_limit), + ('resource_name', resource_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'description': description, 'region': self.region} + self.registered_limit_mock.create.assert_called_with( + self.service, resource_name, default_limit, **kwargs + ) + + collist = ('default_limit', 'description', 'id', 'region_id', + 'resource_name', 'service_id') + + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + description, + identity_fakes.registered_limit_id, + identity_fakes.region_id, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestRegisteredLimitDelete(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitDelete, self).setUp() + + self.cmd = registered_limit.DeleteRegisteredLimit(self.app, None) + + def test_registered_limit_delete(self): + self.registered_limit_mock.delete.return_value = None + + arglist = [identity_fakes.registered_limit_id] + verifylist = [ + ('registered_limit_id', [identity_fakes.registered_limit_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.delete.assert_called_with( + identity_fakes.registered_limit_id + ) + self.assertIsNone(result) + + def test_registered_limit_delete_with_exception(self): + return_value = ksa_exceptions.NotFound() + self.registered_limit_mock.delete.side_effect = return_value + + arglist = ['fake-registered-limit-id'] + verifylist = [ + ('registered_limit_id', ['fake-registered-limit-id']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 1 registered limits failed to delete.', str(e) + ) + + +class TestRegisteredLimitShow(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitShow, self).setUp() + + self.registered_limit_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT), + loaded=True + ) + + self.cmd = registered_limit.ShowRegisteredLimit(self.app, None) + + def test_registered_limit_show(self): + arglist = [identity_fakes.registered_limit_id] + verifylist = [ + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.get.assert_called_with( + identity_fakes.registered_limit_id + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestRegisteredLimitSet(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitSet, self).setUp() + self.cmd = registered_limit.SetRegisteredLimit(self.app, None) + + def test_registered_limit_set_description(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + registered_limit['description'] = ( + identity_fakes.registered_limit_description + ) + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + + arglist = [ + '--description', identity_fakes.registered_limit_description, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('description', identity_fakes.registered_limit_description), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=None, + default_limit=None, + description=identity_fakes.registered_limit_description, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + identity_fakes.registered_limit_description, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_default_limit(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + default_limit = 20 + registered_limit['default_limit'] = default_limit + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + + arglist = [ + '--default-limit', str(default_limit), + identity_fakes.registered_limit_id + ] + verifylist = [ + ('default_limit', default_limit), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=None, + default_limit=default_limit, + description=None, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_resource_name(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + resource_name = 'volumes' + registered_limit['resource_name'] = resource_name + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + + arglist = [ + '--resource-name', resource_name, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('resource_name', resource_name), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=resource_name, + default_limit=None, + description=None, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_service(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + service = identity_fakes.FakeService.create_one_service() + registered_limit['service_id'] = service.id + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + self.services_mock.get.return_value = service + + arglist = [ + '--service', service.id, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('service', service.id), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=service, + resource_name=None, + default_limit=None, + description=None, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + service.id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_region(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + region = identity_fakes.REGION + region['id'] = 'RegionTwo' + region = fakes.FakeResource( + None, + copy.deepcopy(region), + loaded=True + ) + registered_limit['region_id'] = region.id + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + self.regions_mock.get.return_value = region + + arglist = [ + '--region', region.id, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('region', region.id), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=None, + default_limit=None, + description=None, + region=region + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + region.id, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestRegisteredLimitList(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitList, self).setUp() + + self.registered_limit_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT), + loaded=True + ) + + self.cmd = registered_limit.ShowRegisteredLimit(self.app, None) + + def test_limit_show(self): + arglist = [identity_fakes.registered_limit_id] + verifylist = [ + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.get.assert_called_with( + identity_fakes.registered_limit_id + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index 835837e6..bff6c56d 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -34,6 +34,7 @@ class TestRoleAssignmentList(TestRoleAssignment): 'Group', 'Project', 'Domain', + 'System', 'Inherited', ) @@ -95,6 +96,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, role=None, @@ -110,12 +112,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -143,6 +147,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', identity_fakes.user_name), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -159,6 +164,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=self.users_mock.get(), group=None, project=None, @@ -174,12 +180,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -207,6 +215,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', identity_fakes.group_name), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -223,6 +232,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=self.groups_mock.get(), effective=False, project=None, @@ -238,12 +248,14 @@ class TestRoleAssignmentList(TestRoleAssignment): identity_fakes.group_id, '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -271,6 +283,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', identity_fakes.domain_name), ('project', None), ('role', None), @@ -287,6 +300,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=self.domains_mock.get(), + system=None, group=None, effective=False, project=None, @@ -302,12 +316,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, '', identity_fakes.domain_id, + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -335,6 +351,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', identity_fakes.project_name), ('role', None), @@ -351,6 +368,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=self.projects_mock.get(), @@ -366,12 +384,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -398,6 +418,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -416,6 +437,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=self.users_mock.get(), group=None, project=self.projects_mock.get(), @@ -431,6 +453,7 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -456,6 +479,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -472,6 +496,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=True, project=None, @@ -487,12 +512,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + '', False ),) self.assertEqual(tuple(data), datalist) @@ -520,6 +547,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -536,6 +564,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=None, @@ -551,12 +580,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '', identity_fakes.project_id, '', + '', True ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + '', True ),) self.assertEqual(datalist, tuple(data)) @@ -584,6 +615,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -602,6 +634,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=None, @@ -610,7 +643,9 @@ class TestRoleAssignmentList(TestRoleAssignment): os_inherit_extension_inherited_to=None, include_names=True) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + collist = ( + 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited' + ) self.assertEqual(columns, collist) datalist1 = (( @@ -620,12 +655,14 @@ class TestRoleAssignmentList(TestRoleAssignment): '@'.join([identity_fakes.project_name, identity_fakes.domain_name]), '', + '', False ), (identity_fakes.role_name, '@'.join([identity_fakes.user_name, identity_fakes.domain_name]), '', '', identity_fakes.domain_name, + '', False ),) self.assertEqual(tuple(data), datalist1) @@ -648,6 +685,7 @@ class TestRoleAssignmentList(TestRoleAssignment): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', identity_fakes.ROLE_2['name']), @@ -664,6 +702,7 @@ class TestRoleAssignmentList(TestRoleAssignment): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=None, group=None, project=None, @@ -679,6 +718,7 @@ class TestRoleAssignmentList(TestRoleAssignment): '', '', identity_fakes.domain_id, + '', False ),) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index e1a79d13..170a7f03 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -527,6 +527,7 @@ class TestImageList(TestImage): verifylist = [ ('public', False), ('private', False), + ('community', False), ('shared', False), ('long', False), ] @@ -550,6 +551,7 @@ class TestImageList(TestImage): verifylist = [ ('public', True), ('private', False), + ('community', False), ('shared', False), ('long', False), ] @@ -574,6 +576,7 @@ class TestImageList(TestImage): verifylist = [ ('public', False), ('private', True), + ('community', False), ('shared', False), ('long', False), ] @@ -591,6 +594,31 @@ class TestImageList(TestImage): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_image_list_community_option(self): + arglist = [ + '--community', + ] + verifylist = [ + ('public', False), + ('private', False), + ('community', True), + ('shared', False), + ('long', False), + ] + 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) + self.api_mock.image_list.assert_called_with( + community=True, + marker=self._image.id, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + def test_image_list_shared_option(self): arglist = [ '--shared', @@ -598,6 +626,7 @@ class TestImageList(TestImage): verifylist = [ ('public', False), ('private', False), + ('community', False), ('shared', True), ('long', False), ] @@ -708,7 +737,8 @@ class TestImageList(TestImage): ) si_mock.assert_called_with( [self._image], - 'name:asc' + 'name:asc', + str, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -779,6 +809,66 @@ class TestImageList(TestImage): status='active', marker=self._image.id ) + def test_image_list_tag_option(self): + arglist = [ + '--tag', 'abc', + ] + verifylist = [ + ('tag', 'abc'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + tag='abc', marker=self._image.id + ) + + +class TestListImageProjects(TestImage): + + project = identity_fakes.FakeProject.create_one_project() + _image = image_fakes.FakeImage.create_one_image() + member = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': _image.id, + 'member_id': project.id} + ) + + columns = ( + "Image ID", + "Member ID", + "Status" + ) + + datalist = (( + _image.id, + member.member_id, + member.status, + )) + + def setUp(self): + super(TestListImageProjects, self).setUp() + + self.images_mock.get.return_value = self._image + self.image_members_mock.list.return_value = self.datalist + + self.cmd = image.ListImageProjects(self.app, None) + + def test_image_member_list(self): + arglist = [ + self._image.id + ] + verifylist = [ + ('image', self._image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.image_members_mock.list.assert_called_with(self._image.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(len(self.datalist), len(tuple(data))) + class TestRemoveProjectImage(TestImage): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 4291dd1d..aec7402f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -335,6 +335,8 @@ class FakeNetwork(object): 'name': 'network-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', 'description': 'network-description-' + uuid.uuid4().hex, + 'dns_domain': 'example.org.', + 'mtu': '1350', 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'admin_state_up': True, 'shared': False, @@ -347,7 +349,6 @@ class FakeNetwork(object): 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, - 'tags': ['test'], 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, @@ -1132,6 +1133,7 @@ class FakeSecurityGroup(object): 'description': 'security-group-description-' + uuid.uuid4().hex, 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], + 'tags': [] } # Overwrite default attributes. @@ -1380,6 +1382,7 @@ class FakeFloatingIP(object): 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'description': 'floating-ip-description-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index f19849c4..209c01cf 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -44,11 +44,13 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip created. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'dns_domain': 'example.org.', + 'dns_name': 'fip1', } ) @@ -65,6 +67,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -80,12 +83,14 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): super(TestCreateFloatingIPNetwork, self).setUp() self.network.create_ip = mock.Mock(return_value=self.floating_ip) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_network = mock.Mock( return_value=self.floating_network) @@ -126,6 +131,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): '--floating-ip-address', self.floating_ip.floating_ip_address, '--fixed-ip-address', self.floating_ip.fixed_ip_address, '--description', self.floating_ip.description, + '--dns-domain', self.floating_ip.dns_domain, + '--dns-name', self.floating_ip.dns_name, self.floating_ip.floating_network_id, ] verifylist = [ @@ -134,6 +141,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): ('fixed_ip_address', self.floating_ip.fixed_ip_address), ('network', self.floating_ip.floating_network_id), ('description', self.floating_ip.description), + ('dns_domain', self.floating_ip.dns_domain), + ('dns_name', self.floating_ip.dns_name), ('floating_ip_address', self.floating_ip.floating_ip_address), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -147,6 +156,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): 'fixed_ip_address': self.floating_ip.fixed_ip_address, 'floating_network_id': self.floating_ip.floating_network_id, 'description': self.floating_ip.description, + 'dns_domain': self.floating_ip.dns_domain, + 'dns_name': self.floating_ip.dns_name, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -221,6 +232,42 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self.floating_ip.floating_network_id] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + }) + if add_tags: + self.network.set_tags.assert_called_once_with( + self.floating_ip, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): @@ -353,6 +400,9 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Router', 'Status', 'Description', + 'Tags', + 'DNS Name', + 'DNS Domain', ) data = [] @@ -376,6 +426,9 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.router_id, ip.status, ip.description, + ip.tags, + ip.dns_domain, + ip.dns_name, )) def setUp(self): @@ -539,6 +592,31 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestShowFloatingIPNetwork(TestFloatingIPNetwork): @@ -558,6 +636,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -573,6 +652,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): @@ -609,11 +689,12 @@ class TestSetFloatingIP(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip to be set. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'tags': ['green', 'red'], } ) @@ -622,6 +703,7 @@ class TestSetFloatingIP(TestFloatingIPNetwork): self.network.find_ip = mock.Mock(return_value=self.floating_ip) self.network.find_port = mock.Mock(return_value=self.port) self.network.update_ip = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.SetFloatingIP(self.app, self.namespace) @@ -677,6 +759,31 @@ class TestSetFloatingIP(TestFloatingIPNetwork): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def test_qos_policy_option(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + arglist = [ + "--qos-policy", qos_policy.id, + self.floating_ip.id, + ] + verifylist = [ + ('qos_policy', qos_policy.id), + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': qos_policy.id, + } + self.network.find_ip.assert_called_once_with( + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + def test_port_and_qos_policy_option(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() self.network.find_qos_policy = mock.Mock(return_value=qos_policy) @@ -705,6 +812,29 @@ class TestSetFloatingIP(TestFloatingIPNetwork): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def test_no_qos_policy_option(self): + arglist = [ + "--no-qos-policy", + self.floating_ip.id, + ] + verifylist = [ + ('no_qos_policy', True), + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': None, + } + self.network.find_ip.assert_called_once_with( + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + def test_port_and_no_qos_policy_option(self): arglist = [ "--no-qos-policy", @@ -731,6 +861,33 @@ class TestSetFloatingIP(TestFloatingIPNetwork): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.extend([self.floating_ip.id]) + verifylist.extend([('floating_ip', self.floating_ip.id)]) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_ip.called) + self.network.set_tags.assert_called_once_with( + self.floating_ip, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestUnsetFloatingIP(TestFloatingIPNetwork): @@ -738,11 +895,12 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip to be unset. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'tags': ['green', 'red'], } ) @@ -750,6 +908,7 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): super(TestUnsetFloatingIP, self).setUp() self.network.find_ip = mock.Mock(return_value=self.floating_ip) self.network.update_ip = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) @@ -803,3 +962,31 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): self.floating_ip, **attrs) self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self.floating_ip.id) + verifylist.append( + ('floating_ip', self.floating_ip.id)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_ip.called) + self.network.set_tags.assert_called_once_with( + self.floating_ip, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 357088f3..0f57f0ee 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -60,10 +60,12 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints', 'availability_zones', 'description', + 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'mtu', 'name', 'port_security_enabled', 'project_id', @@ -83,10 +85,12 @@ class TestCreateNetworkIdentityV3(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.description, + _network.dns_domain, _network.id, _network.ipv4_address_scope_id, _network.ipv6_address_scope_id, _network.is_default, + _network.mtu, _network.name, _network.is_port_security_enabled, _network.project_id, @@ -149,6 +153,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): "--disable", "--share", "--description", self._network.description, + "--mtu", self._network.mtu, "--project", self.project.name, "--project-domain", self.domain.name, "--availability-zone-hint", "nova", @@ -159,12 +164,14 @@ class TestCreateNetworkIdentityV3(TestNetwork): "--qos-policy", self.qos_policy.id, "--transparent-vlan", "--enable-port-security", + "--dns-domain", "example.org.", self._network.name, ] verifylist = [ ('disable', True), ('share', True), ('description', self._network.description), + ('mtu', self._network.mtu), ('project', self.project.name), ('project_domain', self.domain.name), ('availability_zone_hints', ["nova"]), @@ -177,6 +184,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): ('transparent_vlan', True), ('enable_port_security', True), ('name', self._network.name), + ('dns_domain', 'example.org.'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -188,6 +196,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'name': self._network.name, 'shared': True, 'description': self._network.description, + 'mtu': self._network.mtu, # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor 'tenant_id': self.project.id, 'project_id': self.project.id, @@ -199,6 +208,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'qos_policy_id': self.qos_policy.id, 'vlan_transparent': True, 'port_security_enabled': True, + 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -282,10 +292,12 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'availability_zone_hints', 'availability_zones', 'description', + 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'mtu', 'name', 'port_security_enabled', 'project_id', @@ -305,10 +317,12 @@ class TestCreateNetworkIdentityV2(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.description, + _network.dns_domain, _network.id, _network.ipv4_address_scope_id, _network.ipv6_address_scope_id, _network.is_default, + _network.mtu, _network.name, _network.is_port_security_enabled, _network.project_id, @@ -894,6 +908,7 @@ class TestSetNetwork(TestNetwork): '--name', 'noob', '--share', '--description', self._network.description, + '--dns-domain', 'example.org.', '--external', '--default', '--provider-network-type', 'vlan', @@ -915,6 +930,7 @@ class TestSetNetwork(TestNetwork): ('segmentation_id', '400'), ('enable_port_security', True), ('qos_policy', self.qos_policy.name), + ('dns_domain', 'example.org.'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -932,6 +948,7 @@ class TestSetNetwork(TestNetwork): 'provider:segmentation_id': '400', 'port_security_enabled': True, 'qos_policy_id': self.qos_policy.id, + 'dns_domain': 'example.org.', } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -1019,10 +1036,12 @@ class TestShowNetwork(TestNetwork): 'availability_zone_hints', 'availability_zones', 'description', + 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'mtu', 'name', 'port_security_enabled', 'project_id', @@ -1042,10 +1061,12 @@ class TestShowNetwork(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.description, + _network.dns_domain, _network.id, _network.ipv4_address_scope_id, _network.ipv6_address_scope_id, _network.is_default, + _network.mtu, _network.name, _network.is_port_security_enabled, _network.project_id, diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index 176bc86d..5b54d318 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -127,7 +127,7 @@ class TestCreateNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "minimum-bandwidth" ' - 'requires arguments direction, min_kbps') + 'requires arguments: direction, min_kbps') self.assertEqual(msg, str(e)) @@ -213,7 +213,7 @@ class TestCreateNetworkQosRuleDSCPMarking(TestNetworkQosRule): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "dscp-marking" ' - 'requires arguments dscp_mark') + 'requires arguments: dscp_mark') self.assertEqual(msg, str(e)) @@ -266,6 +266,49 @@ class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule): arglist = [ '--type', RULE_TYPE_BANDWIDTH_LIMIT, '--max-kbps', str(self.new_rule.max_kbps), + '--egress', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_BANDWIDTH_LIMIT), + ('max_kbps', self.new_rule.max_kbps), + ('egress', True), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_BANDWIDTH_LIMIT}) + rule.max_burst_kbits = 0 + expected_data = ( + rule.direction, + rule.id, + rule.max_burst_kbits, + rule.max_kbps, + rule.project_id, + rule.qos_policy_id, + rule.type, + ) + + with mock.patch.object( + self.network, "create_qos_bandwidth_limit_rule", + return_value=rule) as create_qos_bandwidth_limit_rule: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + create_qos_bandwidth_limit_rule.assert_called_once_with( + self.qos_policy.id, + **{'max_kbps': self.new_rule.max_kbps, + 'direction': self.new_rule.direction} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(expected_data, data) + + def test_create_all_options(self): + arglist = [ + '--type', RULE_TYPE_BANDWIDTH_LIMIT, + '--max-kbps', str(self.new_rule.max_kbps), '--max-burst-kbits', str(self.new_rule.max_burst_kbits), '--egress', self.new_rule.qos_policy_id, @@ -309,7 +352,7 @@ class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "bandwidth-limit" ' - 'requires arguments max_burst_kbps, max_kbps') + 'requires arguments: max_kbps') self.assertEqual(msg, str(e)) @@ -579,7 +622,7 @@ class TestSetNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"minimum-bandwidth" only requires arguments direction, ' + '"minimum-bandwidth" only requires arguments: direction, ' 'min_kbps' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) @@ -673,7 +716,7 @@ class TestSetNetworkQosRuleDSCPMarking(TestNetworkQosRule): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"dscp-marking" only requires arguments dscp_mark' % + '"dscp-marking" only requires arguments: dscp_mark' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) @@ -837,7 +880,7 @@ class TestSetNetworkQosRuleBandwidthLimit(TestNetworkQosRule): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"bandwidth-limit" only requires arguments direction, ' + '"bandwidth-limit" only requires arguments: direction, ' 'max_burst_kbps, max_kbps' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 03e1d841..78d7fd6c 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -867,6 +867,24 @@ class TestListPort(TestPort): self.assertEqual(self.columns, columns) self.assertEqual(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] + arglist = [ + '--fixed-ip', "ip-substring=%s" % ip_address_ss, + ] + verifylist = [ + ('fixed_ip', [{'ip-substring': ip_address_ss}]) + ] + + 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': ['ip_address_substr=%s' % ip_address_ss]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] arglist = [ 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 35b7e366..83208287 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -40,8 +40,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be created. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group()) columns = ( 'description', @@ -49,6 +49,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'tags', ) data = ( @@ -57,6 +58,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.name, _security_group.project_id, '', + _security_group.tags, ) def setUp(self): @@ -67,6 +69,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) @@ -118,6 +121,43 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self._security_group.name] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + + verifylist = [ + ('name', self._security_group.name), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_security_group.assert_called_once_with(**{ + 'description': self._security_group.name, + 'name': self._security_group.name, + }) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): @@ -214,6 +254,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): 'Name', 'Description', 'Project', + 'Tags', ) data = [] @@ -223,6 +264,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): grp.name, grp.description, grp.project_id, + grp.tags, )) def setUp(self): @@ -300,12 +342,38 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_groups.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be set. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'tags': ['green', 'red']})) def setUp(self): super(TestSetSecurityGroupNetwork, self).setUp() @@ -314,6 +382,7 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.find_security_group = mock.Mock( return_value=self._security_group) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = security_group.SetSecurityGroup(self.app, self.namespace) @@ -366,6 +435,34 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): ) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._security_group.name) + verifylist.append( + ('group', self._security_group.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertTrue(self.network.update_security_group.called) + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): @@ -385,6 +482,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'tags', ) data = ( @@ -394,6 +492,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.project_id, security_group._format_network_security_group_rules( [_security_group_rule._info]), + _security_group.tags, ) def setUp(self): @@ -424,3 +523,70 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group to be unset. + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'tags': ['green', 'red']})) + + def setUp(self): + super(TestUnsetSecurityGroupNetwork, self).setUp() + + self.network.update_security_group = mock.Mock(return_value=None) + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + self.network.set_tags = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = security_group.UnsetSecurityGroup(self.app, self.namespace) + + def test_set_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_set_no_updates(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_security_group.called) + self.assertFalse(self.network.set_tags.called) + self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._security_group.name) + verifylist.append( + ('group', self._security_group.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_security_group.called) + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index b7f741cd..39cb4f53 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -1046,6 +1046,36 @@ class TestSetSubnet(TestSubnet): _testsubnet, **attrs) self.assertIsNone(result) + def test_clear_options(self): + _testsubnet = network_fakes.FakeSubnet.create_one_subnet( + {'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}], + 'allocation_pools': [{'start': '8.8.8.200', + 'end': '8.8.8.250'}], + 'dns_nameservers': ['10.0.0.1'], }) + self.network.find_subnet = mock.Mock(return_value=_testsubnet) + arglist = [ + '--no-host-route', + '--no-allocation-pool', + '--no-dns-nameservers', + _testsubnet.name, + ] + verifylist = [ + ('no_dns_nameservers', True), + ('no_host_route', True), + ('no_allocation_pool', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'host_routes': [], + 'allocation_pools': [], + 'dns_nameservers': [], + } + self.network.update_subnet.assert_called_once_with( + _testsubnet, **attrs) + self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): if with_tags: arglist = ['--tag', 'red', '--tag', 'blue'] @@ -1074,6 +1104,29 @@ class TestSetSubnet(TestSubnet): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) + def test_set_segment(self): + _net = network_fakes.FakeNetwork.create_one_network() + _segment = network_fakes.FakeNetworkSegment.create_one_network_segment( + attrs={'network_id': _net.id}) + _subnet = network_fakes.FakeSubnet.create_one_subnet( + {'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}], + 'allocation_pools': [{'start': '8.8.8.200', + 'end': '8.8.8.250'}], + 'dns_nameservers': ["10.0.0.1"], + 'network_id': _net.id, + 'segment_id': None}) + self.network.find_subnet = mock.Mock(return_value=_subnet) + self.network.find_segment = mock.Mock(return_value=_segment) + arglist = ['--network-segment', _segment.id, _subnet.name] + verifylist = [('network_segment', _segment.id)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = {'segment_id': _segment.id} + self.network.update_subnet.assert_called_once_with(_subnet, **attrs) + self.network.update_subnet.assert_called_with(_subnet, **attrs) + self.assertIsNone(result) + class TestShowSubnet(TestSubnet): # The subnets to be shown diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py index dbf9592f..60591eff 100644 --- a/openstackclient/tests/unit/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -15,8 +15,8 @@ import mock -from cinderclient.v1 import volume_snapshots -from cinderclient.v1 import volumes +from cinderclient.v3 import volume_snapshots +from cinderclient.v3 import volumes from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 27f37bd8..8d1db63e 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -193,6 +193,100 @@ class FakeService(object): return services +class FakeCapability(object): + """Fake capability.""" + + @staticmethod + def create_one_capability(attrs=None): + """Create a fake volume backend capability. + + :param Dictionary attrs: + A dictionary with all attributes of the Capabilities. + :return: + A FakeResource object with capability name and attrs. + """ + # Set default attribute + capability_info = { + "namespace": "OS::Storage::Capabilities::fake", + "vendor_name": "OpenStack", + "volume_backend_name": "lvmdriver-1", + "pool_name": "pool", + "driver_version": "2.0.0", + "storage_protocol": "iSCSI", + "display_name": "Capabilities of Cinder LVM driver", + "description": "Blah, blah.", + "visibility": "public", + "replication_targets": [], + "properties": { + "compression": { + "title": "Compression", + "description": "Enables compression.", + "type": "boolean" + }, + "qos": { + "title": "QoS", + "description": "Enables QoS.", + "type": "boolean" + }, + "replication": { + "title": "Replication", + "description": "Enables replication.", + "type": "boolean" + }, + "thin_provisioning": { + "title": "Thin Provisioning", + "description": "Sets thin provisioning.", + "type": "boolean" + } + } + } + + # Overwrite default attributes if there are some attributes set + capability_info.update(attrs or {}) + + capability = fakes.FakeResource( + None, + capability_info, + loaded=True) + + return capability + + +class FakePool(object): + """Fake Pools.""" + + @staticmethod + def create_one_pool(attrs=None): + """Create a fake pool. + + :param Dictionary attrs: + A dictionary with all attributes of the pool + :return: + A FakeResource object with pool name and attrs. + """ + # Set default attribute + pool_info = { + 'name': 'host@lvmdriver-1#lvmdriver-1', + 'storage_protocol': 'iSCSI', + 'thick_provisioning_support': False, + 'thin_provisioning_support': True, + 'total_volumes': 99, + 'total_capacity_gb': 1000.00, + 'allocated_capacity_gb': 100, + 'max_over_subscription_ratio': 200.0, + } + + # Overwrite default attributes if there are some attributes set + pool_info.update(attrs or {}) + + pool = fakes.FakeResource( + None, + pool_info, + loaded=True) + + return pool + + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -200,6 +294,8 @@ class FakeVolumeClient(object): self.volumes.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() @@ -231,6 +327,10 @@ class FakeVolumeClient(object): self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.capabilities = mock.Mock() + self.capabilities.resource_class = fakes.FakeResource(None, {}) + self.pools = mock.Mock() + self.pools.resource_class = fakes.FakeResource(None, {}) class TestVolume(utils.TestCommand): @@ -1004,3 +1104,101 @@ class FakeQuota(object): quota.project_id = quota_attrs['id'] return quota + + +class FakeLimits(object): + """Fake limits""" + + def __init__(self, absolute_attrs=None): + self.absolute_limits_attrs = { + 'totalSnapshotsUsed': 1, + 'maxTotalBackups': 10, + 'maxTotalVolumeGigabytes': 1000, + 'maxTotalSnapshots': 10, + 'maxTotalBackupGigabytes': 1000, + 'totalBackupGigabytesUsed': 0, + 'maxTotalVolumes': 10, + 'totalVolumesUsed': 4, + 'totalBackupsUsed': 0, + 'totalGigabytesUsed': 35 + } + absolute_attrs = absolute_attrs or {} + self.absolute_limits_attrs.update(absolute_attrs) + + self.rate_limits_attrs = [{ + "uri": "*", + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 10, + "verb": "PUT", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 100, + "verb": "DELETE", + "remaining": 100, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + } + ] + }] + + @property + def absolute(self): + for (name, value) in self.absolute_limits_attrs.items(): + yield FakeAbsoluteLimit(name, value) + + def absolute_limits(self): + reference_data = [] + for (name, value) in self.absolute_limits_attrs.items(): + reference_data.append((name, value)) + return reference_data + + @property + def rate(self): + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + yield FakeRateLimit(rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + def rate_limits(self): + reference_data = [] + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + reference_data.append((rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available'])) + return reference_data + + +class FakeAbsoluteLimit(object): + """Data model that represents an absolute limit.""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class FakeRateLimit(object): + """Data model that represents a flattened view of a single rate limit.""" + + def __init__(self, verb, uri, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 2fa924b8..183fb228 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -126,15 +126,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -152,7 +148,6 @@ class TestVolumeCreate(TestVolume): '--availability-zone', self.new_volume.availability_zone, '--consistency-group', consistency_group.id, '--hint', 'k=v', - '--multi-attach', self.new_volume.name, ] verifylist = [ @@ -162,7 +157,6 @@ class TestVolumeCreate(TestVolume): ('availability_zone', self.new_volume.availability_zone), ('consistency_group', consistency_group.id), ('hint', {'k': 'v'}), - ('multi_attach', True), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -178,112 +172,50 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=self.new_volume.description, volume_type=self.new_volume.volume_type, - user_id=None, - project_id=None, availability_zone=self.new_volume.availability_zone, metadata=None, imageRef=None, source_volid=None, consistencygroup_id=consistency_group.id, - source_replica=None, - multiattach=True, scheduler_hints={'k': 'v'}, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) - def test_volume_create_user_project_id(self): - # Return a project - self.projects_mock.get.return_value = self.project - # Return a user - self.users_mock.get.return_value = self.user - + def test_volume_create_user(self): arglist = [ '--size', str(self.new_volume.size), - '--project', self.project.id, '--user', self.user.id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('project', self.project.id), ('user', self.user.id), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstract method take_action() - # returns a two-part tuple with a tuple of column names and a tuple of - # data to be shown. - columns, data = self.cmd.take_action(parsed_args) - - self.volumes_mock.create.assert_called_with( - size=self.new_volume.size, - snapshot_id=None, - name=self.new_volume.name, - description=None, - volume_type=None, - user_id=self.user.id, - project_id=self.project.id, - availability_zone=None, - metadata=None, - imageRef=None, - source_volid=None, - consistencygroup_id=None, - source_replica=None, - multiattach=False, - scheduler_hints=None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - - def test_volume_create_user_project_name(self): - # Return a project - self.projects_mock.get.return_value = self.project - # Return a user - self.users_mock.get.return_value = self.user + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + self.volumes_mock.create.assert_not_called() + def test_volume_create_project(self): arglist = [ '--size', str(self.new_volume.size), - '--project', self.project.name, - '--user', self.user.name, + '--project', self.project.id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('project', self.project.name), - ('user', self.user.name), + ('project', self.project.id), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstract method take_action() - # returns a two-part tuple with a tuple of column names and a tuple of - # data to be shown. - columns, data = self.cmd.take_action(parsed_args) - - self.volumes_mock.create.assert_called_with( - size=self.new_volume.size, - snapshot_id=None, - name=self.new_volume.name, - description=None, - volume_type=None, - user_id=self.user.id, - project_id=self.project.id, - availability_zone=None, - metadata=None, - imageRef=None, - source_volid=None, - consistencygroup_id=None, - source_replica=None, - multiattach=False, - scheduler_hints=None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + self.volumes_mock.create.assert_not_called() def test_volume_create_properties(self): arglist = [ @@ -310,15 +242,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata={'Alpha': 'a', 'Beta': 'b'}, imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -352,15 +280,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=image.id, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -394,15 +318,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=image.id, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -430,20 +350,16 @@ class TestVolumeCreate(TestVolume): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_once_with( - size=None, + size=snapshot.size, snapshot_id=snapshot.id, name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -477,15 +393,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -523,15 +435,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -578,15 +486,11 @@ class TestVolumeCreate(TestVolume): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, - multiattach=False, scheduler_hints=None, ) @@ -598,40 +502,6 @@ class TestVolumeCreate(TestVolume): self.volumes_mock.update_readonly_flag.assert_called_with( self.new_volume.id, True) - def test_volume_create_with_source_replicated(self): - self.volumes_mock.get.return_value = self.new_volume - arglist = [ - '--source-replicated', self.new_volume.id, - self.new_volume.name, - ] - verifylist = [ - ('source_replicated', self.new_volume.id), - ('name', self.new_volume.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.volumes_mock.create.assert_called_once_with( - size=None, - snapshot_id=None, - name=self.new_volume.name, - description=None, - volume_type=None, - user_id=None, - project_id=None, - availability_zone=None, - metadata=None, - imageRef=None, - source_volid=None, - consistencygroup_id=None, - source_replica=self.new_volume.id, - multiattach=False, - scheduler_hints=None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - def test_volume_create_without_size(self): arglist = [ self.new_volume.name, @@ -649,7 +519,6 @@ class TestVolumeCreate(TestVolume): '--image', 'source_image', '--source', 'source_volume', '--snapshot', 'source_snapshot', - '--source-replicated', 'source_replicated_volume', '--size', str(self.new_volume.size), self.new_volume.name, ] @@ -657,7 +526,6 @@ class TestVolumeCreate(TestVolume): ('image', 'source_image'), ('source', 'source_volume'), ('snapshot', 'source_snapshot'), - ('source-replicated', 'source_replicated_volume'), ('size', self.new_volume.size), ('name', self.new_volume.name), ] diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py new file mode 100644 index 00000000..db188660 --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py @@ -0,0 +1,168 @@ +# +# 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.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import volume_backend + + +class TestShowVolumeCapability(volume_fakes.TestVolume): + """Test backend capability functionality.""" + + # The capability to be listed + capability = volume_fakes.FakeCapability.create_one_capability() + + def setUp(self): + super(TestShowVolumeCapability, self).setUp() + + # Get a shortcut to the capability Mock + self.capability_mock = self.app.client_manager.volume.capabilities + self.capability_mock.get.return_value = self.capability + + # Get the command object to test + self.cmd = volume_backend.ShowCapability(self.app, None) + + def test_capability_show(self): + arglist = [ + 'fake', + ] + verifylist = [ + ('host', 'fake'), + ] + 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) + + expected_columns = [ + 'Title', + 'Key', + 'Type', + 'Description', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + capabilities = [ + 'Compression', + 'Replication', + 'QoS', + 'Thin Provisioning', + ] + + # confirming if all expected values are present in the result. + for cap in data: + self.assertTrue(cap[0] in capabilities) + + # checking if proper call was made to get capabilities + self.capability_mock.get.assert_called_with( + 'fake', + ) + + +class TestListVolumePool(volume_fakes.TestVolume): + """Tests for volume backend pool listing.""" + + # The pool to be listed + pools = volume_fakes.FakePool.create_one_pool() + + def setUp(self): + super(TestListVolumePool, self).setUp() + + self.pool_mock = self.app.client_manager.volume.pools + self.pool_mock.list.return_value = [self.pools] + + # Get the command object to test + self.cmd = volume_backend.ListPool(self.app, None) + + def test_pool_list(self): + 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) + + expected_columns = [ + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.pools.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list pools + self.pool_mock.list.assert_called_with( + detailed=False, + ) + + # checking if long columns are present in output + self.assertNotIn("total_volumes", columns) + self.assertNotIn("storage_protocol", columns) + + def test_service_list_with_long_option(self): + arglist = [ + '--long' + ] + verifylist = [ + ('long', 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) + + expected_columns = [ + 'Name', + 'Protocol', + 'Thick', + 'Thin', + 'Volumes', + 'Capacity', + 'Allocated', + 'Max Over Ratio', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.pools.name, + self.pools.storage_protocol, + self.pools.thick_provisioning_support, + self.pools.thin_provisioning_support, + self.pools.total_volumes, + self.pools.total_capacity_gb, + self.pools.allocated_capacity_gb, + self.pools.max_over_subscription_ratio, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.pool_mock.list.assert_called_with( + detailed=True, + ) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index c4b0dfca..e0e670a9 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -37,13 +37,20 @@ def make_client(instance): # Defer client imports until we actually need them from cinderclient import extension - from cinderclient.v1.contrib import list_extensions - from cinderclient.v1 import volume_snapshots - from cinderclient.v1 import volumes - - # Monkey patch for v1 cinderclient - volumes.Volume.NAME_ATTR = 'display_name' - volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + from cinderclient.v3.contrib import list_extensions + from cinderclient.v3 import volume_snapshots + from cinderclient.v3 import volumes + + # Try a small import to check if cinderclient v1 is supported + try: + from cinderclient.v1 import services # noqa + except Exception: + del API_VERSIONS['1'] + + if instance._api_version[API_NAME] == '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, diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index f9baa5be..b4d8eaca 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -70,8 +70,8 @@ class CreateVolumeType(command.ShowOne): parser.add_argument( '--encryption-provider', metavar='<provider>', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume. Consider using other encryption options ' 'such as: "--encryption-cipher", "--encryption-key-size" ' @@ -254,8 +254,8 @@ class SetVolumeType(command.Command): parser.add_argument( '--encryption-provider', metavar='<provider>', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume. Consider using other encryption options ' 'such as: "--encryption-cipher", "--encryption-key-size" ' diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 61f846b0..7a5c207a 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -14,6 +14,7 @@ """Volume V2 Volume action implementations""" +import argparse import copy import logging @@ -37,7 +38,7 @@ def _check_size_arg(args): volume is not specified. """ - if ((args.snapshot or args.source or args.source_replicated) + if ((args.snapshot or args.source) is None and args.size is None): msg = _("--size is a required option if snapshot " "or source volume is not specified.") @@ -59,7 +60,7 @@ class CreateVolume(command.ShowOne): metavar="<size>", type=int, help=_("Volume size in GB (Required unless --snapshot or " - "--source or --source-replicated is specified)"), + "--source is specified)"), ) parser.add_argument( "--type", @@ -85,7 +86,7 @@ class CreateVolume(command.ShowOne): source_group.add_argument( "--source-replicated", metavar="<replicated-volume>", - help=_("Replicated volume to clone (name or ID)"), + help=argparse.SUPPRESS, ) parser.add_argument( "--description", @@ -95,12 +96,12 @@ class CreateVolume(command.ShowOne): parser.add_argument( '--user', metavar='<user>', - help=_('Specify an alternate user (name or ID)'), + help=argparse.SUPPRESS, ) parser.add_argument( '--project', metavar='<project>', - help=_('Specify an alternate project (name or ID)'), + help=argparse.SUPPRESS, ) parser.add_argument( "--availability-zone", @@ -158,7 +159,6 @@ class CreateVolume(command.ShowOne): def take_action(self, parsed_args): _check_size_arg(parsed_args) - identity_client = self.app.client_manager.identity volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image @@ -168,12 +168,6 @@ class CreateVolume(command.ShowOne): volume_client.volumes, parsed_args.source).id - replicated_source_volume = None - if parsed_args.source_replicated: - replicated_source_volume = utils.find_resource( - volume_client.volumes, - parsed_args.source_replicated).id - consistency_group = None if parsed_args.consistency_group: consistency_group = utils.find_resource( @@ -186,39 +180,53 @@ class CreateVolume(command.ShowOne): image_client.images, parsed_args.image).id + size = parsed_args.size + snapshot = None if parsed_args.snapshot: - snapshot = utils.find_resource( + snapshot_obj = utils.find_resource( volume_client.volume_snapshots, - parsed_args.snapshot).id - - project = None + parsed_args.snapshot) + snapshot = snapshot_obj.id + # Cinder requires a value for size when creating a volume + # even if creating from a snapshot. Cinder will create the + # volume with at least the same size as the snapshot anyway, + # so since we have the object here, just override the size + # value if it's either not given or is smaller than the + # snapshot size. + size = max(size or 0, snapshot_obj.size) + + # NOTE(abishop): Cinder's volumes.create() has 'project_id' and + # 'user_id' args, but they're not wired up to anything. The only way + # to specify an alternate project or user for the volume is to use + # the identity overrides (e.g. "--os-project-id"). + # + # Now, if the project or user arg is specified then the command is + # rejected. Otherwise, Cinder would actually create a volume, but + # without the specified property. if parsed_args.project: - project = utils.find_resource( - identity_client.projects, - parsed_args.project).id - - user = None + raise exceptions.CommandError( + _("ERROR: --project is deprecated, please use" + " --os-project-name or --os-project-id instead.")) if parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user).id + raise exceptions.CommandError( + _("ERROR: --user is deprecated, please use" + " --os-username instead.")) + if parsed_args.multi_attach: + LOG.warning(_("'--multi-attach' option is no longer supported by " + "the block storage service.")) volume = volume_client.volumes.create( - size=parsed_args.size, + size=size, snapshot_id=snapshot, name=parsed_args.name, description=parsed_args.description, volume_type=parsed_args.type, - user_id=user, - project_id=project, availability_zone=parsed_args.availability_zone, metadata=parsed_args.property, imageRef=image, source_volid=source_volume, consistencygroup_id=consistency_group, - source_replica=replicated_source_volume, - multiattach=parsed_args.multi_attach, scheduler_hints=parsed_args.hint, ) diff --git a/openstackclient/volume/v2/volume_backend.py b/openstackclient/volume/v2/volume_backend.py new file mode 100644 index 00000000..c5194d35 --- /dev/null +++ b/openstackclient/volume/v2/volume_backend.py @@ -0,0 +1,113 @@ +# +# 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. +# + +"""Storage backend action implementations""" + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +class ShowCapability(command.Lister): + _description = _("Show capability command") + + def get_parser(self, prog_name): + parser = super(ShowCapability, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="<host>", + help=_("List capabilities of specified host (host@backend-name)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + columns = [ + 'Title', + 'Key', + 'Type', + 'Description', + ] + + data = volume_client.capabilities.get(parsed_args.host) + + # The get capabilities API is... interesting. We only want the names of + # the capabilities that can set for a backend through extra specs, so + # we need to extract out that part of the mess that is returned. + print_data = [] + keys = data.properties + for key in keys: + # Stuff the key into the details to make it easier to output + capability_data = data.properties[key] + capability_data['key'] = key + print_data.append(capability_data) + + return (columns, + (utils.get_dict_properties( + s, columns, + ) for s in print_data)) + + +class ListPool(command.Lister): + _description = _("List pool command") + + def get_parser(self, prog_name): + parser = super(ListPool, self).get_parser(prog_name) + parser.add_argument( + "--long", + action="store_true", + default=False, + help=_("Show detailed information about pools.") + ) + # TODO(smcginnis): Starting with Cinder microversion 3.33, user is also + # able to pass in --filters with a <key>=<value> pair to filter on. + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + 'name', + 'storage_protocol', + 'thick_provisioning_support', + 'thin_provisioning_support', + 'total_volumes', + 'total_capacity_gb', + 'allocated_capacity_gb', + 'max_over_subscription_ratio', + ] + headers = [ + 'Name', + 'Protocol', + 'Thick', + 'Thin', + 'Volumes', + 'Capacity', + 'Allocated', + 'Max Over Ratio' + ] + else: + columns = [ + 'Name', + ] + headers = columns + + data = volume_client.pools.list(detailed=parsed_args.long) + return (headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 64c4d652..71e94a2b 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -112,8 +112,8 @@ class CreateVolumeType(command.ShowOne): parser.add_argument( '--encryption-provider', metavar='<provider>', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume. Consider using other encryption options ' 'such as: "--encryption-cipher", "--encryption-key-size" ' @@ -371,8 +371,8 @@ class SetVolumeType(command.Command): parser.add_argument( '--encryption-provider', metavar='<provider>', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume for the first time. Consider using other ' 'encryption options such as: "--encryption-cipher", ' |
