test_state.py 27 KB

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