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