test_saltcli.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 shutil
  13. import pytest
  14. import salt.defaults.exitcodes
  15. import salt.utils.path
  16. log = logging.getLogger(__name__)
  17. @pytest.mark.windows_whitelisted
  18. class TestRetcode(object):
  19. """
  20. Tests to ensure that we set non-zero retcodes when execution fails
  21. """
  22. def test_zero_exit_code_salt(self, salt_cli):
  23. """
  24. Test that a zero exit code is set when there are no errors and there is
  25. no explicit False result set in the return data.
  26. """
  27. ret = salt_cli.run("test.ping", minion_tgt="minion")
  28. assert ret.exitcode == 0, ret
  29. def test_zero_exit_code_salt_call(self, salt_call_cli):
  30. """
  31. Test that a zero exit code is set when there are no errors and there is
  32. no explicit False result set in the return data.
  33. """
  34. ret = salt_call_cli.run("test.ping")
  35. assert ret.exitcode == 0, ret
  36. def test_context_retcode_salt(self, salt_cli):
  37. """
  38. Test that a nonzero retcode set in the context dunder will cause the
  39. salt CLI to set a nonzero retcode.
  40. """
  41. # test.retcode will set the retcode in the context dunder
  42. ret = salt_cli.run("test.retcode", "0", minion_tgt="minion")
  43. assert ret.exitcode == 0, ret
  44. ret = salt_cli.run("test.retcode", "42", minion_tgt="minion")
  45. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  46. def test_context_retcode_salt_call(self, salt_call_cli):
  47. """
  48. Test that a nonzero retcode set in the context dunder will cause the
  49. salt CLI to set a nonzero retcode.
  50. """
  51. # Test salt-call, making sure to also confirm the behavior of
  52. # retcode_passthrough.
  53. ret = salt_call_cli.run("test.retcode", "0")
  54. assert ret.exitcode == 0, ret
  55. ret = salt_call_cli.run("test.retcode", "42")
  56. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  57. ret = salt_call_cli.run("--retcode-passthrough", "test.retcode", "42")
  58. assert ret.exitcode == 42, ret
  59. # Test a state run that exits with one or more failures
  60. ret = salt_call_cli.run("state.single", "test.fail_without_changes", "foo")
  61. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  62. ret = salt_call_cli.run(
  63. "--retcode-passthrough", "state.single", "test.fail_without_changes", "foo"
  64. )
  65. assert ret.exitcode == salt.defaults.exitcodes.EX_STATE_FAILURE, ret
  66. # Test a state compiler error
  67. ret = salt_call_cli.run("state.apply", "thisslsfiledoesnotexist")
  68. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  69. ret = salt_call_cli.run(
  70. "--retcode-passthrough", "state.apply", "thisslsfiledoesnotexist"
  71. )
  72. assert ret.exitcode == salt.defaults.exitcodes.EX_STATE_COMPILER_ERROR, ret
  73. def test_salt_error(self, salt_cli):
  74. """
  75. Test that we return the expected retcode when a minion function raises
  76. an exception.
  77. """
  78. ret = salt_cli.run("test.raise_exception", "TypeError", minion_tgt="minion")
  79. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  80. ret = salt_cli.run(
  81. "test.raise_exception",
  82. "salt.exceptions.CommandNotFoundError",
  83. minion_tgt="minion",
  84. )
  85. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  86. ret = salt_cli.run(
  87. "test.raise_exception",
  88. "salt.exceptions.CommandExecutionError",
  89. minion_tgt="minion",
  90. )
  91. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  92. ret = salt_cli.run(
  93. "test.raise_exception",
  94. "salt.exceptions.SaltInvocationError",
  95. minion_tgt="minion",
  96. )
  97. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  98. ret = salt_cli.run(
  99. "test.raise_exception",
  100. "OSError",
  101. "2",
  102. '"No such file or directory" /tmp/foo.txt',
  103. minion_tgt="minion",
  104. )
  105. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  106. ret = salt_cli.run(
  107. "test.echo", "{foo: bar, result: False}", minion_tgt="minion"
  108. )
  109. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  110. ret = salt_cli.run(
  111. "test.echo", "{foo: bar, success: False}", minion_tgt="minion"
  112. )
  113. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  114. def test_salt_call_error(self, salt_call_cli):
  115. """
  116. Test that we return the expected retcode when a minion function raises
  117. an exception.
  118. """
  119. ret = salt_call_cli.run("test.raise_exception", "TypeError")
  120. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  121. ret = salt_call_cli.run(
  122. "test.raise_exception", "salt.exceptions.CommandNotFoundError"
  123. )
  124. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  125. ret = salt_call_cli.run(
  126. "test.raise_exception", "salt.exceptions.CommandExecutionError"
  127. )
  128. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  129. ret = salt_call_cli.run(
  130. "test.raise_exception", "salt.exceptions.SaltInvocationError"
  131. )
  132. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  133. ret = salt_call_cli.run(
  134. "test.raise_exception",
  135. "OSError",
  136. "2",
  137. "No such file or directory",
  138. "/tmp/foo.txt",
  139. )
  140. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  141. ret = salt_call_cli.run("test.echo", "{foo: bar, result: False}")
  142. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  143. ret = salt_call_cli.run("test.echo", "{foo: bar, success: False}")
  144. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  145. def test_missing_minion(self, salt_cli, salt_master):
  146. """
  147. Test that a minion which doesn't respond results in a nonzeo exit code
  148. """
  149. good = salt.utils.path.join(salt_master.config["pki_dir"], "minions", "minion")
  150. bad = salt.utils.path.join(salt_master.config["pki_dir"], "minions", "minion2")
  151. try:
  152. # Copy the key
  153. shutil.copyfile(good, bad)
  154. ret = salt_cli.run(
  155. "--timeout=5", "test.ping", minion_tgt="minion2", _timeout=120
  156. )
  157. assert ret.exitcode == salt.defaults.exitcodes.EX_GENERIC, ret
  158. finally:
  159. # Now get rid of it
  160. try:
  161. os.remove(bad)
  162. except OSError as exc:
  163. if exc.errno != os.errno.ENOENT:
  164. log.error(
  165. "Failed to remove %s, this may affect other tests: %s", bad, exc
  166. )
  167. def test_exit_status_unknown_argument(self, salt_cli):
  168. """
  169. Ensure correct exit status when an unknown argument is passed to salt CLI.
  170. """
  171. ret = salt_cli.run("--unknown-argument")
  172. assert ret.exitcode == salt.defaults.exitcodes.EX_USAGE, ret
  173. assert "Usage" in ret.stderr
  174. assert "no such option: --unknown-argument" in ret.stderr
  175. def test_exit_status_correct_usage(self, salt_cli):
  176. """
  177. Ensure correct exit status when salt CLI starts correctly.
  178. """
  179. ret = salt_cli.run("test.ping", minion_tgt="minion")
  180. assert ret.exitcode == salt.defaults.exitcodes.EX_OK, ret