test_host.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Rahul Handay <rahulha@saltstack.com>
  4. '''
  5. # Import Python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. # Import Salt Libs
  8. import salt.states.host as host
  9. # Import Salt Testing Libs
  10. from tests.support.mixins import LoaderModuleMockMixin
  11. from tests.support.unit import TestCase
  12. from tests.support.mock import (
  13. MagicMock,
  14. call,
  15. patch,
  16. )
  17. class HostTestCase(TestCase, LoaderModuleMockMixin):
  18. '''
  19. Validate the host state
  20. '''
  21. hostname = 'salt'
  22. localhost_ip = '127.0.0.1'
  23. ip_list = ['203.0.113.113', '203.0.113.14']
  24. default_hosts = {
  25. ip_list[0]: [hostname],
  26. ip_list[1]: [hostname],
  27. }
  28. def setUp(self):
  29. self.add_host_mock = MagicMock(return_value=True)
  30. self.rm_host_mock = MagicMock(return_value=True)
  31. self.list_hosts_mock = MagicMock(return_value=self.default_hosts)
  32. def setup_loader_modules(self):
  33. return {
  34. host: {
  35. '__opts__': {
  36. 'test': False,
  37. },
  38. },
  39. }
  40. def test_present(self):
  41. '''
  42. Test to ensures that the named host is present with the given ip
  43. '''
  44. add_host = MagicMock(return_value=True)
  45. rm_host = MagicMock(return_value=True)
  46. hostname = 'salt'
  47. ip_str = '127.0.0.1'
  48. ip_list = ['10.1.2.3', '10.4.5.6']
  49. # Case 1: No match for hostname. Single IP address passed to the state.
  50. list_hosts = MagicMock(return_value={
  51. '127.0.0.1': ['localhost'],
  52. })
  53. with patch.dict(host.__salt__,
  54. {'hosts.list_hosts': list_hosts,
  55. 'hosts.add_host': add_host,
  56. 'hosts.rm_host': rm_host}):
  57. ret = host.present(hostname, ip_str)
  58. assert ret['result'] is True
  59. assert ret['comment'] == 'Added host {0} ({1})'.format(hostname, ip_str), ret['comment']
  60. assert ret['changes'] == {
  61. 'added': {
  62. ip_str: [hostname],
  63. }
  64. }, ret['changes']
  65. expected = [call(ip_str, hostname)]
  66. assert add_host.mock_calls == expected, add_host.mock_calls
  67. assert rm_host.mock_calls == [], rm_host.mock_calls
  68. # Case 2: No match for hostname. Multiple IP addresses passed to the
  69. # state.
  70. list_hosts = MagicMock(return_value={
  71. '127.0.0.1': ['localhost'],
  72. })
  73. add_host.reset_mock()
  74. rm_host.reset_mock()
  75. with patch.dict(host.__salt__,
  76. {'hosts.list_hosts': list_hosts,
  77. 'hosts.add_host': add_host,
  78. 'hosts.rm_host': rm_host}):
  79. ret = host.present(hostname, ip_list)
  80. assert ret['result'] is True
  81. assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
  82. assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
  83. assert ret['changes'] == {
  84. 'added': {
  85. ip_list[0]: [hostname],
  86. ip_list[1]: [hostname],
  87. }
  88. }, ret['changes']
  89. expected = sorted([call(x, hostname) for x in ip_list])
  90. assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
  91. assert rm_host.mock_calls == [], rm_host.mock_calls
  92. # Case 3: Match for hostname, but no matching IP. Single IP address
  93. # passed to the state.
  94. list_hosts = MagicMock(return_value={
  95. '127.0.0.1': ['localhost'],
  96. ip_list[0]: [hostname],
  97. })
  98. add_host.reset_mock()
  99. rm_host.reset_mock()
  100. with patch.dict(host.__salt__,
  101. {'hosts.list_hosts': list_hosts,
  102. 'hosts.add_host': add_host,
  103. 'hosts.rm_host': rm_host}):
  104. ret = host.present(hostname, ip_str)
  105. assert ret['result'] is True
  106. assert 'Added host {0} ({1})'.format(hostname, ip_str) in ret['comment']
  107. assert 'Host {0} present for IP address {1}'.format(hostname, ip_list[0]) in ret['warnings'][0]
  108. assert ret['changes'] == {
  109. 'added': {
  110. ip_str: [hostname],
  111. },
  112. }, ret['changes']
  113. expected = [call(ip_str, hostname)]
  114. assert add_host.mock_calls == expected, add_host.mock_calls
  115. assert rm_host.mock_calls == [], rm_host.mock_calls
  116. # Case 3a: Repeat the above with clean=True
  117. add_host.reset_mock()
  118. rm_host.reset_mock()
  119. with patch.dict(host.__salt__,
  120. {'hosts.list_hosts': list_hosts,
  121. 'hosts.add_host': add_host,
  122. 'hosts.rm_host': rm_host}):
  123. ret = host.present(hostname, ip_str, clean=True)
  124. assert ret['result'] is True
  125. assert 'Added host {0} ({1})'.format(hostname, ip_str) in ret['comment']
  126. assert 'Removed host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
  127. assert ret['changes'] == {
  128. 'added': {
  129. ip_str: [hostname],
  130. },
  131. 'removed': {
  132. ip_list[0]: [hostname],
  133. }
  134. }, ret['changes']
  135. expected = [call(ip_str, hostname)]
  136. assert add_host.mock_calls == expected, add_host.mock_calls
  137. expected = [call(ip_list[0], hostname)]
  138. assert rm_host.mock_calls == expected, rm_host.mock_calls
  139. # Case 4: Match for hostname, but no matching IP. Multiple IP addresses
  140. # passed to the state.
  141. cur_ip = '1.2.3.4'
  142. list_hosts = MagicMock(return_value={
  143. '127.0.0.1': ['localhost'],
  144. cur_ip: [hostname],
  145. })
  146. add_host.reset_mock()
  147. rm_host.reset_mock()
  148. with patch.dict(host.__salt__,
  149. {'hosts.list_hosts': list_hosts,
  150. 'hosts.add_host': add_host,
  151. 'hosts.rm_host': rm_host}):
  152. ret = host.present(hostname, ip_list)
  153. assert ret['result'] is True
  154. assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
  155. assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
  156. assert ret['changes'] == {
  157. 'added': {
  158. ip_list[0]: [hostname],
  159. ip_list[1]: [hostname],
  160. },
  161. }, ret['changes']
  162. expected = sorted([call(x, hostname) for x in ip_list])
  163. assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
  164. assert rm_host.mock_calls == [], rm_host.mock_calls
  165. # Case 4a: Repeat the above with clean=True
  166. add_host.reset_mock()
  167. rm_host.reset_mock()
  168. with patch.dict(host.__salt__,
  169. {'hosts.list_hosts': list_hosts,
  170. 'hosts.add_host': add_host,
  171. 'hosts.rm_host': rm_host}):
  172. ret = host.present(hostname, ip_list, clean=True)
  173. assert ret['result'] is True
  174. assert 'Added host {0} ({1})'.format(hostname, ip_list[0]) in ret['comment']
  175. assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
  176. assert 'Removed host {0} ({1})'.format(hostname, cur_ip) in ret['comment']
  177. assert ret['changes'] == {
  178. 'added': {
  179. ip_list[0]: [hostname],
  180. ip_list[1]: [hostname],
  181. },
  182. 'removed': {
  183. cur_ip: [hostname],
  184. }
  185. }, ret['changes']
  186. expected = sorted([call(x, hostname) for x in ip_list])
  187. assert sorted(add_host.mock_calls) == expected, add_host.mock_calls
  188. expected = [call(cur_ip, hostname)]
  189. assert rm_host.mock_calls == expected, rm_host.mock_calls
  190. # Case 5: Multiple IP addresses passed to the state. One of them
  191. # matches, the other does not. There is also a non-matching IP that
  192. # must be removed.
  193. cur_ip = '1.2.3.4'
  194. list_hosts = MagicMock(return_value={
  195. '127.0.0.1': ['localhost'],
  196. cur_ip: [hostname],
  197. ip_list[0]: [hostname],
  198. })
  199. add_host.reset_mock()
  200. rm_host.reset_mock()
  201. with patch.dict(host.__salt__,
  202. {'hosts.list_hosts': list_hosts,
  203. 'hosts.add_host': add_host,
  204. 'hosts.rm_host': rm_host}):
  205. ret = host.present(hostname, ip_list)
  206. assert ret['result'] is True
  207. assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
  208. assert ret['changes'] == {
  209. 'added': {
  210. ip_list[1]: [hostname],
  211. },
  212. }, ret['changes']
  213. expected = [call(ip_list[1], hostname)]
  214. assert add_host.mock_calls == expected, add_host.mock_calls
  215. assert rm_host.mock_calls == [], rm_host.mock_calls
  216. # Case 5a: Repeat the above with clean=True
  217. add_host.reset_mock()
  218. rm_host.reset_mock()
  219. with patch.dict(host.__salt__,
  220. {'hosts.list_hosts': list_hosts,
  221. 'hosts.add_host': add_host,
  222. 'hosts.rm_host': rm_host}):
  223. ret = host.present(hostname, ip_list, clean=True)
  224. assert ret['result'] is True
  225. assert 'Added host {0} ({1})'.format(hostname, ip_list[1]) in ret['comment']
  226. assert 'Removed host {0} ({1})'.format(hostname, cur_ip) in ret['comment']
  227. assert ret['changes'] == {
  228. 'added': {
  229. ip_list[1]: [hostname],
  230. },
  231. 'removed': {
  232. cur_ip: [hostname],
  233. }
  234. }, ret['changes']
  235. expected = [call(ip_list[1], hostname)]
  236. assert add_host.mock_calls == expected, add_host.mock_calls
  237. expected = [call(cur_ip, hostname)]
  238. assert rm_host.mock_calls == expected, rm_host.mock_calls
  239. # Case 6: Single IP address passed to the state, which matches the
  240. # current configuration for that hostname. No changes should be made.
  241. list_hosts = MagicMock(return_value={
  242. ip_str: [hostname],
  243. })
  244. add_host.reset_mock()
  245. rm_host.reset_mock()
  246. with patch.dict(host.__salt__,
  247. {'hosts.list_hosts': list_hosts,
  248. 'hosts.add_host': add_host,
  249. 'hosts.rm_host': rm_host}):
  250. ret = host.present(hostname, ip_str)
  251. assert ret['result'] is True
  252. assert ret['comment'] == 'Host {0} ({1}) already present'.format(hostname, ip_str) in ret['comment']
  253. assert ret['changes'] == {}, ret['changes']
  254. assert add_host.mock_calls == [], add_host.mock_calls
  255. assert rm_host.mock_calls == [], rm_host.mock_calls
  256. # Case 7: Multiple IP addresses passed to the state, which both match
  257. # the current configuration for that hostname. No changes should be
  258. # made.
  259. list_hosts = MagicMock(return_value={
  260. ip_list[0]: [hostname],
  261. ip_list[1]: [hostname],
  262. })
  263. add_host.reset_mock()
  264. rm_host.reset_mock()
  265. with patch.dict(host.__salt__,
  266. {'hosts.list_hosts': list_hosts,
  267. 'hosts.add_host': add_host,
  268. 'hosts.rm_host': rm_host}):
  269. ret = host.present(hostname, ip_list)
  270. assert ret['result'] is True
  271. assert 'Host {0} ({1}) already present'.format(hostname, ip_list[0]) in ret['comment']
  272. assert 'Host {0} ({1}) already present'.format(hostname, ip_list[1]) in ret['comment']
  273. assert ret['changes'] == {}, ret['changes']
  274. assert add_host.mock_calls == [], add_host.mock_calls
  275. assert rm_host.mock_calls == [], rm_host.mock_calls
  276. def test_host_present_should_return_True_if_test_and_no_changes(self):
  277. expected = {
  278. 'comment': 'Host {} ({}) already present'.format(
  279. self.hostname,
  280. self.ip_list[0],
  281. ),
  282. 'changes': {},
  283. 'name': self.hostname,
  284. 'result': True,
  285. }
  286. list_hosts = MagicMock(
  287. return_value={self.ip_list[0]: [self.hostname]},
  288. )
  289. with patch.dict(host.__salt__,
  290. {'hosts.list_hosts': list_hosts,
  291. 'hosts.add_host': self.add_host_mock,
  292. 'hosts.rm_host': self.rm_host_mock}):
  293. with patch.dict(host.__opts__, {'test': True}):
  294. ret = host.present(self.hostname, self.ip_list[:1])
  295. self.assertDictEqual(ret, expected)
  296. def test_host_present_should_return_None_if_test_and_adding(self):
  297. expected = {
  298. 'comment': '\n'.join([
  299. 'Host {} ({}) already present',
  300. 'Host {} ({}) would be added',
  301. ]).format(
  302. self.hostname,
  303. self.ip_list[0],
  304. self.hostname,
  305. self.ip_list[1],
  306. ),
  307. 'changes': {'added': {self.ip_list[1]: [self.hostname]}},
  308. 'name': self.hostname,
  309. 'result': None,
  310. }
  311. list_hosts = MagicMock(
  312. return_value={self.ip_list[0]: [self.hostname]},
  313. )
  314. with patch.dict(host.__salt__,
  315. {'hosts.list_hosts': list_hosts,
  316. 'hosts.add_host': self.add_host_mock,
  317. 'hosts.rm_host': self.rm_host_mock}):
  318. with patch.dict(host.__opts__, {'test': True}):
  319. ret = host.present(self.hostname, self.ip_list)
  320. self.assertDictEqual(ret, expected)
  321. def test_host_present_should_return_None_if_test_and_removing(self):
  322. expected = {
  323. 'comment': '\n'.join([
  324. 'Host {} ({}) already present',
  325. 'Host {} ({}) would be removed',
  326. ]).format(
  327. self.hostname,
  328. self.ip_list[0],
  329. self.hostname,
  330. self.ip_list[1],
  331. ),
  332. 'changes': {'removed': {self.ip_list[1]: [self.hostname]}},
  333. 'name': self.hostname,
  334. 'result': None,
  335. }
  336. with patch.dict(host.__salt__,
  337. {'hosts.list_hosts': self.list_hosts_mock,
  338. 'hosts.add_host': self.add_host_mock,
  339. 'hosts.rm_host': self.rm_host_mock}):
  340. with patch.dict(host.__opts__, {'test': True}):
  341. ret = host.present(self.hostname, self.ip_list[:1], clean=True)
  342. self.assertDictEqual(ret, expected)
  343. def test_absent(self):
  344. '''
  345. Test to ensure that the named host is absent
  346. '''
  347. ret = {'changes': {},
  348. 'comment': 'Host salt (127.0.0.1) already absent',
  349. 'name': 'salt', 'result': True}
  350. mock = MagicMock(return_value=False)
  351. with patch.dict(host.__salt__, {'hosts.has_pair': mock}):
  352. self.assertDictEqual(host.absent("salt", "127.0.0.1"), ret)
  353. def test_only_already(self):
  354. '''
  355. Test only() when the state hasn't changed
  356. '''
  357. expected = {
  358. 'name': '127.0.1.1',
  359. 'changes': {},
  360. 'result': True,
  361. 'comment': 'IP address 127.0.1.1 already set to "foo.bar"'}
  362. mock1 = MagicMock(return_value=['foo.bar'])
  363. with patch.dict(host.__salt__, {'hosts.get_alias': mock1}):
  364. mock2 = MagicMock(return_value=True)
  365. with patch.dict(host.__salt__, {'hosts.set_host': mock2}):
  366. with patch.dict(host.__opts__, {'test': False}):
  367. self.assertDictEqual(
  368. expected,
  369. host.only("127.0.1.1", 'foo.bar'))
  370. def test_only_dryrun(self):
  371. '''
  372. Test only() when state would change, but it's a dry run
  373. '''
  374. expected = {
  375. 'name': '127.0.1.1',
  376. 'changes': {},
  377. 'result': None,
  378. 'comment': 'Would change 127.0.1.1 from "foo.bar" to "foo.bar foo"'}
  379. mock1 = MagicMock(return_value=['foo.bar'])
  380. with patch.dict(host.__salt__, {'hosts.get_alias': mock1}):
  381. mock2 = MagicMock(return_value=True)
  382. with patch.dict(host.__salt__, {'hosts.set_host': mock2}):
  383. with patch.dict(host.__opts__, {'test': True}):
  384. self.assertDictEqual(
  385. expected,
  386. host.only("127.0.1.1", ['foo.bar', 'foo']))
  387. def test_only_fail(self):
  388. '''
  389. Test only() when state change fails
  390. '''
  391. expected = {
  392. 'name': '127.0.1.1',
  393. 'changes': {},
  394. 'result': False,
  395. 'comment': 'hosts.set_host failed to change 127.0.1.1'
  396. + ' from "foo.bar" to "foo.bar foo"'}
  397. mock = MagicMock(return_value=['foo.bar'])
  398. with patch.dict(host.__salt__, {'hosts.get_alias': mock}):
  399. mock = MagicMock(return_value=False)
  400. with patch.dict(host.__salt__, {'hosts.set_host': mock}):
  401. with patch.dict(host.__opts__, {'test': False}):
  402. self.assertDictEqual(
  403. expected,
  404. host.only("127.0.1.1", ['foo.bar', 'foo']))
  405. def test_only_success(self):
  406. '''
  407. Test only() when state successfully changes
  408. '''
  409. expected = {
  410. 'name': '127.0.1.1',
  411. 'changes': {'127.0.1.1': {'old': 'foo.bar', 'new': 'foo.bar foo'}},
  412. 'result': True,
  413. 'comment': 'successfully changed 127.0.1.1'
  414. + ' from "foo.bar" to "foo.bar foo"'}
  415. mock = MagicMock(return_value=['foo.bar'])
  416. with patch.dict(host.__salt__, {'hosts.get_alias': mock}):
  417. mock = MagicMock(return_value=True)
  418. with patch.dict(host.__salt__, {'hosts.set_host': mock}):
  419. with patch.dict(host.__opts__, {'test': False}):
  420. self.assertDictEqual(
  421. expected,
  422. host.only("127.0.1.1", ['foo.bar', 'foo']))