test_call.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Pedro Algarvio (pedro@algarvio.me)
  4. tests.integration.shell.call
  5. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. """
  7. from __future__ import absolute_import
  8. import logging
  9. import os
  10. import re
  11. import shutil
  12. import sys
  13. import pytest
  14. import salt.utils.files
  15. import salt.utils.json
  16. import salt.utils.platform
  17. import salt.utils.yaml
  18. from salt.ext import six
  19. from tests.integration.utils import testprogram
  20. from tests.support.case import ShellCase
  21. from tests.support.helpers import flaky, with_tempfile
  22. from tests.support.mixins import ShellCaseCommonTestsMixin
  23. from tests.support.runtests import RUNTIME_VARS
  24. from tests.support.unit import skipIf
  25. log = logging.getLogger(__name__)
  26. @pytest.mark.windows_whitelisted
  27. class CallTest(ShellCase, testprogram.TestProgramCase, ShellCaseCommonTestsMixin):
  28. _call_binary_ = "salt-call"
  29. @skipIf(True, "SLOWTEST skip")
  30. def test_default_output(self):
  31. out = self.run_call("-l quiet test.fib 3")
  32. expect = ["local:", " - 2"]
  33. self.assertEqual(expect, out[:-1])
  34. @skipIf(True, "SLOWTEST skip")
  35. def test_text_output(self):
  36. out = self.run_call("-l quiet --out txt test.fib 3")
  37. expect = ["local: (2"]
  38. self.assertEqual("".join(expect), "".join(out).rsplit(",", 1)[0])
  39. @skipIf(True, "SLOWTEST skip")
  40. def test_json_out_indent(self):
  41. out = self.run_call("test.ping -l quiet --out=json --out-indent=-1")
  42. self.assertIn('"local": true', "".join(out))
  43. out = self.run_call("test.ping -l quiet --out=json --out-indent=0")
  44. self.assertIn('"local": true', "".join(out))
  45. out = self.run_call("test.ping -l quiet --out=json --out-indent=1")
  46. self.assertIn('"local": true', "".join(out))
  47. @skipIf(True, "SLOWTEST skip")
  48. def test_local_sls_call(self):
  49. fileroot = os.path.join(RUNTIME_VARS.FILES, "file", "base")
  50. out = self.run_call(
  51. "--file-root {0} state.sls saltcalllocal".format(fileroot), local=True
  52. )
  53. self.assertIn("Name: test.echo", "".join(out))
  54. self.assertIn("Result: True", "".join(out))
  55. self.assertIn("hello", "".join(out))
  56. self.assertIn("Succeeded: 1", "".join(out))
  57. @with_tempfile()
  58. @skipIf(True, "SLOWTEST skip")
  59. def test_local_salt_call(self, name):
  60. """
  61. This tests to make sure that salt-call does not execute the
  62. function twice, see https://github.com/saltstack/salt/pull/49552
  63. """
  64. def _run_call(cmd):
  65. cmd = "--out=json " + cmd
  66. return salt.utils.json.loads("".join(self.run_call(cmd, local=True)))[
  67. "local"
  68. ]
  69. ret = _run_call('state.single file.append name={0} text="foo"'.format(name))
  70. ret = ret[next(iter(ret))]
  71. # Make sure we made changes
  72. assert ret["changes"]
  73. # 2nd sanity check: make sure that "foo" only exists once in the file
  74. with salt.utils.files.fopen(name) as fp_:
  75. contents = fp_.read()
  76. assert contents.count("foo") == 1, contents
  77. @skipIf(
  78. salt.utils.platform.is_windows() or salt.utils.platform.is_darwin(),
  79. "This test requires a supported master",
  80. )
  81. @skipIf(True, "SLOWTEST skip")
  82. def test_user_delete_kw_output(self):
  83. ret = self.run_call("-l quiet -d user.delete")
  84. assert "salt '*' user.delete name remove=True force=True" in "".join(ret)
  85. @skipIf(True, "SLOWTEST skip")
  86. def test_salt_documentation_too_many_arguments(self):
  87. """
  88. Test to see if passing additional arguments shows an error
  89. """
  90. data = self.run_call("-d virtualenv.create /tmp/ve", catch_stderr=True)
  91. self.assertIn(
  92. "You can only get documentation for one method at one time",
  93. "\n".join(data[1]),
  94. )
  95. @skipIf(True, "SLOWTEST skip")
  96. def test_issue_6973_state_highstate_exit_code(self):
  97. """
  98. If there is no tops/master_tops or state file matches
  99. for this minion, salt-call should exit non-zero if invoked with
  100. option --retcode-passthrough
  101. """
  102. src = os.path.join(RUNTIME_VARS.BASE_FILES, "top.sls")
  103. dst = os.path.join(RUNTIME_VARS.BASE_FILES, "top.sls.bak")
  104. shutil.move(src, dst)
  105. expected_comment = "No states found for this minion"
  106. try:
  107. stdout, retcode = self.run_call(
  108. "-l quiet --retcode-passthrough state.highstate", with_retcode=True
  109. )
  110. finally:
  111. shutil.move(dst, src)
  112. self.assertIn(expected_comment, "".join(stdout))
  113. self.assertNotEqual(0, retcode)
  114. @skipIf(sys.platform.startswith("win"), "This test does not apply on Win")
  115. @skipIf(True, "to be re-enabled when #23623 is merged")
  116. def test_return(self):
  117. self.run_call('cmd.run "echo returnTOmaster"')
  118. jobs = [a for a in self.run_run("jobs.list_jobs")]
  119. self.assertTrue(True in ["returnTOmaster" in j for j in jobs])
  120. # lookback jid
  121. first_match = [(i, j) for i, j in enumerate(jobs) if "returnTOmaster" in j][0]
  122. jid, idx = None, first_match[0]
  123. while idx > 0:
  124. jid = re.match("([0-9]+):", jobs[idx])
  125. if jid:
  126. jid = jid.group(1)
  127. break
  128. idx -= 1
  129. assert idx > 0
  130. assert jid
  131. master_out = [a for a in self.run_run("jobs.lookup_jid {0}".format(jid))]
  132. self.assertTrue(True in ["returnTOmaster" in a for a in master_out])
  133. @skipIf(salt.utils.platform.is_windows(), "Skip on Windows")
  134. @skipIf(True, "SLOWTEST skip")
  135. def test_syslog_file_not_found(self):
  136. """
  137. test when log_file is set to a syslog file that does not exist
  138. """
  139. old_cwd = os.getcwd()
  140. config_dir = os.path.join(RUNTIME_VARS.TMP, "log_file_incorrect")
  141. if not os.path.isdir(config_dir):
  142. os.makedirs(config_dir)
  143. os.chdir(config_dir)
  144. with salt.utils.files.fopen(self.get_config_file_path("minion"), "r") as fh_:
  145. minion_config = salt.utils.yaml.load(fh_.read())
  146. minion_config["log_file"] = "file:///dev/doesnotexist"
  147. with salt.utils.files.fopen(os.path.join(config_dir, "minion"), "w") as fh_:
  148. fh_.write(salt.utils.yaml.dump(minion_config, default_flow_style=False))
  149. ret = self.run_script(
  150. "salt-call",
  151. '--config-dir {0} cmd.run "echo foo"'.format(config_dir),
  152. timeout=120,
  153. catch_stderr=True,
  154. with_retcode=True,
  155. )
  156. try:
  157. if sys.version_info >= (3, 5, 4):
  158. self.assertIn("local:", ret[0])
  159. self.assertIn(
  160. "[WARNING ] The log_file does not exist. Logging not setup correctly or syslog service not started.",
  161. ret[1],
  162. )
  163. self.assertEqual(ret[2], 0)
  164. else:
  165. self.assertIn(
  166. "Failed to setup the Syslog logging handler", "\n".join(ret[1])
  167. )
  168. self.assertEqual(ret[2], 2)
  169. finally:
  170. self.chdir(old_cwd)
  171. if os.path.isdir(config_dir):
  172. shutil.rmtree(config_dir)
  173. @skipIf(True, "This test is unreliable. Need to investigate why more deeply.")
  174. @flaky
  175. def test_issue_15074_output_file_append(self):
  176. output_file_append = os.path.join(RUNTIME_VARS.TMP, "issue-15074")
  177. try:
  178. # Let's create an initial output file with some data
  179. _ = self.run_script(
  180. "salt-call",
  181. "-c {0} --output-file={1} test.versions".format(
  182. RUNTIME_VARS.TMP_MINION_CONF_DIR, output_file_append
  183. ),
  184. catch_stderr=True,
  185. with_retcode=True,
  186. )
  187. with salt.utils.files.fopen(output_file_append) as ofa:
  188. output = ofa.read()
  189. self.run_script(
  190. "salt-call",
  191. "-c {0} --output-file={1} --output-file-append test.versions".format(
  192. self.config_dir, output_file_append
  193. ),
  194. catch_stderr=True,
  195. with_retcode=True,
  196. )
  197. with salt.utils.files.fopen(output_file_append) as ofa:
  198. self.assertEqual(ofa.read(), output + output)
  199. finally:
  200. if os.path.exists(output_file_append):
  201. os.unlink(output_file_append)
  202. @skipIf(True, "This test is unreliable. Need to investigate why more deeply.")
  203. @flaky
  204. def test_issue_14979_output_file_permissions(self):
  205. output_file = os.path.join(RUNTIME_VARS.TMP, "issue-14979")
  206. with salt.utils.files.set_umask(0o077):
  207. try:
  208. # Let's create an initial output file with some data
  209. self.run_script(
  210. "salt-call",
  211. "-c {0} --output-file={1} -l trace -g".format(
  212. RUNTIME_VARS.TMP_MINION_CONF_DIR, output_file
  213. ),
  214. catch_stderr=True,
  215. with_retcode=True,
  216. )
  217. try:
  218. stat1 = os.stat(output_file)
  219. except OSError:
  220. self.fail("Failed to generate output file, see log for details")
  221. # Let's change umask
  222. os.umask(0o777) # pylint: disable=blacklisted-function
  223. self.run_script(
  224. "salt-call",
  225. "-c {0} --output-file={1} --output-file-append -g".format(
  226. RUNTIME_VARS.TMP_MINION_CONF_DIR, output_file
  227. ),
  228. catch_stderr=True,
  229. with_retcode=True,
  230. )
  231. try:
  232. stat2 = os.stat(output_file)
  233. except OSError:
  234. self.fail("Failed to generate output file, see log for details")
  235. self.assertEqual(stat1.st_mode, stat2.st_mode)
  236. # Data was appeneded to file
  237. self.assertTrue(stat1.st_size < stat2.st_size)
  238. # Let's remove the output file
  239. os.unlink(output_file)
  240. # Not appending data
  241. self.run_script(
  242. "salt-call",
  243. "-c {0} --output-file={1} -g".format(
  244. RUNTIME_VARS.TMP_MINION_CONF_DIR, output_file
  245. ),
  246. catch_stderr=True,
  247. with_retcode=True,
  248. )
  249. try:
  250. stat3 = os.stat(output_file)
  251. except OSError:
  252. self.fail("Failed to generate output file, see log for details")
  253. # Mode must have changed since we're creating a new log file
  254. self.assertNotEqual(stat1.st_mode, stat3.st_mode)
  255. finally:
  256. if os.path.exists(output_file):
  257. os.unlink(output_file)
  258. @skipIf(sys.platform.startswith("win"), "This test does not apply on Win")
  259. @skipIf(True, "SLOWTEST skip")
  260. def test_42116_cli_pillar_override(self):
  261. ret = self.run_call(
  262. "state.apply issue-42116-cli-pillar-override "
  263. 'pillar=\'{"myhost": "localhost"}\''
  264. )
  265. for line in ret:
  266. line = line.lstrip()
  267. if line == 'Comment: Command "ping -c 2 localhost" run':
  268. # Successful test
  269. break
  270. else:
  271. log.debug("salt-call output:\n\n%s", "\n".join(ret))
  272. self.fail("CLI pillar override not found in pillar data")
  273. @skipIf(True, "SLOWTEST skip")
  274. def test_pillar_items_masterless(self):
  275. """
  276. Test to ensure we get expected output
  277. from pillar.items with salt-call
  278. """
  279. get_items = self.run_call("pillar.items", local=True)
  280. exp_out = [
  281. " - Lancelot",
  282. " - Galahad",
  283. " - Bedevere",
  284. " monty:",
  285. " python",
  286. ]
  287. for out in exp_out:
  288. self.assertIn(out, get_items)
  289. def tearDown(self):
  290. """
  291. Teardown method to remove installed packages
  292. """
  293. user = ""
  294. user_info = self.run_call(" grains.get username", local=True)
  295. if (
  296. user_info
  297. and isinstance(user_info, (list, tuple))
  298. and isinstance(user_info[-1], six.string_types)
  299. ):
  300. user = user_info[-1].strip()
  301. super(CallTest, self).tearDown()
  302. @skipIf(True, "SLOWTEST skip")
  303. def test_exit_status_unknown_argument(self):
  304. """
  305. Ensure correct exit status when an unknown argument is passed to salt-call.
  306. """
  307. call = testprogram.TestProgramSaltCall(
  308. name="unknown_argument", parent_dir=self._test_dir,
  309. )
  310. # Call setup here to ensure config and script exist
  311. call.setup()
  312. stdout, stderr, status = call.run(
  313. args=["--unknown-argument"], catch_stderr=True, with_retcode=True,
  314. )
  315. self.assert_exit_status(
  316. status, "EX_USAGE", message="unknown argument", stdout=stdout, stderr=stderr
  317. )
  318. @skipIf(True, "SLOWTEST skip")
  319. def test_masterless_highstate(self):
  320. """
  321. test state.highstate in masterless mode
  322. """
  323. ret = self.run_call("state.highstate", local=True)
  324. destpath = os.path.join(RUNTIME_VARS.TMP, "testfile")
  325. exp_out = [
  326. " Function: file.managed",
  327. " Result: True",
  328. " ID: {0}".format(destpath),
  329. ]
  330. for out in exp_out:
  331. self.assertIn(out, ret)
  332. self.assertTrue(os.path.exists(destpath))
  333. @skipIf(True, "SLOWTEST skip")
  334. def test_exit_status_correct_usage(self):
  335. """
  336. Ensure correct exit status when salt-call starts correctly.
  337. """
  338. call = testprogram.TestProgramSaltCall(
  339. name="correct_usage", parent_dir=self._test_dir,
  340. )
  341. # Call setup here to ensure config and script exist
  342. call.setup()
  343. stdout, stderr, status = call.run(
  344. args=["--local", "test.true"], catch_stderr=True, with_retcode=True,
  345. )
  346. self.assert_exit_status(
  347. status, "EX_OK", message="correct usage", stdout=stdout, stderr=stderr
  348. )