test_systemd.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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
  8. from tests.support.mock import Mock, patch
  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. 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. {'salt.utils.systemd.booted': True,
  171. 'salt.utils.systemd.version': _version},
  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. {'salt.utils.systemd.booted': True,
  202. 'salt.utils.systemd.version': _version},
  203. )
  204. def test_has_scope_systemd206(self):
  205. '''
  206. Scopes are available in systemd>=205. Make sure that this function
  207. returns the expected boolean. We do three separate unit tests for
  208. versions 204 through 206 because mock doesn't like us altering the
  209. return_value in a loop.
  210. '''
  211. with patch('subprocess.Popen') as popen_mock:
  212. _expected = True
  213. _version = 206
  214. _output = 'systemd {0}\n-SYSVINIT'.format(_version)
  215. popen_mock.return_value = Mock(
  216. communicate=lambda *args, **kwargs: (_output, None),
  217. pid=lambda: 12345,
  218. retcode=0
  219. )
  220. # Ensure that os.stat returns True. os.stat doesn't return a bool
  221. # normally, but the code is doing a simple truth check on the
  222. # return data, so it is sufficient enough to mock it as True for
  223. # these tests.
  224. with patch('os.stat', side_effect=_booted_effect):
  225. # Test without context dict passed
  226. self.assertEqual(_systemd.has_scope(), _expected)
  227. # Test that context key is set when context dict is passed
  228. context = {}
  229. self.assertEqual(_systemd.has_scope(context), _expected)
  230. self.assertEqual(
  231. context,
  232. {'salt.utils.systemd.booted': True,
  233. 'salt.utils.systemd.version': _version},
  234. )
  235. def test_has_scope_no_systemd(self):
  236. '''
  237. Test the case where the system is not systemd-booted. We should not be
  238. performing a version check in these cases as there is no need.
  239. '''
  240. with patch('os.stat', side_effect=_not_booted_effect):
  241. # Test without context dict passed
  242. self.assertFalse(_systemd.has_scope())
  243. # Test that context key is set when context dict is passed.
  244. # Because we are not systemd-booted, there should be no key in the
  245. # context dict for the version check, as we shouldn't have
  246. # performed this check.
  247. context = {}
  248. self.assertFalse(_systemd.has_scope(context))
  249. self.assertEqual(context, {'salt.utils.systemd.booted': False})
  250. def test_has_scope_version_parse_problem(self):
  251. '''
  252. Test the case where the system is systemd-booted, but we failed to
  253. parse the "systemctl --version" output.
  254. '''
  255. with patch('subprocess.Popen') as popen_mock:
  256. popen_mock.return_value = Mock(
  257. communicate=lambda *args, **kwargs: ('invalid', None),
  258. pid=lambda: 12345,
  259. retcode=0
  260. )
  261. with patch('os.stat', side_effect=_booted_effect):
  262. # Test without context dict passed
  263. self.assertFalse(_systemd.has_scope())
  264. # Test that context key is set when context dict is passed. A
  265. # failure to parse the systemctl output should not set a context
  266. # key, so it should not be present in the context dict.
  267. context = {}
  268. self.assertFalse(_systemd.has_scope(context))
  269. self.assertEqual(context, {'salt.utils.systemd.booted': True})
  270. def test_has_scope_invalid_context(self):
  271. '''
  272. Test with invalid context data. The context value must be a dict, so
  273. this should raise a SaltInvocationError.
  274. '''
  275. # Test with invalid context data
  276. with self.assertRaises(SaltInvocationError):
  277. _systemd.has_scope(99999)