123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- # -*- coding: utf-8 -*-
- """
- :codeauthor: Pedro Algarvio (pedro@algarvio.me)
- tests.integration.shell.minion
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- """
- from __future__ import absolute_import
- import getpass
- import logging
- import os
- import platform
- import sys
- import pytest
- import salt.utils.files
- import salt.utils.platform
- import salt.utils.yaml
- import tests.integration.utils
- from salt.ext import six
- from tests.integration.utils import testprogram
- from tests.support.case import ShellCase
- from tests.support.helpers import slowTest
- from tests.support.mixins import ShellCaseCommonTestsMixin
- from tests.support.runtests import RUNTIME_VARS
- from tests.support.unit import skipIf
- log = logging.getLogger(__name__)
- DEBUG = True
- @pytest.mark.windows_whitelisted
- class MinionTest(ShellCase, testprogram.TestProgramCase, ShellCaseCommonTestsMixin):
- """
- Various integration tests for the salt-minion executable.
- """
- _call_binary_ = "salt-minion"
- _test_minions = (
- "minion",
- "subminion",
- )
- def _run_initscript(
- self, init_script, minions, minion_running, action, exitstatus=None, message=""
- ):
- """
- Wrapper that runs the initscript for the configured minions and
- verifies the results.
- """
- user = getpass.getuser()
- ret = init_script.run(
- [action],
- catch_stderr=True,
- with_retcode=True,
- env={
- "SALTMINION_CONFIGS": "\n".join(
- [
- "{0} {1}".format(user, minion.abs_path(minion.config_dir))
- for minion in minions
- ]
- ),
- },
- timeout=90,
- )
- for line in ret[0]:
- log.debug("script: salt-minion: stdout: {0}".format(line))
- for line in ret[1]:
- log.debug("script: salt-minion: stderr: {0}".format(line))
- log.debug("exit status: {0}".format(ret[2]))
- if six.PY3:
- std_out = b"\nSTDOUT:".join(ret[0])
- std_err = b"\nSTDERR:".join(ret[1])
- else:
- std_out = "\nSTDOUT:".join(ret[0])
- std_err = "\nSTDERR:".join(ret[1])
- # Check minion state
- for minion in minions:
- self.assertEqual(
- minion.is_running(),
- minion_running,
- 'script action "{0}" should result in minion "{1}" {2} and is not.\nSTDOUT:{3}\nSTDERR:{4}'.format(
- action,
- minion.name,
- ["stopped", "running"][minion_running],
- std_out,
- std_err,
- ),
- )
- if exitstatus is not None:
- self.assertEqual(
- ret[2],
- exitstatus,
- 'script action "{0}" {1} exited {2}, must be {3}\nSTDOUT:{4}\nSTDERR:{5}'.format(
- action, message, ret[2], exitstatus, std_out, std_err,
- ),
- )
- return ret
- def _initscript_setup(self, minions):
- """Re-usable setup for running salt-minion tests"""
- _minions = []
- for mname in minions:
- pid_file = "salt-{0}.pid".format(mname)
- minion = testprogram.TestDaemonSaltMinion(
- name=mname,
- root_dir="init_script",
- config_dir=os.path.join("etc", mname),
- parent_dir=self._test_dir,
- pid_file=pid_file,
- configs={
- "minion": {
- "map": {
- "pidfile": os.path.join("var", "run", pid_file),
- "sock_dir": os.path.join("var", "run", "salt", mname),
- "log_file": os.path.join("var", "log", "salt", mname),
- },
- },
- },
- )
- # Call setup here to ensure config and script exist
- minion.setup()
- _minions.append(minion)
- # Need salt-call, salt-minion for wrapper script
- salt_call = testprogram.TestProgramSaltCall(
- root_dir="init_script", parent_dir=self._test_dir
- )
- # Ensure that run-time files are generated
- salt_call.setup()
- sysconf_dir = os.path.dirname(_minions[0].abs_path(_minions[0].config_dir))
- cmd_env = {
- "PATH": ":".join(
- [salt_call.abs_path(salt_call.script_dir), os.getenv("PATH")]
- ),
- "SALTMINION_DEBUG": "1" if DEBUG else "",
- "SALTMINION_PYTHON": sys.executable,
- "SALTMINION_SYSCONFDIR": sysconf_dir,
- "SALTMINION_BINDIR": _minions[0].abs_path(_minions[0].script_dir),
- }
- default_dir = os.path.join(sysconf_dir, "default")
- if not os.path.exists(default_dir):
- os.makedirs(default_dir)
- with salt.utils.files.fopen(os.path.join(default_dir, "salt"), "w") as defaults:
- # Test suites is quite slow - extend the timeout
- defaults.write("TIMEOUT=60\n" "TICK=1\n")
- init_script = testprogram.TestProgram(
- name="init:salt-minion",
- program=os.path.join(RUNTIME_VARS.CODE_DIR, "pkg", "rpm", "salt-minion"),
- env=cmd_env,
- )
- return _minions, salt_call, init_script
- @skipIf(True, "Disabled. Test suite hanging")
- def test_linux_initscript(self):
- """
- Various tests of the init script to verify that it properly controls a salt minion.
- """
- pform = platform.uname()[0].lower()
- if pform not in ("linux",):
- self.skipTest(
- "salt-minion init script is unavailable on {1}".format(platform)
- )
- minions, _, init_script = self._initscript_setup(self._test_minions)
- try:
- # These tests are grouped together, rather than split into individual test functions,
- # because subsequent tests leverage the state from the previous test which minimizes
- # setup for each test.
- # I take visual readability with aligned columns over strict PEP8
- # (bad-whitespace) Exactly one space required after comma
- # pylint: disable=C0326
- ret = self._run_initscript(
- init_script, minions[:1], False, "bogusaction", 2
- )
- ret = self._run_initscript(
- init_script, minions[:1], False, "reload", 3
- ) # Not implemented
- ret = self._run_initscript(
- init_script, minions[:1], False, "stop", 0, "when not running"
- )
- ret = self._run_initscript(
- init_script, minions[:1], False, "status", 3, "when not running"
- )
- ret = self._run_initscript(
- init_script, minions[:1], False, "condrestart", 7, "when not running"
- )
- ret = self._run_initscript(
- init_script, minions[:1], False, "try-restart", 7, "when not running"
- )
- ret = self._run_initscript(
- init_script, minions, True, "start", 0, "when not running"
- )
- ret = self._run_initscript(
- init_script, minions, True, "status", 0, "when running"
- )
- # Verify that PIDs match
- mpids = {}
- for line in ret[0]:
- segs = line.decode(__salt_system_encoding__).split()
- minfo = segs[0].split(":")
- mpids[minfo[-1]] = int(segs[-1]) if segs[-1].isdigit() else None
- for minion in minions:
- self.assertEqual(
- minion.daemon_pid,
- mpids[minion.name],
- 'PID in "{0}" is {1} and does not match status PID {2}'.format(
- minion.abs_path(minion.pid_path),
- minion.daemon_pid,
- mpids[minion.name],
- ),
- )
- ret = self._run_initscript(
- init_script, minions, True, "start", 0, "when running"
- )
- ret = self._run_initscript(
- init_script, minions, True, "condrestart", 0, "when running"
- )
- ret = self._run_initscript(
- init_script, minions, True, "try-restart", 0, "when running"
- )
- ret = self._run_initscript(
- init_script, minions, False, "stop", 0, "when running"
- )
- finally:
- # Ensure that minions are shutdown
- for minion in minions:
- minion.shutdown()
- @skipIf(salt.utils.platform.is_windows(), "Skip on Windows OS")
- @slowTest
- def test_exit_status_unknown_user(self):
- """
- Ensure correct exit status when the minion is configured to run as an unknown user.
- Skipped on windows because daemonization not supported
- """
- minion = testprogram.TestDaemonSaltMinion(
- name="unknown_user",
- configs={"minion": {"map": {"user": "some_unknown_user_xyz"}}},
- parent_dir=self._test_dir,
- )
- # Call setup here to ensure config and script exist
- minion.setup()
- stdout, stderr, status = minion.run(
- args=["-d"], catch_stderr=True, with_retcode=True,
- )
- try:
- self.assert_exit_status(
- status,
- "EX_NOUSER",
- message="unknown user not on system",
- stdout=stdout,
- stderr=tests.integration.utils.decode_byte_list(stderr),
- )
- finally:
- # Although the start-up should fail, call shutdown() to set the
- # internal _shutdown flag and avoid the registered atexit calls to
- # cause timeout exceptions and respective traceback
- minion.shutdown()
- # @skipIf(salt.utils.platform.is_windows(), 'Skip on Windows OS')
- @slowTest
- def test_exit_status_unknown_argument(self):
- """
- Ensure correct exit status when an unknown argument is passed to salt-minion.
- """
- minion = testprogram.TestDaemonSaltMinion(
- name="unknown_argument", parent_dir=self._test_dir,
- )
- # Call setup here to ensure config and script exist
- minion.setup()
- stdout, stderr, status = minion.run(
- args=["-d", "--unknown-argument"], catch_stderr=True, with_retcode=True,
- )
- try:
- self.assert_exit_status(
- status,
- "EX_USAGE",
- message="unknown argument",
- stdout=stdout,
- stderr=tests.integration.utils.decode_byte_list(stderr),
- )
- finally:
- # Although the start-up should fail, call shutdown() to set the
- # internal _shutdown flag and avoid the registered atexit calls to
- # cause timeout exceptions and respective traceback
- minion.shutdown()
- @skipIf(salt.utils.platform.is_windows(), "Skip on Windows OS")
- @slowTest
- def test_exit_status_correct_usage(self):
- """
- Ensure correct exit status when salt-minion starts correctly.
- Skipped on windows because daemonization not supported
- """
- minion = testprogram.TestDaemonSaltMinion(
- name="correct_usage", parent_dir=self._test_dir,
- )
- # Call setup here to ensure config and script exist
- minion.setup()
- stdout, stderr, status = minion.run(
- args=["-d"], catch_stderr=True, with_retcode=True,
- )
- self.assert_exit_status(
- status, "EX_OK", message="correct usage", stdout=stdout, stderr=stderr
- )
- minion.shutdown(wait_for_orphans=3)
|