test_systemd.py 13 KB

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