5afe48040c
Each command should have a summary line followed by a longer description, if needed. Some commands were not following this. Fix them. Change-Id: If1ce7654037d192626460f34c069ea0979919b9b Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
1019 lines
32 KiB
Python
1019 lines
32 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 osc_lib.command import command
|
|
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',
|
|
'fixed_ips': 'fixed-ips',
|
|
'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_groups': 'server-groups',
|
|
'server_group_members': 'server-group-members',
|
|
}
|
|
|
|
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 = {
|
|
'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.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:
|
|
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
|
|
|
|
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
|
|
|
|
# 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().get_parser(prog_name)
|
|
# TODO(stephenfin): Remove in OSC 8.0
|
|
parser.add_argument(
|
|
'--project',
|
|
metavar='<project>',
|
|
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=_(
|
|
"**Deprecated** Show details about quotas usage. "
|
|
"Use 'quota show --usage' instead."
|
|
),
|
|
)
|
|
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 _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:
|
|
for p in self.app.client_manager.identity.projects.list():
|
|
project_ids.append(getattr(p, 'id', ''))
|
|
else:
|
|
identity_client = self.app.client_manager.identity
|
|
project = utils.find_resource(
|
|
identity_client.projects,
|
|
parsed_args.project,
|
|
)
|
|
project_ids.append(getattr(project, 'id', ''))
|
|
|
|
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
|
|
):
|
|
# Project not found, move on to next one
|
|
LOG.warning("Project %s not found: %s" % (p, ex))
|
|
continue
|
|
else:
|
|
raise
|
|
|
|
result_data = _xform_get_quota(
|
|
data,
|
|
p,
|
|
COMPUTE_QUOTAS.keys(),
|
|
)
|
|
default_data = compute_client.quotas.defaults(p)
|
|
result_default = _xform_get_quota(
|
|
default_data,
|
|
p,
|
|
COMPUTE_QUOTAS.keys(),
|
|
)
|
|
if result_default != result_data:
|
|
result += result_data
|
|
|
|
columns = (
|
|
'id',
|
|
'cores',
|
|
'fixed_ips',
|
|
'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',
|
|
'Fixed IPs',
|
|
'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),
|
|
)
|
|
|
|
if parsed_args.volume:
|
|
if parsed_args.detail:
|
|
return self._get_detailed_quotas(parsed_args)
|
|
|
|
volume_client = self.app.client_manager.volume
|
|
for p in project_ids:
|
|
try:
|
|
data = volume_client.quotas.get(p)
|
|
except Exception as ex:
|
|
if type(ex).__name__ == 'NotFound':
|
|
# Project not found, move on to next one
|
|
LOG.warning("Project %s not found: %s" % (p, ex))
|
|
continue
|
|
else:
|
|
raise
|
|
|
|
result_data = _xform_get_quota(
|
|
data,
|
|
p,
|
|
VOLUME_QUOTAS.keys(),
|
|
)
|
|
default_data = volume_client.quotas.defaults(p)
|
|
result_default = _xform_get_quota(
|
|
default_data,
|
|
p,
|
|
VOLUME_QUOTAS.keys(),
|
|
)
|
|
if result_default != result_data:
|
|
result += result_data
|
|
|
|
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),
|
|
)
|
|
|
|
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:
|
|
data = client.get_quota(p)
|
|
except Exception as ex:
|
|
if type(ex).__name__ == 'NotFound':
|
|
# Project not found, move on to next one
|
|
LOG.warning("Project %s not found: %s" % (p, ex))
|
|
continue
|
|
else:
|
|
raise
|
|
|
|
result_data = _xform_get_quota(
|
|
data,
|
|
p,
|
|
NETWORK_KEYS,
|
|
)
|
|
default_data = client.get_quota_default(p)
|
|
result_default = _xform_get_quota(
|
|
default_data,
|
|
p,
|
|
NETWORK_KEYS,
|
|
)
|
|
if result_default != result_data:
|
|
result += result_data
|
|
|
|
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),
|
|
)
|
|
|
|
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(SetQuota, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'project',
|
|
metavar='<project/class>',
|
|
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=_(
|
|
'**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(
|
|
'--%s' % v,
|
|
metavar='<%s>' % 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',
|
|
# 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)'
|
|
),
|
|
)
|
|
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_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
|
|
volume_client = self.app.client_manager.volume
|
|
compute_kwargs = {}
|
|
for k, v in COMPUTE_QUOTAS.items():
|
|
value = getattr(parsed_args, k, None)
|
|
if value is not None:
|
|
compute_kwargs[k] = value
|
|
|
|
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:
|
|
k = k + '_%s' % parsed_args.volume_type
|
|
volume_kwargs[k] = value
|
|
|
|
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
|
|
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():
|
|
value = getattr(parsed_args, k, None)
|
|
if value is not None:
|
|
network_kwargs[k] = value
|
|
else:
|
|
for k, v in NOVA_NETWORK_QUOTAS.items():
|
|
value = getattr(parsed_args, k, None)
|
|
if value is not None:
|
|
compute_kwargs[k] = value
|
|
|
|
if parsed_args.quota_class:
|
|
if compute_kwargs:
|
|
compute_client.quota_classes.update(
|
|
parsed_args.project,
|
|
**compute_kwargs,
|
|
)
|
|
if volume_kwargs:
|
|
volume_client.quota_classes.update(
|
|
parsed_args.project,
|
|
**volume_kwargs,
|
|
)
|
|
if network_kwargs:
|
|
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)
|
|
if volume_kwargs:
|
|
volume_client.quotas.update(project, **volume_kwargs)
|
|
if (
|
|
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)
|
|
|
|
|
|
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/class>',
|
|
nargs='?',
|
|
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=_(
|
|
'**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>'),
|
|
)
|
|
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
|
|
|
|
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)
|
|
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 '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
|