Browse Source

Merge branch 'master' of https://github.com/CloudVE/cloudbridge into middleware-refactor

almahmoud 7 years ago
parent
commit
62f723ca42

+ 14 - 7
cloudbridge/cloud/base/events.py

@@ -10,7 +10,7 @@ from ..interfaces.exceptions import HandlerException
 log = logging.getLogger(__name__)
 
 
-class InterceptingEventHandler(EventHandler):
+class BaseEventHandler(EventHandler):
 
     def __init__(self, event_pattern, priority, callback):
         self.__dispatcher = None
@@ -58,6 +58,17 @@ class InterceptingEventHandler(EventHandler):
     def dispatcher(self, value):
         self.__dispatcher = value
 
+    def unsubscribe(self):
+        if self.dispatcher:
+            self.dispatcher.unsubscribe(self)
+
+
+class InterceptingEventHandler(BaseEventHandler):
+
+    def __init__(self, event_pattern, priority, callback):
+        super(InterceptingEventHandler, self).__init__(event_pattern, priority,
+                                                       callback)
+
     def invoke(self, event_args, *args, **kwargs):
         next_handler = self._get_next_handler(event_args.get('event'))
         event_args['next_handler'] = next_handler
@@ -68,12 +79,8 @@ class InterceptingEventHandler(EventHandler):
         event_args.pop('next_handler', None)
         return result
 
-    def unsubscribe(self):
-        if self.dispatcher:
-            self.dispatcher.unsubscribe(self)
-
 
