test_service.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Rahul Handay <rahulha@saltstack.com>
  4. """
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import pytest
  7. import salt.config
  8. import salt.loader
  9. import salt.states.service as service
  10. import salt.utils.platform
  11. from tests.support.mixins import LoaderModuleMockMixin
  12. from tests.support.mock import MagicMock, patch
  13. from tests.support.unit import TestCase, skipIf
  14. class ServiceTestCase(TestCase, LoaderModuleMockMixin):
  15. """
  16. Validate the service state
  17. """
  18. def setup_loader_modules(self):
  19. return {service: {}}
  20. def test_running(self):
  21. """
  22. Test to verify that the service is running
  23. """
  24. ret = [
  25. {"comment": "", "changes": {}, "name": "salt", "result": True},
  26. {
  27. "changes": {},
  28. "comment": "The service salt is already running",
  29. "name": "salt",
  30. "result": True,
  31. },
  32. {
  33. "changes": "saltstack",
  34. "comment": "The service salt is already running",
  35. "name": "salt",
  36. "result": True,
  37. },
  38. {
  39. "changes": {},
  40. "comment": "Service salt is set to start",
  41. "name": "salt",
  42. "result": None,
  43. },
  44. {
  45. "changes": "saltstack",
  46. "comment": "Started Service salt",
  47. "name": "salt",
  48. "result": True,
  49. },
  50. {
  51. "changes": {},
  52. "comment": "The service salt is already running",
  53. "name": "salt",
  54. "result": True,
  55. },
  56. {
  57. "changes": "saltstack",
  58. "comment": "Service salt failed to start",
  59. "name": "salt",
  60. "result": False,
  61. },
  62. {
  63. "changes": "saltstack",
  64. "comment": "Started Service salt\nService masking not available on this minion",
  65. "name": "salt",
  66. "result": True,
  67. "warnings": [
  68. "The 'unmask' argument is not supported by this platform/action"
  69. ],
  70. },
  71. ]
  72. tmock = MagicMock(return_value=True)
  73. fmock = MagicMock(return_value=False)
  74. vmock = MagicMock(return_value="salt")
  75. with patch.object(service, "_enabled_used_error", vmock):
  76. self.assertEqual(service.running("salt", enabled=1), "salt")
  77. with patch.object(service, "_available", fmock):
  78. self.assertDictEqual(service.running("salt"), ret[0])
  79. with patch.object(service, "_available", tmock):
  80. with patch.dict(service.__opts__, {"test": False}):
  81. with patch.dict(
  82. service.__salt__,
  83. {"service.enabled": tmock, "service.status": tmock},
  84. ):
  85. self.assertDictEqual(service.running("salt"), ret[1])
  86. mock = MagicMock(return_value={"changes": "saltstack"})
  87. with patch.dict(
  88. service.__salt__,
  89. {
  90. "service.enabled": MagicMock(side_effect=[False, True]),
  91. "service.status": tmock,
  92. },
  93. ):
  94. with patch.object(service, "_enable", mock):
  95. self.assertDictEqual(service.running("salt", True), ret[2])
  96. with patch.dict(
  97. service.__salt__,
  98. {
  99. "service.enabled": MagicMock(side_effect=[True, False]),
  100. "service.status": tmock,
  101. },
  102. ):
  103. with patch.object(service, "_disable", mock):
  104. self.assertDictEqual(service.running("salt", False), ret[2])
  105. with patch.dict(
  106. service.__salt__,
  107. {
  108. "service.status": MagicMock(side_effect=[False, True]),
  109. "service.enabled": MagicMock(side_effect=[False, True]),
  110. "service.start": MagicMock(return_value="stack"),
  111. },
  112. ):
  113. with patch.object(
  114. service,
  115. "_enable",
  116. MagicMock(return_value={"changes": "saltstack"}),
  117. ):
  118. self.assertDictEqual(service.running("salt", True), ret[4])
  119. with patch.dict(
  120. service.__salt__,
  121. {
  122. "service.status": MagicMock(side_effect=[False, True]),
  123. "service.enabled": MagicMock(side_effect=[False, True]),
  124. "service.unmask": MagicMock(side_effect=[False, True]),
  125. "service.start": MagicMock(return_value="stack"),
  126. },
  127. ):
  128. with patch.object(
  129. service,
  130. "_enable",
  131. MagicMock(return_value={"changes": "saltstack"}),
  132. ):
  133. self.assertDictEqual(
  134. service.running("salt", True, unmask=True), ret[7]
  135. )
  136. with patch.dict(service.__opts__, {"test": True}):
  137. with patch.dict(service.__salt__, {"service.status": tmock}):
  138. self.assertDictEqual(service.running("salt"), ret[5])
  139. with patch.dict(service.__salt__, {"service.status": fmock}):
  140. self.assertDictEqual(service.running("salt"), ret[3])
  141. with patch.dict(service.__opts__, {"test": False}):
  142. with patch.dict(
  143. service.__salt__,
  144. {
  145. "service.status": MagicMock(side_effect=[False, False]),
  146. "service.enabled": MagicMock(side_effecct=[True, True]),
  147. "service.start": MagicMock(return_value="stack"),
  148. },
  149. ):
  150. with patch.object(
  151. service,
  152. "_enable",
  153. MagicMock(return_value={"changes": "saltstack"}),
  154. ):
  155. self.assertDictEqual(service.running("salt", True), ret[6])
  156. def test_dead(self):
  157. """
  158. Test to ensure that the named service is dead
  159. """
  160. ret = [
  161. {"changes": {}, "comment": "", "name": "salt", "result": True},
  162. {
  163. "changes": "saltstack",
  164. "comment": "The service salt is already dead",
  165. "name": "salt",
  166. "result": True,
  167. },
  168. {
  169. "changes": {},
  170. "comment": "Service salt is set to be killed",
  171. "name": "salt",
  172. "result": None,
  173. },
  174. {
  175. "changes": "saltstack",
  176. "comment": "Service salt was killed",
  177. "name": "salt",
  178. "result": True,
  179. },
  180. {
  181. "changes": {},
  182. "comment": "Service salt failed to die",
  183. "name": "salt",
  184. "result": False,
  185. },
  186. {
  187. "changes": "saltstack",
  188. "comment": "The service salt is already dead",
  189. "name": "salt",
  190. "result": True,
  191. },
  192. ]
  193. info_mock = MagicMock(return_value={"StartType": ""})
  194. mock = MagicMock(return_value="salt")
  195. with patch.object(service, "_enabled_used_error", mock):
  196. self.assertEqual(service.dead("salt", enabled=1), "salt")
  197. tmock = MagicMock(return_value=True)
  198. fmock = MagicMock(return_value=False)
  199. with patch.object(service, "_available", fmock):
  200. self.assertDictEqual(service.dead("salt"), ret[0])
  201. with patch.object(service, "_available", tmock):
  202. mock = MagicMock(return_value={"changes": "saltstack"})
  203. with patch.dict(service.__opts__, {"test": True}):
  204. with patch.dict(
  205. service.__salt__,
  206. {
  207. "service.enabled": fmock,
  208. "service.stop": tmock,
  209. "service.status": fmock,
  210. "service.info": info_mock,
  211. },
  212. ):
  213. with patch.object(service, "_enable", mock):
  214. self.assertDictEqual(service.dead("salt", True), ret[5])
  215. with patch.dict(
  216. service.__salt__,
  217. {
  218. "service.enabled": tmock,
  219. "service.status": tmock,
  220. "service.info": info_mock,
  221. },
  222. ):
  223. self.assertDictEqual(service.dead("salt"), ret[2])
  224. with patch.dict(service.__opts__, {"test": False}):
  225. with patch.dict(
  226. service.__salt__,
  227. {
  228. "service.enabled": fmock,
  229. "service.stop": tmock,
  230. "service.status": fmock,
  231. "service.info": info_mock,
  232. },
  233. ):
  234. with patch.object(service, "_enable", mock):
  235. self.assertDictEqual(service.dead("salt", True), ret[1])
  236. with patch.dict(
  237. service.__salt__,
  238. {
  239. "service.enabled": MagicMock(side_effect=[True, True, False]),
  240. "service.status": MagicMock(side_effect=[True, False, False]),
  241. "service.stop": MagicMock(return_value="stack"),
  242. "service.info": info_mock,
  243. },
  244. ):
  245. with patch.object(
  246. service,
  247. "_enable",
  248. MagicMock(return_value={"changes": "saltstack"}),
  249. ):
  250. self.assertDictEqual(service.dead("salt", True), ret[3])
  251. # test an initd which a wrong status (True even if dead)
  252. with patch.dict(
  253. service.__salt__,
  254. {
  255. "service.enabled": MagicMock(side_effect=[False, False, False]),
  256. "service.status": MagicMock(side_effect=[True, True, True]),
  257. "service.stop": MagicMock(return_value="stack"),
  258. "service.info": info_mock,
  259. },
  260. ):
  261. with patch.object(service, "_disable", MagicMock(return_value={})):
  262. self.assertDictEqual(service.dead("salt", False), ret[4])
  263. def test_dead_with_missing_service(self):
  264. """
  265. Tests the case in which a service.dead state is executed on a state
  266. which does not exist.
  267. See https://github.com/saltstack/salt/issues/37511
  268. """
  269. name = "thisisnotarealservice"
  270. with patch.dict(
  271. service.__salt__, {"service.available": MagicMock(return_value=False)}
  272. ):
  273. ret = service.dead(name=name)
  274. self.assertDictEqual(
  275. ret,
  276. {
  277. "changes": {},
  278. "comment": "The named service {0} is not available".format(name),
  279. "result": True,
  280. "name": name,
  281. },
  282. )
  283. def test_enabled(self):
  284. """
  285. Test to verify that the service is enabled
  286. """
  287. ret = {"changes": "saltstack", "comment": "", "name": "salt", "result": True}
  288. mock = MagicMock(return_value={"changes": "saltstack"})
  289. with patch.object(service, "_enable", mock):
  290. self.assertDictEqual(service.enabled("salt"), ret)
  291. def test_disabled(self):
  292. """
  293. Test to verify that the service is disabled
  294. """
  295. ret = {"changes": "saltstack", "comment": "", "name": "salt", "result": True}
  296. mock = MagicMock(return_value={"changes": "saltstack"})
  297. with patch.object(service, "_disable", mock):
  298. self.assertDictEqual(service.disabled("salt"), ret)
  299. def test_mod_watch(self):
  300. """
  301. Test to the service watcher, called to invoke the watch command.
  302. """
  303. ret = [
  304. {
  305. "changes": {},
  306. "comment": "Service is already stopped",
  307. "name": "salt",
  308. "result": True,
  309. },
  310. {
  311. "changes": {},
  312. "comment": "Unable to trigger watch for service.stack",
  313. "name": "salt",
  314. "result": False,
  315. },
  316. {
  317. "changes": {},
  318. "comment": "Service is set to be started",
  319. "name": "salt",
  320. "result": None,
  321. },
  322. {
  323. "changes": {"salt": "salt"},
  324. "comment": "Service started",
  325. "name": "salt",
  326. "result": "salt",
  327. },
  328. ]
  329. def func(name):
  330. """
  331. Mock func method
  332. """
  333. return name
  334. mock = MagicMock(return_value=False)
  335. with patch.dict(service.__salt__, {"service.status": mock}):
  336. self.assertDictEqual(service.mod_watch("salt", "dead"), ret[0])
  337. with patch.dict(service.__salt__, {"service.start": func}):
  338. with patch.dict(service.__opts__, {"test": True}):
  339. self.assertDictEqual(service.mod_watch("salt", "running"), ret[2])
  340. with patch.dict(service.__opts__, {"test": False}):
  341. self.assertDictEqual(service.mod_watch("salt", "running"), ret[3])
  342. self.assertDictEqual(service.mod_watch("salt", "stack"), ret[1])
  343. @skipIf(salt.utils.platform.is_darwin(), "service.running is currently failing on OSX")
  344. @pytest.mark.destructive_test
  345. class ServiceTestCaseFunctional(TestCase, LoaderModuleMockMixin):
  346. """
  347. Validate the service state
  348. """
  349. def setup_loader_modules(self):
  350. self.opts = salt.config.DEFAULT_MINION_OPTS.copy()
  351. self.opts["grains"] = salt.loader.grains(self.opts)
  352. self.utils = salt.loader.utils(self.opts)
  353. self.modules = salt.loader.minion_mods(self.opts, utils=self.utils)
  354. self.service_name = "cron"
  355. cmd_name = "crontab"
  356. os_family = self.opts["grains"]["os_family"]
  357. os_release = self.opts["grains"]["osrelease"]
  358. if os_family == "RedHat":
  359. self.service_name = "crond"
  360. elif os_family == "Arch":
  361. self.service_name = "sshd"
  362. cmd_name = "systemctl"
  363. elif os_family == "MacOS":
  364. self.service_name = "org.ntp.ntpd"
  365. if int(os_release.split(".")[1]) >= 13:
  366. self.service_name = "com.openssh.sshd"
  367. elif os_family == "Windows":
  368. self.service_name = "Spooler"
  369. if os_family != "Windows" and salt.utils.path.which(cmd_name) is None:
  370. self.skipTest("{0} is not installed".format(cmd_name))
  371. return {
  372. service: {
  373. "__grains__": self.opts["grains"],
  374. "__opts__": self.opts,
  375. "__salt__": self.modules,
  376. "__utils__": self.utils,
  377. },
  378. }
  379. def setUp(self):
  380. self.pre_srv_enabled = (
  381. True
  382. if self.service_name in self.modules["service.get_enabled"]()
  383. else False
  384. )
  385. self.post_srv_disable = False
  386. if not self.pre_srv_enabled:
  387. self.modules["service.enable"](self.service_name)
  388. self.post_srv_disable = True
  389. def tearDown(self):
  390. if self.post_srv_disable:
  391. self.modules["service.disable"](self.service_name)
  392. @pytest.mark.slow_test(seconds=10) # Test takes >5 and <=10 seconds
  393. def test_running_with_reload(self):
  394. with patch.dict(service.__opts__, {"test": False}):
  395. service.dead(self.service_name, enable=False)
  396. result = service.running(name=self.service_name, enable=True, reload=False)
  397. expected = {
  398. "changes": {self.service_name: True},
  399. "comment": "Service {0} has been enabled, and is "
  400. "running".format(self.service_name),
  401. "name": self.service_name,
  402. "result": True,
  403. }
  404. self.assertDictEqual(result, expected)