Eliminate lookup of "resource extend" funcs by name

By registering functions directly we cut off the dependency of the
"resource extend" functions on the plugin. This is a step towards
the goal of removing the CommonDbMixin mixin class.

Also, we register all "resource extend" functions at plugin create
(in __new__) instead of in the class definition (which caused the
hooks to be registered on import). This ensures the "resource
extend" functions are only registered for the plugins/mixins that
are actually used.

Note that decorators are used to register "resource extend" methods,
similar to the callback receiver decorators.

Related-Blueprint: neutron-lib

Change-Id: I128cfda773d5f9597df9cd61261fdc05f2a174aa
This commit is contained in:
Henry Gessau 2016-12-05 21:45:50 -05:00
parent 0479f0f9d2
commit b3c0d5f239
36 changed files with 328 additions and 233 deletions

View File

@ -15,6 +15,9 @@ NOTE: This module shall not be used by external projects. It will be moved
to neutron-lib in due course, and then it can be used from there.
"""
import collections
import inspect
from neutron.common import utils
# This dictionary will store methods for extending API resources.
@ -25,6 +28,10 @@ _resource_extend_functions = {
# ...
}
# This dictionary will store @extends decorated methods with a list of
# resources that each method will extend on class initialization.
_DECORATED_EXTEND_METHODS = collections.defaultdict(list)
def register_funcs(resource, funcs):
"""Add functions to extend a resource.
@ -59,3 +66,65 @@ def get_funcs(resource):
"""
return _resource_extend_functions.get(resource, [])
def extends(resources):
"""Use to decorate methods on classes before initialization.
Any classes that use this must themselves be decorated with the
@has_resource_extenders decorator to setup the __new__ method to
actually register the instance methods after initialization.
:param resources: Resource collection names. The decorated method will
be registered with each resource as an extend function.
:type resources: list of str
"""
def decorator(method):
_DECORATED_EXTEND_METHODS[method].extend(resources)
return method
return decorator
def has_resource_extenders(klass):
"""Decorator to setup __new__ method in classes to extend resources.
Any method decorated with @extends above is an unbound method on a class.
This decorator sets up the class __new__ method to add the bound
method to _resource_extend_functions after object instantiation.
"""
orig_new = klass.__new__
new_inherited = '__new__' not in klass.__dict__
@staticmethod
def replacement_new(cls, *args, **kwargs):
if new_inherited:
# class didn't define __new__ so we need to call inherited __new__
super_new = super(klass, cls).__new__
if super_new is object.__new__:
# object.__new__ doesn't accept args nor kwargs
instance = super_new(cls)
else:
instance = super_new(cls, *args, **kwargs)
else:
instance = orig_new(cls, *args, **kwargs)
if getattr(instance, '_DECORATED_METHODS_REGISTERED', False):
# Avoid running this logic twice for classes inheriting other
# classes with this same decorator. Only one needs to execute
# to subscribe all decorated methods.
return instance
for name, unbound_method in inspect.getmembers(cls):
if (not inspect.ismethod(unbound_method) and
not inspect.isfunction(unbound_method)):
continue
# Handle py27/py34 difference
method = getattr(unbound_method, 'im_func', unbound_method)
if method not in _DECORATED_EXTEND_METHODS:
continue
for resource in _DECORATED_EXTEND_METHODS[method]:
# Register the bound method for the resourse
register_funcs(resource, [method])
setattr(instance, '_DECORATED_METHODS_REGISTERED', True)
return instance
klass.__new__ = replacement_new
return klass

View File

@ -26,6 +26,7 @@ from neutron.objects import base as base_obj
from neutron.objects import subnetpool as subnetpool_obj
@resource_extend.has_resource_extenders
class AddressScopeDbMixin(ext_address_scope.AddressScopePluginBase):
"""Mixin class to add address scope to db_base_plugin_v2."""
@ -117,7 +118,9 @@ class AddressScopeDbMixin(ext_address_scope.AddressScopePluginBase):
address_scope = self._get_address_scope(context, id)
address_scope.delete()
def _extend_network_dict_address_scope(self, network_res, network_db):
@staticmethod
@resource_extend.extends([attr.NETWORKS])
def _extend_network_dict_address_scope(network_res, network_db):
network_res[ext_address_scope.IPV4_ADDRESS_SCOPE] = None
network_res[ext_address_scope.IPV6_ADDRESS_SCOPE] = None
subnetpools = {subnet.subnetpool for subnet in network_db.subnets
@ -132,6 +135,3 @@ class AddressScopeDbMixin(ext_address_scope.AddressScopePluginBase):
if subnetpool['ip_version'] == constants.IP_VERSION_6:
network_res[ext_address_scope.IPV6_ADDRESS_SCOPE] = as_id
return network_res
resource_extend.register_funcs(
attr.NETWORKS, ['_extend_network_dict_address_scope'])

View File

@ -26,6 +26,7 @@ from neutron.objects.port.extensions import (allowedaddresspairs
as obj_addr_pair)
@resource_extend.has_resource_extenders
class AllowedAddressPairsMixin(object):
"""Mixin class for allowed address pairs."""
@ -63,19 +64,19 @@ class AllowedAddressPairsMixin(object):
return [self._make_allowed_address_pairs_dict(pair.db_obj)
for pair in pairs]
def _extend_port_dict_allowed_address_pairs(self, port_res, port_db):
@staticmethod
@resource_extend.extends([attr.PORTS])
def _extend_port_dict_allowed_address_pairs(port_res, port_db):
# If port_db is provided, allowed address pairs will be accessed via
# sqlalchemy models. As they're loaded together with ports this
# will not cause an extra query.
allowed_address_pairs = [
self._make_allowed_address_pairs_dict(address_pair) for
AllowedAddressPairsMixin._make_allowed_address_pairs_dict(
address_pair) for
address_pair in port_db.allowed_address_pairs]
port_res[addr_pair.ADDRESS_PAIRS] = allowed_address_pairs
return port_res
resource_extend.register_funcs(
attr.PORTS, ['_extend_port_dict_allowed_address_pairs'])
def _delete_allowed_address_pairs(self, context, id):
obj_addr_pair.AllowedAddressPair.delete_objects(
context, port_id=id)

View File

@ -11,20 +11,23 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.plugins import directory
from neutron.api.v2 import attributes
from neutron.db import _resource_extend as resource_extend
from neutron.extensions import availability_zone as az_ext
from neutron.extensions import network_availability_zone as net_az
@resource_extend.has_resource_extenders
class NetworkAvailabilityZoneMixin(net_az.NetworkAvailabilityZonePluginBase):
"""Mixin class to enable network's availability zone attributes."""
def _extend_availability_zone(self, net_res, net_db):
@staticmethod
@resource_extend.extends([attributes.NETWORKS])
def _extend_availability_zone(net_res, net_db):
net_res[az_ext.AZ_HINTS] = az_ext.convert_az_string_to_list(
net_db[az_ext.AZ_HINTS])
plugin = directory.get_plugin()
net_res[az_ext.AVAILABILITY_ZONES] = (
self.get_network_availability_zones(net_db))
resource_extend.register_funcs(
attributes.NETWORKS, ['_extend_availability_zone'])
plugin.get_network_availability_zones(net_db))

