Преглед изворни кода

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

almahmoud пре 7 година
родитељ
комит
62f723ca42
4 измењених фајлова са 156 додато и 16 уклоњено
  1. 14 7
      cloudbridge/cloud/base/events.py
  2. 36 7
      cloudbridge/cloud/base/middleware.py
  3. 14 2
      docs/topics/setup.rst
  4. 92 0
      test/test_middleware_system.py

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

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

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

@@ -1,3 +1,4 @@
+import functools
 import inspect
 import inspect
 import logging
 import logging
 import sys
 import sys
@@ -9,6 +10,7 @@ from ..base.events import InterceptingEventHandler
 from ..base.events import ObservingEventHandler
 from ..base.events import ObservingEventHandler
 from ..interfaces.events import EventHandler
 from ..interfaces.events import EventHandler
 from ..interfaces.exceptions import CloudBridgeBaseException
 from ..interfaces.exceptions import CloudBridgeBaseException
+from ..interfaces.exceptions import HandlerException
 from ..interfaces.middleware import Middleware
 from ..interfaces.middleware import Middleware
 from ..interfaces.middleware import MiddlewareManager
 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
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
         # set during auto discovery
         f.__event_handler = InterceptingEventHandler(
         f.__event_handler = InterceptingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
         return f
     return deco
     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
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
         # set during auto discovery
         f.__event_handler = ObservingEventHandler(
         f.__event_handler = ObservingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
         return f
     return deco
     return deco
 
 
@@ -40,14 +42,40 @@ def observe(event_pattern, priority):
 def implement(event_pattern, priority):
 def implement(event_pattern, priority):
     def deco(f):
     def deco(f):
         # Mark function as having an event_handler so we can discover it
         # 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(
         f.__event_handler = ImplementingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
         return f
     return deco
     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):
 class SimpleMiddlewareManager(MiddlewareManager):
 
 
     def __init__(self, event_manager):
     def __init__(self, event_manager):
@@ -117,8 +145,9 @@ class BaseMiddleware(Middleware):
         for _, func in getmembers_static(class_or_obj, inspect.ismethod):
         for _, func in getmembers_static(class_or_obj, inspect.ismethod):
             handler = getattr(func, "__event_handler", None)
             handler = getattr(func, "__event_handler", None)
             if handler and isinstance(handler, EventHandler):
             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)
                 discovered_handlers.append(handler)
         return discovered_handlers
         return discovered_handlers
 
 

+ 14 - 2
docs/topics/setup.rst

@@ -41,8 +41,20 @@ will override environment values.
     ## For GCE
     ## For GCE
     config = {'gce_service_creds_file': '<service_creds_file_name>.json'}
     config = {'gce_service_creds_file': '<service_creds_file_name>.json'}
     # Alternatively, we can supply a dictionary with the credentials values
     # 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)
     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 EventDebugLoggingMiddleware
 from cloudbridge.cloud.base.middleware import ExceptionWrappingMiddleware
 from cloudbridge.cloud.base.middleware import ExceptionWrappingMiddleware
 from cloudbridge.cloud.base.middleware import SimpleMiddlewareManager
 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 implement
 from cloudbridge.cloud.base.middleware import intercept
 from cloudbridge.cloud.base.middleware import intercept
 from cloudbridge.cloud.base.middleware import observe
 from cloudbridge.cloud.base.middleware import observe
 from cloudbridge.cloud.interfaces.exceptions import CloudBridgeBaseException
 from cloudbridge.cloud.interfaces.exceptions import CloudBridgeBaseException
+from cloudbridge.cloud.interfaces.exceptions import HandlerException
 from cloudbridge.cloud.interfaces.exceptions import \
 from cloudbridge.cloud.interfaces.exceptions import \
     InvalidConfigurationException
     InvalidConfigurationException
 from cloudbridge.cloud.interfaces.middleware import Middleware
 from cloudbridge.cloud.interfaces.middleware import Middleware
@@ -207,6 +209,96 @@ class MiddlewareSystemTestCase(unittest.TestCase):
             [handler.callback for handler in
             [handler.callback for handler in
              dispatcher.get_handlers_for_event(EVENT_NAME)])
              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):
 class ExceptionWrappingMiddlewareTestCase(unittest.TestCase):