test_saltmod.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
  4. '''
  5. # Import Python libs
  6. from __future__ import absolute_import, unicode_literals, print_function
  7. import os
  8. import time
  9. import tempfile
  10. # Import Salt Testing Libs
  11. from tests.support.runtests import RUNTIME_VARS
  12. from tests.support.mixins import LoaderModuleMockMixin
  13. from tests.support.unit import TestCase
  14. from tests.support.mock import (
  15. MagicMock,
  16. patch
  17. )
  18. # Import Salt Libs
  19. import salt.config
  20. import salt.loader
  21. import salt.utils.jid
  22. import salt.utils.event
  23. import salt.states.saltmod as saltmod
  24. class SaltmodTestCase(TestCase, LoaderModuleMockMixin):
  25. '''
  26. Test cases for salt.states.saltmod
  27. '''
  28. def setup_loader_modules(self):
  29. utils = salt.loader.utils(
  30. salt.config.DEFAULT_MINION_OPTS.copy(),
  31. whitelist=['state']
  32. )
  33. return {
  34. saltmod: {
  35. '__env__': 'base',
  36. '__opts__': {
  37. '__role': 'master',
  38. 'file_client': 'remote',
  39. 'sock_dir': tempfile.mkdtemp(dir=RUNTIME_VARS.TMP),
  40. 'transport': 'tcp'
  41. },
  42. '__salt__': {'saltutil.cmd': MagicMock()},
  43. '__orchestration_jid__': salt.utils.jid.gen_jid({}),
  44. '__utils__': utils,
  45. }
  46. }
  47. # 'state' function tests: 1
  48. def test_state(self):
  49. '''
  50. Test to invoke a state run on a given target
  51. '''
  52. name = 'state'
  53. tgt = 'minion1'
  54. comt = ('Passed invalid value for \'allow_fail\', must be an int')
  55. ret = {'name': name,
  56. 'changes': {},
  57. 'result': False,
  58. 'comment': comt}
  59. test_ret = {'name': name,
  60. 'changes': {},
  61. 'result': True,
  62. 'comment': 'States ran successfully.'
  63. }
  64. test_batch_return = {
  65. 'minion1': {
  66. 'ret': {
  67. 'test_|-notify_me_|-this is a name_|-show_notification': {
  68. 'comment': 'Notify me',
  69. 'name': 'this is a name',
  70. 'start_time': '10:43:41.487565',
  71. 'result': True,
  72. 'duration': 0.35,
  73. '__run_num__': 0,
  74. '__sls__': 'demo',
  75. 'changes': {},
  76. '__id__': 'notify_me'
  77. },
  78. 'retcode': 0
  79. },
  80. 'out': 'highstate'
  81. },
  82. 'minion2': {
  83. 'ret': {
  84. 'test_|-notify_me_|-this is a name_|-show_notification': {
  85. 'comment': 'Notify me',
  86. 'name': 'this is a name',
  87. 'start_time': '10:43:41.487565',
  88. 'result': True,
  89. 'duration': 0.35,
  90. '__run_num__': 0,
  91. '__sls__': 'demo',
  92. 'changes': {},
  93. '__id__': 'notify_me'
  94. },
  95. 'retcode': 0
  96. },
  97. 'out': 'highstate'
  98. },
  99. 'minion3': {
  100. 'ret': {
  101. 'test_|-notify_me_|-this is a name_|-show_notification': {
  102. 'comment': 'Notify me',
  103. 'name': 'this is a name',
  104. 'start_time': '10:43:41.487565',
  105. 'result': True,
  106. 'duration': 0.35,
  107. '__run_num__': 0,
  108. '__sls__': 'demo',
  109. 'changes': {},
  110. '__id__': 'notify_me'
  111. },
  112. 'retcode': 0
  113. },
  114. 'out': 'highstate'
  115. }
  116. }
  117. self.assertDictEqual(saltmod.state(name, tgt, allow_fail='a'), ret)
  118. comt = ('No highstate or sls specified, no execution made')
  119. ret.update({'comment': comt})
  120. self.assertDictEqual(saltmod.state(name, tgt), ret)
  121. comt = ("Must pass in boolean for value of 'concurrent'")
  122. ret.update({'comment': comt})
  123. self.assertDictEqual(saltmod.state(name, tgt, highstate=True,
  124. concurrent='a'), ret)
  125. ret.update({'comment': comt, 'result': None})
  126. with patch.dict(saltmod.__opts__, {'test': True}):
  127. self.assertDictEqual(saltmod.state(name, tgt, highstate=True), test_ret)
  128. ret.update({'comment': 'States ran successfully. No changes made to silver.', 'result': True, '__jid__': '20170406104341210934'})
  129. with patch.dict(saltmod.__opts__, {'test': False}):
  130. mock = MagicMock(return_value={'silver': {'jid': '20170406104341210934', 'retcode': 0, 'ret': {'test_|-notify_me_|-this is a name_|-show_notification': {'comment': 'Notify me', 'name': 'this is a name', 'start_time': '10:43:41.487565', 'result': True, 'duration': 0.35, '__run_num__': 0, '__sls__': 'demo', 'changes': {}, '__id__': 'notify_me'}}, 'out': 'highstate'}})
  131. with patch.dict(saltmod.__salt__, {'saltutil.cmd': mock}):
  132. self.assertDictEqual(saltmod.state(name, tgt, highstate=True), ret)
  133. ret.update({'comment': 'States ran successfully. No changes made to minion1, minion3, minion2.'})
  134. del ret['__jid__']
  135. with patch.dict(saltmod.__opts__, {'test': False}):
  136. with patch.dict(saltmod.__salt__, {'saltutil.cmd': MagicMock(return_value=test_batch_return)}):
  137. state_run = saltmod.state(name, tgt, highstate=True)
  138. # Test return without checking the comment contents. Comments are tested later.
  139. comment = state_run.pop('comment')
  140. ret.pop('comment')
  141. self.assertDictEqual(state_run, ret)
  142. # Check the comment contents in a non-order specific way (ordering fails sometimes on PY3)
  143. self.assertIn('States ran successfully. No changes made to', comment)
  144. for minion in ['minion1', 'minion2', 'minion3']:
  145. self.assertIn(minion, comment)
  146. # 'function' function tests: 1
  147. def test_function(self):
  148. '''
  149. Test to execute a single module function on a remote
  150. minion via salt or salt-ssh
  151. '''
  152. name = 'state'
  153. tgt = 'larry'
  154. ret = {'name': name,
  155. 'changes': {},
  156. 'result': None,
  157. 'comment': 'Function state would be executed '
  158. 'on target {0}'.format(tgt)}
  159. with patch.dict(saltmod.__opts__, {'test': True}):
  160. self.assertDictEqual(saltmod.function(name, tgt), ret)
  161. ret.update({'result': True,
  162. 'changes': {'out': 'highstate', 'ret': {tgt: ''}},
  163. 'comment': 'Function ran successfully.'
  164. ' Function state ran on {0}.'.format(tgt)})
  165. with patch.dict(saltmod.__opts__, {'test': False}):
  166. mock_ret = {'larry': {'ret': '', 'retcode': 0, 'failed': False}}
  167. mock_cmd = MagicMock(return_value=mock_ret)
  168. with patch.dict(saltmod.__salt__, {'saltutil.cmd': mock_cmd}):
  169. self.assertDictEqual(saltmod.function(name, tgt), ret)
  170. # 'wait_for_event' function tests: 1
  171. def test_wait_for_event(self):
  172. '''
  173. Test to watch Salt's event bus and block until a condition is met
  174. '''
  175. name = 'state'
  176. tgt = 'minion1'
  177. comt = ('Timeout value reached.')
  178. ret = {'name': name,
  179. 'changes': {},
  180. 'result': False,
  181. 'comment': comt}
  182. class Mockevent(object):
  183. '''
  184. Mock event class
  185. '''
  186. flag = None
  187. def __init__(self):
  188. self.full = None
  189. def get_event(self, full):
  190. '''
  191. Mock get_event method
  192. '''
  193. self.full = full
  194. if self.flag:
  195. return {'tag': name, 'data': {}}
  196. return None
  197. with patch.object(salt.utils.event, 'get_event',
  198. MagicMock(return_value=Mockevent())):
  199. with patch.dict(saltmod.__opts__, {'sock_dir': True,
  200. 'transport': True}):
  201. with patch.object(time, 'time', MagicMock(return_value=1.0)):
  202. self.assertDictEqual(saltmod.wait_for_event(name, 'salt',
  203. timeout=-1.0),
  204. ret)
  205. Mockevent.flag = True
  206. ret.update({'comment': 'All events seen in 0.0 seconds.',
  207. 'result': True})
  208. self.assertDictEqual(saltmod.wait_for_event(name, ''), ret)
  209. ret.update({'comment': 'Timeout value reached.',
  210. 'result': False})
  211. self.assertDictEqual(saltmod.wait_for_event(name, tgt,
  212. timeout=-1.0),
  213. ret)
  214. # 'runner' function tests: 1
  215. def test_runner(self):
  216. '''
  217. Test to execute a runner module on the master
  218. '''
  219. name = 'state'
  220. ret = {'changes': {'return': True}, 'name': 'state', 'result': True,
  221. 'comment': 'Runner function \'state\' executed.',
  222. '__orchestration__': True}
  223. runner_mock = MagicMock(return_value={'return': True})
  224. with patch.dict(saltmod.__salt__, {'saltutil.runner': runner_mock}):
  225. self.assertDictEqual(saltmod.runner(name), ret)
  226. # 'wheel' function tests: 1
  227. def test_wheel(self):
  228. '''
  229. Test to execute a wheel module on the master
  230. '''
  231. name = 'state'
  232. ret = {'changes': {'return': True}, 'name': 'state', 'result': True,
  233. 'comment': 'Wheel function \'state\' executed.',
  234. '__orchestration__': True}
  235. wheel_mock = MagicMock(return_value={'return': True})
  236. with patch.dict(saltmod.__salt__, {'saltutil.wheel': wheel_mock}):
  237. self.assertDictEqual(saltmod.wheel(name), ret)
  238. def test_state_ssh(self):
  239. '''
  240. Test saltmod passes roster to saltutil.cmd
  241. '''
  242. origcmd = saltmod.__salt__['saltutil.cmd']
  243. cmd_kwargs = {}
  244. cmd_args = []
  245. def cmd_mock(*args, **kwargs):
  246. cmd_args.extend(args)
  247. cmd_kwargs.update(kwargs)
  248. return origcmd(*args, **kwargs)
  249. with patch.dict(saltmod.__salt__, {'saltutil.cmd': cmd_mock}):
  250. ret = saltmod.state('state.sls', tgt='*', ssh=True, highstate=True, roster='my_roster')
  251. assert 'roster' in cmd_kwargs
  252. assert cmd_kwargs['roster'] == 'my_roster'
  253. class StatemodTests(TestCase, LoaderModuleMockMixin):
  254. def setup_loader_modules(self):
  255. self.tmp_cachedir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  256. return {
  257. saltmod: {
  258. '__env__': 'base',
  259. '__opts__': {
  260. 'id': 'webserver2',
  261. 'argv': [],
  262. '__role': 'master',
  263. 'cachedir': self.tmp_cachedir,
  264. 'extension_modules': os.path.join(self.tmp_cachedir, 'extmods'),
  265. },
  266. '__salt__': {'saltutil.cmd': MagicMock()},
  267. '__orchestration_jid__': salt.utils.jid.gen_jid({})
  268. }
  269. }
  270. def test_statemod_state(self):
  271. ''' Smoke test for for salt.states.statemod.state(). Ensures that we
  272. don't take an exception if optional parameters are not specified in
  273. __opts__ or __env__.
  274. '''
  275. args = ('webserver_setup', 'webserver2')
  276. kwargs = {
  277. 'tgt_type': 'glob',
  278. 'fail_minions': None,
  279. 'pillar': None,
  280. 'top': None,
  281. 'batch': None,
  282. 'orchestration_jid': None,
  283. 'sls': 'vroom',
  284. 'queue': False,
  285. 'concurrent': False,
  286. 'highstate': None,
  287. 'expr_form': None,
  288. 'ret': '',
  289. 'ssh': False,
  290. 'timeout': None, 'test': False,
  291. 'allow_fail': 0,
  292. 'saltenv': None,
  293. 'expect_minions': False
  294. }
  295. ret = saltmod.state(*args, **kwargs)
  296. expected = {
  297. 'comment': 'States ran successfully.',
  298. 'changes': {},
  299. 'name': 'webserver_setup',
  300. 'result': True
  301. }
  302. self.assertEqual(ret, expected)