| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- Working with the CloudBridge Event System
- =========================================
- In order to provide more comprehensive logging and standardize CloudBridge
- functions, we have adopted a middleware layer to handle event calls. In short,
- each event has a corresponding list of dispatchers called in priority order.
- For the time being, only a listening subscription model is implemented, thus
- each event has a series of subscribed methods accepting the same parameters,
- that get run in priority order along with the main function call.
- This Event System allows both developers and users to easily add
- intermediary functions by event name, without having to modify the
- pre-existing code, thus improving the library's flexibility.
- Event Handler
- -------------
- Each function attached to an event has a corresponding handler. This handler
- has a type, a callback function, and a link to the next handler. When
- invoked, the handler will call its callback function and, when available,
- invoke the next handler in the linked list of handlers.
- Handler Types
- -------------
- Each Event Handler has a type, which determines how it's invoked. There are
- currently two supported types: `SUBSCRIPTION`, and `RESULT_SUBSCRIPTION`.
- Handlers of `SUBSCRIPTION` type are simple listeners, who intercept the main
- function arguments but do not modify them. They are independent of any
- previous or future handler, and have no return value. Their associated
- callback function expects the exact same parameters as the main function.
- Handlers of `RESULT_SUBSCRIPTION` type are similar to `SUBSCRIPTION` handlers,
- but have access to the last non-null return value from any previous handler.
- They are similarly listeners, intercepting arguments without modifying them
- and do not return any value. Their associated callback will however be
- called with an additional keyword parameter named `callback_result` holding
- the last non-null return value from any previous handler. The callback
- function thus needs to accept such a parameter.
- Event Dispatcher
- ----------------
- A single event dispatcher is initialized with the provider, and will hold
- the entirety of the handlers for all events. This dispatcher handles new
- subscriptions and event calls. When an event is called, the dispatcher will
- link each handler to the next one in line, then invoke the first handler,
- thus triggering the chain of handlers.
- Priorities
- ----------
- As previously mentioned, dispatchers will be invoked in order of priority.
- These priorities are assigned at subscription time, and must be unique.
- Below are the default priorities used across events:
- +------------------------+----------+
- | Handler | Priority |
- +------------------------+----------+
- | Pre-Logger | 2000 |
- +------------------------+----------+
- | Main Function Call | 2500 |
- +------------------------+----------+
- | Post-Logger | 3000 |
- +------------------------+----------+
- The Pre- and Post- loggers represent universal loggers respectively keeping
- track of the event called and its parameters before the call, and the returned
- value after the call. The main function call represents the core function,
- which is not subscribed permanently, but rather called directly with the event.
- User Example
- ------------
- From a user's perspective, the Event System is invisible unless the user
- wishes to extend the chain of handlers with their own code:
- .. code-block:: python
- from cloudbridge.cloud.factory import CloudProviderFactory, ProviderList
- provider = CloudProviderFactory().create_provider(ProviderList.FIRST, {})
- id = 'thisIsAnID'
- obj = provider.storage.buckets.get(id)
- However, if they wish to add their own logging interface, for example, they
- can do so without modifying CloudBridge code:
- .. code-block:: python
- from cloudbridge.cloud.factory import CloudProviderFactory, ProviderList
- provider = CloudProviderFactory().create_provider(ProviderList.AZURE, {})
- ## I don't want to setup a logger, just want to print some messages for
- ## debugging
- def print_id(obj_id):
- print("I am getting this id: " + obj_id)
- provider.storage.buckets.subscribe("get", priority=1500, callback=print_id)
- id1 = 'thisIsAnID'
- id2 = 'thisIsAnID2'
- ## The subscribed print function will get called every time the get
- ## method is invoked
- obj1 = provider.storage.buckets.get(id1)
- ## I am getting this id: thisIsAnID
- obj2 = provider.storage.buckets.get(id2)
- ## I am getting this id: thisIsAnID2
- Developer Example
- -----------------
- Below is an example of the way in which the Event System works for a simple
- getter, from the CloudBridge developer perspective.
- .. code-block:: python
- ## Provider Specific code
- class MyFirstProviderService(BaseService):
- def __init__(self, provider):
- super(MyFirstProviderService, self).__init__(provider)
- def get(self, obj_id):
- # do the getting
- resource = ...
- return MyFirstProviderResource(resource)
- class MySecondProviderService(BaseService):
- def __init__(self, provider):
- super(MySecondProviderService, self).__init__(provider)
- def get(self, obj_id):
- # do the getting
- resource = ...
- return MySecondProviderResource(resource)
- ## Base code
- class BaseService(ProviderService):
- def __init__(self, provider):
- super(Service, self).__init__(provider)
- # Example: provider.storage.buckets for buckets
- self._service_event_name = "provider.service.servicename"
- def _init_get(self):
- def _get_pre_log(obj_id):
- log.debug("Getting {} object with the id: {}".format(
- self.provider.name, bucket_id))
- def _get_post_log(callback_result, obj_id):
- log.debug("Returned object: {}".format(callback_result))
- self.subscribe("get", 2000, _get_pre_log)
- self.subscribe("get", 3000, _get_post_log,
- result_callback=True)
- self.mark_initialized("get")
- # Public get function
- def get(self, obj_id):
- """
- Returns an object given its ID. Returns ``None`` if the object
- does not exist.
- """
- if not self.check_initialized("get"):
- self._init_get()
- return self.call("get", priority=2500,
- main_call=self._get,
- obj_id=obj_id)
- Thus, adding a new provider only requires adding the Service class with a
- protected class accepting the same parameters, and the logging and public
- method signature will remain the same, as the code will not be re-written
- for each provider.
- Additionally, if a developer needs to add additional logging for a
- particular service, beyond the default logging for all services, they can do
- so in the event initialisation function, and it will be applied to all
- providers. For example:
- .. code-block:: python
- ## Base code
- class BaseService(ProviderService):
- def __init__(self, provider):
- super(Service, self).__init__(provider)
- self._service_event_name = "provider.service"
- def _init_get(self):
- def _get_pre_log(obj_id):
- log.debug("Getting {} object with the id: {}".format(
- self.provider.name, bucket_id))
- def _get_post_log(callback_result, obj_id):
- log.debug("Returned object: {}".format(callback_result))
- def _special_none_log(callback_result, obj_id):
- if not callback_result:
- log.debug("There is no object with id '{}'".format(obj_id))
- self.subscribe("get", 2000, _get_pre_log)
- self.subscribe("get", 3000, _get_post_log,
- result_callback=True)
- self.subscribe("get", 2750, _special_none_log,
- result_callback=True)
- self.mark_initialized("get")
- # Public get function
- def get(self, obj_id):
- """
- Returns an object given its ID. Returns ``None`` if the object
- does not exist.
- """
- if not self.check_initialized("get"):
- self._init_get()
- return self.call("get", priority=2500,
- main_call=self._get,
- obj_id=obj_id)
|