Merge "Improve DB operations for quota reservation"

This commit is contained in:
Jenkins 2015-09-08 14:42:27 +00:00 committed by Gerrit Code Review
commit dbe420c2b7
6 changed files with 24 additions and 40 deletions

View File

@ -19,6 +19,7 @@ import sqlalchemy as sa
from sqlalchemy.orm import exc as orm_exc
from sqlalchemy import sql
from neutron.db import api as db_api
from neutron.db import common_db_mixin as common_db_api
from neutron.db.quota import models as quota_models
@ -96,10 +97,11 @@ def set_quota_usage(context, resource, tenant_id,
:param delta: Specifies whether in_use is an absolute number
or a delta (default to False)
"""
query = common_db_api.model_query(context, quota_models.QuotaUsage)
query = query.filter_by(resource=resource).filter_by(tenant_id=tenant_id)
usage_data = query.first()
with context.session.begin(subtransactions=True):
with db_api.autonested_transaction(context.session):
query = common_db_api.model_query(context, quota_models.QuotaUsage)
query = query.filter_by(resource=resource).filter_by(
tenant_id=tenant_id)
usage_data = query.first()
if not usage_data:
# Must create entry
usage_data = quota_models.QuotaUsage(
@ -174,10 +176,6 @@ def create_reservation(context, tenant_id, deltas, expiration=None):
quota_models.ResourceDelta(resource=resource,
amount=delta,
reservation=resv))
# quota_usage for all resources involved in this reservation must
# be marked as dirty
set_resources_quota_usage_dirty(
context, deltas.keys(), tenant_id)
return ReservationInfo(resv['id'],
resv['tenant_id'],
resv['expiration'],
@ -249,7 +247,7 @@ def get_reservations_for_resources(context, tenant_id, resources,
quota_models.ResourceDelta.resource,
quota_models.Reservation.expiration)
return dict((resource, total_reserved)
for (resource, exp, total_reserved) in resv_query)
for (resource, exp, total_reserved) in resv_query)
def remove_expired_reservations(context, tenant_id=None):

View File

@ -134,6 +134,8 @@ class DbQuotaDriver(object):
context, tenant_id=tenant_id)
@oslo_db_api.wrap_db_retry(max_retries=db_api.MAX_RETRIES,
retry_interval=0.1,
inc_retry_interval=True,
retry_on_request=True,
retry_on_deadlock=True)
def make_reservation(self, context, tenant_id, resources, deltas, plugin):
@ -150,7 +152,7 @@ class DbQuotaDriver(object):
# locks should be ok to use when support for sending "hotspot" writes
# to a single node will be avaialable.
requested_resources = deltas.keys()
with context.session.begin():
with db_api.autonested_transaction(context.session):
# Gather current usage information
# TODO(salv-orlando): calling count() for every resource triggers
# multiple queries on quota usage. This should be improved, however
@ -160,7 +162,7 @@ class DbQuotaDriver(object):
# instances
current_usages = dict(
(resource, resources[resource].count(
context, plugin, tenant_id)) for
context, plugin, tenant_id, resync_usage=False)) for
resource in requested_resources)
# get_tenant_quotes needs in inout a dictionary mapping resource
# name to BaseResosurce instances so that the default quota can be

View File

@ -131,7 +131,7 @@ class CountableResource(BaseResource):
name, flag=flag, plural_name=plural_name)
self._count_func = count
def count(self, context, plugin, tenant_id):
def count(self, context, plugin, tenant_id, **kwargs):
return self._count_func(context, plugin, self.plural_name, tenant_id)
@ -176,10 +176,10 @@ class TrackedResource(BaseResource):
def dirty(self):
return self._dirty_tenants
def mark_dirty(self, context, nested=False):
def mark_dirty(self, context):
if not self._dirty_tenants:
return
with context.session.begin(nested=nested, subtransactions=True):
with db_api.autonested_transaction(context.session):
# It is not necessary to protect this operation with a lock.
# Indeed when this method is called the request has been processed
# and therefore all resources created or deleted.
@ -211,6 +211,7 @@ class TrackedResource(BaseResource):
# ensure that an UPDATE statement is emitted rather than an INSERT one
@oslo_db_api.wrap_db_retry(
max_retries=db_api.MAX_RETRIES,
retry_on_deadlock=True,
exception_checker=lambda exc:
isinstance(exc, oslo_db_exception.DBDuplicateEntry))
def _set_quota_usage(self, context, tenant_id, in_use):
@ -239,7 +240,7 @@ class TrackedResource(BaseResource):
# Update quota usage
return self._resync(context, tenant_id, in_use)
def count(self, context, _plugin, tenant_id, resync_usage=False):
def count(self, context, _plugin, tenant_id, resync_usage=True):
"""Return the current usage count for the resource.
This method will fetch aggregate information for resource usage
@ -279,15 +280,14 @@ class TrackedResource(BaseResource):
# Update quota usage, if requested (by default do not do that, as
# typically one counts before adding a record, and that would mark
# the usage counter as dirty again)
if resync_usage or not usage_info:
if resync_usage:
usage_info = self._resync(context, tenant_id, in_use)
else:
# NOTE(salv-orlando): Passing 0 for reserved amount as
# reservations are currently not supported
usage_info = quota_api.QuotaUsageInfo(usage_info.resource,
usage_info.tenant_id,
in_use,
usage_info.dirty)
resource = usage_info.resource if usage_info else self.name
tenant_id = usage_info.tenant_id if usage_info else tenant_id
dirty = usage_info.dirty if usage_info else True
usage_info = quota_api.QuotaUsageInfo(
resource, tenant_id, in_use, dirty)
LOG.debug(("Quota usage for %(resource)s was recalculated. "
"Used quota:%(used)d."),

View File

@ -67,7 +67,7 @@ def set_resources_dirty(context):
for res in get_all_resources().values():
with context.session.begin(subtransactions=True):
if is_tracked(res.name) and res.dirty:
res.mark_dirty(context, nested=True)
res.mark_dirty(context)
def resync_resource(context, resource_name, tenant_id):

View File

@ -263,22 +263,6 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
self.assertIsNone(quota_api.get_reservations_for_resources(
self.context, self.tenant_id, []))
def _test_remove_reservation(self, set_dirty):
resources = {'goals': 2, 'assists': 1}
resv = self._create_reservation(resources)
self.assertEqual(1, quota_api.remove_reservation(
self.context, resv.reservation_id, set_dirty=set_dirty))
def test_remove_reservation(self):
self._test_remove_reservation(False)
def test_remove_reservation_and_set_dirty(self):
routine = 'neutron.db.quota.api.set_resources_quota_usage_dirty'
with mock.patch(routine) as mock_routine:
self._test_remove_reservation(False)
mock_routine.assert_called_once_with(
self.context, mock.ANY, self.tenant_id)
def test_remove_expired_reservations(self):
with mock.patch('neutron.db.quota.api.utcnow') as mock_utcnow:
mock_utcnow.return_value = datetime.datetime(

View File

@ -156,4 +156,4 @@ class TestAuxiliaryFunctions(base.DietTestCase):
# This ensures dirty is true
res._dirty_tenants.add('tenant_id')
resource_registry.set_resources_dirty(ctx)
mock_mark_dirty.assert_called_once_with(ctx, nested=True)
mock_mark_dirty.assert_called_once_with(ctx)