From 8640ca9348d87bfe73ac513df5498df19e2e0605 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Mon, 1 Jun 2015 17:35:18 -0400 Subject: Remove quota module Per discussion at the oslo graduation working session at the 2015 Vancouver Summit, remove the quota module as it is no longer needed. See also: https://etherpad.openstack.org/p/YVR-oslo-graduation-schedule Co-Authored-By: Ian Cordasco Change-Id: I523f95b9e1d981ef4ad688c4732f928913388a49 --- MAINTAINERS | 6 - openstack/common/quota.py | 1211 --------------------------------------------- tests/unit/test_quota.py | 456 ----------------- 3 files changed, 1673 deletions(-) delete mode 100644 openstack/common/quota.py delete mode 100644 tests/unit/test_quota.py diff --git a/MAINTAINERS b/MAINTAINERS index 8ed0cb50..cd17dadb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -89,12 +89,6 @@ M: S: Orphan F: memorycache.py -== quota == - -M: Sergey Skripnick -S: Maintained -F: quota.py - == reports == M: diff --git a/openstack/common/quota.py b/openstack/common/quota.py deleted file mode 100644 index cf71a833..00000000 --- a/openstack/common/quota.py +++ /dev/null @@ -1,1211 +0,0 @@ -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -"""Common quotas""" - -import copy -import datetime -import logging - -from oslo_config import cfg -from oslo_log import versionutils -from oslo_utils import importutils -from oslo_utils import timeutils -import six - -from openstack.common._i18n import _, _LE - -LOG = logging.getLogger(__name__) - -common_quota_opts = [ - cfg.BoolOpt('use_default_quota_class', - default=True, - help='Enables or disables use of default quota class.'), - cfg.StrOpt('quota_driver', - default='openstack.common.quota.DbQuotaDriver', - help='Default driver to use for quota checks.'), - cfg.IntOpt('until_refresh', - default=0, - help='Count of reservations until usage is refreshed.'), - cfg.IntOpt('max_age', - default=0, - help='Number of seconds between subsequent usage refreshes.'), - cfg.IntOpt('reservation_expire', - default=86400, - help='Number of seconds until a reservation expires.'), -] - -CONF = cfg.CONF -CONF.register_opts(common_quota_opts) - - -def list_opts(): - """Entry point for oslo-config-generator.""" - return [(None, copy.deepcopy(common_quota_opts))] - - -class QuotaException(Exception): - """Base exception for quota. - - To correctly use this class, inherit from it and define - a 'msg_fmt' property. That msg_fmt will get printf'd - with the keyword arguments provided to the constructor. - - """ - msg_fmt = _("Quota exception occurred.") - code = 500 - headers = {} - safe = False - - def __init__(self, message=None, **kwargs): - self.kwargs = {'code': self.code} - self.kwargs.update(kwargs) - if not message: - try: - message = self.msg_fmt % self.kwargs - except Exception: - # kwargs doesn't match a variable in the message - # log the issue and the kwargs - LOG.exception(_LE('Exception in string format operation')) - for name, value in six.iteritems(kwargs): - LOG.error(_LE("%(name)s: %(value)s"), {'name': name, - 'value': value}) - # at least get the core message out if something happened - message = self.msg_fmt - super(QuotaException, self).__init__(message) - - def format_message(self): - return six.text_type(self) - - -class QuotaError(QuotaException): - msg_fmt = _("Quota exceeded: code=%(code)s") - code = 413 - headers = {'Retry-After': 0} - safe = True - - -class InvalidQuotaValue(QuotaException): - msg_fmt = _("Change would make usage less than 0 for the following " - "resources: %(unders)s") - - -class OverQuota(QuotaException): - msg_fmt = _("Quota exceeded for resources: %(overs)s") - - -class QuotaExists(QuotaException): - message = _("Quota exists") - - -class QuotaResourceUnknown(QuotaException): - msg_fmt = _("Unknown quota resources %(unknown)s.") - - -class QuotaNotFound(QuotaException): - code = 404 - message = _("Quota could not be found") - - -class QuotaUsageNotFound(QuotaNotFound): - msg_fmt = _("Quota usage for project %(project_id)s could not be found.") - - -class ProjectQuotaNotFound(QuotaNotFound): - msg_fmt = _("Quota for project %(project_id)s could not be found.") - - -class ProjectUserQuotaNotFound(QuotaNotFound): - msg_fmt = _("Quota for user %(user_id)s in project %(project_id)s " - "could not be found.") - - -class QuotaClassNotFound(QuotaNotFound): - msg_fmt = _("Quota class %(class_name)s could not be found.") - - -class ReservationNotFound(QuotaNotFound): - msg_fmt = _("Quota reservation %(uuid)s could not be found.") - - -class InvalidReservationExpiration(QuotaException): - code = 400 - msg_fmt = _("Invalid reservation expiration %(expire)s.") - - -class DbQuotaDriver(object): - """Database quota driver. - - Driver to perform necessary checks to enforce quotas and obtain - quota information. The default driver utilizes the local - database. - """ - - def __init__(self, db): - self.db = db - - def get_by_project_and_user(self, context, project_id, user_id, resource): - """Get a specific quota by project and user.""" - - return self.db.quota_get(context, project_id, user_id, resource) - - def get_by_project(self, context, project_id, resource_name): - """Get a specific quota by project.""" - - return self.db.quota_get(context, project_id, resource_name) - - def get_by_class(self, context, quota_class, resource_name): - """Get a specific quota by quota class.""" - - return self.db.quota_class_get(context, quota_class, resource_name) - - def get_default(self, context, resource): - """Get a specific default quota for a resource.""" - - default_quotas = self.db.quota_class_get_default(context) - return default_quotas.get(resource.name, resource.default) - - def get_defaults(self, context, resources): - """Given a list of resources, retrieve the default quotas. - - Use the class quotas named `_DEFAULT_QUOTA_NAME` as default quotas, - if it exists. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - """ - - quotas = {} - default_quotas = {} - if CONF.use_default_quota_class: - default_quotas = self.db.quota_class_get_default(context) - for resource in resources.values(): - if resource.name not in default_quotas: - versionutils.report_deprecated_feature( - LOG, - "Default quota for resource: %(res)s is set " - "by the default quota flag: quota_%(res)s, " - "it is now deprecated. Please use the " - "the default quota class for default " - "quota." % {'res': resource.name}) - quotas[resource.name] = default_quotas.get(resource.name, - resource.default) - - return quotas - - def get_class_quotas(self, context, resources, quota_class, - defaults=True): - """Given a list of resources, get quotas for the given quota class. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param quota_class: The name of the quota class to return - quotas for. - :param defaults: If True, the default value will be reported - if there is no specific value for the - resource. - """ - - quotas = {} - default_quotas = {} - class_quotas = self.db.quota_class_get_all_by_name(context, - quota_class) - if defaults: - default_quotas = self.db.quota_class_get_default(context) - for resource in resources.values(): - if resource.name in class_quotas: - quotas[resource.name] = class_quotas[resource.name] - continue - - if defaults: - quotas[resource.name] = default_quotas.get(resource.name, - resource.default) - - return quotas - - def _process_quotas(self, context, resources, project_id, quotas, - quota_class=None, defaults=True, usages=None, - remains=False): - """Get the quotas for the appropriate class. - - If the project ID matches the one in the context, we use the - quota_class from the context, otherwise, we use the provided - quota_class (if any) - """ - - modified_quotas = {} - if project_id == context.project_id: - quota_class = context.quota_class - if quota_class: - class_quotas = self.db.quota_class_get_all_by_name(context, - quota_class) - else: - class_quotas = {} - - default_quotas = self.get_defaults(context, resources) - - for resource in resources.values(): - # Omit default/quota class values - if not defaults and resource.name not in quotas: - continue - class_quota = class_quotas.get(resource.name, - default_quotas[resource.name]) - limit = quotas.get(resource.name, class_quota) - modified_quotas[resource.name] = dict(limit=limit) - - # Include usages if desired. This is optional because one - # internal consumer of this interface wants to access the - # usages directly from inside a transaction. - if usages: - usage = usages.get(resource.name, {}) - modified_quotas[resource.name].update( - in_use=usage.get('in_use', 0), - reserved=usage.get('reserved', 0), - ) - # Initialize remains quotas. - if remains: - modified_quotas[resource.name].update(remains=limit) - - if remains: - all_quotas = self.db.quota_get_all(context, project_id) - for quota in all_quotas: - if quota['resource'] in modified_quotas: - modified_quotas[quota['resource']]['remains'] -= \ - quota['hard_limit'] - - return modified_quotas - - def get_user_quotas(self, context, resources, project_id, user_id, - quota_class=None, defaults=True, - usages=True, project_quotas=None, - user_quotas=None): - """Get user quotas for given user and project. - - Given a list of resources, retrieve the quotas for the given - user and project. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param project_id: The ID of the project to return quotas for. - :param user_id: The ID of the user to return quotas for. - :param quota_class: If project_id != context.project_id, the - quota class cannot be determined. This - parameter allows it to be specified. It - will be ignored if project_id == - context.project_id. - :param defaults: If True, the quota class value (or the - default value, if there is no value from the - quota class) will be reported if there is no - specific value for the resource. - :param usages: If True, the current in_use and reserved counts - will also be returned. - :param project_quotas: Quotas dictionary for the specified project. - :param user_quotas: Quotas dictionary for the specified project - and user. - """ - user_quotas = user_quotas or self.db.quota_get_all_by_project_and_user( - context, project_id, user_id) - # Use the project quota for default user quota. - proj_quotas = project_quotas or self.db.quota_get_all_by_project( - context, project_id) - for key, value in six.iteritems(proj_quotas): - if key not in user_quotas: - user_quotas[key] = value - user_usages = None - if usages: - user_usages = self.db.quota_usage_get_all_by_project_and_user( - context, project_id, user_id) - return self._process_quotas(context, resources, project_id, - user_quotas, quota_class, - defaults=defaults, usages=user_usages) - - def get_project_quotas(self, context, resources, project_id, - quota_class=None, defaults=True, - usages=True, remains=False, - project_quotas=None): - """Given a list of resources, get the quotas for the given project. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param project_id: The ID of the project to return quotas for. - :param quota_class: If project_id != context.project_id, the - quota class cannot be determined. This - parameter allows it to be specified. It - will be ignored if project_id == - context.project_id. - :param defaults: If True, the quota class value (or the - default value, if there is no value from the - quota class) will be reported if there is no - specific value for the resource. - :param usages: If True, the current in_use and reserved counts - will also be returned. - :param remains: If True, the current remains of the project will - will be returned. - :param project_quotas: Quotas dictionary for the specified project. - """ - project_quotas = project_quotas or self.db.quota_get_all_by_project( - context, project_id) - project_usages = None - if usages: - project_usages = self.db.quota_usage_get_all_by_project( - context, project_id) - return self._process_quotas(context, resources, project_id, - project_quotas, quota_class, - defaults=defaults, usages=project_usages, - remains=remains) - - def get_settable_quotas(self, context, resources, project_id, - user_id=None): - """Get settable quotas for given user and project. - - Given a list of resources, retrieve the range of settable quotas for - the given user or project. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param project_id: The ID of the project to return quotas for. - :param user_id: The ID of the user to return quotas for. - """ - settable_quotas = {} - db_proj_quotas = self.db.quota_get_all_by_project( - context, project_id) - project_quotas = self.get_project_quotas(context, resources, - project_id, remains=True, - project_quotas=db_proj_quotas) - if user_id: - setted_quotas = self.db.quota_get_all_by_project_and_user( - context, project_id, user_id) - user_quotas = self.get_user_quotas(context, resources, - project_id, user_id, - project_quotas=db_proj_quotas, - user_quotas=setted_quotas) - for key, value in user_quotas.items(): - maximum = project_quotas[key]['remains'] +\ - setted_quotas.get(key, 0) - settable_quotas[key] = dict( - minimum=value['in_use'] + value['reserved'], - maximum=maximum, - ) - else: - for key, value in project_quotas.items(): - minimum = max(int(value['limit'] - value['remains']), - int(value['in_use'] + value['reserved'])) - settable_quotas[key] = dict(minimum=minimum, maximum=-1) - return settable_quotas - - def _get_quotas(self, context, resources, keys, has_sync, project_id=None, - user_id=None, project_quotas=None): - """Get guotas for resources identified by keys. - - A helper method which retrieves the quotas for the specific - resources identified by keys, and which apply to the current - context. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param keys: A list of the desired quotas to retrieve. - :param has_sync: If True, indicates that the resource must - have a sync attribute; if False, indicates - that the resource must NOT have a sync - attribute. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - :param user_id: Specify the user_id if current context - is admin and admin wants to impact on - common user. - :param project_quotas: Quotas dictionary for the specified project. - """ - - # Filter resources - if has_sync: - sync_filt = lambda x: hasattr(x, 'sync') - else: - sync_filt = lambda x: not hasattr(x, 'sync') - desired = set(keys) - sub_resources = dict((k, v) for k, v in resources.items() - if k in desired and sync_filt(v)) - - # Make sure we accounted for all of them... - if len(keys) != len(sub_resources): - unknown = desired - set(sub_resources.keys()) - raise QuotaResourceUnknown(unknown=sorted(unknown)) - - if user_id: - # Grab and return the quotas (without usages) - quotas = self.get_user_quotas(context, sub_resources, - project_id, user_id, - context.quota_class, usages=False, - project_quotas=project_quotas) - else: - # Grab and return the quotas (without usages) - quotas = self.get_project_quotas(context, sub_resources, - project_id, - context.quota_class, - usages=False, - project_quotas=project_quotas) - - return dict((k, v['limit']) for k, v in quotas.items()) - - def limit_check(self, context, resources, values, project_id=None, - user_id=None): - """Check simple quota limits. - - For limits--those quotas for which there is no usage - synchronization function--this method checks that a set of - proposed values are permitted by the limit restriction. - - This method will raise a QuotaResourceUnknown exception if a - given resource is unknown or if it is not a simple limit - resource. - - If any of the proposed values is over the defined quota, an - OverQuota exception will be raised with the sorted list of the - resources which are too high. Otherwise, the method returns - nothing. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param values: A dictionary of the values to check against the - quota. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - :param user_id: Specify the user_id if current context - is admin and admin wants to impact on - common user. - """ - - # Ensure no value is less than zero - unders = [key for key, val in values.items() if val < 0] - if unders: - raise InvalidQuotaValue(unders=sorted(unders)) - - # If project_id is None, then we use the project_id in context - if project_id is None: - project_id = context.project_id - # If user id is None, then we use the user_id in context - if user_id is None: - user_id = context.user_id - - # Get the applicable quotas - project_quotas = self.db.quota_get_all_by_project( - context, project_id) - quotas = self._get_quotas(context, resources, values.keys(), - has_sync=False, project_id=project_id, - project_quotas=project_quotas) - user_quotas = self._get_quotas(context, resources, values.keys(), - has_sync=False, project_id=project_id, - user_id=user_id, - project_quotas=project_quotas) - # Check the quotas and construct a list of the resources that - # would be put over limit by the desired values - overs = [key for key, val in values.items() - if (quotas[key] >= 0 and quotas[key] < val) or - (user_quotas[key] >= 0 and user_quotas[key] < val)] - if overs: - raise OverQuota(overs=sorted(overs), quotas=quotas, - usages={}) - - def reserve(self, context, resources, deltas, expire=None, - project_id=None, user_id=None): - """Check quotas and reserve resources. - - For counting quotas--those quotas for which there is a usage - synchronization function--this method checks quotas against - current usage and the desired deltas. - - This method will raise a QuotaResourceUnknown exception if a - given resource is unknown or if it does not have a usage - synchronization function. - - If any of the proposed values is over the defined quota, an - OverQuota exception will be raised with the sorted list of the - resources which are too high. Otherwise, the method returns a - list of reservation UUIDs which were created. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param deltas: A dictionary of the proposed delta changes. - :param expire: An optional parameter specifying an expiration - time for the reservations. If it is a simple - number, it is interpreted as a number of - seconds and added to the current time; if it is - a datetime.timedelta object, it will also be - added to the current time. A datetime.datetime - object will be interpreted as the absolute - expiration time. If None is specified, the - default expiration time set by - --default-reservation-expire will be used (this - value will be treated as a number of seconds). - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - :param user_id: Specify the user_id if current context - is admin and admin wants to impact on - common user. - """ - - # Set up the reservation expiration - if expire is None: - expire = CONF.reservation_expire - if isinstance(expire, six.integer_types): - expire = datetime.timedelta(seconds=expire) - if isinstance(expire, datetime.timedelta): - expire = timeutils.utcnow() + expire - if not isinstance(expire, datetime.datetime): - raise InvalidReservationExpiration(expire=expire) - - # If project_id is None, then we use the project_id in context - if project_id is None: - project_id = context.project_id - # If user_id is None, then we use the project_id in context - if user_id is None: - user_id = context.user_id - - # Get the applicable quotas. - # NOTE(Vek): We're not worried about races at this point. - # Yes, the admin may be in the process of reducing - # quotas, but that's a pretty rare thing. - project_quotas = self.db.quota_get_all_by_project( - context, project_id) - quotas = self._get_quotas(context, resources, deltas.keys(), - has_sync=True, project_id=project_id, - project_quotas=project_quotas) - user_quotas = self._get_quotas(context, resources, deltas.keys(), - has_sync=True, project_id=project_id, - user_id=user_id, - project_quotas=project_quotas) - # NOTE(Vek): Most of the work here has to be done in the DB - # API, because we have to do it in a transaction, - # which means access to the session. Since the - # session isn't available outside the DBAPI, we - # have to do the work there. - return self.db.quota_reserve(context, resources, quotas, user_quotas, - deltas, expire, - CONF.until_refresh, CONF.max_age, - project_id=project_id, user_id=user_id) - - def commit(self, context, reservations, project_id=None, user_id=None): - """Commit reservations. - - :param context: The request context, for access checks. - :param reservations: A list of the reservation UUIDs, as - returned by the reserve() method. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - :param user_id: Specify the user_id if current context - is admin and admin wants to impact on - common user. - """ - # If project_id is None, then we use the project_id in context - if project_id is None: - project_id = context.project_id - # If user_id is None, then we use the user_id in context - if user_id is None: - user_id = context.user_id - - self.db.reservation_commit(context, reservations, - project_id=project_id, user_id=user_id) - - def rollback(self, context, reservations, project_id=None, user_id=None): - """Roll back reservations. - - :param context: The request context, for access checks. - :param reservations: A list of the reservation UUIDs, as - returned by the reserve() method. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - :param user_id: Specify the user_id if current context - is admin and admin wants to impact on - common user. - """ - # If project_id is None, then we use the project_id in context - if project_id is None: - project_id = context.project_id - # If user_id is None, then we use the user_id in context - if user_id is None: - user_id = context.user_id - - self.db.reservation_rollback(context, reservations, - project_id=project_id, user_id=user_id) - - def usage_reset(self, context, resources): - """Reset the usage records. - - Reset usages for a particular user on a list of resources. - This will force that user's usage records to be refreshed - the next time a reservation is made. - - Note: this does not affect the currently outstanding - reservations the user has; those reservations must be - committed or rolled back (or expired). - - :param context: The request context, for access checks. - :param resources: A list of the resource names for which the - usage must be reset. - """ - - # We need an elevated context for the calls to - # quota_usage_update() - elevated = context.elevated() - - for resource in resources: - try: - # Reset the usage to -1, which will force it to be - # refreshed - self.db.quota_usage_update(elevated, context.project_id, - context.user_id, - resource, in_use=-1) - except QuotaUsageNotFound: - # That means it'll be refreshed anyway - pass - - def destroy_all_by_project_and_user(self, context, project_id, user_id): - """Destroy objects by project and user. - - Destroy all quotas, usages, and reservations associated with a - project and user. - - :param context: The request context, for access checks. - :param project_id: The ID of the project being deleted. - :param user_id: The ID of the user being deleted. - """ - - self.db.quota_destroy_all_by_project_and_user(context, project_id, - user_id) - - def destroy_all_by_project(self, context, project_id): - """Destroy quotas, usages, and reservations for given project. - - :param context: The request context, for access checks. - :param project_id: The ID of the project being deleted. - """ - - self.db.quota_destroy_all_by_project(context, project_id) - - def expire(self, context): - """Expire reservations. - - Explores all currently existing reservations and rolls back - any that have expired. - - :param context: The request context, for access checks. - """ - - self.db.reservation_expire(context) - - -class BaseResource(object): - """Describe a single resource for quota checking.""" - - def __init__(self, name, flag=None): - """Initializes a Resource. - - :param name: The name of the resource, i.e., "volumes". - :param flag: The name of the flag or configuration option - which specifies the default value of the quota - for this resource. - """ - - self.name = name - self.flag = flag - - def quota(self, driver, context, **kwargs): - """Given a driver and context, obtain the quota for this resource. - - :param driver: A quota driver. - :param context: The request context. - :param project_id: The project to obtain the quota value for. - If not provided, it is taken from the - context. If it is given as None, no - project-specific quota will be searched - for. - :param quota_class: The quota class corresponding to the - project, or for which the quota is to be - looked up. If not provided, it is taken - from the context. If it is given as None, - no quota class-specific quota will be - searched for. Note that the quota class - defaults to the value in the context, - which may not correspond to the project if - project_id is not the same as the one in - the context. - """ - - # Get the project ID - project_id = kwargs.get('project_id', context.project_id) - - # Ditto for the quota class - quota_class = kwargs.get('quota_class', context.quota_class) - - # Look up the quota for the project - if project_id: - try: - return driver.get_by_project(context, project_id, self.name) - except ProjectQuotaNotFound: - pass - - # Try for the quota class - if quota_class: - try: - return driver.get_by_class(context, quota_class, self.name) - except QuotaClassNotFound: - pass - - # OK, return the default - return driver.get_default(context, self) - - @property - def default(self): - """Return the default value of the quota.""" - - return CONF[self.flag] if self.flag else -1 - - -class ReservableResource(BaseResource): - """Describe a reservable resource.""" - - def __init__(self, name, sync, flag=None): - """Initializes a ReservableResource. - - Reservable resources are those resources which directly - correspond to objects in the database, i.e., instances, - cores, etc. - - Usage synchronization function must be associated with each - object. This function will be called to determine the current - counts of one or more resources. This association is done in - database backend. See QUOTA_SYNC_FUNCTIONS in db/sqlalchemy/api.py. - - The usage synchronization function will be passed three - arguments: an admin context, the project ID, and an opaque - session object, which should in turn be passed to the - underlying database function. Synchronization functions - should return a dictionary mapping resource names to the - current in_use count for those resources; more than one - resource and resource count may be returned. Note that - synchronization functions may be associated with more than one - ReservableResource. - - :param name: The name of the resource, i.e., "volumes". - :param sync: A dbapi methods name which returns a dictionary - to resynchronize the in_use count for one or more - resources, as described above. - :param flag: The name of the flag or configuration option - which specifies the default value of the quota - for this resource. - """ - - super(ReservableResource, self).__init__(name, flag=flag) - self.sync = sync - - -class AbsoluteResource(BaseResource): - """Describe a non-reservable resource.""" - - pass - - -class CountableResource(AbsoluteResource): - """Countable resource. - - Describe a resource where the counts aren't based solely on the - project ID. - """ - - def __init__(self, name, count, flag=None): - """Initializes a CountableResource. - - Countable resources are those resources which directly - correspond to objects in the database, i.e., volumes, gigabytes, - etc., but for which a count by project ID is inappropriate. A - CountableResource must be constructed with a counting - function, which will be called to determine the current counts - of the resource. - - The counting function will be passed the context, along with - the extra positional and keyword arguments that are passed to - Quota.count(). It should return an integer specifying the - count. - - Note that this counting is not performed in a transaction-safe - manner. This resource class is a temporary measure to provide - required functionality, until a better approach to solving - this problem can be evolved. - - :param name: The name of the resource, i.e., "volumes". - :param count: A callable which returns the count of the - resource. The arguments passed are as described - above. - :param flag: The name of the flag or configuration option - which specifies the default value of the quota - for this resource. - """ - - super(CountableResource, self).__init__(name, flag=flag) - self.count = count - - -class QuotaEngine(object): - """Represent the set of recognized quotas.""" - - def __init__(self, db, quota_driver_class=None): - """Initialize a Quota object.""" - self.db = db - self._resources = {} - self._driver_cls = quota_driver_class - self.__driver = None - - @property - def _driver(self): - if self.__driver: - return self.__driver - if not self._driver_cls: - self._driver_cls = CONF.quota_driver - if isinstance(self._driver_cls, six.string_types): - self._driver_cls = importutils.import_object(self._driver_cls, - self.db) - self.__driver = self._driver_cls - return self.__driver - - def __contains__(self, resource): - return resource in self.resources - - def register_resource(self, resource): - """Register a resource.""" - - self._resources[resource.name] = resource - - def register_resources(self, resources): - """Register a list of resources.""" - - for resource in resources: - self.register_resource(resource) - - def get_by_project_and_user(self, context, project_id, user_id, resource): - """Get a specific quota by project and user.""" - - return self._driver.get_by_project_and_user(context, project_id, - user_id, resource) - - def get_by_project(self, context, project_id, resource_name): - """Get a specific quota by project.""" - - return self._driver.get_by_project(context, project_id, resource_name) - - def get_by_class(self, context, quota_class, resource_name): - """Get a specific quota by quota class.""" - - return self._driver.get_by_class(context, quota_class, resource_name) - - def get_default(self, context, resource): - """Get a specific default quota for a resource.""" - - return self._driver.get_default(context, resource) - - def get_defaults(self, context): - """Retrieve the default quotas. - - :param context: The request context, for access checks. - """ - - return self._driver.get_defaults(context, self.resources) - - def get_class_quotas(self, context, quota_class, defaults=True): - """Retrieve the quotas for the given quota class. - - :param context: The request context, for access checks. - :param quota_class: The name of the quota class to return - quotas for. - :param defaults: If True, the default value will be reported - if there is no specific value for the - resource. - """ - - return self._driver.get_class_quotas(context, self.resources, - quota_class, defaults=defaults) - - def get_user_quotas(self, context, project_id, user_id, quota_class=None, - defaults=True, usages=True): - """Retrieve the quotas for the given user and project. - - :param context: The request context, for access checks. - :param project_id: The ID of the project to return quotas for. - :param user_id: The ID of the user to return quotas for. - :param quota_class: If project_id != context.project_id, the - quota class cannot be determined. This - parameter allows it to be specified. - :param defaults: If True, the quota class value (or the - default value, if there is no value from the - quota class) will be reported if there is no - specific value for the resource. - :param usages: If True, the current in_use and reserved counts - will also be returned. - """ - - return self._driver.get_user_quotas(context, self.resources, - project_id, user_id, - quota_class=quota_class, - defaults=defaults, - usages=usages) - - def get_project_quotas(self, context, project_id, quota_class=None, - defaults=True, usages=True, remains=False): - """Retrieve the quotas for the given project. - - :param context: The request context, for access checks. - :param project_id: The ID of the project to return quotas for. - :param quota_class: If project_id != context.project_id, the - quota class cannot be determined. This - parameter allows it to be specified. - :param defaults: If True, the quota class value (or the - default value, if there is no value from the - quota class) will be reported if there is no - specific value for the resource. - :param usages: If True, the current in_use and reserved counts - will also be returned. - :param remains: If True, the current remains of the project will - will be returned. - """ - - return self._driver.get_project_quotas(context, self.resources, - project_id, - quota_class=quota_class, - defaults=defaults, - usages=usages, - remains=remains) - - def get_settable_quotas(self, context, project_id, user_id=None): - """Get settable quotas for given user and project. - - Given a list of resources, retrieve the range of settable quotas for - the given user or project. - - :param context: The request context, for access checks. - :param resources: A dictionary of the registered resources. - :param project_id: The ID of the project to return quotas for. - :param user_id: The ID of the user to return quotas for. - """ - - return self._driver.get_settable_quotas(context, self.resources, - project_id, - user_id=user_id) - - def count(self, context, resource, *args, **kwargs): - """Count a resource. - - For countable resources, invokes the count() function and - returns its result. Arguments following the context and - resource are passed directly to the count function declared by - the resource. - - :param context: The request context, for access checks. - :param resource: The name of the resource, as a string. - """ - - # Get the resource - res = self.resources.get(resource) - if not res or not hasattr(res, 'count'): - raise QuotaResourceUnknown(unknown=[resource]) - - return res.count(context, *args, **kwargs) - - def limit_check(self, context, project_id=None, user_id=None, **values): - """Check simple quota limits. - - For limits--those quotas for which there is no usage - synchronization function--this method checks that a set of - proposed values are permitted by the limit restriction. The - values to check are given as keyword arguments, where the key - identifies the specific quota limit to check, and the value is - the proposed value. - - This method will raise a QuotaResourceUnknown exception if a - given resource is unknown or if it is not a simple limit - resource. - - If any of the proposed values is over the defined quota, an - OverQuota exception will be raised with the sorted list of the - resources which are too high. Otherwise, the method returns - nothing. - - :param context: The request context, for access checks. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - :param user_id: Specify the user_id if current context - is admin and admin wants to impact on - common user. - """ - - return self._driver.limit_check(context, self.resources, values, - project_id=project_id, user_id=user_id) - - def reserve(self, context, expire=None, project_id=None, user_id=None, - **deltas): - """Check quotas and reserve resources. - - For counting quotas--those quotas for which there is a usage - synchronization function--this method checks quotas against - current usage and the desired deltas. The deltas are given as - keyword arguments, and current usage and other reservations - are factored into the quota check. - - This method will raise a QuotaResourceUnknown exception if a - given resource is unknown or if it does not have a usage - synchronization function. - - If any of the proposed values is over the defined quota, an - OverQuota exception will be raised with the sorted list of the - resources which are too high. Otherwise, the method returns a - list of reservation UUIDs which were created. - - :param context: The request context, for access checks. - :param expire: An optional parameter specifying an expiration - time for the reservations. If it is a simple - number, it is interpreted as a number of - seconds and added to the current time; if it is - a datetime.timedelta object, it will also be - added to the current time. A datetime.datetime - object will be interpreted as the absolute - expiration time. If None is specified, the - default expiration time set by - --default-reservation-expire will be used (this - value will be treated as a number of seconds). - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - """ - - reservations = self._driver.reserve(context, self.resources, deltas, - expire=expire, - project_id=project_id, - user_id=user_id) - - LOG.debug("Created reservations %s", reservations) - - return reservations - - def commit(self, context, reservations, project_id=None, user_id=None): - """Commit reservations. - - :param context: The request context, for access checks. - :param reservations: A list of the reservation UUIDs, as - returned by the reserve() method. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - """ - - try: - self._driver.commit(context, reservations, project_id=project_id, - user_id=user_id) - except Exception: - # NOTE(Vek): Ignoring exceptions here is safe, because the - # usage resynchronization and the reservation expiration - # mechanisms will resolve the issue. The exception is - # logged, however, because this is less than optimal. - LOG.exception(_LE("Failed to commit reservations %s"), - reservations) - return - LOG.debug("Committed reservations %s", reservations) - - def rollback(self, context, reservations, project_id=None, user_id=None): - """Roll back reservations. - - :param context: The request context, for access checks. - :param reservations: A list of the reservation UUIDs, as - returned by the reserve() method. - :param project_id: Specify the project_id if current context - is admin and admin wants to impact on - common user's tenant. - """ - - try: - self._driver.rollback(context, reservations, project_id=project_id, - user_id=user_id) - except Exception: - # NOTE(Vek): Ignoring exceptions here is safe, because the - # usage resynchronization and the reservation expiration - # mechanisms will resolve the issue. The exception is - # logged, however, because this is less than optimal. - LOG.exception(_LE("Failed to roll back reservations %s"), - reservations) - return - LOG.debug("Rolled back reservations %s", reservations) - - def usage_reset(self, context, resources): - """Reset the usage records. - - Reset usages for a particular user on a list of resources. - This will force that user's usage records to be refreshed - the next time a reservation is made. - - Note: this does not affect the currently outstanding - reservations the user has; those reservations must be - committed or rolled back (or expired). - - :param context: The request context, for access checks. - :param resources: A list of the resource names for which the - usage must be reset. - """ - - self._driver.usage_reset(context, resources) - - def destroy_all_by_project_and_user(self, context, project_id, user_id): - """Destroy all objects for given user and project. - - Destroy all quotas, usages, and reservations associated with a - project and user. - - :param context: The request context, for access checks. - :param project_id: The ID of the project being deleted. - :param user_id: The ID of the user being deleted. - """ - - self._driver.destroy_all_by_project_and_user(context, - project_id, user_id) - - def destroy_all_by_project(self, context, project_id): - """Destroy all quotas, usages, and reservations for given project. - - :param context: The request context, for access checks. - :param project_id: The ID of the project being deleted. - """ - - self._driver.destroy_all_by_project(context, project_id) - - def expire(self, context): - """Expire reservations. - - Explores all currently existing reservations and rolls back - any that have expired. - - :param context: The request context, for access checks. - """ - - self._driver.expire(context) - - @property - def resource_names(self): - return sorted(self.resources.keys()) - - @property - def resources(self): - return self._resources diff --git a/tests/unit/test_quota.py b/tests/unit/test_quota.py deleted file mode 100644 index 29e5378b..00000000 --- a/tests/unit/test_quota.py +++ /dev/null @@ -1,456 +0,0 @@ -# -# 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. - -import datetime - -import mock -from oslo_config import fixture as config -from oslotest import base as test_base -from oslotest import moxstubout - -from openstack.common import quota - - -class FakeContext(object): - project_id = 'p1' - user_id = 'u1' - quota_class = 'QuotaClass_' - - def elevated(self): - return self - - -class ExceptionTestCase(test_base.BaseTestCase): - - def _get_raised_exception(self, exception, *args, **kwargs): - try: - raise exception(*args, **kwargs) - except Exception as e: - return e - - def test_quota_exception_format(self): - - class TestException(quota.QuotaException): - msg_fmt = "Test format %(string)s" - - e = self._get_raised_exception(TestException) - self.assertEqual(str(e), e.msg_fmt) - - e = self._get_raised_exception(TestException, number=42) - self.assertEqual(str(e), e.msg_fmt) - - e = self._get_raised_exception(TestException, string="test") - self.assertEqual(str(e), e.msg_fmt % {"string": "test"}) - - -class DbQuotaDriverTestCase(test_base.BaseTestCase): - - def setUp(self): - self.stubs = self.useFixture(moxstubout.MoxStubout()).stubs - self.CONF = self.useFixture(config.Config()).conf - self.sample_resources = {'r1': quota.BaseResource('r1'), - 'r2': quota.BaseResource('r2')} - - dbapi = mock.Mock() - dbapi.quota_usage_get_all_by_project_and_user = mock.Mock( - return_value={'project_id': 'p1', 'user_id': 'u1', - 'r1': {'reserved': 1, 'in_use': 2}, - 'r2': {'reserved': 2, 'in_use': 3}}) - dbapi.quota_get_all_by_project_and_user = mock.Mock( - return_value={'project_id': 'p1', 'user_id': 'u1', - 'r1': 5, 'r2': 6}) - dbapi.quota_get = mock.Mock(return_value='quota_get') - dbapi.quota_reserve = mock.Mock(return_value='quota_reserve') - dbapi.quota_class_get = mock.Mock(return_value='quota_class_get') - dbapi.quota_class_reserve = mock.Mock( - return_value='quota_class_reserve') - dbapi.quota_class_get_default = mock.Mock( - return_value={'r1': 1, 'r2': 2}) - dbapi.quota_class_get_all_by_name = mock.Mock(return_value={'r1': 9}) - dbapi.quota_get_all_by_project = mock.Mock( - return_value=dict([('r%d' % i, i) for i in range(3)])) - dbapi.quota_get_all = mock.Mock( - return_value=[{'resource': 'r1', 'hard_limit': 3}, - {'resource': 'r2', 'hard_limit': 4}]) - dbapi.quota_usage_get_all_by_project = mock.Mock( - return_value=dict([('r%d' % i, {'in_use': i, 'reserved': i + 1}) - for i in range(3)])) - self.dbapi = dbapi - self.driver = quota.DbQuotaDriver(dbapi) - self.ctxt = FakeContext() - return super(DbQuotaDriverTestCase, self).setUp() - - def test_get_by_project(self): - args = ['p1', 'resource'] - self.assertEqual('quota_get', - self.driver.get_by_project(self.ctxt, *args)) - self.driver.db.quota_get.assert_called_once_with(self.ctxt, *args) - - def test_get_by_project_and_user(self): - args = ['p1', 'u1', 'resource'] - self.assertEqual('quota_get', - self.driver.get_by_project_and_user(self.ctxt, *args)) - self.driver.db.quota_get.assert_called_once_with(self.ctxt, *args) - - def test_get_by_class(self): - args = ['class', 'resource'] - self.assertEqual('quota_class_get', - self.driver.get_by_class(self.ctxt, *args)) - self.driver.db.quota_class_get.assert_called_once_with(self.ctxt, - *args) - - def test_get_defaults(self): - defaults = self.driver.get_defaults(self.ctxt, self.sample_resources) - self.assertEqual(defaults, {'r1': 1, 'r2': 2}) - self.sample_resources.pop('r1') - defaults = self.driver.get_defaults(self.ctxt, self.sample_resources) - self.assertEqual(defaults, {'r2': 2}) - - def test_get_class_quotas(self): - quotas = self.driver.get_class_quotas(self.ctxt, - self.sample_resources, - 'ClassName') - self.assertEqual(quotas, {'r1': 9, 'r2': 2}) - - def test_get_user_quotas(self): - actual = self.driver.get_user_quotas( - self.ctxt, self.sample_resources.copy(), 'p1', 'u1') - expected = {'r1': {'in_use': 2, 'limit': 5, 'reserved': 1}, - 'r2': {'in_use': 3, 'limit': 6, 'reserved': 2}} - self.assertEqual(actual, expected) - - def test_get_default_user_quotas(self): - self.dbapi.quota_get_all_by_project_and_user = mock.Mock( - return_value={'project_id': 'p1', 'user_id': 'u1'}) - self.dbapi.quota_get_all_by_project = mock.Mock( - return_value={'r1': 5, 'r2': 6}) - driver = quota.DbQuotaDriver(self.dbapi) - actual = driver.get_user_quotas( - self.ctxt, self.sample_resources.copy(), 'p1', 'u1') - expected = {'r1': {'in_use': 2, 'limit': 5, 'reserved': 1}, - 'r2': {'in_use': 3, 'limit': 6, 'reserved': 2}} - self.assertEqual(actual, expected) - - def test_get_settable_quotas(self): - actual = self.driver.get_settable_quotas(self.ctxt, - self.sample_resources, 'p1') - expected = {'r1': {'maximum': -1, 'minimum': 3}, - 'r2': {'maximum': -1, 'minimum': 5}} - self.assertEqual(actual, expected) - - def test_get_settable_quotas_with_user_id(self): - actual = self.driver.get_settable_quotas( - self.ctxt, self.sample_resources, 'p1', user_id='u1') - expected = {'r1': {'maximum': 3, 'minimum': 3}, - 'r2': {'maximum': 4, 'minimum': 5}} - self.assertEqual(actual, expected) - - def test_get_project_quotas(self): - self.ctxt.quota_class = 'ClassName' - expected = {'r1': {'limit': 1, 'in_use': 1, 'reserved': 2}, - 'r2': {'limit': 2, 'in_use': 2, 'reserved': 3}} - quotas = self.driver.get_project_quotas(self.ctxt, - self.sample_resources, 'p1') - self.assertEqual(quotas, expected) - - def test_get_project_quotas_project_id_differs(self): - self.ctxt.project_id = 'p2' - expected = {'r1': {'limit': 1, 'in_use': 1, 'reserved': 2}, - 'r2': {'limit': 2, 'in_use': 2, 'reserved': 3}} - quotas = self.driver.get_project_quotas(self.ctxt, - self.sample_resources, 'p1') - self.assertEqual(quotas, expected) - - def test_get_project_quotas_omit_default_quota_class(self): - self.sample_resources['r3'] = quota.BaseResource('r3') - quotas = self.driver.get_project_quotas( - self.ctxt, self.sample_resources, 'p1', defaults=False) - expected = {'r1': {'limit': 1, 'in_use': 1, 'reserved': 2}, - 'r2': {'limit': 2, 'in_use': 2, 'reserved': 3}} - self.assertEqual(quotas, expected) - - def test_limit_check_invalid_quota_value(self): - self.assertRaises(quota.InvalidQuotaValue, - self.driver.limit_check, self.ctxt, [], {'r1': -1}) - - def test_limit_check_quota_resource_unknown(self): - self.assertRaises(quota.QuotaResourceUnknown, - self.driver.limit_check, - self.ctxt, - {'r1': quota.ReservableResource('r1', 'r1')}, - {'r1': 42}) - - def test_limit_check_over_quota(self): - self.assertRaises(quota.OverQuota, - self.driver.limit_check, - self.ctxt, - {'r1': quota.BaseResource('r1')}, - {'r1': 2}) - - def test_limit_check(self): - self.assertIsNone(self.driver.limit_check( - self.ctxt, {'r1': quota.BaseResource('r1')}, {'r1': 1})) - - def test_quota_reserve(self): - now = datetime.datetime.utcnow() - - class FakeTimeutils(object): - @staticmethod - def utcnow(): - return now - - self.stubs.Set(quota, "timeutils", FakeTimeutils) - - expected = [self.ctxt, self.sample_resources, {}, {}, {}, None, - self.CONF.until_refresh, self.CONF.max_age] - - # expire as None - self.assertEqual('quota_reserve', self.driver.reserve( - self.ctxt, self.sample_resources, {}, None, 'p1')) - expected[5] = now + datetime.timedelta( - seconds=self.CONF.reservation_expire) - self.driver.db.quota_reserve.assert_called_once_with(*expected, - project_id='p1', - user_id='u1') - self.driver.db.reset_mock() - # expire as seconds - self.assertEqual('quota_reserve', self.driver.reserve( - self.ctxt, self.sample_resources, {}, 42, 'p1')) - expected[5] = now + datetime.timedelta(seconds=42) - self.driver.db.quota_reserve.assert_called_once_with(*expected, - project_id='p1', - user_id='u1') - self.driver.db.reset_mock() - # expire as absolute - expected[5] = now + datetime.timedelta(hours=1) - self.assertEqual('quota_reserve', self.driver.reserve( - self.ctxt, self.sample_resources, {}, - now + datetime.timedelta(hours=1), 'p1')) - self.driver.db.quota_reserve.assert_called_once_with(*expected, - project_id='p1', - user_id='u1') - self.driver.db.reset_mock() - # InvalidReservationExpiration - self.assertRaises(quota.InvalidReservationExpiration, - self.driver.reserve, self.ctxt, - self.sample_resources, {}, (), 'p1') - self.driver.db.reset_mock() - # project_id is None - self.assertEqual('quota_reserve', self.driver.reserve( - self.ctxt, self.sample_resources, {}, - now + datetime.timedelta(hours=1))) - self.driver.db.quota_reserve.assert_called_once_with(*expected, - project_id='p1', - user_id='u1') - - def test_commit(self): - self.assertIsNone(self.driver.commit(self.ctxt, 'reservations', - project_id='p1')) - self.driver.db.reservation_commit.assert_called_once_with( - self.ctxt, 'reservations', project_id='p1', user_id='u1') - - def test_commit_project_id_none(self): - self.assertIsNone(self.driver.commit(self.ctxt, 'reservations')) - self.driver.db.reservation_commit.assert_called_once_with( - self.ctxt, 'reservations', project_id='p1', user_id='u1') - - def test_rollback(self): - self.assertIsNone(self.driver.rollback(self.ctxt, 'reservations', - project_id='p1')) - self.driver.db.reservation_rollback.assert_called_once_with( - self.ctxt, 'reservations', project_id='p1', user_id='u1') - - def test_rollback_project_id_none(self): - self.assertIsNone(self.driver.rollback(self.ctxt, 'reservations')) - self.driver.db.reservation_rollback.assert_called_once_with( - self.ctxt, 'reservations', project_id='p1', user_id='u1') - - def test_usage_reset(self): - resource = self.sample_resources['r1'] - self.assertIsNone(self.driver.usage_reset(self.ctxt, [resource])) - self.driver.db.quota_usage_update.assert_called_once_with( - self.ctxt, 'p1', 'u1', resource, in_use=-1) - - def test_usage_reset_quota_usage_not_found(self): - resource = self.sample_resources['r1'] - self.driver.db.quota_usage_update = mock.Mock( - side_effect=quota.QuotaUsageNotFound) - self.assertIsNone(self.driver.usage_reset(self.ctxt, [resource])) - self.driver.db.quota_usage_update.assert_called_once_with( - self.ctxt, 'p1', 'u1', resource, in_use=-1) - - def test_destroy_all_by_project_and_user(self): - self.assertIsNone(self.driver.destroy_all_by_project_and_user( - self.ctxt, 'p1', 'u1')) - method = self.driver.db.quota_destroy_all_by_project_and_user - method.assert_called_once_with(self.ctxt, 'p1', 'u1') - - def test_destroy_all_by_project(self): - self.assertIsNone(self.driver.destroy_all_by_project(self.ctxt, 'p1')) - self.driver.db.quota_destroy_all_by_project.assert_called_once_with( - self.ctxt, 'p1') - - def test_expire(self): - self.assertIsNone(self.driver.expire(self.ctxt)) - self.driver.db.reservation_expire.assert_called_once_with(self.ctxt) - - -class BaseResourceTestCase(test_base.BaseTestCase): - - def setUp(self): - self.ctxt = FakeContext() - self.dbapi = mock.Mock() - self.dbapi.quota_get = mock.Mock(return_value='quota_get') - self.dbapi.quota_class_get = mock.Mock( - return_value='quota_class_get') - self.dbapi.quota_class_get_default = mock.Mock( - return_value={'r1': 1}) - self.driver = quota.DbQuotaDriver(self.dbapi) - super(BaseResourceTestCase, self).setUp() - - def test_quota(self): - resource = quota.BaseResource('r1') - self.assertEqual('quota_get', resource.quota(self.driver, self.ctxt)) - - def test_quota_no_project_id(self): - self.ctxt.project_id = None - resource = quota.BaseResource('r1') - self.assertEqual('quota_class_get', - resource.quota(self.driver, self.ctxt)) - - def test_quota_project_quota_not_found(self): - self.dbapi.quota_get = mock.Mock( - side_effect=quota.ProjectQuotaNotFound()) - resource = quota.BaseResource('r1') - self.assertEqual('quota_class_get', - resource.quota(self.driver, self.ctxt)) - - def test_quota_quota_class_not_found(self): - self.dbapi.quota_get = mock.Mock( - side_effect=quota.ProjectQuotaNotFound(project_id='p1')) - self.dbapi.quota_class_get = mock.Mock( - side_effect=quota.QuotaClassNotFound(class_name='ClassName')) - resource = quota.BaseResource('r1') - self.assertEqual(1, resource.quota(self.driver, self.ctxt)) - - -class CountableResourceTestCase(test_base.BaseTestCase): - - def test_init(self): - resource = quota.CountableResource('r1', 42) - self.assertEqual('r1', resource.name) - self.assertEqual(42, resource.count) - - -class QuotaEngineTestCase(test_base.BaseTestCase): - - def setUp(self): - self.ctxt = FakeContext() - self.dbapi = mock.Mock() - self.quota_driver = mock.Mock() - self.engine = quota.QuotaEngine(self.dbapi, self.quota_driver) - self.r1 = quota.BaseResource('r1') - self.r2 = quota.BaseResource('r2') - self.engine.register_resources([self.r1, self.r2]) - super(QuotaEngineTestCase, self).setUp() - - def assertProxyMethod(self, method, *args, **kwargs): - if 'retval' in kwargs: - retval = kwargs.pop('retval') - else: - retval = method - setattr(self.quota_driver, method, mock.Mock(return_value=method)) - actual = getattr(self.engine, method)(self.ctxt, *args, **kwargs) - getattr(self.quota_driver, method).assert_called_once_with(self.ctxt, - *args, - **kwargs) - self.assertEqual(actual, retval) - - def assertMethod(self, method, args, kwargs, called_args, - called_kwargs, retval): - setattr(self.quota_driver, method, mock.Mock(return_value=method)) - actual = getattr(self.engine, method)(self.ctxt, *args, **kwargs) - getattr(self.quota_driver, method).assert_called_once_with( - self.ctxt, *called_args, **called_kwargs) - self.assertEqual(actual, retval) - - def test_proxy_methods(self): - self.assertProxyMethod('get_by_project', 'p1', 'resname') - self.assertProxyMethod('get_by_project_and_user', 'p1', 'u1', 'res') - self.assertProxyMethod('get_by_class', 'quota_class', 'resname') - self.assertProxyMethod('get_default', 'resource') - self.assertProxyMethod('expire', retval=None) - self.assertProxyMethod('usage_reset', 'resources', retval=None) - self.assertProxyMethod('destroy_all_by_project', 'p1', retval=None) - self.assertProxyMethod('destroy_all_by_project_and_user', 'p1', - 'u1', retval=None) - self.assertProxyMethod('commit', 'reservations', project_id='p1', - user_id='u1', retval=None) - self.assertProxyMethod('rollback', 'reservations', project_id='p1', - user_id=None, retval=None) - - self.assertMethod('get_settable_quotas', ['p1'], {'user_id': 'u1'}, - [self.engine.resources, 'p1'], {'user_id': 'u1'}, - 'get_settable_quotas') - self.assertMethod('get_defaults', [], {}, - [self.engine.resources], {}, 'get_defaults') - self.assertMethod('get_project_quotas', ['p1', 'quotaclass'], - {'defaults': 'defaults', 'usages': 'usages'}, - [self.engine.resources, 'p1'], - {'quota_class': 'quotaclass', 'defaults': 'defaults', - 'usages': 'usages', 'remains': False}, - 'get_project_quotas') - self.assertMethod('reserve', [], - {'expire': 'expire', 'project_id': 'p1', - 'user_id': 'u1', 'deltas': 'd1'}, - [self.engine.resources, {'deltas': 'd1'}], - {'expire': 'expire', - 'project_id': 'p1', 'user_id': 'u1'}, 'reserve') - self.assertMethod('get_class_quotas', - ['quota_class'], {'defaults': 'defaults'}, - [self.engine.resources, 'quota_class'], - {'defaults': 'defaults'}, 'get_class_quotas') - self.assertMethod('get_user_quotas', ['project_id', 'user_id'], - {'quota_class': 'qc', 'defaults': 'de', - 'usages': 'us'}, - [self.engine.resources, 'project_id', 'user_id'], - {'quota_class': 'qc', 'defaults': 'de', - 'usages': 'us'}, - 'get_user_quotas') - self.assertMethod('limit_check', - [], {'project_id': 'p1', 'user_id': 'u1', - 'val1': 'val1'}, - [self.engine.resources, {'val1': 'val1'}], - {'project_id': 'p1', 'user_id': 'u1'}, 'limit_check') - - def test_resource_names(self): - self.assertEqual(['r1', 'r2'], self.engine.resource_names) - - def test_contains(self): - self.assertTrue(self.r1.name in self.engine) - self.assertTrue(self.r2.name in self.engine) - self.assertFalse('r3' in self.engine) - - def test_count(self): - count = mock.Mock(return_value=42) - r = quota.CountableResource('r1', count) - self.engine.register_resource(r) - actual = self.engine.count(self.ctxt, 'r1') - self.assertEqual(42, actual) - count.assert_called_once_with(self.ctxt) - self.assertRaises(quota.QuotaResourceUnknown, - self.engine.count, self.ctxt, 'r2') - - def test_init(self): - engine = quota.QuotaEngine(self.dbapi) - self.assertIsInstance(engine._driver, quota.DbQuotaDriver) -- cgit v1.2.1