Remove old-style quotas code

In Pike, we re-architected quotas to count resources for quota usage
instead of tracking it separately and stopped using reservations.
During an upgrade to Queens, old computes (Pike) will be using
new-style quotas and so the old-style quotas code is no longer needed.

Change-Id: Ie01ab1c3a1219f1d123f0ecedc66a00dfb2eb2c1
This commit is contained in:
melanie witt 2017-10-13 01:45:13 +00:00
parent 16a968363d
commit c8daf8b1d8
7 changed files with 21 additions and 1814 deletions

View File

@ -1198,74 +1198,6 @@ def quota_class_update(context, class_name, resource, limit):
###################
def quota_usage_get(context, project_id, resource, user_id=None):
"""Retrieve a quota usage or raise if it does not exist."""
return IMPL.quota_usage_get(context, project_id, resource, user_id=user_id)
def quota_usage_get_all_by_project_and_user(context, project_id, user_id):
"""Retrieve all usage associated with a given resource."""
return IMPL.quota_usage_get_all_by_project_and_user(context,
project_id, user_id)
def quota_usage_get_all_by_project(context, project_id):
"""Retrieve all usage associated with a given resource."""
return IMPL.quota_usage_get_all_by_project(context, project_id)
def quota_usage_update(context, project_id, user_id, resource, **kwargs):
"""Update a quota usage or raise if it does not exist."""
return IMPL.quota_usage_update(context, project_id, user_id, resource,
**kwargs)
def quota_usage_refresh(context, resources, keys, until_refresh, max_age,
project_id=None, user_id=None):
"""Refresh the quota usages.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param keys: Names of the resources whose usage is to be refreshed.
:param until_refresh: The until_refresh configuration value.
:param max_age: The max_age configuration value.
:param project_id: (Optional) The project_id containing the usages
to be refreshed. Defaults to the project_id
in the context.
:param user_id: (Optional) The user_id containing the usages
to be refreshed. Defaults to the user_id
in the context.
"""
return IMPL.quota_usage_refresh(context, resources, keys, until_refresh,
max_age, project_id=project_id, user_id=user_id)
###################
def quota_reserve(context, resources, quotas, user_quotas, deltas, expire,
until_refresh, max_age, project_id=None, user_id=None):
"""Check quotas and create appropriate reservations."""
return IMPL.quota_reserve(context, resources, quotas, user_quotas, deltas,
expire, until_refresh, max_age,
project_id=project_id, user_id=user_id)
def reservation_commit(context, reservations, project_id=None, user_id=None):
"""Commit quota reservations."""
return IMPL.reservation_commit(context, reservations,
project_id=project_id,
user_id=user_id)
def reservation_rollback(context, reservations, project_id=None, user_id=None):
"""Roll back quota reservations."""
return IMPL.reservation_rollback(context, reservations,
project_id=project_id,
user_id=user_id)
def quota_destroy_all_by_project_and_user(context, project_id, user_id):
"""Destroy all quotas associated with a given project and user."""
return IMPL.quota_destroy_all_by_project_and_user(context,

View File

@ -352,37 +352,6 @@ def convert_objects_related_datetimes(values, *datetime_keys):
return values
def _sync_instances(context, project_id, user_id):
return dict(zip(('instances', 'cores', 'ram'),
_instance_data_get_for_user(context, project_id, user_id)))
def _sync_floating_ips(context, project_id, user_id):
return dict(floating_ips=_floating_ip_count_by_project(
context, project_id))
def _sync_fixed_ips(context, project_id, user_id):
return dict(fixed_ips=_fixed_ip_count_by_project(context, project_id))
def _sync_security_groups(context, project_id, user_id):
return dict(security_groups=_security_group_count_by_project_and_user(
context, project_id, user_id))
def _sync_server_groups(context, project_id, user_id):
return dict(server_groups=_instance_group_count_by_project_and_user(
context, project_id, user_id))
QUOTA_SYNC_FUNCTIONS = {
'_sync_instances': _sync_instances,
'_sync_floating_ips': _sync_floating_ips,
'_sync_fixed_ips': _sync_fixed_ips,
'_sync_security_groups': _sync_security_groups,
'_sync_server_groups': _sync_server_groups,
}
###################
@ -3595,481 +3564,6 @@ def quota_class_update(context, class_name, resource, limit):
###################
@require_context
@pick_context_manager_reader
def quota_usage_get(context, project_id, resource, user_id=None):
query = model_query(context, models.QuotaUsage, read_deleted="no").\
filter_by(project_id=project_id).\
filter_by(resource=resource)
if user_id:
if resource not in PER_PROJECT_QUOTAS:
result = query.filter_by(user_id=user_id).first()
else:
result = query.filter_by(user_id=None).first()
else:
result = query.first()
if not result:
raise exception.QuotaUsageNotFound(project_id=project_id)
return result
def _quota_usage_get_all(context, project_id, user_id=None):
query = model_query(context, models.QuotaUsage, read_deleted="no").\
filter_by(project_id=project_id)
result = {'project_id': project_id}
if user_id:
query = query.filter(or_(models.QuotaUsage.user_id == user_id,
models.QuotaUsage.user_id == null()))
result['user_id'] = user_id
rows = query.all()
for row in rows:
if row.resource in result:
result[row.resource]['in_use'] += row.in_use
result[row.resource]['reserved'] += row.reserved
else:
result[row.resource] = dict(in_use=row.in_use,
reserved=row.reserved)
return result
@require_context
@pick_context_manager_reader
def quota_usage_get_all_by_project_and_user(context, project_id, user_id):
return _quota_usage_get_all(context, project_id, user_id=user_id)
@require_context
@pick_context_manager_reader
def quota_usage_get_all_by_project(context, project_id):
return _quota_usage_get_all(context, project_id)
def _quota_usage_create(project_id, user_id, resource, in_use,
reserved, until_refresh, session):
quota_usage_ref = models.QuotaUsage()
quota_usage_ref.project_id = project_id
quota_usage_ref.user_id = user_id
quota_usage_ref.resource = resource
quota_usage_ref.in_use = in_use
quota_usage_ref.reserved = reserved
quota_usage_ref.until_refresh = until_refresh
# updated_at is needed for judgement of max_age
quota_usage_ref.updated_at = timeutils.utcnow()
quota_usage_ref.save(session)
return quota_usage_ref
@pick_context_manager_writer
def quota_usage_update(context, project_id, user_id, resource, **kwargs):
updates = {}
for key in ['in_use', 'reserved', 'until_refresh']:
if key in kwargs:
updates[key] = kwargs[key]
result = model_query(context, models.QuotaUsage, read_deleted="no").\
filter_by(project_id=project_id).\
filter_by(resource=resource).\
filter(or_(models.QuotaUsage.user_id == user_id,
models.QuotaUsage.user_id == null())).\
update(updates)
if not result:
raise exception.QuotaUsageNotFound(project_id=project_id)
###################
def _reservation_create(uuid, usage, project_id, user_id, resource,
delta, expire, session):
reservation_ref = models.Reservation()
reservation_ref.uuid = uuid
reservation_ref.usage_id = usage['id']
reservation_ref.project_id = project_id
reservation_ref.user_id = user_id
reservation_ref.resource = resource
reservation_ref.delta = delta
reservation_ref.expire = expire
reservation_ref.save(session)
return reservation_ref
###################
# NOTE(johannes): The quota code uses SQL locking to ensure races don't
# cause under or over counting of resources. To avoid deadlocks, this
# code always acquires the lock on quota_usages before acquiring the lock
# on reservations.
def _get_project_user_quota_usages(context, project_id, user_id):
rows = model_query(context, models.QuotaUsage,
read_deleted="no").\
filter_by(project_id=project_id).\
order_by(models.QuotaUsage.id.asc()).\
with_lockmode('update').\
all()
proj_result = dict()
user_result = dict()
# Get the total count of in_use,reserved
for row in rows:
proj_result.setdefault(row.resource,
dict(in_use=0, reserved=0, total=0))
proj_result[row.resource]['in_use'] += row.in_use
proj_result[row.resource]['reserved'] += row.reserved
proj_result[row.resource]['total'] += (row.in_use + row.reserved)
if row.user_id is None or row.user_id == user_id:
user_result[row.resource] = row
return proj_result, user_result
def _create_quota_usage_if_missing(user_usages, resource, until_refresh,
project_id, user_id, session):
"""Creates a QuotaUsage record and adds to user_usages if not present.
:param user_usages: dict of resource keys to QuotaUsage records. This is
updated if resource is not in user_usages yet or
until_refresh is not None.
:param resource: The resource being checked for quota usage.
:param until_refresh: Count of reservations until usage is refreshed,
int or None
:param project_id: The project being checked for quota usage.
:param user_id: The user being checked for quota usage.
:param session: DB session holding a transaction lock.
:return: True if a new QuotaUsage record was created and added
to user_usages, False otherwise.
"""
new_usage = None
if resource not in user_usages:
user_id_to_use = user_id
if resource in PER_PROJECT_QUOTAS:
user_id_to_use = None
new_usage = _quota_usage_create(project_id, user_id_to_use, resource,
0, 0, until_refresh or None, session)
user_usages[resource] = new_usage
return new_usage is not None
def _is_quota_refresh_needed(quota_usage, max_age):
"""Determines if a quota usage refresh is needed.
:param quota_usage: A QuotaUsage object for a given resource.
:param max_age: Number of seconds between subsequent usage refreshes.
:return: True if a refresh is needed, False otherwise.
"""
refresh = False
if quota_usage.in_use < 0:
# Negative in_use count indicates a desync, so try to
# heal from that...
LOG.debug('in_use has dropped below 0; forcing refresh for '
'QuotaUsage: %s', dict(quota_usage))
refresh = True
elif quota_usage.until_refresh is not None:
quota_usage.until_refresh -= 1
if quota_usage.until_refresh <= 0:
refresh = True
elif max_age and (timeutils.utcnow() -
quota_usage.updated_at).seconds >= max_age:
refresh = True
return refresh
def _refresh_quota_usages(quota_usage, until_refresh, in_use):
"""Refreshes quota usage for the given resource.
:param quota_usage: A QuotaUsage object for a given resource.
:param until_refresh: Count of reservations until usage is refreshed,
int or None
:param in_use: Actual quota usage for the resource.
"""
if quota_usage.in_use != in_use:
LOG.info('quota_usages out of sync, updating. '
'project_id: %(project_id)s, '
'user_id: %(user_id)s, '
'resource: %(res)s, '
'tracked usage: %(tracked_use)s, '
'actual usage: %(in_use)s',
{'project_id': quota_usage.project_id,
'user_id': quota_usage.user_id,
'res': quota_usage.resource,
'tracked_use': quota_usage.in_use,
'in_use': in_use})
else:
LOG.debug('QuotaUsage has not changed, refresh is unnecessary for: %s',
dict(quota_usage))
# Update the usage
quota_usage.in_use = in_use
quota_usage.until_refresh = until_refresh or None
def _refresh_quota_usages_if_needed(user_usages, context, resources, keys,
project_id, user_id, until_refresh,
max_age, force_refresh=False):
elevated = context.elevated()
# Handle usage refresh
work = set(keys)
while work:
resource = work.pop()
# Do we need to refresh the usage?
created = _create_quota_usage_if_missing(user_usages, resource,
until_refresh, project_id,
user_id, context.session)
refresh = force_refresh
if not refresh:
refresh = created or \
_is_quota_refresh_needed(user_usages[resource], max_age)
# OK, refresh the usage
if refresh:
# Grab the sync routine
sync = QUOTA_SYNC_FUNCTIONS[resources[resource].sync]
updates = sync(elevated, project_id, user_id)
for res, in_use in updates.items():
# Make sure we have a destination for the usage!
_create_quota_usage_if_missing(user_usages, res,
until_refresh, project_id,
user_id, context.session)
_refresh_quota_usages(user_usages[res], until_refresh,
in_use)
# Because more than one resource may be refreshed
# by the call to the sync routine, and we don't
# want to double-sync, we make sure all refreshed
# resources are dropped from the work set.
work.discard(res)
# NOTE(Vek): We make the assumption that the sync
# routine actually refreshes the
# resources that it is the sync routine
# for. We don't check, because this is
# a best-effort mechanism.
def _calculate_overquota(project_quotas, user_quotas, deltas,
project_usages, user_usages):
"""Checks if any resources will go over quota based on the request.
:param project_quotas: dict of resource quotas (limits) for the project.
:param user_quotas: dict of resource quotas (limits) for the user.
:param deltas: dict of resource keys to positive/negative quota
changes for the resources in a given operation.
:param project_usages: dict of resource keys to QuotaUsage records for the
project.
:param user_usages: dict of resource keys to QuotaUsage records for the
user.
:return: list of resources that are over-quota for the
operation.
"""
overs = []
for res, delta in deltas.items():
# We can't go over-quota if we're not reserving anything.
if delta >= 0:
# We can't go over-quota if we have unlimited quotas.
# over if the project usage + delta is more than project quota
if 0 <= project_quotas[res] < delta + project_usages[res]['total']:
LOG.debug('Request is over project quota for resource '
'"%(res)s". Project limit: %(limit)s, delta: '
'%(delta)s, current total project usage: %(total)s',
{'res': res, 'limit': project_quotas[res],
'delta': delta,
'total': project_usages[res]['total']})
overs.append(res)
# We can't go over-quota if we have unlimited quotas.
# over if the user usage + delta is more than user quota
elif 0 <= user_quotas[res] < delta + user_usages[res]['total']:
LOG.debug('Request is over user quota for resource '
'"%(res)s". User limit: %(limit)s, delta: '
'%(delta)s, current total user usage: %(total)s',
{'res': res, 'limit': user_quotas[res],
'delta': delta, 'total': user_usages[res]['total']})
overs.append(res)
return overs
@require_context
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
@pick_context_manager_writer
def quota_usage_refresh(context, resources, keys, until_refresh, max_age,
project_id=None, user_id=None):
if project_id is None:
project_id = context.project_id
if user_id is None:
user_id = context.user_id
# Get the current usages
project_usages, user_usages = _get_project_user_quota_usages(
context, project_id, user_id)
# Force refresh of the usages
_refresh_quota_usages_if_needed(user_usages, context, resources, keys,
project_id, user_id, until_refresh,
max_age, force_refresh=True)
@require_context
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
@pick_context_manager_writer
def quota_reserve(context, resources, project_quotas, user_quotas, deltas,
expire, until_refresh, max_age, project_id=None,
user_id=None):
if project_id is None:
project_id = context.project_id
if user_id is None:
user_id = context.user_id
# Get the current usages
project_usages, user_usages = _get_project_user_quota_usages(
context, project_id, user_id)
_refresh_quota_usages_if_needed(user_usages, context, resources,
deltas.keys(), project_id, user_id,
until_refresh, max_age)
# Check for deltas that would go negative
unders = [res for res, delta in deltas.items()
if delta < 0 and
delta + user_usages[res].in_use < 0]
# Now, let's check the quotas
# NOTE(Vek): We're only concerned about positive increments.
# If a project has gone over quota, we want them to
# be able to reduce their usage without any
# problems.
for key, value in user_usages.items():
if key not in project_usages:
LOG.debug('Copying QuotaUsage for resource "%(key)s" from '
'user_usages into project_usages: %(value)s',
{'key': key, 'value': dict(value)})
project_usages[key] = value
overs = _calculate_overquota(project_quotas, user_quotas, deltas,
project_usages, user_usages)
# NOTE(Vek): The quota check needs to be in the transaction,
# but the transaction doesn't fail just because
# we're over quota, so the OverQuota raise is
# outside the transaction. If we did the raise
# here, our usage updates would be discarded, but
# they're not invalidated by being over-quota.
# Create the reservations
if not overs:
reservations = []
for res, delta in deltas.items():
reservation = _reservation_create(
uuidutils.generate_uuid(),
user_usages[res],
project_id,
user_id,
res, delta, expire,
context.session)
reservations.append(reservation.uuid)
# Also update the reserved quantity
# NOTE(Vek): Again, we are only concerned here about
# positive increments. Here, though, we're
# worried about the following scenario:
#
# 1) User initiates resize down.
# 2) User allocates a new instance.
# 3) Resize down fails or is reverted.
# 4) User is now over quota.
#
# To prevent this, we only update the
# reserved value if the delta is positive.
if delta > 0:
user_usages[res].reserved += delta
# Apply updates to the usages table
for usage_ref in user_usages.values():
context.session.add(usage_ref)
if unders:
LOG.warning("Change will make usage less than 0 for the following "
"resources: %s", unders)
if overs:
if project_quotas == user_quotas:
usages = project_usages
else:
# NOTE(mriedem): user_usages is a dict of resource keys to
# QuotaUsage sqlalchemy dict-like objects and doesn't log well
# so convert the user_usages values to something useful for
# logging. Remove this if we ever change how
# _get_project_user_quota_usages returns the user_usages values.
user_usages = {k: dict(in_use=v['in_use'], reserved=v['reserved'],
total=v['total'])
for k, v in user_usages.items()}
usages = user_usages
usages = {k: dict(in_use=v['in_use'], reserved=v['reserved'])
for k, v in usages.items()}
LOG.debug('Raise OverQuota exception because: '
'project_quotas: %(project_quotas)s, '
'user_quotas: %(user_quotas)s, deltas: %(deltas)s, '
'overs: %(overs)s, project_usages: %(project_usages)s, '
'user_usages: %(user_usages)s',
{'project_quotas': project_quotas,
'user_quotas': user_quotas,
'overs': overs, 'deltas': deltas,
'project_usages': project_usages,
'user_usages': user_usages})
raise exception.OverQuota(overs=sorted(overs), quotas=user_quotas,
usages=usages)
return reservations
def _quota_reservations_query(context, reservations):
"""Return the relevant reservations."""
# Get the listed reservations
return model_query(context, models.Reservation, read_deleted="no").\
filter(models.Reservation.uuid.in_(reservations)).\
with_lockmode('update')
@require_context
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
@pick_context_manager_writer
def reservation_commit(context, reservations, project_id=None, user_id=None):
_project_usages, user_usages = _get_project_user_quota_usages(
context, project_id, user_id)
reservation_query = _quota_reservations_query(context, reservations)
for reservation in reservation_query.all():
usage = user_usages[reservation.resource]
if reservation.delta >= 0:
usage.reserved -= reservation.delta
usage.in_use += reservation.delta
reservation_query.soft_delete(synchronize_session=False)
@require_context
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
@pick_context_manager_writer
def reservation_rollback(context, reservations, project_id=None, user_id=None):
_project_usages, user_usages = _get_project_user_quota_usages(
context, project_id, user_id)
reservation_query = _quota_reservations_query(context, reservations)
for reservation in reservation_query.all():
usage = user_usages[reservation.resource]
if reservation.delta >= 0:
usage.reserved -= reservation.delta
reservation_query.soft_delete(synchronize_session=False)
@pick_context_manager_writer
def quota_destroy_all_by_project_and_user(context, project_id, user_id):
model_query(context, models.ProjectUserQuota, read_deleted="no").\
@ -4553,20 +4047,6 @@ def _security_group_ensure_default(context):
'user_id': context.user_id,
'project_id': context.project_id}
default_group = security_group_create(context, values)
usage = model_query(context, models.QuotaUsage, read_deleted="no").\
filter_by(project_id=context.project_id).\
filter_by(user_id=context.user_id).\
filter_by(resource='security_groups')
# Create quota usage for auto created default security group
if not usage.first():
_quota_usage_create(context.project_id,
context.user_id,
'security_groups',
1, 0,
CONF.quota.until_refresh,
context.session)
else:
usage.update({'in_use': int(usage.first().in_use) + 1})
default_rules = _security_group_rule_get_default_query(context).all()
for default_rule in default_rules:

