test_minions.py 19 KB

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