Merge "Refactor db_base_plugin_v2 and to remove code duplication"
This commit is contained in:
commit
cf7bd0a2db
@ -49,49 +49,39 @@ AGENT_OWNER_PREFIX = 'network:'
|
|||||||
AUTO_DELETE_PORT_OWNERS = ['network:dhcp']
|
AUTO_DELETE_PORT_OWNERS = ['network:dhcp']
|
||||||
|
|
||||||
|
|
||||||
class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
class CommonDbMixin(object):
|
||||||
"""V2 Quantum plugin interface implementation using SQLAlchemy models.
|
"""Common methods used in core and service plugins."""
|
||||||
|
|
||||||
Whenever a non-read call happens the plugin will call an event handler
|
|
||||||
class method (e.g., network_created()). The result is that this class
|
|
||||||
can be sub-classed by other classes that add custom behaviors on certain
|
|
||||||
events.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# This attribute specifies whether the plugin supports or not
|
|
||||||
# bulk/pagination/sorting operations. Name mangling is used in
|
|
||||||
# order to ensure it is qualified by class
|
|
||||||
__native_bulk_support = True
|
|
||||||
__native_pagination_support = True
|
|
||||||
__native_sorting_support = True
|
|
||||||
# Plugins, mixin classes implementing extension will register
|
# Plugins, mixin classes implementing extension will register
|
||||||
# hooks into the dict below for "augmenting" the "core way" of
|
# hooks into the dict below for "augmenting" the "core way" of
|
||||||
# building a query for retrieving objects from a model class.
|
# building a query for retrieving objects from a model class.
|
||||||
# To this aim, the register_model_query_hook and unregister_query_hook
|
# To this aim, the register_model_query_hook and unregister_query_hook
|
||||||
# from this class should be invoked
|
# from this class should be invoked
|
||||||
_model_query_hooks = {}
|
_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 = {}
|
|
||||||
|
|
||||||
def __init__(self):
|
@classmethod
|
||||||
# NOTE(jkoelker) This is an incomlete implementation. Subclasses
|
def register_model_query_hook(cls, model, name, query_hook, filter_hook,
|
||||||
# must override __init__ and setup the database
|
result_filters=None):
|
||||||
# and not call into this class's __init__.
|
"""Register a hook to be invoked when a query is executed.
|
||||||
# This connection is setup as memory for the tests.
|
|
||||||
db.configure_db()
|
|
||||||
|
|
||||||
def _get_tenant_id_for_create(self, context, resource):
|
Add the hooks to the _model_query_hooks dict. Models are the keys
|
||||||
if context.is_admin and 'tenant_id' in resource:
|
of this dict, whereas the value is another dict mapping hook names to
|
||||||
tenant_id = resource['tenant_id']
|
callables performing the hook.
|
||||||
elif ('tenant_id' in resource and
|
Each hook has a "query" component, used to build the query expression
|
||||||
resource['tenant_id'] != context.tenant_id):
|
and a "filter" component, which is used to build the filter expression.
|
||||||
reason = _('Cannot create resource for another tenant')
|
|
||||||
raise q_exc.AdminRequired(reason=reason)
|
Query hooks take as input the query being built and return a
|
||||||
else:
|
transformed query expression.
|
||||||
tenant_id = context.tenant_id
|
|
||||||
return tenant_id
|
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}
|
||||||
|
|
||||||
def _model_query(self, context, model):
|
def _model_query(self, context, model):
|
||||||
query = context.session.query(model)
|
query = context.session.query(model)
|
||||||
@ -121,95 +111,27 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
query = query.filter(query_filter)
|
query = query.filter(query_filter)
|
||||||
return query
|
return query
|
||||||
|
|
||||||
@classmethod
|
def _fields(self, resource, fields):
|
||||||
def register_dict_extend_funcs(cls, resource, funcs):
|
if fields:
|
||||||
cur_funcs = cls._dict_extend_functions.get(resource, [])
|
return dict(((key, item) for key, item in resource.items()
|
||||||
cur_funcs.extend(funcs)
|
if key in fields))
|
||||||
cls._dict_extend_functions[resource] = cur_funcs
|
return resource
|
||||||
|
|
||||||
@classmethod
|
def _get_tenant_id_for_create(self, context, resource):
|
||||||
def register_model_query_hook(cls, model, name, query_hook, filter_hook,
|
if context.is_admin and 'tenant_id' in resource:
|
||||||
result_filters=None):
|
tenant_id = resource['tenant_id']
|
||||||
"""Register a hook to be invoked when a query is executed.
|
elif ('tenant_id' in resource and
|
||||||
|
resource['tenant_id'] != context.tenant_id):
|
||||||
Add the hooks to the _model_query_hooks dict. Models are the keys
|
reason = _('Cannot create resource for another tenant')
|
||||||
of this dict, whereas the value is another dict mapping hook names to
|
raise q_exc.AdminRequired(reason=reason)
|
||||||
callables performing the hook.
|
else:
|
||||||
Each hook has a "query" component, used to build the query expression
|
tenant_id = context.tenant_id
|
||||||
and a "filter" component, which is used to build the filter expression.
|
return tenant_id
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def _get_by_id(self, context, model, id):
|
def _get_by_id(self, context, model, id):
|
||||||
query = self._model_query(context, model)
|
query = self._model_query(context, model)
|
||||||
return query.filter(model.id == id).one()
|
return query.filter(model.id == id).one()
|
||||||
|
|
||||||
def _get_network(self, context, id):
|
|
||||||
try:
|
|
||||||
network = self._get_by_id(context, models_v2.Network, id)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise q_exc.NetworkNotFound(net_id=id)
|
|
||||||
return network
|
|
||||||
|
|
||||||
def _get_subnet(self, context, id):
|
|
||||||
try:
|
|
||||||
subnet = self._get_by_id(context, models_v2.Subnet, id)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
raise q_exc.SubnetNotFound(subnet_id=id)
|
|
||||||
return subnet
|
|
||||||
|
|
||||||
def _get_port(self, context, id):
|
|
||||||
try:
|
|
||||||
port = self._get_by_id(context, models_v2.Port, id)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
# NOTE(jkoelker) The PortNotFound exceptions requires net_id
|
|
||||||
# kwarg in order to set the message correctly
|
|
||||||
raise q_exc.PortNotFound(port_id=id, net_id=None)
|
|
||||||
return port
|
|
||||||
|
|
||||||
def _get_dns_by_subnet(self, context, subnet_id):
|
|
||||||
dns_qry = context.session.query(models_v2.DNSNameServer)
|
|
||||||
return dns_qry.filter_by(subnet_id=subnet_id).all()
|
|
||||||
|
|
||||||
def _get_route_by_subnet(self, context, subnet_id):
|
|
||||||
route_qry = context.session.query(models_v2.SubnetRoute)
|
|
||||||
return route_qry.filter_by(subnet_id=subnet_id).all()
|
|
||||||
|
|
||||||
def _get_subnets_by_network(self, context, network_id):
|
|
||||||
subnet_qry = context.session.query(models_v2.Subnet)
|
|
||||||
return subnet_qry.filter_by(network_id=network_id).all()
|
|
||||||
|
|
||||||
def _get_all_subnets(self, context):
|
|
||||||
# NOTE(salvatore-orlando): This query might end up putting
|
|
||||||
# a lot of stress on the db. Consider adding a cache layer
|
|
||||||
return context.session.query(models_v2.Subnet).all()
|
|
||||||
|
|
||||||
def _fields(self, resource, fields):
|
|
||||||
if fields:
|
|
||||||
return dict(((key, item) for key, item in resource.iteritems()
|
|
||||||
if key in fields))
|
|
||||||
return resource
|
|
||||||
|
|
||||||
def _apply_filters_to_query(self, query, model, filters):
|
def _apply_filters_to_query(self, query, model, filters):
|
||||||
if filters:
|
if filters:
|
||||||
for key, value in filters.iteritems():
|
for key, value in filters.iteritems():
|
||||||
@ -251,6 +173,90 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
def _get_collection_count(self, context, model, filters=None):
|
def _get_collection_count(self, context, model, filters=None):
|
||||||
return self._get_collection_query(context, model, filters).count()
|
return self._get_collection_query(context, model, filters).count()
|
||||||
|
|
||||||
|
|
||||||
|
class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2,
|
||||||
|
CommonDbMixin):
|
||||||
|
"""V2 Quantum plugin interface implementation using SQLAlchemy models.
|
||||||
|
|
||||||
|
Whenever a non-read call happens the plugin will call an event handler
|
||||||
|
class method (e.g., network_created()). The result is that this class
|
||||||
|
can be sub-classed by other classes that add custom behaviors on certain
|
||||||
|
events.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This attribute specifies whether the plugin supports or not
|
||||||
|
# bulk/pagination/sorting operations. Name mangling is used in
|
||||||
|
# order to ensure it is qualified by class
|
||||||
|
__native_bulk_support = True
|
||||||
|
__native_pagination_support = True
|
||||||
|
__native_sorting_support = True
|
||||||
|
|
||||||
|
# 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 = {}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# NOTE(jkoelker) This is an incomlete implementation. Subclasses
|
||||||
|
# must override __init__ and setup the database
|
||||||
|
# and not call into this class's __init__.
|
||||||
|
# This connection is setup as memory for the tests.
|
||||||
|
db.configure_db()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_dict_extend_funcs(cls, resource, funcs):
|
||||||
|
cur_funcs = cls._dict_extend_functions.get(resource, [])
|
||||||
|
cur_funcs.extend(funcs)
|
||||||
|
cls._dict_extend_functions[resource] = cur_funcs
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _get_network(self, context, id):
|
||||||
|
try:
|
||||||
|
network = self._get_by_id(context, models_v2.Network, id)
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise q_exc.NetworkNotFound(net_id=id)
|
||||||
|
return network
|
||||||
|
|
||||||
|
def _get_subnet(self, context, id):
|
||||||
|
try:
|
||||||
|
subnet = self._get_by_id(context, models_v2.Subnet, id)
|
||||||
|
except exc.NoResultFound:
|
||||||
|
raise q_exc.SubnetNotFound(subnet_id=id)
|
||||||
|
return subnet
|
||||||
|
|
||||||
|
def _get_port(self, context, id):
|
||||||
|
try:
|
||||||
|
port = self._get_by_id(context, models_v2.Port, id)
|
||||||
|
except exc.NoResultFound:
|
||||||
|
# NOTE(jkoelker) The PortNotFound exceptions requires net_id
|
||||||
|
# kwarg in order to set the message correctly
|
||||||
|
raise q_exc.PortNotFound(port_id=id, net_id=None)
|
||||||
|
return port
|
||||||
|
|
||||||
|
def _get_dns_by_subnet(self, context, subnet_id):
|
||||||
|
dns_qry = context.session.query(models_v2.DNSNameServer)
|
||||||
|
return dns_qry.filter_by(subnet_id=subnet_id).all()
|
||||||
|
|
||||||
|
def _get_route_by_subnet(self, context, subnet_id):
|
||||||
|
route_qry = context.session.query(models_v2.SubnetRoute)
|
||||||
|
return route_qry.filter_by(subnet_id=subnet_id).all()
|
||||||
|
|
||||||
|
def _get_subnets_by_network(self, context, network_id):
|
||||||
|
subnet_qry = context.session.query(models_v2.Subnet)
|
||||||
|
return subnet_qry.filter_by(network_id=network_id).all()
|
||||||
|
|
||||||
|
def _get_all_subnets(self, context):
|
||||||
|
# NOTE(salvatore-orlando): This query might end up putting
|
||||||
|
# a lot of stress on the db. Consider adding a cache layer
|
||||||
|
return context.session.query(models_v2.Subnet).all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_mac(context, network_id):
|
def _generate_mac(context, network_id):
|
||||||
base_mac = cfg.CONF.base_mac.split(':')
|
base_mac = cfg.CONF.base_mac.split(':')
|
||||||
|
@ -22,6 +22,7 @@ from sqlalchemy.orm import exc
|
|||||||
|
|
||||||
from quantum.api.v2 import attributes
|
from quantum.api.v2 import attributes
|
||||||
from quantum.common import exceptions as q_exc
|
from quantum.common import exceptions as q_exc
|
||||||
|
from quantum.db import db_base_plugin_v2 as base_db
|
||||||
from quantum.db import model_base
|
from quantum.db import model_base
|
||||||
from quantum.db import models_v2
|
from quantum.db import models_v2
|
||||||
from quantum.extensions import loadbalancer
|
from quantum.extensions import loadbalancer
|
||||||
@ -151,7 +152,8 @@ class PoolMonitorAssociation(model_base.BASEV2):
|
|||||||
primary_key=True)
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class LoadBalancerPluginDb(LoadBalancerPluginBase):
|
class LoadBalancerPluginDb(LoadBalancerPluginBase,
|
||||||
|
base_db.CommonDbMixin):
|
||||||
"""Wraps loadbalancer with SQLAlchemy models.
|
"""Wraps loadbalancer with SQLAlchemy models.
|
||||||
|
|
||||||
A class that wraps the implementation of the Quantum loadbalancer
|
A class that wraps the implementation of the Quantum loadbalancer
|
||||||
@ -162,68 +164,6 @@ class LoadBalancerPluginDb(LoadBalancerPluginBase):
|
|||||||
def _core_plugin(self):
|
def _core_plugin(self):
|
||||||
return manager.QuantumManager.get_plugin()
|
return manager.QuantumManager.get_plugin()
|
||||||
|
|
||||||
# TODO(lcui):
|
|
||||||
# A set of internal facility methods are borrowed from QuantumDbPluginV2
|
|
||||||
# class and hence this is duplicate. We need to pull out those methods
|
|
||||||
# into a seperate class which can be used by both QuantumDbPluginV2 and
|
|
||||||
# this class (and others).
|
|
||||||
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 q_exc.AdminRequired(reason=reason)
|
|
||||||
else:
|
|
||||||
tenant_id = context.tenant_id
|
|
||||||
return tenant_id
|
|
||||||
|
|
||||||
def _fields(self, resource, fields):
|
|
||||||
if fields:
|
|
||||||
return dict((key, item) for key, item in resource.iteritems()
|
|
||||||
if key in fields)
|
|
||||||
return resource
|
|
||||||
|
|
||||||
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))
|
|
||||||
return query
|
|
||||||
|
|
||||||
def _get_collection_query(self, context, model, filters=None):
|
|
||||||
collection = self._model_query(context, model)
|
|
||||||
collection = self._apply_filters_to_query(collection, model, filters)
|
|
||||||
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)
|
|
||||||
return [dict_func(c, fields) for c in query]
|
|
||||||
|
|
||||||
def _get_collection_count(self, context, model, filters=None):
|
|
||||||
return self._get_collection_query(context, model, filters).count()
|
|
||||||
|
|
||||||
def _model_query(self, context, model):
|
|
||||||
query = context.session.query(model)
|
|
||||||
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))
|
|
||||||
else:
|
|
||||||
query_filter = (model.tenant_id == context.tenant_id)
|
|
||||||
|
|
||||||
if query_filter is not None:
|
|
||||||
query = query.filter(query_filter)
|
|
||||||
return query
|
|
||||||
|
|
||||||
def _get_by_id(self, context, model, id):
|
|
||||||
query = self._model_query(context, model)
|
|
||||||
return query.filter(model.id == id).one()
|
|
||||||
|
|
||||||
def update_status(self, context, model, id, status):
|
def update_status(self, context, model, id, status):
|
||||||
with context.session.begin(subtransactions=True):
|
with context.session.begin(subtransactions=True):
|
||||||
v_db = self._get_resource(context, model, id)
|
v_db = self._get_resource(context, model, id)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user