Skip to content

Commit

Permalink
Merge "Eliminate lookup of "resource extend" funcs by name"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Apr 26, 2017
2 parents cbddbaa + b3c0d5f commit 92372b9
Show file tree
Hide file tree
Showing 36 changed files with 331 additions and 236 deletions.
69 changes: 69 additions & 0 deletions neutron/db/_resource_extend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
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.
Expand All @@ -25,6 +28,10 @@
# ...
}

# 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.
Expand Down Expand Up @@ -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
8 changes: 4 additions & 4 deletions neutron/db/address_scope_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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."""

Expand Down Expand Up @@ -117,7 +118,9 @@ def delete_address_scope(self, context, id):
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
Expand All @@ -132,6 +135,3 @@ def _extend_network_dict_address_scope(self, network_res, network_db):
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'])
11 changes: 6 additions & 5 deletions neutron/db/allowedaddresspairs_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
as obj_addr_pair)


@resource_extend.has_resource_extenders
class AllowedAddressPairsMixin(object):
"""Mixin class for allowed address pairs."""

Expand Down Expand Up @@ -63,19 +64,19 @@ def get_allowed_address_pairs(self, context, port_id):
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)
Expand Down
13 changes: 8 additions & 5 deletions neutron/db/availability_zone/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
23 changes: 12 additions & 11 deletions neutron/db/availability_zone/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 15 additions & 21 deletions neutron/db/common_db_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

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_
Expand All @@ -36,6 +35,13 @@
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."""

Expand Down Expand Up @@ -85,11 +91,11 @@ def _model_query(self, context, model):
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)

Expand Down Expand Up @@ -164,31 +170,19 @@ def _apply_filters_to_query(self, query, model, filters, context=None):
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,
Expand Down
3 changes: 2 additions & 1 deletion neutron/db/data_plane_status_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def _process_update_port_data_plane_status(self, context, data,
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):
Expand Down
8 changes: 4 additions & 4 deletions neutron/db/dns_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, current_dns_name=None, current_dns_domain=None,
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."""

Expand All @@ -63,17 +64,16 @@ def dns_driver(self):
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:
floatingip_res['dns_domain'] = floatingip_db.dns['dns_domain']
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
Expand Down
8 changes: 4 additions & 4 deletions neutron/db/external_net_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -81,14 +82,13 @@ def _network_is_external(self, context, net_id):
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)
Expand Down
8 changes: 4 additions & 4 deletions neutron/db/extradhcpopt_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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.
Expand Down Expand Up @@ -114,12 +115,11 @@ def _update_extra_dhcp_opts_on_port(self, context, id, port,

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'])
Loading

0 comments on commit 92372b9

Please sign in to comment.