summaryrefslogtreecommitdiff
path: root/openstackclient/common
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient/common')
-rw-r--r--openstackclient/common/configuration.py6
-rw-r--r--openstackclient/common/quota.py791
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