PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/unit/utils/event_test.py

https://gitlab.com/ricardo.hernandez/salt
Python | 371 lines | 323 code | 28 blank | 20 comment | 15 complexity | 6012c9a641b1d35b4f43bf093fb03f16 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
  4. tests.unit.utils.event_test
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. '''
  7. # Import python libs
  8. from __future__ import absolute_import
  9. import os
  10. import hashlib
  11. import time
  12. from tornado.testing import AsyncTestCase
  13. import zmq
  14. import zmq.eventloop.ioloop
  15. # support pyzmq 13.0.x, TODO: remove once we force people to 14.0.x
  16. if not hasattr(zmq.eventloop.ioloop, 'ZMQIOLoop'):
  17. zmq.eventloop.ioloop.ZMQIOLoop = zmq.eventloop.ioloop.IOLoop
  18. from contextlib import contextmanager
  19. from multiprocessing import Process
  20. # Import Salt Testing libs
  21. from salttesting import (expectedFailure, skipIf)
  22. from salttesting import TestCase
  23. from salttesting.helpers import ensure_in_syspath
  24. ensure_in_syspath('../../')
  25. # Import salt libs
  26. import integration
  27. from salt.utils.process import clean_proc
  28. from salt.utils import event
  29. # Import 3rd-+arty libs
  30. from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
  31. SOCK_DIR = os.path.join(integration.TMP, 'test-socks')
  32. NO_LONG_IPC = False
  33. if getattr(zmq, 'IPC_PATH_MAX_LEN', 103) <= 103:
  34. NO_LONG_IPC = True
  35. @contextmanager
  36. def eventpublisher_process():
  37. proc = event.EventPublisher({'sock_dir': SOCK_DIR})
  38. proc.start()
  39. try:
  40. if os.environ.get('TRAVIS_PYTHON_VERSION', None) is not None:
  41. # Travis is slow
  42. time.sleep(10)
  43. else:
  44. time.sleep(2)
  45. yield
  46. finally:
  47. clean_proc(proc)
  48. class EventSender(Process):
  49. def __init__(self, data, tag, wait):
  50. super(EventSender, self).__init__()
  51. self.data = data
  52. self.tag = tag
  53. self.wait = wait
  54. def run(self):
  55. me = event.MasterEvent(SOCK_DIR, listen=False)
  56. time.sleep(self.wait)
  57. me.fire_event(self.data, self.tag)
  58. # Wait a few seconds before tearing down the zmq context
  59. if os.environ.get('TRAVIS_PYTHON_VERSION', None) is not None:
  60. # Travis is slow
  61. time.sleep(10)
  62. else:
  63. time.sleep(2)
  64. @contextmanager
  65. def eventsender_process(data, tag, wait=0):
  66. proc = EventSender(data, tag, wait)
  67. proc.start()
  68. try:
  69. yield
  70. finally:
  71. clean_proc(proc)
  72. @skipIf(NO_LONG_IPC, "This system does not support long IPC paths. Skipping event tests!")
  73. class TestSaltEvent(TestCase):
  74. def setUp(self):
  75. if not os.path.exists(SOCK_DIR):
  76. os.makedirs(SOCK_DIR)
  77. def assertGotEvent(self, evt, data, msg=None):
  78. self.assertIsNotNone(evt, msg)
  79. for key in data:
  80. self.assertIn(key, evt, '{0}: Key {1} missing'.format(msg, key))
  81. assertMsg = '{0}: Key {1} value mismatch, {2} != {3}'
  82. assertMsg = assertMsg.format(msg, key, data[key], evt[key])
  83. self.assertEqual(data[key], evt[key], assertMsg)
  84. def test_master_event(self):
  85. me = event.MasterEvent(SOCK_DIR, listen=False)
  86. self.assertEqual(
  87. me.puburi, '{0}'.format(
  88. os.path.join(SOCK_DIR, 'master_event_pub.ipc')
  89. )
  90. )
  91. self.assertEqual(
  92. me.pulluri,
  93. '{0}'.format(
  94. os.path.join(SOCK_DIR, 'master_event_pull.ipc')
  95. )
  96. )
  97. def test_minion_event(self):
  98. opts = dict(id='foo', sock_dir=SOCK_DIR)
  99. id_hash = hashlib.md5(opts['id']).hexdigest()[:10]
  100. me = event.MinionEvent(opts, listen=False)
  101. self.assertEqual(
  102. me.puburi,
  103. '{0}'.format(
  104. os.path.join(
  105. SOCK_DIR, 'minion_event_{0}_pub.ipc'.format(id_hash)
  106. )
  107. )
  108. )
  109. self.assertEqual(
  110. me.pulluri,
  111. '{0}'.format(
  112. os.path.join(
  113. SOCK_DIR, 'minion_event_{0}_pull.ipc'.format(id_hash)
  114. )
  115. )
  116. )
  117. def test_minion_event_tcp_ipc_mode(self):
  118. opts = dict(id='foo', ipc_mode='tcp')
  119. me = event.MinionEvent(opts, listen=False)
  120. self.assertEqual(me.puburi, 4510)
  121. self.assertEqual(me.pulluri, 4511)
  122. def test_minion_event_no_id(self):
  123. me = event.MinionEvent(dict(sock_dir=SOCK_DIR), listen=False)
  124. id_hash = hashlib.md5('').hexdigest()[:10]
  125. self.assertEqual(
  126. me.puburi,
  127. '{0}'.format(
  128. os.path.join(
  129. SOCK_DIR, 'minion_event_{0}_pub.ipc'.format(id_hash)
  130. )
  131. )
  132. )
  133. self.assertEqual(
  134. me.pulluri,
  135. '{0}'.format(
  136. os.path.join(
  137. SOCK_DIR, 'minion_event_{0}_pull.ipc'.format(id_hash)
  138. )
  139. )
  140. )
  141. def test_event_single(self):
  142. '''Test a single event is received'''
  143. with eventpublisher_process():
  144. me = event.MasterEvent(SOCK_DIR, listen=True)
  145. me.fire_event({'data': 'foo1'}, 'evt1')
  146. evt1 = me.get_event(tag='evt1')
  147. self.assertGotEvent(evt1, {'data': 'foo1'})
  148. def test_event_single_no_block(self):
  149. '''Test a single event is received, no block'''
  150. with eventpublisher_process():
  151. me = event.MasterEvent(SOCK_DIR, listen=True)
  152. start = time.time()
  153. finish = start + 5
  154. evt1 = me.get_event(wait=0, tag='evt1', no_block=True)
  155. # We should get None and way before the 5 seconds wait since it's
  156. # non-blocking, otherwise it would wait for an event which we
  157. # didn't even send
  158. self.assertIsNone(evt1, None)
  159. self.assertLess(start, finish)
  160. me.fire_event({'data': 'foo1'}, 'evt1')
  161. evt1 = me.get_event(wait=0, tag='evt1')
  162. self.assertGotEvent(evt1, {'data': 'foo1'})
  163. def test_event_single_wait_0_no_block_False(self):
  164. '''Test a single event is received with wait=0 and no_block=False and doesn't spin the while loop'''
  165. with eventpublisher_process():
  166. me = event.MasterEvent(SOCK_DIR, listen=True)
  167. me.fire_event({'data': 'foo1'}, 'evt1')
  168. # This is too fast and will be None but assures we're not blocking
  169. evt1 = me.get_event(wait=0, tag='evt1', no_block=False)
  170. self.assertGotEvent(evt1, {'data': 'foo1'})
  171. def test_event_timeout(self):
  172. '''Test no event is received if the timeout is reached'''
  173. with eventpublisher_process():
  174. me = event.MasterEvent(SOCK_DIR, listen=True)
  175. me.fire_event({'data': 'foo1'}, 'evt1')
  176. evt1 = me.get_event(tag='evt1')
  177. self.assertGotEvent(evt1, {'data': 'foo1'})
  178. evt2 = me.get_event(tag='evt1')
  179. self.assertIsNone(evt2)
  180. def test_event_no_timeout(self):
  181. '''Test no wait timeout, we should block forever, until we get one '''
  182. with eventpublisher_process():
  183. me = event.MasterEvent(SOCK_DIR, listen=True)
  184. with eventsender_process({'data': 'foo2'}, 'evt2', 5):
  185. evt = me.get_event(tag='evt2', wait=0, no_block=False)
  186. self.assertGotEvent(evt, {'data': 'foo2'})
  187. def test_event_matching(self):
  188. '''Test a startswith match'''
  189. with eventpublisher_process():
  190. me = event.MasterEvent(SOCK_DIR, listen=True)
  191. me.fire_event({'data': 'foo1'}, 'evt1')
  192. evt1 = me.get_event(tag='ev')
  193. self.assertGotEvent(evt1, {'data': 'foo1'})
  194. def test_event_matching_regex(self):
  195. '''Test a regex match'''
  196. with eventpublisher_process():
  197. me = event.MasterEvent(SOCK_DIR, listen=True)
  198. me.fire_event({'data': 'foo1'}, 'evt1')
  199. evt1 = me.get_event(tag='^ev', match_type='regex')
  200. self.assertGotEvent(evt1, {'data': 'foo1'})
  201. def test_event_matching_all(self):
  202. '''Test an all match'''
  203. with eventpublisher_process():
  204. me = event.MasterEvent(SOCK_DIR, listen=True)
  205. me.fire_event({'data': 'foo1'}, 'evt1')
  206. evt1 = me.get_event(tag='')
  207. self.assertGotEvent(evt1, {'data': 'foo1'})
  208. def test_event_matching_all_when_tag_is_None(self):
  209. '''Test event matching all when not passing a tag'''
  210. with eventpublisher_process():
  211. me = event.MasterEvent(SOCK_DIR, listen=True)
  212. me.fire_event({'data': 'foo1'}, 'evt1')
  213. evt1 = me.get_event()
  214. self.assertGotEvent(evt1, {'data': 'foo1'})
  215. def test_event_not_subscribed(self):
  216. '''Test get_event drops non-subscribed events'''
  217. with eventpublisher_process():
  218. me = event.MasterEvent(SOCK_DIR, listen=True)
  219. me.fire_event({'data': 'foo1'}, 'evt1')
  220. me.fire_event({'data': 'foo2'}, 'evt2')
  221. evt2 = me.get_event(tag='evt2')
  222. evt1 = me.get_event(tag='evt1')
  223. self.assertGotEvent(evt2, {'data': 'foo2'})
  224. self.assertIsNone(evt1)
  225. def test_event_subscription_cache(self):
  226. '''Test subscriptions cache a message until requested'''
  227. with eventpublisher_process():
  228. me = event.MasterEvent(SOCK_DIR, listen=True)
  229. me.subscribe('evt1')
  230. me.fire_event({'data': 'foo1'}, 'evt1')
  231. me.fire_event({'data': 'foo2'}, 'evt2')
  232. evt2 = me.get_event(tag='evt2')
  233. evt1 = me.get_event(tag='evt1')
  234. self.assertGotEvent(evt2, {'data': 'foo2'})
  235. self.assertGotEvent(evt1, {'data': 'foo1'})
  236. def test_event_subscriptions_cache_regex(self):
  237. '''Test regex subscriptions cache a message until requested'''
  238. with eventpublisher_process():
  239. me = event.MasterEvent(SOCK_DIR, listen=True)
  240. me.subscribe('e..1$', 'regex')
  241. me.fire_event({'data': 'foo1'}, 'evt1')
  242. me.fire_event({'data': 'foo2'}, 'evt2')
  243. evt2 = me.get_event(tag='evt2')
  244. evt1 = me.get_event(tag='evt1')
  245. self.assertGotEvent(evt2, {'data': 'foo2'})
  246. self.assertGotEvent(evt1, {'data': 'foo1'})
  247. def test_event_multiple_clients(self):
  248. '''Test event is received by multiple clients'''
  249. with eventpublisher_process():
  250. me1 = event.MasterEvent(SOCK_DIR, listen=True)
  251. me2 = event.MasterEvent(SOCK_DIR, listen=True)
  252. # We need to sleep here to avoid a race condition wherein
  253. # the second socket may not be connected by the time the first socket
  254. # sends the event.
  255. time.sleep(0.5)
  256. me1.fire_event({'data': 'foo1'}, 'evt1')
  257. evt1 = me1.get_event(tag='evt1')
  258. self.assertGotEvent(evt1, {'data': 'foo1'})
  259. evt2 = me2.get_event(tag='evt1')
  260. self.assertGotEvent(evt2, {'data': 'foo1'})
  261. @expectedFailure
  262. def test_event_nested_sub_all(self):
  263. '''Test nested event subscriptions do not drop events, get event for all tags'''
  264. # Show why not to call get_event(tag='')
  265. with eventpublisher_process():
  266. me = event.MasterEvent(SOCK_DIR, listen=True)
  267. me.fire_event({'data': 'foo1'}, 'evt1')
  268. me.fire_event({'data': 'foo2'}, 'evt2')
  269. evt2 = me.get_event(tag='')
  270. evt1 = me.get_event(tag='')
  271. self.assertGotEvent(evt2, {'data': 'foo2'})
  272. self.assertGotEvent(evt1, {'data': 'foo1'})
  273. def test_event_many(self):
  274. '''Test a large number of events, one at a time'''
  275. with eventpublisher_process():
  276. me = event.MasterEvent(SOCK_DIR, listen=True)
  277. for i in range(500):
  278. me.fire_event({'data': '{0}'.format(i)}, 'testevents')
  279. evt = me.get_event(tag='testevents')
  280. self.assertGotEvent(evt, {'data': '{0}'.format(i)}, 'Event {0}'.format(i))
  281. def test_event_many_backlog(self):
  282. '''Test a large number of events, send all then recv all'''
  283. with eventpublisher_process():
  284. me = event.MasterEvent(SOCK_DIR, listen=True)
  285. # Must not exceed zmq HWM
  286. for i in range(500):
  287. me.fire_event({'data': '{0}'.format(i)}, 'testevents')
  288. for i in range(500):
  289. evt = me.get_event(tag='testevents')
  290. self.assertGotEvent(evt, {'data': '{0}'.format(i)}, 'Event {0}'.format(i))
  291. # Test the fire_master function. As it wraps the underlying fire_event,
  292. # we don't need to perform extensive testing.
  293. def test_send_master_event(self):
  294. '''Tests that sending an event through fire_master generates expected event'''
  295. with eventpublisher_process():
  296. me = event.MasterEvent(SOCK_DIR, listen=True)
  297. data = {'data': 'foo1'}
  298. me.fire_master(data, 'test_master')
  299. evt = me.get_event(tag='fire_master')
  300. self.assertGotEvent(evt, {'data': data, 'tag': 'test_master', 'events': None, 'pretag': None})
  301. class TestAsyncEventPublisher(AsyncTestCase):
  302. def get_new_ioloop(self):
  303. return zmq.eventloop.ioloop.ZMQIOLoop()
  304. def setUp(self):
  305. super(TestAsyncEventPublisher, self).setUp()
  306. self.publisher = event.AsyncEventPublisher(
  307. {'sock_dir': SOCK_DIR},
  308. self._handle_publish,
  309. self.io_loop,
  310. )
  311. def _handle_publish(self, raw):
  312. self.tag, self.data = event.SaltEvent.unpack(raw)
  313. self.stop()
  314. def test_event_subscription(self):
  315. '''Test a single event is received'''
  316. me = event.MinionEvent({'sock_dir': SOCK_DIR}, listen=True)
  317. me.fire_event({'data': 'foo1'}, 'evt1')
  318. self.wait()
  319. evt1 = me.get_event(tag='evt1')
  320. self.assertEqual(self.tag, 'evt1')
  321. self.data.pop('_stamp') # drop the stamp
  322. self.assertEqual(self.data, {'data': 'foo1'})
  323. if __name__ == '__main__':
  324. from integration import run_tests
  325. run_tests(TestSaltEvent, needs_daemon=False)