View File

@ -63,6 +63,7 @@ class Quotas(base.NovaObject):
VERSION = '1.3'
fields = {
# TODO(melwitt): Remove this field in version 2.0 of the object.
'reservations': fields.ListOfStringsField(nullable=True),
'project_id': fields.StringField(nullable=True),
'user_id': fields.StringField(nullable=True),
@ -249,54 +250,27 @@ class Quotas(base.NovaObject):
if not result:
raise exception.QuotaClassNotFound(class_name=class_name)
@classmethod
def from_reservations(cls, context, reservations, instance=None):
"""Transitional for compatibility."""
if instance is None:
project_id = None
user_id = None
else:
project_id, user_id = ids_from_instance(context, instance)
quotas = cls()
quotas._context = context
quotas.reservations = reservations
quotas.project_id = project_id
quotas.user_id = user_id
quotas.obj_reset_changes()
return quotas
# TODO(melwitt): Remove this method in version 2.0 of the object.
@base.remotable
def reserve(self, expire=None, project_id=None, user_id=None,
**deltas):
reservations = quota.QUOTAS.reserve(self._context, expire=expire,
project_id=project_id,
user_id=user_id,
**deltas)
self.reservations = reservations
# Honor the expected attributes even though we're not reserving
# anything anymore. This will protect against things exploding if a
# someone has an Ocata compute host running by accident, for example.
self.reservations = None
self.project_id = project_id
self.user_id = user_id
self.obj_reset_changes()
# TODO(melwitt): Remove this method in version 2.0 of the object.
@base.remotable
def commit(self):
if not self.reservations:
return
quota.QUOTAS.commit(self._context, self.reservations,
project_id=self.project_id,
user_id=self.user_id)
self.reservations = None
self.obj_reset_changes()
pass
# TODO(melwitt): Remove this method in version 2.0 of the object.
@base.remotable
def rollback(self):
"""Rollback quotas."""
if not self.reservations:
return
quota.QUOTAS.rollback(self._context, self.reservations,
project_id=self.project_id,
user_id=self.user_id)
self.reservations = None
self.obj_reset_changes()
pass
@base.remotable_classmethod
def limit_check(cls, context, project_id=None, user_id=None, **values):
@ -504,13 +478,16 @@ class Quotas(base.NovaObject):
@base.NovaObjectRegistry.register
class QuotasNoOp(Quotas):
# TODO(melwitt): Remove this method in version 2.0 of the object.
def reserve(context, expire=None, project_id=None, user_id=None,
**deltas):
pass
# TODO(melwitt): Remove this method in version 2.0 of the object.
def commit(self, context=None):
pass
# TODO(melwitt): Remove this method in version 2.0 of the object.
def rollback(self, context=None):
pass

View File

@ -17,18 +17,15 @@
"""Quotas for resources per project."""
import copy
import datetime
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_utils import timeutils
import six
import nova.conf
from nova import context as nova_context
from nova import db
from nova import exception
from nova.i18n import _LE
from nova import objects
from nova import utils
@ -180,10 +177,8 @@ class DbQuotaDriver(object):
"""
usages = {}
for resource in resources.values():
# NOTE(melwitt): This is to keep things working while we're in the
# middle of converting ReservableResources to CountableResources.
# We should skip resources that are not countable and eventually
# when there are no more ReservableResources, we won't need this.
# NOTE(melwitt): We should skip resources that are not countable,
# such as AbsoluteResources.
if not isinstance(resource, CountableResource):
continue
if resource.name in usages:
@ -373,33 +368,6 @@ class DbQuotaDriver(object):
settable_quotas[key] = {'minimum': minimum, 'maximum': -1}
return settable_quotas
def _get_syncable_resources(self, resources, user_id=None):
"""Given a list of resources, retrieve the syncable resources
scoped to a project or a user.
A resource is syncable if it has a function to sync the quota
usage record with the actual usage of the project or user.
:param resources: A dictionary of the registered resources.
:param user_id: Optional. If user_id is specified, user-scoped
resources will be returned. Otherwise,
project-scoped resources will be returned.
:returns: A list of resource names scoped to a project or
user that can be sync'd.
"""
syncable_resources = []
per_project_resources = db.quota_get_per_project_resources()
for key, value in resources.items():
if isinstance(value, ReservableResource):
# Resources are either project-scoped or user-scoped
project_scoped = (user_id is None and
key in per_project_resources)
user_scoped = (user_id is not None and
key not in per_project_resources)
if project_scoped or user_scoped:
syncable_resources.append(key)
return syncable_resources
def _get_quotas(self, context, resources, keys, project_id=None,
user_id=None, project_quotas=None):
"""A helper method which retrieves the quotas for the specific
@ -662,227 +630,6 @@ class DbQuotaDriver(object):
quotas=quotas_exceeded, usages={},
headroom=headroom)
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.
"""
_valid_method_call_check_resources(deltas, 'reserve', resources)
# Set up the reservation expiration
if expire is None:
expire = CONF.quota.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 exception.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
LOG.debug('Reserving resources using context.project_id: %s',
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
LOG.debug('Reserving resources using context.user_id: %s',
user_id)
LOG.debug('Attempting to reserve resources for project %(project_id)s '
'and user %(user_id)s. Deltas: %(deltas)s',
{'project_id': project_id, 'user_id': user_id,
'deltas': deltas})
# 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 = objects.Quotas.get_all_by_project(context, project_id)
LOG.debug('Quota limits for project %(project_id)s: '
'%(project_quotas)s', {'project_id': project_id,
'project_quotas': project_quotas})
quotas = self._get_quotas(context, resources, deltas.keys(),
project_id=project_id,
project_quotas=project_quotas)
LOG.debug('Quotas for project %(project_id)s after resource sync: '
'%(quotas)s', {'project_id': project_id, 'quotas': quotas})
user_quotas = self._get_quotas(context, resources, deltas.keys(),
project_id=project_id,
user_id=user_id,
project_quotas=project_quotas)
LOG.debug('Quotas for project %(project_id)s and user %(user_id)s '
'after resource sync: %(quotas)s',
{'project_id': project_id, 'user_id': user_id,
'quotas': user_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 db.quota_reserve(context, resources, quotas, user_quotas,
deltas, expire,
CONF.quota.until_refresh, CONF.quota.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
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
db.reservation_rollback(context, reservations, project_id=project_id,
user_id=user_id)
def usage_reset(self, context, resources):
"""Reset the usage records 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
db.quota_usage_update(elevated, context.project_id,
context.user_id,
resource, in_use=-1)
except exception.QuotaUsageNotFound:
# That means it'll be refreshed anyway
pass
def usage_refresh(self, context, resources, project_id=None,
user_id=None, resource_names=None):
"""Refresh the usage records for a particular project and user
on a list of resources. This will force usage records to be
sync'd immediately to the actual usage.
This method will raise a QuotaUsageRefreshNotAllowed exception if a
usage refresh is not allowed on a resource for the given project
or user.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param project_id: Optional: Project whose resources to
refresh. If not set, then the project_id
is taken from the context.
:param user_id: Optional: User whose resources to refresh.
If not set, then the user_id is taken from the
context.
:param resources_names: Optional: A list of the resource names
for which the usage must be refreshed.
If not specified, then all the usages
for the project and user will be refreshed.
"""
if project_id is None:
project_id = context.project_id
if user_id is None:
user_id = context.user_id
syncable_resources = self._get_syncable_resources(resources, user_id)
if resource_names:
for res_name in resource_names:
if res_name not in syncable_resources:
raise exception.QuotaUsageRefreshNotAllowed(
resource=res_name,
project_id=project_id,
user_id=user_id,
syncable=syncable_resources)
else:
resource_names = syncable_resources
return db.quota_usage_refresh(context, resources, resource_names,
CONF.quota.until_refresh,
CONF.quota.max_age,
project_id=project_id, user_id=user_id)
def destroy_all_by_project_and_user(self, context, project_id, user_id):
"""Destroy all quotas associated with a project and user.
@ -1092,117 +839,6 @@ class NoopQuotaDriver(object):
"""
pass
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.
"""
return []
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.
"""
pass
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.
"""
pass
def usage_reset(self, context, resources):
"""Reset the usage records 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.
"""
pass
def usage_refresh(self, context, resources, project_id=None, user_id=None,
resource_names=None):
"""Refresh the usage records for a particular project and user
on a list of resources. This will force usage records to be
sync'd immediately to the actual usage.
This method will raise a QuotaUsageRefreshNotAllowed exception if a
usage refresh is not allowed on a resource for the given project
or user.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param project_id: Optional: Project whose resources to
refresh. If not set, then the project_id
is taken from the context.
:param user_id: Optional: User whose resources to refresh.
If not set, then the user_id is taken from the
context.
:param resources_names: Optional: A list of the resource names
for which the usage must be refreshed.
If not specified, then all the usages
for the project and user will be refreshed.
"""
pass
def destroy_all_by_project_and_user(self, context, project_id, user_id):
"""Destroy all quotas associated with a project and user.
@ -1294,44 +930,6 @@ class BaseResource(object):
return CONF.quota[self.flag] if self.flag else -1
class ReservableResource(BaseResource):
"""Describe a reservable resource."""
valid_method = 'reserve'
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.
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 resource that does not correspond to database objects."""
valid_method = 'check'
@ -1617,141 +1215,6 @@ class QuotaEngine(object):
context, self._resources, project_values=project_values,
user_values=user_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 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 usage_refresh(self, context, project_id=None, user_id=None,
resource_names=None):
"""Refresh the usage records for a particular project and user
on a list of resources. This will force usage records to be
sync'd immediately to the actual usage.
This method will raise a QuotaUsageRefreshNotAllowed exception if a
usage refresh is not allowed on a resource for the given project
or user.
:param context: The request context, for access checks.
:param project_id: Optional: Project whose resources to
refresh. If not set, then the project_id
is taken from the context.
:param user_id: Optional: User whose resources to refresh.
If not set, then the user_id is taken from the
context.
:param resources_names: Optional: A list of the resource names
for which the usage must be refreshed.
If not specified, then all the usages
for the project and user will be refreshed.
"""
self._driver.usage_refresh(context, self._resources, project_id,
user_id, resource_names)
def destroy_all_by_project_and_user(self, context, project_id, user_id):
"""Destroy all quotas, usages, and reservations associated with a
project and user.

