
There is a flaw (IMO) in the design of Nova's os-quota-sets API: despite project IDs forming the identifier for an individual resource, we get a HTTP 400 (Bad Request) error if you pass an ID that does not exist, rather than the HTTP 404 (Not Found) we would expect. Correct this, noting why we're doing what we're doing for readers from the future (hi!). Note that HTTP 400 is unfortunately quite broad and means we'll also catch things like invalid requests but the exception may have been translated so we can't rely on a string match. Change-Id: I720502930d50be8ead5f2033d9dbcab5d99a37a9 Signed-off-by: Stephen Finucane <stephenfin@redhat.com> Closes-bug: #2091086
902 lines
28 KiB
Python
902 lines
28 KiB
Python
# Copyright 2012 OpenStack Foundation
|
|
#
|
|
# 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.
|
|
|
|
"""Quota action implementations"""
|
|
|
|
import argparse
|
|
import itertools
|
|
import logging
|
|
import sys
|
|
|
|
from openstack import exceptions as sdk_exceptions
|
|
from osc_lib.command import command
|
|
from osc_lib import exceptions
|
|
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
|
|
# name that the user sees.
|
|
|
|
COMPUTE_QUOTAS = {
|
|
'cores': 'cores',
|
|
'injected_file_content_bytes': 'injected-file-size',
|
|
'injected_file_path_bytes': 'injected-path-size',
|
|
'injected_files': 'injected-files',
|
|
'instances': 'instances',
|
|
'key_pairs': 'key-pairs',
|
|
'metadata_items': 'properties',
|
|
'ram': 'ram',
|
|
'server_group_members': 'server-group-members',
|
|
'server_groups': 'server-groups',
|
|
}
|
|
|
|
VOLUME_QUOTAS = {
|
|
'backups': 'backups',
|
|
'backup_gigabytes': 'backup-gigabytes',
|
|
'gigabytes': 'gigabytes',
|
|
'per_volume_gigabytes': 'per-volume-gigabytes',
|
|
'snapshots': 'snapshots',
|
|
'volumes': 'volumes',
|
|
}
|
|
|
|
IMPACT_VOLUME_TYPE_QUOTAS = [
|
|
'gigabytes',
|
|
'snapshots',
|
|
'volumes',
|
|
]
|
|
|
|
NOVA_NETWORK_QUOTAS = {
|
|
'fixed_ips': 'fixed-ips',
|
|
'floating_ips': 'floating-ips',
|
|
'security_group_rules': 'secgroup-rules',
|
|
'security_groups': 'secgroups',
|
|
}
|
|
|
|
NETWORK_QUOTAS = {
|
|
'floatingip': 'floating-ips',
|
|
'security_group_rule': 'secgroup-rules',
|
|
'security_group': 'secgroups',
|
|
'network': 'networks',
|
|
'subnet': 'subnets',
|
|
'port': 'ports',
|
|
'router': 'routers',
|
|
'rbac_policy': 'rbac-policies',
|
|
'subnetpool': 'subnetpools',
|
|
}
|
|
|
|
NETWORK_KEYS = [
|
|
'floating_ips',
|
|
'networks',
|
|
'rbac_policies',
|
|
'routers',
|
|
'ports',
|
|
'security_group_rules',
|
|
'security_groups',
|
|
'subnet_pools',
|
|
'subnets',
|
|
]
|
|
|
|
|
|
def _xform_get_quota(data, value, keys):
|
|
res = []
|
|
res_info = {}
|
|
for key in keys:
|
|
res_info[key] = getattr(data, key, '')
|
|
|
|
res_info['id'] = value
|
|
res.append(res_info)
|
|
return res
|
|
|
|
|
|
def get_project(app, project):
|
|
if project is not None:
|
|
identity_client = app.client_manager.sdk_connection.identity
|
|
project = identity_client.find_project(project, ignore_missing=False)
|
|
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,
|
|
*,
|
|
detail=False,
|
|
default=False,
|
|
):
|
|
try:
|
|
client = app.client_manager.sdk_connection.compute
|
|
if default:
|
|
quota = client.get_quota_set_defaults(project_id)
|
|
else:
|
|
quota = client.get_quota_set(project_id, usage=detail)
|
|
except sdk_exceptions.EndpointNotFound:
|
|
return {}
|
|
data = quota.to_dict()
|
|
if not detail:
|
|
del data['usage']
|
|
del data['reservation']
|
|
return data
|
|
|
|
|
|
def get_volume_quotas(
|
|
app,
|
|
project_id,
|
|
*,
|
|
detail=False,
|
|
default=False,
|
|
):
|
|
try:
|
|
client = app.client_manager.sdk_connection.volume
|
|
if default:
|
|
quota = client.get_quota_set_defaults(project_id)
|
|
else:
|
|
quota = client.get_quota_set(project_id, usage=detail)
|
|
except sdk_exceptions.EndpointNotFound:
|
|
return {}
|
|
data = quota.to_dict()
|
|
if not detail:
|
|
del data['usage']
|
|
del data['reservation']
|
|
return data
|
|
|
|
|
|
def get_network_quotas(
|
|
app,
|
|
project_id,
|
|
*,
|
|
detail=False,
|
|
default=False,
|
|
):
|
|
def _network_quota_to_dict(network_quota, detail=False):
|
|
if not isinstance(network_quota, dict):
|
|
dict_quota = network_quota.to_dict()
|
|
else:
|
|
dict_quota = network_quota
|
|
|
|
result = {}
|
|
|
|
for key, values in dict_quota.items():
|
|
if values is None:
|
|
continue
|
|
|
|
# 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")
|
|
|
|
result[key] = values
|
|
|
|
return result
|
|
|
|
# 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):
|
|
"""List quotas for all projects with non-default quota values.
|
|
|
|
Empty output means all projects are using default quotas, which can be
|
|
inspected with 'openstack quota show --default'.
|
|
"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
option = parser.add_mutually_exclusive_group(required=True)
|
|
option.add_argument(
|
|
'--compute',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('List compute quota'),
|
|
)
|
|
option.add_argument(
|
|
'--volume',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('List volume quota'),
|
|
)
|
|
option.add_argument(
|
|
'--network',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('List network quota'),
|
|
)
|
|
return parser
|
|
|
|
def _list_quota_compute(self, parsed_args, project_ids):
|
|
compute_client = self.app.client_manager.sdk_connection.compute
|
|
result = []
|
|
|
|
for project_id in project_ids:
|
|
try:
|
|
project_data = compute_client.get_quota_set(project_id)
|
|
# NOTE(stephenfin): Unfortunately, Nova raises a HTTP 400 (Bad
|
|
# Request) if the project ID is invalid, even though the project
|
|
# ID is actually the resource's identifier which would normally
|
|
# lead us to expect a HTTP 404 (Not Found).
|
|
except (
|
|
sdk_exceptions.BadRequestException,
|
|
sdk_exceptions.ForbiddenException,
|
|
sdk_exceptions.NotFoundException,
|
|
) as exc:
|
|
# Project not found, move on to next one
|
|
LOG.warning(f"Project {project_id} not found: {exc}")
|
|
continue
|
|
|
|
project_result = _xform_get_quota(
|
|
project_data,
|
|
project_id,
|
|
COMPUTE_QUOTAS.keys(),
|
|
)
|
|
|
|
default_data = compute_client.get_quota_set_defaults(project_id)
|
|
default_result = _xform_get_quota(
|
|
default_data,
|
|
project_id,
|
|
COMPUTE_QUOTAS.keys(),
|
|
)
|
|
|
|
if default_result != project_result:
|
|
result += project_result
|
|
|
|
columns = (
|
|
'id',
|
|
'cores',
|
|
'injected_files',
|
|
'injected_file_content_bytes',
|
|
'injected_file_path_bytes',
|
|
'instances',
|
|
'key_pairs',
|
|
'metadata_items',
|
|
'ram',
|
|
'server_groups',
|
|
'server_group_members',
|
|
)
|
|
column_headers = (
|
|
'Project ID',
|
|
'Cores',
|
|
'Injected Files',
|
|
'Injected File Content Bytes',
|
|
'Injected File Path Bytes',
|
|
'Instances',
|
|
'Key Pairs',
|
|
'Metadata Items',
|
|
'Ram',
|
|
'Server Groups',
|
|
'Server Group Members',
|
|
)
|
|
return (
|
|
column_headers,
|
|
(utils.get_dict_properties(s, columns) for s in result),
|
|
)
|
|
|
|
def _list_quota_volume(self, parsed_args, project_ids):
|
|
volume_client = self.app.client_manager.sdk_connection.volume
|
|
result = []
|
|
|
|
for project_id in project_ids:
|
|
try:
|
|
project_data = volume_client.get_quota_set(project_id)
|
|
except (
|
|
sdk_exceptions.ForbiddenException,
|
|
sdk_exceptions.NotFoundException,
|
|
) as exc:
|
|
# Project not found, move on to next one
|
|
LOG.warning(f"Project {project_id} not found: {exc}")
|
|
continue
|
|
|
|
project_result = _xform_get_quota(
|
|
project_data,
|
|
project_id,
|
|
VOLUME_QUOTAS.keys(),
|
|
)
|
|
|
|
default_data = volume_client.get_quota_set_defaults(project_id)
|
|
default_result = _xform_get_quota(
|
|
default_data,
|
|
project_id,
|
|
VOLUME_QUOTAS.keys(),
|
|
)
|
|
|
|
if default_result != project_result:
|
|
result += project_result
|
|
|
|
columns = (
|
|
'id',
|
|
'backups',
|
|
'backup_gigabytes',
|
|
'gigabytes',
|
|
'per_volume_gigabytes',
|
|
'snapshots',
|
|
'volumes',
|
|
)
|
|
column_headers = (
|
|
'Project ID',
|
|
'Backups',
|
|
'Backup Gigabytes',
|
|
'Gigabytes',
|
|
'Per Volume Gigabytes',
|
|
'Snapshots',
|
|
'Volumes',
|
|
)
|
|
|
|
return (
|
|
column_headers,
|
|
(utils.get_dict_properties(s, columns) for s in result),
|
|
)
|
|
|
|
def _list_quota_network(self, parsed_args, project_ids):
|
|
network_client = self.app.client_manager.network
|
|
result = []
|
|
|
|
for project_id in project_ids:
|
|
try:
|
|
project_data = network_client.get_quota(project_id)
|
|
except (
|
|
sdk_exceptions.NotFoundException,
|
|
sdk_exceptions.ForbiddenException,
|
|
) as exc:
|
|
# Project not found, move on to next one
|
|
LOG.warning(f"Project {project_id} not found: {exc}")
|
|
continue
|
|
|
|
project_result = _xform_get_quota(
|
|
project_data,
|
|
project_id,
|
|
NETWORK_KEYS,
|
|
)
|
|
|
|
default_data = network_client.get_quota_default(project_id)
|
|
default_result = _xform_get_quota(
|
|
default_data,
|
|
project_id,
|
|
NETWORK_KEYS,
|
|
)
|
|
|
|
if default_result != project_result:
|
|
result += project_result
|
|
|
|
columns = (
|
|
'id',
|
|
'floating_ips',
|
|
'networks',
|
|
'ports',
|
|
'rbac_policies',
|
|
'routers',
|
|
'security_groups',
|
|
'security_group_rules',
|
|
'subnets',
|
|
'subnet_pools',
|
|
)
|
|
column_headers = (
|
|
'Project ID',
|
|
'Floating IPs',
|
|
'Networks',
|
|
'Ports',
|
|
'RBAC Policies',
|
|
'Routers',
|
|
'Security Groups',
|
|
'Security Group Rules',
|
|
'Subnets',
|
|
'Subnet Pools',
|
|
)
|
|
|
|
return (
|
|
column_headers,
|
|
(utils.get_dict_properties(s, columns) for s in result),
|
|
)
|
|
|
|
def take_action(self, parsed_args):
|
|
project_ids = [
|
|
p.id
|
|
for p in self.app.client_manager.sdk_connection.identity.projects()
|
|
]
|
|
if parsed_args.compute:
|
|
return self._list_quota_compute(parsed_args, project_ids)
|
|
elif parsed_args.volume:
|
|
return self._list_quota_volume(parsed_args, project_ids)
|
|
elif parsed_args.network:
|
|
return self._list_quota_network(parsed_args, project_ids)
|
|
|
|
# will never get here
|
|
return ((), ())
|
|
|
|
|
|
class SetQuota(common.NetDetectionMixin, command.Command):
|
|
_description = _("Set quotas for project or class")
|
|
|
|
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(),
|
|
)
|
|
]
|
|
# 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.
|
|
if self.is_docs_build:
|
|
# NOTE(efried): This takes advantage of the fact that we know the
|
|
# nova-net options are a subset of the neutron options. If that
|
|
# ever changes, this algorithm will need to be adjusted accordingly
|
|
inv_compute = set(NOVA_NETWORK_QUOTAS.values())
|
|
for k, v in NETWORK_QUOTAS.items():
|
|
_help = help_fmt % v
|
|
if v not in inv_compute:
|
|
# This one is unique to neutron
|
|
_help = self.enhance_help_neutron(_help)
|
|
rets.append((k, v, _help))
|
|
elif self.is_neutron:
|
|
rets.extend(
|
|
[(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()]
|
|
)
|
|
return rets
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'project',
|
|
metavar='<project/class>',
|
|
nargs='?',
|
|
help=_(
|
|
'Set quotas for this project or class (name or ID) '
|
|
'(defaults to current project)'
|
|
),
|
|
)
|
|
# TODO(stephenfin): Remove in OSC 8.0
|
|
type_group = parser.add_mutually_exclusive_group()
|
|
type_group.add_argument(
|
|
'--class',
|
|
dest='quota_class',
|
|
action='store_true',
|
|
default=False,
|
|
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)'
|
|
),
|
|
)
|
|
type_group.add_argument(
|
|
'--default',
|
|
dest='default',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('Set default quotas for <project>'),
|
|
)
|
|
for k, v, h in self._build_options_list():
|
|
parser.add_argument(
|
|
f'--{v}',
|
|
metavar=f'<{v}>',
|
|
dest=k,
|
|
type=int,
|
|
help=h,
|
|
)
|
|
parser.add_argument(
|
|
'--volume-type',
|
|
metavar='<volume-type>',
|
|
help=_('Set quotas for a specific <volume-type>'),
|
|
)
|
|
force_group = parser.add_mutually_exclusive_group()
|
|
force_group.add_argument(
|
|
'--force',
|
|
action='store_true',
|
|
dest='force',
|
|
default=False,
|
|
help=_(
|
|
'Force quota update (only supported by compute and network)'
|
|
),
|
|
)
|
|
force_group.add_argument(
|
|
'--no-force',
|
|
action='store_false',
|
|
dest='force',
|
|
default=False,
|
|
help=_(
|
|
'Do not force quota update '
|
|
'(only supported by compute and network) (default)'
|
|
),
|
|
)
|
|
# kept here for backwards compatibility/to keep the neutron folks happy
|
|
force_group.add_argument(
|
|
'--check-limit',
|
|
action='store_false',
|
|
dest='force',
|
|
default=False,
|
|
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 set --default' instead."
|
|
)
|
|
self.log.warning(msg)
|
|
|
|
if (
|
|
parsed_args.quota_class or parsed_args.default
|
|
) and parsed_args.force:
|
|
msg = _('--force cannot be used with --class or --default')
|
|
raise exceptions.CommandError(msg)
|
|
|
|
compute_kwargs = {}
|
|
volume_kwargs = {}
|
|
network_kwargs = {}
|
|
|
|
if self.app.client_manager.is_compute_endpoint_enabled():
|
|
compute_client = self.app.client_manager.sdk_connection.compute
|
|
|
|
for k, v in COMPUTE_QUOTAS.items():
|
|
value = getattr(parsed_args, k, None)
|
|
if value is not None:
|
|
compute_kwargs[k] = value
|
|
|
|
if compute_kwargs and parsed_args.force is True:
|
|
compute_kwargs['force'] = parsed_args.force
|
|
|
|
if self.app.client_manager.is_volume_endpoint_enabled():
|
|
volume_client = self.app.client_manager.sdk_connection.volume
|
|
|
|
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
|
|
):
|
|
k = k + f'_{parsed_args.volume_type}'
|
|
volume_kwargs[k] = value
|
|
|
|
if self.app.client_manager.is_network_endpoint_enabled():
|
|
network_client = self.app.client_manager.network
|
|
|
|
for k, v in NETWORK_QUOTAS.items():
|
|
value = getattr(parsed_args, k, None)
|
|
if value is not None:
|
|
network_kwargs[k] = value
|
|
elif self.app.client_manager.is_compute_endpoint_enabled():
|
|
for k, v in NOVA_NETWORK_QUOTAS.items():
|
|
value = getattr(parsed_args, k, None)
|
|
if value is not None:
|
|
compute_kwargs[k] = value
|
|
|
|
if network_kwargs:
|
|
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
|
|
else:
|
|
network_kwargs['check_limit'] = True
|
|
|
|
if parsed_args.quota_class or parsed_args.default:
|
|
if compute_kwargs:
|
|
compute_client.update_quota_class_set(
|
|
parsed_args.project or 'default',
|
|
**compute_kwargs,
|
|
)
|
|
if volume_kwargs:
|
|
volume_client.update_quota_class_set(
|
|
parsed_args.project or 'default',
|
|
**volume_kwargs,
|
|
)
|
|
if network_kwargs:
|
|
sys.stderr.write(
|
|
"Network quotas are ignored since quota classes are not "
|
|
"supported."
|
|
)
|
|
|
|
return
|
|
|
|
project_info = get_project(self.app, parsed_args.project)
|
|
project = project_info['id']
|
|
|
|
if compute_kwargs:
|
|
compute_client.update_quota_set(project, **compute_kwargs)
|
|
if volume_kwargs:
|
|
volume_client.update_quota_set(project, **volume_kwargs)
|
|
if network_kwargs:
|
|
network_client.update_quota(project, **network_kwargs)
|
|
|
|
|
|
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."""
|
|
)
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super().get_parser(prog_name)
|
|
parser.add_argument(
|
|
'project',
|
|
metavar='<project>',
|
|
nargs='?',
|
|
help=_(
|
|
'Show quotas for this project (name or ID) '
|
|
'(defaults to current project)'
|
|
),
|
|
)
|
|
type_group = parser.add_mutually_exclusive_group()
|
|
type_group.add_argument(
|
|
'--default',
|
|
dest='default',
|
|
action='store_true',
|
|
default=False,
|
|
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_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,
|
|
default=parsed_args.default,
|
|
)
|
|
if parsed_args.service in {'all', 'volume'}:
|
|
volume_quota_info = get_volume_quotas(
|
|
self.app,
|
|
project,
|
|
detail=parsed_args.usage,
|
|
default=parsed_args.default,
|
|
)
|
|
if parsed_args.service in {'all', 'network'}:
|
|
network_quota_info = get_network_quotas(
|
|
self.app,
|
|
project,
|
|
detail=parsed_args.usage,
|
|
default=parsed_args.default,
|
|
)
|
|
|
|
info = {}
|
|
info.update(compute_quota_info)
|
|
info.update(volume_quota_info)
|
|
info.update(network_quota_info)
|
|
|
|
# Map the internal quota names to the external ones
|
|
# COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips,
|
|
# secgroup-rules and secgroups as dict value, so when
|
|
# 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(),
|
|
):
|
|
if not k == v and info.get(k) is not None:
|
|
info[v] = info[k]
|
|
info.pop(k)
|
|
|
|
# Remove the 'id' field since it's not very useful
|
|
if 'id' in info:
|
|
del info['id']
|
|
|
|
# Remove the sdk-derived fields
|
|
for field in ('location', 'name', 'force'):
|
|
if field in info:
|
|
del info[field]
|
|
|
|
if not parsed_args.usage:
|
|
result = [{'resource': k, 'limit': v} for k, v in info.items()]
|
|
else:
|
|
result = [
|
|
{
|
|
'resource': k,
|
|
'limit': v or 0,
|
|
'in_use': info['usage'].get(k, 0),
|
|
'reserved': info['reservation'].get(k, 0),
|
|
}
|
|
for k, v in info.items()
|
|
if k not in ('usage', 'reservation')
|
|
]
|
|
|
|
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.sdk_connection.identity
|
|
project = identity_client.find_project(
|
|
parsed_args.project, ignore_missing=False
|
|
)
|
|
|
|
# compute quotas
|
|
if parsed_args.service in {'all', 'compute'}:
|
|
compute_client = self.app.client_manager.sdk_connection.compute
|
|
compute_client.revert_quota_set(project.id)
|
|
|
|
# volume quotas
|
|
if parsed_args.service in {'all', 'volume'}:
|
|
volume_client = self.app.client_manager.sdk_connection.volume
|
|
volume_client.revert_quota_set(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
|