objects: Add filter query hook to NeutronDbObject
Extensions add query hooks to db models. This patch adds support for filtering hooks for objects, which means we're able to register new name that can be used for filtering when getting an object from database. Change-Id: I106a21efe42f848ee18cb8349fe89afd404d25b1 Partially-Implements: blueprint adopt-oslo-versioned-objects-for-db
This commit is contained in:
parent
260ce9c161
commit
3ead5f959e
|
@ -27,7 +27,10 @@ from neutron.db import model_base
|
|||
from neutron.objects.db import api as obj_db_api
|
||||
from neutron.objects.extensions import standardattributes
|
||||
|
||||
_NO_DB_MODEL = object()
|
||||
|
||||
|
||||
#TODO(jlibosva): Move these classes to exceptions module
|
||||
class NeutronObjectUpdateForbidden(exceptions.NeutronException):
|
||||
message = _("Unable to update the following object fields: %(fields)s")
|
||||
|
||||
|
@ -44,6 +47,10 @@ class NeutronDbObjectDuplicateEntry(exceptions.Conflict):
|
|||
values=db_exception.value)
|
||||
|
||||
|
||||
class NeutronDbObjectNotFoundByModel(exceptions.NotFound):
|
||||
message = _("NeutronDbObject not found by model %(model)s.")
|
||||
|
||||
|
||||
class NeutronPrimaryKeyMissing(exceptions.BadRequest):
|
||||
message = _("For class %(object_type)s missing primary keys: "
|
||||
"%(missing_keys)s")
|
||||
|
@ -69,6 +76,19 @@ def get_updatable_fields(cls, fields):
|
|||
return fields
|
||||
|
||||
|
||||
def get_object_class_by_model(model):
|
||||
for obj_class in obj_base.VersionedObjectRegistry.obj_classes().values():
|
||||
obj_class = obj_class[0]
|
||||
if getattr(obj_class, 'db_model', _NO_DB_MODEL) is model:
|
||||
return obj_class
|
||||
raise NeutronDbObjectNotFoundByModel(model=model.__name__)
|
||||
|
||||
|
||||
def register_filter_hook_on_model(model, filter_name):
|
||||
obj_class = get_object_class_by_model(model)
|
||||
obj_class.add_extra_filter_name(filter_name)
|
||||
|
||||
|
||||
class Pager(object):
|
||||
'''
|
||||
This class represents a pager object. It is consumed by get_objects to
|
||||
|
@ -98,6 +118,7 @@ class NeutronObject(obj_base.VersionedObject,
|
|||
obj_base.ComparableVersionedObject):
|
||||
|
||||
synthetic_fields = []
|
||||
extra_filter_names = set()
|
||||
|
||||
def __init__(self, context=None, **kwargs):
|
||||
super(NeutronObject, self).__init__(context, **kwargs)
|
||||
|
@ -138,10 +159,24 @@ class NeutronObject(obj_base.VersionedObject,
|
|||
def get_object(cls, context, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def add_extra_filter_name(cls, filter_name):
|
||||
"""Register filter passed from API layer.
|
||||
|
||||
:param filter_name: Name of the filter passed in the URL
|
||||
|
||||
Filter names are validated in validate_filters() method which by
|
||||
default allows filters based on fields' names. Extensions can create
|
||||
new filter names. Such names must be registered to particular object
|
||||
with this method.
|
||||
"""
|
||||
cls.extra_filter_names.add(filter_name)
|
||||
|
||||
@classmethod
|
||||
def validate_filters(cls, **kwargs):
|
||||
bad_filters = [key for key in kwargs
|
||||
if key not in cls.fields or cls.is_synthetic(key)]
|
||||
bad_filters = {key for key in kwargs
|
||||
if key not in cls.fields or cls.is_synthetic(key)}
|
||||
bad_filters.difference_update(cls.extra_filter_names)
|
||||
if bad_filters:
|
||||
bad_filters = ', '.join(bad_filters)
|
||||
msg = _("'%s' is not supported for filtering") % bad_filters
|
||||
|
@ -174,6 +209,8 @@ class DeclarativeObject(abc.ABCMeta):
|
|||
if (hasattr(cls, 'has_standard_attributes') and
|
||||
cls.has_standard_attributes()):
|
||||
standardattributes.add_standard_attributes(cls)
|
||||
# Instantiate extra filters per class
|
||||
cls.extra_filter_names = set()
|
||||
|
||||
|
||||
@six.add_metaclass(DeclarativeObject)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import importlib
|
||||
import os
|
||||
import platform
|
||||
|
@ -31,6 +32,7 @@ import unittest2
|
|||
|
||||
import neutron
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.db import common_db_mixin
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.common import ipv6_utils
|
||||
|
||||
|
@ -119,6 +121,17 @@ class SafeCleanupFixture(fixtures.Fixture):
|
|||
self.addCleanup(cleanUp)
|
||||
|
||||
|
||||
class CommonDbMixinHooksFixture(fixtures.Fixture):
|
||||
def _setUp(self):
|
||||
self.original_hooks = common_db_mixin.CommonDbMixin._model_query_hooks
|
||||
self.addCleanup(self.restore_hooks)
|
||||
common_db_mixin.CommonDbMixin._model_query_hooks = copy.deepcopy(
|
||||
common_db_mixin.CommonDbMixin._model_query_hooks)
|
||||
|
||||
def restore_hooks(self):
|
||||
common_db_mixin.CommonDbMixin._model_query_hooks = self.original_hooks
|
||||
|
||||
|
||||
from neutron.common import utils
|
||||
|
||||
|
||||
|
|
|
@ -22,10 +22,12 @@ from oslo_utils import uuidutils
|
|||
from oslo_versionedobjects import base as obj_base
|
||||
from oslo_versionedobjects import fields as obj_fields
|
||||
from oslo_versionedobjects import fixture
|
||||
import testtools
|
||||
|
||||
from neutron.common import constants
|
||||
from neutron.common import utils as common_utils
|
||||
from neutron import context
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import models_v2
|
||||
from neutron.objects import base
|
||||
from neutron.objects import common_types
|
||||
|
@ -682,6 +684,10 @@ class BaseDbObjectMultipleForeignKeysTestCase(_BaseObjectTestCase,
|
|||
|
||||
|
||||
class BaseDbObjectTestCase(_BaseObjectTestCase):
|
||||
def setUp(self):
|
||||
super(BaseDbObjectTestCase, self).setUp()
|
||||
self.useFixture(tools.CommonDbMixinHooksFixture())
|
||||
|
||||
def _create_test_network(self):
|
||||
# TODO(ihrachys): replace with network.create() once we get an object
|
||||
# implementation for networks
|
||||
|
@ -807,3 +813,60 @@ class BaseDbObjectTestCase(_BaseObjectTestCase):
|
|||
obj = self._test_class.get_object(self.context,
|
||||
**obj._get_composite_keys())
|
||||
self.assertEqual(2, mock_commit.call_count)
|
||||
|
||||
def test_get_objects_supports_extra_filtername(self):
|
||||
self.filtered_args = None
|
||||
def foo_filter(query, filters):
|
||||
self.filtered_args = filters
|
||||
return query
|
||||
|
||||
self.obj_registry.register(self._test_class)
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_model_query_hook(
|
||||
self._test_class.db_model,
|
||||
'foo_filter',
|
||||
None,
|
||||
None,
|
||||
foo_filter)
|
||||
base.register_filter_hook_on_model(self._test_class.db_model, 'foo')
|
||||
|
||||
self._test_class.get_objects(self.context, foo=42)
|
||||
self.assertEqual({'foo': [42]}, self.filtered_args)
|
||||
|
||||
|
||||
class UniqueObjectBase(test_base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(UniqueObjectBase, self).setUp()
|
||||
obj_registry = self.useFixture(
|
||||
fixture.VersionedObjectRegistryFixture())
|
||||
self.db_model = FakeModel
|
||||
|
||||
class RegisteredObject(base.NeutronDbObject):
|
||||
db_model = self.db_model
|
||||
|
||||
self.registered_object = RegisteredObject
|
||||
obj_registry.register(self.registered_object)
|
||||
|
||||
|
||||
class GetObjectClassByModelTestCase(UniqueObjectBase):
|
||||
def setUp(self):
|
||||
super(GetObjectClassByModelTestCase, self).setUp()
|
||||
self.not_registered_object = FakeSmallNeutronObject
|
||||
|
||||
def test_object_found_by_model(self):
|
||||
found_obj = base.get_object_class_by_model(
|
||||
self.registered_object.db_model)
|
||||
self.assertIs(self.registered_object, found_obj)
|
||||
|
||||
def test_not_registed_object_raises_exception(self):
|
||||
with testtools.ExpectedException(base.NeutronDbObjectNotFoundByModel):
|
||||
base.get_object_class_by_model(self.not_registered_object.db_model)
|
||||
|
||||
|
||||
class RegisterFilterHookOnModelTestCase(UniqueObjectBase):
|
||||
def test_filtername_is_added(self):
|
||||
filter_name = 'foo'
|
||||
self.assertNotIn(
|
||||
filter_name, self.registered_object.extra_filter_names)
|
||||
base.register_filter_hook_on_model(
|
||||
FakeNeutronObject.db_model, filter_name)
|
||||
self.assertIn(filter_name, self.registered_object.extra_filter_names)
|
||||
|
|
Loading…
Reference in New Issue