1
0

test_state.py 27 KB

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