View File

@ -61,7 +61,6 @@ from nova.db.sqlalchemy import utils as db_utils
from nova import exception
from nova import objects
from nova.objects import fields
from nova import quota
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import fake_console_auth_token
@ -116,23 +115,10 @@ def _make_compute_node(host, node, hv_type, service_id):
return compute_node_dict
def _quota_reserve(context, project_id, user_id):
"""Create sample Quota, QuotaUsage and Reservation objects.
There is no method db.quota_usage_create(), so we have to use
db.quota_reserve() for creating QuotaUsage objects.
Returns reservations uuids.
"""
def get_sync(resource, usage):
def sync(elevated, project_id, user_id):
return {resource: usage}
return sync
def _quota_create(context, project_id, user_id):
"""Create sample Quota objects."""
quotas = {}
user_quotas = {}
resources = {}
deltas = {}
for i in range(3):
resource = 'resource%d' % i
if i == 2:
@ -149,16 +135,6 @@ def _quota_reserve(context, project_id, user_id):
user_quotas[resource] = db.quota_create(context, project_id,
resource, i + 1,
user_id=user_id).hard_limit
sync_name = '_sync_%s' % resource
resources[resource] = quota.ReservableResource(
resource, sync_name, 'quota_res_%d' % i)
deltas[resource] = i
setattr(sqlalchemy_api, sync_name, get_sync(resource, i))
sqlalchemy_api.QUOTA_SYNC_FUNCTIONS[sync_name] = getattr(
sqlalchemy_api, sync_name)
return db.quota_reserve(context, resources, quotas, user_quotas, deltas,
timeutils.utcnow(), CONF.quota.until_refresh,
datetime.timedelta(days=1), project_id, user_id)
class DbTestCase(test.TestCase):
@ -1814,147 +1790,6 @@ class InstanceSystemMetadataTestCase(test.TestCase):
{'key': 'value'}, True)
class RefreshUsageTestCase(test.TestCase):
"""Tests for the db.api.quota_usage_refresh method. """
def setUp(self):
super(RefreshUsageTestCase, self).setUp()
self.ctxt = context.get_admin_context()
self.project_id = 'project1'
self.user_id = 'user1'
def _quota_refresh(self, keys):
"""Refresh the in_use count on the QuotaUsage objects.
The QuotaUsage objects are created if they don't exist.
"""
def get_sync(resource, usage):
def sync(elevated, project_id, user_id):
return {resource: usage}
return sync
resources = {}
for i in range(4):
resource = 'resource%d' % i
if i == 2:
# test for project level resources
resource = 'fixed_ips'
if i == 3:
# test for project level resources
resource = 'floating_ips'
sync_name = '_sync_%s' % resource
resources[resource] = quota.ReservableResource(
resource, sync_name, 'quota_res_%d' % i)
setattr(sqlalchemy_api, sync_name, get_sync(resource, i + 1))
sqlalchemy_api.QUOTA_SYNC_FUNCTIONS[sync_name] = getattr(
sqlalchemy_api, sync_name)
db.quota_usage_refresh(self.ctxt, resources, keys,
until_refresh=3,
max_age=0,
project_id=self.project_id,
user_id=self.user_id)
def _compare_resource_usages(self, keys, expected, project_id,
user_id = None):
for key in keys:
actual = db.quota_usage_get(self.ctxt, project_id, key, user_id)
self.assertEqual(expected['project_id'], actual.project_id)
self.assertEqual(expected['user_id'], actual.user_id)
self.assertEqual(key, actual.resource)
self.assertEqual(expected[key]['in_use'], actual.in_use)
self.assertEqual(expected[key]['reserved'], actual.reserved)
self.assertEqual(expected[key]['until_refresh'],
actual.until_refresh)
def test_refresh_created_project_usages(self):
# The refresh will create the usages and then sync
# in_use from 0 to 3 for fixed_ips and 0 to 4 for floating_ips.
keys = ['fixed_ips', 'floating_ips']
self._quota_refresh(keys)
expected = {'project_id': self.project_id,
# User ID will be none for per-project resources
'user_id': None,
'fixed_ips': {'in_use': 3, 'reserved': 0,
'until_refresh': 3},
'floating_ips': {'in_use': 4, 'reserved': 0,
'until_refresh': 3}}
self._compare_resource_usages(keys, expected, self.project_id,
self.user_id)
def test_refresh_created_user_usages(self):
# The refresh will create the usages and then sync
# in_use from 0 to 1 for resource0 and 0 to 2 for resource1.
keys = ['resource0', 'resource1']
self._quota_refresh(keys)
expected = {'project_id': self.project_id,
'user_id': self.user_id,
'resource0': {'in_use': 1, 'reserved': 0,
'until_refresh': 3},
'resource1': {'in_use': 2, 'reserved': 0,
'until_refresh': 3}}
self._compare_resource_usages(keys, expected, self.project_id,
self.user_id)
class ReservationTestCase(test.TestCase, ModelsObjectComparatorMixin):
"""Tests for db.api.reservation_* methods."""
def setUp(self):
super(ReservationTestCase, self).setUp()
self.ctxt = context.get_admin_context()
self.reservations = _quota_reserve(self.ctxt, 'project1', 'user1')
usage = db.quota_usage_get(self.ctxt, 'project1', 'resource1', 'user1')
self.values = {'uuid': 'sample-uuid',
'project_id': 'project1',
'user_id': 'user1',
'resource': 'resource1',
'delta': 42,
'expire': timeutils.utcnow() + datetime.timedelta(days=1),
'usage': {'id': usage.id}}
def test_reservation_commit(self):
expected = {'project_id': 'project1', 'user_id': 'user1',
'resource0': {'reserved': 0, 'in_use': 0},
'resource1': {'reserved': 1, 'in_use': 1},
'fixed_ips': {'reserved': 2, 'in_use': 2}}
self.assertEqual(expected, db.quota_usage_get_all_by_project_and_user(
self.ctxt, 'project1', 'user1'))
_reservation_get(self.ctxt, self.reservations[0])
db.reservation_commit(self.ctxt, self.reservations, 'project1',
'user1')
self.assertRaises(exception.ReservationNotFound,
_reservation_get, self.ctxt, self.reservations[0])
expected = {'project_id': 'project1', 'user_id': 'user1',
'resource0': {'reserved': 0, 'in_use': 0},
'resource1': {'reserved': 0, 'in_use': 2},
'fixed_ips': {'reserved': 0, 'in_use': 4}}
self.assertEqual(expected, db.quota_usage_get_all_by_project_and_user(
self.ctxt, 'project1', 'user1'))
def test_reservation_rollback(self):
expected = {'project_id': 'project1', 'user_id': 'user1',
'resource0': {'reserved': 0, 'in_use': 0},
'resource1': {'reserved': 1, 'in_use': 1},
'fixed_ips': {'reserved': 2, 'in_use': 2}}
self.assertEqual(expected, db.quota_usage_get_all_by_project_and_user(
self.ctxt, 'project1', 'user1'))
_reservation_get(self.ctxt, self.reservations[0])
db.reservation_rollback(self.ctxt, self.reservations, 'project1',
'user1')
self.assertRaises(exception.ReservationNotFound,
_reservation_get, self.ctxt, self.reservations[0])
expected = {'project_id': 'project1', 'user_id': 'user1',
'resource0': {'reserved': 0, 'in_use': 0},
'resource1': {'reserved': 0, 'in_use': 1},
'fixed_ips': {'reserved': 0, 'in_use': 2}}
self.assertEqual(expected, db.quota_usage_get_all_by_project_and_user(
self.ctxt, 'project1', 'user1'))
class SecurityGroupRuleTestCase(test.TestCase, ModelsObjectComparatorMixin):
def setUp(self):
super(SecurityGroupRuleTestCase, self).setUp()
@ -2286,23 +2121,6 @@ class SecurityGroupTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertEqual(1, len(security_groups))
self.assertEqual("default", security_groups[0]["name"])
usage = db.quota_usage_get(self.ctxt,
self.ctxt.project_id,
'security_groups',
self.ctxt.user_id)
self.assertEqual(1, usage.in_use)
def test_security_group_ensure_default_until_refresh(self):
self.flags(until_refresh=2, group='quota')
self.ctxt.project_id = 'fake'
self.ctxt.user_id = 'fake'
db.security_group_ensure_default(self.ctxt)
usage = db.quota_usage_get(self.ctxt,
self.ctxt.project_id,
'security_groups',
self.ctxt.user_id)
self.assertEqual(2, usage.until_refresh)
@mock.patch.object(db.sqlalchemy.api, '_security_group_get_by_names')
def test_security_group_ensure_default_called_concurrently(self, sg_mock):
# make sure NotFound is always raised here to trick Nova to insert the
@ -7521,151 +7339,23 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertRaises(exception.ProjectQuotaNotFound,
db.quota_get, self.ctxt, 'project1', 'resource1')
def test_quota_reserve_all_resources(self):
quotas = {}
deltas = {}
reservable_resources = {}
for i, resource in enumerate(quota.resources):
if isinstance(resource, quota.ReservableResource):
quotas[resource.name] = db.quota_create(self.ctxt, 'project1',
resource.name,
100).hard_limit
deltas[resource.name] = i
reservable_resources[resource.name] = resource
usages = {'instances': 3, 'cores': 6, 'ram': 9}
instances = []
for i in range(3):
instances.append(db.instance_create(self.ctxt,
{'vcpus': 2, 'memory_mb': 3,
'project_id': 'project1'}))
usages['fixed_ips'] = 2
network = db.network_create_safe(self.ctxt, {})
for i in range(2):
address = '192.168.0.%d' % i
db.fixed_ip_create(self.ctxt, {'project_id': 'project1',
'address': address,
'network_id': network['id']})
db.fixed_ip_associate(self.ctxt, address,
instances[0].uuid, network['id'])
usages['floating_ips'] = 5
for i in range(5):
db.floating_ip_create(self.ctxt, {'project_id': 'project1'})
usages['security_groups'] = 3
for i in range(3):
db.security_group_create(self.ctxt, {'project_id': 'project1'})
usages['server_groups'] = 4
for i in range(4):
db.instance_group_create(self.ctxt, {'uuid': str(i),
'project_id': 'project1'})
reservations_uuids = db.quota_reserve(self.ctxt, reservable_resources,
quotas, quotas, deltas, None,
None, None, 'project1')
resources_names = list(reservable_resources.keys())
for reservation_uuid in reservations_uuids:
reservation = _reservation_get(self.ctxt, reservation_uuid)
usage = db.quota_usage_get(self.ctxt, 'project1',
reservation.resource)
self.assertEqual(usage.in_use, usages[reservation.resource],
'Resource: %s' % reservation.resource)
self.assertEqual(usage.reserved, deltas[reservation.resource])
self.assertIn(reservation.resource, resources_names)
resources_names.remove(reservation.resource)
self.assertEqual(len(resources_names), 0)
def test_quota_destroy_all_by_project(self):
reservations = _quota_reserve(self.ctxt, 'project1', 'user1')
_quota_create(self.ctxt, 'project1', 'user1')
db.quota_destroy_all_by_project(self.ctxt, 'project1')
self.assertEqual(db.quota_get_all_by_project(self.ctxt, 'project1'),
{'project_id': 'project1'})
self.assertEqual(db.quota_get_all_by_project_and_user(self.ctxt,
'project1', 'user1'),
{'project_id': 'project1', 'user_id': 'user1'})
self.assertEqual(db.quota_usage_get_all_by_project(
self.ctxt, 'project1'),
{'project_id': 'project1'})
for r in reservations:
self.assertRaises(exception.ReservationNotFound,
_reservation_get, self.ctxt, r)
def test_quota_destroy_all_by_project_and_user(self):
reservations = _quota_reserve(self.ctxt, 'project1', 'user1')
_quota_create(self.ctxt, 'project1', 'user1')
db.quota_destroy_all_by_project_and_user(self.ctxt, 'project1',
'user1')
self.assertEqual(db.quota_get_all_by_project_and_user(self.ctxt,
'project1', 'user1'),
{'project_id': 'project1',
'user_id': 'user1'})
self.assertEqual(db.quota_usage_get_all_by_project_and_user(
self.ctxt, 'project1', 'user1'),
{'project_id': 'project1',
'user_id': 'user1',
'fixed_ips': {'in_use': 2, 'reserved': 2}})
for r in reservations:
self.assertRaises(exception.ReservationNotFound,
_reservation_get, self.ctxt, r)
def test_quota_usage_get_nonexistent(self):
self.assertRaises(exception.QuotaUsageNotFound, db.quota_usage_get,
self.ctxt, 'p1', 'nonexitent_resource')
def test_quota_usage_get(self):
_quota_reserve(self.ctxt, 'p1', 'u1')
quota_usage = db.quota_usage_get(self.ctxt, 'p1', 'resource0')
expected = {'resource': 'resource0', 'project_id': 'p1',
'in_use': 0, 'reserved': 0, 'total': 0}
for key, value in expected.items():
self.assertEqual(value, quota_usage[key])
def test_quota_usage_get_all_by_project(self):
_quota_reserve(self.ctxt, 'p1', 'u1')
expected = {'project_id': 'p1',
'resource0': {'in_use': 0, 'reserved': 0},
'resource1': {'in_use': 1, 'reserved': 1},
'fixed_ips': {'in_use': 2, 'reserved': 2}}
self.assertEqual(expected, db.quota_usage_get_all_by_project(
self.ctxt, 'p1'))
def test_quota_usage_get_all_by_project_and_user(self):
_quota_reserve(self.ctxt, 'p1', 'u1')
expected = {'project_id': 'p1',
'user_id': 'u1',
'resource0': {'in_use': 0, 'reserved': 0},
'resource1': {'in_use': 1, 'reserved': 1},
'fixed_ips': {'in_use': 2, 'reserved': 2}}
self.assertEqual(expected, db.quota_usage_get_all_by_project_and_user(
self.ctxt, 'p1', 'u1'))
def test_get_project_user_quota_usages_in_order(self):
_quota_reserve(self.ctxt, 'p1', 'u1')
@sqlalchemy_api.pick_context_manager_reader
def test(context):
with mock.patch.object(query.Query, 'order_by') as order_mock:
sqlalchemy_api._get_project_user_quota_usages(
context, 'p1', 'u1')
self.assertTrue(order_mock.called)
test(self.ctxt)
def test_quota_usage_update_nonexistent(self):
self.assertRaises(exception.QuotaUsageNotFound, db.quota_usage_update,
self.ctxt, 'p1', 'u1', 'resource', in_use=42)
def test_quota_usage_update(self):
_quota_reserve(self.ctxt, 'p1', 'u1')
db.quota_usage_update(self.ctxt, 'p1', 'u1', 'resource0', in_use=42,
reserved=43)
quota_usage = db.quota_usage_get(self.ctxt, 'p1', 'resource0', 'u1')
expected = {'resource': 'resource0', 'project_id': 'p1',
'user_id': 'u1', 'in_use': 42, 'reserved': 43, 'total': 85}
for key, value in expected.items():
self.assertEqual(value, quota_usage[key])
def test_quota_create_exists(self):
db.quota_create(self.ctxt, 'project1', 'resource1', 41)
@ -7673,144 +7363,6 @@ class QuotaTestCase(test.TestCase, ModelsObjectComparatorMixin):
'project1', 'resource1', 42)
class QuotaReserveNoDbTestCase(test.NoDBTestCase):
"""Tests quota reserve/refresh operations using mock."""
def test_create_quota_usage_if_missing_not_created(self):
# Tests that QuotaUsage isn't created if it's already in user_usages.
resource = 'fake-resource'
project_id = 'fake-project'
user_id = 'fake_user'
session = mock.sentinel
quota_usage = mock.sentinel
user_usages = {resource: quota_usage}
with mock.patch.object(sqlalchemy_api, '_quota_usage_create') as quc:
self.assertFalse(sqlalchemy_api._create_quota_usage_if_missing(
user_usages, resource, None,
project_id, user_id, session))
self.assertFalse(quc.called)
def _test_create_quota_usage_if_missing_created(self, per_project_quotas):
# Tests that the QuotaUsage is created.
user_usages = {}
if per_project_quotas:
resource = sqlalchemy_api.PER_PROJECT_QUOTAS[0]
else:
resource = 'fake-resource'
project_id = 'fake-project'
user_id = 'fake_user'
session = mock.sentinel
quota_usage = mock.sentinel
with mock.patch.object(sqlalchemy_api, '_quota_usage_create',
return_value=quota_usage) as quc:
self.assertTrue(sqlalchemy_api._create_quota_usage_if_missing(
user_usages, resource, None,
project_id, user_id, session))
self.assertEqual(quota_usage, user_usages[resource])
# Now test if the QuotaUsage was created with a user_id or not.
if per_project_quotas:
quc.assert_called_once_with(
project_id, None, resource, 0, 0, None, session)
else:
quc.assert_called_once_with(
project_id, user_id, resource, 0, 0, None, session)
def test_create_quota_usage_if_missing_created_per_project_quotas(self):
self._test_create_quota_usage_if_missing_created(True)
def test_create_quota_usage_if_missing_created_user_quotas(self):
self._test_create_quota_usage_if_missing_created(False)
def test_is_quota_refresh_needed_in_use(self):
# Tests when a quota refresh is needed based on the in_use value.
for in_use in range(-1, 1):
# We have to set until_refresh=None otherwise mock will give it
# a value which runs some code we don't want.
quota_usage = mock.MagicMock(in_use=in_use, until_refresh=None)
if in_use < 0:
self.assertTrue(sqlalchemy_api._is_quota_refresh_needed(
quota_usage, max_age=0))
else:
self.assertFalse(sqlalchemy_api._is_quota_refresh_needed(
quota_usage, max_age=0))
def test_is_quota_refresh_needed_until_refresh_none(self):
quota_usage = mock.MagicMock(in_use=0, until_refresh=None)
self.assertFalse(sqlalchemy_api._is_quota_refresh_needed(quota_usage,
max_age=0))
def test_is_quota_refresh_needed_until_refresh_not_none(self):
# Tests different values for the until_refresh counter.
for until_refresh in range(3):
quota_usage = mock.MagicMock(in_use=0, until_refresh=until_refresh)
refresh = sqlalchemy_api._is_quota_refresh_needed(quota_usage,
max_age=0)
until_refresh -= 1
if until_refresh <= 0:
self.assertTrue(refresh)
else:
self.assertFalse(refresh)
self.assertEqual(until_refresh, quota_usage.until_refresh)
def test_refresh_quota_usages(self):
quota_usage = mock.Mock(spec=models.QuotaUsage)
quota_usage.in_use = 5
quota_usage.until_refresh = None
sqlalchemy_api._refresh_quota_usages(quota_usage, until_refresh=5,
in_use=6)
self.assertEqual(6, quota_usage.in_use)
self.assertEqual(5, quota_usage.until_refresh)
def test_calculate_overquota_no_delta(self):
deltas = {'foo': -1}
user_quotas = {'foo': 10}
overs = sqlalchemy_api._calculate_overquota({}, user_quotas, deltas,
{}, {})
self.assertFalse(overs)
def test_calculate_overquota_unlimited_user_quota(self):
deltas = {'foo': 1}
project_quotas = {'foo': -1}
user_quotas = {'foo': -1}
project_usages = {'foo': {'total': 10}}
user_usages = {'foo': {'total': 10}}
overs = sqlalchemy_api._calculate_overquota(
project_quotas, user_quotas, deltas, project_usages, user_usages)
self.assertFalse(overs)
def test_calculate_overquota_unlimited_project_quota(self):
deltas = {'foo': 1}
project_quotas = {'foo': -1}
user_quotas = {'foo': 1}
project_usages = {'foo': {'total': 0}}
user_usages = {'foo': {'total': 0}}
overs = sqlalchemy_api._calculate_overquota(
project_quotas, user_quotas, deltas, project_usages, user_usages)
self.assertFalse(overs)
def _test_calculate_overquota(self, resource, project_usages, user_usages):
deltas = {resource: 1}
project_quotas = {resource: 10}
user_quotas = {resource: 10}
overs = sqlalchemy_api._calculate_overquota(
project_quotas, user_quotas, deltas, project_usages, user_usages)
self.assertEqual(resource, overs[0])
def test_calculate_overquota_per_project_quota_overquota(self):
# In this test, user quotas are fine but project quotas are over.
resource = 'foo'
project_usages = {resource: {'total': 10}}
user_usages = {resource: {'total': 5}}
self._test_calculate_overquota(resource, project_usages, user_usages)
def test_calculate_overquota_per_user_quota_overquota(self):
# In this test, project quotas are fine but user quotas are over.
resource = 'foo'
project_usages = {resource: {'total': 5}}
user_usages = {resource: {'total': 10}}
self._test_calculate_overquota(resource, project_usages, user_usages)
class QuotaClassTestCase(test.TestCase, ModelsObjectComparatorMixin):
def setUp(self):
@ -7867,11 +7419,6 @@ class QuotaClassTestCase(test.TestCase, ModelsObjectComparatorMixin):
self.assertRaises(exception.QuotaClassNotFound, db.quota_class_update,
self.ctxt, 'class name', 'resource', 42)
def test_refresh_quota_usages(self):
quota_usages = mock.Mock()
sqlalchemy_api._refresh_quota_usages(quota_usages, until_refresh=5,
in_use=6)
class S3ImageTestCase(test.TestCase):

