test_app.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. # -*- coding: utf-8 -*-
  2. # Import Python Libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import os
  5. import time
  6. import threading
  7. # Import Salt Libs
  8. import salt.utils.json
  9. import salt.utils.stringutils
  10. from salt.netapi.rest_tornado import saltnado
  11. from salt.utils.versions import StrictVersion
  12. # Import Salt Testing Libs
  13. from tests.unit.netapi.test_rest_tornado import SaltnadoTestCase
  14. from tests.support.helpers import flaky
  15. from tests.support.unit import skipIf
  16. # Import 3rd-party libs
  17. from salt.ext import six
  18. from salt.utils.zeromq import zmq, ZMQDefaultLoop as ZMQIOLoop
  19. HAS_ZMQ_IOLOOP = bool(zmq)
  20. class _SaltnadoIntegrationTestCase(SaltnadoTestCase): # pylint: disable=abstract-method
  21. @property
  22. def opts(self):
  23. return self.get_config('client_config', from_scratch=True)
  24. @property
  25. def mod_opts(self):
  26. return self.get_config('minion', from_scratch=True)
  27. @skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
  28. @skipIf(StrictVersion(zmq.__version__) < StrictVersion('14.0.1'), 'PyZMQ must be >= 14.0.1 to run these tests.')
  29. class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
  30. def setUp(self):
  31. super(TestSaltAPIHandler, self).setUp()
  32. os.environ['ASYNC_TEST_TIMEOUT'] = '300'
  33. def get_app(self):
  34. urls = [('/', saltnado.SaltAPIHandler)]
  35. application = self.build_tornado_app(urls)
  36. application.event_listener = saltnado.EventListener({}, self.opts)
  37. self.application = application
  38. return application
  39. def test_root(self):
  40. '''
  41. Test the root path which returns the list of clients we support
  42. '''
  43. response = self.fetch('/',
  44. connect_timeout=30,
  45. request_timeout=30,
  46. )
  47. self.assertEqual(response.code, 200)
  48. response_obj = salt.utils.json.loads(response.body)
  49. self.assertEqual(sorted(response_obj['clients']),
  50. ['local', 'local_async', 'runner', 'runner_async'])
  51. self.assertEqual(response_obj['return'], 'Welcome')
  52. def test_post_no_auth(self):
  53. '''
  54. Test post with no auth token, should 401
  55. '''
  56. # get a token for this test
  57. low = [{'client': 'local',
  58. 'tgt': '*',
  59. 'fun': 'test.ping',
  60. }]
  61. response = self.fetch('/',
  62. method='POST',
  63. body=salt.utils.json.dumps(low),
  64. headers={'Content-Type': self.content_type_map['json']},
  65. follow_redirects=False,
  66. connect_timeout=30,
  67. request_timeout=30,
  68. )
  69. self.assertEqual(response.code, 302)
  70. self.assertEqual(response.headers['Location'], '/login')
  71. # Local client tests
  72. @skipIf(True, 'to be re-enabled when #23623 is merged')
  73. def test_simple_local_post(self):
  74. '''
  75. Test a basic API of /
  76. '''
  77. low = [{'client': 'local',
  78. 'tgt': '*',
  79. 'fun': 'test.ping',
  80. }]
  81. response = self.fetch('/',
  82. method='POST',
  83. body=salt.utils.json.dumps(low),
  84. headers={'Content-Type': self.content_type_map['json'],
  85. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  86. connect_timeout=30,
  87. request_timeout=30,
  88. )
  89. response_obj = salt.utils.json.loads(response.body)
  90. self.assertEqual(len(response_obj['return']), 1)
  91. # If --proxy is set, it will cause an extra minion_id to be in the
  92. # response. Since there's not a great way to know if the test
  93. # runner's proxy minion is running, and we're not testing proxy
  94. # minions here anyway, just remove it from the response.
  95. response_obj['return'][0].pop('proxytest', None)
  96. self.assertEqual(response_obj['return'][0], {'minion': True, 'sub_minion': True})
  97. def test_simple_local_post_no_tgt(self):
  98. '''
  99. POST job with invalid tgt
  100. '''
  101. low = [{'client': 'local',
  102. 'tgt': 'minion_we_dont_have',
  103. 'fun': 'test.ping',
  104. }]
  105. response = self.fetch('/',
  106. method='POST',
  107. body=salt.utils.json.dumps(low),
  108. headers={'Content-Type': self.content_type_map['json'],
  109. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  110. connect_timeout=30,
  111. request_timeout=30,
  112. )
  113. response_obj = salt.utils.json.loads(response.body)
  114. self.assertEqual(response_obj['return'], ["No minions matched the target. No command was sent, no jid was assigned."])
  115. # local client request body test
  116. @skipIf(True, 'Undetermined race condition in test. Temporarily disabled.')
  117. def test_simple_local_post_only_dictionary_request(self):
  118. '''
  119. Test a basic API of /
  120. '''
  121. low = {'client': 'local',
  122. 'tgt': '*',
  123. 'fun': 'test.ping',
  124. }
  125. response = self.fetch('/',
  126. method='POST',
  127. body=salt.utils.json.dumps(low),
  128. headers={'Content-Type': self.content_type_map['json'],
  129. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  130. connect_timeout=30,
  131. request_timeout=30,
  132. )
  133. response_obj = salt.utils.json.loads(response.body)
  134. self.assertEqual(len(response_obj['return']), 1)
  135. # If --proxy is set, it will cause an extra minion_id to be in the
  136. # response. Since there's not a great way to know if the test
  137. # runner's proxy minion is running, and we're not testing proxy
  138. # minions here anyway, just remove it from the response.
  139. response_obj['return'][0].pop('proxytest', None)
  140. self.assertEqual(response_obj['return'][0], {'minion': True, 'sub_minion': True})
  141. def test_simple_local_post_invalid_request(self):
  142. '''
  143. Test a basic API of /
  144. '''
  145. low = ["invalid request"]
  146. response = self.fetch('/',
  147. method='POST',
  148. body=salt.utils.json.dumps(low),
  149. headers={'Content-Type': self.content_type_map['json'],
  150. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  151. connect_timeout=30,
  152. request_timeout=30,
  153. )
  154. self.assertEqual(response.code, 400)
  155. # local_async tests
  156. def test_simple_local_async_post(self):
  157. low = [{'client': 'local_async',
  158. 'tgt': '*',
  159. 'fun': 'test.ping',
  160. }]
  161. response = self.fetch('/',
  162. method='POST',
  163. body=salt.utils.json.dumps(low),
  164. headers={'Content-Type': self.content_type_map['json'],
  165. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  166. )
  167. response_obj = salt.utils.json.loads(response.body)
  168. ret = response_obj['return']
  169. ret[0]['minions'] = sorted(ret[0]['minions'])
  170. try:
  171. # If --proxy is set, it will cause an extra minion_id to be in the
  172. # response. Since there's not a great way to know if the test
  173. # runner's proxy minion is running, and we're not testing proxy
  174. # minions here anyway, just remove it from the response.
  175. ret[0]['minions'].remove('proxytest')
  176. except ValueError:
  177. pass
  178. # TODO: verify pub function? Maybe look at how we test the publisher
  179. self.assertEqual(len(ret), 1)
  180. self.assertIn('jid', ret[0])
  181. self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
  182. def test_multi_local_async_post(self):
  183. low = [{'client': 'local_async',
  184. 'tgt': '*',
  185. 'fun': 'test.ping',
  186. },
  187. {'client': 'local_async',
  188. 'tgt': '*',
  189. 'fun': 'test.ping',
  190. }]
  191. response = self.fetch('/',
  192. method='POST',
  193. body=salt.utils.json.dumps(low),
  194. headers={'Content-Type': self.content_type_map['json'],
  195. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  196. )
  197. response_obj = salt.utils.json.loads(response.body)
  198. ret = response_obj['return']
  199. ret[0]['minions'] = sorted(ret[0]['minions'])
  200. ret[1]['minions'] = sorted(ret[1]['minions'])
  201. try:
  202. # If --proxy is set, it will cause an extra minion_id to be in the
  203. # response. Since there's not a great way to know if the test
  204. # runner's proxy minion is running, and we're not testing proxy
  205. # minions here anyway, just remove it from the response.
  206. ret[0]['minions'].remove('proxytest')
  207. ret[1]['minions'].remove('proxytest')
  208. except ValueError:
  209. pass
  210. self.assertEqual(len(ret), 2)
  211. self.assertIn('jid', ret[0])
  212. self.assertIn('jid', ret[1])
  213. self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
  214. self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion']))
  215. def test_multi_local_async_post_multitoken(self):
  216. low = [{'client': 'local_async',
  217. 'tgt': '*',
  218. 'fun': 'test.ping',
  219. },
  220. {'client': 'local_async',
  221. 'tgt': '*',
  222. 'fun': 'test.ping',
  223. 'token': self.token['token'], # send a different (but still valid token)
  224. },
  225. {'client': 'local_async',
  226. 'tgt': '*',
  227. 'fun': 'test.ping',
  228. 'token': 'BAD_TOKEN', # send a bad token
  229. },
  230. ]
  231. response = self.fetch('/',
  232. method='POST',
  233. body=salt.utils.json.dumps(low),
  234. headers={'Content-Type': self.content_type_map['json'],
  235. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  236. )
  237. response_obj = salt.utils.json.loads(response.body)
  238. ret = response_obj['return']
  239. ret[0]['minions'] = sorted(ret[0]['minions'])
  240. ret[1]['minions'] = sorted(ret[1]['minions'])
  241. try:
  242. # If --proxy is set, it will cause an extra minion_id to be in the
  243. # response. Since there's not a great way to know if the test
  244. # runner's proxy minion is running, and we're not testing proxy
  245. # minions here anyway, just remove it from the response.
  246. ret[0]['minions'].remove('proxytest')
  247. ret[1]['minions'].remove('proxytest')
  248. except ValueError:
  249. pass
  250. self.assertEqual(len(ret), 3) # make sure we got 3 responses
  251. self.assertIn('jid', ret[0]) # the first 2 are regular returns
  252. self.assertIn('jid', ret[1])
  253. self.assertIn('Failed to authenticate', ret[2]) # bad auth
  254. self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
  255. self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion']))
  256. def test_simple_local_async_post_no_tgt(self):
  257. low = [{'client': 'local_async',
  258. 'tgt': 'minion_we_dont_have',
  259. 'fun': 'test.ping',
  260. }]
  261. response = self.fetch('/',
  262. method='POST',
  263. body=salt.utils.json.dumps(low),
  264. headers={'Content-Type': self.content_type_map['json'],
  265. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  266. )
  267. response_obj = salt.utils.json.loads(response.body)
  268. self.assertEqual(response_obj['return'], [{}])
  269. @skipIf(True, 'Undetermined race condition in test. Temporarily disabled.')
  270. def test_simple_local_post_only_dictionary_request_with_order_masters(self):
  271. '''
  272. Test a basic API of /
  273. '''
  274. low = {'client': 'local',
  275. 'tgt': '*',
  276. 'fun': 'test.ping',
  277. }
  278. self.application.opts['order_masters'] = True
  279. self.application.opts['syndic_wait'] = 5
  280. response = self.fetch('/',
  281. method='POST',
  282. body=salt.utils.json.dumps(low),
  283. headers={'Content-Type': self.content_type_map['json'],
  284. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  285. connect_timeout=30,
  286. request_timeout=30,
  287. )
  288. response_obj = salt.utils.json.loads(response.body)
  289. self.application.opts['order_masters'] = []
  290. self.application.opts['syndic_wait'] = 5
  291. # If --proxy is set, it will cause an extra minion_id to be in the
  292. # response. Since there's not a great way to know if the test runner's
  293. # proxy minion is running, and we're not testing proxy minions here
  294. # anyway, just remove it from the response.
  295. response_obj[0]['return'].pop('proxytest', None)
  296. self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
  297. # runner tests
  298. def test_simple_local_runner_post(self):
  299. low = [{'client': 'runner',
  300. 'fun': 'manage.up',
  301. }]
  302. response = self.fetch('/',
  303. method='POST',
  304. body=salt.utils.json.dumps(low),
  305. headers={'Content-Type': self.content_type_map['json'],
  306. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  307. connect_timeout=30,
  308. request_timeout=300,
  309. )
  310. response_obj = salt.utils.json.loads(response.body)
  311. self.assertEqual(len(response_obj['return']), 1)
  312. try:
  313. # If --proxy is set, it will cause an extra minion_id to be in the
  314. # response. Since there's not a great way to know if the test
  315. # runner's proxy minion is running, and we're not testing proxy
  316. # minions here anyway, just remove it from the response.
  317. response_obj['return'][0].remove('proxytest')
  318. except ValueError:
  319. pass
  320. self.assertEqual(sorted(response_obj['return'][0]), sorted(['minion', 'sub_minion']))
  321. # runner_async tests
  322. def test_simple_local_runner_async_post(self):
  323. low = [{'client': 'runner_async',
  324. 'fun': 'manage.up',
  325. }]
  326. response = self.fetch('/',
  327. method='POST',
  328. body=salt.utils.json.dumps(low),
  329. headers={'Content-Type': self.content_type_map['json'],
  330. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  331. connect_timeout=10,
  332. request_timeout=10,
  333. )
  334. response_obj = salt.utils.json.loads(response.body)
  335. self.assertIn('return', response_obj)
  336. self.assertEqual(1, len(response_obj['return']))
  337. self.assertIn('jid', response_obj['return'][0])
  338. self.assertIn('tag', response_obj['return'][0])
  339. @flaky
  340. @skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
  341. class TestMinionSaltAPIHandler(_SaltnadoIntegrationTestCase):
  342. def get_app(self):
  343. urls = [(r"/minions/(.*)", saltnado.MinionSaltAPIHandler),
  344. (r"/minions", saltnado.MinionSaltAPIHandler),
  345. ]
  346. application = self.build_tornado_app(urls)
  347. application.event_listener = saltnado.EventListener({}, self.opts)
  348. return application
  349. @skipIf(True, 'issue #34753')
  350. def test_get_no_mid(self):
  351. response = self.fetch('/minions',
  352. method='GET',
  353. headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  354. follow_redirects=False,
  355. )
  356. response_obj = salt.utils.json.loads(response.body)
  357. self.assertEqual(len(response_obj['return']), 1)
  358. # one per minion
  359. self.assertEqual(len(response_obj['return'][0]), 2)
  360. # check a single grain
  361. for minion_id, grains in six.iteritems(response_obj['return'][0]):
  362. self.assertEqual(minion_id, grains['id'])
  363. @skipIf(True, 'to be re-enabled when #23623 is merged')
  364. def test_get(self):
  365. response = self.fetch('/minions/minion',
  366. method='GET',
  367. headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  368. follow_redirects=False,
  369. )
  370. response_obj = salt.utils.json.loads(response.body)
  371. self.assertEqual(len(response_obj['return']), 1)
  372. self.assertEqual(len(response_obj['return'][0]), 1)
  373. # check a single grain
  374. self.assertEqual(response_obj['return'][0]['minion']['id'], 'minion')
  375. def test_post(self):
  376. low = [{'tgt': '*minion',
  377. 'fun': 'test.ping',
  378. }]
  379. response = self.fetch('/minions',
  380. method='POST',
  381. body=salt.utils.json.dumps(low),
  382. headers={'Content-Type': self.content_type_map['json'],
  383. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  384. )
  385. response_obj = salt.utils.json.loads(response.body)
  386. ret = response_obj['return']
  387. ret[0]['minions'] = sorted(ret[0]['minions'])
  388. # TODO: verify pub function? Maybe look at how we test the publisher
  389. self.assertEqual(len(ret), 1)
  390. self.assertIn('jid', ret[0])
  391. self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
  392. def test_post_with_client(self):
  393. # get a token for this test
  394. low = [{'client': 'local_async',
  395. 'tgt': '*minion',
  396. 'fun': 'test.ping',
  397. }]
  398. response = self.fetch('/minions',
  399. method='POST',
  400. body=salt.utils.json.dumps(low),
  401. headers={'Content-Type': self.content_type_map['json'],
  402. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  403. )
  404. response_obj = salt.utils.json.loads(response.body)
  405. ret = response_obj['return']
  406. ret[0]['minions'] = sorted(ret[0]['minions'])
  407. # TODO: verify pub function? Maybe look at how we test the publisher
  408. self.assertEqual(len(ret), 1)
  409. self.assertIn('jid', ret[0])
  410. self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
  411. def test_post_with_incorrect_client(self):
  412. '''
  413. The /minions endpoint is asynchronous only, so if you try something else
  414. make sure you get an error
  415. '''
  416. # get a token for this test
  417. low = [{'client': 'local',
  418. 'tgt': '*',
  419. 'fun': 'test.ping',
  420. }]
  421. response = self.fetch('/minions',
  422. method='POST',
  423. body=salt.utils.json.dumps(low),
  424. headers={'Content-Type': self.content_type_map['json'],
  425. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  426. )
  427. self.assertEqual(response.code, 400)
  428. @skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
  429. class TestJobsSaltAPIHandler(_SaltnadoIntegrationTestCase):
  430. def get_app(self):
  431. urls = [(r"/jobs/(.*)", saltnado.JobsSaltAPIHandler),
  432. (r"/jobs", saltnado.JobsSaltAPIHandler),
  433. ]
  434. application = self.build_tornado_app(urls)
  435. application.event_listener = saltnado.EventListener({}, self.opts)
  436. return application
  437. @skipIf(True, 'to be re-enabled when #23623 is merged')
  438. def test_get(self):
  439. # test with no JID
  440. self.http_client.fetch(self.get_url('/jobs'),
  441. self.stop,
  442. method='GET',
  443. headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  444. follow_redirects=False,
  445. )
  446. response = self.wait(timeout=30)
  447. response_obj = salt.utils.json.loads(response.body)['return'][0]
  448. try:
  449. for jid, ret in six.iteritems(response_obj):
  450. self.assertIn('Function', ret)
  451. self.assertIn('Target', ret)
  452. self.assertIn('Target-type', ret)
  453. self.assertIn('User', ret)
  454. self.assertIn('StartTime', ret)
  455. self.assertIn('Arguments', ret)
  456. except AttributeError as attribute_error:
  457. print(salt.utils.json.loads(response.body))
  458. raise
  459. # test with a specific JID passed in
  460. jid = next(six.iterkeys(response_obj))
  461. self.http_client.fetch(self.get_url('/jobs/{0}'.format(jid)),
  462. self.stop,
  463. method='GET',
  464. headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  465. follow_redirects=False,
  466. )
  467. response = self.wait(timeout=30)
  468. response_obj = salt.utils.json.loads(response.body)['return'][0]
  469. self.assertIn('Function', response_obj)
  470. self.assertIn('Target', response_obj)
  471. self.assertIn('Target-type', response_obj)
  472. self.assertIn('User', response_obj)
  473. self.assertIn('StartTime', response_obj)
  474. self.assertIn('Arguments', response_obj)
  475. self.assertIn('Result', response_obj)
  476. # TODO: run all the same tests from the root handler, but for now since they are
  477. # the same code, we'll just sanity check
  478. @skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
  479. class TestRunSaltAPIHandler(_SaltnadoIntegrationTestCase):
  480. def get_app(self):
  481. urls = [("/run", saltnado.RunSaltAPIHandler),
  482. ]
  483. application = self.build_tornado_app(urls)
  484. application.event_listener = saltnado.EventListener({}, self.opts)
  485. return application
  486. @skipIf(True, 'to be re-enabled when #23623 is merged')
  487. def test_get(self):
  488. low = [{'client': 'local',
  489. 'tgt': '*',
  490. 'fun': 'test.ping',
  491. }]
  492. response = self.fetch('/run',
  493. method='POST',
  494. body=salt.utils.json.dumps(low),
  495. headers={'Content-Type': self.content_type_map['json'],
  496. saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  497. )
  498. response_obj = salt.utils.json.loads(response.body)
  499. self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
  500. @skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
  501. class TestEventsSaltAPIHandler(_SaltnadoIntegrationTestCase):
  502. def get_app(self):
  503. urls = [(r"/events", saltnado.EventsSaltAPIHandler),
  504. ]
  505. application = self.build_tornado_app(urls)
  506. application.event_listener = saltnado.EventListener({}, self.opts)
  507. # store a reference, for magic later!
  508. self.application = application
  509. self.events_to_fire = 0
  510. return application
  511. def test_get(self):
  512. self.events_to_fire = 5
  513. response = self.fetch('/events',
  514. headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  515. streaming_callback=self.on_event,
  516. )
  517. def _stop(self):
  518. self.stop()
  519. def on_event(self, event):
  520. if six.PY3:
  521. event = event.decode('utf-8')
  522. if self.events_to_fire > 0:
  523. self.application.event_listener.event.fire_event({
  524. 'foo': 'bar',
  525. 'baz': 'qux',
  526. }, 'salt/netapi/test')
  527. self.events_to_fire -= 1
  528. # once we've fired all the events, lets call it a day
  529. else:
  530. # wait so that we can ensure that the next future is ready to go
  531. # to make sure we don't explode if the next one is ready
  532. ZMQIOLoop.current().add_timeout(time.time() + 0.5, self._stop)
  533. event = event.strip()
  534. # if we got a retry, just continue
  535. if event != 'retry: 400':
  536. tag, data = event.splitlines()
  537. self.assertTrue(tag.startswith('tag: '))
  538. self.assertTrue(data.startswith('data: '))
  539. @skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
  540. class TestWebhookSaltAPIHandler(_SaltnadoIntegrationTestCase):
  541. def get_app(self):
  542. urls = [(r"/hook(/.*)?", saltnado.WebhookSaltAPIHandler),
  543. ]
  544. application = self.build_tornado_app(urls)
  545. self.application = application
  546. application.event_listener = saltnado.EventListener({}, self.opts)
  547. return application
  548. @skipIf(True, 'Skipping until we can devote more resources to debugging this test.')
  549. def test_post(self):
  550. self._future_resolved = threading.Event()
  551. try:
  552. def verify_event(future):
  553. '''
  554. Notify the threading event that the future is resolved
  555. '''
  556. self._future_resolved.set()
  557. self._finished = False # TODO: remove after some cleanup of the event listener
  558. # get an event future
  559. future = self.application.event_listener.get_event(self,
  560. tag='salt/netapi/hook',
  561. callback=verify_event)
  562. # fire the event
  563. response = self.fetch('/hook',
  564. method='POST',
  565. body='foo=bar',
  566. headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
  567. )
  568. response_obj = salt.utils.json.loads(response.body)
  569. self.assertTrue(response_obj['success'])
  570. resolve_future_timeout = 60
  571. self._future_resolved.wait(resolve_future_timeout)
  572. try:
  573. event = future.result()
  574. except Exception as exc: # pylint: disable=broad-except
  575. self.fail('Failed to resolve future under {} secs: {}'.format(resolve_future_timeout, exc))
  576. self.assertEqual(event['tag'], 'salt/netapi/hook')
  577. self.assertIn('headers', event['data'])
  578. self.assertEqual(
  579. event['data']['post'],
  580. {'foo': salt.utils.stringutils.to_bytes('bar')}
  581. )
  582. finally:
  583. self._future_resolved.clear()
  584. del self._future_resolved