1
0

test_app.py 27 KB

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