test_systemd.py 13 KB

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