test_state.py 27 KB

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