Explorar o código

Merge pull request #172 from CloudVE/middleware2

Middleware tests + refactoring
Nuwan Goonasekera %!s(int64=7) %!d(string=hai) anos
pai
achega
d8298824d3

+ 168 - 0
cloudbridge/cloud/base/events.py

@@ -0,0 +1,168 @@
+import bisect
+import fnmatch
+import logging
+import re
+
+from ..interfaces.events import EventDispatcher
+from ..interfaces.events import EventHandler
+from ..interfaces.exceptions import HandlerException
+
+log = logging.getLogger(__name__)
+
+
+class InterceptingEventHandler(EventHandler):
+
+    def __init__(self, event_pattern, priority, callback):
+        self.__dispatcher = None
+        self.event_pattern = event_pattern
+        self.priority = priority
+        self.callback = callback
+
+    def __lt__(self, other):
+        # This is required for the bisect module to insert
+        # event handlers sorted by priority
+        return self.priority < other.priority
+
+    def _get_next_handler(self, event):
+        handler_list = self.dispatcher.get_handlers_for_event(event)
+        # find position of this handler
+        pos = bisect.bisect_left(handler_list, self)
+        assert handler_list[pos] == self
+        if pos < len(handler_list)-1:
+            return handler_list[pos+1]
+        else:
+            return None
+
+    def event_pattern(self):
+        pass
+
+    def priority(self):
+        pass
+
+    def callback(self):
+        pass
+
+    @property
+    def dispatcher(self):
+        return self.__dispatcher
+
+    @dispatcher.setter
+    # pylint:disable=arguments-differ
+    def dispatcher(self, value):
+        self.__dispatcher = value
+
+    def invoke(self, **kwargs):
+        kwargs.pop('next_handler', None)
+        next_handler = self._get_next_handler(kwargs.get('event', None))
+        # callback is responsible for invoking the next_handler and
+        # controlling the result value
+        return self.callback(next_handler=next_handler, **kwargs)
+
+    def unsubscribe(self):
+        if self.dispatcher:
+            self.dispatcher.unsubscribe(self)
+
+
+class ObservingEventHandler(InterceptingEventHandler):
+
+    def __init__(self, event_pattern, priority, callback):
+        super(ObservingEventHandler, self).__init__(event_pattern, priority,
+                                                    callback)
+
+    def invoke(self, **kwargs):
+        # Notify listener. Ignore result from observable handler
+        kwargs.pop('next_handler', None)
+        self.callback(**kwargs)
+        # Kick off the handler chain
+        next_handler = self._get_next_handler(kwargs.get('event', None))
+        if next_handler:
+            return next_handler.invoke(**kwargs)
+        else:
+            return None
+
+
+class SimpleEventDispatcher(EventDispatcher):
+
+    def __init__(self):
+        # The dict key is event_pattern.
+        # The dict value is a list of handlers for the event pattern, sorted
+        # by event priority
+        self.__events = {}
+        self.__handler_cache = {}
+
+    def get_handlers_for_event(self, event):
+        handlers = self.__handler_cache.get(event)
+        if handlers is None:
+            self.__handler_cache[event] = self._create_handler_cache(
+                event)
+            return self.__handler_cache.get(event)
+        else:
+            return handlers
+
+    def _create_handler_cache(self, event):
+        cache_list = []
+        # Find all patterns matching event
+        for key in self.__events.keys():
+            if re.search(fnmatch.translate(key), event):
+                cache_list.extend(self.__events[key])
+        cache_list.sort(key=lambda h: h.priority)
+
+        # Make sure all priorities are unique
+        priority_list = [h.priority for h in cache_list]
+        if len(set(priority_list)) != len(priority_list):
+            guilty_prio = None
+            for prio in priority_list:
+                if prio == guilty_prio:
+                    break
+                guilty_prio = prio
+
+            # guilty_prio should never be none since we checked for
+            # duplicates before iterating
+            guilty_names = [h.callback.__name__ for h in cache_list
+                            if h.priority == guilty_prio]
+
+            message = "Event '{}' has multiple subscribed handlers " \
+                      "at priority '{}', with function names [{}]. " \
+                      "Each priority must only have a single " \
+                      "corresponding handler." \
+                .format(event, guilty_prio, ", ".join(guilty_names))
+            raise HandlerException(message)
+        return cache_list
+
+    def subscribe(self, event_handler):
+        event_handler.dispatcher = self
+        handler_list = self.__events.get(event_handler.event_pattern, [])
+        handler_list.append(event_handler)
+        self.__events[event_handler.event_pattern] = handler_list
+        # invalidate cache
+        self.__handler_cache = {}
+
+    def unsubscribe(self, event_handler):
+        handler_list = self.__events.get(event_handler.event_pattern, [])
+        handler_list.remove(event_handler)
+        event_handler.dispatcher = None
+        # invalidate cache
+        self.__handler_cache = {}
+
+    def observe(self, event_pattern, priority, callback):
+        handler = ObservingEventHandler(event_pattern, priority, callback)
+        self.subscribe(handler)
+        return handler
+
+    def intercept(self, event_pattern, priority, callback):
+        handler = InterceptingEventHandler(event_pattern, priority, callback)
+        self.subscribe(handler)
+        return handler
+
+    def emit(self, sender, event, **kwargs):
+        handlers = self.get_handlers_for_event(event)
+
+        if handlers:
+            # only kick off first handler in chain
+            return handlers[0].invoke(sender=sender, event=event,
+                                      **kwargs)
+        else:
+            message = "Event '{}' has no subscribed handlers.".\
+                format(event)
+            log.warning(message)
+            return None

+ 154 - 0
cloudbridge/cloud/base/middleware.py

