test_minions.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, unicode_literals
  4. import sys
  5. # Import Salt Libs
  6. import salt.utils.minions
  7. # Import Salt Testing Libs
  8. from tests.support.unit import TestCase, skipIf
  9. from tests.support.mock import (
  10. patch,
  11. MagicMock,
  12. )
  13. NODEGROUPS = {
  14. 'group1': 'L@host1,host2,host3',
  15. 'group2': ['G@foo:bar', 'or', 'web1*'],
  16. 'group3': ['N@group1', 'or', 'N@group2'],
  17. 'group4': ['host4', 'host5', 'host6'],
  18. 'group5': 'N@group4',
  19. 'group6': 'N@group3',
  20. 'group7': ['host1']
  21. }
  22. EXPECTED = {
  23. 'group1': ['L@host1,host2,host3'],
  24. 'group2': ['G@foo:bar', 'or', 'web1*'],
  25. 'group3': ['(', '(', 'L@host1,host2,host3', ')', 'or', '(', 'G@foo:bar', 'or', 'web1*', ')', ')'],
  26. 'group4': ['L@host4,host5,host6'],
  27. 'group5': ['(', 'L@host4,host5,host6', ')'],
  28. 'group6': ['(', '(', '(', 'L@host1,host2,host3', ')', 'or', '(',
  29. 'G@foo:bar', 'or', 'web1*', ')', ')', ')'],
  30. 'group7': ['L@host1']
  31. }
  32. class MinionsTestCase(TestCase):
  33. '''
  34. TestCase for salt.utils.minions module functions
  35. '''
  36. def test_nodegroup_comp(self):
  37. '''
  38. Test a simple string nodegroup
  39. '''
  40. for nodegroup in NODEGROUPS:
  41. expected = EXPECTED[nodegroup]
  42. ret = salt.utils.minions.nodegroup_comp(nodegroup, NODEGROUPS)
  43. self.assertEqual(ret, expected)
  44. class CkMinionsTestCase(TestCase):
  45. '''
  46. TestCase for salt.utils.minions.CkMinions class
  47. '''
  48. def setUp(self):
  49. self.ckminions = salt.utils.minions.CkMinions({'minion_data_cache': True})
  50. def test_spec_check(self):
  51. # Test spec-only rule
  52. auth_list = ['@runner']
  53. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  54. self.assertTrue(ret)
  55. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'wheel')
  56. self.assertFalse(ret)
  57. ret = self.ckminions.spec_check(auth_list, 'testarg', {}, 'runner')
  58. mock_ret = {'error': {'name': 'SaltInvocationError',
  59. 'message': 'A command invocation error occurred: Check syntax.'}}
  60. self.assertDictEqual(mock_ret, ret)
  61. # Test spec in plural form
  62. auth_list = ['@runners']
  63. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  64. self.assertTrue(ret)
  65. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'wheel')
  66. self.assertFalse(ret)
  67. # Test spec with module.function restriction
  68. auth_list = [{'@runner': 'test.arg'}]
  69. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  70. self.assertTrue(ret)
  71. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'wheel')
  72. self.assertFalse(ret)
  73. ret = self.ckminions.spec_check(auth_list, 'tes.arg', {}, 'runner')
  74. self.assertFalse(ret)
  75. ret = self.ckminions.spec_check(auth_list, 'test.ar', {}, 'runner')
  76. self.assertFalse(ret)
  77. # Test function name is a regex
  78. auth_list = [{'@runner': 'test.arg.*some'}]
  79. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  80. self.assertFalse(ret)
  81. ret = self.ckminions.spec_check(auth_list, 'test.argsome', {}, 'runner')
  82. self.assertTrue(ret)
  83. ret = self.ckminions.spec_check(auth_list, 'test.arg_aaa_some', {}, 'runner')
  84. self.assertTrue(ret)
  85. # Test a list of funcs
  86. auth_list = [{'@runner': ['test.arg', 'jobs.active']}]
  87. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  88. self.assertTrue(ret)
  89. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'runner')
  90. self.assertTrue(ret)
  91. ret = self.ckminions.spec_check(auth_list, 'test.active', {}, 'runner')
  92. self.assertFalse(ret)
  93. ret = self.ckminions.spec_check(auth_list, 'jobs.arg', {}, 'runner')
  94. self.assertFalse(ret)
  95. # Test args-kwargs rules
  96. auth_list = [{
  97. '@runner': {
  98. 'test.arg': {
  99. 'args': ['1', '2'],
  100. 'kwargs': {
  101. 'aaa': 'bbb',
  102. 'ccc': 'ddd'
  103. }
  104. }
  105. }
  106. }]
  107. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  108. self.assertFalse(ret)
  109. args = {
  110. 'arg': ['1', '2'],
  111. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  112. }
  113. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  114. self.assertTrue(ret)
  115. args = {
  116. 'arg': ['1', '2', '3'],
  117. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  118. }
  119. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  120. self.assertTrue(ret)
  121. args = {
  122. 'arg': ['1', '2'],
  123. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd', 'zzz': 'zzz'}
  124. }
  125. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  126. self.assertTrue(ret)
  127. args = {
  128. 'arg': ['1', '2'],
  129. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddc'}
  130. }
  131. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  132. self.assertFalse(ret)
  133. args = {
  134. 'arg': ['1', '2'],
  135. 'kwarg': {'aaa': 'bbb'}
  136. }
  137. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  138. self.assertFalse(ret)
  139. args = {
  140. 'arg': ['1', '3'],
  141. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  142. }
  143. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  144. self.assertFalse(ret)
  145. args = {
  146. 'arg': ['1'],
  147. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  148. }
  149. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  150. self.assertFalse(ret)
  151. args = {
  152. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  153. }
  154. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  155. self.assertFalse(ret)
  156. args = {
  157. 'arg': ['1', '2'],
  158. }
  159. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  160. self.assertFalse(ret)
  161. # Test kwargs only
  162. auth_list = [{
  163. '@runner': {
  164. 'test.arg': {
  165. 'kwargs': {
  166. 'aaa': 'bbb',
  167. 'ccc': 'ddd'
  168. }
  169. }
  170. }
  171. }]
  172. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  173. self.assertFalse(ret)
  174. args = {
  175. 'arg': ['1', '2'],
  176. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  177. }
  178. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  179. self.assertTrue(ret)
  180. # Test args only
  181. auth_list = [{
  182. '@runner': {
  183. 'test.arg': {
  184. 'args': ['1', '2']
  185. }
  186. }
  187. }]
  188. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  189. self.assertFalse(ret)
  190. args = {
  191. 'arg': ['1', '2'],
  192. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  193. }
  194. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  195. self.assertTrue(ret)
  196. # Test list of args
  197. auth_list = [{'@runner': [{'test.arg': [{'args': ['1', '2'],
  198. 'kwargs': {'aaa': 'bbb',
  199. 'ccc': 'ddd'
  200. }
  201. },
  202. {'args': ['2', '3'],
  203. 'kwargs': {'aaa': 'aaa',
  204. 'ccc': 'ccc'
  205. }
  206. }]
  207. }]
  208. }]
  209. args = {
  210. 'arg': ['1', '2'],
  211. 'kwarg': {'aaa': 'bbb', 'ccc': 'ddd'}
  212. }
  213. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  214. self.assertTrue(ret)
  215. args = {
  216. 'arg': ['2', '3'],
  217. 'kwarg': {'aaa': 'aaa', 'ccc': 'ccc'}
  218. }
  219. ret = self.ckminions.spec_check(auth_list, 'test.arg', args, 'runner')
  220. self.assertTrue(ret)
  221. # Test @module form
  222. auth_list = ['@jobs']
  223. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'runner')
  224. self.assertTrue(ret)
  225. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'wheel')
  226. self.assertTrue(ret)
  227. ret = self.ckminions.spec_check(auth_list, 'test.arg', {}, 'runner')
  228. self.assertFalse(ret)
  229. ret = self.ckminions.spec_check(auth_list, 'job.arg', {}, 'runner')
  230. self.assertFalse(ret)
  231. # Test @module: function
  232. auth_list = [{'@jobs': 'active'}]
  233. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'runner')
  234. self.assertTrue(ret)
  235. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'wheel')
  236. self.assertTrue(ret)
  237. ret = self.ckminions.spec_check(auth_list, 'jobs.active_jobs', {}, 'runner')
  238. self.assertTrue(ret)
  239. ret = self.ckminions.spec_check(auth_list, 'jobs.activ', {}, 'runner')
  240. self.assertFalse(ret)
  241. # Test @module: [functions]
  242. auth_list = [{'@jobs': ['active', 'li']}]
  243. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'runner')
  244. self.assertTrue(ret)
  245. ret = self.ckminions.spec_check(auth_list, 'jobs.list_jobs', {}, 'runner')
  246. self.assertTrue(ret)
  247. ret = self.ckminions.spec_check(auth_list, 'jobs.last_run', {}, 'runner')
  248. self.assertFalse(ret)
  249. # Test @module: function with args
  250. auth_list = [{'@jobs': {'active': {'args': ['1', '2'],
  251. 'kwargs': {'a': 'b', 'c': 'd'}}}}]
  252. args = {'arg': ['1', '2'],
  253. 'kwarg': {'a': 'b', 'c': 'd'}}
  254. ret = self.ckminions.spec_check(auth_list, 'jobs.active', args, 'runner')
  255. self.assertTrue(ret)
  256. ret = self.ckminions.spec_check(auth_list, 'jobs.active', {}, 'runner')
  257. self.assertFalse(ret)
  258. @patch('salt.utils.minions.CkMinions._pki_minions', MagicMock(return_value=['alpha', 'beta', 'gamma']))
  259. def test_auth_check(self):
  260. # Test function-only rule
  261. auth_list = ['test.ping']
  262. ret = self.ckminions.auth_check(auth_list, 'test.ping', None, 'alpha')
  263. self.assertTrue(ret)
  264. ret = self.ckminions.auth_check(auth_list, 'test.arg', None, 'alpha')
  265. self.assertFalse(ret)
  266. # Test minion and function
  267. auth_list = [{'alpha': 'test.ping'}]
  268. ret = self.ckminions.auth_check(auth_list, 'test.ping', None, 'alpha')
  269. self.assertTrue(ret)
  270. ret = self.ckminions.auth_check(auth_list, 'test.arg', None, 'alpha')
  271. self.assertFalse(ret)
  272. ret = self.ckminions.auth_check(auth_list, 'test.ping', None, 'beta')
  273. self.assertFalse(ret)
  274. # Test function list
  275. auth_list = [{'*': ['test.*', 'saltutil.cmd']}]
  276. ret = self.ckminions.auth_check(auth_list, 'test.arg', None, 'alpha')
  277. self.assertTrue(ret)
  278. ret = self.ckminions.auth_check(auth_list, 'test.ping', None, 'beta')
  279. self.assertTrue(ret)
  280. ret = self.ckminions.auth_check(auth_list, 'saltutil.cmd', None, 'gamma')
  281. self.assertTrue(ret)
  282. ret = self.ckminions.auth_check(auth_list, 'saltutil.running', None, 'gamma')
  283. self.assertFalse(ret)
  284. # Test an args and kwargs rule
  285. auth_list = [{
  286. 'alpha': {
  287. 'test.arg': {
  288. 'args': ['1', '2'],
  289. 'kwargs': {
  290. 'aaa': 'bbb',
  291. 'ccc': 'ddd'
  292. }
  293. }
  294. }
  295. }]
  296. ret = self.ckminions.auth_check(auth_list, 'test.arg', None, 'runner')
  297. self.assertFalse(ret)
  298. ret = self.ckminions.auth_check(auth_list, 'test.arg', [], 'runner')
  299. self.assertFalse(ret)
  300. args = ['1', '2', {'aaa': 'bbb', 'ccc': 'ddd', '__kwarg__': True}]
  301. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  302. self.assertTrue(ret)
  303. args = ['1', '2', '3', {'aaa': 'bbb', 'ccc': 'ddd', 'eee': 'fff', '__kwarg__': True}]
  304. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  305. self.assertTrue(ret)
  306. args = ['1', {'aaa': 'bbb', 'ccc': 'ddd', '__kwarg__': True}]
  307. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  308. self.assertFalse(ret)
  309. args = ['1', '2', {'aaa': 'bbb', '__kwarg__': True}]
  310. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  311. self.assertFalse(ret)
  312. args = ['1', '3', {'aaa': 'bbb', 'ccc': 'ddd', '__kwarg__': True}]
  313. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  314. self.assertFalse(ret)
  315. args = ['1', '2', {'aaa': 'bbb', 'ccc': 'fff', '__kwarg__': True}]
  316. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  317. self.assertFalse(ret)
  318. # Test kwargs only rule
  319. auth_list = [{
  320. 'alpha': {
  321. 'test.arg': {
  322. 'kwargs': {
  323. 'aaa': 'bbb',
  324. 'ccc': 'ddd'
  325. }
  326. }
  327. }
  328. }]
  329. args = ['1', '2', {'aaa': 'bbb', 'ccc': 'ddd', '__kwarg__': True}]
  330. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  331. self.assertTrue(ret)
  332. args = [{'aaa': 'bbb', 'ccc': 'ddd', 'eee': 'fff', '__kwarg__': True}]
  333. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  334. self.assertTrue(ret)
  335. # Test args only rule
  336. auth_list = [{
  337. 'alpha': {
  338. 'test.arg': {
  339. 'args': ['1', '2'],
  340. }
  341. }
  342. }]
  343. args = ['1', '2', {'aaa': 'bbb', 'ccc': 'ddd', '__kwarg__': True}]
  344. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  345. self.assertTrue(ret)
  346. args = ['1', '2']
  347. ret = self.ckminions.auth_check(auth_list, 'test.arg', args, 'runner')
  348. self.assertTrue(ret)
  349. @skipIf(sys.version_info < (2, 7), 'Python 2.7 needed for dictionary equality assertions')
  350. class TargetParseTestCase(TestCase):
  351. def test_parse_grains_target(self):
  352. '''
  353. Ensure proper parsing for grains
  354. '''
  355. g_tgt = 'G@a:b'
  356. ret = salt.utils.minions.parse_target(g_tgt)
  357. self.assertDictEqual(ret, {'engine': 'G', 'pattern': 'a:b', 'delimiter': None})
  358. def test_parse_grains_pcre_target(self):
  359. '''
  360. Ensure proper parsing for grains PCRE matching
  361. '''
  362. p_tgt = 'P@a:b'
  363. ret = salt.utils.minions.parse_target(p_tgt)
  364. self.assertDictEqual(ret, {'engine': 'P', 'pattern': 'a:b', 'delimiter': None})
  365. def test_parse_pillar_pcre_target(self):
  366. '''
  367. Ensure proper parsing for pillar PCRE matching
  368. '''
  369. j_tgt = 'J@a:b'
  370. ret = salt.utils.minions.parse_target(j_tgt)
  371. self.assertDictEqual(ret, {'engine': 'J', 'pattern': 'a:b', 'delimiter': None})
  372. def test_parse_list_target(self):
  373. '''
  374. Ensure proper parsing for list matching
  375. '''
  376. l_tgt = 'L@a:b'
  377. ret = salt.utils.minions.parse_target(l_tgt)
  378. self.assertDictEqual(ret, {'engine': 'L', 'pattern': 'a:b', 'delimiter': None})
  379. def test_parse_nodegroup_target(self):
  380. '''
  381. Ensure proper parsing for pillar matching
  382. '''
  383. n_tgt = 'N@a:b'
  384. ret = salt.utils.minions.parse_target(n_tgt)
  385. self.assertDictEqual(ret, {'engine': 'N', 'pattern': 'a:b', 'delimiter': None})
  386. def test_parse_subnet_target(self):
  387. '''
  388. Ensure proper parsing for subnet matching
  389. '''
  390. s_tgt = 'S@a:b'
  391. ret = salt.utils.minions.parse_target(s_tgt)
  392. self.assertDictEqual(ret, {'engine': 'S', 'pattern': 'a:b', 'delimiter': None})
  393. def test_parse_minion_pcre_target(self):
  394. '''
  395. Ensure proper parsing for minion PCRE matching
  396. '''
  397. e_tgt = 'E@a:b'
  398. ret = salt.utils.minions.parse_target(e_tgt)
  399. self.assertDictEqual(ret, {'engine': 'E', 'pattern': 'a:b', 'delimiter': None})
  400. def test_parse_range_target(self):
  401. '''
  402. Ensure proper parsing for range matching
  403. '''
  404. r_tgt = 'R@a:b'
  405. ret = salt.utils.minions.parse_target(r_tgt)
  406. self.assertDictEqual(ret, {'engine': 'R', 'pattern': 'a:b', 'delimiter': None})
  407. def test_parse_multiword_target(self):
  408. '''
  409. Ensure proper parsing for multi-word targets
  410. Refs https://github.com/saltstack/salt/issues/37231
  411. '''
  412. mw_tgt = 'G@a:b c'
  413. ret = salt.utils.minions.parse_target(mw_tgt)
  414. self.assertEqual(ret['pattern'], 'a:b c')
  415. class NodegroupCompTest(TestCase):
  416. '''
  417. Test nodegroup comparisons found in
  418. salt.utils.minions.nodgroup_comp()
  419. '''
  420. def test_simple_nodegroup(self):
  421. '''
  422. Smoke test a very simple nodegroup. No recursion.
  423. '''
  424. simple_nodegroup = {'group1': 'L@foo.domain.com,bar.domain.com,baz.domain.com or bl*.domain.com'}
  425. ret = salt.utils.minions.nodegroup_comp('group1', simple_nodegroup)
  426. expected_ret = ['L@foo.domain.com,bar.domain.com,baz.domain.com', 'or', 'bl*.domain.com']
  427. self.assertListEqual(ret, expected_ret)
  428. def test_simple_expression_nodegroup(self):
  429. '''
  430. Smoke test a nodegroup with a simple expression. No recursion.
  431. '''
  432. simple_nodegroup = {'group1': '[foo,bar,baz].domain.com'}
  433. ret = salt.utils.minions.nodegroup_comp('group1', simple_nodegroup)
  434. expected_ret = ['E@[foo,bar,baz].domain.com']
  435. self.assertListEqual(ret, expected_ret)
  436. def test_simple_recurse(self):
  437. '''
  438. Test a case where one nodegroup contains a second nodegroup
  439. '''
  440. referenced_nodegroups = {
  441. 'group1': 'L@foo.domain.com,bar.domain.com,baz.domain.com or bl*.domain.com',
  442. 'group2': 'G@os:Debian and N@group1'
  443. }
  444. ret = salt.utils.minions.nodegroup_comp('group2', referenced_nodegroups)
  445. expected_ret = [
  446. '(',
  447. 'G@os:Debian',
  448. 'and',
  449. '(',
  450. 'L@foo.domain.com,bar.domain.com,baz.domain.com',
  451. 'or',
  452. 'bl*.domain.com',
  453. ')',
  454. ')'
  455. ]
  456. self.assertListEqual(ret, expected_ret)
  457. def test_circular_nodegroup_reference(self):
  458. '''
  459. Test to see what happens if A refers to B
  460. and B in turn refers back to A
  461. '''
  462. referenced_nodegroups = {
  463. 'group1': 'N@group2',
  464. 'group2': 'N@group1'
  465. }
  466. # If this works, it should also print an error to the console
  467. ret = salt.utils.minions.nodegroup_comp('group1', referenced_nodegroups)
  468. self.assertEqual(ret, [])