test_saltcli.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Thayne Harbaugh (tharbaug@adobe.com)
  4. tests.integration.shell.saltcli
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. :NOTE: this was named ``saltcli`` rather than ``salt`` because ``salt`` conflates
  7. in the python importer with the expected ``salt`` namespace and breaks imports.
  8. '''
  9. # Import python libs
  10. from __future__ import absolute_import
  11. import logging
  12. import os
  13. # Import Salt libs
  14. import salt.defaults.exitcodes
  15. import salt.utils.files
  16. import salt.utils.path
  17. # Import Salt Testing libs
  18. from tests.support.case import ShellCase
  19. from tests.integration.utils import testprogram
  20. import pytest
  21. log = logging.getLogger(__name__)
  22. @pytest.mark.windows_whitelisted
  23. class SaltTest(testprogram.TestProgramCase):
  24. '''
  25. Various integration tests for the salt executable.
  26. '''
  27. # pylint: disable=invalid-name
  28. def test_exit_status_unknown_argument(self):
  29. '''
  30. Ensure correct exit status when an unknown argument is passed to salt-run.
  31. '''
  32. runner = testprogram.TestProgramSalt(
  33. name='run-unknown_argument',
  34. parent_dir=self._test_dir,
  35. )
  36. # Call setup here to ensure config and script exist
  37. runner.setup()
  38. stdout, stderr, status = runner.run(
  39. args=['--unknown-argument'],
  40. catch_stderr=True,
  41. with_retcode=True,
  42. )
  43. self.assert_exit_status(
  44. status, 'EX_USAGE',
  45. message='unknown argument',
  46. stdout=stdout, stderr=stderr
  47. )
  48. # runner.shutdown() should be unnecessary since the start-up should fail
  49. def test_exit_status_correct_usage(self):
  50. '''
  51. Ensure correct exit status when salt-run starts correctly.
  52. '''
  53. runner = testprogram.TestProgramSalt(
  54. name='run-correct_usage',
  55. parent_dir=self._test_dir,
  56. )
  57. # Call setup here to ensure config and script exist
  58. runner.setup()
  59. stdout, stderr, status = runner.run(
  60. args=['*', '-h'],
  61. catch_stderr=True,
  62. with_retcode=True,
  63. )
  64. self.assert_exit_status(
  65. status, 'EX_OK',
  66. message='correct usage',
  67. stdout=stdout, stderr=stderr
  68. )
  69. @pytest.mark.windows_whitelisted
  70. class RetcodeTestCase(ShellCase):
  71. '''
  72. Tests to ensure that we set non-zero retcodes when execution fails
  73. '''
  74. # Hard-coding these instead of substituting values from
  75. # salt.defaults.exitcodes will give us a heads-up in the event that someone
  76. # tries to do something daft like change these values.
  77. error_status = salt.defaults.exitcodes.EX_GENERIC
  78. state_compiler_error = salt.defaults.exitcodes.EX_STATE_COMPILER_ERROR
  79. state_failure = salt.defaults.exitcodes.EX_STATE_FAILURE
  80. def _salt(self, command):
  81. cmdline = 'minion ' + command
  82. return self.run_salt(cmdline, with_retcode=True)[1]
  83. def _salt_call(self, command, retcode_passthrough=False):
  84. cmdline = '--retcode-passthrough ' if retcode_passthrough else ''
  85. cmdline += command
  86. return self.run_call(cmdline, with_retcode=True)[1]
  87. def _test_error(self, salt_call=False):
  88. '''
  89. Tests retcode when various error conditions are triggered
  90. '''
  91. _run = self._salt_call if salt_call else self._salt
  92. retcode = _run('test.raise_exception TypeError')
  93. assert retcode == self.error_status, retcode
  94. retcode = _run('test.raise_exception salt.exceptions.CommandNotFoundError')
  95. assert retcode == self.error_status, retcode
  96. retcode = _run('test.raise_exception salt.exceptions.CommandExecutionError')
  97. assert retcode == self.error_status, retcode
  98. retcode = _run('test.raise_exception salt.exceptions.SaltInvocationError')
  99. assert retcode == self.error_status, retcode
  100. retcode = _run(
  101. 'test.raise_exception '
  102. 'OSError 2 "No such file or directory" /tmp/foo.txt')
  103. assert retcode == self.error_status, retcode
  104. retcode = _run('test.echo "{foo: bar, result: False}"')
  105. assert retcode == self.error_status, retcode
  106. retcode = _run('test.echo "{foo: bar, success: False}"')
  107. assert retcode == self.error_status, retcode
  108. def test_zero_exit_code(self):
  109. '''
  110. Test that a zero exit code is set when there are no errors and there is
  111. no explicit False result set in the return data.
  112. '''
  113. retcode = self._salt('test.ping')
  114. assert retcode == 0, retcode
  115. retcode = self._salt_call('test.ping')
  116. assert retcode == 0, retcode
  117. def test_context_retcode(self):
  118. '''
  119. Test that a nonzero retcode set in the context dunder will cause the
  120. salt CLI to set a nonzero retcode.
  121. '''
  122. # test.retcode will set the retcode in the context dunder
  123. retcode = self._salt('test.retcode 0')
  124. assert retcode == 0, retcode
  125. retcode = self._salt('test.retcode 42')
  126. assert retcode == self.error_status, retcode
  127. # Test salt-call, making sure to also confirm the behavior of
  128. # retcode_passthrough.
  129. retcode = self._salt_call('test.retcode 0')
  130. assert retcode == 0, retcode
  131. retcode = self._salt_call('test.retcode 42')
  132. assert retcode == self.error_status, retcode
  133. retcode = self._salt_call('test.retcode 42', retcode_passthrough=True)
  134. assert retcode == 42, retcode
  135. # Test a state run that exits with one or more failures
  136. retcode = self._salt_call('state.single test.fail_without_changes foo')
  137. assert retcode == self.error_status, retcode
  138. retcode = self._salt_call(
  139. 'state.single test.fail_without_changes foo',
  140. retcode_passthrough=True)
  141. assert retcode == self.state_failure, retcode
  142. # Test a state compiler error
  143. retcode = self._salt_call('state.apply thisslsfiledoesnotexist')
  144. assert retcode == self.error_status, retcode
  145. retcode = self._salt_call(
  146. 'state.apply thisslsfiledoesnotexist',
  147. retcode_passthrough=True)
  148. assert retcode == self.state_compiler_error, retcode
  149. def test_salt_error(self):
  150. '''
  151. Test that we return the expected retcode when a minion function raises
  152. an exception.
  153. '''
  154. self._test_error()
  155. self._test_error(salt_call=True)
  156. def test_missing_minion(self):
  157. '''
  158. Test that a minion which doesn't respond results in a nonzeo exit code
  159. '''
  160. good = salt.utils.path.join(self.master_opts['pki_dir'], 'minions', 'minion')
  161. bad = salt.utils.path.join(self.master_opts['pki_dir'], 'minions', 'minion2')
  162. try:
  163. # Copy the key
  164. with salt.utils.files.fopen(good, 'rb') as fhr, \
  165. salt.utils.files.fopen(bad, 'wb') as fhw:
  166. fhw.write(fhr.read())
  167. retcode = self.run_script(
  168. 'salt',
  169. '-c {0} -t 5 minion2 test.ping'.format(self.config_dir),
  170. with_retcode=True,
  171. timeout=60)[1]
  172. assert retcode == salt.defaults.exitcodes.EX_GENERIC, retcode
  173. finally:
  174. # Now get rid of it
  175. try:
  176. os.remove(bad)
  177. except OSError as exc:
  178. if exc.errno != os.errno.ENOENT:
  179. log.error(
  180. 'Failed to remove %s, this may affect other tests: %s',
  181. bad, exc
  182. )