@@ -0,0 +1,154 @@
+import inspect
+import logging
+import sys
+
+import six
+
+from ..base.events import InterceptingEventHandler
+from ..base.events import ObservingEventHandler
+from ..interfaces.exceptions import CloudBridgeBaseException
+from ..interfaces.middleware import Middleware
+from ..interfaces.middleware import MiddlewareManager
+
+log = logging.getLogger(__name__)
+
+
+def intercept(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
+        f.__event_handler = InterceptingEventHandler(
+            event_pattern, priority, None)
+        return f
+    return deco
+
+
+def observe(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
+        f.__event_handler = ObservingEventHandler(
+            event_pattern, priority, None)
+        return f
+    return deco
+
+
+class SimpleMiddlewareManager(MiddlewareManager):
+
+    def __init__(self, event_manager):
+        self.events = event_manager
+        self.middleware_list = []
+
+    def add(self, middleware):
+        if isinstance(middleware, Middleware):
+            m = middleware
+        else:
+            m = AutoDiscoveredMiddleware(middleware)
+        m.install(self.events)
+        self.middleware_list.append(m)
+        return m
+
+    def remove(self, middleware):
+        middleware.uninstall()
+        self.middleware_list.remove(middleware)
+
+
+class BaseMiddleware(Middleware):
+
+    def __init__(self):
+        self.event_handlers = []
+
+    def install(self, event_manager):
+        self.events = event_manager
+        discovered_handlers = self.discover_handlers(self)
+        self.add_handlers(discovered_handlers)
+
+    def add_handlers(self, handlers):
+        if not hasattr(self, "event_handlers"):
+            # In case the user forgot to call super class init
+            self.event_handlers = []
+        for handler in handlers:
+            self.events.subscribe(handler)
+        self.event_handlers.extend(handlers)
+
+    def discover_handlers(self, class_or_obj):
+        discovered_handlers = []
+        for _, func in inspect.getmembers(class_or_obj, inspect.ismethod):
+            handler = getattr(func, "__event_handler", None)
+            if handler:
+                # Set the properly bound method as the callback
+                handler.callback = func
+                discovered_handlers.append(handler)
+        return discovered_handlers
+
+    def uninstall(self):
+        for handler in self.event_handlers:
+            handler.unsubscribe()
+        self.event_handlers = []
+        self.events = None
+
+
+class AutoDiscoveredMiddleware(BaseMiddleware):
+
+    def __init__(self, class_or_obj):
+        super(AutoDiscoveredMiddleware, self).__init__()
+        self.obj_to_discover = class_or_obj
+
+    def install(self, event_manager):
+        super(AutoDiscoveredMiddleware, self).install(event_manager)
+        discovered_handlers = self.discover_handlers(self.obj_to_discover)
+        self.add_handlers(discovered_handlers)
+
+
+class EventDebugLoggingMiddleware(BaseMiddleware):
+    """
+    Logs all event parameters. This middleware should not be enabled other
+    than for debugging, as it could log sensitive parameters such as
+    access keys.
+    """
+    def setup(self):
+        self.add_observer(
+            event_pattern="*", priority=1100, callback=self.pre_log_event)
+        self.add_interceptor(
+            event_pattern="*", priority=1150, callback=self.post_log_event)
+
+    @observe(event_pattern="*", priority=1100)
+    def pre_log_event(self, **kwargs):
+        log.debug("Event: {0} invoked with args: {1}".format(
+            kwargs.get("event"), kwargs))
+
+    @intercept(event_pattern="*", priority=1150)
+    def post_log_event(self, **kwargs):
+        next_handler = kwargs.pop("next_handler")
+        result = next_handler.invoke(**kwargs)
+        log.debug("Event: {0} result: {1}".format(
+            kwargs.get("event"), result))
+        return result
+
+
+class ExceptionWrappingMiddleware(BaseMiddleware):
+    """
+    Wraps all unhandled exceptions in cloudbridge exceptions.
+    """
+    def setup(self):
+        self.add_interceptor(
+            event_pattern="*", priority=1050, callback=self.wrap_exception)
+
+    def wrap_exception(self, **kwargs):
+        next_handler = kwargs.pop("next_handler")
+        try:
+            return next_handler.invoke(**kwargs)
+        except Exception as e:
+            if isinstance(e, CloudBridgeBaseException):
+                raise
+            else:
+                ex_type, ex_value, traceback = sys.exc_info()
+                cb_ex = CloudBridgeBaseException(
+                    "CloudBridgeBaseException: {0} from exception type: {1}"
+                    .format(ex_value, ex_type))
+                if sys.version_info >= (3, 0):
+                    six.raise_from(cb_ex, e)
+                else:
+                    six.reraise(CloudBridgeBaseException, cb_ex, traceback)

+ 11 - 176
cloudbridge/cloud/base/provider.py

@@ -1,10 +1,7 @@
 """Base implementation of a provider interface."""
 """Base implementation of a provider interface."""
-import fnmatch
 import functools
 import functools
 import logging
 import logging
 import os
 import os
-import re
-from copy import deepcopy
 from os.path import expanduser
 from os.path import expanduser
 try:
 try:
     from configparser import ConfigParser
     from configparser import ConfigParser
@@ -13,11 +10,11 @@ except ImportError:  # Python 2
 
 
 import six
 import six
 
 
-from cloudbridge.cloud.interfaces import CloudProvider
-from cloudbridge.cloud.interfaces.exceptions import HandlerException
-from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
-from cloudbridge.cloud.interfaces.provider import HandlerType
-from cloudbridge.cloud.interfaces.resources import Configuration
+from ..base.events import SimpleEventDispatcher
+from ..base.middleware import SimpleMiddlewareManager
+from ..interfaces import CloudProvider
+from ..interfaces.exceptions import ProviderConnectionException
+from ..interfaces.resources import Configuration
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
@@ -84,179 +81,13 @@ class BaseConfiguration(Configuration):
         return self.get('cb_debug', os.environ.get('CB_DEBUG', False))
         return self.get('cb_debug', os.environ.get('CB_DEBUG', False))
 
 
 
 
-class EventDispatcher(object):
-    def __init__(self):
-        self.__events = {}
-        self.__initialized = {}
-
-    def get_handlers(self, event_name):
-        return self.__events.get(event_name)
-
-    def check_initialized(self, event_name):
-        return self.__initialized.get(event_name) or False
-
-    def mark_initialized(self, event_name):
-        self.__initialized[event_name] = True
-
-    def subscribe(self, event_name, priority, callback):
-        """
-        Subscribe a handler by event name to the dispatcher.
-
-        :type event_name: str
-        :param event_name: The name of the event to which you are subscribing
-        the callback function.
-        :type priority: int
-        :param priority: The priority that this handler should be given.
-        When the event is emitted, all handlers will be run in order of
-        priority.
-        :type callback: function
-        :param callback: The callback function that should be called with
-        the parameters given at when the even is emitted.
-        """
-        handler = EventHandler(HandlerType.SUBSCRIPTION, callback, priority)
-        if not self.__events.get(event_name):
-            self.__events[event_name] = list()
-        self.__events[event_name].append((priority, handler))
-
-    def intercept(self, event_name, priority, callback):
-        handler = EventHandler(HandlerType.INTERCEPTION, callback, priority)
-        if not self.__events.get(event_name):
-            self.__events[event_name] = list()
-        self.__events[event_name].append((priority, handler))
-
-    def call(self, event_name, priority, callback, **kwargs):
-        handler = EventHandler(HandlerType.SUBSCRIPTION, callback, priority)
-        if not self.__events.get(event_name):
-            self.__events[event_name] = list()
-        # Although handler object has priority property, keep it a pair to not
-        # access each handler when sorting
-        self.__events[event_name].append((priority, handler))
-        try:
-            ret_obj = self._emit(event_name, **kwargs)
-        finally:
-            self.__events[event_name].remove((priority, handler))
-        return ret_obj
-
-    def _emit(self, event_name, **kwargs):
-
-        def _match_and_sort(event_name):
-            new_list = []
-            for key in self.__events.keys():
-                if re.search(fnmatch.translate(key), event_name):
-                    new_list.extend(deepcopy(self.__events[key]))
-            new_list.sort(key=lambda x: x[0])
-            # Make sure all priorities are unique
-            priority_list = [x[0] for x in new_list]
-            if len(set(priority_list)) != len(priority_list):
-
-                guilty_prio = None
-                for prio in priority_list:
-                    if prio == guilty_prio:
-                        break
-                    guilty_prio = prio
-
-                # guilty_prio should never be none since we checked for
-                # duplicates before iterating
-                guilty_names = [x[1].callback.__name__
-                                        for x in new_list
-                                        if x[0] == guilty_prio]
-
-                message = "Event '{}' has multiple subscribed handlers " \
-                          "at priority '{}', with function names [{}]. " \
-                          "Each priority must only have a single " \
-                          "corresponding handler." \
-                    .format(event_name, priority, ", ".join(guilty_names))
-                raise HandlerException(message)
-
-            return new_list
-
-        if not self.__events.get(event_name):
-            message = "Event '{}' has no subscribed handlers.".\
-                format(event_name)
-            raise HandlerException(message)
-
-        prev_handler = None
-        first_handler = None
-        for (priority, handler) in _match_and_sort(event_name):
-            if not first_handler:
-                first_handler = handler
-            if prev_handler:
-                prev_handler.next_handler = handler
-            prev_handler = handler
-        return first_handler.invoke(**kwargs)
-
-
-class EventHandler(object):
-    def __init__(self, handler_type, callback, priority):
-        self.handler_type = handler_type
-        self.callback = callback
-        self._next_handler = None
-        self.priority = priority
-
-    @property
-    def next_handler(self):
-        return self._next_handler
-
-    @next_handler.setter
-    def next_handler(self, new_handler):
-        self._next_handler = new_handler
-
-    def invoke(self, **kwargs):
-        if self.handler_type == HandlerType.SUBSCRIPTION:
-            result = self.callback(**kwargs)
-
-            next = self.next_handler
-            if next:
-                if next.handler_type == HandlerType.SUBSCRIPTION:
-                    if result or not kwargs.get('callback_result', None):
-                        kwargs['callback_result'] = result
-                    new_result = next.invoke(**kwargs)
-                elif next.handler_type == HandlerType.INTERCEPTION:
-                    new_result = next.invoke(**kwargs)
-
-                if new_result:
-                    result = new_result
-
-            self.next_handler = None
-
-        elif self.handler_type == HandlerType.INTERCEPTION:
-            kwargs.pop('next_handler', None)
-            result = self.callback(next_handler=self.next_handler, **kwargs)
-            self.next_handler = None
-
-        return result
-
-    def skip(self, **kwargs):
-        if self.next_handler:
-            self.next_handler.invoke(**kwargs)
-            self.next_handler = None
-
-    def skip_to_name(self, function_name, **kwargs):
-        if self.callback.__name__ == function_name:
-            self.invoke(**kwargs)
-        elif self.next_handler:
-            self.next_handler.skip_to_name(function_name, **kwargs)
-            self.next_handler = None
-
-    def skip_to_priority(self, priority, **kwargs):
-        if self.priority == priority:
-            self.invoke(**kwargs)
-        elif self.next_handler:
-            self.next_handler.skip_to_priority(priority, **kwargs)
-            self.next_handler = None
-
-    def skip_rest(self):
-        if self.next_handler:
-            self.next_handler.skip_rest()
-            self.next_handler = None
-
-
 class BaseCloudProvider(CloudProvider):
 class BaseCloudProvider(CloudProvider):
     def __init__(self, config):
     def __init__(self, config):
         self._config = BaseConfiguration(config)
         self._config = BaseConfiguration(config)
         self._config_parser = ConfigParser()
         self._config_parser = ConfigParser()
         self._config_parser.read(CloudBridgeConfigLocations)
         self._config_parser.read(CloudBridgeConfigLocations)
-        self._events = EventDispatcher()
+        self._events = SimpleEventDispatcher()
+        self._middleware = SimpleMiddlewareManager(self._events)
 
 
     @property
     @property
     def config(self):
     def config(self):
@@ -270,6 +101,10 @@ class BaseCloudProvider(CloudProvider):
     def events(self):
     def events(self):
         return self._events
         return self._events
 
 
+    @property
+    def middleware(self):
+        return self._middleware
+
     def authenticate(self):
     def authenticate(self):
         """
         """
         A basic implementation which simply runs a low impact command to
         A basic implementation which simply runs a low impact command to

+ 22 - 22
cloudbridge/cloud/base/services.py

@@ -41,30 +41,30 @@ class BaseCloudService(CloudService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         self._provider = provider
         self._provider = provider
-        self._service_event_name = "provider"
+        self._service_event_pattern = "provider"
 
 
     @property
     @property
     def provider(self):
     def provider(self):
         return self._provider
         return self._provider
 
 
-    def _generate_event_name(self, func_name):
-        return ".".join((self._service_event_name, func_name))
+    def _generate_event_pattern(self, func_name):
+        return ".".join((self._service_event_pattern, func_name))
 
 
-    def subscribe(self, func_name, priority, callback):
-        event_name = self._generate_event_name(func_name)
-        self.provider.events.subscribe(event_name, priority, callback)
+    def observe(self, func_name, priority, callback):
+        event_pattern = self._generate_event_pattern(func_name)
+        self.provider.events.observe(event_pattern, priority, callback)
 
 
     def check_initialized(self, func_name):
     def check_initialized(self, func_name):
-        event_name = self._generate_event_name(func_name)
-        return self.provider.events.check_initialized(event_name)
+        event_pattern = self._generate_event_pattern(func_name)
+        return self.provider.events.check_initialized(event_pattern)
 
 
     def mark_initialized(self, func_name):
     def mark_initialized(self, func_name):
-        event_name = self._generate_event_name(func_name)
-        self.provider.events.mark_initialized(event_name)
+        event_pattern = self._generate_event_pattern(func_name)
+        self.provider.events.mark_initialized(event_pattern)
 
 
     def call(self, func_name, priority, callback, **kwargs):
     def call(self, func_name, priority, callback, **kwargs):
-        event_name = self._generate_event_name(func_name)
-        return self.provider.events.call(event_name, priority, callback,
+        event_pattern = self._generate_event_pattern(func_name)
+        return self.provider.events.call(event_pattern, priority, callback,
                                          **kwargs)
                                          **kwargs)
 
 
 
 
@@ -145,7 +145,7 @@ class BaseBucketService(
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseBucketService, self).__init__(provider)
         super(BaseBucketService, self).__init__(provider)
-        self._service_event_name = "provider.storage.buckets"
+        self._service_event_pattern = "provider.storage.buckets"
 
 
     def _init_get(self):
     def _init_get(self):
         def _get_pre_log(bucket_id):
         def _get_pre_log(bucket_id):
@@ -155,8 +155,8 @@ class BaseBucketService(
         def _get_post_log(callback_result, bucket_id):
         def _get_post_log(callback_result, bucket_id):
             log.debug("Returned bucket object: {}".format(callback_result))
             log.debug("Returned bucket object: {}".format(callback_result))
 
 
-        self.subscribe("get", 2000, _get_pre_log)
-        self.subscribe("get", 3000, _get_post_log)
+        self.observe("get", 2000, _get_pre_log)
+        self.observe("get", 3000, _get_post_log)
         self.mark_initialized("get")
         self.mark_initialized("get")
 
 
     def _init_find(self):
     def _init_find(self):
@@ -167,8 +167,8 @@ class BaseBucketService(
         def _find_post_log(callback_result, **kwargs):
         def _find_post_log(callback_result, **kwargs):
             log.debug("Returned bucket objects: {}".format(callback_result))
             log.debug("Returned bucket objects: {}".format(callback_result))
 
 
-        self.subscribe("find", 2000, _find_pre_log)
-        self.subscribe("find", 3000, _find_post_log)
+        self.observe("find", 2000, _find_pre_log)
+        self.observe("find", 3000, _find_post_log)
         self.mark_initialized("find")
         self.mark_initialized("find")
 
 
     def _init_list(self):
     def _init_list(self):
@@ -184,8 +184,8 @@ class BaseBucketService(
                 log.debug(
                 log.debug(
                     "Returned bucket objects: {}".format(callback_result))
                     "Returned bucket objects: {}".format(callback_result))
 
 
-            self.subscribe("list", 2000, _list_pre_log)
-            self.subscribe("list", 3000, _list_post_log)
+            self.observe("list", 2000, _list_pre_log)
+            self.observe("list", 3000, _list_post_log)
             self.mark_initialized("list")
             self.mark_initialized("list")
 
 
     def _init_create(self):
     def _init_create(self):
@@ -199,8 +199,8 @@ class BaseBucketService(
         def _create_post_log(callback_result, name, location):
         def _create_post_log(callback_result, name, location):
             log.debug("Returned bucket object: {}".format(callback_result))
             log.debug("Returned bucket object: {}".format(callback_result))
 
 
-        self.subscribe("create", 2000, _create_pre_log)
-        self.subscribe("create", 3000, _create_post_log)
+        self.observe("create", 2000, _create_pre_log)
+        self.observe("create", 3000, _create_post_log)
         self.mark_initialized("create")
         self.mark_initialized("create")
 
 
     def get(self, bucket_id):
     def get(self, bucket_id):
@@ -264,7 +264,7 @@ class BaseBucketObjectService(
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(BaseBucketObjectService, self).__init__(provider)
         super(BaseBucketObjectService, self).__init__(provider)
-        self._service_event_name = "provider.storage.bucket_objects"
+        self._service_event_pattern = "provider.storage.bucket_objects"
         self._bucket = None
         self._bucket = None
 
 
     def set_bucket(self, bucket):
     def set_bucket(self, bucket):

+ 181 - 0
cloudbridge/cloud/interfaces/events.py

@@ -0,0 +1,181 @@
+from abc import ABCMeta, abstractmethod
+from abc import abstractproperty
+
+
+class EventDispatcher(object):
+
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def observe(self, event_pattern, priority, callback):
+        """
+        Register a callback to be invoked when a given event occurs. `observe`
+        will allow you to listen to events as they occur, but not modify the
+        event chain or its parameters. If you need to modify an event, use
+        the `intercept` method. `observe` is a simplified case of `intercept`,
+        and receives a simpler list of parameters in its callback.
+
+        :type event_pattern: str
+        :param event_pattern: The name or pattern of the event to which you are
+            subscribing the callback function. The pattern may contain glob
+            wildcard parameters to be notified on any matching event name.
+
+        :type priority: int
+        :param priority: The priority that this handler should be given.
+            When the event is emitted, all handlers will be run in order of
+            priority.
+
+        :type callback: function
+        :param callback: The callback function that should be called with
+            the parameters given at when the even is emitted.
+
+        :rtype: :class:`.EventHandler`
+        :return:  An object of class EventHandler. The EventHandler will
+        already be subscribed to the dispatcher, and need not be manually
+        subscribed. The returned event handler can be used to unsubscribe
+        from future events when required.
+        """
+        pass
+
+    @abstractmethod
+    def intercept(self, event_pattern, priority, callback):
+        """
+        Register a callback to be invoked when a given event occurs. Intercept
+        will allow you to both observe events and modify the event chain and
+        its parameters. If you only want to observe an event, use the `observe`
+        method. Intercept and `observe` only differ in what parameters the
+        callback receives, with intercept receiving additional parameters to
+        allow controlling the event chain.
+
+        :type event_pattern: str
+        :param event_pattern: The name or pattern of the event to which you are
+            subscribing the callback function. The pattern may contain glob
+            wildcard parameters to be notified on any matching event name.
+
+        :type priority: int
+        :param priority: The priority that this handler should be given.
+            When the event is emitted, all handlers will be run in order of
+            priority.
+
+        :type callback: function
+        :param callback: The callback function that should be called with
+            the parameters given at when the even is emitted.
+
+        :rtype: :class:`.EventHandler`
+        :return:  An object of class EventHandler. The EventHandler will
+        already be subscribed to the dispatcher, and need not be manually
+        subscribed. The returned event handler can be used to unsubscribe
+        from future events when required.
+        """
+        pass
+
+    @abstractmethod
+    def emit(self, sender, event, **kwargs):
+        """
+        Raises an event while registering a given callback
+
+        :type event: str
+        :param event: The name of the event which is being raised.
+
+        :type sender: object
+        :param sender: The object which is raising the event
+        """
+        pass
+
+    @abstractmethod
+    def subscribe(self, event_handler):
+        """
+        Register an event handler with this dispatcher. The observe and
+        intercept methods will construct an event handler and subscribe it for
+        you automatically, and therefore, there is usually no need to invoke
+        subscribe directly unless you have a special type of event handler.
+
+        :type event_handler: :class:`.EventHandler`
+        :param event_handler: An object of class EventHandler.
+        """
+        pass
+
+    @abstractmethod
+    def unsubscribe(self, event_handler):
+        """
+        Unregister an event handler from this dispatcher. The event handler
+        will no longer be notified on events.
+
+        :type event_handler: :class:`.EventHandler`
+        :param event_handler: An object of class EventHandler.
+        """
+        pass
+
+    @abstractmethod
+    def get_handlers_for_event(self, event):
+        """
+        Returns a list of all registered handlers for a given event, sorted
+        in order of priority.
+
+        :type event: str
+        :param event: The name of the event
+        """
+        pass
+
+
+class EventHandler(object):
+
+    __metaclass__ = ABCMeta
+
+    @abstractproperty
+    def event_pattern(self):
+        """
+        The event pattern that this handler is listening to. May include glob
+        patterns, in which case, any matching event name will trigger this
+        handler.
+        e.g.
+            provider.storage.*
+            provider.storage.volumes.list
+        """
+        pass
+
+    @abstractproperty
+    def priority(self):
+        """
+        The priority of this handler. When a matching event occurs, handlers
+        are invoked in order of priority.
+        The priorities ranges from 0-1000 and 2000-3000 and >4000 are reserved
+        for use by cloudbridge.
+        Users should listen on priorities between 1000-2000 for pre handlers
+        and 2000-3000 for post handlers.
+        e.g.
+            provider.storage.*
+            provider.storage.volumes.list
+        """
+        pass
+
+    @abstractproperty
+    def callback(self):
+        """
+        The callback that will be triggered when this event handler is invoked.
+        The callback signature must accept **kwargs and pass them through.
+        In general, the callback will always receive the event that
+        triggered this handler as an argument.
+        """
+        pass
+
+    @abstractmethod
+    def invoke(self, **kwargs):
+        """
+        Executes this event handler's callback
+        """
+        pass
+
+    @abstractmethod
+    def unsubscribe(self):
+        """
+        Unsubscribes from currently subscribed events.
+        """
+        pass
+
+    @abstractproperty
+    def dispatcher(self):
+        """
+        Get or sets the dispatcher currently associated with this event handler
+        """
+        pass

+ 61 - 0
cloudbridge/cloud/interfaces/middleware.py

@@ -0,0 +1,61 @@
+from abc import ABCMeta, abstractmethod
+
+
+class Middleware(object):
+    """
+    Provides a mechanism for grouping related event handlers together, to
+    provide logically cohesive middleware. The middleware class allows event
+    handlers to subscribe to events through the install method, and unsubscribe
+    through the uninstall method. This allows event handlers to be added and
+    removed as a group. The event handler implementations will also typically
+    live inside the middleware class. For example, LoggingMiddleware may
+    register multiple event handlers to log data before and after calls.
+    ResourceTrackingMiddleware may track all objects that are created or
+    deleted.
+    """
+
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def install(self, provider):
+        """
+        Use this method to subscribe all event handlers that are part of this
+        middleware. The install method will be called when the middleware is
+        first added to a MiddleWareManager.
+
+        :type provider: :class:`.Provider`
+        :param provider: The provider that this middleware belongs to
+        """
+        pass
+
+    @abstractmethod
+    def uninstall(self, provider):
+        """
+        Use this method to unsubscribe all event handlers for this middleware.
+        """
+        pass
+
+
+class MiddlewareManager(object):
+    """
+    Provides a mechanism for tracking a list of installed middleware
+    """
+
+    __metaclass__ = ABCMeta
+
+    @abstractmethod
+    def add(self, middleware):
+        """
+        Use this method to add middleware to this middleware manager.
+
+        :type middleware: :class:`.Middleware`
+        :param middleware: The middleware implementation
+        """
+        pass
+
+    @abstractmethod
+    def remove(self, middleware):
+        """
+        Use this method to remove this middleware from the middleware manager.
+        """
+        pass

+ 0 - 9
cloudbridge/cloud/interfaces/provider.py

@@ -2,7 +2,6 @@
 Specification for a provider interface
 Specification for a provider interface
 """
 """
 from abc import ABCMeta, abstractmethod, abstractproperty
 from abc import ABCMeta, abstractmethod, abstractproperty
-from enum import Enum
 
 
 
 
 class CloudProvider(object):
 class CloudProvider(object):
@@ -246,11 +245,3 @@ class DeploymentProvider(object):
         Deploys on given target, where target is an Instance or Container
         Deploys on given target, where target is an Instance or Container
         """
         """
         pass
         pass
-
-
-class HandlerType(Enum):
-    """
-    Handler Types.
-    """
-    SUBSCRIPTION = 'subscription'
-    INTERCEPTION = 'intercept'

+ 355 - 0
test/test_event_system.py

@@ -0,0 +1,355 @@
+import unittest
+
+from cloudbridge.cloud.base.events import SimpleEventDispatcher
+from cloudbridge.cloud.interfaces.events import EventHandler
+from cloudbridge.cloud.interfaces.exceptions import HandlerException
+
+
+class EventSystemTestCase(unittest.TestCase):
+
+    def test_emit_event_no_handlers(self):
+        dispatcher = SimpleEventDispatcher()
+        result = dispatcher.emit(self, "event.hello.world")
+        self.assertIsNone(result, "Result should be none as there are no"
+                          "registered handlers")
+
+    def test_emit_event_observing_handler(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': EVENT_NAME})
+            callback_tracker[0] += 'obs'
+            return "hello"
+
+        dispatcher = SimpleEventDispatcher()
+        handler = dispatcher.observe(event_pattern=EVENT_NAME, priority=1000,
+                                     callback=my_callback)
+        self.assertIsInstance(handler, EventHandler)
+        result = dispatcher.emit(self, EVENT_NAME)
+        self.assertEqual(
+            callback_tracker[0], "obs", "callback should have been invoked"
+            "once and contain value `obs` but tracker value is {0}".format(
+                callback_tracker[0]))
+        self.assertIsNone(result, "Result should be none as this is an"
+                          " observing handler")
+
+    def test_emit_event_intercepting_handler(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': EVENT_NAME,
+                                  'next_handler': None})
+            callback_tracker[0] += "intcpt"
+            return "world"
+
+        dispatcher = SimpleEventDispatcher()
+        handler = dispatcher.intercept(event_pattern=EVENT_NAME, priority=1000,
+                                       callback=my_callback)
+        self.assertIsInstance(handler, EventHandler)
+        result = dispatcher.emit(self, EVENT_NAME)
+        self.assertEqual(
+            callback_tracker[0], "intcpt", "callback should have been invoked"
+            "once and contain value `intcpt` but tracker value is {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "world", "Result should be `world` as this"
+                         " is an intercepting handler")
+
+    def test_emit_event_observe_precedes_intercept(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback_obs(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': EVENT_NAME})
+            callback_tracker[0] += "obs_"
+            return "hello"
+
+        def my_callback_intcpt(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': EVENT_NAME,
+                                  'next_handler': None})
+            callback_tracker[0] += "intcpt_"
+            return "world"
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.observe(EVENT_NAME, 1000, my_callback_obs)
+        dispatcher.intercept(EVENT_NAME, 1001, my_callback_intcpt)
+        result = dispatcher.emit(self, EVENT_NAME)
+        self.assertEqual(
+            callback_tracker[0], "obs_intcpt_", "callback was not invoked in "
+            "expected order. Should have been obs_intcpt_ but is {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "world", "Result should be `world` as this"
+                         " is the return value of the intercepting handler")
+
+    def test_emit_event_observe_follows_intercept(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback_intcpt(**kwargs):
+            self.assertEqual(kwargs.get('sender'), self)
+            self.assertEqual(kwargs.get('next_handler').priority, 1001)
+            self.assertEqual(kwargs.get('next_handler').callback.__name__,
+                             "my_callback_obs")
+            callback_tracker[0] += "intcpt_"
+            # invoke next handler
+            next_handler = kwargs.get('next_handler')
+            assert next_handler.priority == 1001
+            assert next_handler.event_pattern == EVENT_NAME
+            assert next_handler.callback == my_callback_obs
+            retval = next_handler.invoke(**kwargs)
+            self.assertIsNone(retval, "Return values of observable handlers"
+                              " should not be propagated.")
+            return "world"
+
+        def my_callback_obs(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': EVENT_NAME})
+            callback_tracker[0] += "obs_"
+            return "hello"
+
+        dispatcher = SimpleEventDispatcher()
+        # register priorities out of order to test that too
+        dispatcher.observe(EVENT_NAME, 1001, my_callback_obs)
+        dispatcher.intercept(EVENT_NAME, 1000, my_callback_intcpt)
+        result = dispatcher.emit(self, EVENT_NAME)
+        self.assertEqual(
+            callback_tracker[0], "intcpt_obs_", "callback was not invoked in "
+            "expected order. Should have been intcpt_obs_ but is {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "world", "Result should be `world` as this"
+                         " is the return value of the intercepting handler")
+
+    def test_emit_event_intercept_follows_intercept(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback_intcpt1(**kwargs):
+            self.assertEqual(kwargs.get('sender'), self)
+            self.assertEqual(kwargs.get('next_handler').priority, 2020)
+            self.assertEqual(kwargs.get('next_handler').callback.__name__,
+                             "my_callback_intcpt2")
+            callback_tracker[0] += "intcpt1_"
+            # invoke next handler but ignore return value
+            return "hello" + kwargs.get('next_handler').invoke(**kwargs)
+
+        def my_callback_intcpt2(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': EVENT_NAME,
+                                  'next_handler': None})
+            callback_tracker[0] += "intcpt2_"
+            return "world"
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.intercept(EVENT_NAME, 2000, my_callback_intcpt1)
+        dispatcher.intercept(EVENT_NAME, 2020, my_callback_intcpt2)
+        result = dispatcher.emit(self, EVENT_NAME)
+        self.assertEqual(
+            callback_tracker[0], "intcpt1_intcpt2_", "callback was not invoked"
+            " in expected order. Should have been intcpt1_intcpt2_ but is"
+            " {0}".format(callback_tracker[0]))
+        self.assertEqual(result, "helloworld", "Result should be `helloworld` "
+                         "as this is the expected return value from the chain")
+
+    def test_subscribe_event_duplicate_priority(self):
+
+        def my_callback(**kwargs):
+            pass
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.intercept("event.hello.world", 1000, my_callback)
+        dispatcher.intercept("event.hello.world", 1000, my_callback)
+        with self.assertRaises(HandlerException):
+            dispatcher.emit(self, "event.hello.world")
+
+    def test_subscribe_event_duplicate_wildcard_priority(self):
+
+        def my_callback(**kwargs):
+            pass
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.intercept("event.hello.world", 1000, my_callback)
+        dispatcher.intercept("event.hello.*", 1000, my_callback)
+        with self.assertRaises(HandlerException):
+            dispatcher.emit(self, "event.hello.world")
+
+    def test_subscribe_event_duplicate_wildcard_priority_allowed(self):
+        # duplicate priorities for different wildcard namespaces allowed
+        def my_callback(**kwargs):
+            pass
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.intercept("event.hello.world", 1000, my_callback)
+        dispatcher.intercept("someevent.hello.*", 1000, my_callback)
+        # emit should work fine in this case with no exceptions
+        dispatcher.emit(self, "event.hello.world")
+
+    def test_subscribe_multiple_events(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback1(**kwargs):
+            self.assertDictEqual(kwargs, {'sender': self,
+                                          'event': EVENT_NAME})
+            callback_tracker[0] += "event1_"
+            return "hello"
+
+        def my_callback2(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': "event.hello.anotherworld"})
+            callback_tracker[0] += "event2_"
+            return "another"
+
+        def my_callback3(**kwargs):
+            self.assertDictEqual(kwargs,
+                                 {'sender': self,
+                                  'event': "event.hello.anotherworld",
+                                  'next_handler': None})
+            callback_tracker[0] += "event3_"
+            return "world"
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.observe(EVENT_NAME, 2000, my_callback1)
+        # register to a different event with the same priority
+        dispatcher.observe("event.hello.anotherworld", 2000, my_callback2)
+        dispatcher.intercept("event.hello.anotherworld", 2020, my_callback3)
+        result = dispatcher.emit(self, EVENT_NAME)
+        self.assertEqual(
+            callback_tracker[0], "event1_", "only `event.hello.world` handlers"
+            " should have been  triggered but received {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, None, "Result should be `helloworld` "
+                         "as this is the expected return value from the chain")
+
+        result = dispatcher.emit(self, "event.hello.anotherworld")
+        self.assertEqual(
+            callback_tracker[0], "event1_event2_event3_", "only handlers for"
+            "  event `event.hello.anotherworld` should have been  triggered"
+            " but received {0}".format(callback_tracker[0]))
+        self.assertEqual(result, "world", "Result should be `world` "
+                         "as this is the expected return value from the chain")
+
+    def test_subscribe_wildcard(self):
+        callback_tracker = ['']
+
+        def my_callback1(**kwargs):
+            callback_tracker[0] += "event1_"
+            return "hello" + kwargs.get('next_handler').invoke(**kwargs)
+
+        def my_callback2(**kwargs):
+            callback_tracker[0] += "event2_"
+            return "some" + kwargs.get('next_handler').invoke(**kwargs)
+
+        def my_callback3(**kwargs):
+            callback_tracker[0] += "event3_"
+            return "other" + kwargs.get('next_handler').invoke(**kwargs)
+
+        def my_callback4(**kwargs):
+            callback_tracker[0] += "event4_"
+            return "world"
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.intercept("event.*", 2000, my_callback1)
+        # register to a different event with the same priority
+        dispatcher.intercept("event.hello.*", 2010, my_callback2)
+        dispatcher.intercept("event.hello.there", 2030, my_callback4)
+        dispatcher.intercept("event.*.there", 2020, my_callback3)
+        dispatcher.intercept("event.*.world", 2020, my_callback4)
+        dispatcher.intercept("someevent.hello.there", 2030, my_callback3)
+        # emit a series of events
+        result = dispatcher.emit(self, "event.hello.there")
+
+        self.assertEqual(
+            callback_tracker[0], "event1_event2_event3_event4_",
+            "Event handlers executed in unexpected order {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "hellosomeotherworld")
+
+        result = dispatcher.emit(self, "event.test.hello.world")
+        self.assertEqual(
+            callback_tracker[0], "event1_event2_event3_event4_event1_event4_",
+            "Event handlers executed in unexpected order {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "helloworld")
+
+    # make sure cache gets invalidated when subscribing after emit
+    def test_subscribe_after_emit(self):
+        callback_tracker = ['']
+
+        def my_callback1(**kwargs):
+            callback_tracker[0] += "event1_"
+            if kwargs.get('next_handler'):
+                return "hello" + kwargs.get('next_handler').invoke(**kwargs)
+            else:
+                return "hello"
+
+        def my_callback2(**kwargs):
+            callback_tracker[0] += "event2_"
+            return "some"
+
+        dispatcher = SimpleEventDispatcher()
+        dispatcher.intercept("event.hello.world", 1000, my_callback1)
+        dispatcher.emit(self, "event.hello.world")
+        dispatcher.intercept("event.hello.*", 1001, my_callback2)
+        result = dispatcher.emit(self, "event.hello.world")
+
+        self.assertEqual(
+            callback_tracker[0], "event1_event1_event2_",
+            "Event handlers executed in unexpected order {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "hellosome")
+
+    def test_unubscribe(self):
+        EVENT_NAME = "event.hello.world"
+        callback_tracker = ['']
+
+        def my_callback1(**kwargs):
+            callback_tracker[0] += "event1_"
+            if kwargs.get('next_handler'):
+                return "hello" + kwargs.get('next_handler').invoke(**kwargs)
+            else:
+                return "hello"
+
+        def my_callback2(**kwargs):
+            callback_tracker[0] += "event2_"
+            return "some"
+
+        dispatcher = SimpleEventDispatcher()
+        hndlr1 = dispatcher.intercept(EVENT_NAME, 1000, my_callback1)
+        dispatcher.emit(self, EVENT_NAME)
+        hndlr2 = dispatcher.intercept("event.hello.*", 1001, my_callback2)
+        # Both handlers should be registered
+        self.assertListEqual(
+            [my_callback1, my_callback2],
+            [handler.callback for handler in
+             dispatcher.get_handlers_for_event(EVENT_NAME)])
+        hndlr1.unsubscribe()
+
+        # Only my_callback2 should be registered after unsubscribe
+        self.assertListEqual(
+            [my_callback2],
+            [handler.callback for handler in
+             dispatcher.get_handlers_for_event(EVENT_NAME)])
+
+        result = dispatcher.emit(self, EVENT_NAME)
+
+        self.assertEqual(
+            callback_tracker[0], "event1_event2_",
+            "Event handlers executed in unexpected order {0}".format(
+                callback_tracker[0]))
+        self.assertEqual(result, "some")
+
+        hndlr2.unsubscribe()
+        result = dispatcher.emit(self, "event.hello.world")
+        self.assertEqual(result, None)

+ 167 - 0
test/test_middleware_system.py

@@ -0,0 +1,167 @@
+import unittest
+
+from cloudbridge.cloud.base.events import SimpleEventDispatcher
+from cloudbridge.cloud.base.middleware import BaseMiddleware
+from cloudbridge.cloud.base.middleware import SimpleMiddlewareManager
+from cloudbridge.cloud.base.middleware import intercept
+from cloudbridge.cloud.base.middleware import observe
+from cloudbridge.cloud.interfaces.middleware import Middleware
+
+
+class MiddlewareSystemTestCase(unittest.TestCase):
+
+    def test_basic_middleware(self):
+
+        class DummyMiddleWare(Middleware):
+
+            def __init__(self):
+                self.invocation_order = ""
+
+            def install(self, event_manager):
+                self.event_manager = event_manager
+                self.invocation_order += "install_"
+
+            def uninstall(self):
+                self.invocation_order += "uninstall"
+
+        dispatcher = SimpleEventDispatcher()
+        manager = SimpleMiddlewareManager(dispatcher)
+        middleware = DummyMiddleWare()
+        manager.add(middleware)
+
+        self.assertEqual(middleware.invocation_order, "install_",
+                         "install should be called when adding new middleware")
+
+        manager.remove(middleware)
+        self.assertEqual(middleware.invocation_order, "install_uninstall",
+                         "uninstall should be called when removing middleware")
+
+    def test_base_middleware(self):
+        EVENT_NAME = "some.event.occurred"
+
+        class DummyMiddleWare(BaseMiddleware):
+
+            def __init__(self):
+                self.invocation_order = ""
+
+            @observe(event_pattern="some.event.*", priority=1000)
+            def my_callback_obs(self, **kwargs):
+                self.invocation_order += "observe"
+
+            @intercept(event_pattern="some.event.*", priority=900)
+            def my_callback_intcpt(self, **kwargs):
+                self.invocation_order += "intercept_"
+                return kwargs.get('next_handler').invoke(**kwargs)
+
+        dispatcher = SimpleEventDispatcher()
+        manager = SimpleMiddlewareManager(dispatcher)
+        middleware = DummyMiddleWare()
+        manager.add(middleware)
+        dispatcher.emit(self, EVENT_NAME)
+
+        self.assertEqual(middleware.invocation_order, "intercept_observe")
+        self.assertListEqual(
+            [middleware.my_callback_intcpt, middleware.my_callback_obs],
+            [handler.callback for handler
+             in dispatcher.get_handlers_for_event(EVENT_NAME)])
+
+        manager.remove(middleware)
+
+        self.assertListEqual([], dispatcher.get_handlers_for_event(EVENT_NAME))
+
+    def test_multiple_middleware(self):
+        EVENT_NAME = "some.really.interesting.event.occurred"
+
+        class DummyMiddleWare1(BaseMiddleware):
+
+            @observe(event_pattern="some.really.*", priority=1000)
+            def my_callback_obs1(self, **kwargs):
+                pass
+
+            @intercept(event_pattern="some.*", priority=900)
+            def my_callback_intcpt2(self, **kwargs):
+                return kwargs.get('next_handler').invoke(**kwargs)
+
+        class DummyMiddleWare2(BaseMiddleware):
+
+            @observe(event_pattern="some.really.*", priority=1050)
+            def my_callback_obs3(self, **kwargs):
+                pass
+
+            @intercept(event_pattern="*", priority=950)
+            def my_callback_intcpt4(self, **kwargs):
+                return kwargs.get('next_handler').invoke(**kwargs)
+
+        dispatcher = SimpleEventDispatcher()
+        manager = SimpleMiddlewareManager(dispatcher)
+        middleware1 = DummyMiddleWare1()
+        middleware2 = DummyMiddleWare2()
+        manager.add(middleware1)
+        manager.add(middleware2)
+        dispatcher.emit(self, EVENT_NAME)
+
+        # Callbacks in both middleware classes should be registered
+        self.assertListEqual(
+            [middleware1.my_callback_intcpt2, middleware2.my_callback_intcpt4,
+             middleware1.my_callback_obs1, middleware2.my_callback_obs3],
+            [handler.callback for handler
+             in dispatcher.get_handlers_for_event(EVENT_NAME)])
+
+        manager.remove(middleware1)
+
+        # Only middleware2 callbacks should be registered
+        self.assertListEqual(
+            [middleware2.my_callback_intcpt4, middleware2.my_callback_obs3],
+            [handler.callback for handler in
+             dispatcher.get_handlers_for_event(EVENT_NAME)])
+
+        # add middleware back to check that internal state is properly handled
+        manager.add(middleware1)
+
+        # should one again equal original list
+        self.assertListEqual(
+            [middleware1.my_callback_intcpt2, middleware2.my_callback_intcpt4,
+             middleware1.my_callback_obs1, middleware2.my_callback_obs3],
+            [handler.callback for handler
+             in dispatcher.get_handlers_for_event(EVENT_NAME)])
+
+    def test_automatic_middleware(self):
+        EVENT_NAME = "another.interesting.event.occurred"
+
+        class SomeDummyClass(object):
+
+            @observe(event_pattern="another.really.*", priority=1000)
+            def not_a_match(self, **kwargs):
+                pass
+
+            @intercept(event_pattern="another.*", priority=900)
+            def my_callback_intcpt2(self, **kwargs):
+                pass
+
+            def not_an_event_handler(self, **kwargs):
+                pass
+
+            @observe(event_pattern="another.interesting.*", priority=1000)
+            def my_callback_obs1(self, **kwargs):
+                pass
+
+        dispatcher = SimpleEventDispatcher()
+        manager = SimpleMiddlewareManager(dispatcher)
+        some_obj = SomeDummyClass()
+        middleware = manager.add(some_obj)
+        dispatcher.emit(self, EVENT_NAME)
+
+        # Middleware should be discovered even if class containing interceptors
+        # doesn't inherit from Middleware
+        self.assertListEqual(
+            [some_obj.my_callback_intcpt2, some_obj.my_callback_obs1],
+            [handler.callback for handler
+             in dispatcher.get_handlers_for_event(EVENT_NAME)])
+
+        manager.remove(middleware)
+
+        # Callbacks should be correctly removed
+        self.assertListEqual(
+            [],
+            [handler.callback for handler in
+             dispatcher.get_handlers_for_event(EVENT_NAME)])