-class ObservingEventHandler(InterceptingEventHandler):
+class ObservingEventHandler(BaseEventHandler):
 
     def __init__(self, event_pattern, priority, callback):
         super(ObservingEventHandler, self).__init__(event_pattern, priority,
@@ -92,7 +99,7 @@ class ObservingEventHandler(InterceptingEventHandler):
             return None
 
 
-class ImplementingEventHandler(InterceptingEventHandler):
+class ImplementingEventHandler(BaseEventHandler):
 
     def __init__(self, event_pattern, priority, callback):
         super(ImplementingEventHandler, self).__init__(event_pattern, priority,

+ 36 - 7
cloudbridge/cloud/base/middleware.py

@@ -1,3 +1,4 @@
+import functools
 import inspect
 import logging
 import sys
@@ -9,6 +10,7 @@ from ..base.events import InterceptingEventHandler
 from ..base.events import ObservingEventHandler
 from ..interfaces.events import EventHandler
 from ..interfaces.exceptions import CloudBridgeBaseException
+from ..interfaces.exceptions import HandlerException
 from ..interfaces.middleware import Middleware
 from ..interfaces.middleware import MiddlewareManager
 
@@ -21,7 +23,7 @@ def intercept(event_pattern, priority):
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
         f.__event_handler = InterceptingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
     return deco
 
@@ -32,7 +34,7 @@ def observe(event_pattern, priority):
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
         f.__event_handler = ObservingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
     return deco
 
@@ -40,14 +42,40 @@ def observe(event_pattern, priority):
 def implement(event_pattern, priority):
     def deco(f):
         # Mark function as having an event_handler so we can discover it
-        # The callback cannot be set to f as it is not bound yet and will be
-        # set during auto discovery
+        # The callback will be unbound since we do not have access to `self`
+        # yet, and must be bound before invocation. This binding is done
+        # during middleware auto discovery
         f.__event_handler = ImplementingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
     return deco
 
 
+def dispatch_event(event):
+    """
+    The event decorator combines the functionality of the implement decorator
+    and a manual event dispatch into a single decorator.
+    """
+    def deco(f):
+        @functools.wraps(f)
+        def wrapper(self, *args, **kwargs):
+            events = getattr(self, 'events', None)
+            if events:
+                # Don't call the wrapped method, just dispatch the event,
+                # and the event handler will get invoked
+                return events.dispatch(self, event, *args, **kwargs)
+            else:
+                raise HandlerException(
+                    "Cannot dispatch event: {0}. The object {1} should have"
+                    " an events property".format(event, self))
+        # Mark function as having an event_handler so we can discover it
+        # The callback f is unbound and will be bound during middleware
+        # auto discovery
+        wrapper.__event_handler = ImplementingEventHandler(event, 2500, f)
+        return wrapper
+    return deco
+
+
 class SimpleMiddlewareManager(MiddlewareManager):
 
     def __init__(self, event_manager):
@@ -117,8 +145,9 @@ class BaseMiddleware(Middleware):
         for _, func in getmembers_static(class_or_obj, inspect.ismethod):
             handler = getattr(func, "__event_handler", None)
             if handler and isinstance(handler, EventHandler):
-                # Set the properly bound method as the callback
-                handler.callback = func
+                # Bind the currently unbound method
+                # and set the bound method as the callback
+                handler.callback = handler.callback.__get__(class_or_obj)
                 discovered_handlers.append(handler)
         return discovered_handlers
 

+ 14 - 2
docs/topics/setup.rst

@@ -41,8 +41,20 @@ will override environment values.
     ## For GCE
     config = {'gce_service_creds_file': '<service_creds_file_name>.json'}
     # Alternatively, we can supply a dictionary with the credentials values
-    # shown on the access credentials procurement page.
-    config = {'gce_service_creds_dict': credentials_dictionary}
+    # as the following:
+    gce_creds = {
+        "type": "service_account",
+        "project_id": "<project_name>",
+        "private_key_id": "<private_key_id>",
+        "private_key": "<private_key>",
+        "client_email": "<client_email>",
+        "client_id": "<client_id>",
+        "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+        "token_uri": "https://oauth2.googleapis.com/token",
+        "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+        "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-name%40my-project.iam.gserviceaccount.com"
+    }
+    config = {'gce_service_creds_dict': gce_creds}
     provider = CloudProviderFactory().create_provider(ProviderList.GCE, config)
 
 

+ 92 - 0
test/test_middleware_system.py

@@ -5,10 +5,12 @@ from cloudbridge.cloud.base.middleware import BaseMiddleware
 from cloudbridge.cloud.base.middleware import EventDebugLoggingMiddleware
 from cloudbridge.cloud.base.middleware import ExceptionWrappingMiddleware
 from cloudbridge.cloud.base.middleware import SimpleMiddlewareManager
+from cloudbridge.cloud.base.middleware import dispatch_event
 from cloudbridge.cloud.base.middleware import implement
 from cloudbridge.cloud.base.middleware import intercept
 from cloudbridge.cloud.base.middleware import observe
 from cloudbridge.cloud.interfaces.exceptions import CloudBridgeBaseException
+from cloudbridge.cloud.interfaces.exceptions import HandlerException
 from cloudbridge.cloud.interfaces.exceptions import \
     InvalidConfigurationException
 from cloudbridge.cloud.interfaces.middleware import Middleware
@@ -207,6 +209,96 @@ class MiddlewareSystemTestCase(unittest.TestCase):
             [handler.callback for handler in
              dispatcher.get_handlers_for_event(EVENT_NAME)])
 
+    def test_event_decorator(self):
+        EVENT_NAME = "some.event.occurred"
+
+        class SomeDummyClass(object):
+
+            def __init__(self):
+                self.invocation_order = ""
+                self.events = SimpleEventDispatcher()
+
+            @intercept(event_pattern="some.event.*", priority=900)
+            def my_callback_intcpt(self, event_args, *args, **kwargs):
+                self.invocation_order += "intcpt_"
+                assert 'first_pos_arg' in args
+                assert kwargs.get('a_keyword_arg') == "something"
+                next_handler = event_args.get('next_handler')
+                return next_handler.invoke(event_args, *args, **kwargs)
+
+            @observe(event_pattern="some.event.*", priority=3000)
+            def my_callback_obs(self, event_args, *args, **kwargs):
+                self.invocation_order += "obs"
+                assert 'first_pos_arg' in args
+                assert event_args['result'] == "hello"
+                assert kwargs.get('a_keyword_arg') == "something"
+
+            @dispatch_event(EVENT_NAME)
+            def my_callback_impl(self, *args, **kwargs):
+                self.invocation_order += "impl_"
+                assert 'first_pos_arg' in args
+                assert kwargs.get('a_keyword_arg') == "something"
+                return "hello"
+
+        obj = SomeDummyClass()
+        manager = SimpleMiddlewareManager(obj.events)
+        middleware = manager.add(obj)
+
+        # calling my_implementation should trigger all events
+        result = obj.my_callback_impl(
+            'first_pos_arg', a_keyword_arg='something')
+
+        self.assertEqual(result, "hello")
+        self.assertEqual(obj.invocation_order, "intcpt_impl_obs")
+        callbacks = [handler.callback for handler
+                     in middleware.events.get_handlers_for_event(EVENT_NAME)]
+
+        self.assertNotIn(
+            obj.my_callback_impl, callbacks,
+            "The event impl callback should not be directly contained"
+            " in callbacks to avoid a circular dispatch")
+
+        self.assertEqual(
+            len(set(callbacks).difference(
+                set([obj.my_callback_intcpt,
+                     obj.my_callback_obs]))),
+            1,
+            "The event impl callback should be included in the list of"
+            "  callbacks indirectly")
+
+        manager.remove(middleware)
+
+        # calling my_implementation again should trigger a None response
+        result = obj.my_callback_impl(
+            'first_pos_arg', a_keyword_arg='something')
+
+        self.assertEqual(result, None)
+
+    def test_event_decorator_no_event_property(self):
+        EVENT_NAME = "some.event.occurred"
+
+        class SomeDummyClass(object):
+
+            @dispatch_event(EVENT_NAME)
+            def my_callback_impl(self, *args, **kwargs):
+                assert 'first_pos_arg' in args
+                assert kwargs.get('a_keyword_arg') == "something"
+                return "hello"
+
+        obj = SomeDummyClass()
+        events = SimpleEventDispatcher()
+        manager = SimpleMiddlewareManager(events)
+        manager.add(obj)
+
+        # calling my_implementation should raise an exception
+        with self.assertRaises(HandlerException):
+            obj.my_callback_impl('first_pos_arg', a_keyword_arg='something')
+
+        obj.events = events
+        result = obj.my_callback_impl('first_pos_arg',
+                                      a_keyword_arg='something')
+        self.assertEqual(result, "hello")
+
 
 class ExceptionWrappingMiddlewareTestCase(unittest.TestCase):