View File

@ -54,93 +54,6 @@ class _TestQuotasObject(object):
self.instance = fake_instance.fake_db_instance(
project_id='fake_proj2', user_id='fake_user2')
def test_from_reservations(self):
fake_reservations = ['1', '2']
quotas = quotas_obj.Quotas.from_reservations(
self.context, fake_reservations)
self.assertEqual(self.context, quotas._context)
self.assertEqual(fake_reservations, quotas.reservations)
self.assertIsNone(quotas.project_id)
self.assertIsNone(quotas.user_id)
def test_from_reservations_bogus(self):
fake_reservations = [_TestQuotasObject, _TestQuotasObject]
self.assertRaises(ValueError,
quotas_obj.Quotas.from_reservations,
self.context, fake_reservations)
def test_from_reservations_instance(self):
fake_reservations = ['1', '2']
quotas = quotas_obj.Quotas.from_reservations(
self.context, fake_reservations,
instance=self.instance)
self.assertEqual(self.context, quotas._context)
self.assertEqual(fake_reservations, quotas.reservations)
self.assertEqual('fake_proj1', quotas.project_id)
self.assertEqual('fake_user2', quotas.user_id)
def test_from_reservations_instance_admin(self):
fake_reservations = ['1', '2']
elevated = self.context.elevated()
quotas = quotas_obj.Quotas.from_reservations(
elevated, fake_reservations,
instance=self.instance)
self.assertEqual(elevated, quotas._context)
self.assertEqual(fake_reservations, quotas.reservations)
self.assertEqual('fake_proj2', quotas.project_id)
self.assertEqual('fake_user2', quotas.user_id)
@mock.patch.object(QUOTAS, 'reserve')
def test_reserve(self, reserve_mock):
fake_reservations = ['1', '2']
quotas = quotas_obj.Quotas(context=self.context)
reserve_mock.return_value = fake_reservations
quotas.reserve(expire='expire',
project_id='project_id', user_id='user_id',
moo='cow')
self.assertEqual(self.context, quotas._context)
self.assertEqual(fake_reservations, quotas.reservations)
self.assertEqual('project_id', quotas.project_id)
self.assertEqual('user_id', quotas.user_id)
reserve_mock.assert_called_once_with(
self.context, expire='expire', project_id='project_id',
user_id='user_id', moo='cow')
@mock.patch.object(QUOTAS, 'commit')
def test_commit(self, commit_mock):
fake_reservations = ['1', '2']
quotas = quotas_obj.Quotas.from_reservations(
self.context, fake_reservations)
quotas.commit()
self.assertIsNone(quotas.reservations)
commit_mock.assert_called_once_with(
self.context, fake_reservations, project_id=None, user_id=None)
@mock.patch.object(QUOTAS, 'commit')
def test_commit_none_reservations(self, commit_mock):
quotas = quotas_obj.Quotas.from_reservations(self.context, None)
quotas.commit()
self.assertFalse(commit_mock.called)
@mock.patch.object(QUOTAS, 'rollback')
def test_rollback(self, rollback_mock):
fake_reservations = ['1', '2']
quotas = quotas_obj.Quotas.from_reservations(
self.context, fake_reservations)
quotas.rollback()
self.assertIsNone(quotas.reservations)
rollback_mock.assert_called_once_with(
self.context, fake_reservations, project_id=None, user_id=None)
@mock.patch.object(QUOTAS, 'rollback')
def test_rollback_none_reservations(self, rollback_mock):
quotas = quotas_obj.Quotas.from_reservations(self.context, None)
quotas.rollback()
self.assertFalse(rollback_mock.called)
@mock.patch('nova.db.quota_get', side_effect=exception.QuotaNotFound)
@mock.patch('nova.objects.Quotas._create_limit_in_db')
def test_create_limit(self, mock_create, mock_get):

