2
0

event_system.rst 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. Working with the CloudBridge Event System
  2. =========================================
  3. In order to provide more comprehensive logging and standardize CloudBridge
  4. functions, we have adopted a middleware layer to handle event calls. In short,
  5. each event has a corresponding list of dispatchers called in priority order.
  6. For the time being, only a listening subscription model is implemented, thus
  7. each event has a series of subscribed methods accepting the same parameters,
  8. that get run in priority order along with the main function call.
  9. This Event System allows both developers and users to easily add
  10. intermediary functions by event name, without having to modify the
  11. pre-existing code, thus improving the library's flexibility.
  12. Event Handler
  13. -------------
  14. Each function attached to an event has a corresponding handler. This handler
  15. has a type, a callback function, and a link to the next handler. When
  16. invoked, the handler will call its callback function and, when available,
  17. invoke the next handler in the linked list of handlers.
  18. Handler Types
  19. -------------
  20. Each Event Handler has a type, which determines how it's invoked. There are
  21. currently two supported types: `SUBSCRIPTION`, and `RESULT_SUBSCRIPTION`.
  22. Handlers of `SUBSCRIPTION` type are simple listeners, who intercept the main
  23. function arguments but do not modify them. They are independent of any
  24. previous or future handler, and have no return value. Their associated
  25. callback function expects the exact same parameters as the main function.
  26. Handlers of `RESULT_SUBSCRIPTION` type are similar to `SUBSCRIPTION` handlers,
  27. but have access to the last non-null return value from any previous handler.
  28. They are similarly listeners, intercepting arguments without modifying them
  29. and do not return any value. Their associated callback will however be
  30. called with an additional keyword parameter named `callback_result` holding
  31. the last non-null return value from any previous handler. The callback
  32. function thus needs to accept such a parameter.
  33. Event Dispatcher
  34. ----------------
  35. A single event dispatcher is initialized with the provider, and will hold
  36. the entirety of the handlers for all events. This dispatcher handles new
  37. subscriptions and event calls. When an event is called, the dispatcher will
  38. link each handler to the next one in line, then invoke the first handler,
  39. thus triggering the chain of handlers.
  40. Priorities
  41. ----------
  42. As previously mentioned, dispatchers will be invoked in order of priority.
  43. These priorities are assigned at subscription time, and must be unique.
  44. Below are the default priorities used across events:
  45. +------------------------+----------+
  46. | Handler | Priority |
  47. +------------------------+----------+
  48. | Pre-Logger | 2000 |
  49. +------------------------+----------+
  50. | Main Function Call | 2500 |
  51. +------------------------+----------+
  52. | Post-Logger | 3000 |
  53. +------------------------+----------+
  54. The Pre- and Post- loggers represent universal loggers respectively keeping
  55. track of the event called and its parameters before the call, and the returned
  56. value after the call. The main function call represents the core function,
  57. which is not subscribed permanently, but rather called directly with the event.
  58. User Example
  59. ------------
  60. From a user's perspective, the Event System is invisible unless the user
  61. wishes to extend the chain of handlers with their own code:
  62. .. code-block:: python
  63. from cloudbridge.factory import CloudProviderFactory, ProviderList
  64. provider = CloudProviderFactory().create_provider(ProviderList.FIRST, {})
  65. id = 'thisIsAnID'
  66. obj = provider.storage.buckets.get(id)
  67. However, if they wish to add their own logging interface, for example, they
  68. can do so without modifying CloudBridge code:
  69. .. code-block:: python
  70. from cloudbridge.factory import CloudProviderFactory, ProviderList
  71. provider = CloudProviderFactory().create_provider(ProviderList.AZURE, {})
  72. ## I don't want to setup a logger, just want to print some messages for
  73. ## debugging
  74. def print_id(obj_id):
  75. print("I am getting this id: " + obj_id)
  76. provider.storage.buckets.subscribe("get", priority=1500, callback=print_id)
  77. id1 = 'thisIsAnID'
  78. id2 = 'thisIsAnID2'
  79. ## The subscribed print function will get called every time the get
  80. ## method is invoked
  81. obj1 = provider.storage.buckets.get(id1)
  82. ## I am getting this id: thisIsAnID
  83. obj2 = provider.storage.buckets.get(id2)
  84. ## I am getting this id: thisIsAnID2
  85. Developer Example
  86. -----------------
  87. Below is an example of the way in which the Event System works for a simple
  88. getter, from the CloudBridge developer perspective.
  89. .. code-block:: python
  90. ## Provider Specific code
  91. class MyFirstProviderService(BaseService):
  92. def __init__(self, provider):
  93. super(MyFirstProviderService, self).__init__(provider)
  94. def get(self, obj_id):
  95. # do the getting
  96. resource = ...
  97. return MyFirstProviderResource(resource)
  98. class MySecondProviderService(BaseService):
  99. def __init__(self, provider):
  100. super(MySecondProviderService, self).__init__(provider)
  101. def get(self, obj_id):
  102. # do the getting
  103. resource = ...
  104. return MySecondProviderResource(resource)
  105. ## Base code
  106. class BaseService(ProviderService):
  107. def __init__(self, provider):
  108. super(Service, self).__init__(provider)
  109. # Example: provider.storage.buckets for buckets
  110. self._service_event_name = "provider.service.servicename"
  111. def _init_get(self):
  112. def _get_pre_log(obj_id):
  113. log.debug("Getting {} object with the id: {}".format(
  114. self.provider.name, bucket_id))
  115. def _get_post_log(callback_result, obj_id):
  116. log.debug("Returned object: {}".format(callback_result))
  117. self.subscribe("get", 2000, _get_pre_log)
  118. self.subscribe("get", 3000, _get_post_log,
  119. result_callback=True)
  120. self.mark_initialized("get")
  121. # Public get function
  122. def get(self, obj_id):
  123. """
  124. Returns an object given its ID. Returns ``None`` if the object
  125. does not exist.
  126. """
  127. if not self.check_initialized("get"):
  128. self._init_get()
  129. return self.call("get", priority=2500,
  130. main_call=self._get,
  131. obj_id=obj_id)
  132. Thus, adding a new provider only requires adding the Service class with a
  133. protected class accepting the same parameters, and the logging and public
  134. method signature will remain the same, as the code will not be re-written
  135. for each provider.
  136. Additionally, if a developer needs to add additional logging for a
  137. particular service, beyond the default logging for all services, they can do
  138. so in the event initialisation function, and it will be applied to all
  139. providers. For example:
  140. .. code-block:: python
  141. ## Base code
  142. class BaseService(ProviderService):
  143. def __init__(self, provider):
  144. super(Service, self).__init__(provider)
  145. self._service_event_name = "provider.service"
  146. def _init_get(self):
  147. def _get_pre_log(obj_id):
  148. log.debug("Getting {} object with the id: {}".format(
  149. self.provider.name, bucket_id))
  150. def _get_post_log(callback_result, obj_id):
  151. log.debug("Returned object: {}".format(callback_result))
  152. def _special_none_log(callback_result, obj_id):
  153. if not callback_result:
  154. log.debug("There is no object with id '{}'".format(obj_id))
  155. self.subscribe("get", 2000, _get_pre_log)
  156. self.subscribe("get", 3000, _get_post_log,
  157. result_callback=True)
  158. self.subscribe("get", 2750, _special_none_log,
  159. result_callback=True)
  160. self.mark_initialized("get")
  161. # Public get function
  162. def get(self, obj_id):
  163. """
  164. Returns an object given its ID. Returns ``None`` if the object
  165. does not exist.
  166. """
  167. if not self.check_initialized("get"):
  168. self._init_get()
  169. return self.call("get", priority=2500,
  170. main_call=self._get,
  171. obj_id=obj_id)