summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavanum Srinivas <davanum@gmail.com>2015-06-01 17:35:18 -0400
committerIan Cordasco <graffatcolmingov@gmail.com>2015-07-15 15:44:27 -0500
commit8640ca9348d87bfe73ac513df5498df19e2e0605 (patch)
treeebd90490f20b6990df12a5ddaec6b68d3f8de0b6
parente1fc0a4dd6829d9634c160c174ee19760b20eca1 (diff)
downloadoslo-incubator-8640ca9348d87bfe73ac513df5498df19e2e0605.tar.gz
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 <graffatcolmingov@gmail.com> Change-Id: I523f95b9e1d981ef4ad688c4732f928913388a49
-rw-r--r--MAINTAINERS6
-rw-r--r--openstack/common/quota.py1211
-rw-r--r--tests/unit/test_quota.py456
3 files changed, 0 insertions, 1673 deletions
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 <sskripnick@mirantis.com>
-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)