Merge "OVO for Quotas and Reservation"

This commit is contained in:
Jenkins 2017-03-26 01:34:16 +00:00 committed by Gerrit Code Review
commit b9e3fc64b2
8 changed files with 392 additions and 138 deletions

View File

@ -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)

View File

@ -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.

152
neutron/objects/quota.py Normal file
View File

@ -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)

View File

@ -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(

View File

@ -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)}

View File

@ -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',

View File

@ -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)

View File

@ -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'