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:
Jakub Libosvar 2016-06-10 12:24:17 +00:00
parent 260ce9c161
commit 3ead5f959e
3 changed files with 115 additions and 2 deletions

View File

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

View File

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

View File

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