Merge "OVO for Quotas and Reservation"
commit
b9e3fc64b2
|
@ -15,13 +15,8 @@
|
|||
import collections
|
||||
import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import exc as orm_exc
|
||||
from sqlalchemy import sql
|
||||
|
||||
from neutron.db import _utils as db_utils
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db.quota import models as quota_models
|
||||
from neutron.objects import quota as quota_obj
|
||||
|
||||
|
||||
# Wrapper for utcnow - needed for mocking it in unit tests
|
||||
|
@ -50,40 +45,30 @@ def get_quota_usage_by_resource_and_tenant(context, resource, tenant_id):
|
|||
:returns: a QuotaUsageInfo instance
|
||||
"""
|
||||
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(resource=resource, tenant_id=tenant_id)
|
||||
|
||||
# NOTE(manjeets) as lock mode was just for protecting dirty bits
|
||||
# an update on dirty will prevent the race.
|
||||
query.filter_by(dirty=True).update({'dirty': True})
|
||||
|
||||
result = query.first()
|
||||
result = quota_obj.QuotaUsage.get_object_dirty_protected(
|
||||
context, resource=resource, project_id=tenant_id)
|
||||
if not result:
|
||||
return
|
||||
return QuotaUsageInfo(result.resource,
|
||||
result.tenant_id,
|
||||
result.in_use,
|
||||
return QuotaUsageInfo(result.resource, result.project_id, result.in_use,
|
||||
result.dirty)
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
def get_quota_usage_by_resource(context, resource):
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(resource=resource)
|
||||
objs = quota_obj.QuotaUsage.get_objects(context, resource=resource)
|
||||
return [QuotaUsageInfo(item.resource,
|
||||
item.tenant_id,
|
||||
item.project_id,
|
||||
item.in_use,
|
||||
item.dirty) for item in query]
|
||||
item.dirty) for item in objs]
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
def get_quota_usage_by_tenant_id(context, tenant_id):
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(tenant_id=tenant_id)
|
||||
objs = quota_obj.QuotaUsage.get_objects(context, project_id=tenant_id)
|
||||
return [QuotaUsageInfo(item.resource,
|
||||
item.tenant_id,
|
||||
tenant_id,
|
||||
item.in_use,
|
||||
item.dirty) for item in query]
|
||||
item.dirty) for item in objs]
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -101,16 +86,13 @@ def set_quota_usage(context, resource, tenant_id,
|
|||
or a delta (default to False)
|
||||
"""
|
||||
with db_api.autonested_transaction(context.session):
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(resource=resource).filter_by(
|
||||
tenant_id=tenant_id)
|
||||
usage_data = query.first()
|
||||
usage_data = quota_obj.QuotaUsage.get_object(
|
||||
context, resource=resource, project_id=tenant_id)
|
||||
if not usage_data:
|
||||
# Must create entry
|
||||
usage_data = quota_models.QuotaUsage(
|
||||
resource=resource,
|
||||
tenant_id=tenant_id)
|
||||
context.session.add(usage_data)
|
||||
usage_data = quota_obj.QuotaUsage(
|
||||
context, resource=resource, project_id=tenant_id)
|
||||
usage_data.create()
|
||||
# Perform explicit comparison with None as 0 is a valid value
|
||||
if in_use is not None:
|
||||
if delta:
|
||||
|
@ -118,10 +100,9 @@ def set_quota_usage(context, resource, tenant_id,
|
|||
usage_data.in_use = in_use
|
||||
# After an explicit update the dirty bit should always be reset
|
||||
usage_data.dirty = False
|
||||
return QuotaUsageInfo(usage_data.resource,
|
||||
usage_data.tenant_id,
|
||||
usage_data.in_use,
|
||||
usage_data.dirty)
|
||||
usage_data.update()
|
||||
return QuotaUsageInfo(usage_data.resource, usage_data.project_id,
|
||||
usage_data.in_use, usage_data.dirty)
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -134,9 +115,13 @@ def set_quota_usage_dirty(context, resource, tenant_id, dirty=True):
|
|||
:param dirty: the desired value for the dirty bit (defaults to True)
|
||||
:returns: 1 if the quota usage data were updated, 0 otherwise.
|
||||
"""
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(resource=resource).filter_by(tenant_id=tenant_id)
|
||||
return query.update({'dirty': dirty})
|
||||
obj = quota_obj.QuotaUsage.get_object(
|
||||
context, resource=resource, project_id=tenant_id)
|
||||
if obj:
|
||||
obj.dirty = dirty
|
||||
obj.update()
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -150,12 +135,14 @@ def set_resources_quota_usage_dirty(context, resources, tenant_id, dirty=True):
|
|||
:param dirty: the desired value for the dirty bit (defaults to True)
|
||||
:returns: the number of records for which the bit was actually set.
|
||||
"""
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(tenant_id=tenant_id)
|
||||
filters = {'project_id': tenant_id}
|
||||
if resources:
|
||||
query = query.filter(quota_models.QuotaUsage.resource.in_(resources))
|
||||
# synchronize_session=False needed because of the IN condition
|
||||
return query.update({'dirty': dirty}, synchronize_session=False)
|
||||
filters['resource'] = resources
|
||||
objs = quota_obj.QuotaUsage.get_objects(context, **filters)
|
||||
for obj in objs:
|
||||
obj.dirty = dirty
|
||||
obj.update()
|
||||
return len(objs)
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -167,66 +154,62 @@ def set_all_quota_usage_dirty(context, resource, dirty=True):
|
|||
:returns: the number of tenants for which the dirty bit was
|
||||
actually updated
|
||||
"""
|
||||
query = db_utils.model_query(context, quota_models.QuotaUsage)
|
||||
query = query.filter_by(resource=resource)
|
||||
return query.update({'dirty': dirty})
|
||||
# TODO(manjeets) consider squashing this method with
|
||||
# set_resources_quota_usage_dirty
|
||||
objs = quota_obj.QuotaUsage.get_objects(context, resource=resource)
|
||||
for obj in objs:
|
||||
obj.dirty = dirty
|
||||
obj.update()
|
||||
return len(objs)
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
def create_reservation(context, tenant_id, deltas, expiration=None):
|
||||
# This method is usually called from within another transaction.
|
||||
# Consider using begin_nested
|
||||
with context.session.begin(subtransactions=True):
|
||||
expiration = expiration or (utcnow() + datetime.timedelta(0, 120))
|
||||
resv = quota_models.Reservation(tenant_id=tenant_id,
|
||||
expiration=expiration)
|
||||
context.session.add(resv)
|
||||
for (resource, delta) in deltas.items():
|
||||
context.session.add(
|
||||
quota_models.ResourceDelta(resource=resource,
|
||||
amount=delta,
|
||||
reservation=resv))
|
||||
return ReservationInfo(resv['id'],
|
||||
resv['tenant_id'],
|
||||
resv['expiration'],
|
||||
expiration = expiration or (utcnow() + datetime.timedelta(0, 120))
|
||||
delta_objs = []
|
||||
for (resource, delta) in deltas.items():
|
||||
delta_objs.append(quota_obj.ResourceDelta(
|
||||
context, resource=resource, amount=delta))
|
||||
reserv_obj = quota_obj.Reservation(
|
||||
context, project_id=tenant_id, expiration=expiration,
|
||||
resource_deltas=delta_objs)
|
||||
reserv_obj.create()
|
||||
return ReservationInfo(reserv_obj['id'],
|
||||
reserv_obj['project_id'],
|
||||
reserv_obj['expiration'],
|
||||
dict((delta.resource, delta.amount)
|
||||
for delta in resv.resource_deltas))
|
||||
for delta in reserv_obj.resource_deltas))
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
def get_reservation(context, reservation_id):
|
||||
query = context.session.query(quota_models.Reservation).filter_by(
|
||||
id=reservation_id)
|
||||
resv = query.first()
|
||||
if not resv:
|
||||
reserv_obj = quota_obj.Reservation.get_object(context, id=reservation_id)
|
||||
if not reserv_obj:
|
||||
return
|
||||
return ReservationInfo(resv['id'],
|
||||
resv['tenant_id'],
|
||||
resv['expiration'],
|
||||
return ReservationInfo(reserv_obj['id'],
|
||||
reserv_obj['project_id'],
|
||||
reserv_obj['expiration'],
|
||||
dict((delta.resource, delta.amount)
|
||||
for delta in resv.resource_deltas))
|
||||
for delta in reserv_obj.resource_deltas))
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
@db_api.context_manager.writer
|
||||
def remove_reservation(context, reservation_id, set_dirty=False):
|
||||
delete_query = context.session.query(quota_models.Reservation).filter_by(
|
||||
id=reservation_id)
|
||||
# Not handling MultipleResultsFound as the query is filtering by primary
|
||||
# key
|
||||
try:
|
||||
reservation = delete_query.one()
|
||||
except orm_exc.NoResultFound:
|
||||
reservation = quota_obj.Reservation.get_object(context, id=reservation_id)
|
||||
if not reservation:
|
||||
# TODO(salv-orlando): Raise here and then handle the exception?
|
||||
return
|
||||
tenant_id = reservation.tenant_id
|
||||
tenant_id = reservation.project_id
|
||||
resources = [delta.resource for delta in reservation.resource_deltas]
|
||||
num_deleted = delete_query.delete()
|
||||
reservation.delete()
|
||||
if set_dirty:
|
||||
# quota_usage for all resource involved in this reservation must
|
||||
# be marked as dirty
|
||||
set_resources_quota_usage_dirty(context, resources, tenant_id)
|
||||
return num_deleted
|
||||
return 1
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -241,38 +224,14 @@ def get_reservations_for_resources(context, tenant_id, resources,
|
|||
reservations (defaults to False)
|
||||
:returns: a dictionary mapping resources with corresponding deltas
|
||||
"""
|
||||
if not resources:
|
||||
# Do not waste time
|
||||
return
|
||||
now = utcnow()
|
||||
resv_query = context.session.query(
|
||||
quota_models.ResourceDelta.resource,
|
||||
quota_models.Reservation.expiration,
|
||||
sql.func.sum(quota_models.ResourceDelta.amount)).join(
|
||||
quota_models.Reservation)
|
||||
if expired:
|
||||
exp_expr = (quota_models.Reservation.expiration < now)
|
||||
else:
|
||||
exp_expr = (quota_models.Reservation.expiration >= now)
|
||||
resv_query = resv_query.filter(sa.and_(
|
||||
quota_models.Reservation.tenant_id == tenant_id,
|
||||
quota_models.ResourceDelta.resource.in_(resources),
|
||||
exp_expr)).group_by(
|
||||
quota_models.ResourceDelta.resource,
|
||||
quota_models.Reservation.expiration)
|
||||
return dict((resource, total_reserved)
|
||||
for (resource, exp, total_reserved) in resv_query)
|
||||
# NOTE(manjeets) we are using utcnow() here because it
|
||||
# can be mocked easily where as datetime is built in type
|
||||
# mock.path does not allow mocking built in types.
|
||||
return quota_obj.Reservation.get_total_reservations_map(
|
||||
context, utcnow(), tenant_id, resources, expired)
|
||||
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
@db_api.context_manager.writer
|
||||
def remove_expired_reservations(context, tenant_id=None):
|
||||
now = utcnow()
|
||||
resv_query = context.session.query(quota_models.Reservation)
|
||||
if tenant_id:
|
||||
tenant_expr = (quota_models.Reservation.tenant_id == tenant_id)
|
||||
else:
|
||||
tenant_expr = sql.true()
|
||||
resv_query = resv_query.filter(sa.and_(
|
||||
tenant_expr, quota_models.Reservation.expiration < now))
|
||||
return resv_query.delete()
|
||||
return quota_obj.Reservation.delete_expired(context, utcnow(), tenant_id)
|
||||
|
|
|
@ -17,10 +17,9 @@ from neutron_lib import exceptions
|
|||
from oslo_log import log
|
||||
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.db import _utils as db_utils
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db.quota import api as quota_api
|
||||
from neutron.db.quota import models as quota_models
|
||||
from neutron.objects import quota as quota_obj
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
@ -66,9 +65,8 @@ class DbQuotaDriver(object):
|
|||
for key, resource in resources.items())
|
||||
|
||||
# update with tenant specific limits
|
||||
q_qry = db_utils.model_query(context, quota_models.Quota).filter_by(
|
||||
tenant_id=tenant_id)
|
||||
for item in q_qry:
|
||||
quota_objs = quota_obj.Quota.get_objects(context, project_id=tenant_id)
|
||||
for item in quota_objs:
|
||||
tenant_quota[item['resource']] = item['limit']
|
||||
|
||||
return tenant_quota
|
||||
|
@ -82,12 +80,10 @@ class DbQuotaDriver(object):
|
|||
Raise a "not found" error if the quota for the given tenant was
|
||||
never defined.
|
||||
"""
|
||||
with context.session.begin():
|
||||
tenant_quotas = context.session.query(quota_models.Quota)
|
||||
tenant_quotas = tenant_quotas.filter_by(tenant_id=tenant_id)
|
||||
if not tenant_quotas.delete():
|
||||
# No record deleted means the quota was not found
|
||||
raise n_exc.TenantQuotaNotFound(tenant_id=tenant_id)
|
||||
if quota_obj.Quota.delete_objects(
|
||||
context, project_id=tenant_id) < 1:
|
||||
# No record deleted means the quota was not found
|
||||
raise n_exc.TenantQuotaNotFound(tenant_id=tenant_id)
|
||||
|
||||
@staticmethod
|
||||
@db_api.retry_if_session_inactive()
|
||||
|
@ -104,8 +100,8 @@ class DbQuotaDriver(object):
|
|||
|
||||
all_tenant_quotas = {}
|
||||
|
||||
for quota in context.session.query(quota_models.Quota):
|
||||
tenant_id = quota['tenant_id']
|
||||
for quota in quota_obj.Quota.get_objects(context):
|
||||
tenant_id = quota['project_id']
|
||||
|
||||
# avoid setdefault() because only want to copy when actually
|
||||
# required
|
||||
|
@ -124,17 +120,14 @@ class DbQuotaDriver(object):
|
|||
@staticmethod
|
||||
@db_api.retry_if_session_inactive()
|
||||
def update_quota_limit(context, tenant_id, resource, limit):
|
||||
with context.session.begin():
|
||||
tenant_quota = context.session.query(quota_models.Quota).filter_by(
|
||||
tenant_id=tenant_id, resource=resource).first()
|
||||
|
||||
if tenant_quota:
|
||||
tenant_quota.update({'limit': limit})
|
||||
else:
|
||||
tenant_quota = quota_models.Quota(tenant_id=tenant_id,
|
||||
resource=resource,
|
||||
limit=limit)
|
||||
context.session.add(tenant_quota)
|
||||
tenant_quotas = quota_obj.Quota.get_objects(
|
||||
context, project_id=tenant_id, resource=resource)
|
||||
if tenant_quotas:
|
||||
tenant_quotas[0].limit = limit
|
||||
tenant_quotas[0].update()
|
||||
else:
|
||||
quota_obj.Quota(context, project_id=tenant_id, resource=resource,
|
||||
limit=limit).create()
|
||||
|
||||
def _get_quotas(self, context, tenant_id, resources):
|
||||
"""Retrieves the quotas for specific resources.
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
# Copyright (c) 2016 Intel Corporation. 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.
|
||||
|
||||
from oslo_versionedobjects import base as obj_base
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import sql
|
||||
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db.quota import models
|
||||
from neutron.objects import base
|
||||
from neutron.objects import common_types
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register
|
||||
class ResourceDelta(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = models.ResourceDelta
|
||||
|
||||
primary_keys = ['resource', 'reservation_id']
|
||||
|
||||
foreign_keys = {'Reservation': {'reservation_id': 'id'}}
|
||||
|
||||
fields = {
|
||||
'resource': obj_fields.StringField(),
|
||||
'reservation_id': common_types.UUIDField(),
|
||||
'amount': obj_fields.IntegerField(nullable=True),
|
||||
}
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register
|
||||
class Reservation(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = models.Reservation
|
||||
|
||||
fields = {
|
||||
'id': common_types.UUIDField(),
|
||||
'project_id': obj_fields.StringField(nullable=True),
|
||||
'expiration': obj_fields.DateTimeField(tzinfo_aware=False,
|
||||
nullable=True),
|
||||
'resource_deltas': obj_fields.ListOfObjectsField(
|
||||
ResourceDelta.__name__, nullable=True),
|
||||
}
|
||||
|
||||
synthetic_fields = ['resource_deltas']
|
||||
|
||||
def create(self):
|
||||
deltas = self.resource_deltas
|
||||
with db_api.autonested_transaction(self.obj_context.session):
|
||||
super(Reservation, self).create()
|
||||
if deltas:
|
||||
for delta in deltas:
|
||||
delta.reservation_id = self.id
|
||||
delta.create()
|
||||
self.resource_deltas.append(delta)
|
||||
self.obj_reset_changes(['resource_deltas'])
|
||||
|
||||
@classmethod
|
||||
def delete_expired(cls, context, now, project_id):
|
||||
resv_query = context.session.query(models.Reservation)
|
||||
if project_id:
|
||||
project_expr = (models.Reservation.project_id == project_id)
|
||||
else:
|
||||
project_expr = sql.true()
|
||||
# TODO(manjeets) Fetch and delete objects using
|
||||
# object/db/api.py once comparison operations are
|
||||
# supported
|
||||
resv_query = resv_query.filter(sa.and_(
|
||||
project_expr, models.Reservation.expiration < now))
|
||||
return resv_query.delete()
|
||||
|
||||
@classmethod
|
||||
def get_total_reservations_map(cls, context, now, project_id,
|
||||
resources, expired):
|
||||
if not resources:
|
||||
return
|
||||
resv_query = context.session.query(
|
||||
models.ResourceDelta.resource,
|
||||
models.Reservation.expiration,
|
||||
sql.func.sum(models.ResourceDelta.amount)).join(
|
||||
models.Reservation)
|
||||
if expired:
|
||||
exp_expr = (models.Reservation.expiration < now)
|
||||
else:
|
||||
exp_expr = (models.Reservation.expiration >= now)
|
||||
resv_query = resv_query.filter(sa.and_(
|
||||
models.Reservation.project_id == project_id,
|
||||
models.ResourceDelta.resource.in_(resources),
|
||||
exp_expr)).group_by(
|
||||
models.ResourceDelta.resource,
|
||||
models.Reservation.expiration)
|
||||
return dict((resource, total_reserved)
|
||||
for (resource, exp, total_reserved) in resv_query)
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register
|
||||
class Quota(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = models.Quota
|
||||
|
||||
fields = {
|
||||
'id': common_types.UUIDField(),
|
||||
'project_id': obj_fields.StringField(nullable=True),
|
||||
'resource': obj_fields.StringField(nullable=True),
|
||||
'limit': obj_fields.IntegerField(nullable=True),
|
||||
}
|
||||
|
||||
|
||||
@obj_base.VersionedObjectRegistry.register
|
||||
class QuotaUsage(base.NeutronDbObject):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
db_model = models.QuotaUsage
|
||||
|
||||
primary_keys = ['resource', 'project_id']
|
||||
|
||||
fields = {
|
||||
'resource': obj_fields.StringField(),
|
||||
'project_id': obj_fields.StringField(),
|
||||
'dirty': obj_fields.BooleanField(default=False),
|
||||
'in_use': obj_fields.IntegerField(default=0),
|
||||
'reserved': obj_fields.IntegerField(default=0),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_object_dirty_protected(cls, context, **kwargs):
|
||||
query = context.session.query(cls.db_model)
|
||||
query = query.filter_by(**cls.modify_fields_to_db(kwargs))
|
||||
# NOTE(manjeets) as lock mode was just for protecting dirty bits
|
||||
# an update on dirty will prevent the race.
|
||||
query.filter_by(dirty=True).update({'dirty': True})
|
||||
res = query.first()
|
||||
if res:
|
||||
return cls._load_object(context, res)
|
|
@ -15,12 +15,19 @@
|
|||
import datetime
|
||||
|
||||
import mock
|
||||
from neutron_lib import constants as const
|
||||
from neutron_lib import context
|
||||
from neutron_lib.plugins import directory
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.db.quota import api as quota_api
|
||||
from neutron.tests.unit.db.quota import test_driver
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
|
||||
|
||||
|
||||
class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
|
||||
|
||||
def _set_context(self):
|
||||
|
@ -36,8 +43,8 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
|
|||
|
||||
def _create_quota_usage(self, resource, used, tenant_id=None):
|
||||
tenant_id = tenant_id or self.tenant_id
|
||||
return quota_api.set_quota_usage(
|
||||
self.context, resource, tenant_id, in_use=used)
|
||||
return quota_api.set_quota_usage(context.get_admin_context(),
|
||||
resource, tenant_id, in_use=used)
|
||||
|
||||
def _verify_quota_usage(self, usage_info,
|
||||
expected_resource=None,
|
||||
|
@ -54,6 +61,9 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
|
|||
def setUp(self):
|
||||
super(TestQuotaDbApi, self).setUp()
|
||||
self._set_context()
|
||||
self.plugin = test_driver.FakePlugin()
|
||||
directory.add_plugin(const.CORE, self.plugin)
|
||||
cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS)
|
||||
|
||||
def test_create_quota_usage(self):
|
||||
usage_info = self._create_quota_usage('goals', 26)
|
||||
|
@ -291,7 +301,7 @@ class TestQuotaDbApi(testlib_api.SqlTestCaseLight):
|
|||
expiration=exp_date_2,
|
||||
tenant_id='Callejon')
|
||||
self.assertEqual(2, quota_api.remove_expired_reservations(
|
||||
self.context))
|
||||
context.get_admin_context()))
|
||||
self.assertIsNone(quota_api.get_reservation(
|
||||
self.context, resv_2.reservation_id))
|
||||
self.assertIsNone(quota_api.get_reservation(
|
||||
|
|
|
@ -19,9 +19,13 @@ from neutron_lib import exceptions as lib_exc
|
|||
from neutron.common import exceptions
|
||||
from neutron.db import db_base_plugin_v2 as base_plugin
|
||||
from neutron.db.quota import driver
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
|
||||
|
||||
|
||||
class FakePlugin(base_plugin.NeutronDbPluginV2, driver.DbQuotaDriver):
|
||||
"""A fake plugin class containing all DB methods."""
|
||||
|
||||
|
@ -47,11 +51,13 @@ RESOURCE = 'res_test'
|
|||
ALT_RESOURCE = 'res_test_meh'
|
||||
|
||||
|
||||
class TestDbQuotaDriver(testlib_api.SqlTestCase):
|
||||
class TestDbQuotaDriver(testlib_api.SqlTestCase,
|
||||
base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestDbQuotaDriver, self).setUp()
|
||||
self.plugin = FakePlugin()
|
||||
self.context = context.get_admin_context()
|
||||
self.setup_coreplugin(core_plugin=DB_PLUGIN_KLASS)
|
||||
|
||||
def test_create_quota_limit(self):
|
||||
defaults = {RESOURCE: TestResource(RESOURCE, 4)}
|
||||
|
|
|
@ -64,6 +64,10 @@ object_data = {
|
|||
'QosMinimumBandwidthRule': '1.2-314c3419f4799067cc31cc319080adff',
|
||||
'QosRuleType': '1.2-e6fd08fcca152c339cbd5e9b94b1b8e7',
|
||||
'QosPolicy': '1.4-50460f619c34428ec5651916e938e5a0',
|
||||
'Quota': '1.0-6bb6a0f1bd5d66a2134ffa1a61873097',
|
||||
'QuotaUsage': '1.0-6fbf820368681aac7c5d664662605cf9',
|
||||
'Reservation': '1.0-49929fef8e82051660342eed51b48f2a',
|
||||
'ResourceDelta': '1.0-a980b37e0a52618b5af8db29af18be76',
|
||||
'Route': '1.0-a9883a63b416126f9e345523ec09483b',
|
||||
'RouterExtraAttributes': '1.0-ef8d61ae2864f0ec9af0ab7939cab318',
|
||||
'RouterL3AgentBinding': '1.0-c5ba6c95e3a4c1236a55f490cd67da82',
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
# Copyright (c) 2016 Intel Corporation. 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.
|
||||
|
||||
import datetime
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.objects import quota
|
||||
from neutron.tests.unit.objects import test_base as obj_test_base
|
||||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
class ResourceDeltaObjectIfaceTestCase(
|
||||
obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = quota.ResourceDelta
|
||||
|
||||
|
||||
class ResourceDeltaDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = quota.ResourceDelta
|
||||
|
||||
def setUp(self):
|
||||
super(ResourceDeltaDbObjectTestCase, self).setUp()
|
||||
for obj in self.obj_fields:
|
||||
self._create_test_reservation(res_id=obj['reservation_id'])
|
||||
|
||||
def _create_test_reservation(self, res_id):
|
||||
self._reservation = quota.Reservation(self.context, id=res_id)
|
||||
self._reservation.create()
|
||||
|
||||
|
||||
class ReservationObjectIfaceTestCase(
|
||||
obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = quota.Reservation
|
||||
|
||||
|
||||
class ReservationDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = quota.Reservation
|
||||
|
||||
def _create_test_reservation(self, res=None, exp=None):
|
||||
res_id = uuidutils.generate_uuid()
|
||||
reservation = self._test_class(self.context,
|
||||
id=res_id, resource=res, expiration=exp)
|
||||
reservation.create()
|
||||
return reservation
|
||||
|
||||
def test_delete_expired(self):
|
||||
dt = datetime.datetime.utcnow()
|
||||
resources = {'goals': 2, 'assists': 1}
|
||||
exp_date1 = datetime.datetime(2016, 3, 31, 14, 30)
|
||||
res1 = self._create_test_reservation(resources, exp_date1)
|
||||
exp_date2 = datetime.datetime(2015, 3, 31, 14, 30)
|
||||
res2 = self._create_test_reservation(resources, exp_date2)
|
||||
self.assertEqual(2, self._test_class.delete_expired(
|
||||
self.context, dt, None))
|
||||
objs = self._test_class.get_objects(self.context,
|
||||
id=[res1.id, res2.id])
|
||||
self.assertEqual([], objs)
|
||||
|
||||
def test_reservation_synthetic_field(self):
|
||||
res = self._create_test_reservation()
|
||||
resource = 'test-res'
|
||||
res_delta = quota.ResourceDelta(self.context,
|
||||
resource=resource, reservation_id=res.id, amount='10')
|
||||
res_delta.create()
|
||||
obj = self._test_class.get_object(self.context, id=res.id)
|
||||
self.assertEqual(res_delta, obj.resource_deltas[0])
|
||||
res_delta.delete()
|
||||
obj.update()
|
||||
# NOTE(manjeets) update on reservation should reflect
|
||||
# changes on synthetic field when it is deleted.
|
||||
obj = self._test_class.get_object(self.context, id=res.id)
|
||||
self.assertEqual([], obj.resource_deltas)
|
||||
|
||||
|
||||
class QuotaObjectIfaceTestCase(obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = quota.Quota
|
||||
|
||||
|
||||
class QuotaDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = quota.Quota
|
||||
|
||||
|
||||
class QuotaUsageObjectIfaceTestCase(obj_test_base.BaseObjectIfaceTestCase):
|
||||
|
||||
_test_class = quota.QuotaUsage
|
||||
|
||||
|
||||
class QuotaUsageDbObjectTestCase(obj_test_base.BaseDbObjectTestCase,
|
||||
testlib_api.SqlTestCase):
|
||||
|
||||
_test_class = quota.QuotaUsage
|
||||
|
||||
def _test_get_object_dirty_protected(self, obj, dirty=True):
|
||||
obj.create()
|
||||
obj.dirty = dirty
|
||||
obj.update()
|
||||
new = self._test_class.get_object_dirty_protected(
|
||||
self.context,
|
||||
**obj._get_composite_keys())
|
||||
self.assertEqual(obj, new)
|
||||
self.assertEqual(dirty, new.dirty)
|
||||
|
||||
def test_get_object_dirty_protected(self):
|
||||
obj = self._make_object(self.obj_fields[0])
|
||||
obj1 = self._make_object(self.obj_fields[1])
|
||||
self._test_get_object_dirty_protected(obj, dirty=False)
|
||||
self._test_get_object_dirty_protected(obj1)
|
|
@ -26,6 +26,9 @@ from neutron.tests.unit import quota as test_quota
|
|||
from neutron.tests.unit import testlib_api
|
||||
|
||||
|
||||
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
|
||||
|
||||
|
||||
meh_quota_flag = 'quota_meh'
|
||||
meh_quota_opts = [cfg.IntOpt(meh_quota_flag, default=99)]
|
||||
|
||||
|
@ -90,6 +93,7 @@ class TestTrackedResource(testlib_api.SqlTestCaseLight):
|
|||
def setUp(self):
|
||||
base.BaseTestCase.config_parse()
|
||||
cfg.CONF.register_opts(meh_quota_opts, 'QUOTAS')
|
||||
cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS)
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
self.resource = 'meh'
|
||||
self.other_resource = 'othermeh'
|
||||
|
|
Loading…
Reference in New Issue