Extract CommonDBMixin to a separate file
db_base_plugin_v2 imports too much modules that are not necessary usually, so extract CommonDBMixin in different file. Plus using db_base_plugin_v2 for some types of modules can lead to cycles in imports, this refactoring should resolve the issue. Closes-Bug: #1340145 Change-Id: Idb027d7c5cee2d5bc7598f805c56c55fd4aca048
This commit is contained in:
parent
d7bd5ce0cd
commit
2b58062cee
197
neutron/db/common_db_mixin.py
Normal file
197
neutron/db/common_db_mixin.py
Normal file
@ -0,0 +1,197 @@
|
||||
# Copyright (c) 2014 OpenStack Foundation.
|
||||
# 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 weakref
|
||||
|
||||
from oslo.config import cfg
|
||||
from sqlalchemy import sql
|
||||
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.db import sqlalchemyutils
|
||||
from neutron.plugins.common import constants as service_constants
|
||||
|
||||
|
||||
class CommonDbMixin(object):
|
||||
"""Common methods used in core and service plugins."""
|
||||
# Plugins, mixin classes implementing extension will register
|
||||
# hooks into the dict below for "augmenting" the "core way" of
|
||||
# building a query for retrieving objects from a model class.
|
||||
# To this aim, the register_model_query_hook and unregister_query_hook
|
||||
# from this class should be invoked
|
||||
_model_query_hooks = {}
|
||||
|
||||
# This dictionary will store methods for extending attributes of
|
||||
# api resources. Mixins can use this dict for adding their own methods
|
||||
# TODO(salvatore-orlando): Avoid using class-level variables
|
||||
_dict_extend_functions = {}
|
||||
|
||||
@classmethod
|
||||
def register_model_query_hook(cls, model, name, query_hook, filter_hook,
|
||||
result_filters=None):
|
||||
"""Register a hook to be invoked when a query is executed.
|
||||
|
||||
Add the hooks to the _model_query_hooks dict. Models are the keys
|
||||
of this dict, whereas the value is another dict mapping hook names to
|
||||
callables performing the hook.
|
||||
Each hook has a "query" component, used to build the query expression
|
||||
and a "filter" component, which is used to build the filter expression.
|
||||
|
||||
Query hooks take as input the query being built and return a
|
||||
transformed query expression.
|
||||
|
||||
Filter hooks take as input the filter expression being built and return
|
||||
a transformed filter expression
|
||||
"""
|
||||
model_hooks = cls._model_query_hooks.get(model)
|
||||
if not model_hooks:
|
||||
# add key to dict
|
||||
model_hooks = {}
|
||||
cls._model_query_hooks[model] = model_hooks
|
||||
model_hooks[name] = {'query': query_hook, 'filter': filter_hook,
|
||||
'result_filters': result_filters}
|
||||
|
||||
@property
|
||||
def safe_reference(self):
|
||||
"""Return a weakref to the instance.
|
||||
|
||||
Minimize the potential for the instance persisting
|
||||
unnecessarily in memory by returning a weakref proxy that
|
||||
won't prevent deallocation.
|
||||
"""
|
||||
return weakref.proxy(self)
|
||||
|
||||
def _model_query(self, context, model):
|
||||
query = context.session.query(model)
|
||||
# define basic filter condition for model query
|
||||
# NOTE(jkoelker) non-admin queries are scoped to their tenant_id
|
||||
# NOTE(salvatore-orlando): unless the model allows for shared objects
|
||||
query_filter = None
|
||||
if not context.is_admin and hasattr(model, 'tenant_id'):
|
||||
if hasattr(model, 'shared'):
|
||||
query_filter = ((model.tenant_id == context.tenant_id) |
|
||||
(model.shared == sql.true()))
|
||||
else:
|
||||
query_filter = (model.tenant_id == context.tenant_id)
|
||||
# Execute query hooks registered from mixins and plugins
|
||||
for _name, hooks in self._model_query_hooks.get(model,
|
||||
{}).iteritems():
|
||||
query_hook = hooks.get('query')
|
||||
if isinstance(query_hook, basestring):
|
||||
query_hook = getattr(self, query_hook, None)
|
||||
if query_hook:
|
||||
query = query_hook(context, model, query)
|
||||
|
||||
filter_hook = hooks.get('filter')
|
||||
if isinstance(filter_hook, basestring):
|
||||
filter_hook = getattr(self, filter_hook, None)
|
||||
if filter_hook:
|
||||
query_filter = filter_hook(context, model, query_filter)
|
||||
|
||||
# NOTE(salvatore-orlando): 'if query_filter' will try to evaluate the
|
||||
# condition, raising an exception
|
||||
if query_filter is not None:
|
||||
query = query.filter(query_filter)
|
||||
return query
|
||||
|
||||
def _fields(self, resource, fields):
|
||||
if fields:
|
||||
return dict(((key, item) for key, item in resource.items()
|
||||
if key in fields))
|
||||
return resource
|
||||
|
||||
def _get_tenant_id_for_create(self, context, resource):
|
||||
if context.is_admin and 'tenant_id' in resource:
|
||||
tenant_id = resource['tenant_id']
|
||||
elif ('tenant_id' in resource and
|
||||
resource['tenant_id'] != context.tenant_id):
|
||||
reason = _('Cannot create resource for another tenant')
|
||||
raise n_exc.AdminRequired(reason=reason)
|
||||
else:
|
||||
tenant_id = context.tenant_id
|
||||
return tenant_id
|
||||
|
||||
def _get_by_id(self, context, model, id):
|
||||
query = self._model_query(context, model)
|
||||
return query.filter(model.id == id).one()
|
||||
|
||||
def _apply_filters_to_query(self, query, model, filters):
|
||||
if filters:
|
||||
for key, value in filters.iteritems():
|
||||
column = getattr(model, key, None)
|
||||
if column:
|
||||
query = query.filter(column.in_(value))
|
||||
for _name, hooks in self._model_query_hooks.get(model,
|
||||
{}).iteritems():
|
||||
result_filter = hooks.get('result_filters', None)
|
||||
if isinstance(result_filter, basestring):
|
||||
result_filter = getattr(self, result_filter, None)
|
||||
|
||||
if result_filter:
|
||||
query = result_filter(query, filters)
|
||||
return query
|
||||
|
||||
def _apply_dict_extend_functions(self, resource_type,
|
||||
response, db_object):
|
||||
for func in self._dict_extend_functions.get(
|
||||
resource_type, []):
|
||||
args = (response, db_object)
|
||||
if isinstance(func, basestring):
|
||||
func = getattr(self, func, None)
|
||||
else:
|
||||
# must call unbound method - use self as 1st argument
|
||||
args = (self,) + args
|
||||
if func:
|
||||
func(*args)
|
||||
|
||||
def _get_collection_query(self, context, model, filters=None,
|
||||
sorts=None, limit=None, marker_obj=None,
|
||||
page_reverse=False):
|
||||
collection = self._model_query(context, model)
|
||||
collection = self._apply_filters_to_query(collection, model, filters)
|
||||
if limit and page_reverse and sorts:
|
||||
sorts = [(s[0], not s[1]) for s in sorts]
|
||||
collection = sqlalchemyutils.paginate_query(collection, model, limit,
|
||||
sorts,
|
||||
marker_obj=marker_obj)
|
||||
return collection
|
||||
|
||||
def _get_collection(self, context, model, dict_func, filters=None,
|
||||
fields=None, sorts=None, limit=None, marker_obj=None,
|
||||
page_reverse=False):
|
||||
query = self._get_collection_query(context, model, filters=filters,
|
||||
sorts=sorts,
|
||||
limit=limit,
|
||||
marker_obj=marker_obj,
|
||||
page_reverse=page_reverse)
|
||||
items = [dict_func(c, fields) for c in query]
|
||||
if limit and page_reverse:
|
||||
items.reverse()
|
||||
return items
|
||||
|
||||
def _get_collection_count(self, context, model, filters=None):
|
||||
return self._get_collection_query(context, model, filters).count()
|
||||
|
||||
def _get_marker_obj(self, context, resource, limit, marker):
|
||||
if limit and marker:
|
||||
return getattr(self, '_get_%s' % resource)(context, marker)
|
||||
return None
|
||||
|
||||
def _filter_non_model_columns(self, data, model):
|
||||
"""Remove all the attributes from data which are not columns of
|
||||
the model passed as second parameter.
|
||||
"""
|
||||
columns = [c.name for c in model.__table__.columns]
|
||||
return dict((k, v) for (k, v) in
|
||||
data.iteritems() if k in columns)
|
@ -29,6 +29,7 @@ from neutron.common import exceptions as n_exc
|
||||
from neutron.common import ipv6_utils
|
||||
from neutron import context as ctx
|
||||
from neutron.db import api as db
|
||||
from neutron.db import common_db_mixin
|
||||
from neutron.db import models_v2
|
||||
from neutron.db import sqlalchemyutils
|
||||
from neutron.extensions import l3
|
||||
@ -52,182 +53,8 @@ LOG = logging.getLogger(__name__)
|
||||
AUTO_DELETE_PORT_OWNERS = [constants.DEVICE_OWNER_DHCP]
|
||||
|
||||
|
||||
class CommonDbMixin(object):
|
||||
"""Common methods used in core and service plugins."""
|
||||
# Plugins, mixin classes implementing extension will register
|
||||
# hooks into the dict below for "augmenting" the "core way" of
|
||||
# building a query for retrieving objects from a model class.
|
||||
# To this aim, the register_model_query_hook and unregister_query_hook
|
||||
# from this class should be invoked
|
||||
_model_query_hooks = {}
|
||||
|
||||
# This dictionary will store methods for extending attributes of
|
||||
# api resources. Mixins can use this dict for adding their own methods
|
||||
# TODO(salvatore-orlando): Avoid using class-level variables
|
||||
_dict_extend_functions = {}
|
||||
|
||||
@classmethod
|
||||
def register_model_query_hook(cls, model, name, query_hook, filter_hook,
|
||||
result_filters=None):
|
||||
"""Register a hook to be invoked when a query is executed.
|
||||
|
||||
Add the hooks to the _model_query_hooks dict. Models are the keys
|
||||
of this dict, whereas the value is another dict mapping hook names to
|
||||
callables performing the hook.
|
||||
Each hook has a "query" component, used to build the query expression
|
||||
and a "filter" component, which is used to build the filter expression.
|
||||
|
||||
Query hooks take as input the query being built and return a
|
||||
transformed query expression.
|
||||
|
||||
Filter hooks take as input the filter expression being built and return
|
||||
a transformed filter expression
|
||||
"""
|
||||
model_hooks = cls._model_query_hooks.get(model)
|
||||
if not model_hooks:
|
||||
# add key to dict
|
||||
model_hooks = {}
|
||||
cls._model_query_hooks[model] = model_hooks
|
||||
model_hooks[name] = {'query': query_hook, 'filter': filter_hook,
|
||||
'result_filters': result_filters}
|
||||
|
||||
@property
|
||||
def safe_reference(self):
|
||||
"""Return a weakref to the instance.
|
||||
|
||||
Minimize the potential for the instance persisting
|
||||
unnecessarily in memory by returning a weakref proxy that
|
||||
won't prevent deallocation.
|
||||
"""
|
||||
return weakref.proxy(self)
|
||||
|
||||
def _model_query(self, context, model):
|
||||
query = context.session.query(model)
|
||||
# define basic filter condition for model query
|
||||
# NOTE(jkoelker) non-admin queries are scoped to their tenant_id
|
||||
# NOTE(salvatore-orlando): unless the model allows for shared objects
|
||||
query_filter = None
|
||||
if not context.is_admin and hasattr(model, 'tenant_id'):
|
||||
if hasattr(model, 'shared'):
|
||||
query_filter = ((model.tenant_id == context.tenant_id) |
|
||||
(model.shared == sql.true()))
|
||||
else:
|
||||
query_filter = (model.tenant_id == context.tenant_id)
|
||||
# Execute query hooks registered from mixins and plugins
|
||||
for _name, hooks in self._model_query_hooks.get(model,
|
||||
{}).iteritems():
|
||||
query_hook = hooks.get('query')
|
||||
if isinstance(query_hook, basestring):
|
||||
query_hook = getattr(self, query_hook, None)
|
||||
if query_hook:
|
||||
query = query_hook(context, model, query)
|
||||
|
||||
filter_hook = hooks.get('filter')
|
||||
if isinstance(filter_hook, basestring):
|
||||
filter_hook = getattr(self, filter_hook, None)
|
||||
if filter_hook:
|
||||
query_filter = filter_hook(context, model, query_filter)
|
||||
|
||||
# NOTE(salvatore-orlando): 'if query_filter' will try to evaluate the
|
||||
# condition, raising an exception
|
||||
if query_filter is not None:
|
||||
query = query.filter(query_filter)
|
||||
return query
|
||||
|
||||
def _fields(self, resource, fields):
|
||||
if fields:
|
||||
return dict(((key, item) for key, item in resource.items()
|
||||
if key in fields))
|
||||
return resource
|
||||
|
||||
def _get_tenant_id_for_create(self, context, resource):
|
||||
if context.is_admin and 'tenant_id' in resource:
|
||||
tenant_id = resource['tenant_id']
|
||||
elif ('tenant_id' in resource and
|
||||
resource['tenant_id'] != context.tenant_id):
|
||||
reason = _('Cannot create resource for another tenant')
|
||||
raise n_exc.AdminRequired(reason=reason)
|
||||
else:
|
||||
tenant_id = context.tenant_id
|
||||
return tenant_id
|
||||
|
||||
def _get_by_id(self, context, model, id):
|
||||
query = self._model_query(context, model)
|
||||
return query.filter(model.id == id).one()
|
||||
|
||||
def _apply_filters_to_query(self, query, model, filters):
|
||||
if filters:
|
||||
for key, value in filters.iteritems():
|
||||
column = getattr(model, key, None)
|
||||
if column:
|
||||
query = query.filter(column.in_(value))
|
||||
for _name, hooks in self._model_query_hooks.get(model,
|
||||
{}).iteritems():
|
||||
result_filter = hooks.get('result_filters', None)
|
||||
if isinstance(result_filter, basestring):
|
||||
result_filter = getattr(self, result_filter, None)
|
||||
|
||||
if result_filter:
|
||||
query = result_filter(query, filters)
|
||||
return query
|
||||
|
||||
def _apply_dict_extend_functions(self, resource_type,
|
||||
response, db_object):
|
||||
for func in self._dict_extend_functions.get(
|
||||
resource_type, []):
|
||||
args = (response, db_object)
|
||||
if isinstance(func, basestring):
|
||||
func = getattr(self, func, None)
|
||||
else:
|
||||
# must call unbound method - use self as 1st argument
|
||||
args = (self,) + args
|
||||
if func:
|
||||
func(*args)
|
||||
|
||||
def _get_collection_query(self, context, model, filters=None,
|
||||
sorts=None, limit=None, marker_obj=None,
|
||||
page_reverse=False):
|
||||
collection = self._model_query(context, model)
|
||||
collection = self._apply_filters_to_query(collection, model, filters)
|
||||
if limit and page_reverse and sorts:
|
||||
sorts = [(s[0], not s[1]) for s in sorts]
|
||||
collection = sqlalchemyutils.paginate_query(collection, model, limit,
|
||||
sorts,
|
||||
marker_obj=marker_obj)
|
||||
return collection
|
||||
|
||||
def _get_collection(self, context, model, dict_func, filters=None,
|
||||
fields=None, sorts=None, limit=None, marker_obj=None,
|
||||
page_reverse=False):
|
||||
query = self._get_collection_query(context, model, filters=filters,
|
||||
sorts=sorts,
|
||||
limit=limit,
|
||||
marker_obj=marker_obj,
|
||||
page_reverse=page_reverse)
|
||||
items = [dict_func(c, fields) for c in query]
|
||||
if limit and page_reverse:
|
||||
items.reverse()
|
||||
return items
|
||||
|
||||
def _get_collection_count(self, context, model, filters=None):
|
||||
return self._get_collection_query(context, model, filters).count()
|
||||
|
||||
def _get_marker_obj(self, context, resource, limit, marker):
|
||||
if limit and marker:
|
||||
return getattr(self, '_get_%s' % resource)(context, marker)
|
||||
return None
|
||||
|
||||
def _filter_non_model_columns(self, data, model):
|
||||
"""Remove all the attributes from data which are not columns of
|
||||
the model passed as second parameter.
|
||||
"""
|
||||
columns = [c.name for c in model.__table__.columns]
|
||||
return dict((k, v) for (k, v) in
|
||||
data.iteritems() if k in columns)
|
||||
|
||||
|
||||
class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
||||
CommonDbMixin):
|
||||
common_db_mixin.CommonDbMixin):
|
||||
"""V2 Neutron plugin interface implementation using SQLAlchemy models.
|
||||
|
||||
Whenever a non-read call happens the plugin will call an event handler
|
||||
|
@ -20,7 +20,7 @@ from sqlalchemy.ext.orderinglist import ordering_list
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.orm import exc
|
||||
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import common_db_mixin as base_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import firewall
|
||||
|
@ -21,7 +21,7 @@ from sqlalchemy.orm import validates
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import common_db_mixin as base_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.db import servicetype_db as st_db
|
||||
|
@ -21,7 +21,7 @@ from sqlalchemy import orm
|
||||
from neutron.api.rpc.agentnotifiers import metering_rpc_agent_api
|
||||
from neutron.common import constants
|
||||
from neutron.db import api as dbapi
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import common_db_mixin as base_db
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
|
@ -22,7 +22,7 @@ from sqlalchemy.orm import exc
|
||||
|
||||
from neutron.common import constants as n_constants
|
||||
from neutron.db import api as qdbapi
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import common_db_mixin as base_db
|
||||
from neutron.db import l3_agentschedulers_db as l3_agent_db
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import model_base
|
||||
|
@ -21,7 +21,7 @@ from sqlalchemy import sql
|
||||
|
||||
from neutron.common import constants as const
|
||||
from neutron.db import agents_db
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import common_db_mixin as base_db
|
||||
from neutron.db import models_v2
|
||||
from neutron.openstack.common import jsonutils
|
||||
from neutron.openstack.common import timeutils
|
||||
|
@ -14,7 +14,7 @@
|
||||
#
|
||||
# @author: Ronak Shah, Nuage Networks, Alcatel-Lucent USA Inc.
|
||||
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import common_db_mixin
|
||||
from neutron.plugins.nuage import nuage_models
|
||||
|
||||
|
||||
@ -130,7 +130,7 @@ def get_net_partition_by_id(session, id):
|
||||
|
||||
def get_net_partitions(session, filters=None, fields=None):
|
||||
query = session.query(nuage_models.NetPartition)
|
||||
common_db = db_base_plugin_v2.CommonDbMixin()
|
||||
common_db = common_db_mixin.CommonDbMixin()
|
||||
query = common_db._apply_filters_to_query(query,
|
||||
nuage_models.NetPartition,
|
||||
filters)
|
||||
|
@ -29,7 +29,6 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApicL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
db_base_plugin_v2.CommonDbMixin,
|
||||
extraroute_db.ExtraRoute_db_mixin,
|
||||
l3_gwmode_db.L3_NAT_db_mixin):
|
||||
"""Implementation of the APIC L3 Router Service Plugin.
|
||||
|
@ -22,7 +22,7 @@ from neutron.common import constants as q_const
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron.common import topics
|
||||
from neutron.db import api as qdbapi
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import common_db_mixin
|
||||
from neutron.db import extraroute_db
|
||||
from neutron.db import l3_agentschedulers_db
|
||||
from neutron.db import l3_gwmode_db
|
||||
@ -38,7 +38,7 @@ class L3RouterPluginRpcCallbacks(n_rpc.RpcCallback,
|
||||
RPC_API_VERSION = '1.1'
|
||||
|
||||
|
||||
class L3RouterPlugin(db_base_plugin_v2.CommonDbMixin,
|
||||
class L3RouterPlugin(common_db_mixin.CommonDbMixin,
|
||||
extraroute_db.ExtraRoute_db_mixin,
|
||||
l3_gwmode_db.L3_NAT_db_mixin,
|
||||
l3_agentschedulers_db.L3AgentSchedulerDbMixin):
|
||||
|
@ -23,7 +23,7 @@ from testtools import matchers
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron import context
|
||||
from neutron.db import api as db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import common_db_mixin
|
||||
from neutron.plugins.cisco.common import cisco_constants
|
||||
from neutron.plugins.cisco.common import cisco_exceptions as c_exc
|
||||
from neutron.plugins.cisco.db import n1kv_db_v2
|
||||
@ -769,7 +769,7 @@ class PolicyProfileTests(base.BaseTestCase):
|
||||
|
||||
class ProfileBindingTests(base.BaseTestCase,
|
||||
n1kv_db_v2.NetworkProfile_db_mixin,
|
||||
db_base_plugin_v2.CommonDbMixin):
|
||||
common_db_mixin.CommonDbMixin):
|
||||
|
||||
def setUp(self):
|
||||
super(ProfileBindingTests, self).setUp()
|
||||
|
@ -27,6 +27,7 @@ from neutron.common import constants as l3_constants
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron import context
|
||||
from neutron.db import api as qdbapi
|
||||
from neutron.db import common_db_mixin
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.db import external_net_db
|
||||
from neutron.db import l3_agentschedulers_db
|
||||
@ -283,7 +284,7 @@ class TestNoL3NatPlugin(TestL3NatBasePlugin):
|
||||
|
||||
# A L3 routing service plugin class for tests with plugins that
|
||||
# delegate away L3 routing functionality
|
||||
class TestL3NatServicePlugin(db_base_plugin_v2.CommonDbMixin,
|
||||
class TestL3NatServicePlugin(common_db_mixin.CommonDbMixin,
|
||||
l3_db.L3_NAT_db_mixin):
|
||||
|
||||
supported_extension_aliases = ["router"]
|
||||
|
Loading…
Reference in New Issue
Block a user