test_state.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests for the state runner
  4. """
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import errno
  7. import logging
  8. import os
  9. import shutil
  10. import signal
  11. import tempfile
  12. import textwrap
  13. import threading
  14. import time
  15. import pytest
  16. import salt.exceptions
  17. import salt.utils.event
  18. import salt.utils.files
  19. import salt.utils.json
  20. import salt.utils.platform
  21. import salt.utils.stringutils
  22. import salt.utils.yaml
  23. from salt.ext import six
  24. from salt.ext.six.moves import queue
  25. from tests.support.case import ShellCase
  26. from tests.support.mock import MagicMock, patch
  27. from tests.support.runtests import RUNTIME_VARS
  28. from tests.support.unit import skipIf
  29. log = logging.getLogger(__name__)
  30. @pytest.mark.flaky(max_runs=4)
  31. class StateRunnerTest(ShellCase):
  32. """
  33. Test the state runner.
  34. """
  35. def add_to_queue(self, q, cmd):
  36. """
  37. helper method to add salt-run
  38. return data to a queue
  39. """
  40. ret = self.run_run(cmd)
  41. q.put(ret)
  42. q.task_done()
  43. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  44. def test_orchestrate_output(self):
  45. """
  46. Ensure the orchestrate runner outputs useful state data.
  47. In Issue #31330, the output only contains ['outputter:', ' highstate'],
  48. and not the full stateful return. This tests ensures we don't regress in that
  49. manner again.
  50. Also test against some sample "good" output that would be included in a correct
  51. orchestrate run.
  52. """
  53. ret_output = self.run_run("state.orchestrate orch.simple")
  54. bad_out = ["outputter:", " highstate"]
  55. good_out = [
  56. " Function: salt.state",
  57. " Result: True",
  58. "Succeeded: 1 (changed=1)",
  59. "Failed: 0",
  60. "Total states run: 1",
  61. ]
  62. # First, check that we don't have the "bad" output that was displaying in
  63. # Issue #31330 where only the highstate outputter was listed
  64. assert bad_out != ret_output
  65. # Now test that some expected good sample output is present in the return.
  66. for item in good_out:
  67. assert item in ret_output
  68. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  69. def test_orchestrate_nested(self):
  70. """
  71. test salt-run state.orchestrate and failhard with nested orchestration
  72. """
  73. if os.path.exists("/tmp/ewu-2016-12-13"):
  74. os.remove("/tmp/ewu-2016-12-13")
  75. _, code = self.run_run("state.orchestrate nested-orch.outer", with_retcode=True)
  76. assert os.path.exists("/tmp/ewu-2016-12-13") is False
  77. assert code != 0
  78. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  79. def test_orchestrate_with_mine(self):
  80. """
  81. test salt-run state.orchestrate with mine.get call in sls
  82. """
  83. fail_time = time.time() + 120
  84. self.run_run('mine.update "*"')
  85. exp_ret = "Succeeded: 1 (changed=1)"
  86. while True:
  87. ret = self.run_run("state.orchestrate orch.mine")
  88. try:
  89. assert exp_ret in ret
  90. break
  91. except AssertionError:
  92. if time.time() > fail_time:
  93. self.fail(
  94. '"{0}" was not found in the orchestration call'.format(exp_ret)
  95. )
  96. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  97. def test_orchestrate_state_and_function_failure(self):
  98. """
  99. Ensure that returns from failed minions are in the changes dict where
  100. they belong, so they can be programatically analyzed.
  101. See https://github.com/saltstack/salt/issues/43204
  102. """
  103. self.run_run("saltutil.sync_modules")
  104. ret = salt.utils.json.loads(
  105. "\n".join(self.run_run("state.orchestrate orch.issue43204 --out=json"))
  106. )
  107. # Drill down to the changes dict
  108. state_ret = ret["data"]["master"]["salt_|-Step01_|-Step01_|-state"]["changes"]
  109. func_ret = ret["data"]["master"][
  110. "salt_|-Step02_|-runtests_helpers.nonzero_retcode_return_false_|-function"
  111. ]["changes"]
  112. # Remove duration and start time from the results, since they would
  113. # vary with each run and that would make it impossible to test.
  114. for item in ("duration", "start_time"):
  115. state_ret["ret"]["minion"][
  116. "test_|-test fail with changes_|-test fail with changes_|-fail_with_changes"
  117. ].pop(item)
  118. self.assertEqual(
  119. state_ret,
  120. {
  121. "out": "highstate",
  122. "ret": {
  123. "minion": {
  124. "test_|-test fail with changes_|-test fail with changes_|-fail_with_changes": {
  125. "__id__": "test fail with changes",
  126. "__run_num__": 0,
  127. "__sls__": "orch.issue43204.fail_with_changes",
  128. "changes": {
  129. "testing": {
  130. "new": "Something pretended to change",
  131. "old": "Unchanged",
  132. }
  133. },
  134. "comment": "Failure!",
  135. "name": "test fail with changes",
  136. "result": False,
  137. }
  138. }
  139. },
  140. },
  141. )
  142. self.assertEqual(func_ret, {"out": "highstate", "ret": {"minion": False}})
  143. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  144. def test_orchestrate_target_exists(self):
  145. """
  146. test orchestration when target exists
  147. while using multiple states
  148. """
  149. ret = self.run_run("state.orchestrate orch.target-exists")
  150. first = [" ID: core", " Function: salt.state", " Result: True"]
  151. second = [
  152. " ID: test-state",
  153. " Function: salt.state",
  154. " Result: True",
  155. ]
  156. third = [
  157. " ID: cmd.run",
  158. " Function: salt.function",
  159. " Result: True",
  160. ]
  161. ret_out = [first, second, third]
  162. for out in ret_out:
  163. for item in out:
  164. assert item in ret
  165. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  166. def test_orchestrate_retcode(self):
  167. """
  168. Test orchestration with nonzero retcode set in __context__
  169. """
  170. self.run_run("saltutil.sync_runners")
  171. self.run_run("saltutil.sync_wheel")
  172. ret = "\n".join(self.run_run("state.orchestrate orch.retcode"))
  173. for result in (
  174. " ID: test_runner_success\n"
  175. " Function: salt.runner\n"
  176. " Name: runtests_helpers.success\n"
  177. " Result: True",
  178. " ID: test_runner_failure\n"
  179. " Function: salt.runner\n"
  180. " Name: runtests_helpers.failure\n"
  181. " Result: False",
  182. " ID: test_wheel_success\n"
  183. " Function: salt.wheel\n"
  184. " Name: runtests_helpers.success\n"
  185. " Result: True",
  186. " ID: test_wheel_failure\n"
  187. " Function: salt.wheel\n"
  188. " Name: runtests_helpers.failure\n"
  189. " Result: False",
  190. ):
  191. self.assertIn(result, ret)
  192. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  193. def test_orchestrate_target_does_not_exist(self):
  194. """
  195. test orchestration when target doesn't exist
  196. while using multiple states
  197. """
  198. ret = self.run_run("state.orchestrate orch.target-does-not-exist")
  199. first = [
  200. "No minions matched the target. No command was sent, no jid was assigned.",
  201. " ID: core",
  202. " Function: salt.state",
  203. " Result: False",
  204. ]
  205. second = [
  206. " ID: test-state",
  207. " Function: salt.state",
  208. " Result: True",
  209. ]
  210. third = [
  211. " ID: cmd.run",
  212. " Function: salt.function",
  213. " Result: True",
  214. ]
  215. ret_out = [first, second, third]
  216. for out in ret_out:
  217. for item in out:
  218. assert item in ret
  219. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  220. def test_orchestrate_batch_with_failhard_error(self):
  221. """
  222. test orchestration properly stops with failhard and batch.
  223. """
  224. ret = self.run_run("state.orchestrate orch.batch --out=json -l critical")
  225. ret_json = salt.utils.json.loads("\n".join(ret))
  226. retcode = ret_json["retcode"]
  227. result = ret_json["data"]["master"][
  228. "salt_|-call_fail_state_|-call_fail_state_|-state"
  229. ]["result"]
  230. changes = ret_json["data"]["master"][
  231. "salt_|-call_fail_state_|-call_fail_state_|-state"
  232. ]["changes"]
  233. # Looks like there is a platform differences in execution.
  234. # I see empty changes dict in MacOS for some reason. Maybe it's a bug?
  235. if changes:
  236. changes_ret = changes["ret"]
  237. # Debug
  238. print("Retcode: {}".format(retcode))
  239. print("Changes: {}".format(changes))
  240. print("Result: {}".format(result))
  241. assert retcode != 0
  242. assert result is False
  243. if changes:
  244. # The execution should stop after first error, so return dict should contain only one minion
  245. assert len(changes_ret) == 1
  246. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  247. def test_state_event(self):
  248. """
  249. test to ensure state.event
  250. runner returns correct data
  251. """
  252. q = queue.Queue(maxsize=0)
  253. cmd = "state.event salt/job/*/new count=1"
  254. expect = '"minions": ["minion"]'
  255. server_thread = threading.Thread(target=self.add_to_queue, args=(q, cmd))
  256. server_thread.setDaemon(True)
  257. server_thread.start()
  258. while q.empty():
  259. self.run_salt("minion test.ping --static")
  260. out = q.get()
  261. assert expect in six.text_type(out)
  262. server_thread.join()
  263. @pytest.mark.slow_test(seconds=60) # Test takes >30 and <=60 seconds
  264. def test_orchestrate_subset(self):
  265. """
  266. test orchestration state using subset
  267. """
  268. ret = self.run_run("state.orchestrate orch.subset", timeout=500)
  269. def count(thing, listobj):
  270. return sum([obj.strip() == thing for obj in listobj])
  271. assert count("ID: test subset", ret) == 1
  272. assert count("Succeeded: 1", ret) == 1
  273. assert count("Failed: 0", ret) == 1
  274. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  275. def test_orchestrate_salt_function_return_false_failure(self):
  276. """
  277. Ensure that functions that only return False in the return
  278. are flagged as failed when run as orchestrations.
  279. See https://github.com/saltstack/salt/issues/30367
  280. """
  281. self.run_run("saltutil.sync_modules")
  282. ret = salt.utils.json.loads(
  283. "\n".join(self.run_run("state.orchestrate orch.issue30367 --out=json"))
  284. )
  285. # Drill down to the changes dict
  286. state_result = ret["data"]["master"][
  287. "salt_|-deploy_check_|-test.false_|-function"
  288. ]["result"]
  289. func_ret = ret["data"]["master"]["salt_|-deploy_check_|-test.false_|-function"][
  290. "changes"
  291. ]
  292. assert state_result is False
  293. self.assertEqual(func_ret, {"out": "highstate", "ret": {"minion": False}})
  294. @skipIf(salt.utils.platform.is_windows(), "*NIX-only test")
  295. @pytest.mark.flaky(max_runs=4)
  296. class OrchEventTest(ShellCase):
  297. """
  298. Tests for orchestration events
  299. """
  300. def setUp(self):
  301. self.timeout = 60
  302. self.master_d_dir = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, "master.d")
  303. try:
  304. os.makedirs(self.master_d_dir)
  305. except OSError as exc:
  306. if exc.errno != errno.EEXIST:
  307. raise
  308. self.conf = tempfile.NamedTemporaryFile(
  309. mode="w", suffix=".conf", dir=self.master_d_dir, delete=True,
  310. )
  311. self.base_env = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  312. self.addCleanup(shutil.rmtree, self.base_env)
  313. self.addCleanup(self.conf.close)
  314. for attr in ("timeout", "master_d_dir", "conf", "base_env"):
  315. self.addCleanup(delattr, self, attr)
  316. # Force a reload of the configuration now that our temp config file has
  317. # been removed.
  318. self.addCleanup(self.run_run_plus, "test.arg", __reload_config=True)
  319. def alarm_handler(self, signal, frame):
  320. raise Exception("Timeout of {0} seconds reached".format(self.timeout))
  321. def write_conf(self, data):
  322. """
  323. Dump the config dict to the conf file
  324. """
  325. self.conf.write(salt.utils.yaml.safe_dump(data, default_flow_style=False))
  326. self.conf.flush()
  327. @pytest.mark.expensive_test
  328. def test_jid_in_ret_event(self):
  329. """
  330. Test to confirm that the ret event for the orchestration contains the
  331. jid for the jobs spawned.
  332. """
  333. self.write_conf(
  334. {"fileserver_backend": ["roots"], "file_roots": {"base": [self.base_env]}}
  335. )
  336. state_sls = os.path.join(self.base_env, "test_state.sls")
  337. with salt.utils.files.fopen(state_sls, "w") as fp_:
  338. fp_.write(
  339. salt.utils.stringutils.to_str(
  340. textwrap.dedent(
  341. """
  342. date:
  343. cmd.run
  344. """
  345. )
  346. )
  347. )
  348. orch_sls = os.path.join(self.base_env, "test_orch.sls")
  349. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  350. fp_.write(
  351. salt.utils.stringutils.to_str(
  352. textwrap.dedent(
  353. """
  354. date_cmd:
  355. salt.state:
  356. - tgt: minion
  357. - sls: test_state
  358. ping_minion:
  359. salt.function:
  360. - name: test.ping
  361. - tgt: minion
  362. fileserver.file_list:
  363. salt.runner
  364. config.values:
  365. salt.wheel
  366. """
  367. )
  368. )
  369. )
  370. listener = salt.utils.event.get_event(
  371. "master",
  372. sock_dir=self.master_opts["sock_dir"],
  373. transport=self.master_opts["transport"],
  374. opts=self.master_opts,
  375. )
  376. jid = self.run_run_plus(
  377. "state.orchestrate", "test_orch", __reload_config=True
  378. ).get("jid")
  379. if jid is None:
  380. raise Exception("jid missing from run_run_plus output")
  381. signal.signal(signal.SIGALRM, self.alarm_handler)
  382. signal.alarm(self.timeout)
  383. try:
  384. while True:
  385. event = listener.get_event(full=True)
  386. if event is None:
  387. continue
  388. if event["tag"] == "salt/run/{0}/ret".format(jid):
  389. # Don't wrap this in a try/except. We want to know if the
  390. # data structure is different from what we expect!
  391. ret = event["data"]["return"]["data"]["master"]
  392. for job in ret:
  393. self.assertTrue("__jid__" in ret[job])
  394. break
  395. finally:
  396. del listener
  397. signal.alarm(0)
  398. @pytest.mark.expensive_test
  399. def test_parallel_orchestrations(self):
  400. """
  401. Test to confirm that the parallel state requisite works in orch
  402. we do this by running 10 test.sleep's of 10 seconds, and insure it only takes roughly 10s
  403. """
  404. self.write_conf(
  405. {"fileserver_backend": ["roots"], "file_roots": {"base": [self.base_env]}}
  406. )
  407. orch_sls = os.path.join(self.base_env, "test_par_orch.sls")
  408. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  409. fp_.write(
  410. textwrap.dedent(
  411. """
  412. {% for count in range(1, 20) %}
  413. sleep {{ count }}:
  414. module.run:
  415. - name: test.sleep
  416. - length: 10
  417. - parallel: True
  418. {% endfor %}
  419. sleep 21:
  420. module.run:
  421. - name: test.sleep
  422. - length: 10
  423. - parallel: True
  424. - require:
  425. - module: sleep 1
  426. """
  427. )
  428. )
  429. orch_sls = os.path.join(self.base_env, "test_par_orch.sls")
  430. listener = salt.utils.event.get_event(
  431. "master",
  432. sock_dir=self.master_opts["sock_dir"],
  433. transport=self.master_opts["transport"],
  434. opts=self.master_opts,
  435. )
  436. start_time = time.time()
  437. jid = self.run_run_plus(
  438. "state.orchestrate", "test_par_orch", __reload_config=True
  439. ).get("jid")
  440. if jid is None:
  441. raise Exception("jid missing from run_run_plus output")
  442. signal.signal(signal.SIGALRM, self.alarm_handler)
  443. signal.alarm(self.timeout)
  444. received = False
  445. try:
  446. while True:
  447. event = listener.get_event(full=True)
  448. if event is None:
  449. continue
  450. # if we receive the ret for this job before self.timeout (60),
  451. # the test is implicitly successful; if it were happening in serial it would be
  452. # atleast 110 seconds.
  453. if event["tag"] == "salt/run/{0}/ret".format(jid):
  454. received = True
  455. # Don't wrap this in a try/except. We want to know if the
  456. # data structure is different from what we expect!
  457. ret = event["data"]["return"]["data"]["master"]
  458. for state in ret:
  459. data = ret[state]
  460. # we expect each duration to be greater than 10s
  461. self.assertTrue(data["duration"] > 10000)
  462. break
  463. # self confirm that the total runtime is roughly 30s (left 10s for buffer)
  464. self.assertTrue((time.time() - start_time) < 40)
  465. finally:
  466. self.assertTrue(received)
  467. del listener
  468. signal.alarm(0)
  469. @pytest.mark.expensive_test
  470. def test_orchestration_soft_kill(self):
  471. """
  472. Test to confirm that the parallel state requisite works in orch
  473. we do this by running 10 test.sleep's of 10 seconds, and insure it only takes roughly 10s
  474. """
  475. self.write_conf(
  476. {"fileserver_backend": ["roots"], "file_roots": {"base": [self.base_env]}}
  477. )
  478. orch_sls = os.path.join(self.base_env, "two_stage_orch_kill.sls")
  479. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  480. fp_.write(
  481. textwrap.dedent(
  482. """
  483. stage_one:
  484. test.succeed_without_changes
  485. stage_two:
  486. test.fail_without_changes
  487. """
  488. )
  489. )
  490. listener = salt.utils.event.get_event(
  491. "master",
  492. sock_dir=self.master_opts["sock_dir"],
  493. transport=self.master_opts["transport"],
  494. opts=self.master_opts,
  495. )
  496. mock_jid = "20131219120000000000"
  497. self.run_run("state.soft_kill {0} stage_two".format(mock_jid))
  498. with patch("salt.utils.jid.gen_jid", MagicMock(return_value=mock_jid)):
  499. jid = self.run_run_plus(
  500. "state.orchestrate", "two_stage_orch_kill", __reload_config=True
  501. ).get("jid")
  502. if jid is None:
  503. raise Exception("jid missing from run_run_plus output")
  504. signal.signal(signal.SIGALRM, self.alarm_handler)
  505. signal.alarm(self.timeout)
  506. received = False
  507. try:
  508. while True:
  509. event = listener.get_event(full=True)
  510. if event is None:
  511. continue
  512. # Ensure that stage_two of the state does not run
  513. if event["tag"] == "salt/run/{0}/ret".format(jid):
  514. received = True
  515. # Don't wrap this in a try/except. We want to know if the
  516. # data structure is different from what we expect!
  517. ret = event["data"]["return"]["data"]["master"]
  518. self.assertNotIn(
  519. "test_|-stage_two_|-stage_two_|-fail_without_changes", ret
  520. )
  521. break
  522. finally:
  523. self.assertTrue(received)
  524. del listener
  525. signal.alarm(0)
  526. @pytest.mark.slow_test(seconds=10) # Test takes >5 and <=10 seconds
  527. def test_orchestration_with_pillar_dot_items(self):
  528. """
  529. Test to confirm when using a state file that includes other state file, if
  530. one of those state files includes pillar related functions that will not
  531. be pulling from the pillar cache that all the state files are available and
  532. the file_roots has been preserved. See issues #48277 and #46986.
  533. """
  534. self.write_conf(
  535. {"fileserver_backend": ["roots"], "file_roots": {"base": [self.base_env]}}
  536. )
  537. orch_sls = os.path.join(self.base_env, "main.sls")
  538. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  539. fp_.write(
  540. textwrap.dedent(
  541. """
  542. include:
  543. - one
  544. - two
  545. - three
  546. """
  547. )
  548. )
  549. orch_sls = os.path.join(self.base_env, "one.sls")
  550. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  551. fp_.write(
  552. textwrap.dedent(
  553. """
  554. {%- set foo = salt['saltutil.runner']('pillar.show_pillar') %}
  555. placeholder_one:
  556. test.succeed_without_changes
  557. """
  558. )
  559. )
  560. orch_sls = os.path.join(self.base_env, "two.sls")
  561. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  562. fp_.write(
  563. textwrap.dedent(
  564. """
  565. placeholder_two:
  566. test.succeed_without_changes
  567. """
  568. )
  569. )
  570. orch_sls = os.path.join(self.base_env, "three.sls")
  571. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  572. fp_.write(
  573. textwrap.dedent(
  574. """
  575. placeholder_three:
  576. test.succeed_without_changes
  577. """
  578. )
  579. )
  580. orch_sls = os.path.join(self.base_env, "main.sls")
  581. listener = salt.utils.event.get_event(
  582. "master",
  583. sock_dir=self.master_opts["sock_dir"],
  584. transport=self.master_opts["transport"],
  585. opts=self.master_opts,
  586. )
  587. jid = self.run_run_plus("state.orchestrate", "main", __reload_config=True).get(
  588. "jid"
  589. )
  590. if jid is None:
  591. raise salt.exceptions.SaltInvocationError(
  592. "jid missing from run_run_plus output"
  593. )
  594. signal.signal(signal.SIGALRM, self.alarm_handler)
  595. signal.alarm(self.timeout)
  596. received = False
  597. try:
  598. while True:
  599. event = listener.get_event(full=True)
  600. if event is None:
  601. continue
  602. if event.get("tag", "") == "salt/run/{0}/ret".format(jid):
  603. received = True
  604. # Don't wrap this in a try/except. We want to know if the
  605. # data structure is different from what we expect!
  606. ret = event["data"]["return"]["data"]["master"]
  607. for state in ret:
  608. data = ret[state]
  609. # Each state should be successful
  610. self.assertEqual(data["comment"], "Success!")
  611. break
  612. finally:
  613. self.assertTrue(received)
  614. del listener
  615. signal.alarm(0)
  616. @pytest.mark.slow_test(seconds=30) # Test takes >10 and <=30 seconds
  617. def test_orchestration_onchanges_and_prereq(self):
  618. """
  619. Test to confirm that the parallel state requisite works in orch
  620. we do this by running 10 test.sleep's of 10 seconds, and insure it only takes roughly 10s
  621. """
  622. self.write_conf(
  623. {"fileserver_backend": ["roots"], "file_roots": {"base": [self.base_env]}}
  624. )
  625. orch_sls = os.path.join(self.base_env, "orch.sls")
  626. with salt.utils.files.fopen(orch_sls, "w") as fp_:
  627. fp_.write(
  628. textwrap.dedent(
  629. """
  630. manage_a_file:
  631. salt.state:
  632. - tgt: minion
  633. - sls:
  634. - orch.req_test
  635. do_onchanges:
  636. salt.function:
  637. - tgt: minion
  638. - name: test.ping
  639. - onchanges:
  640. - salt: manage_a_file
  641. do_prereq:
  642. salt.function:
  643. - tgt: minion
  644. - name: test.ping
  645. - prereq:
  646. - salt: manage_a_file
  647. """
  648. )
  649. )
  650. listener = salt.utils.event.get_event(
  651. "master",
  652. sock_dir=self.master_opts["sock_dir"],
  653. transport=self.master_opts["transport"],
  654. opts=self.master_opts,
  655. )
  656. try:
  657. jid1 = self.run_run_plus(
  658. "state.orchestrate", "orch", test=True, __reload_config=True
  659. ).get("jid")
  660. # Run for real to create the file
  661. self.run_run_plus("state.orchestrate", "orch", __reload_config=True).get(
  662. "jid"
  663. )
  664. # Run again in test mode. Since there were no changes, the
  665. # requisites should not fire.
  666. jid2 = self.run_run_plus(
  667. "state.orchestrate", "orch", test=True, __reload_config=True
  668. ).get("jid")
  669. finally:
  670. try:
  671. os.remove(os.path.join(RUNTIME_VARS.TMP, "orch.req_test"))
  672. except OSError:
  673. pass
  674. assert jid1 is not None
  675. assert jid2 is not None
  676. tags = {"salt/run/{0}/ret".format(x): x for x in (jid1, jid2)}
  677. ret = {}
  678. signal.signal(signal.SIGALRM, self.alarm_handler)
  679. signal.alarm(self.timeout)
  680. try:
  681. while True:
  682. event = listener.get_event(full=True)
  683. if event is None:
  684. continue
  685. if event["tag"] in tags:
  686. ret[tags.pop(event["tag"])] = self.repack_state_returns(
  687. event["data"]["return"]["data"]["master"]
  688. )
  689. if not tags:
  690. # If tags is empty, we've grabbed all the returns we
  691. # wanted, so let's stop listening to the event bus.
  692. break
  693. finally:
  694. del listener
  695. signal.alarm(0)
  696. for sls_id in ("manage_a_file", "do_onchanges", "do_prereq"):
  697. # The first time through, all three states should have a None
  698. # result, while the second time through, they should all have a
  699. # True result.
  700. assert (
  701. ret[jid1][sls_id]["result"] is None
  702. ), "result of {0} ({1}) is not None".format(
  703. sls_id, ret[jid1][sls_id]["result"]
  704. )
  705. assert (
  706. ret[jid2][sls_id]["result"] is True
  707. ), "result of {0} ({1}) is not True".format(
  708. sls_id, ret[jid2][sls_id]["result"]
  709. )
  710. # The file.managed state should have shown changes in the test mode
  711. # return data.
  712. assert ret[jid1]["manage_a_file"]["changes"]
  713. # After the file was created, running again in test mode should have
  714. # shown no changes.
  715. assert not ret[jid2]["manage_a_file"]["changes"], ret[jid2]["manage_a_file"][
  716. "changes"
  717. ]