test_saltcli.py 7.4 KB

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