test_systemd.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import errno
  5. import os
  6. # Import Salt libs
  7. import salt.utils.systemd as _systemd
  8. from salt.exceptions import SaltInvocationError
  9. from tests.support.mock import Mock, patch
  10. # Import Salt Testing libs
  11. from tests.support.unit import TestCase
  12. def _booted_effect(path):
  13. return True if path == "/run/systemd/system" else os.stat(path)
  14. def _not_booted_effect(path):
  15. if path == "/run/systemd/system":
  16. raise OSError(errno.ENOENT, "No such file or directory", path)
  17. return os.stat(path)
  18. class SystemdTestCase(TestCase):
  19. """
  20. Tests the functions in salt.utils.systemd
  21. """
  22. def test_booted(self):
  23. """
  24. Test that salt.utils.systemd.booted() returns True when minion is
  25. systemd-booted.
  26. """
  27. # Ensure that os.stat returns True. os.stat doesn't return a bool
  28. # normally, but the code is doing a simple truth check on the return
  29. # data, so it is sufficient enough to mock it as True for these tests.
  30. with patch("os.stat", side_effect=_booted_effect):
  31. # Test without context dict passed
  32. self.assertTrue(_systemd.booted())
  33. # Test that context key is set when context dict is passed
  34. context = {}
  35. self.assertTrue(_systemd.booted(context))
  36. self.assertEqual(context, {"salt.utils.systemd.booted": True})
  37. def test_not_booted(self):
  38. """
  39. Test that salt.utils.systemd.booted() returns False when minion is not
  40. systemd-booted.
  41. """
  42. # Ensure that os.stat raises an exception even if test is being run on
  43. # a systemd-booted host.
  44. with patch("os.stat", side_effect=_not_booted_effect):
  45. # Test without context dict passed
  46. self.assertFalse(_systemd.booted())
  47. # Test that context key is set when context dict is passed
  48. context = {}
  49. self.assertFalse(_systemd.booted(context))
  50. self.assertEqual(context, {"salt.utils.systemd.booted": False})
  51. def test_booted_return_from_context(self):
  52. """
  53. Test that the context data is returned when present. To ensure we're
  54. getting data from the context dict, we use a non-boolean value to
  55. differentiate it from the True/False return this function normally
  56. produces.
  57. """
  58. context = {"salt.utils.systemd.booted": "foo"}
  59. self.assertEqual(_systemd.booted(context), "foo")
  60. def test_booted_invalid_context(self):
  61. """
  62. Test with invalid context data. The context value must be a dict, so
  63. this should raise a SaltInvocationError.
  64. """
  65. # Test with invalid context data
  66. with self.assertRaises(SaltInvocationError):
  67. _systemd.booted(99999)
  68. def test_version(self):
  69. """
  70. Test that salt.utils.systemd.booted() returns True when minion is
  71. systemd-booted.
  72. """
  73. with patch("subprocess.Popen") as popen_mock:
  74. _version = 231
  75. output = "systemd {0}\n-SYSVINIT".format(_version)
  76. popen_mock.return_value = Mock(
  77. communicate=lambda *args, **kwargs: (output, None),
  78. pid=lambda: 12345,
  79. retcode=0,
  80. )
  81. # Test without context dict passed
  82. self.assertEqual(_systemd.version(), _version)
  83. # Test that context key is set when context dict is passed
  84. context = {}
  85. self.assertTrue(_systemd.version(context))
  86. self.assertEqual(context, {"salt.utils.systemd.version": _version})
  87. def test_version_generated_from_git_describe(self):
  88. """
  89. Test with version string matching versions generated by git describe
  90. in systemd. This feature is used in systemd>=241.
  91. """
  92. with patch("subprocess.Popen") as popen_mock:
  93. _version = 241
  94. output = "systemd {0} ({0}.0-0-dist)\n-SYSVINIT".format(_version)
  95. popen_mock.return_value = Mock(
  96. communicate=lambda *args, **kwargs: (output, None),
  97. pid=lambda: 12345,
  98. retcode=0,
  99. )
  100. # Test without context dict passed
  101. self.assertEqual(_systemd.version(), _version)
  102. # Test that context key is set when context dict is passed
  103. context = {}
  104. self.assertTrue(_systemd.version(context))
  105. self.assertEqual(context, {"salt.utils.systemd.version": _version})
  106. def test_version_return_from_context(self):
  107. """
  108. Test that the context data is returned when present. To ensure we're
  109. getting data from the context dict, we use a non-integer value to
  110. differentiate it from the integer return this function normally
  111. produces.
  112. """
  113. context = {"salt.utils.systemd.version": "foo"}
  114. self.assertEqual(_systemd.version(context), "foo")
  115. def test_version_invalid_context(self):
  116. """
  117. Test with invalid context data. The context value must be a dict, so
  118. this should raise a SaltInvocationError.
  119. """
  120. # Test with invalid context data
  121. with self.assertRaises(SaltInvocationError):
  122. _systemd.version(99999)
  123. def test_version_parse_problem(self):
  124. """
  125. Test with invalid context data. The context value must be a dict, so
  126. this should raise a SaltInvocationError.
  127. """
  128. with patch("subprocess.Popen") as popen_mock:
  129. popen_mock.return_value = Mock(
  130. communicate=lambda *args, **kwargs: ("invalid", None),
  131. pid=lambda: 12345,
  132. retcode=0,
  133. )
  134. # Test without context dict passed
  135. self.assertIsNone(_systemd.version())
  136. # Test that context key is set when context dict is passed. A failure
  137. # to parse the systemctl output should not set a context key, so it
  138. # should not be present in the context dict.
  139. context = {}
  140. self.assertIsNone(_systemd.version(context))
  141. self.assertEqual(context, {})
  142. def test_has_scope_systemd204(self):
  143. """
  144. Scopes are available in systemd>=205. Make sure that this function
  145. returns the expected boolean. We do three separate unit tests for
  146. versions 204 through 206 because mock doesn't like us altering the
  147. return_value in a loop.
  148. """
  149. with patch("subprocess.Popen") as popen_mock:
  150. _expected = False
  151. _version = 204
  152. _output = "systemd {0}\n-SYSVINIT".format(_version)
  153. popen_mock.return_value = Mock(
  154. communicate=lambda *args, **kwargs: (_output, None),
  155. pid=lambda: 12345,
  156. retcode=0,
  157. )
  158. # Ensure that os.stat returns True. os.stat doesn't return a bool
  159. # normally, but the code is doing a simple truth check on the
  160. # return data, so it is sufficient enough to mock it as True for
  161. # these tests.
  162. with patch("os.stat", side_effect=_booted_effect):
  163. # Test without context dict passed
  164. self.assertEqual(_systemd.has_scope(), _expected)
  165. # Test that context key is set when context dict is passed
  166. context = {}
  167. self.assertEqual(_systemd.has_scope(context), _expected)
  168. self.assertEqual(
  169. context,
  170. {
  171. "salt.utils.systemd.booted": True,
  172. "salt.utils.systemd.version": _version,
  173. },
  174. )
  175. def test_has_scope_systemd205(self):
  176. """
  177. Scopes are available in systemd>=205. Make sure that this function
  178. returns the expected boolean. We do three separate unit tests for
  179. versions 204 through 206 because mock doesn't like us altering the
  180. return_value in a loop.
  181. """
  182. with patch("subprocess.Popen") as popen_mock:
  183. _expected = True
  184. _version = 205
  185. _output = "systemd {0}\n-SYSVINIT".format(_version)
  186. popen_mock.return_value = Mock(
  187. communicate=lambda *args, **kwargs: (_output, None),
  188. pid=lambda: 12345,
  189. retcode=0,
  190. )
  191. # Ensure that os.stat returns True. os.stat doesn't return a bool
  192. # normally, but the code is doing a simple truth check on the
  193. # return data, so it is sufficient enough to mock it as True for
  194. # these tests.
  195. with patch("os.stat", side_effect=_booted_effect):
  196. # Test without context dict passed
  197. self.assertEqual(_systemd.has_scope(), _expected)
  198. # Test that context key is set when context dict is passed
  199. context = {}
  200. self.assertEqual(_systemd.has_scope(context), _expected)
  201. self.assertEqual(
  202. context,
  203. {
  204. "salt.utils.systemd.booted": True,
  205. "salt.utils.systemd.version": _version,
  206. },
  207. )
  208. def test_has_scope_systemd206(self):
  209. """
  210. Scopes are available in systemd>=205. Make sure that this function
  211. returns the expected boolean. We do three separate unit tests for
  212. versions 204 through 206 because mock doesn't like us altering the
  213. return_value in a loop.
  214. """
  215. with patch("subprocess.Popen") as popen_mock:
  216. _expected = True
  217. _version = 206
  218. _output = "systemd {0}\n-SYSVINIT".format(_version)
  219. popen_mock.return_value = Mock(
  220. communicate=lambda *args, **kwargs: (_output, None),
  221. pid=lambda: 12345,
  222. retcode=0,
  223. )
  224. # Ensure that os.stat returns True. os.stat doesn't return a bool
  225. # normally, but the code is doing a simple truth check on the
  226. # return data, so it is sufficient enough to mock it as True for
  227. # these tests.
  228. with patch("os.stat", side_effect=_booted_effect):
  229. # Test without context dict passed
  230. self.assertEqual(_systemd.has_scope(), _expected)
  231. # Test that context key is set when context dict is passed
  232. context = {}
  233. self.assertEqual(_systemd.has_scope(context), _expected)
  234. self.assertEqual(
  235. context,
  236. {
  237. "salt.utils.systemd.booted": True,
  238. "salt.utils.systemd.version": _version,
  239. },
  240. )
  241. def test_has_scope_no_systemd(self):
  242. """
  243. Test the case where the system is not systemd-booted. We should not be
  244. performing a version check in these cases as there is no need.
  245. """
  246. with patch("os.stat", side_effect=_not_booted_effect):
  247. # Test without context dict passed
  248. self.assertFalse(_systemd.has_scope())
  249. # Test that context key is set when context dict is passed.
  250. # Because we are not systemd-booted, there should be no key in the
  251. # context dict for the version check, as we shouldn't have
  252. # performed this check.
  253. context = {}
  254. self.assertFalse(_systemd.has_scope(context))
  255. self.assertEqual(context, {"salt.utils.systemd.booted": False})
  256. def test_has_scope_version_parse_problem(self):
  257. """
  258. Test the case where the system is systemd-booted, but we failed to
  259. parse the "systemctl --version" output.
  260. """
  261. with patch("subprocess.Popen") as popen_mock:
  262. popen_mock.return_value = Mock(
  263. communicate=lambda *args, **kwargs: ("invalid", None),
  264. pid=lambda: 12345,
  265. retcode=0,
  266. )
  267. with patch("os.stat", side_effect=_booted_effect):
  268. # Test without context dict passed
  269. self.assertFalse(_systemd.has_scope())
  270. # Test that context key is set when context dict is passed. A
  271. # failure to parse the systemctl output should not set a context
  272. # key, so it should not be present in the context dict.
  273. context = {}
  274. self.assertFalse(_systemd.has_scope(context))
  275. self.assertEqual(context, {"salt.utils.systemd.booted": True})
  276. def test_has_scope_invalid_context(self):
  277. """
  278. Test with invalid context data. The context value must be a dict, so
  279. this should raise a SaltInvocationError.
  280. """
  281. # Test with invalid context data
  282. with self.assertRaises(SaltInvocationError):
  283. _systemd.has_scope(99999)