test_middleware_system.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import unittest
  2. from cloudbridge.cloud.base.events import SimpleEventDispatcher
  3. from cloudbridge.cloud.base.middleware import BaseMiddleware
  4. from cloudbridge.cloud.base.middleware import EventDebugLoggingMiddleware
  5. from cloudbridge.cloud.base.middleware import ExceptionWrappingMiddleware
  6. from cloudbridge.cloud.base.middleware import SimpleMiddlewareManager
  7. from cloudbridge.cloud.base.middleware import dispatch
  8. from cloudbridge.cloud.base.middleware import implement
  9. from cloudbridge.cloud.base.middleware import intercept
  10. from cloudbridge.cloud.base.middleware import observe
  11. from cloudbridge.cloud.interfaces.exceptions import CloudBridgeBaseException
  12. from cloudbridge.cloud.interfaces.exceptions import HandlerException
  13. from cloudbridge.cloud.interfaces.exceptions import \
  14. InvalidConfigurationException
  15. from cloudbridge.cloud.interfaces.middleware import Middleware
  16. from .helpers import skipIfPython
  17. class MiddlewareSystemTestCase(unittest.TestCase):
  18. def test_basic_middleware(self):
  19. class DummyMiddleWare(Middleware):
  20. def __init__(self):
  21. self.invocation_order = ""
  22. def install(self, event_manager):
  23. self.event_manager = event_manager
  24. self.invocation_order += "install_"
  25. def uninstall(self):
  26. self.invocation_order += "uninstall"
  27. dispatcher = SimpleEventDispatcher()
  28. manager = SimpleMiddlewareManager(dispatcher)
  29. middleware = DummyMiddleWare()
  30. manager.add(middleware)
  31. self.assertEqual(middleware.invocation_order, "install_",
  32. "install should be called when adding new middleware")
  33. manager.remove(middleware)
  34. self.assertEqual(middleware.invocation_order, "install_uninstall",
  35. "uninstall should be called when removing middleware")
  36. def test_base_middleware(self):
  37. EVENT_NAME = "some.event.occurred"
  38. class DummyMiddleWare(BaseMiddleware):
  39. def __init__(self):
  40. self.invocation_order = ""
  41. @intercept(event_pattern="some.event.*", priority=900)
  42. def my_callback_intcpt(self, event_args, *args, **kwargs):
  43. self.invocation_order += "intcpt_"
  44. assert 'first_pos_arg' in args
  45. assert kwargs.get('a_keyword_arg') == "something"
  46. next_handler = event_args.get('next_handler')
  47. return next_handler.invoke(event_args, *args, **kwargs)
  48. @implement(event_pattern="some.event.*", priority=950)
  49. def my_callback_impl(self, *args, **kwargs):
  50. self.invocation_order += "impl_"
  51. assert 'first_pos_arg' in args
  52. assert kwargs.get('a_keyword_arg') == "something"
  53. return "hello"
  54. @observe(event_pattern="some.event.*", priority=1000)
  55. def my_callback_obs(self, event_args, *args, **kwargs):
  56. self.invocation_order += "obs"
  57. assert 'first_pos_arg' in args
  58. assert event_args['result'] == "hello"
  59. assert kwargs.get('a_keyword_arg') == "something"
  60. dispatcher = SimpleEventDispatcher()
  61. manager = SimpleMiddlewareManager(dispatcher)
  62. middleware = DummyMiddleWare()
  63. manager.add(middleware)
  64. dispatcher.dispatch(self, EVENT_NAME, 'first_pos_arg',
  65. a_keyword_arg='something')
  66. self.assertEqual(middleware.invocation_order, "intcpt_impl_obs")
  67. self.assertListEqual(
  68. [middleware.my_callback_intcpt, middleware.my_callback_impl,
  69. middleware.my_callback_obs],
  70. [handler.callback for handler
  71. in dispatcher.get_handlers_for_event(EVENT_NAME)])
  72. manager.remove(middleware)
  73. self.assertListEqual([], dispatcher.get_handlers_for_event(EVENT_NAME))
  74. def test_multiple_middleware(self):
  75. EVENT_NAME = "some.really.interesting.event.occurred"
  76. class DummyMiddleWare1(BaseMiddleware):
  77. @observe(event_pattern="some.really.*", priority=1000)
  78. def my_obs1_3(self, *args, **kwargs):
  79. pass
  80. @implement(event_pattern="some.*", priority=970)
  81. def my_impl1_2(self, *args, **kwargs):
  82. return "hello"
  83. @intercept(event_pattern="some.*", priority=900)
  84. def my_intcpt1_1(self, event_args, *args, **kwargs):
  85. next_handler = event_args.get('next_handler')
  86. return next_handler.invoke(event_args, *args, **kwargs)
  87. class DummyMiddleWare2(BaseMiddleware):
  88. @observe(event_pattern="some.really.*", priority=1050)
  89. def my_obs2_3(self, *args, **kwargs):
  90. pass
  91. @intercept(event_pattern="*", priority=950)
  92. def my_intcpt2_2(self, event_args, *args, **kwargs):
  93. next_handler = event_args.get('next_handler')
  94. return next_handler.invoke(event_args, *args, **kwargs)
  95. @implement(event_pattern="some.really.*", priority=920)
  96. def my_impl2_1(self, *args, **kwargs):
  97. pass
  98. dispatcher = SimpleEventDispatcher()
  99. manager = SimpleMiddlewareManager(dispatcher)
  100. middleware1 = DummyMiddleWare1()
  101. middleware2 = DummyMiddleWare2()
  102. manager.add(middleware1)
  103. manager.add(middleware2)
  104. dispatcher.dispatch(self, EVENT_NAME)
  105. # Callbacks in both middleware classes should be registered
  106. self.assertListEqual(
  107. [middleware1.my_intcpt1_1, middleware2.my_impl2_1,
  108. middleware2.my_intcpt2_2, middleware1.my_impl1_2,
  109. middleware1.my_obs1_3, middleware2.my_obs2_3],
  110. [handler.callback for handler
  111. in dispatcher.get_handlers_for_event(EVENT_NAME)])
  112. manager.remove(middleware1)
  113. # Only middleware2 callbacks should be registered
  114. self.assertListEqual(
  115. [middleware2.my_impl2_1, middleware2.my_intcpt2_2,
  116. middleware2.my_obs2_3],
  117. [handler.callback for handler in
  118. dispatcher.get_handlers_for_event(EVENT_NAME)])
  119. # add middleware back to check that internal state is properly handled
  120. manager.add(middleware1)
  121. # should one again equal original list
  122. self.assertListEqual(
  123. [middleware1.my_intcpt1_1, middleware2.my_impl2_1,
  124. middleware2.my_intcpt2_2, middleware1.my_impl1_2,
  125. middleware1.my_obs1_3, middleware2.my_obs2_3],
  126. [handler.callback for handler
  127. in dispatcher.get_handlers_for_event(EVENT_NAME)])
  128. def test_automatic_middleware(self):
  129. EVENT_NAME = "another.interesting.event.occurred"
  130. class SomeDummyClass(object):
  131. @observe(event_pattern="another.really.*", priority=1000)
  132. def not_a_match(self, *args, **kwargs):
  133. pass
  134. @intercept(event_pattern="another.*", priority=900)
  135. def my_callback_intcpt2(self, *args, **kwargs):
  136. pass
  137. def not_an_event_handler(self, *args, **kwargs):
  138. pass
  139. @observe(event_pattern="another.interesting.*", priority=1000)
  140. def my_callback_obs1(self, *args, **kwargs):
  141. pass
  142. @implement(event_pattern="another.interesting.*", priority=1050)
  143. def my_callback_impl(self, *args, **kwargs):
  144. pass
  145. dispatcher = SimpleEventDispatcher()
  146. manager = SimpleMiddlewareManager(dispatcher)
  147. some_obj = SomeDummyClass()
  148. middleware = manager.add(some_obj)
  149. dispatcher.dispatch(self, EVENT_NAME)
  150. # Middleware should be discovered even if class containing interceptors
  151. # doesn't inherit from Middleware
  152. self.assertListEqual(
  153. [some_obj.my_callback_intcpt2, some_obj.my_callback_obs1,
  154. some_obj.my_callback_impl],
  155. [handler.callback for handler
  156. in dispatcher.get_handlers_for_event(EVENT_NAME)])
  157. manager.remove(middleware)
  158. # Callbacks should be correctly removed
  159. self.assertListEqual(
  160. [],
  161. [handler.callback for handler in
  162. dispatcher.get_handlers_for_event(EVENT_NAME)])
  163. def test_event_decorator(self):
  164. EVENT_NAME = "some.event.occurred"
  165. class SomeDummyClass(object):
  166. def __init__(self):
  167. self.invocation_order = ""
  168. self.events = SimpleEventDispatcher()
  169. @intercept(event_pattern="some.event.*", priority=900)
  170. def my_callback_intcpt(self, event_args, *args, **kwargs):
  171. self.invocation_order += "intcpt_"
  172. assert 'first_pos_arg' in args
  173. assert kwargs.get('a_keyword_arg') == "something"
  174. next_handler = event_args.get('next_handler')
  175. return next_handler.invoke(event_args, *args, **kwargs)
  176. @observe(event_pattern="some.event.*", priority=3000)
  177. def my_callback_obs(self, event_args, *args, **kwargs):
  178. self.invocation_order += "obs"
  179. assert 'first_pos_arg' in args
  180. assert event_args['result'] == "hello"
  181. assert kwargs.get('a_keyword_arg') == "something"
  182. @dispatch(event=EVENT_NAME, priority=2500)
  183. def my_callback_impl(self, *args, **kwargs):
  184. self.invocation_order += "impl_"
  185. assert 'first_pos_arg' in args
  186. assert kwargs.get('a_keyword_arg') == "something"
  187. return "hello"
  188. obj = SomeDummyClass()
  189. manager = SimpleMiddlewareManager(obj.events)
  190. middleware = manager.add(obj)
  191. # calling my_implementation should trigger all events
  192. result = obj.my_callback_impl(
  193. 'first_pos_arg', a_keyword_arg='something')
  194. self.assertEqual(result, "hello")
  195. self.assertEqual(obj.invocation_order, "intcpt_impl_obs")
  196. callbacks = [handler.callback for handler
  197. in middleware.events.get_handlers_for_event(EVENT_NAME)]
  198. self.assertNotIn(
  199. obj.my_callback_impl, callbacks,
  200. "The event impl callback should not be directly contained"
  201. " in callbacks to avoid a circular dispatch")
  202. self.assertEqual(
  203. len(set(callbacks).difference(
  204. set([obj.my_callback_intcpt,
  205. obj.my_callback_obs]))),
  206. 1,
  207. "The event impl callback should be included in the list of"
  208. " callbacks indirectly")
  209. manager.remove(middleware)
  210. # calling my_implementation again should trigger a None response
  211. result = obj.my_callback_impl(
  212. 'first_pos_arg', a_keyword_arg='something')
  213. self.assertEqual(result, None)
  214. def test_event_decorator_no_event_property(self):
  215. EVENT_NAME = "some.event.occurred"
  216. class SomeDummyClass(object):
  217. @dispatch(event=EVENT_NAME, priority=2500)
  218. def my_callback_impl(self, *args, **kwargs):
  219. assert 'first_pos_arg' in args
  220. assert kwargs.get('a_keyword_arg') == "something"
  221. return "hello"
  222. obj = SomeDummyClass()
  223. events = SimpleEventDispatcher()
  224. manager = SimpleMiddlewareManager(events)
  225. manager.add(obj)
  226. # calling my_implementation should raise an exception
  227. with self.assertRaises(HandlerException):
  228. obj.my_callback_impl('first_pos_arg', a_keyword_arg='something')
  229. obj.events = events
  230. result = obj.my_callback_impl('first_pos_arg',
  231. a_keyword_arg='something')
  232. self.assertEqual(result, "hello")
  233. class ExceptionWrappingMiddlewareTestCase(unittest.TestCase):
  234. def test_unknown_exception_is_wrapped(self):
  235. EVENT_NAME = "an.exceptional.event"
  236. class SomeDummyClass(object):
  237. @implement(event_pattern=EVENT_NAME, priority=2500)
  238. def raise_a_non_cloudbridge_exception(self, *args, **kwargs):
  239. raise Exception("Some unhandled exception")
  240. dispatcher = SimpleEventDispatcher()
  241. manager = SimpleMiddlewareManager(dispatcher)
  242. middleware = ExceptionWrappingMiddleware()
  243. manager.add(middleware)
  244. # no exception should be raised when there's no next handler
  245. dispatcher.dispatch(self, EVENT_NAME)
  246. some_obj = SomeDummyClass()
  247. manager.add(some_obj)
  248. with self.assertRaises(CloudBridgeBaseException):
  249. dispatcher.dispatch(self, EVENT_NAME)
  250. def test_cloudbridge_exception_is_passed_through(self):
  251. EVENT_NAME = "an.exceptional.event"
  252. class SomeDummyClass(object):
  253. @implement(event_pattern=EVENT_NAME, priority=2500)
  254. def raise_a_cloudbridge_exception(self, *args, **kwargs):
  255. raise InvalidConfigurationException()
  256. dispatcher = SimpleEventDispatcher()
  257. manager = SimpleMiddlewareManager(dispatcher)
  258. some_obj = SomeDummyClass()
  259. manager.add(some_obj)
  260. middleware = ExceptionWrappingMiddleware()
  261. manager.add(middleware)
  262. with self.assertRaises(InvalidConfigurationException):
  263. dispatcher.dispatch(self, EVENT_NAME)
  264. class EventDebugLoggingMiddlewareTestCase(unittest.TestCase):
  265. # Only python 3 has assertLogs support
  266. @skipIfPython("<", 3, 0)
  267. def test_messages_logged(self):
  268. EVENT_NAME = "an.exceptional.event"
  269. class SomeDummyClass(object):
  270. @implement(event_pattern=EVENT_NAME, priority=2500)
  271. def return_some_value(self, *args, **kwargs):
  272. return "hello world"
  273. dispatcher = SimpleEventDispatcher()
  274. manager = SimpleMiddlewareManager(dispatcher)
  275. middleware = EventDebugLoggingMiddleware()
  276. manager.add(middleware)
  277. some_obj = SomeDummyClass()
  278. manager.add(some_obj)
  279. with self.assertLogs('cloudbridge.cloud.base.middleware',
  280. level='DEBUG') as cm:
  281. dispatcher.dispatch(self, EVENT_NAME,
  282. "named_param", keyword_param="hello")
  283. self.assertTrue(
  284. "named_param" in cm.output[0]
  285. and "keyword_param" in cm.output[0] and "hello" in cm.output[0],
  286. "Log output {0} not as expected".format(cm.output[0]))
  287. self.assertTrue(
  288. "hello world" in cm.output[1],
  289. "Log output {0} does not contain result".format(cm.output[1]))