# 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 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 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 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): 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 {} class ListQuota(command.Lister, BaseQuota): _description = _( "List quotas for all projects with non-default quota values or " "list detailed quota informations for requested project") 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 = [] for resource, values in quotas.items(): # NOTE(slaweq): there is no detailed quotas info for some resources # and it should'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)) def get_parser(self, prog_name): parser = super(ListQuota, self).get_parser(prog_name) parser.add_argument( '--project', metavar='', help=_('List quotas for this project (name or ID)'), ) parser.add_argument( '--detail', dest='detail', action='store_true', default=False, help=_('Show details about quotas usage') ) 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 take_action(self, parsed_args): 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: LOG.warning("Volume service doesn't provide detailed quota" " information") 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='', help=_('Set quotas for this project or class (name/ID)'), ) parser.add_argument( '--class', dest='quota_class', action='store_true', default=False, help=_('Set quotas for '), ) 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='', help=_('Set quotas for a specific '), ) parser.add_argument( '--force', action='store_true', help=_('Force quota update (only supported by compute)') ) return parser def take_action(self, parsed_args): 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: compute_kwargs['force'] = True 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 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 class" " is 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.ShowOne, BaseQuota): _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(ShowQuota, self).get_parser(prog_name) parser.add_argument( 'project', metavar='', nargs='?', help=_('Show quotas for this project or class (name or ID)'), ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( '--class', dest='quota_class', action='store_true', default=False, help=_('Show quotas for '), ) type_group.add_argument( '--default', dest='default', action='store_true', default=False, help=_('Show default quotas for ') ) return parser def take_action(self, parsed_args): 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() 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) # Handle project ID special as it only appears in output 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()))