1
0

test_state.py 26 KB

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