View File

@ -11,32 +11,33 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib import constants
from neutron_lib.plugins import directory
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.common import utils
from neutron.db import db_base_plugin_v2
from neutron.db import _resource_extend as resource_extend
from neutron.db import l3_attrs_db
from neutron.extensions import availability_zone as az_ext
from neutron.extensions import l3
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class RouterAvailabilityZoneMixin(l3_attrs_db.ExtraAttributesMixin):
"""Mixin class to enable router's availability zone attributes."""
def __new__(cls, *args, **kwargs):
inst = super(RouterAvailabilityZoneMixin, cls).__new__(
cls, *args, **kwargs)
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
l3.ROUTERS, [inst._add_az_to_response])
return inst
def _add_az_to_response(self, plugin, router_res, router_db):
if not utils.is_extension_supported(self, 'router_availability_zone'):
@staticmethod
@resource_extend.extends([l3.ROUTERS])
def _add_az_to_response(router_res, router_db):
l3_plugin = directory.get_plugin(constants.L3)
if not utils.is_extension_supported(l3_plugin,
'router_availability_zone'):
return
router_res['availability_zones'] = (
self.get_router_availability_zones(router_db))
l3_plugin.get_router_availability_zones(router_db))
@registry.receives(resources.ROUTER, [events.PRECOMMIT_CREATE])
def _process_az_request(self, resource, event, trigger, context,

View File

@ -17,7 +17,6 @@ import weakref
from neutron_lib.db import utils as db_utils
from oslo_db.sqlalchemy import utils as sa_utils
import six
from sqlalchemy import and_
from sqlalchemy.ext import associationproxy
from sqlalchemy import or_
@ -36,6 +35,13 @@ model_query = ndb_utils.model_query
resource_fields = ndb_utils.resource_fields
def _resolve_ref(ref):
"""Handles dereference of weakref."""
if isinstance(ref, weakref.ref):
ref = ref()
return ref
class CommonDbMixin(object):
"""Common methods used in core and service plugins."""
@ -85,11 +91,11 @@ class CommonDbMixin(object):
query_filter = (model.tenant_id == context.tenant_id)
# Execute query hooks registered from mixins and plugins
for hook in _model_query.get_hooks(model):
query_hook = self._resolve_ref(hook.get('query'))
query_hook = _resolve_ref(hook.get('query'))
if query_hook:
query = query_hook(context, model, query)
filter_hook = self._resolve_ref(hook.get('filter'))
filter_hook = _resolve_ref(hook.get('filter'))
if filter_hook:
query_filter = filter_hook(context, model, query_filter)
@ -164,31 +170,19 @@ class CommonDbMixin(object):
query = query.outerjoin(model.rbac_entries)
query = query.filter(is_shared)
for hook in _model_query.get_hooks(model):
result_filter = self._resolve_ref(
hook.get('result_filters', None))
result_filter = _resolve_ref(hook.get('result_filters', None))
if result_filter:
query = result_filter(query, filters)
return query
def _resolve_ref(self, ref):
"""Finds string ref functions, handles dereference of weakref."""
if isinstance(ref, six.string_types):
ref = getattr(self, ref, None)
if isinstance(ref, weakref.ref):
ref = ref()
return ref
def _apply_dict_extend_functions(self, resource_type,
@staticmethod
def _apply_dict_extend_functions(resource_type,
response, db_object):
for func in _resource_extend.get_funcs(resource_type):
args = (response, db_object)
if not isinstance(func, six.string_types):
# must call unbound method - use self as 1st argument
args = (self,) + args
func = self._resolve_ref(func)
if func:
func(*args)
resolved_func = _resolve_ref(func)
if resolved_func:
resolved_func(response, db_object)
def _get_collection_query(self, context, model, filters=None,
sorts=None, limit=None, marker_obj=None,

View File

@ -40,7 +40,8 @@ class DataPlaneStatusMixin(object):
else:
self._process_create_port_data_plane_status(context, data, res)
def _extend_port_data_plane_status(self, port_res, port_db):
@staticmethod
def _extend_port_data_plane_status(port_res, port_db):
port_res[dps_lib.DATA_PLANE_STATUS] = None
if port_db.get(dps_lib.DATA_PLANE_STATUS):

View File

@ -41,6 +41,7 @@ class DNSActionsData(object):
self.previous_dns_domain = previous_dns_domain
@resource_extend.has_resource_extenders
class DNSDbMixin(object):
"""Mixin class to add DNS methods to db_base_plugin_v2."""
@ -63,7 +64,9 @@ class DNSDbMixin(object):
raise dns.ExternalDNSDriverNotFound(
driver=cfg.CONF.external_dns_driver)
def _extend_floatingip_dict_dns(self, floatingip_res, floatingip_db):
@staticmethod
@resource_extend.extends([l3.FLOATINGIPS])
def _extend_floatingip_dict_dns(floatingip_res, floatingip_db):
floatingip_res['dns_domain'] = ''
floatingip_res['dns_name'] = ''
if floatingip_db.dns:
@ -71,9 +74,6 @@ class DNSDbMixin(object):
floatingip_res['dns_name'] = floatingip_db.dns['dns_name']
return floatingip_res
resource_extend.register_funcs(
l3.FLOATINGIPS, ['_extend_floatingip_dict_dns'])
def _process_dns_floatingip_create_precommit(self, context,
floatingip_data, req_data):
# expects to be called within a plugin's session

View File

@ -64,6 +64,7 @@ def _network_result_filter_hook(query, filters):
return query.filter(~models_v2.Network.external.has())
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class External_net_db_mixin(object):
"""Mixin class to add external network methods to db_base_plugin_v2."""
@ -81,14 +82,13 @@ class External_net_db_mixin(object):
return net_obj.ExternalNetwork.objects_exist(
context, network_id=net_id)
def _extend_network_dict_l3(self, network_res, network_db):
@staticmethod
@resource_extend.extends([attributes.NETWORKS])
def _extend_network_dict_l3(network_res, network_db):
# Comparing with None for converting uuid into bool
network_res[external_net.EXTERNAL] = network_db.external is not None
return network_res
resource_extend.register_funcs(
attributes.NETWORKS, ['_extend_network_dict_l3'])
def _process_l3_create(self, context, net_data, req_data):
external = req_data.get(external_net.EXTERNAL)
external_set = validators.is_attr_set(external)

View File

@ -20,6 +20,7 @@ from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.objects.port.extensions import extra_dhcp_opt as obj_extra_dhcp
@resource_extend.has_resource_extenders
class ExtraDhcpOptMixin(object):
"""Mixin class to add extra options to the DHCP opts file
and associate them to a port.
@ -114,12 +115,11 @@ class ExtraDhcpOptMixin(object):
return bool(dopts)
def _extend_port_dict_extra_dhcp_opt(self, res, port):
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _extend_port_dict_extra_dhcp_opt(res, port):
res[edo_ext.EXTRADHCPOPTS] = [{'opt_name': dho.opt_name,
'opt_value': dho.opt_value,
'ip_version': dho.ip_version}
for dho in port.dhcp_opts]
return res
resource_extend.register_funcs(
attributes.PORTS, ['_extend_port_dict_extra_dhcp_opt'])

View File

@ -39,18 +39,18 @@ extra_route_opts = [
cfg.CONF.register_opts(extra_route_opts)
@resource_extend.has_resource_extenders
class ExtraRoute_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin):
"""Mixin class to support extra route configuration on router."""
def _extend_router_dict_extraroute(self, router_res, router_db):
@staticmethod
@resource_extend.extends([l3.ROUTERS])
def _extend_router_dict_extraroute(router_res, router_db):
router_res['routes'] = (ExtraRoute_dbonly_mixin.
_make_extra_route_list(
router_db['route_list']
))
resource_extend.register_funcs(
l3.ROUTERS, ['_extend_router_dict_extraroute'])
def update_router(self, context, id, router):
r = router['router']
if 'routes' in r:

View File

@ -33,10 +33,13 @@ def get_attr_info():
}
@resource_extend.has_resource_extenders
class ExtraAttributesMixin(object):
"""Mixin class to enable router's extra attributes."""
def _extend_extra_router_dict(self, router_res, router_db):
@staticmethod
@resource_extend.extends([l3.ROUTERS])
def _extend_extra_router_dict(router_res, router_db):
extra_attrs = router_db['extra_attributes'] or {}
for name, info in get_attr_info().items():
from_db = info.get('transform_from_db', lambda x: x)
@ -61,6 +64,3 @@ class ExtraAttributesMixin(object):
return
raise RuntimeError(_("Tried to set a key '%s' that doesn't exist "
"in the extra attributes table.") % key)
resource_extend.register_funcs(
l3.ROUTERS, ['_extend_extra_router_dict'])

View File

@ -38,13 +38,13 @@ setattr(l3_models.Router, 'enable_snat',
nullable=False))
@resource_extend.has_resource_extenders
class L3_NAT_dbonly_mixin(l3_db.L3_NAT_dbonly_mixin):
"""Mixin class to add configurable gateway modes."""
resource_extend.register_funcs(
l3.ROUTERS, ['_extend_router_dict_gw_mode'])
def _extend_router_dict_gw_mode(self, router_res, router_db):
@staticmethod
@resource_extend.extends([l3.ROUTERS])
def _extend_router_dict_gw_mode(router_res, router_db):
if router_db.gw_port_id:
nw_id = router_db.gw_port['network_id']
router_res[EXTERNAL_GW_INFO] = {

View File

@ -13,11 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.plugins import directory
from neutron.api.v2 import attributes
from neutron.db import _resource_extend as resource_extend
@resource_extend.has_resource_extenders
class PortBindingBaseMixin(object):
# Initialized by core plugin or ml2 mechanism driver(s)
base_binding_dict = None
def _process_portbindings_create_and_update(self, context, port_data,
@ -28,13 +33,10 @@ class PortBindingBaseMixin(object):
if self.base_binding_dict:
port_res.update(self.base_binding_dict)
def _extend_port_dict_binding(plugin, port_res, port_db):
if not isinstance(plugin, PortBindingBaseMixin):
return
plugin.extend_port_dict_binding(port_res, port_db)
def register_port_dict_function():
resource_extend.register_funcs(
attributes.PORTS, [_extend_port_dict_binding])
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _extend_port_dict_binding(port_res, port_db):
plugin = directory.get_plugin()
if not isinstance(plugin, PortBindingBaseMixin):
return
plugin.extend_port_dict_binding(port_res, port_db)

View File

@ -15,6 +15,7 @@
from neutron_lib.api.definitions import portbindings
from neutron_lib.api import validators
from neutron_lib.plugins import directory
from neutron.api.v2 import attributes
from neutron.db import _model_query as model_query
@ -40,8 +41,8 @@ def _port_result_filter_hook(query, filters):
return query
@resource_extend.has_resource_extenders
class PortBindingMixin(portbindings_base.PortBindingBaseMixin):
extra_binding_dict = None
def __new__(cls, *args, **kwargs):
model_query.register_hook(
@ -101,11 +102,10 @@ class PortBindingMixin(portbindings_base.PortBindingBaseMixin):
host = port_db.portbinding.host if port_db.portbinding else None
self._extend_port_dict_binding_host(port_res, host)
def _extend_port_dict_binding(plugin, port_res, port_db):
if not isinstance(plugin, PortBindingMixin):
return
plugin.extend_port_dict_binding(port_res, port_db)
resource_extend.register_funcs(attributes.PORTS, [_extend_port_dict_binding])
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _extend_port_dict_binding(port_res, port_db):
plugin = directory.get_plugin()
if not isinstance(plugin, PortBindingMixin):
return
plugin.extend_port_dict_binding(port_res, port_db)

View File

@ -13,6 +13,7 @@
# under the License.
from neutron_lib.api import validators
from neutron_lib.plugins import directory
from neutron.api.v2 import attributes as attrs
from neutron.common import utils
@ -21,16 +22,16 @@ from neutron.db import portsecurity_db_common
from neutron.extensions import portsecurity as psec
@resource_extend.has_resource_extenders
class PortSecurityDbMixin(portsecurity_db_common.PortSecurityDbCommon):
resource_extend.register_funcs(
attrs.NETWORKS, ['_extend_port_security_dict'])
resource_extend.register_funcs(
attrs.PORTS, ['_extend_port_security_dict'])
def _extend_port_security_dict(self, response_data, db_data):
@staticmethod
@resource_extend.extends([attrs.NETWORKS, attrs.PORTS])
def _extend_port_security_dict(response_data, db_data):
plugin = directory.get_plugin()
if ('port-security' in
getattr(self, 'supported_extension_aliases', [])):
super(PortSecurityDbMixin, self)._extend_port_security_dict(
getattr(plugin, 'supported_extension_aliases', [])):
super(PortSecurityDbMixin, plugin)._extend_port_security_dict(
response_data, db_data)
def _determine_port_security_and_has_ip(self, context, port):

View File

@ -21,7 +21,8 @@ from neutron.objects.port.extensions import port_security as p_ps
class PortSecurityDbCommon(object):
"""Mixin class to add port security."""
def _extend_port_security_dict(self, response_data, db_data):
@staticmethod
def _extend_port_security_dict(response_data, db_data):
if db_data.get('port_security') is None:
response_data[psec.PORTSECURITY] = psec.DEFAULT_PORT_SECURITY
else:

View File

@ -35,6 +35,7 @@ from neutron.db.models import securitygroup as sg_models
from neutron.extensions import securitygroup as ext_sg
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
"""Mixin class to add security group to db_base_plugin_v2."""
@ -662,7 +663,9 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
resources.SECURITY_GROUP_RULE, events.AFTER_DELETE, self,
**kwargs)
def _extend_port_dict_security_group(self, port_res, port_db):
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _extend_port_dict_security_group(port_res, port_db):
# Security group bindings will be retrieved from the SQLAlchemy
# model. As they're loaded eagerly with ports because of the
# joined load they will not cause an extra query.
@ -671,9 +674,6 @@ class SecurityGroupDbMixin(ext_sg.SecurityGroupPluginBase):
port_res[ext_sg.SECURITYGROUPS] = security_group_ids
return port_res
resource_extend.register_funcs(
attributes.PORTS, ['_extend_port_dict_security_group'])
def _process_port_create_security_group(self, context, port,
security_group_ids):
if validators.is_attr_set(security_group_ids):

View File

@ -16,17 +16,14 @@ from neutron.db import _resource_extend as resource_extend
from neutron.db import standard_attr
@resource_extend.has_resource_extenders
class StandardAttrDescriptionMixin(object):
supported_extension_aliases = ['standard-attr-description']
def _extend_standard_attr_description(self, res, db_object):
@staticmethod
@resource_extend.extends(
list(standard_attr.get_standard_attr_resource_model_map()))
def _extend_standard_attr_description(res, db_object):
if not hasattr(db_object, 'description'):
return
res['description'] = db_object.description
def __new__(cls, *args, **kwargs):
for resource in standard_attr.get_standard_attr_resource_model_map():
resource_extend.register_funcs(
resource, ['_extend_standard_attr_description'])
return super(StandardAttrDescriptionMixin, cls).__new__(cls, *args,
**kwargs)

View File

@ -20,13 +20,13 @@ from neutron.api.v2 import attributes
from neutron.db import _resource_extend as resource_extend
@resource_extend.has_resource_extenders
class SubnetServiceTypeMixin(object):
"""Mixin class to extend subnet with service type attribute"""
def _extend_subnet_service_types(self, subnet_res, subnet_db):
@staticmethod
@resource_extend.extends([attributes.SUBNETS])
def _extend_subnet_service_types(subnet_res, subnet_db):
subnet_res['service_types'] = [service_type['service_type'] for
service_type in
subnet_db.service_types]
resource_extend.register_funcs(
attributes.SUBNETS, [_extend_subnet_service_types])

View File

@ -17,13 +17,13 @@ from neutron.db import _resource_extend as resource_extend
from neutron.extensions import vlantransparent
@resource_extend.has_resource_extenders
class Vlantransparent_db_mixin(object):
"""Mixin class to add vlan transparent methods to db_base_plugin_v2."""
def _extend_network_dict_vlan_transparent(self, network_res, network_db):
@staticmethod
@resource_extend.extends([attributes.NETWORKS])
def _extend_network_dict_vlan_transparent(network_res, network_db):
network_res[vlantransparent.VLANTRANSPARENT] = (
network_db.vlan_transparent)
return network_res
resource_extend.register_funcs(
attributes.NETWORKS, ['_extend_network_dict_vlan_transparent'])

View File

@ -109,6 +109,7 @@ def _ml2_port_result_filter_hook(query, filters):
return query.filter(models_v2.Port.port_binding.has(bind_criteria))
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
dvr_mac_db.DVRDbMixin,
@ -602,34 +603,37 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
'port': binding.port_id})
return {}
def _ml2_extend_port_dict_binding(self, port_res, port_db):
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _ml2_extend_port_dict_binding(port_res, port_db):
plugin = directory.get_plugin()
# None when called during unit tests for other plugins.
if port_db.port_binding:
self._update_port_dict_binding(port_res, port_db.port_binding)
resource_extend.register_funcs(
attributes.PORTS, ['_ml2_extend_port_dict_binding'])
plugin._update_port_dict_binding(port_res, port_db.port_binding)
# ML2's resource extend functions allow extension drivers that extend
# attributes for the resources to add those attributes to the result.
resource_extend.register_funcs(
attributes.NETWORKS, ['_ml2_md_extend_network_dict'])
resource_extend.register_funcs(
attributes.PORTS, ['_ml2_md_extend_port_dict'])
resource_extend.register_funcs(
attributes.SUBNETS, ['_ml2_md_extend_subnet_dict'])
def _ml2_md_extend_network_dict(self, result, netdb):
session = self._object_session_or_new_session(netdb)
self.extension_manager.extend_network_dict(session, netdb, result)
@staticmethod
@resource_extend.extends([attributes.NETWORKS])
def _ml2_md_extend_network_dict(result, netdb):
plugin = directory.get_plugin()
session = plugin._object_session_or_new_session(netdb)
plugin.extension_manager.extend_network_dict(session, netdb, result)
def _ml2_md_extend_port_dict(self, result, portdb):
session = self._object_session_or_new_session(portdb)
self.extension_manager.extend_port_dict(session, portdb, result)
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _ml2_md_extend_port_dict(result, portdb):
plugin = directory.get_plugin()
session = plugin._object_session_or_new_session(portdb)
plugin.extension_manager.extend_port_dict(session, portdb, result)
def _ml2_md_extend_subnet_dict(self, result, subnetdb):
session = self._object_session_or_new_session(subnetdb)
self.extension_manager.extend_subnet_dict(session, subnetdb, result)
@staticmethod
@resource_extend.extends([attributes.SUBNETS])
def _ml2_md_extend_subnet_dict(result, subnetdb):
plugin = directory.get_plugin()
session = plugin._object_session_or_new_session(subnetdb)
plugin.extension_manager.extend_subnet_dict(session, subnetdb, result)
@staticmethod
def _object_session_or_new_session(sql_obj):

View File

@ -42,13 +42,6 @@ IS_DEFAULT = 'is_default'
CHECK_REQUIREMENTS = 'dry-run'
def _extend_external_network_default(core_plugin, net_res, net_db):
"""Add is_default field to 'show' response."""
if net_db.external is not None:
net_res[IS_DEFAULT] = net_db.external.is_default
return net_res
@db_api.retry_if_session_inactive()
def _ensure_external_network_default_value_callback(
resource, event, trigger, context, request, network, **kwargs):
@ -78,11 +71,9 @@ def _ensure_external_network_default_value_callback(
obj.update()
@resource_extend.has_resource_extenders
class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
resource_extend.register_funcs(
attributes.NETWORKS, [_extend_external_network_default])
def __new__(cls, *args, **kwargs):
# NOTE(kevinbenton): we subscribe on object construction because
# the tests blow away the callback manager for each run
@ -116,6 +107,14 @@ class AutoAllocatedTopologyMixin(common_db_mixin.CommonDbMixin):
self._l3_plugin = directory.get_plugin(constants.L3)
return self._l3_plugin
@staticmethod
@resource_extend.extends([attributes.NETWORKS])
def _extend_external_network_default(net_res, net_db):
"""Add is_default field to 'show' response."""
if net_db.external is not None:
net_res[IS_DEFAULT] = net_db.external.is_default
return net_res
def get_auto_allocated_topology(self, context, tenant_id, fields=None):
"""Return tenant's network associated to auto-allocated topology.

View File

@ -51,6 +51,7 @@ def disable_dvr_extension_by_config(aliases):
aliases.remove('dvr')
@resource_extend.has_resource_extenders
class L3RouterPlugin(service_base.ServicePluginBase,
common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin,
@ -141,10 +142,7 @@ class L3RouterPlugin(service_base.ServicePluginBase,
context, floatingip,
initial_status=n_const.FLOATINGIP_STATUS_DOWN)
def add_flavor_id(plugin, router_res, router_db):
router_res['flavor_id'] = router_db['flavor_id']
resource_extend.register_funcs(
l3.ROUTERS, [add_flavor_id])
@staticmethod
@resource_extend.extends([l3.ROUTERS])
def add_flavor_id(router_res, router_db):
router_res['flavor_id'] = router_db['flavor_id']

View File

@ -25,6 +25,7 @@ from neutron.db import standard_attr
LOG = logging.getLogger(__name__)
@resource_extend.has_resource_extenders
class RevisionPlugin(service_base.ServicePluginBase):
"""Plugin to populate revision numbers into standard attr resources."""
@ -32,9 +33,6 @@ class RevisionPlugin(service_base.ServicePluginBase):
def __init__(self):
super(RevisionPlugin, self).__init__()
for resource in standard_attr.get_standard_attr_resource_model_map():
resource_extend.register_funcs(
resource, [self.extend_resource_dict_revision])
db_api.sqla_listen(se.Session, 'before_flush', self.bump_revisions)
def bump_revisions(self, session, context, instances):
@ -76,7 +74,10 @@ class RevisionPlugin(service_base.ServicePluginBase):
def get_plugin_description(self):
return "Adds revision numbers to resources."
def extend_resource_dict_revision(self, plugin, resource_res, resource_db):
@staticmethod
@resource_extend.extends(
list(standard_attr.get_standard_attr_resource_model_map()))
def extend_resource_dict_revision(resource_res, resource_db):
resource_res['revision_number'] = resource_db.revision_number
def _find_related_obj(self, session, obj, relationship_col):

View File

@ -49,30 +49,7 @@ SEGMENT_NAME_STUB = 'Neutron segment id %s'
MAX_INVENTORY_UPDATE_RETRIES = 10
def _extend_network_dict_binding(plugin, network_res, network_db):
if not directory.get_plugin('segments'):
return
# TODO(carl_baldwin) Make this work with service subnets when it's a thing.
is_adjacent = (not network_db.subnets
or not network_db.subnets[0].segment_id)
network_res[l2_adjacency.L2_ADJACENCY] = is_adjacent
def _extend_subnet_dict_binding(plugin, subnet_res, subnet_db):
subnet_res['segment_id'] = subnet_db.get('segment_id')
def _extend_port_dict_binding(plugin, port_res, port_db):
if not directory.get_plugin('segments'):
return
value = ip_allocation.IP_ALLOCATION_IMMEDIATE
if port_db.get('ip_allocation'):
value = port_db.get('ip_allocation')
port_res[ip_allocation.IP_ALLOCATION] = value
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
@ -81,14 +58,36 @@ class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
supported_extension_aliases = ["segment", "ip_allocation", "l2_adjacency"]
def __init__(self):
resource_extend.register_funcs(
attributes.NETWORKS, [_extend_network_dict_binding])
resource_extend.register_funcs(
attributes.SUBNETS, [_extend_subnet_dict_binding])
resource_extend.register_funcs(
attributes.PORTS, [_extend_port_dict_binding])
self.nova_updater = NovaSegmentNotifier()
@staticmethod
@resource_extend.extends([attributes.NETWORKS])
def _extend_network_dict_binding(network_res, network_db):
if not directory.get_plugin('segments'):
return
# TODO(carl_baldwin) Make this work with service subnets when
# it's a thing.
is_adjacent = (not network_db.subnets
or not network_db.subnets[0].segment_id)
network_res[l2_adjacency.L2_ADJACENCY] = is_adjacent
@staticmethod
@resource_extend.extends([attributes.SUBNETS])
def _extend_subnet_dict_binding(subnet_res, subnet_db):
subnet_res['segment_id'] = subnet_db.get('segment_id')
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _extend_port_dict_binding(port_res, port_db):
if not directory.get_plugin('segments'):
return
value = ip_allocation.IP_ALLOCATION_IMMEDIATE
if port_db.get('ip_allocation'):
value = port_db.get('ip_allocation')
port_res[ip_allocation.IP_ALLOCATION] = value
@classmethod
def get_instance(cls):
if cls._instance is None:

View File

@ -44,13 +44,7 @@ resource_model_map = {
}
def _extend_tags_dict(plugin, response_data, db_data):
if not directory.get_plugin(tag_ext.TAG_PLUGIN_TYPE):
return
tags = [tag_db.tag for tag_db in db_data.standard_attr.tags]
response_data['tags'] = tags
@resource_extend.has_resource_extenders
class TagPlugin(common_db_mixin.CommonDbMixin, tag_ext.TagPluginBase):
"""Implementation of the Neutron Tag Service Plugin."""
@ -59,8 +53,7 @@ class TagPlugin(common_db_mixin.CommonDbMixin, tag_ext.TagPluginBase):
def __new__(cls, *args, **kwargs):
inst = super(TagPlugin, cls).__new__(cls, *args, **kwargs)
inst._filter_methods = [] # prevent GC of our partial functions
for resource, model in resource_model_map.items():
resource_extend.register_funcs(resource, [_extend_tags_dict])
for model in resource_model_map.values():
method = functools.partial(tag_methods.apply_tag_filters, model)
inst._filter_methods.append(method)
model_query.register_hook(model, "tag",
@ -69,6 +62,14 @@ class TagPlugin(common_db_mixin.CommonDbMixin, tag_ext.TagPluginBase):
result_filters=method)
return inst
@staticmethod
@resource_extend.extends(list(resource_model_map))
def _extend_tags_dict(response_data, db_data):
if not directory.get_plugin(tag_ext.TAG_PLUGIN_TYPE):
return
tags = [tag_db.tag for tag_db in db_data.standard_attr.tags]
response_data['tags'] = tags
def _get_resource(self, context, resource, resource_id):
model = resource_model_map[resource]
try:

View File

@ -70,12 +70,6 @@ def _format_timestamp(resource_db, result):
strftime(TIME_FORMAT_WHOLE_SECONDS)) + 'Z'
def _extend_resource_dict_timestamp(plugin_obj, resource_res, resource_db):
if (resource_db and resource_db.created_at and
resource_db.updated_at):
_format_timestamp(resource_db, resource_res)
def _add_timestamp(mapper, _conn, target):
if not target.created_at and not target.updated_at:
time = timeutils.utcnow()
@ -84,14 +78,13 @@ def _add_timestamp(mapper, _conn, target):
return target
@resource_extend.has_resource_extenders
class TimeStamp_db_mixin(object):
"""Mixin class to add Time Stamp methods."""
def __new__(cls, *args, **kwargs):
rs_model_maps = standard_attr.get_standard_attr_resource_model_map()
for rsmap, model in rs_model_maps.items():
resource_extend.register_funcs(
rsmap, [_extend_resource_dict_timestamp])
for model in rs_model_maps.values():
model_query.register_hook(
model,
"change_since_query",
@ -105,3 +98,11 @@ class TimeStamp_db_mixin(object):
listen(standard_attr.StandardAttribute, 'before_insert',
_add_timestamp)
listen(se.Session, 'before_flush', _update_timestamp)
@staticmethod
@resource_extend.extends(
list(standard_attr.get_standard_attr_resource_model_map()))
def _extend_resource_dict_timestamp(resource_res, resource_db):
if (resource_db and resource_db.created_at and
resource_db.updated_at):
_format_timestamp(resource_db, resource_res)

View File

@ -16,6 +16,7 @@ import copy
from neutron_lib.api.definitions import portbindings
from neutron_lib import context
from neutron_lib.plugins import directory
from neutron_lib.services import base as service_base
from oslo_log import log as logging
from oslo_utils import uuidutils
@ -40,26 +41,7 @@ from neutron.services.trunk.seg_types import validators
LOG = logging.getLogger(__name__)
def _extend_port_trunk_details(core_plugin, port_res, port_db):
"""Add trunk details to a port."""
if port_db.trunk_port:
subports = {
x.port_id: {'segmentation_id': x.segmentation_id,
'segmentation_type': x.segmentation_type,
'port_id': x.port_id}
for x in port_db.trunk_port.sub_ports
}
ports = core_plugin.get_ports(
context.get_admin_context(), filters={'id': subports})
for port in ports:
subports[port['id']]['mac_address'] = port['mac_address']
trunk_details = {'trunk_id': port_db.trunk_port.id,
'sub_ports': [x for x in subports.values()]}
port_res['trunk_details'] = trunk_details
return port_res
@resource_extend.has_resource_extenders
@registry.has_registry_receivers
class TrunkPlugin(service_base.ServicePluginBase,
common_db_mixin.CommonDbMixin):
@ -70,8 +52,6 @@ class TrunkPlugin(service_base.ServicePluginBase,
__native_sorting_support = True
def __init__(self):
resource_extend.register_funcs(
attributes.PORTS, [_extend_port_trunk_details])
self._rpc_backend = None
self._drivers = []
self._segmentation_types = {}
@ -85,6 +65,28 @@ class TrunkPlugin(service_base.ServicePluginBase,
LOG.debug('Trunk plugin loaded with driver %s', driver.name)
self.check_compatibility()
@staticmethod
@resource_extend.extends([attributes.PORTS])
def _extend_port_trunk_details(port_res, port_db):
"""Add trunk details to a port."""
if port_db.trunk_port:
subports = {
x.port_id: {'segmentation_id': x.segmentation_id,
'segmentation_type': x.segmentation_type,
'port_id': x.port_id}
for x in port_db.trunk_port.sub_ports
}
core_plugin = directory.get_plugin()
ports = core_plugin.get_ports(
context.get_admin_context(), filters={'id': subports})
for port in ports:
subports[port['id']]['mac_address'] = port['mac_address']
trunk_details = {'trunk_id': port_db.trunk_port.id,
'sub_ports': [x for x in subports.values()]}
port_res['trunk_details'] = trunk_details
return port_res
def check_compatibility(self):
"""Verify the plugin can load correctly and fail otherwise."""
self.check_driver_compatibility()

View File

@ -47,6 +47,7 @@ from neutron.callbacks import registry
from neutron.common import config
from neutron.common import rpc as n_rpc
from neutron.db import _model_query as model_query
from neutron.db import _resource_extend as resource_extend
from neutron.db import agentschedulers_db
from neutron.db import api as db_api
from neutron import manager
@ -168,6 +169,7 @@ class DietTestCase(base.BaseTestCase):
self.addCleanup(mock.patch.stopall)
self.addCleanup(self.reset_model_query_hooks)
self.addCleanup(self.reset_resource_extend_functions)
self.addOnException(self.check_for_systemexit)
self.orig_pid = os.getpid()
@ -178,6 +180,10 @@ class DietTestCase(base.BaseTestCase):
def reset_model_query_hooks():
model_query._model_query_hooks = {}
@staticmethod
def reset_resource_extend_functions():
resource_extend._resource_extend_functions = {}
def addOnException(self, handler):
def safe_handler(*args, **kwargs):

View File

@ -18,6 +18,7 @@ import random
from neutron_lib import constants
from neutron_lib import context
from neutron_lib.plugins import directory
from oslo_utils import uuidutils
import testscenarios
@ -45,6 +46,7 @@ class L3SchedulerBaseTest(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
super(L3SchedulerBaseTest, self).setUp(PLUGIN_NAME)
self.l3_plugin = l3_router_plugin.L3RouterPlugin()
directory.add_plugin(constants.L3, self.l3_plugin)
self.adminContext = context.get_admin_context()
self.adminContext.tenant_id = _uuid()
@ -296,6 +298,7 @@ class L3AZSchedulerBaseTest(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
super(L3AZSchedulerBaseTest, self).setUp(plugin='ml2')
self.l3_plugin = l3_router_plugin.L3RouterPlugin()
directory.add_plugin(constants.L3, self.l3_plugin)
self.l3_plugin.router_scheduler = None
self.adminContext = context.get_admin_context()
self.adminContext.tenant_id = '_func_test_tenant_'

View File

@ -48,6 +48,7 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
self.core_plugin = directory.get_plugin()
self.ctx = context.get_admin_context()
self.mixin = FakeL3Plugin()
directory.add_plugin(const.L3, self.mixin)
def _create_router(self, router):
with self.ctx.session.begin(subtransactions=True):

View File

@ -11,6 +11,8 @@
# under the License.
import mock
from neutron_lib import constants
from neutron_lib.plugins import directory
from neutron.db import portsecurity_db as pd
from neutron.db import portsecurity_db_common as pdc
@ -29,6 +31,7 @@ class PortSecurityDbMixinTestCase(base.BaseTestCase):
def setUp(self):
super(PortSecurityDbMixinTestCase, self).setUp()
self.plugin = FakePlugin()
directory.add_plugin(constants.CORE, self.plugin)
@mock.patch.object(common, '_extend_port_security_dict')
def test__extend_port_security_dict_relies_on_common(self, extend):

View File

@ -41,11 +41,18 @@ class DataPlaneStatusTestExtensionManager(object):
return dps_ext.Data_plane_status.get_extended_resources(version)
@resource_extend.has_resource_extenders
class DataPlaneStatusExtensionTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
dps_db.DataPlaneStatusMixin):
supported_extension_aliases = ["data-plane-status"]
@staticmethod
@resource_extend.extends([attrs.PORTS])
def _extend_port_data_plane_status(port_res, port_db):
return dps_db.DataPlaneStatusMixin._extend_port_data_plane_status(
port_res, port_db)
def update_port(self, context, id, port):
with context.session.begin(subtransactions=True):
ret_port = super(DataPlaneStatusExtensionTestPlugin,
@ -56,9 +63,6 @@ class DataPlaneStatusExtensionTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
ret_port)
return ret_port
resource_extend.register_funcs(attrs.PORTS,
['_extend_port_data_plane_status'])
class DataPlaneStatusExtensionTestCase(
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):

View File

@ -571,6 +571,7 @@ class ExtraAttributesMixinTestCase(testlib_api.SqlTestCase):
def setUp(self):
super(ExtraAttributesMixinTestCase, self).setUp()
self.mixin = l3_attrs_db.ExtraAttributesMixin()
directory.add_plugin(lib_constants.L3, self.mixin)
self.ctx = context.get_admin_context()
self.router = l3_models.Router()
with self.ctx.session.begin():

View File

@ -13,6 +13,7 @@
import webob.exc
from neutron.db import db_base_plugin_v2
from neutron.db import subnet_service_type_db_models
from neutron.extensions import subnet_service_types
from neutron.tests.unit.db import test_db_base_plugin_v2
@ -34,7 +35,8 @@ class SubnetServiceTypesExtensionManager(object):
class SubnetServiceTypesExtensionTestPlugin(
db_base_plugin_v2.NeutronDbPluginV2):
db_base_plugin_v2.NeutronDbPluginV2,
subnet_service_type_db_models.SubnetServiceTypeMixin):
"""Test plugin to mixin the subnet service_types extension.
"""