test_saltcli.py 7.5 KB

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