View File

@ -340,23 +340,6 @@ class FakeDriver(object):
self.called.append(('limit_check_project_and_user', context, resources,
project_values, user_values, project_id, user_id))
def reserve(self, context, resources, deltas, expire=None,
project_id=None, user_id=None):
self.called.append(('reserve', context, resources, deltas,
expire, project_id, user_id))
return self.reservations
def commit(self, context, reservations, project_id=None, user_id=None):
self.called.append(('commit', context, reservations, project_id,
user_id))
def rollback(self, context, reservations, project_id=None, user_id=None):
self.called.append(('rollback', context, reservations, project_id,
user_id))
def usage_reset(self, context, resources):
self.called.append(('usage_reset', context, resources))
def destroy_all_by_project_and_user(self, context, project_id, user_id):
self.called.append(('destroy_all_by_project_and_user', context,
project_id, user_id))
@ -488,7 +471,7 @@ class BaseResourceTestCase(test.TestCase):
quota._valid_method_call_check_resources,
resources, 'check', quota.QUOTAS._resources)
def test_valid_method_call_check_wrong_method_reserve(self):
def test_valid_method_call_check_wrong_method(self):
resources = {'key_pairs': 1}
engine_resources = {'key_pairs': quota.CountableResource('key_pairs',
None,
@ -496,17 +479,7 @@ class BaseResourceTestCase(test.TestCase):
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'reserve', engine_resources)
def test_valid_method_call_check_wrong_method_check(self):
resources = {'instances': 1}
engine_resources = {'instances': quota.ReservableResource('instances',
None,
'instances')}
self.assertRaises(exception.InvalidQuotaMethodUsage,
quota._valid_method_call_check_resources,
resources, 'check', engine_resources)
resources, 'bogus', engine_resources)
class QuotaEngineTestCase(test.TestCase):
@ -734,84 +707,6 @@ class QuotaEngineTestCase(test.TestCase):
None, None)],
driver.called)
def test_reserve(self):
context = FakeContext(None, None)
driver = FakeDriver(reservations=[
'resv-01', 'resv-02', 'resv-03', 'resv-04',
])
quota_obj = self._make_quota_obj(driver)
result1 = quota_obj.reserve(context, test_resource1=4,
test_resource2=3, test_resource3=2,
test_resource4=1)
result2 = quota_obj.reserve(context, expire=3600,
test_resource1=1, test_resource2=2,
test_resource3=3, test_resource4=4)
result3 = quota_obj.reserve(context, project_id='fake_project',
test_resource1=1, test_resource2=2,
test_resource3=3, test_resource4=4)
self.assertEqual(driver.called, [
('reserve', context, quota_obj._resources, dict(
test_resource1=4,
test_resource2=3,
test_resource3=2,
test_resource4=1,
), None, None, None),
('reserve', context, quota_obj._resources, dict(
test_resource1=1,
test_resource2=2,
test_resource3=3,
test_resource4=4,
), 3600, None, None),
('reserve', context, quota_obj._resources, dict(
test_resource1=1,
test_resource2=2,
test_resource3=3,
test_resource4=4,
), None, 'fake_project', None),
])
self.assertEqual(result1, [
'resv-01', 'resv-02', 'resv-03', 'resv-04',
])
self.assertEqual(result2, [
'resv-01', 'resv-02', 'resv-03', 'resv-04',
])
self.assertEqual(result3, [
'resv-01', 'resv-02', 'resv-03', 'resv-04',
])
def test_commit(self):
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
quota_obj.commit(context, ['resv-01', 'resv-02', 'resv-03'])
self.assertEqual(driver.called, [
('commit', context, ['resv-01', 'resv-02', 'resv-03'], None,
None),
])
def test_rollback(self):
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
quota_obj.rollback(context, ['resv-01', 'resv-02', 'resv-03'])
self.assertEqual(driver.called, [
('rollback', context, ['resv-01', 'resv-02', 'resv-03'], None,
None),
])
def test_usage_reset(self):
context = FakeContext(None, None)
driver = FakeDriver()
quota_obj = self._make_quota_obj(driver)
quota_obj.usage_reset(context, ['res1', 'res2', 'res3'])
self.assertEqual(driver.called, [
('usage_reset', context, ['res1', 'res2', 'res3']),
])
def test_destroy_all_by_project_and_user(self):
context = FakeContext(None, None)
driver = FakeDriver()