diff options
Diffstat (limited to 'openstackclient')
71 files changed, 3636 insertions, 754 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/compute_v2.py b/openstackclient/api/compute_v2.py index 0ffed655..0c89e912 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -93,7 +93,51 @@ class APIv2(api.BaseAPI): return ret - # Flaoting IPs + # Floating IPs + + def floating_ip_add( + self, + server, + address, + fixed_address=None, + ): + """Add a floating IP to a server + + :param server: + The :class:`Server` (or its ID) to add an IP to. + :param address: + The FloatingIP or string floating address to add. + :param fixed_address: + The FixedIP the floatingIP should be associated with (optional) + """ + + url = '/servers' + + server = self.find( + url, + attr='name', + value=server, + ) + + address = address.ip if hasattr(address, 'ip') else address + if fixed_address: + if hasattr(fixed_address, 'ip'): + fixed_address = fixed_address.ip + + body = { + 'address': address, + 'fixed_address': fixed_address, + } + else: + body = { + 'address': address, + } + + return self._request( + "POST", + "/%s/%s/action" % (url, server['id']), + json={'addFloatingIp': body}, + ) def floating_ip_create( self, @@ -175,6 +219,38 @@ class APIv2(api.BaseAPI): return self.list(url)["floating_ips"] + def floating_ip_remove( + self, + server, + address, + ): + """Remove a floating IP from a server + + :param server: + The :class:`Server` (or its ID) to add an IP to. + :param address: + The FloatingIP or string floating address to add. + """ + + url = '/servers' + + server = self.find( + url, + attr='name', + value=server, + ) + + address = address.ip if hasattr(address, 'ip') else address + body = { + 'address': address, + } + + return self._request( + "POST", + "/%s/%s/action" % (url, server['id']), + json={'removeFloatingIp': body}, + ) + # Floating IP Pools def floating_ip_pool_list( @@ -192,6 +268,84 @@ class APIv2(api.BaseAPI): return self.list(url)["floating_ip_pools"] + # Hosts + + def host_list( + self, + zone=None, + ): + """Lists hypervisor Hosts + + https://developer.openstack.org/api-ref/compute/#list-hosts + Valid for Compute 2.0 - 2.42 + + :param string zone: + Availability zone + :returns: A dict of the floating IP attributes + """ + + url = "/os-hosts" + if zone: + url = '/os-hosts?zone=%s' % zone + + return self.list(url)["hosts"] + + def host_set( + self, + host=None, + status=None, + maintenance_mode=None, + **params + ): + """Modify host properties + + https://developer.openstack.org/api-ref/compute/#update-host-status + Valid for Compute 2.0 - 2.42 + + status + maintenance_mode + """ + + url = "/os-hosts" + + params = {} + if status: + params['status'] = status + if maintenance_mode: + params['maintenance_mode'] = maintenance_mode + if params == {}: + # Don't bother calling if nothing given + return None + else: + return self._request( + "PUT", + "/%s/%s" % (url, host), + json=params, + ).json() + + def host_show( + self, + host=None, + ): + """Show host + + https://developer.openstack.org/api-ref/compute/#show-host-details + Valid for Compute 2.0 - 2.42 + """ + + url = "/os-hosts" + + r_host = self.find( + url, + attr='host_name', + value=host, + ) + + data = [] + for h in r_host: + data.append(h['resource']) + return data + # Networks def network_create( 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/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/host.py b/openstackclient/compute/v2/host.py index a495b367..9fdfd927 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -40,9 +40,9 @@ class ListHost(command.Lister): "Service", "Zone" ) - data = compute_client.hosts.list_all(parsed_args.zone) + data = compute_client.api.host_list(parsed_args.zone) return (columns, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in data)) @@ -95,13 +95,7 @@ class SetHost(command.Command): compute_client = self.app.client_manager.compute - # More than one hosts will be returned by using find_resource() - # so that the return value cannot be used in host update() method. - # find_resource() is just used for checking existence of host and - # keeping the exception message consistent with other commands. - utils.find_resource(compute_client.hosts, parsed_args.host) - - compute_client.hosts.update( + compute_client.api.host_set( parsed_args.host, kwargs ) @@ -128,8 +122,10 @@ class ShowHost(command.Lister): "Memory MB", "Disk GB" ) - data = compute_client.hosts.get(parsed_args.host) + + data = compute_client.api.host_show(parsed_args.host) + return (columns, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in data)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c08f5cae..a7b99306 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.v2 import servers from osc_lib.cli import parseractions @@ -32,6 +31,7 @@ import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common as network_common LOG = logging.getLogger(__name__) @@ -120,17 +120,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', {}) @@ -144,12 +148,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( @@ -178,7 +188,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']) @@ -189,12 +199,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") @@ -234,11 +238,10 @@ class AddFixedIP(command.Command): ) -class AddFloatingIP(command.Command): +class AddFloatingIP(network_common.NetworkAndComputeCommand): _description = _("Add floating IP address to server") - def get_parser(self, prog_name): - parser = super(AddFloatingIP, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( "server", metavar="<server>", @@ -252,19 +255,37 @@ class AddFloatingIP(command.Command): parser.add_argument( "--fixed-ip-address", metavar="<ip-address>", - help=_("Fixed IP address to associate with this floating IP " - "address"), + help=_( + "Fixed IP address to associate with this floating IP address" + ), ) return parser - def take_action(self, parsed_args): + def take_action_network(self, client, parsed_args): compute_client = self.app.client_manager.compute + attrs = {} + obj = client.find_ip( + parsed_args.ip_address, + ignore_missing=False, + ) server = utils.find_resource( - compute_client.servers, parsed_args.server) + compute_client.servers, + parsed_args.server, + ) + port = list(client.ports(device_id=server.id))[0] + attrs['port_id'] = port.id + if parsed_args.fixed_ip_address: + attrs['fixed_ip_address'] = parsed_args.fixed_ip_address - server.add_floating_ip(parsed_args.ip_address, - parsed_args.fixed_ip_address) + client.update_ip(obj, **attrs) + + def take_action_compute(self, client, parsed_args): + client.api.floating_ip_add( + parsed_args.server, + parsed_args.ip_address, + fixed_address=parsed_args.fixed_ip_address, + ) class AddPort(command.Command): @@ -425,6 +446,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 ' @@ -580,6 +607,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 @@ -592,6 +625,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: @@ -814,11 +886,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) @@ -872,6 +944,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( @@ -883,11 +961,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 @@ -1185,6 +1263,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 = '' @@ -1290,6 +1372,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( @@ -1313,13 +1400,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 @@ -1380,6 +1468,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) @@ -1391,11 +1485,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 @@ -1428,6 +1522,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 @@ -1435,7 +1535,8 @@ 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) @@ -1445,14 +1546,15 @@ class RebuildServer(command.ShowOne): 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))) @@ -1482,11 +1584,10 @@ class RemoveFixedIP(command.Command): server.remove_fixed_ip(parsed_args.ip_address) -class RemoveFloatingIP(command.Command): +class RemoveFloatingIP(network_common.NetworkAndComputeCommand): _description = _("Remove floating IP address from server") - def get_parser(self, prog_name): - parser = super(RemoveFloatingIP, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( "server", metavar="<server>", @@ -1501,13 +1602,21 @@ class RemoveFloatingIP(command.Command): ) return parser - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + def take_action_network(self, client, parsed_args): + attrs = {} + obj = client.find_ip( + parsed_args.ip_address, + ignore_missing=False, + ) + attrs['port_id'] = None - server = utils.find_resource( - compute_client.servers, parsed_args.server) + client.update_ip(obj, **attrs) - server.remove_floating_ip(parsed_args.ip_address) + def take_action_compute(self, client, parsed_args): + client.api.floating_ip_remove( + parsed_args.server, + parsed_args.ip_address, + ) class RemovePort(command.Command): @@ -1727,6 +1836,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, @@ -1745,11 +1859,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) @@ -1890,7 +2004,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) @@ -1915,11 +2031,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/common.py b/openstackclient/identity/common.py index e119f660..f36f5f73 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -101,6 +101,13 @@ def _get_token_resource(client, resource, parsed_name, parsed_domain=None): # user/project under different domain may has a same name if parsed_domain and parsed_domain not in obj['domain'].values(): return parsed_name + if isinstance(obj, list): + for item in obj: + if item['name'] == parsed_name: + return item['id'] + if item['id'] == parsed_name: + return parsed_name + return parsed_name return obj['id'] if obj['name'] == parsed_name else parsed_name # diaper defense in case parsing the token fails except Exception: # noqa diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py new file mode 100644 index 00000000..747fa20e --- /dev/null +++ b/openstackclient/identity/v3/application_credential.py @@ -0,0 +1,220 @@ +# Copyright 2018 SUSE Linux GmbH +# +# 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. +# + +"""Identity v3 Application Credential action implementations""" + +import datetime +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 + + +LOG = logging.getLogger(__name__) + + +class CreateApplicationCredential(command.ShowOne): + _description = _("Create new application credential") + + def get_parser(self, prog_name): + parser = super(CreateApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='<name>', + help=_('Name of the application credential'), + ) + parser.add_argument( + '--secret', + metavar='<secret>', + help=_('Secret to use for authentication (if not provided, one' + ' will be generated)'), + ) + parser.add_argument( + '--role', + metavar='<role>', + action='append', + default=[], + help=_('Roles to authorize (name or ID) (repeat option to set' + ' multiple values)'), + ) + parser.add_argument( + '--expiration', + metavar='<expiration>', + help=_('Sets an expiration date for the application credential,' + ' format of YYYY-mm-ddTHH:MM:SS (if not provided, the' + ' application credential will not expire)'), + ) + parser.add_argument( + '--description', + metavar='<description>', + help=_('Application credential description'), + ) + parser.add_argument( + '--unrestricted', + action="store_true", + help=_('Enable application credential to create and delete other' + ' application credentials and trusts (this is potentially' + ' dangerous behavior and is disabled by default)'), + ) + parser.add_argument( + '--restricted', + action="store_true", + help=_('Prohibit application credential from creating and deleting' + ' other application credentials and trusts (this is the' + ' default behavior)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + role_ids = [] + for role in parsed_args.role: + # A user can only create an application credential for themself, + # not for another user even as an admin, and only on the project to + # which they are currently scoped with a subset of the role + # assignments they have on that project. Don't bother trying to + # look up roles via keystone, just introspect the token. + role_id = common._get_token_resource(identity_client, "roles", + role) + role_ids.append(role_id) + + expires_at = None + if parsed_args.expiration: + expires_at = datetime.datetime.strptime(parsed_args.expiration, + '%Y-%m-%dT%H:%M:%S') + + if parsed_args.restricted: + unrestricted = False + else: + unrestricted = parsed_args.unrestricted + + app_cred_manager = identity_client.application_credentials + application_credential = app_cred_manager.create( + parsed_args.name, + roles=role_ids, + expires_at=expires_at, + description=parsed_args.description, + secret=parsed_args.secret, + unrestricted=unrestricted, + ) + + application_credential._info.pop('links', None) + + # Format roles into something sensible + roles = application_credential._info.pop('roles') + msg = ' '.join(r['name'] for r in roles) + application_credential._info['roles'] = msg + + return zip(*sorted(six.iteritems(application_credential._info))) + + +class DeleteApplicationCredential(command.Command): + _description = _("Delete application credentials(s)") + + def get_parser(self, prog_name): + parser = super(DeleteApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + 'application_credential', + metavar='<application-credential>', + nargs="+", + help=_('Application credentials(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for ac in parsed_args.application_credential: + try: + app_cred = utils.find_resource( + identity_client.application_credentials, ac) + identity_client.application_credentials.delete(app_cred.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete application credential with " + "name or ID '%(ac)s': %(e)s"), + {'ac': ac, 'e': e}) + + if errors > 0: + total = len(parsed_args.application_credential) + msg = (_("%(errors)s of %(total)s application credentials failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListApplicationCredential(command.Lister): + _description = _("List application credentials") + + def get_parser(self, prog_name): + parser = super(ListApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='<user>', + help=_('User whose application credentials to list (name or ID)'), + ) + common.add_user_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + if parsed_args.user: + user_id = common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain).id + else: + user_id = None + + columns = ('ID', 'Name', 'Project ID', 'Description', 'Expires At') + data = identity_client.application_credentials.list( + user=user_id) + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowApplicationCredential(command.ShowOne): + _description = _("Display application credential details") + + def get_parser(self, prog_name): + parser = super(ShowApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + 'application_credential', + metavar='<application-credential>', + help=_('Application credential to display (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + app_cred = utils.find_resource(identity_client.application_credentials, + parsed_args.application_credential) + + app_cred._info.pop('links', None) + + # Format roles into something sensible + roles = app_cred._info.pop('roles') + msg = ' '.join(r['name'] for r in roles) + app_cred._info['roles'] = msg + + return zip(*sorted(six.iteritems(app_cred._info))) diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py new file mode 100644 index 00000000..e254973b --- /dev/null +++ b/openstackclient/identity/v3/endpoint_group.py @@ -0,0 +1,324 @@ +# 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. +# + +"""Identity v3 Endpoint Group action implementations""" + +import json +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 + + +LOG = logging.getLogger(__name__) + + +class _FiltersReader(object): + _description = _("Helper class capable of reading filters from files") + + def _read_filters(self, path): + """Read and parse rules from path + + Expect the file to contain a valid JSON structure. + + :param path: path to the file + :return: loaded and valid dictionary with filters + :raises exception.CommandError: In case the file cannot be + accessed or the content is not a valid JSON. + + Example of the content of the file: + { + "interface": "admin", + "service_id": "1b501a" + } + """ + blob = utils.read_blob_file_contents(path) + try: + rules = json.loads(blob) + except ValueError as e: + msg = _("An error occurred when reading filters from file " + "%(path)s: %(error)s") % {"path": path, "error": e} + raise exceptions.CommandError(msg) + else: + return rules + + +class AddProjectToEndpointGroup(command.Command): + _description = _("Add a project to an endpoint group") + + def get_parser(self, prog_name): + parser = super( + AddProjectToEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='<endpoint-group>', + help=_('Endpoint group (name or ID)'), + ) + parser.add_argument( + 'project', + metavar='<project>', + help=_('Project to associate (name or ID)'), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpointgroup = utils.find_resource(client.endpoint_groups, + parsed_args.endpointgroup) + + project = common.find_project(client, + parsed_args.project, + parsed_args.project_domain) + + client.endpoint_filter.add_endpoint_group_to_project( + endpoint_group=endpointgroup.id, + project=project.id) + + +class CreateEndpointGroup(command.ShowOne, _FiltersReader): + _description = _("Create new endpoint group") + + def get_parser(self, prog_name): + parser = super(CreateEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='<name>', + help=_('Name of the endpoint group'), + ) + parser.add_argument( + 'filters', + metavar='<filename>', + help=_('Filename that contains a new set of filters'), + ) + parser.add_argument( + '--description', + help=_('Description of the endpoint group'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + filters = None + if parsed_args.filters: + filters = self._read_filters(parsed_args.filters) + + endpoint_group = identity_client.endpoint_groups.create( + name=parsed_args.name, + filters=filters, + description=parsed_args.description + ) + + info = {} + endpoint_group._info.pop('links') + info.update(endpoint_group._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteEndpointGroup(command.Command): + _description = _("Delete endpoint group(s)") + + def get_parser(self, prog_name): + parser = super(DeleteEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='<endpoint-group>', + nargs='+', + help=_('Endpoint group(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + result = 0 + for i in parsed_args.endpointgroup: + try: + endpoint_id = utils.find_resource( + identity_client.endpoint_groups, i).id + identity_client.endpoint_groups.delete(endpoint_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete endpoint group with " + "ID '%(endpointgroup)s': %(e)s"), + {'endpointgroup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.endpointgroup) + msg = (_("%(result)s of %(total)s endpointgroups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListEndpointGroup(command.Lister): + _description = _("List endpoint groups") + + def get_parser(self, prog_name): + parser = super(ListEndpointGroup, self).get_parser(prog_name) + list_group = parser.add_mutually_exclusive_group() + list_group.add_argument( + '--endpointgroup', + metavar='<endpoint-group>', + help=_('Endpoint Group (name or ID)'), + ) + list_group.add_argument( + '--project', + metavar='<project>', + help=_('Project (name or ID)'), + ) + parser.add_argument( + '--domain', + metavar='<domain>', + help=_('Domain owning <project> (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpointgroup = None + if parsed_args.endpointgroup: + endpointgroup = utils.find_resource(client.endpoint_groups, + parsed_args.endpointgroup) + project = None + if parsed_args.project: + project = common.find_project(client, + parsed_args.project, + parsed_args.domain) + + if endpointgroup: + # List projects associated to the endpoint group + columns = ('ID', 'Name') + data = client.endpoint_filter.list_projects_for_endpoint_group( + endpoint_group=endpointgroup.id) + elif project: + columns = ('ID', 'Name') + data = client.endpoint_filter.list_endpoint_groups_for_project( + project=project.id) + else: + columns = ('ID', 'Name', 'Description') + data = client.endpoint_groups.list() + + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class RemoveProjectFromEndpointGroup(command.Command): + _description = _("Remove project from endpoint group") + + def get_parser(self, prog_name): + parser = super( + RemoveProjectFromEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='<endpoint-group>', + help=_('Endpoint group (name or ID)'), + ) + parser.add_argument( + 'project', + metavar='<project>', + help=_('Project to remove (name or ID)'), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpointgroup = utils.find_resource(client.endpoint_groups, + parsed_args.endpointgroup) + + project = common.find_project(client, + parsed_args.project, + parsed_args.project_domain) + + client.endpoint_filter.delete_endpoint_group_to_project( + endpoint_group=endpointgroup.id, + project=project.id) + + +class SetEndpointGroup(command.Command, _FiltersReader): + _description = _("Set endpoint group properties") + + def get_parser(self, prog_name): + parser = super(SetEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='<endpoint-group>', + help=_('Endpoint Group to modify (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='<name>', + help=_('New enpoint group name'), + ) + parser.add_argument( + '--filters', + metavar='<filename>', + help=_('Filename that contains a new set of filters'), + ) + parser.add_argument( + '--description', + metavar='<description>', + default='', + help=_('New endpoint group description'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + endpointgroup = utils.find_resource(identity_client.endpoint_groups, + parsed_args.endpointgroup) + + filters = None + if parsed_args.filters: + filters = self._read_filters(parsed_args.filters) + + identity_client.endpoint_groups.update( + endpointgroup.id, + name=parsed_args.name, + filters=filters, + description=parsed_args.description + ) + + +class ShowEndpointGroup(command.ShowOne): + _description = _("Display endpoint group details") + + def get_parser(self, prog_name): + parser = super(ShowEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='<endpointgroup>', + help=_('Endpoint group (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + endpoint_group = utils.find_resource(identity_client.endpoint_groups, + parsed_args.endpointgroup) + + info = {} + endpoint_group._info.pop('links') + info.update(endpoint_group._info) + return zip(*sorted(six.iteritems(info))) 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/project.py b/openstackclient/identity/v3/project.py index 60efbac4..e819a0a8 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -26,7 +26,7 @@ import six from openstackclient.i18n import _ from openstackclient.identity import common - +from openstackclient.identity.v3 import tag LOG = logging.getLogger(__name__) @@ -79,6 +79,7 @@ class CreateProject(command.ShowOne): action='store_true', help=_('Return existing project'), ) + tag.add_tag_option_to_parser_for_create(parser, _('project')) return parser def take_action(self, parsed_args): @@ -102,6 +103,7 @@ class CreateProject(command.ShowOne): kwargs = {} if parsed_args.property: kwargs = parsed_args.property.copy() + kwargs['tags'] = list(set(parsed_args.tags)) try: project = identity_client.projects.create( @@ -207,6 +209,7 @@ class ListProject(command.Lister): '(default: asc), repeat this option to specify multiple ' 'keys and directions.'), ) + tag.add_tag_filtering_option_to_parser(parser, _('projects')) return parser def take_action(self, parsed_args): @@ -234,6 +237,8 @@ class ListProject(command.Lister): kwargs['user'] = user_id + tag.get_tag_filtering_args(parsed_args, kwargs) + if parsed_args.my_projects: # NOTE(adriant): my-projects supersedes all the other filters. kwargs = {'user': self.app.client_manager.auth_ref.user_id} @@ -303,6 +308,7 @@ class SetProject(command.Command): help=_('Set a property on <project> ' '(repeat option to set multiple properties)'), ) + tag.add_tag_option_to_parser_for_set(parser, _('project')) return parser def take_action(self, parsed_args): @@ -323,6 +329,7 @@ class SetProject(command.Command): kwargs['enabled'] = False if parsed_args.property: kwargs.update(parsed_args.property) + tag.update_tags_in_args(parsed_args, project, kwargs) identity_client.projects.update(project.id, **kwargs) 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/tag.py b/openstackclient/identity/v3/tag.py new file mode 100644 index 00000000..abf022d4 --- /dev/null +++ b/openstackclient/identity/v3/tag.py @@ -0,0 +1,116 @@ +# 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 argparse + +from openstackclient.i18n import _ + + +class _CommaListAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values.split(',')) + + +def add_tag_filtering_option_to_parser(parser, collection_name): + parser.add_argument( + '--tags', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('List %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--tags-any', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('List %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-tags', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('Exclude %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-tags-any', + metavar='<tag>[,<tag>,...]', + action=_CommaListAction, + help=_('Exclude %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + + +def get_tag_filtering_args(parsed_args, args): + if parsed_args.tags: + args['tags'] = ','.join(parsed_args.tags) + if parsed_args.tags_any: + args['tags-any'] = ','.join(parsed_args.tags_any) + if parsed_args.not_tags: + args['not-tags'] = ','.join(parsed_args.not_tags) + if parsed_args.not_tags_any: + args['not-tags-any'] = ','.join(parsed_args.not_tags_any) + + +def add_tag_option_to_parser_for_create(parser, resource_name): + tag_group = parser.add_mutually_exclusive_group() + tag_group.add_argument( + '--tag', + action='append', + dest='tags', + metavar='<tag>', + default=[], + help=_('Tag to be added to the %s ' + '(repeat option to set multiple tags)') % resource_name + ) + + +def add_tag_option_to_parser_for_set(parser, resource_name): + parser.add_argument( + '--tag', + action='append', + dest='tags', + metavar='<tag>', + default=[], + help=_('Tag to be added to the %s ' + '(repeat option to set multiple tags)') % resource_name + ) + parser.add_argument( + '--clear-tags', + action='store_true', + help=_('Clear tags associated with the %s. Specify ' + 'both --tag and --clear-tags to overwrite ' + 'current tags') % resource_name + ) + parser.add_argument( + '--remove-tag', + metavar='<tag>', + default=[], + help=_('Tag to be deleted from the %s ' + '(repeat option to delete multiple tags)') % resource_name + ) + + +def update_tags_in_args(parsed_args, obj, args): + if parsed_args.clear_tags: + args['tags'] = [] + obj.tags = [] + if parsed_args.remove_tag: + if parsed_args.remove_tag in obj.tags: + obj.tags.remove(parsed_args.remove_tag) + args['tags'] = list(set(obj.tags)) + return + if parsed_args.tags: + args['tags'] = list(set(obj.tags).union( + set(parsed_args.tags))) 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..4a51062f 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -17,7 +17,6 @@ import argparse import logging -import sys from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions @@ -592,6 +591,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 +685,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/locale/tr_TR/LC_MESSAGES/openstackclient.po b/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po index f3470f18..b44baf88 100644 --- a/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po @@ -2,18 +2,18 @@ # işbaran akçayır <isbaran@gmail.com>, 2017. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 3.12.1.dev21\n" +"Project-Id-Version: python-openstackclient VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2017-08-14 16:23+0000\n" +"POT-Creation-Date: 2018-02-25 01:10+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-08-15 12:09+0000\n" "Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n" "Language-Team: Turkish (Turkey)\n" -"Language: tr-TR\n" +"Language: tr_TR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Zanata 3.9.6\n" +"X-Generator: Zanata 4.3.3\n" "X-POOTLE-MTIME: 1502656444.000000\n" #, python-format @@ -308,12 +308,6 @@ msgstr "" msgid "Activate the image" msgstr "İmajı aktifleştir" -msgid "" -"Add a policy to <name> ('affinity' or 'anti-affinity', default to 'affinity')" -msgstr "" -"<name>'e bir politika ekle ('affinity' veya 'anti-affinity', varsayılan " -"'affinity' için)" - msgid "Add a port to a router" msgstr "Yönlendiriciye bir bağlantı noktası ekle" @@ -2552,9 +2546,6 @@ msgstr "Servise göre filtrele (tür, isim veya ID)" msgid "Filter credentials by <user> (name or ID)" msgstr "Kimlik bilgilerini <user>'ya göre filtrele (isim veya ID)" -msgid "Filter credentials by type: cert, ec2" -msgstr "Kimlik bilgilerinin türe göre filtrele: cert, ec2" - msgid "Filter group list by <domain> (name or ID)" msgstr "Grup listesini <domain>'e göre filtrele (isim veya ID)" @@ -2817,15 +2808,6 @@ msgstr "Sunucuya eklenecek IP adresi (sadece isim)" msgid "IP address to remove from server (name only)" msgstr "Sunucudan kaldırılacak IP adresi (sadece isim)" -msgid "" -"IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, " -"ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " -"udp, udplite, vrrp and integer representations [0-255]; default: tcp)" -msgstr "" -"IP protokolü (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, " -"ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " -"udp, udplite, vrrp ve tam sayı gösterimi [0-255]; varsayılan: tcp)" - msgid "IP protocol (icmp, tcp, udp; default: tcp)" msgstr "IP protokolü (icmp, tcp, udp; varsayılan: tcp)" @@ -4029,9 +4011,6 @@ msgstr "Yeni kap ismi" msgid "New credential data" msgstr "Yeni kimlik bilgileri verisi" -msgid "New credential type: cert, ec2" -msgstr "Yeni kimlik bilgisi türü: cert, ec2" - msgid "New domain description" msgstr "Yeni alan tanımı" @@ -4599,13 +4578,6 @@ msgstr "RX/TX faktörü (varsayılan 1.0)" msgid "Read image data from standard input" msgstr "Standart girdiden imaj verilerini oku" -msgid "" -"Reason for disabling the service (in quotas). Should be used with --disable " -"option." -msgstr "" -"Servisi devre dışı bırakma sebebi (kota cinsinden). --disable seçeneği ile " -"birlikte kullanılmalı." - msgid "Reason for disabling the service (should be used with --disable option)" msgstr "" "Servisi devredışı bırakmak için sebep (--disable seçeneği ile kullanılmalı)" @@ -6046,9 +6018,6 @@ msgstr "Silinecek altağ(lar) (isim veya ID)" msgid "Suspend server(s)" msgstr "Sunucu(yu/ları) durdur" -msgid "Swap space size in MB (default 0M)" -msgstr "MB cinsinden takas alanı boyutu (varsayılan 0M)" - #, python-format msgid "Tag to be added to the %s (repeat option to set multiple tags)" msgstr "" @@ -6342,17 +6311,6 @@ msgstr "" "Proje özelliğini kaldır (birden fazla özellik kaldırmak için seçeneği " "tekrarla)" -msgid "" -"Unset a property on this image (repeat option to set multiple properties)" -msgstr "" -"Bu imajdaki bir özelliği kaldır (birden fazla özelliği ayarlamak için " -"seçeneği tekrarla)" - -msgid "Unset a tag on this image (repeat option to set multiple tags)" -msgstr "" -"Bu imajdan etiketi kaldır (birden fazla etiket ayarlamak için seçeneği " -"tekrarla)" - msgid "Unset account properties" msgstr "Hesap özelliklerinin ayarını kaldır" diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index eca0de3c..37bf1406 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -48,7 +48,10 @@ class NetworkAndComputeCommand(command.Command): parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) parser = self.update_parser_common(parser) LOG.debug('common parser: %s', parser) - if self.app.client_manager.is_network_endpoint_enabled(): + if ( + self.app is None or + self.app.client_manager.is_network_endpoint_enabled() + ): return self.update_parser_network(parser) else: return self.update_parser_compute(parser) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index a6f03404..9019e01d 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,7 @@ 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 def _get_network_columns(item): @@ -139,11 +138,14 @@ class CreateFloatingIP(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, _('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) + # 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 +157,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 +184,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 +234,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 +263,13 @@ class ListFloatingIP(common.NetworkAndComputeLister): 'router_id', 'status', 'description', + 'tags', ) headers = headers + ( 'Router', 'Status', 'Description', + 'Tags', ) query = {} @@ -342,6 +299,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 +334,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") @@ -412,7 +347,7 @@ class SetFloatingIP(command.Command): '--port', metavar='<port>', required=True, - help=_("Assocaite the floating IP with port (name or ID)")), + help=_("Associate the floating IP with port (name or ID)")), parser.add_argument( '--fixed-ip-address', metavar='<ip-address>', @@ -431,6 +366,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): @@ -453,7 +391,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): @@ -483,30 +425,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 +446,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 +464,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..d1c7f005 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 @@ -217,6 +221,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', @@ -619,6 +628,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', diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index f50e58b3..9c4275a8 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) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 032e1787..f13ee7b9 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -127,6 +127,8 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.mac_address is not None: attrs['mac_address'] = parsed_args.mac_address + if parsed_args.dns_domain is not None: + attrs['dns_domain'] = parsed_args.dns_domain if parsed_args.dns_name is not None: attrs['dns_name'] = parsed_args.dns_name # It is possible that name is not updated during 'port set' @@ -269,6 +271,12 @@ def _add_updatable_args(parser): help=argparse.SUPPRESS, ) parser.add_argument( + '--dns-domain', + metavar='dns-domain', + help=_("Set DNS domain to this port " + "(requires dns_domain extension for ports)") + ) + parser.add_argument( '--dns-name', metavar='dns-name', help=_("Set DNS name to this port " diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index caf3236a..f0a51967 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 diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2c71e1e0..9c56186f 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() @@ -559,6 +559,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 +589,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/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py index 7f274099..3a9aef9e 100644 --- a/openstackclient/tests/functional/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -60,8 +60,12 @@ class HelpTests(base.TestCase): """Check server commands in main help message.""" raw_output = self.openstack('help') for command, description in self.SERVER_COMMANDS: - self.assertIn(command, raw_output) - self.assertIn(description, raw_output) + msg = 'Command: %s not found in help output:\n%s' % ( + command, raw_output) + self.assertIn(command, raw_output, msg) + msg = 'Description: %s not found in help output:\n%s' % ( + description, raw_output) + self.assertIn(description, raw_output, msg) def test_server_only_help(self): """Check list of server-related commands only.""" 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/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 33cb5d86..54132be5 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -52,6 +52,8 @@ 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'] @classmethod def setUpClass(cls): @@ -149,6 +151,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') diff --git a/openstackclient/tests/functional/identity/v3/test_application_credential.py b/openstackclient/tests/functional/identity/v3/test_application_credential.py new file mode 100644 index 00000000..daf64607 --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_application_credential.py @@ -0,0 +1,143 @@ +# Copyright 2018 SUSE Linux GmbH +# +# 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 datetime + +from tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class ApplicationCredentialTests(common.IdentityTests): + + APPLICATION_CREDENTIAL_FIELDS = ['id', 'name', 'project_id', + 'description', 'roles', 'expires_at', + 'unrestricted'] + APPLICATION_CREDENTIAL_LIST_HEADERS = ['ID', 'Name', 'Project ID', + 'Description', 'Expires At'] + + def test_application_credential_create(self): + name = data_utils.rand_name('name') + raw_output = self.openstack('application credential create %(name)s' + % {'name': name}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) + + def _create_role_assignments(self): + try: + user = self.openstack('configuration show -f value' + ' -c auth.username') + except Exception: + user = self.openstack('configuration show -f value' + ' -c auth.user_id') + try: + user_domain = self.openstack('configuration show -f value' + ' -c auth.user_domain_name') + except Exception: + user_domain = self.openstack('configuration show -f value' + ' -c auth.user_domain_id') + try: + project = self.openstack('configuration show -f value' + ' -c auth.project_name') + except Exception: + project = self.openstack('configuration show -f value' + ' -c auth.project_id') + try: + project_domain = self.openstack('configuration show -f value' + ' -c auth.project_domain_name') + except Exception: + project_domain = self.openstack('configuration show -f value' + ' -c auth.project_domain_id') + role1 = self._create_dummy_role() + role2 = self._create_dummy_role() + for role in role1, role2: + self.openstack('role add' + ' --user %(user)s' + ' --user-domain %(user_domain)s' + ' --project %(project)s' + ' --project-domain %(project_domain)s' + ' %(role)s' + % {'user': user, + 'user_domain': user_domain, + 'project': project, + 'project_domain': project_domain, + 'role': role}) + self.addCleanup(self.openstack, + 'role remove' + ' --user %(user)s' + ' --user-domain %(user_domain)s' + ' --project %(project)s' + ' --project-domain %(project_domain)s' + ' %(role)s' + % {'user': user, + 'user_domain': user_domain, + 'project': project, + 'project_domain': project_domain, + 'role': role}) + return role1, role2 + + def test_application_credential_create_with_options(self): + name = data_utils.rand_name('name') + secret = data_utils.rand_name('secret') + description = data_utils.rand_name('description') + tomorrow = (datetime.datetime.utcnow() + + datetime.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S%z') + role1, role2 = self._create_role_assignments() + raw_output = self.openstack('application credential create %(name)s' + ' --secret %(secret)s' + ' --description %(description)s' + ' --expiration %(tomorrow)s' + ' --role %(role1)s' + ' --role %(role2)s' + ' --unrestricted' + % {'name': name, + 'secret': secret, + 'description': description, + 'tomorrow': tomorrow, + 'role1': role1, + 'role2': role2}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) + + def test_application_credential_delete(self): + name = data_utils.rand_name('name') + self.openstack('application credential create %(name)s' + % {'name': name}) + raw_output = self.openstack('application credential delete ' + '%(name)s' % {'name': name}) + self.assertEqual(0, len(raw_output)) + + def test_application_credential_list(self): + raw_output = self.openstack('application credential list') + items = self.parse_listing(raw_output) + self.assert_table_structure( + items, self.APPLICATION_CREDENTIAL_LIST_HEADERS) + + def test_application_credential_show(self): + name = data_utils.rand_name('name') + raw_output = self.openstack('application credential create %(name)s' + % {'name': name}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + raw_output = self.openstack('application credential show ' + '%(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) 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/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_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_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/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index 4f3b8031..edf5258f 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -55,6 +55,43 @@ class TestFloatingIP(TestComputeAPIv2): FAKE_FLOATING_IP_RESP_2, ] + FAKE_SERVER_RESP_1 = { + 'id': 1, + 'name': 'server1', + } + + def test_floating_ip_add_id(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + json={'server': {}}, + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_add('1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + + def test_floating_ip_add_name(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + json={'server': {}}, + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/server1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_add('server1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + def test_floating_ip_create(self): self.requests_mock.register_uri( 'POST', @@ -144,6 +181,36 @@ class TestFloatingIP(TestComputeAPIv2): ret = self.api.floating_ip_list() self.assertEqual(self.LIST_FLOATING_IP_RESP, ret) + def test_floating_ip_remove_id(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_remove('1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + + def test_floating_ip_remove_name(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/server1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_remove('server1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + class TestFloatingIPPool(TestComputeAPIv2): @@ -163,6 +230,115 @@ class TestFloatingIPPool(TestComputeAPIv2): self.assertEqual(self.LIST_FLOATING_IP_POOL_RESP, ret) +class TestHost(TestComputeAPIv2): + + FAKE_HOST_RESP_1 = { + "zone": "internal", + "host_name": "myhost", + "service": "conductor", + } + + FAKE_HOST_RESP_2 = { + "zone": "internal", + "host_name": "myhost", + "service": "scheduler", + } + + FAKE_HOST_RESP_3 = { + "zone": "nova", + "host_name": "myhost", + "service": "compute", + } + + LIST_HOST_RESP = [ + FAKE_HOST_RESP_1, + FAKE_HOST_RESP_2, + FAKE_HOST_RESP_3, + ] + + def test_host_list_no_options(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts', + json={'hosts': self.LIST_HOST_RESP}, + status_code=200, + ) + ret = self.api.host_list() + self.assertEqual(self.LIST_HOST_RESP, ret) + + def test_host_list_zone(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts?zone=nova', + json={'hosts': [self.FAKE_HOST_RESP_3]}, + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts', + json={'hosts': [self.FAKE_HOST_RESP_3]}, + status_code=200, + ) + ret = self.api.host_list(zone='nova') + self.assertEqual([self.FAKE_HOST_RESP_3], ret) + + def test_host_set_none(self): + ret = self.api.host_set(host='myhost') + self.assertIsNone(ret) + + def test_host_set(self): + self.requests_mock.register_uri( + 'PUT', + FAKE_URL + '/os-hosts/myhost', + json={}, + status_code=200, + ) + ret = self.api.host_set(host='myhost', status='enabled') + self.assertEqual({}, ret) + + def test_host_show(self): + FAKE_RESOURCE_1 = { + "cpu": 2, + "disk_gb": 1028, + "host": "c1a7de0ac9d94e4baceae031d05caae3", + "memory_mb": 8192, + "project": "(total)", + } + FAKE_RESOURCE_2 = { + "cpu": 0, + "disk_gb": 0, + "host": "c1a7de0ac9d94e4baceae031d05caae3", + "memory_mb": 512, + "project": "(used_now)", + } + FAKE_RESOURCE_3 = { + "cpu": 0, + "disk_gb": 0, + "host": "c1a7de0ac9d94e4baceae031d05caae3", + "memory_mb": 0, + "project": "(used_max)", + } + FAKE_HOST_RESP = [ + {'resource': FAKE_RESOURCE_1}, + {'resource': FAKE_RESOURCE_2}, + {'resource': FAKE_RESOURCE_3}, + ] + FAKE_HOST_LIST = [ + FAKE_RESOURCE_1, + FAKE_RESOURCE_2, + FAKE_RESOURCE_3, + ] + + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts/myhost', + json={'host': FAKE_HOST_RESP}, + status_code=200, + ) + ret = self.api.host_show(host='myhost') + self.assertEqual(FAKE_HOST_LIST, ret) + + class TestNetwork(TestComputeAPIv2): FAKE_NETWORK_RESP = { 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/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 0fae19af..46fa5992 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -149,6 +149,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, {}) @@ -1230,10 +1233,7 @@ class FakeHost(object): 'project': 'project-' + uuid.uuid4().hex, } host_info.update(attrs) - host = fakes.FakeResource( - info=copy.deepcopy(host_info), - loaded=True) - return host + return host_info class FakeServerGroup(object): @@ -1395,3 +1395,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_host.py b/openstackclient/tests/unit/compute/v2/test_host.py index a388172f..329095de 100644 --- a/openstackclient/tests/unit/compute/v2/test_host.py +++ b/openstackclient/tests/unit/compute/v2/test_host.py @@ -13,6 +13,8 @@ # under the License. # +import mock + from openstackclient.compute.v2 import host from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import utils as tests_utils @@ -23,11 +25,13 @@ class TestHost(compute_fakes.TestComputev2): def setUp(self): super(TestHost, self).setUp() - # Get a shortcut to the FlavorManager Mock - self.host_mock = self.app.client_manager.compute.hosts - self.host_mock.reset_mock() + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.host_list' +) class TestHostList(TestHost): host = compute_fakes.FakeHost.create_one_host() @@ -39,19 +43,18 @@ class TestHostList(TestHost): ) data = [( - host.host_name, - host.service, - host.zone, + host['host_name'], + host['service'], + host['zone'], )] def setUp(self): super(TestHostList, self).setUp() - self.host_mock.list_all.return_value = [self.host] - self.cmd = host.ListHost(self.app, None) - def test_host_list_no_option(self): + def test_host_list_no_option(self, h_mock): + h_mock.return_value = [self.host] arglist = [] verifylist = [] @@ -59,44 +62,48 @@ class TestHostList(TestHost): columns, data = self.cmd.take_action(parsed_args) - self.host_mock.list_all.assert_called_with(None) + h_mock.assert_called_with(None) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_host_list_with_option(self): + def test_host_list_with_option(self, h_mock): + h_mock.return_value = [self.host] arglist = [ - '--zone', self.host.zone, + '--zone', self.host['zone'], ] verifylist = [ - ('zone', self.host.zone), + ('zone', self.host['zone']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.host_mock.list_all.assert_called_with(self.host.zone) + h_mock.assert_called_with(self.host['zone']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.host_set' +) class TestHostSet(TestHost): def setUp(self): super(TestHostSet, self).setUp() self.host = compute_fakes.FakeHost.create_one_host() - self.host_mock.get.return_value = self.host - self.host_mock.update.return_value = None self.cmd = host.SetHost(self.app, None) - def test_host_set_no_option(self): + def test_host_set_no_option(self, h_mock): + h_mock.return_value = self.host + h_mock.update.return_value = None arglist = [ - self.host.host + self.host['host'], ] verifylist = [ - ('host', self.host.host) + ('host', self.host['host']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -105,18 +112,20 @@ class TestHostSet(TestHost): self.assertIsNone(result) body = {} - self.host_mock.update.assert_called_with(self.host.host, body) + h_mock.assert_called_with(self.host['host'], body) - def test_host_set(self): + def test_host_set(self, h_mock): + h_mock.return_value = self.host + h_mock.update.return_value = None arglist = [ '--enable', '--disable-maintenance', - self.host.host + self.host['host'], ] verifylist = [ ('enable', True), ('enable_maintenance', False), - ('host', self.host.host) + ('host', self.host['host']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -125,9 +134,12 @@ class TestHostSet(TestHost): self.assertIsNone(result) body = {'status': 'enable', 'maintenance_mode': 'disable'} - self.host_mock.update.assert_called_with(self.host.host, body) + h_mock.assert_called_with(self.host['host'], body) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.host_show' +) class TestHostShow(TestHost): host = compute_fakes.FakeHost.create_one_host() @@ -139,22 +151,22 @@ class TestHostShow(TestHost): 'Memory MB', 'Disk GB', ) + data = [( - host.host, - host.project, - host.cpu, - host.memory_mb, - host.disk_gb, + host['host'], + host['project'], + host['cpu'], + host['memory_mb'], + host['disk_gb'], )] def setUp(self): super(TestHostShow, self).setUp() - self.host_mock.get.return_value = [self.host] - self.cmd = host.ShowHost(self.app, None) - def test_host_show_no_option(self): + def test_host_show_no_option(self, h_mock): + h_mock.host_show.return_value = [self.host] arglist = [] verifylist = [] @@ -162,18 +174,19 @@ class TestHostShow(TestHost): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) - def test_host_show_with_option(self): + def test_host_show_with_option(self, h_mock): + h_mock.return_value = [self.host] arglist = [ - self.host.host_name, + self.host['host_name'], ] verifylist = [ - ('host', self.host.host_name), + ('host', self.host['host_name']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.host_mock.get.assert_called_with(self.host.host_name) + h_mock.assert_called_with(self.host['host_name']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c4e125ba..a53c6c81 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -141,48 +141,148 @@ class TestServerAddFixedIP(TestServer): self._test_server_add_fixed_ip(extralist, '5.6.7.8') -class TestServerAddFloatingIP(TestServer): +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_add' +) +class TestServerAddFloatingIPCompute(compute_fakes.TestComputev2): def setUp(self): - super(TestServerAddFloatingIP, self).setUp() + super(TestServerAddFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False # Get the command object to test self.cmd = server.AddFloatingIP(self.app, None) - # Set add_floating_ip method to be tested. - self.methods = { - 'add_floating_ip': None, - } + def test_server_add_floating_ip_default(self, fip_mock): + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + arglist = [ + 'server1', + _floating_ip['ip'], + ] + verifylist = [ + ('server', 'server1'), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.find_port = mock.Mock() - self.app.client_manager.network.find_port = self.find_port + self.cmd.take_action(parsed_args) - def _test_server_add_floating_ip(self, extralist, fixed_ip_address): - servers = self.setup_servers_mock(count=1) + fip_mock.assert_called_once_with( + 'server1', + _floating_ip['ip'], + fixed_address=None, + ) + def test_server_add_floating_ip_fixed(self, fip_mock): + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() arglist = [ - servers[0].id, - '1.2.3.4', - ] + extralist + '--fixed-ip-address', _floating_ip['fixed_ip'], + 'server1', + _floating_ip['ip'], + ] verifylist = [ - ('server', servers[0].id), - ('ip_address', '1.2.3.4'), - ('fixed_ip_address', fixed_ip_address), + ('fixed_ip_address', _floating_ip['fixed_ip']), + ('server', 'server1'), + ('ip_address', _floating_ip['ip']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) - servers[0].add_floating_ip.assert_called_once_with('1.2.3.4', - fixed_ip_address) - self.assertIsNone(result) + fip_mock.assert_called_once_with( + 'server1', + _floating_ip['ip'], + fixed_address=_floating_ip['fixed_ip'], + ) - def test_server_add_floating_ip(self): - self._test_server_add_floating_ip([], None) - def test_server_add_floating_ip_to_fixed_ip(self): - extralist = ['--fixed-ip-address', '5.6.7.8'] - self._test_server_add_floating_ip(extralist, '5.6.7.8') +class TestServerAddFloatingIPNetwork( + TestServer, + network_fakes.TestNetworkV2, +): + + def setUp(self): + super(TestServerAddFloatingIPNetwork, self).setUp() + + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = server.AddFloatingIP(self.app, self.namespace) + + def test_server_add_floating_ip_default(self): + _server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = _server + _port = network_fakes.FakePort.create_one_port() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) + self.network.ports = mock.Mock(return_value=[_port]) + arglist = [ + _server.id, + _floating_ip['ip'], + ] + verifylist = [ + ('server', _server.id), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': _port.id, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['ip'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=_server.id, + ) + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) + + def test_server_add_floating_ip_fixed(self): + _server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = _server + _port = network_fakes.FakePort.create_one_port() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) + self.network.ports = mock.Mock(return_value=[_port]) + arglist = [ + '--fixed-ip-address', _floating_ip['fixed_ip'], + _server.id, + _floating_ip['ip'], + ] + verifylist = [ + ('fixed_ip_address', _floating_ip['fixed_ip']), + ('server', _server.id), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': _port.id, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['ip'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=_server.id, + ) + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) class TestServerAddPort(TestServer): @@ -925,7 +1025,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( @@ -975,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( @@ -1428,6 +1528,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): @@ -1494,7 +1752,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) @@ -1516,7 +1774,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, ) @@ -2073,17 +2331,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, } @@ -2152,7 +2410,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 ) @@ -2177,7 +2435,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, ) self.servers_mock.get.assert_called_with(self.server.id) @@ -2302,36 +2560,80 @@ class TestServerRescue(TestServer): self.server.rescue.assert_called_with(image=None, password=password) -class TestServerRemoveFloatingIP(TestServer): +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_remove' +) +class TestServerRemoveFloatingIPCompute(compute_fakes.TestComputev2): def setUp(self): - super(TestServerRemoveFloatingIP, self).setUp() + super(TestServerRemoveFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False # Get the command object to test self.cmd = server.RemoveFloatingIP(self.app, None) - # Set unshelve method to be tested. - self.methods = { - 'remove_floating_ip': None, - } + def test_server_remove_floating_ip(self, fip_mock): + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() - def test_server_remove_floating_ip(self): - servers = self.setup_servers_mock(count=1) + arglist = [ + 'server1', + _floating_ip['ip'], + ] + verifylist = [ + ('server', 'server1'), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + fip_mock.assert_called_once_with( + 'server1', + _floating_ip['ip'], + ) + + +class TestServerRemoveFloatingIPNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestServerRemoveFloatingIPNetwork, self).setUp() + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = server.RemoveFloatingIP(self.app, self.namespace) + + def test_server_remove_floating_ip_default(self): + _server = compute_fakes.FakeServer.create_one_server() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) arglist = [ - servers[0].id, - '1.2.3.4', + _server.id, + _floating_ip['ip'], ] verifylist = [ - ('server', servers[0].id), - ('ip_address', '1.2.3.4'), + ('server', _server.id), + ('ip_address', _floating_ip['ip']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) - servers[0].remove_floating_ip.assert_called_once_with('1.2.3.4') - self.assertIsNone(result) + attrs = { + 'port_id': None, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['ip'], + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) class TestServerRemovePort(TestServer): @@ -2619,7 +2921,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 ) @@ -2659,7 +2961,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 ) @@ -2915,6 +3217,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..954973ef 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -140,6 +140,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 +157,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 3e2caf01..7aa9cd7c 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -14,6 +14,7 @@ # import copy +import datetime import uuid from keystoneauth1 import access @@ -34,6 +35,7 @@ DOMAIN = { 'name': domain_name, 'description': domain_description, 'enabled': True, + 'tags': [], 'links': base_url + 'domains/' + domain_id, } @@ -115,6 +117,7 @@ PROJECT = { 'description': project_description, 'enabled': True, 'domain_id': domain_id, + 'tags': [], 'links': base_url + 'projects/' + project_id, } @@ -124,6 +127,7 @@ PROJECT_2 = { 'description': project_description + 'plus four more', 'enabled': True, 'domain_id': domain_id, + 'tags': [], 'links': base_url + 'projects/' + project_id, } @@ -145,6 +149,7 @@ PROJECT_WITH_PARENT = { 'enabled': True, 'domain_id': domain_id, 'parent_id': project_id, + 'tags': [], 'links': base_url + 'projects/' + (project_id + '-with-parent'), } @@ -155,6 +160,7 @@ PROJECT_WITH_GRANDPARENT = { 'enabled': True, 'domain_id': domain_id, 'parent_id': PROJECT_WITH_PARENT['id'], + 'tags': [], 'links': base_url + 'projects/' + (project_id + '-with-grandparent'), } @@ -223,6 +229,20 @@ ENDPOINT = { 'links': base_url + 'endpoints/' + endpoint_id, } +endpoint_group_id = 'eg-123' +endpoint_group_description = 'eg 123 description' +endpoint_group_filters = { + 'service_id': service_id, + 'region_id': endpoint_region, +} + +ENDPOINT_GROUP = { + 'id': endpoint_group_id, + 'filters': endpoint_group_filters, + 'description': endpoint_group_description, + 'links': base_url + 'endpoint_groups/' + endpoint_group_id, +} + user_id = 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' user_name = 'paul' user_description = 'Sir Paul' @@ -438,6 +458,34 @@ OAUTH_VERIFIER = { 'oauth_verifier': oauth_verifier_pin } +app_cred_id = 'app-cred-id' +app_cred_name = 'testing_app_cred' +app_cred_role = {"id": role_id, "name": role_name, "domain": None}, +app_cred_description = 'app credential for testing' +app_cred_expires = datetime.datetime(2022, 1, 1, 0, 0) +app_cred_expires_str = app_cred_expires.strftime('%Y-%m-%dT%H:%M:%S%z') +app_cred_secret = 'moresecuresecret' +APP_CRED_BASIC = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': None, + 'expires_at': None, + 'unrestricted': False, + 'secret': app_cred_secret +} +APP_CRED_OPTIONS = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': app_cred_description, + 'expires_at': app_cred_expires_str, + 'unrestricted': False, + 'secret': app_cred_secret +} + def fake_auth_ref(fake_token, fake_service=None): """Create an auth_ref using keystoneauth's fixtures""" @@ -495,6 +543,8 @@ class FakeIdentityv3Client(object): self.endpoints.resource_class = fakes.FakeResource(None, {}) self.endpoint_filter = mock.Mock() self.endpoint_filter.resource_class = fakes.FakeResource(None, {}) + self.endpoint_groups = mock.Mock() + self.endpoint_groups.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() self.groups.resource_class = fakes.FakeResource(None, {}) self.oauth1 = mock.Mock() @@ -523,6 +573,11 @@ class FakeIdentityv3Client(object): self.auth = FakeAuth() self.auth.client = mock.Mock() self.auth.client.resource_class = fakes.FakeResource(None, {}) + 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, {}) class FakeFederationManager(object): @@ -619,6 +674,7 @@ class FakeProject(object): 'is_domain': False, 'domain_id': 'domain-id-' + uuid.uuid4().hex, 'parent_id': 'parent-id-' + uuid.uuid4().hex, + 'tags': [], 'links': 'links-' + uuid.uuid4().hex, } project_info.update(attrs) @@ -666,6 +722,7 @@ class FakeDomain(object): 'name': 'domain-name-' + uuid.uuid4().hex, 'description': 'domain-description-' + uuid.uuid4().hex, 'enabled': True, + 'tags': [], 'links': 'links-' + uuid.uuid4().hex, } domain_info.update(attrs) diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py new file mode 100644 index 00000000..e7c8ede8 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -0,0 +1,309 @@ +# Copyright 2018 SUSE Linux GmbH +# +# 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 + +import mock +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.identity.v3 import application_credential +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestApplicationCredential(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestApplicationCredential, self).setUp() + + identity_manager = self.app.client_manager.identity + self.app_creds_mock = identity_manager.application_credentials + self.app_creds_mock.reset_mock() + self.roles_mock = identity_manager.roles + self.roles_mock.reset_mock() + + +class TestApplicationCredentialCreate(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialCreate, self).setUp() + + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE), + loaded=True, + ) + + # Get the command object to test + self.cmd = application_credential.CreateApplicationCredential( + self.app, None) + + def test_application_credential_create_basic(self): + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + + name = identity_fakes.app_cred_name + arglist = [ + name + ] + verifylist = [ + ('name', identity_fakes.app_cred_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) + + # Set expected values + kwargs = { + 'secret': None, + 'roles': [], + 'expires_at': None, + 'description': None, + 'unrestricted': False, + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + def test_application_credential_create_with_options(self): + name = identity_fakes.app_cred_name + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_OPTIONS), + loaded=True, + ) + + arglist = [ + name, + '--secret', 'moresecuresecret', + '--role', identity_fakes.role_id, + '--expiration', identity_fakes.app_cred_expires_str, + '--description', 'credential for testing' + ] + verifylist = [ + ('name', identity_fakes.app_cred_name), + ('secret', 'moresecuresecret'), + ('role', [identity_fakes.role_id]), + ('expiration', identity_fakes.app_cred_expires_str), + ('description', 'credential for testing') + ] + 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) + + # Set expected values + kwargs = { + 'secret': 'moresecuresecret', + 'roles': [identity_fakes.role_id], + 'expires_at': identity_fakes.app_cred_expires, + 'description': 'credential for testing', + 'unrestricted': False + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.app_cred_description, + identity_fakes.app_cred_expires_str, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + +class TestApplicationCredentialDelete(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.app_creds_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + self.app_creds_mock.delete.return_value = None + + # Get the command object to test + self.cmd = application_credential.DeleteApplicationCredential( + self.app, None) + + def test_application_credential_delete(self): + arglist = [ + identity_fakes.app_cred_id, + ] + verifylist = [ + ('application_credential', [identity_fakes.app_cred_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.app_creds_mock.delete.assert_called_with( + identity_fakes.app_cred_id, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_app_creds_with_exception(self, find_mock): + find_mock.side_effect = [self.app_creds_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.app_cred_id, + 'nonexistent_app_cred', + ] + verifylist = [ + ('application_credential', arglist), + ] + 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 2 application credentials failed to' + ' delete.', str(e)) + + find_mock.assert_any_call(self.app_creds_mock, + identity_fakes.app_cred_id) + find_mock.assert_any_call(self.app_creds_mock, + 'nonexistent_app_cred') + + self.assertEqual(2, find_mock.call_count) + self.app_creds_mock.delete.assert_called_once_with( + identity_fakes.app_cred_id) + + +class TestApplicationCredentialList(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialList, self).setUp() + + self.app_creds_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = application_credential.ListApplicationCredential(self.app, + None) + + def test_application_credential_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) + + self.app_creds_mock.list.assert_called_with(user=None) + + collist = ('ID', 'Name', 'Project ID', 'Description', 'Expires At') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + None, + None + ), ) + self.assertEqual(datalist, tuple(data)) + + +class TestApplicationCredentialShow(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialShow, self).setUp() + + self.app_creds_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + + # Get the command object to test + self.cmd = application_credential.ShowApplicationCredential(self.app, + None) + + def test_application_credential_show(self): + arglist = [ + identity_fakes.app_cred_id, + ] + verifylist = [ + ('application_credential', identity_fakes.app_cred_id), + ] + 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.app_creds_mock.get.assert_called_with(identity_fakes.app_cred_id) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py index 36f13d33..014986e5 100644 --- a/openstackclient/tests/unit/identity/v3/test_domain.py +++ b/openstackclient/tests/unit/identity/v3/test_domain.py @@ -31,6 +31,7 @@ class TestDomainCreate(TestDomain): 'enabled', 'id', 'name', + 'tags' ) def setUp(self): @@ -43,6 +44,7 @@ class TestDomainCreate(TestDomain): True, self.domain.id, self.domain.name, + self.domain.tags ) # Get the command object to test @@ -390,12 +392,13 @@ class TestDomainShow(TestDomain): self.domain.id, ) - collist = ('description', 'enabled', 'id', 'name') + collist = ('description', 'enabled', 'id', 'name', 'tags') self.assertEqual(collist, columns) datalist = ( self.domain.description, True, self.domain.id, self.domain.name, + self.domain.tags ) self.assertEqual(datalist, data) 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_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 16ac3116..266da227 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -50,6 +50,7 @@ class TestProjectCreate(TestProject): 'is_domain', 'name', 'parent_id', + 'tags' ) def setUp(self): @@ -67,6 +68,7 @@ class TestProjectCreate(TestProject): False, self.project.name, self.project.parent_id, + self.project.tags ) # Get the command object to test self.cmd = project.CreateProject(self.app, None) @@ -80,6 +82,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', self.project.name), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -95,6 +98,7 @@ class TestProjectCreate(TestProject): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -110,6 +114,7 @@ class TestProjectCreate(TestProject): 'is_domain', 'name', 'parent_id', + 'tags' ) self.assertEqual(collist, columns) datalist = ( @@ -120,6 +125,7 @@ class TestProjectCreate(TestProject): False, self.project.name, self.project.parent_id, + self.project.tags ) self.assertEqual(datalist, data) @@ -134,6 +140,7 @@ class TestProjectCreate(TestProject): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -149,6 +156,7 @@ class TestProjectCreate(TestProject): 'description': 'new desc', 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -170,6 +178,7 @@ class TestProjectCreate(TestProject): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -185,6 +194,7 @@ class TestProjectCreate(TestProject): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -206,6 +216,7 @@ class TestProjectCreate(TestProject): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -221,6 +232,7 @@ class TestProjectCreate(TestProject): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } self.projects_mock.create.assert_called_with( **kwargs @@ -238,6 +250,7 @@ class TestProjectCreate(TestProject): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -253,6 +266,7 @@ class TestProjectCreate(TestProject): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -288,6 +302,7 @@ class TestProjectCreate(TestProject): 'description': None, 'enabled': False, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, # description=, enabled=, **kwargs) @@ -324,6 +339,7 @@ class TestProjectCreate(TestProject): 'parent': None, 'fee': 'fi', 'fo': 'fum', + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -352,6 +368,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', self.project.name), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -363,6 +380,7 @@ class TestProjectCreate(TestProject): 'parent': self.parent.id, 'description': None, 'enabled': True, + 'tags': [] } self.projects_mock.create.assert_called_with( @@ -377,6 +395,7 @@ class TestProjectCreate(TestProject): 'is_domain', 'name', 'parent_id', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -387,6 +406,7 @@ class TestProjectCreate(TestProject): self.project.is_domain, self.project.name, self.parent.id, + self.project.tags ) self.assertEqual(data, datalist) @@ -417,6 +437,43 @@ class TestProjectCreate(TestProject): parsed_args, ) + def test_project_create_with_tags(self): + arglist = [ + '--domain', self.project.domain_id, + '--tag', 'foo', + self.project.name, + ] + verifylist = [ + ('domain', self.project.domain_id), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('parent', None), + ('tags', ['foo']) + ] + 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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': self.project.domain_id, + 'description': None, + 'enabled': True, + 'parent': None, + 'tags': ['foo'] + } + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + class TestProjectDelete(TestProject): @@ -816,6 +873,38 @@ class TestProjectSet(TestProject): ) self.assertIsNone(result) + def test_project_set_tags(self): + arglist = [ + '--name', 'qwerty', + '--domain', self.project.domain_id, + '--tag', 'foo', + self.project.name, + ] + verifylist = [ + ('name', 'qwerty'), + ('domain', self.project.domain_id), + ('enable', False), + ('disable', False), + ('project', self.project.name), + ('tags', ['foo']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'qwerty', + 'tags': ['foo'] + } + # ProjectManager.update(project, name=, domain=, description=, + # enabled=, **kwargs) + self.projects_mock.update.assert_called_with( + self.project.id, + **kwargs + ) + self.assertIsNone(result) + class TestProjectShow(TestProject): @@ -867,6 +956,7 @@ class TestProjectShow(TestProject): 'is_domain', 'name', 'parent_id', + 'tags' ) self.assertEqual(collist, columns) datalist = ( @@ -877,6 +967,7 @@ class TestProjectShow(TestProject): False, self.project.name, self.project.parent_id, + self.project.tags ) self.assertEqual(datalist, data) @@ -926,6 +1017,7 @@ class TestProjectShow(TestProject): 'name', 'parent_id', 'parents', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -936,7 +1028,8 @@ class TestProjectShow(TestProject): self.project.is_domain, self.project.name, self.project.parent_id, - [{'project': {'id': self.project.parent_id}}] + [{'project': {'id': self.project.parent_id}}], + self.project.tags ) self.assertEqual(data, datalist) @@ -985,6 +1078,7 @@ class TestProjectShow(TestProject): 'name', 'parent_id', 'subtree', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -995,7 +1089,8 @@ class TestProjectShow(TestProject): self.project.is_domain, self.project.name, self.project.parent_id, - [{'project': {'id': 'children-id'}}] + [{'project': {'id': 'children-id'}}], + self.project.tags ) self.assertEqual(data, datalist) @@ -1047,6 +1142,7 @@ class TestProjectShow(TestProject): 'parent_id', 'parents', 'subtree', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -1058,7 +1154,8 @@ class TestProjectShow(TestProject): self.project.name, self.project.parent_id, [{'project': {'id': self.project.parent_id}}], - [{'project': {'id': 'children-id'}}] + [{'project': {'id': 'children-id'}}], + self.project.tags ) self.assertEqual(data, datalist) 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..301cd037 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -780,6 +780,52 @@ class TestImageList(TestImage): ) +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): project = identity_fakes.FakeProject.create_one_project() diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e69df88a..45392125 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -335,6 +335,7 @@ class FakeNetwork(object): 'name': 'network-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', 'description': 'network-description-' + uuid.uuid4().hex, + 'mtu': '1350', 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'admin_state_up': True, 'shared': False, @@ -347,7 +348,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, @@ -565,6 +565,7 @@ class FakePort(object): 'device_id': 'device-id-' + uuid.uuid4().hex, 'device_owner': 'compute:nova', 'dns_assignment': [{}], + 'dns_domain': 'dns-domain-' + uuid.uuid4().hex, 'dns_name': 'dns-name-' + uuid.uuid4().hex, 'extra_dhcp_opts': [{}], 'fixed_ips': [{'ip_address': '10.0.0.3', @@ -1380,6 +1381,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..65d87377 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -44,7 +44,7 @@ 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, @@ -65,6 +65,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -80,12 +81,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) @@ -221,6 +224,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 +392,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Router', 'Status', 'Description', + 'Tags', ) data = [] @@ -376,6 +416,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.router_id, ip.status, ip.description, + ip.tags, )) def setUp(self): @@ -539,6 +580,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 +624,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -573,6 +640,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): @@ -609,11 +677,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 +691,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) @@ -731,6 +801,36 @@ 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(['--port', self.floating_ip.port_id, + self.floating_ip.id]) + verifylist.extend([ + ('port', self.floating_ip.port_id), + ('floating_ip', self.floating_ip.id)]) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertTrue(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 +838,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 +851,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 +905,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..9f4a6acc 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -64,6 +64,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'mtu', 'name', 'port_security_enabled', 'project_id', @@ -87,6 +88,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): _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 +151,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", @@ -165,6 +168,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): ('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"]), @@ -188,6 +192,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, @@ -286,6 +291,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'mtu', 'name', 'port_security_enabled', 'project_id', @@ -309,6 +315,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): _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, @@ -1023,6 +1030,7 @@ class TestShowNetwork(TestNetwork): 'ipv4_address_scope', 'ipv6_address_scope', 'is_default', + 'mtu', 'name', 'port_security_enabled', 'project_id', @@ -1046,6 +1054,7 @@ class TestShowNetwork(TestNetwork): _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 908177ce..03e1d841 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -50,6 +50,7 @@ class TestPort(network_fakes.TestNetworkV2): 'device_id', 'device_owner', 'dns_assignment', + 'dns_domain', 'dns_name', 'extra_dhcp_opts', 'fixed_ips', @@ -78,6 +79,7 @@ class TestPort(network_fakes.TestNetworkV2): fake_port.device_id, fake_port.device_owner, utils.format_list_of_dicts(fake_port.dns_assignment), + fake_port.dns_domain, fake_port.dns_name, utils.format_list_of_dicts(fake_port.extra_dhcp_opts), utils.format_list_of_dicts(fake_port.fixed_ips), @@ -152,6 +154,7 @@ class TestCreatePort(TestPort): '--binding-profile', 'foo=bar', '--binding-profile', 'foo2=bar2', '--network', self._port.network_id, + '--dns-domain', 'example.org', '--dns-name', '8.8.8.8', 'test-port', @@ -169,6 +172,7 @@ class TestCreatePort(TestPort): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), ('network', self._port.network_id), + ('dns_domain', 'example.org'), ('dns_name', '8.8.8.8'), ('name', 'test-port'), ] @@ -187,6 +191,7 @@ class TestCreatePort(TestPort): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, 'network_id': self._port.network_id, + 'dns_domain': 'example.org', 'dns_name': '8.8.8.8', 'name': 'test-port', }) 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/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 27f37bd8..481509f3 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -200,6 +200,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() @@ -1004,3 +1006,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/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_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", ' |
