diff options
Diffstat (limited to 'openstackclient/common')
| -rw-r--r-- | openstackclient/common/configuration.py | 6 | ||||
| -rw-r--r-- | openstackclient/common/quota.py | 791 |
2 files changed, 561 insertions, 236 deletions
diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 49ef0e05..cb415505 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -45,7 +45,6 @@ class ShowConfiguration(command.ShowOne): return parser def take_action(self, parsed_args): - info = self.app.client_manager.get_configuration() # Assume a default secret list in case we do not have an auth_plugin @@ -63,4 +62,9 @@ class ShowConfiguration(command.ShowOne): value = REDACTED info['auth.' + key] = value + if parsed_args.mask: + for secret_opt in secret_opts: + if secret_opt in info: + info[secret_opt] = REDACTED + return zip(*sorted(info.items())) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 44482367..246e44b3 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -15,6 +15,7 @@ """Quota action implementations""" +import argparse import itertools import logging import sys @@ -25,7 +26,6 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.network import common - LOG = logging.getLogger(__name__) # List the quota items, map the internal argument name to the option @@ -78,9 +78,17 @@ NETWORK_QUOTAS = { 'subnetpool': 'subnetpools', } -NETWORK_KEYS = ['floating_ips', 'networks', 'rbac_policies', 'routers', - 'ports', 'security_group_rules', 'security_groups', - 'subnet_pools', 'subnets'] +NETWORK_KEYS = [ + 'floating_ips', + 'networks', + 'rbac_policies', + 'routers', + 'ports', + 'security_group_rules', + 'security_groups', + 'subnet_pools', + 'subnets', +] def _xform_get_quota(data, value, keys): @@ -94,168 +102,158 @@ def _xform_get_quota(data, value, keys): return res -class BaseQuota(object): - def _get_project(self, parsed_args): - if parsed_args.project is not None: - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - project_id = project.id - project_name = project.name - elif self.app.client_manager.auth_ref: - # Get the project from the current auth - project = self.app.client_manager.auth_ref - project_id = project.project_id - project_name = project.project_name +def get_project(app, project): + if project is not None: + identity_client = app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + project, + ) + project_id = project.id + project_name = project.name + elif app.client_manager.auth_ref: + # Get the project from the current auth + project = app.client_manager.auth_ref + project_id = project.project_id + project_name = project.project_name + else: + project_id = None + project_name = None + + return { + 'id': project_id, + 'name': project_name, + } + + +def get_compute_quotas( + app, + project_id, + *, + quota_class=False, + detail=False, + default=False, +): + try: + client = app.client_manager.compute + if quota_class: + # NOTE(stephenfin): The 'project' argument here could be anything + # as the nova API doesn't care what you pass in. We only pass the + # project in to avoid weirding people out :) + quota = client.quota_classes.get(project_id) + elif default: + quota = client.quotas.defaults(project_id) + else: + quota = client.quotas.get(project_id, detail=detail) + except Exception as e: + if type(e).__name__ == 'EndpointNotFound': + return {} + raise + return quota._info + + +def get_volume_quotas( + app, + project_id, + *, + quota_class=False, + detail=False, + default=False, +): + try: + client = app.client_manager.volume + if quota_class: + quota = client.quota_classes.get(project_id) + elif default: + quota = client.quotas.defaults(project_id) else: - project = None - project_id = None - project_name = None - project_info = {} - project_info['id'] = project_id - project_info['name'] = project_name - return project_info - - def get_compute_quota(self, client, parsed_args): - quota_class = ( - parsed_args.quota_class if 'quota_class' in parsed_args else False) - detail = parsed_args.detail if 'detail' in parsed_args else False - default = parsed_args.default if 'default' in parsed_args else False - try: - if quota_class: - quota = client.quota_classes.get(parsed_args.project) - else: - project_info = self._get_project(parsed_args) - project = project_info['id'] - if default: - quota = client.quotas.defaults(project) - else: - quota = client.quotas.get(project, detail=detail) - except Exception as e: - if type(e).__name__ == 'EndpointNotFound': - return {} - else: - raise - return quota._info - - def get_volume_quota(self, client, parsed_args): - quota_class = ( - parsed_args.quota_class if 'quota_class' in parsed_args else False) - default = parsed_args.default if 'default' in parsed_args else False - try: - if quota_class: - quota = client.quota_classes.get(parsed_args.project) - else: - project_info = self._get_project(parsed_args) - project = project_info['id'] - if default: - quota = client.quotas.defaults(project) - else: - quota = client.quotas.get(project) - except Exception as e: - if type(e).__name__ == 'EndpointNotFound': - return {} - else: - raise - return quota._info - - def _network_quota_to_dict(self, network_quota): + quota = client.quotas.get(project_id, usage=detail) + except Exception as e: + if type(e).__name__ == 'EndpointNotFound': + return {} + else: + raise + return quota._info + + +def get_network_quotas( + app, + project_id, + *, + quota_class=False, + detail=False, + default=False, +): + def _network_quota_to_dict(network_quota, detail=False): if type(network_quota) is not dict: dict_quota = network_quota.to_dict() else: dict_quota = network_quota - return {k: v for k, v in dict_quota.items() if v is not None} - def get_network_quota(self, parsed_args): - quota_class = ( - parsed_args.quota_class if 'quota_class' in parsed_args else False) - detail = parsed_args.detail if 'detail' in parsed_args else False - default = parsed_args.default if 'default' in parsed_args else False - if quota_class: - return {} - if self.app.client_manager.is_network_endpoint_enabled(): - project_info = self._get_project(parsed_args) - project = project_info['id'] - client = self.app.client_manager.network - if default: - network_quota = client.get_quota_default(project) - network_quota = self._network_quota_to_dict(network_quota) - else: - network_quota = client.get_quota(project, - details=detail) - network_quota = self._network_quota_to_dict(network_quota) - if detail: - # NOTE(slaweq): Neutron returns values with key "used" but - # Nova for example returns same data with key "in_use" - # instead. - # Because of that we need to convert Neutron key to - # the same as is returned from Nova to make result - # more consistent - for key, values in network_quota.items(): - if type(values) is dict and "used" in values: - values[u'in_use'] = values.pop("used") - network_quota[key] = values - return network_quota - else: - return {} + result = {} + for key, values in dict_quota.items(): + if values is None: + continue -class ListQuota(command.Lister, BaseQuota): - _description = _( - "List quotas for all projects with non-default quota values or " - "list detailed quota information for requested project") + # NOTE(slaweq): Neutron returns values with key "used" but Nova for + # example returns same data with key "in_use" instead. Because of + # that we need to convert Neutron key to the same as is returned + # from Nova to make result more consistent + if isinstance(values, dict) and 'used' in values: + values['in_use'] = values.pop("used") - def _get_detailed_quotas(self, parsed_args): - columns = ( - 'resource', - 'in_use', - 'reserved', - 'limit' - ) - column_headers = ( - 'Resource', - 'In Use', - 'Reserved', - 'Limit' - ) - quotas = {} - if parsed_args.compute: - quotas.update(self.get_compute_quota( - self.app.client_manager.compute, parsed_args)) - if parsed_args.network: - quotas.update(self.get_network_quota(parsed_args)) + result[key] = values - result = [] - for resource, values in quotas.items(): - # NOTE(slaweq): there is no detailed quotas info for some resources - # and it shouldn't be displayed here - if type(values) is dict: - result.append({ - 'resource': resource, - 'in_use': values.get('in_use'), - 'reserved': values.get('reserved'), - 'limit': values.get('limit') - }) - return (column_headers, - (utils.get_dict_properties( - s, columns, - ) for s in result)) + return result + + # neutron doesn't have the concept of quota classes and if we're using + # nova-network we already fetched this + if quota_class: + return {} + + # we have nothing to return if we are not using neutron + if not app.client_manager.is_network_endpoint_enabled(): + return {} + + client = app.client_manager.network + if default: + network_quota = client.get_quota_default(project_id) + network_quota = _network_quota_to_dict(network_quota) + else: + network_quota = client.get_quota(project_id, details=detail) + network_quota = _network_quota_to_dict(network_quota, detail=detail) + return network_quota + + +class ListQuota(command.Lister): + _description = _( + "List quotas for all projects with non-default quota values or " + "list detailed quota information for requested project" + ) def get_parser(self, prog_name): - parser = super(ListQuota, self).get_parser(prog_name) + parser = super().get_parser(prog_name) + # TODO(stephenfin): Remove in OSC 8.0 parser.add_argument( '--project', metavar='<project>', - help=_('List quotas for this project <project> (name or ID)'), + help=_( + "**Deprecated** List quotas for this project <project> " + "(name or ID). " + "Use 'quota show' instead." + ), ) + # TODO(stephenfin): Remove in OSC 8.0 parser.add_argument( '--detail', dest='detail', action='store_true', default=False, - help=_('Show details about quotas usage') + help=_( + "**Deprecated** Show details about quotas usage. " + "Use 'quota show --usage' instead." + ), ) option = parser.add_mutually_exclusive_group(required=True) option.add_argument( @@ -278,7 +276,85 @@ class ListQuota(command.Lister, BaseQuota): ) return parser + def _get_detailed_quotas(self, parsed_args): + project_info = get_project(self.app, parsed_args.project) + project = project_info['id'] + + quotas = {} + + if parsed_args.compute: + quotas.update( + get_compute_quotas( + self.app, + project, + detail=parsed_args.detail, + ) + ) + + if parsed_args.network: + quotas.update( + get_network_quotas( + self.app, + project, + detail=parsed_args.detail, + ) + ) + + if parsed_args.volume: + quotas.update( + get_volume_quotas( + self.app, + parsed_args, + detail=parsed_args.detail, + ), + ) + + result = [] + for resource, values in quotas.items(): + # NOTE(slaweq): there is no detailed quotas info for some resources + # and it shouldn't be displayed here + if isinstance(values, dict): + result.append( + { + 'resource': resource, + 'in_use': values.get('in_use'), + 'reserved': values.get('reserved'), + 'limit': values.get('limit'), + } + ) + + columns = ( + 'resource', + 'in_use', + 'reserved', + 'limit', + ) + column_headers = ( + 'Resource', + 'In Use', + 'Reserved', + 'Limit', + ) + + return ( + column_headers, + (utils.get_dict_properties(s, columns) for s in result), + ) + def take_action(self, parsed_args): + if parsed_args.detail: + msg = _( + "The --detail option has been deprecated. " + "Use 'openstack quota show --usage' instead." + ) + self.log.warning(msg) + elif parsed_args.project: # elif to avoid being too noisy + msg = _( + "The --project option has been deprecated. " + "Use 'openstack quota show' instead." + ) + self.log.warning(msg) + result = [] project_ids = [] if parsed_args.project is None: @@ -295,14 +371,16 @@ class ListQuota(command.Lister, BaseQuota): if parsed_args.compute: if parsed_args.detail: return self._get_detailed_quotas(parsed_args) + compute_client = self.app.client_manager.compute for p in project_ids: try: data = compute_client.quotas.get(p) except Exception as ex: if ( - type(ex).__name__ == 'NotFound' or - ex.http_status >= 400 and ex.http_status <= 499 + type(ex).__name__ == 'NotFound' + or ex.http_status >= 400 + and ex.http_status <= 499 ): # Project not found, move on to next one LOG.warning("Project %s not found: %s" % (p, ex)) @@ -352,15 +430,15 @@ class ListQuota(command.Lister, BaseQuota): 'Server Groups', 'Server Group Members', ) - return (column_headers, - (utils.get_dict_properties( - s, columns, - ) for s in result)) + return ( + column_headers, + (utils.get_dict_properties(s, columns) for s in result), + ) if parsed_args.volume: if parsed_args.detail: - LOG.warning("Volume service doesn't provide detailed quota" - " information") + return self._get_detailed_quotas(parsed_args) + volume_client = self.app.client_manager.volume for p in project_ids: try: @@ -405,14 +483,16 @@ class ListQuota(command.Lister, BaseQuota): 'Snapshots', 'Volumes', ) - return (column_headers, - (utils.get_dict_properties( - s, columns, - ) for s in result)) + + return ( + column_headers, + (utils.get_dict_properties(s, columns) for s in result), + ) if parsed_args.network: if parsed_args.detail: return self._get_detailed_quotas(parsed_args) + client = self.app.client_manager.network for p in project_ids: try: @@ -461,12 +541,13 @@ class ListQuota(command.Lister, BaseQuota): 'Security Groups', 'Security Group Rules', 'Subnets', - 'Subnet Pools' + 'Subnet Pools', + ) + + return ( + column_headers, + (utils.get_dict_properties(s, columns) for s in result), ) - return (column_headers, - (utils.get_dict_properties( - s, columns, - ) for s in result)) return ((), ()) @@ -477,10 +558,13 @@ class SetQuota(common.NetDetectionMixin, command.Command): def _build_options_list(self): help_fmt = _('New value for the %s quota') # Compute and volume quota options are always the same - rets = [(k, v, help_fmt % v) for k, v in itertools.chain( - COMPUTE_QUOTAS.items(), - VOLUME_QUOTAS.items(), - )] + rets = [ + (k, v, help_fmt % v) + for k, v in itertools.chain( + COMPUTE_QUOTAS.items(), + VOLUME_QUOTAS.items(), + ) + ] # For docs build, we want to produce helps for both neutron and # nova-network options. They overlap, so we have to figure out which # need to be tagged as specific to one network type or the other. @@ -497,10 +581,12 @@ class SetQuota(common.NetDetectionMixin, command.Command): rets.append((k, v, _help)) elif self.is_neutron: rets.extend( - [(k, v, help_fmt % v) for k, v in NETWORK_QUOTAS.items()]) + [(k, v, help_fmt % v) for k, v in NETWORK_QUOTAS.items()] + ) elif self.is_nova_network: rets.extend( - [(k, v, help_fmt % v) for k, v in NOVA_NETWORK_QUOTAS.items()]) + [(k, v, help_fmt % v) for k, v in NOVA_NETWORK_QUOTAS.items()] + ) return rets def get_parser(self, prog_name): @@ -508,14 +594,20 @@ class SetQuota(common.NetDetectionMixin, command.Command): parser.add_argument( 'project', metavar='<project/class>', - help=_('Set quotas for this project or class (name/ID)'), + help=_('Set quotas for this project or class (name or ID)'), ) + # TODO(stephenfin): Remove in OSC 8.0 parser.add_argument( '--class', dest='quota_class', action='store_true', default=False, - help=_('Set quotas for <class>'), + help=_( + '**Deprecated** Set quotas for <class>. ' + 'Deprecated as quota classes were never fully implemented ' + 'and only the default class is supported. ' + '(compute and volume only)' + ), ) for k, v, h in self._build_options_list(): parser.add_argument( @@ -530,21 +622,49 @@ class SetQuota(common.NetDetectionMixin, command.Command): metavar='<volume-type>', help=_('Set quotas for a specific <volume-type>'), ) - parser.add_argument( + force_group = parser.add_mutually_exclusive_group() + force_group.add_argument( '--force', action='store_true', - help=_('Force quota update (only supported by compute and ' - 'network)') + dest='force', + # TODO(stephenfin): Change the default to False in Z or later + default=None, + help=_( + 'Force quota update (only supported by compute and network) ' + '(default for network)' + ), ) - parser.add_argument( + force_group.add_argument( + '--no-force', + action='store_false', + dest='force', + default=None, + help=_( + 'Do not force quota update ' + '(only supported by compute and network) ' + '(default for compute)' + ), + ) + # kept here for backwards compatibility/to keep the neutron folks happy + force_group.add_argument( '--check-limit', - action='store_true', - help=_('Check quota limit when updating (only supported by ' - 'network)') + action='store_false', + dest='force', + default=None, + help=argparse.SUPPRESS, ) return parser def take_action(self, parsed_args): + if parsed_args.quota_class: + msg = _( + "The '--class' option has been deprecated. Quota classes were " + "never fully implemented and the compute and volume services " + "only support a single 'default' quota class while the " + "network service does not support quota classes at all. " + "Please use 'openstack quota show --default' instead." + ) + self.log.warning(msg) identity_client = self.app.client_manager.identity compute_client = self.app.client_manager.compute @@ -555,23 +675,33 @@ class SetQuota(common.NetDetectionMixin, command.Command): if value is not None: compute_kwargs[k] = value - if parsed_args.force: - compute_kwargs['force'] = True + if parsed_args.force is not None: + compute_kwargs['force'] = parsed_args.force volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): value = getattr(parsed_args, k, None) if value is not None: - if (parsed_args.volume_type and - k in IMPACT_VOLUME_TYPE_QUOTAS): + if parsed_args.volume_type and k in IMPACT_VOLUME_TYPE_QUOTAS: k = k + '_%s' % parsed_args.volume_type volume_kwargs[k] = value network_kwargs = {} - if parsed_args.check_limit: - network_kwargs['check_limit'] = True - if parsed_args.force: + if parsed_args.force is True: + # Unlike compute, network doesn't provide a simple boolean option. + # Instead, it provides two options: 'force' and 'check_limit' + # (a.k.a. 'not force') network_kwargs['force'] = True + elif parsed_args.force is False: + network_kwargs['check_limit'] = True + else: + msg = _( + "This command currently defaults to '--force' when modifying " + "network quotas. This behavior will change in a future " + "release. Consider explicitly providing '--force' or " + "'--no-force' options to avoid changes in behavior." + ) + self.log.warning(msg) if self.app.client_manager.is_network_endpoint_enabled(): for k, v in NETWORK_QUOTAS.items(): @@ -588,87 +718,170 @@ class SetQuota(common.NetDetectionMixin, command.Command): if compute_kwargs: compute_client.quota_classes.update( parsed_args.project, - **compute_kwargs) + **compute_kwargs, + ) if volume_kwargs: volume_client.quota_classes.update( parsed_args.project, - **volume_kwargs) + **volume_kwargs, + ) if network_kwargs: - sys.stderr.write("Network quotas are ignored since quota class" - " is not supported.") + sys.stderr.write( + "Network quotas are ignored since quota classes are not " + "supported." + ) else: project = utils.find_resource( identity_client.projects, parsed_args.project, ).id + if compute_kwargs: - compute_client.quotas.update( - project, - **compute_kwargs) + compute_client.quotas.update(project, **compute_kwargs) if volume_kwargs: - volume_client.quotas.update( - project, - **volume_kwargs) + volume_client.quotas.update(project, **volume_kwargs) if ( - network_kwargs and - self.app.client_manager.is_network_endpoint_enabled() + network_kwargs + and self.app.client_manager.is_network_endpoint_enabled() ): network_client = self.app.client_manager.network - network_client.update_quota( - project, - **network_kwargs) + network_client.update_quota(project, **network_kwargs) -class ShowQuota(command.ShowOne, BaseQuota): +class ShowQuota(command.Lister): _description = _( - "Show quotas for project or class. Specify " - "``--os-compute-api-version 2.50`` or higher to see ``server-groups`` " - "and ``server-group-members`` output for a given quota class.") + "Show quotas for project or class. " + "Specify ``--os-compute-api-version 2.50`` or higher to see " + "``server-groups`` and ``server-group-members`` output for a given " + "quota class." + ) def get_parser(self, prog_name): - parser = super(ShowQuota, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( 'project', metavar='<project/class>', nargs='?', - help=_('Show quotas for this project or class (name or ID)'), + help=_( + 'Show quotas for this project or class (name or ID) ' + '(defaults to current project)' + ), ) type_group = parser.add_mutually_exclusive_group() + # TODO(stephenfin): Remove in OSC 8.0 type_group.add_argument( '--class', dest='quota_class', action='store_true', default=False, - help=_('Show quotas for <class>'), + help=_( + '**Deprecated** Show quotas for <class>. ' + 'Deprecated as quota classes were never fully implemented ' + 'and only the default class is supported. ' + 'Use --default instead which is also supported by the network ' + 'service. ' + '(compute and volume only)' + ), ) type_group.add_argument( '--default', dest='default', action='store_true', default=False, - help=_('Show default quotas for <project>') + help=_('Show default quotas for <project>'), + ) + type_group.add_argument( + '--usage', + dest='usage', + action='store_true', + default=False, + help=_('Show details about quotas usage'), + ) + service_group = parser.add_mutually_exclusive_group() + service_group.add_argument( + '--all', + action='store_const', + const='all', + dest='service', + default='all', + help=_('Show quotas for all services'), + ) + service_group.add_argument( + '--compute', + action='store_const', + const='compute', + dest='service', + default='all', + help=_('Show compute quota'), + ) + service_group.add_argument( + '--volume', + action='store_const', + const='volume', + dest='service', + default='all', + help=_('Show volume quota'), + ) + service_group.add_argument( + '--network', + action='store_const', + const='network', + dest='service', + default='all', + help=_('Show network quota'), ) + return parser def take_action(self, parsed_args): + project = parsed_args.project - compute_client = self.app.client_manager.compute - volume_client = self.app.client_manager.volume - # NOTE(dtroyer): These quota API calls do not validate the project - # or class arguments and return what appears to be - # the default quota values if the project or class - # does not exist. If this is determined to be the - # intended behaviour of the API we will validate - # the argument with Identity ourselves later. - compute_quota_info = self.get_compute_quota(compute_client, - parsed_args) - volume_quota_info = self.get_volume_quota(volume_client, - parsed_args) - network_quota_info = self.get_network_quota(parsed_args) - # NOTE(reedip): Remove the below check once requirement for - # Openstack SDK is fixed to version 0.9.12 and above - if type(network_quota_info) is not dict: - network_quota_info = network_quota_info.to_dict() + if parsed_args.quota_class: + msg = _( + "The '--class' option has been deprecated. Quota classes were " + "never fully implemented and the compute and volume services " + "only support a single 'default' quota class while the " + "network service does not support quota classes at all. " + "Please use 'openstack quota show --default' instead." + ) + self.log.warning(msg) + else: + project_info = get_project(self.app, parsed_args.project) + project = project_info['id'] + + compute_quota_info = {} + volume_quota_info = {} + network_quota_info = {} + + # NOTE(stephenfin): These quota API calls do not validate the project + # or class arguments and return what appears to be the default quota + # values if the project or class does not exist. This is expected + # behavior. However, we have already checked for the presence of the + # project above so it shouldn't be an issue. + if parsed_args.service in {'all', 'compute'}: + compute_quota_info = get_compute_quotas( + self.app, + project, + detail=parsed_args.usage, + quota_class=parsed_args.quota_class, + default=parsed_args.default, + ) + if parsed_args.service in {'all', 'volume'}: + volume_quota_info = get_volume_quotas( + self.app, + project, + detail=parsed_args.usage, + quota_class=parsed_args.quota_class, + default=parsed_args.default, + ) + if parsed_args.service in {'all', 'network'}: + network_quota_info = get_network_quotas( + self.app, + project, + detail=parsed_args.usage, + quota_class=parsed_args.quota_class, + default=parsed_args.default, + ) info = {} info.update(compute_quota_info) @@ -681,19 +894,127 @@ class ShowQuota(command.ShowOne, BaseQuota): # neutron is enabled, quotas of these three resources # in nova will be replaced by neutron's. for k, v in itertools.chain( - COMPUTE_QUOTAS.items(), NOVA_NETWORK_QUOTAS.items(), - VOLUME_QUOTAS.items(), NETWORK_QUOTAS.items()): + COMPUTE_QUOTAS.items(), + NOVA_NETWORK_QUOTAS.items(), + VOLUME_QUOTAS.items(), + NETWORK_QUOTAS.items(), + ): if not k == v and info.get(k) is not None: info[v] = info[k] info.pop(k) - # Handle project ID special as it only appears in output + # Remove the 'id' field since it's not very useful if 'id' in info: - info['project'] = info.pop('id') - if 'project_id' in info: - del info['project_id'] - project_info = self._get_project(parsed_args) - project_name = project_info['name'] - info['project_name'] = project_name - - return zip(*sorted(info.items())) + del info['id'] + + # Remove the 'location' field for resources from openstacksdk + if 'location' in info: + del info['location'] + + if not parsed_args.usage: + result = [{'resource': k, 'limit': v} for k, v in info.items()] + else: + result = [{'resource': k, **v} for k, v in info.items()] + + columns = ( + 'resource', + 'limit', + ) + column_headers = ( + 'Resource', + 'Limit', + ) + + if parsed_args.usage: + columns += ( + 'in_use', + 'reserved', + ) + column_headers += ( + 'In Use', + 'Reserved', + ) + + return ( + column_headers, + (utils.get_dict_properties(s, columns) for s in result), + ) + + +class DeleteQuota(command.Command): + _description = _( + "Delete configured quota for a project and revert to defaults." + ) + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'project', + metavar='<project>', + help=_('Delete quotas for this project (name or ID)'), + ) + option = parser.add_mutually_exclusive_group() + option.add_argument( + '--all', + action='store_const', + const='all', + dest='service', + default='all', + help=_('Delete project quotas for all services (default)'), + ) + option.add_argument( + '--compute', + action='store_const', + const='compute', + dest='service', + default='all', + help=_( + 'Delete compute quotas for the project ' + '(including network quotas when using nova-network)' + ), + ) + option.add_argument( + '--volume', + action='store_const', + const='volume', + dest='service', + default='all', + help=_('Delete volume quotas for the project'), + ) + option.add_argument( + '--network', + action='store_const', + const='network', + dest='service', + default='all', + help=_('Delete network quotas for the project'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + + # compute quotas + if parsed_args.service in {'all', 'compute'}: + compute_client = self.app.client_manager.compute + compute_client.quotas.delete(project.id) + + # volume quotas + if parsed_args.service in {'all', 'volume'}: + volume_client = self.app.client_manager.volume + volume_client.quotas.delete(project.id) + + # network quotas (but only if we're not using nova-network, otherwise + # we already deleted the quotas in the compute step) + if ( + parsed_args.service in {'all', 'network'} + and self.app.client_manager.is_network_endpoint_enabled() + ): + network_client = self.app.client_manager.network + network_client.delete_quota(project.id) + + return None |
