Merge "Sync neutron callbacks into lib"

This commit is contained in:
Jenkins
2017-03-06 22:58:38 +00:00
committed by Gerrit Code Review
4 changed files with 143 additions and 0 deletions

View File

@@ -44,6 +44,8 @@ ABORT = 'abort_'
BEFORE = 'before_'
PRECOMMIT = 'precommit_'
OVS_RESTARTED = 'ovs_restarted'
class EventPayload(object):
"""Base event payload object.

View File

@@ -10,12 +10,19 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import inspect
from neutron_lib.callbacks import manager
# TODO(armax): consider adding locking
_CALLBACK_MANAGER = None
# stores a dictionary keyed on function pointers with a list of
# (resource, event) tuples to subscribe to on class initialization
_REGISTERED_CLASS_METHODS = collections.defaultdict(list)
def _get_callback_manager():
global _CALLBACK_MANAGER
@@ -52,3 +59,61 @@ def publish(resource, event, trigger, payload=None):
def clear():
_get_callback_manager().clear()
def receives(resource, events):
"""Use to decorate methods on classes before initialization.
Any classes that use this must themselves be decorated with the
@has_registry_receivers decorator to setup the __new__ method to
actually register the instance methods after initialization.
"""
def decorator(f):
for e in events:
_REGISTERED_CLASS_METHODS[f].append((resource, e))
return f
return decorator
def has_registry_receivers(klass):
"""Decorator to setup __new__ method in classes to subscribe bound methods.
Any method decorated with @receives above is an unbound method on a class.
This decorator sets up the class __new__ method to subscribe the bound
method in the callback registry 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_SUBSCRIBED', 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
func = getattr(unbound_method, 'im_func', unbound_method)
if func not in _REGISTERED_CLASS_METHODS:
continue
for resource, event in _REGISTERED_CLASS_METHODS[func]:
# subscribe the bound method
subscribe(getattr(instance, name), resource, event)
setattr(instance, '_DECORATED_METHODS_SUBSCRIBED', True)
return instance
klass.__new__ = replacement_new
return klass

View File

@@ -26,6 +26,7 @@ ROUTER_INTERFACE = 'router_interface'
SECURITY_GROUP = 'security_group'
SECURITY_GROUP_RULE = 'security_group_rule'
SEGMENT = 'segment'
SEGMENT_HOST_MAPPING = 'segment_host_mapping'
SUBNET = 'subnet'
SUBNETS = 'subnets'
SUBNET_GATEWAY = 'subnet_gateway'

View File

@@ -18,13 +18,88 @@ from oslotest import base
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import fixture
@registry.has_registry_receivers
class ObjectWithDecoratedCallback(object):
def __init__(self):
self.counter = 0
@registry.receives(resources.PORT, [events.AFTER_CREATE,
events.AFTER_UPDATE])
@registry.receives(resources.NETWORK, [events.AFTER_DELETE])
def callback(self, *args, **kwargs):
self.counter += 1
class MixinWithNew(object):
def __new__(cls):
i = super(MixinWithNew, cls).__new__(cls)
i.new_called = True
return i
@registry.has_registry_receivers
class AnotherObjectWithDecoratedCallback(ObjectWithDecoratedCallback,
MixinWithNew):
def __init__(self):
super(AnotherObjectWithDecoratedCallback, self).__init__()
self.counter2 = 0
@registry.receives(resources.NETWORK, [events.AFTER_DELETE])
def callback2(self, *args, **kwargs):
self.counter2 += 1
@registry.has_registry_receivers
class CallbackClassWithParameters(object):
def __init__(self, dummy):
pass
def my_callback():
pass
class CallBacksManagerTestCase(base.BaseTestCase):
def test_decorated_inst_method_receives(self):
i1 = ObjectWithDecoratedCallback()
registry.notify(resources.PORT, events.BEFORE_CREATE, self)
self.assertEqual(0, i1.counter)
registry.notify(resources.PORT, events.AFTER_CREATE, self)
self.assertEqual(1, i1.counter)
registry.notify(resources.PORT, events.AFTER_UPDATE, self)
self.assertEqual(2, i1.counter)
registry.notify(resources.NETWORK, events.AFTER_UPDATE, self)
self.assertEqual(2, i1.counter)
registry.notify(resources.NETWORK, events.AFTER_DELETE, self)
self.assertEqual(3, i1.counter)
i2 = ObjectWithDecoratedCallback()
self.assertEqual(0, i2.counter)
registry.notify(resources.NETWORK, events.AFTER_DELETE, self)
self.assertEqual(4, i1.counter)
self.assertEqual(1, i2.counter)
def test_object_inheriting_others_no_double_subscribe(self):
with mock.patch.object(registry, 'subscribe') as sub:
AnotherObjectWithDecoratedCallback()
# there are 3 methods (2 in parent and one in child) and 1
# subscribes to 2 events, so we expect 4 subscribes
self.assertEqual(4, len(sub.mock_calls))
def test_new_inheritance_not_broken(self):
self.assertTrue(AnotherObjectWithDecoratedCallback().new_called)
def test_object_new_not_broken(self):
CallbackClassWithParameters('dummy')
class TestCallbackRegistryDispatching(base.BaseTestCase):
def setUp(self):