Merge "Sync neutron callbacks into lib"
This commit is contained in:
@@ -44,6 +44,8 @@ ABORT = 'abort_'
|
||||
BEFORE = 'before_'
|
||||
PRECOMMIT = 'precommit_'
|
||||
|
||||
OVS_RESTARTED = 'ovs_restarted'
|
||||
|
||||
|
||||
class EventPayload(object):
|
||||
"""Base event payload object.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user