Do not fail when releasing a quota reservation

The quota reservation release (deletion) is done at the end of a
server call to create a new resource (or resources). The quota
drivers implemented have mechanisms to clean up the expired
reservations when calculating the resource quota.

If a reservation deletion fails, the API call should not be retried.
Instead of this, the reservation should be left and collected by
the quota driver clean up mechanisms.

This patch also adds a timeout delay for expired reservations in
``DbQuotaNoLockDriver``. Now this driver deletes any existing
reservation created ``RESERVATION_EXPIRATION_TIMEOUT = 20`` (seconds)
before. It is assumed that any reservation should be released before
this time (regardless if the resource is created or not).

Closes-Bug: #1940311

Change-Id: I155c401ec5e2fe6e3af6390855852764ee983cf5
This commit is contained in:
Rodolfo Alonso Hernandez 2021-08-18 13:04:08 +00:00
parent 668b1cc652
commit 1146a4d091
3 changed files with 17 additions and 7 deletions

View File

@ -17,10 +17,15 @@ import collections
import datetime
from neutron_lib.db import api as db_api
from oslo_db import exception as db_exc
from neutron.common import utils
from neutron.objects import quota as quota_obj
RESERVATION_EXPIRATION_TIMEOUT = 20 # seconds
# Wrapper for utcnow - needed for mocking it in unit tests
def utcnow():
return datetime.datetime.utcnow()
@ -197,7 +202,7 @@ def get_reservation(context, reservation_id):
for delta in reserv_obj.resource_deltas))
@db_api.retry_if_session_inactive()
@utils.skip_exceptions(db_exc.DBError)
@db_api.CONTEXT_WRITER
def remove_reservation(context, reservation_id, set_dirty=False):
reservation = quota_obj.Reservation.get_object(context, id=reservation_id)
@ -236,8 +241,12 @@ def get_reservations_for_resources(context, tenant_id, resources,
@db_api.retry_if_session_inactive()
@db_api.CONTEXT_WRITER
def remove_expired_reservations(context, tenant_id=None):
return quota_obj.Reservation.delete_expired(context, utcnow(), tenant_id)
def remove_expired_reservations(context, tenant_id=None, timeout=None):
expiring_time = utcnow()
if timeout:
expiring_time -= datetime.timedelta(seconds=timeout)
return quota_obj.Reservation.delete_expired(context, expiring_time,
tenant_id)
class QuotaDriverAPI(object, metaclass=abc.ABCMeta):

View File

@ -53,8 +53,9 @@ class DbQuotaNoLockDriver(quota_driver.DbQuotaDriver):
# Delete expired reservations before counting valid ones. This
# operation is fast and by calling it before making any
# reservation, we ensure the freshness of the reservations.
quota_api.remove_expired_reservations(context,
tenant_id=project_id)
quota_api.remove_expired_reservations(
context, tenant_id=project_id,
timeout=quota_api.RESERVATION_EXPIRATION_TIMEOUT)
# Count the number of (1) used and (2) reserved resources for this
# project_id. If any resource limit is exceeded, raise exception.

View File

@ -70,7 +70,7 @@ class Reservation(base.NeutronDbObject):
self.obj_reset_changes(['resource_deltas'])
@classmethod
def delete_expired(cls, context, now, project_id):
def delete_expired(cls, context, expiring_time, project_id):
resv_query = context.session.query(models.Reservation)
if project_id:
project_expr = (models.Reservation.project_id == project_id)
@ -80,7 +80,7 @@ class Reservation(base.NeutronDbObject):
# object/db/api.py once comparison operations are
# supported
resv_query = resv_query.filter(sa.and_(
project_expr, models.Reservation.expiration < now))
project_expr, models.Reservation.expiration < expiring_time))
return resv_query.delete()
@classmethod