mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Migrate integration.reactor.test_reactor
to PyTest
This commit is contained in:
parent
f90de1d81b
commit
ce1d52ee33
9 changed files with 234 additions and 193 deletions
|
@ -860,9 +860,6 @@ def salt_master_factory(
|
|||
config_defaults["known_hosts_file"] = tests_known_hosts_file
|
||||
config_defaults["syndic_master"] = "localhost"
|
||||
config_defaults["transport"] = salt_syndic_master_factory.config["transport"]
|
||||
config_defaults["reactor"] = [
|
||||
{"salt/test/reactor": [os.path.join(RUNTIME_VARS.FILES, "reactor-test.sls")]}
|
||||
]
|
||||
|
||||
config_overrides = {"log_level_logfile": "quiet"}
|
||||
ext_pillar = []
|
||||
|
|
|
@ -149,7 +149,7 @@ salt/utils/minions.py:
|
|||
- pytests.integration.cli.test_matcher
|
||||
|
||||
salt/utils/reactor.py:
|
||||
- integration.reactor.test_reactor
|
||||
- pytests.integration.reactor.test_reactor
|
||||
|
||||
salt/cli/daemons.py:
|
||||
- pytests.integration.cli.test_salt_master
|
||||
|
|
|
@ -1003,9 +1003,6 @@ class TestDaemon:
|
|||
master_opts.setdefault("reactor", []).append(
|
||||
{"salt/minion/*/start": [os.path.join(FILES, "reactor-sync-minion.sls")]}
|
||||
)
|
||||
master_opts.setdefault("reactor", []).append(
|
||||
{"salt/test/reactor": [os.path.join(FILES, "reactor-test.sls")]}
|
||||
)
|
||||
for opts_dict in (master_opts, syndic_master_opts):
|
||||
if "ext_pillar" not in opts_dict:
|
||||
opts_dict["ext_pillar"] = []
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
reactor-test:
|
||||
runner.event.send:
|
||||
- args:
|
||||
- tag: test_reaction
|
||||
- data:
|
||||
test_reaction: True
|
|
@ -1 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
|
@ -1,178 +0,0 @@
|
|||
"""
|
||||
|
||||
integration.reactor.reactor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test Salt's reactor system
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
import signal
|
||||
|
||||
import pytest
|
||||
import salt.utils.event
|
||||
import salt.utils.reactor
|
||||
from tests.support.case import ShellCase
|
||||
from tests.support.mixins import SaltMinionEventAssertsMixin
|
||||
from tests.support.unit import skipIf
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TimeoutException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.windows_whitelisted
|
||||
class ReactorTest(SaltMinionEventAssertsMixin, ShellCase):
|
||||
"""
|
||||
Test Salt's reactor system
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.timeout = 30
|
||||
|
||||
def get_event(self, class_type="master"):
|
||||
return salt.utils.event.get_event(
|
||||
class_type,
|
||||
sock_dir=self.master_opts["sock_dir"],
|
||||
transport=self.master_opts["transport"],
|
||||
keep_loop=True,
|
||||
opts=self.master_opts,
|
||||
)
|
||||
|
||||
def fire_event(self, tag, data):
|
||||
with self.get_event() as event:
|
||||
event.fire_event(tag, data)
|
||||
|
||||
def alarm_handler(self, signal, frame):
|
||||
raise TimeoutException("Timeout of {} seconds reached".format(self.timeout))
|
||||
|
||||
def test_ping_reaction(self):
|
||||
"""
|
||||
Fire an event on the master and ensure
|
||||
that it pings the minion
|
||||
"""
|
||||
# Create event bus connection
|
||||
with salt.utils.event.get_event(
|
||||
"minion", sock_dir=self.minion_opts["sock_dir"], opts=self.minion_opts
|
||||
) as event:
|
||||
event.fire_event({"a": "b"}, "/test_event")
|
||||
self.assertMinionEventReceived({"a": "b"}, timeout=30)
|
||||
|
||||
@skipIf(salt.utils.platform.is_windows(), "no sigalarm on windows")
|
||||
def test_reactor_reaction(self):
|
||||
"""
|
||||
Fire an event on the master and ensure
|
||||
The reactor event responds
|
||||
"""
|
||||
signal.signal(signal.SIGALRM, self.alarm_handler)
|
||||
signal.alarm(self.timeout)
|
||||
|
||||
with self.get_event() as master_event:
|
||||
master_event.fire_event({"id": "minion"}, "salt/test/reactor")
|
||||
|
||||
try:
|
||||
while True:
|
||||
event = master_event.get_event(full=True)
|
||||
|
||||
if event is None:
|
||||
continue
|
||||
|
||||
if event.get("tag") == "test_reaction":
|
||||
self.assertTrue(event["data"]["test_reaction"])
|
||||
break
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
|
||||
@skipIf(salt.utils.platform.is_windows(), "no sigalarm on windows")
|
||||
def test_reactor_is_leader(self):
|
||||
"""
|
||||
If reactor system is unavailable, an exception is thrown.
|
||||
When leader is true (the default), the reacion event should return.
|
||||
When leader is set to false reactor should timeout/not do anything.
|
||||
"""
|
||||
ret = self.run_run_plus("reactor.is_leader")
|
||||
self.assertIn("CommandExecutionError", ret["return"])
|
||||
|
||||
self.run_run_plus("reactor.set_leader", False)
|
||||
self.assertIn("CommandExecutionError", ret["return"])
|
||||
|
||||
ret = self.run_run_plus("reactor.is_leader")
|
||||
self.assertIn("CommandExecutionError", ret["return"])
|
||||
|
||||
# by default reactor should be leader
|
||||
signal.signal(signal.SIGALRM, self.alarm_handler)
|
||||
signal.alarm(self.timeout)
|
||||
|
||||
# make reactor not the leader
|
||||
# ensure reactor engine is available
|
||||
opts_overrides = {
|
||||
"engines": [
|
||||
{
|
||||
"reactor": {
|
||||
"refresh_interval": 60,
|
||||
"worker_threads": 10,
|
||||
"worker_hwm": 10000,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
self.run_run_plus("reactor.set_leader", False, opts_overrides=opts_overrides)
|
||||
ret = self.run_run_plus("reactor.is_leader", opts_overrides=opts_overrides)
|
||||
self.assertFalse(ret["return"])
|
||||
|
||||
try:
|
||||
with self.get_event() as master_event:
|
||||
self.fire_event({"id": "minion"}, "salt/test/reactor")
|
||||
|
||||
while True:
|
||||
event = master_event.get_event(full=True)
|
||||
|
||||
if event is None:
|
||||
continue
|
||||
|
||||
if event.get("tag") == "test_reaction":
|
||||
# if we reach this point, the test is a failure
|
||||
break
|
||||
except TimeoutException as exc:
|
||||
self.assertTrue("Timeout" in str(exc))
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
|
||||
# make reactor the leader again
|
||||
# ensure reactor engine is available
|
||||
opts_overrides = {
|
||||
"engines": [
|
||||
{
|
||||
"reactor": {
|
||||
"refresh_interval": 60,
|
||||
"worker_threads": 10,
|
||||
"worker_hwm": 10000,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
self.run_run_plus("reactor.set_leader", True, opts_overrides=opts_overrides)
|
||||
ret = self.run_run_plus("reactor.is_leader", opts_overrides=opts_overrides)
|
||||
self.assertTrue(ret["return"])
|
||||
|
||||
# trigger a reaction
|
||||
signal.alarm(self.timeout)
|
||||
|
||||
try:
|
||||
with self.get_event() as master_event:
|
||||
self.fire_event({"id": "minion"}, "salt/test/reactor")
|
||||
|
||||
while True:
|
||||
event = master_event.get_event(full=True)
|
||||
|
||||
if event is None:
|
||||
continue
|
||||
|
||||
if event.get("tag") == "test_reaction":
|
||||
self.assertTrue(event["data"]["test_reaction"])
|
||||
break
|
||||
finally:
|
||||
signal.alarm(0)
|
|
@ -7,6 +7,7 @@ import os
|
|||
import shutil
|
||||
import stat
|
||||
|
||||
import attr
|
||||
import pytest
|
||||
import salt.utils.files
|
||||
import salt.utils.platform
|
||||
|
@ -39,6 +40,38 @@ def vault_port():
|
|||
return get_unused_localhost_port()
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class ReactorEvent:
|
||||
sls_path = attr.ib()
|
||||
tag = attr.ib()
|
||||
event_tag = attr.ib()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def reactor_event(tmp_path_factory):
|
||||
|
||||
reactor_tag = "salt/event/test"
|
||||
event_tag = random_string("test/reaction/")
|
||||
reactors_dir = tmp_path_factory.mktemp("reactors")
|
||||
reactor_test_contents = """
|
||||
reactor-test:
|
||||
runner.event.send:
|
||||
- args:
|
||||
- tag: {}
|
||||
- data:
|
||||
test_reaction: True
|
||||
""".format(
|
||||
event_tag
|
||||
)
|
||||
try:
|
||||
with pytest.helpers.temp_file(
|
||||
"reactor-test.sls", reactor_test_contents, reactors_dir
|
||||
) as reactor_file_path:
|
||||
yield ReactorEvent(reactor_file_path, reactor_tag, event_tag)
|
||||
finally:
|
||||
shutil.rmtree(str(reactors_dir), ignore_errors=True)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def salt_master_factory(
|
||||
request,
|
||||
|
@ -52,6 +85,7 @@ def salt_master_factory(
|
|||
ext_pillar_file_tree_root_dir,
|
||||
sdb_etcd_port,
|
||||
vault_port,
|
||||
reactor_event,
|
||||
):
|
||||
master_id = random_string("master-")
|
||||
root_dir = salt_factories.get_root_dir_for_daemon(master_id)
|
||||
|
@ -70,7 +104,7 @@ def salt_master_factory(
|
|||
config_defaults["syndic_master"] = "localhost"
|
||||
config_defaults["transport"] = request.config.getoption("--transport")
|
||||
config_defaults["reactor"] = [
|
||||
{"salt/test/reactor": [os.path.join(RUNTIME_VARS.FILES, "reactor-test.sls")]}
|
||||
{reactor_event.tag: [str(reactor_event.sls_path)]},
|
||||
]
|
||||
|
||||
nodegroups = {
|
||||
|
|
0
tests/pytests/integration/reactor/__init__.py
Normal file
0
tests/pytests/integration/reactor/__init__.py
Normal file
198
tests/pytests/integration/reactor/test_reactor.py
Normal file
198
tests/pytests/integration/reactor/test_reactor.py
Normal file
|
@ -0,0 +1,198 @@
|
|||
"""
|
||||
tests.pytests.integration.reactor.test_reactor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test Salt's reactor system
|
||||
"""
|
||||
|
||||
import logging
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import salt.utils.event
|
||||
import salt.utils.reactor
|
||||
from salt.serializers import yaml
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.slow_test,
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def event_listener(salt_factories):
|
||||
return salt_factories.event_listener
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def master_event_bus(salt_master):
|
||||
with salt.utils.event.get_master_event(
|
||||
salt_master.config.copy(),
|
||||
salt_master.config["sock_dir"],
|
||||
listen=True,
|
||||
raise_errors=True,
|
||||
) as event:
|
||||
yield event
|
||||
|
||||
|
||||
def test_ping_reaction(event_listener, salt_minion):
|
||||
"""
|
||||
Fire an event on the master and ensure that it pings the minion
|
||||
"""
|
||||
event_tag = "/test_event"
|
||||
start_time = time.time()
|
||||
# Create event bus connection
|
||||
with salt.utils.event.get_event(
|
||||
"minion",
|
||||
sock_dir=salt_minion.config["sock_dir"],
|
||||
opts=salt_minion.config.copy(),
|
||||
) as event:
|
||||
event.fire_event({"a": "b"}, event_tag)
|
||||
|
||||
event_pattern = (salt_minion.id, event_tag)
|
||||
matched_events = event_listener.wait_for_events(
|
||||
[event_pattern], after_time=start_time, timeout=30
|
||||
)
|
||||
assert matched_events.found_all_events
|
||||
for event in matched_events:
|
||||
assert event.data == {"a": "b"}
|
||||
|
||||
|
||||
def test_reactor_reaction(
|
||||
event_listener, salt_master, salt_minion, master_event_bus, reactor_event
|
||||
):
|
||||
"""
|
||||
Fire an event on the master and ensure the reactor event responds
|
||||
"""
|
||||
|
||||
start_time = time.time()
|
||||
master_event_bus.fire_event({"id": salt_minion.id}, reactor_event.tag)
|
||||
event_pattern = (salt_master.id, reactor_event.event_tag)
|
||||
matched_events = event_listener.wait_for_events(
|
||||
[event_pattern], after_time=start_time, timeout=30
|
||||
)
|
||||
assert matched_events.found_all_events
|
||||
for event in matched_events:
|
||||
assert event.data["test_reaction"] is True
|
||||
|
||||
|
||||
def test_reactor_is_leader(
|
||||
event_listener,
|
||||
salt_master,
|
||||
salt_run_cli,
|
||||
master_event_bus,
|
||||
reactor_event,
|
||||
salt_minion,
|
||||
):
|
||||
"""
|
||||
If reactor system is unavailable, an exception is thrown.
|
||||
When leader is true (the default), the reacion event should return.
|
||||
When leader is set to false reactor should timeout/not do anything.
|
||||
"""
|
||||
ret = salt_run_cli.run("reactor.is_leader")
|
||||
assert ret.exitcode == 0
|
||||
assert (
|
||||
"salt.exceptions.CommandExecutionError: Reactor system is not running."
|
||||
in ret.stdout
|
||||
)
|
||||
|
||||
ret = salt_run_cli.run("reactor.set_leader", value=True)
|
||||
assert ret.exitcode == 0
|
||||
assert (
|
||||
"salt.exceptions.CommandExecutionError: Reactor system is not running."
|
||||
in ret.stdout
|
||||
)
|
||||
|
||||
ret = salt_run_cli.run("reactor.is_leader")
|
||||
assert ret.exitcode == 0
|
||||
assert (
|
||||
"salt.exceptions.CommandExecutionError: Reactor system is not running."
|
||||
in ret.stdout
|
||||
)
|
||||
|
||||
# make reactor not the leader; ensure reactor engine is available
|
||||
engines_config = salt_master.config.get("engines").copy()
|
||||
for idx, engine in enumerate(list(engines_config)):
|
||||
if "reactor" in engine:
|
||||
engines_config.pop(idx)
|
||||
|
||||
engines_config.append(
|
||||
{
|
||||
"reactor": {
|
||||
"refresh_interval": 60,
|
||||
"worker_threads": 10,
|
||||
"worker_hwm": 10000,
|
||||
}
|
||||
}
|
||||
)
|
||||
config_overrides = yaml.serialize({"engines": engines_config})
|
||||
confd_dir = (
|
||||
pathlib.Path(salt_master.config_dir)
|
||||
/ pathlib.Path(salt_master.config["default_include"]).parent
|
||||
)
|
||||
confd_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Now, with the temp config in place, ensure the reactor engine is running
|
||||
with pytest.helpers.temp_file("reactor-test.conf", config_overrides, confd_dir):
|
||||
ret = salt_run_cli.run("reactor.set_leader", value=True)
|
||||
assert ret.exitcode == 0
|
||||
assert (
|
||||
"CommandExecutionError" not in ret.stdout
|
||||
), "reactor engine is not running"
|
||||
|
||||
ret = salt_run_cli.run("reactor.is_leader")
|
||||
assert ret.exitcode == 0
|
||||
assert ret.stdout.endswith("\ntrue\n")
|
||||
|
||||
ret = salt_run_cli.run("reactor.set_leader", value=False)
|
||||
assert ret.exitcode == 0
|
||||
|
||||
ret = salt_run_cli.run("reactor.is_leader")
|
||||
assert ret.exitcode == 0
|
||||
assert ret.stdout.endswith("\nfalse\n")
|
||||
|
||||
start_time = time.time()
|
||||
log.warning("START\n\n\n")
|
||||
master_event_bus.fire_event({"id": salt_minion.id}, reactor_event.tag)
|
||||
|
||||
# Since leader is false, let's try and get the fire event to ensure it was triggered
|
||||
event_pattern = (salt_master.id, reactor_event.tag)
|
||||
matched_events = event_listener.wait_for_events(
|
||||
[event_pattern], after_time=start_time, timeout=30
|
||||
)
|
||||
assert matched_events.found_all_events
|
||||
# Now that we matched the trigger event, let's confirm we don't get the reaction event
|
||||
event_pattern = (salt_master.id, reactor_event.event_tag)
|
||||
matched_events = event_listener.wait_for_events(
|
||||
[event_pattern], after_time=start_time, timeout=10
|
||||
)
|
||||
assert matched_events.found_all_events is not True
|
||||
|
||||
# make reactor the leader again; ensure reactor engine is available
|
||||
ret = salt_run_cli.run("reactor.set_leader", value=True)
|
||||
assert ret.exitcode == 0
|
||||
ret = salt_run_cli.run("reactor.is_leader")
|
||||
assert ret.exitcode == 0
|
||||
assert ret.stdout.endswith("\ntrue\n")
|
||||
|
||||
# trigger a reaction
|
||||
start_time = time.time()
|
||||
master_event_bus.fire_event({"id": salt_minion.id}, reactor_event.tag)
|
||||
event_pattern = (salt_master.id, reactor_event.event_tag)
|
||||
matched_events = event_listener.wait_for_events(
|
||||
[event_pattern], after_time=start_time, timeout=30
|
||||
)
|
||||
assert matched_events.found_all_events
|
||||
for event in matched_events:
|
||||
assert event.data["test_reaction"] is True
|
||||
|
||||
# Let's just confirm the engine is not running once again(because the config file is deleted by now)
|
||||
ret = salt_run_cli.run("reactor.is_leader")
|
||||
assert ret.exitcode == 0
|
||||
assert (
|
||||
"salt.exceptions.CommandExecutionError: Reactor system is not running."
|
||||
in ret.stdout
|
||||
)
|
Loading…
Add table
Reference in a new issue