diff --git a/src/bindings/python/setup.py b/src/bindings/python/setup.py index bb844cf32d..1b51bea863 100644 --- a/src/bindings/python/setup.py +++ b/src/bindings/python/setup.py @@ -9,7 +9,7 @@ def readme(): setup( name="bluechi", - version="0.6.0", + version="0.7.0", description="Python bindings for BlueChi's D-Bus API", long_description=readme(), long_description_content_type="text/markdown", diff --git a/src/manager/monitor.c b/src/manager/monitor.c index cb9d960a71..090eeb2a2d 100644 --- a/src/manager/monitor.c +++ b/src/manager/monitor.c @@ -214,7 +214,7 @@ void monitor_close(Monitor *monitor) { MonitorPeer *peer = NULL; MonitorPeer *next_peer = NULL; LIST_FOREACH_SAFE(peers, peer, next_peer, monitor->peers) { - monitor_emit_peer_removed(monitor, peer, "closed"); + monitor_emit_peer_removed(monitor, peer, "monitor closed"); LIST_REMOVE(peers, monitor->peers, peer); free_and_null(peer->name); free_and_null(peer); @@ -421,6 +421,23 @@ static MonitorPeer *monitor_add_peer(Monitor *monitor, const char *peer_name) { return peer; } +static bool monitor_has_peer_with_name(Monitor *monitor, const char *peer_name) { + if (streq(monitor->owner, peer_name)) { + return true; + } + + MonitorPeer *peer = NULL; + MonitorPeer *next_peer = NULL; + LIST_FOREACH_SAFE(peers, peer, next_peer, monitor->peers) { + if (streq(peer->name, peer_name)) { + return true; + } + } + bc_log_debugf("Peer with name '%s' for monitor '%s' not found", peer_name, monitor->object_path); + + return false; +} + static int monitor_method_add_peer(sd_bus_message *m, void *userdata, UNUSED sd_bus_error *ret_error) { Monitor *monitor = userdata; @@ -443,6 +460,11 @@ static int monitor_method_add_peer(sd_bus_message *m, void *userdata, UNUSED sd_ peer_name); } + if (monitor_has_peer_with_name(monitor, peer_name)) { + return sd_bus_reply_method_errorf( + m, SD_BUS_ERROR_INVALID_ARGS, "Peer name '%s' has already been added", peer_name); + } + MonitorPeer *peer = monitor_add_peer(monitor, peer_name); if (peer == NULL) { return sd_bus_reply_method_errorf( @@ -534,6 +556,8 @@ int monitor_on_unit_property_changed( monitor->object_path, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } MonitorPeer *peer = NULL; @@ -548,6 +572,8 @@ int monitor_on_unit_property_changed( peer->name, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } } @@ -569,6 +595,8 @@ int monitor_on_unit_new(void *userdata, const char *node, const char *unit, cons monitor->object_path, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } MonitorPeer *peer = NULL; @@ -583,6 +611,8 @@ int monitor_on_unit_new(void *userdata, const char *node, const char *unit, cons peer->name, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } } @@ -610,6 +640,8 @@ int monitor_on_unit_state_changed( monitor->object_path, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } MonitorPeer *peer = NULL; @@ -624,6 +656,8 @@ int monitor_on_unit_state_changed( peer->name, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } } @@ -645,6 +679,8 @@ int monitor_on_unit_removed(void *userdata, const char *node, const char *unit, monitor->object_path, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } MonitorPeer *peer = NULL; @@ -659,6 +695,8 @@ int monitor_on_unit_removed(void *userdata, const char *node, const char *unit, peer->name, strerror(-r)); } + sd_bus_message_unrefp(&sig); + sig = NULL; } } diff --git a/tests/bluechi_test/container.py b/tests/bluechi_test/container.py index 99d554d877..bb261f6150 100644 --- a/tests/bluechi_test/container.py +++ b/tests/bluechi_test/container.py @@ -107,6 +107,17 @@ def copy_systemd_service(self, service_file_name: str, source_dir: str, target_d self.create_file(target_dir, service_file_name, content) self.systemctl_daemon_reload() + def copy_container_script(self, script_file_name: str): + curr_dir = os.getcwd() + source_path = os.path.join(curr_dir, "..", "..", "..", "bluechi_test", "container_scripts", script_file_name) + content = read_file(source_path) + + target_dir = os.path.join("/", "var") + + LOGGER.info(f"Copy container script '{source_path}' to container path '{curr_dir}'\ + with content:\n{content}") + self.create_file(target_dir, script_file_name, content) + def service_is_active(self, unit_name: str) -> bool: result, _ = self.exec_run(f"systemctl is-active {unit_name}") return result == 0 diff --git a/tests/bluechi_test/container_scripts/create_monitor.py b/tests/bluechi_test/container_scripts/create_monitor.py new file mode 100644 index 0000000000..505683acb7 --- /dev/null +++ b/tests/bluechi_test/container_scripts/create_monitor.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import sys +from bluechi.api import Manager, Monitor +from dasbus.loop import EventLoop + +if len(sys.argv) != 3: + print("Usage: python create_monitor.py ") + exit(1) + +node = sys.argv[1] +service = sys.argv[2] + +loop = EventLoop() +mgr = Manager() +monitor_path = mgr.create_monitor() +monitor = Monitor(monitor_path) +monitor.subscribe(node, service) + +print(monitor_path, flush=True) + +loop.run() diff --git a/tests/bluechi_test/container_scripts/listen.py b/tests/bluechi_test/container_scripts/listen.py new file mode 100644 index 0000000000..105ff32c0e --- /dev/null +++ b/tests/bluechi_test/container_scripts/listen.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import sys +from bluechi.api import Monitor +from dasbus.loop import EventLoop + +if len(sys.argv) != 2: + sys.exit(1) + +monitor_path = sys.argv[1] + +loop = EventLoop() + +monitor = Monitor(monitor_path) + + +def on_new(node, unit, reason): + print(f"Unit New: {node} -- {unit}", flush=True) + + +def on_removed(node, unit, reason): + print(f"Unit Removed: {node} -- {unit}", flush=True) + + +def on_state(node, unit, active, sub, reason): + print(f"Unit state: {node} -- {unit}", flush=True) + + +def on_props(node, unit, interface, props): + print(f"Unit props: {node} -- {unit}", flush=True) + + +monitor.on_unit_new(on_new) +monitor.on_unit_removed(on_removed) +monitor.on_unit_state_changed(on_state) +monitor.on_unit_properties_changed(on_props) + + +def on_removed(reason): + print(f"Removed, reason: {reason}", flush=True) + loop.quit() + + +monitor.get_proxy().PeerRemoved.connect(on_removed) + +print(monitor.bus.connection.get_unique_name(), flush=True) + +loop.run() diff --git a/tests/tests/tier0/monitor-listener-added-as-peer/main.fmf b/tests/tests/tier0/monitor-listener-added-as-peer/main.fmf new file mode 100644 index 0000000000..8f38799195 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-as-peer/main.fmf @@ -0,0 +1,2 @@ +summary: Test if a listener on a monitor does receive signals from it when added as peer +id: 624ba8ae-cf8e-4b17-a8c5-bb09a47ae507 diff --git a/tests/tests/tier0/monitor-listener-added-as-peer/python/added_as_peer.py b/tests/tests/tier0/monitor-listener-added-as-peer/python/added_as_peer.py new file mode 100644 index 0000000000..bbf152b092 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-as-peer/python/added_as_peer.py @@ -0,0 +1,102 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import time +import threading +import unittest +from typing import List +from queue import Queue, Empty +from subprocess import Popen, PIPE + + +from bluechi.api import Node, Monitor + +node = "node-foo" +service = "simple.service" + + +class TestListenerAddedAsPeer(unittest.TestCase): + + def test_listener_added_as_peer(self): + + process_monitor_owner = Popen(["python3", "/var/create_monitor.py", node, + service], stdout=PIPE, universal_newlines=True) + monitor_path = process_monitor_owner.stdout.readline().strip() + + process_monitor_listener = Popen(["python3", "/var/listen.py", monitor_path], + stdout=PIPE, universal_newlines=True) + bus_id = process_monitor_listener.stdout.readline().strip() + + # add listener as peer so it receives signals from the monitor + monitor = Monitor(monitor_path) + monitor.add_peer(bus_id) + + # start the unit on the node monitored + node_foo = Node(node) + node_foo.start_unit(service, "replace") + + events = self.read_no_wait(process_monitor_listener.stdout) + assert len(events) > 0 + + unit_new_found = False + unit_removed_found = False + unit_state_changed_found = False + unit_prop_changed_found = False + for event in events: + if event.startswith("Unit New:"): + unit_new_found = True + elif event.startswith("Unit Removed:"): + unit_removed_found = True + elif event.startswith("Unit state:"): + unit_state_changed_found = True + elif event.startswith("Unit props:"): + unit_prop_changed_found = True + + assert unit_new_found + assert unit_removed_found + assert unit_state_changed_found + assert unit_prop_changed_found + + process_monitor_owner.terminate() + process_monitor_listener.terminate() + + ################## + # Helper functions + + worker_queue: Queue = None + worker: threading.Thread = None + + def read_no_wait(self, out) -> List[str]: + """ + Helper function. + stdout.readline() is a blocking call, use threads to make it non-blocking. + """ + + def enqueue_output(out, queue): + while True: + event = out.readline().strip() + if event == "": + break + queue.put(event) + + if self.worker_queue is None: + self.worker_queue = Queue() + if self.worker is None: + self.worker = threading.Thread(target=enqueue_output, args=(out, self.worker_queue)) + self.worker.daemon = True + self.worker.start() + + # wait a bit for the new worker to catch up + time.sleep(0.5) + + res = [] + while True: + try: + res.append(self.worker_queue.get_nowait()) + except Empty: + break + + return res + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tests/tier0/monitor-listener-added-as-peer/systemd/simple.service b/tests/tests/tier0/monitor-listener-added-as-peer/systemd/simple.service new file mode 100644 index 0000000000..c3413f8891 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-as-peer/systemd/simple.service @@ -0,0 +1,6 @@ +[Unit] +Description=Just being true once + +[Service] +Type=simple +ExecStart=/bin/true diff --git a/tests/tests/tier0/monitor-listener-added-as-peer/test_monitor_listener_added.py b/tests/tests/tier0/monitor-listener-added-as-peer/test_monitor_listener_added.py new file mode 100644 index 0000000000..9fad675e4a --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-as-peer/test_monitor_listener_added.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +from typing import Dict + +from bluechi_test.test import BluechiTest +from bluechi_test.container import BluechiControllerContainer, BluechiNodeContainer +from bluechi_test.config import BluechiControllerConfig, BluechiNodeConfig + + +node_name_foo = "node-foo" +service_simple = "simple.service" + + +def exec(ctrl: BluechiControllerContainer, nodes: Dict[str, BluechiNodeContainer]): + + nodes[node_name_foo].copy_systemd_service( + service_simple, "systemd", os.path.join("/", "etc", "systemd", "system")) + assert nodes[node_name_foo].wait_for_unit_state_to_be(service_simple, "inactive") + + # copy prepared python scripts into container + # will be run by not_added_as_peer.py script to create two processes with different bus ids + ctrl.copy_container_script("create_monitor.py") + ctrl.copy_container_script("listen.py") + + result, output = ctrl.run_python(os.path.join("python", "added_as_peer.py")) + if result != 0: + raise Exception(output) + + +def test_monitor_listener_added( + bluechi_test: BluechiTest, + bluechi_ctrl_default_config: BluechiControllerConfig, + bluechi_node_default_config: BluechiNodeConfig): + + config_node_foo = bluechi_node_default_config.deep_copy() + config_node_foo.node_name = node_name_foo + + bluechi_ctrl_default_config.allowed_node_names = [node_name_foo] + + bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config) + bluechi_test.add_bluechi_node_config(config_node_foo) + + bluechi_test.run(exec) diff --git a/tests/tests/tier0/monitor-listener-added-more-than-once/main.fmf b/tests/tests/tier0/monitor-listener-added-more-than-once/main.fmf new file mode 100644 index 0000000000..842d07cfc0 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-more-than-once/main.fmf @@ -0,0 +1,2 @@ +summary: Test if a listener on a monitor can only be added once added +id: 5e694ded-ca05-48f9-94d4-c801e9595f5e diff --git a/tests/tests/tier0/monitor-listener-added-more-than-once/python/added_more_than_once.py b/tests/tests/tier0/monitor-listener-added-more-than-once/python/added_more_than_once.py new file mode 100644 index 0000000000..2f164f81af --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-more-than-once/python/added_more_than_once.py @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import unittest +from subprocess import Popen, PIPE + +from bluechi.api import Monitor +from dasbus.error import DBusError + +node = "node-foo" +service = "simple.service" + + +class TestListenerAddedMoreThanOnce(unittest.TestCase): + + def test_listener_added_more_than_once(self): + + process_monitor_owner = Popen(["python3", "/var/create_monitor.py", node, + service], stdout=PIPE, universal_newlines=True) + monitor_path = process_monitor_owner.stdout.readline().strip() + + process_monitor_listener = Popen(["python3", "/var/listen.py", monitor_path], + stdout=PIPE, universal_newlines=True) + bus_id = process_monitor_listener.stdout.readline().strip() + + monitor = Monitor(monitor_path) + monitor.add_peer(bus_id) + with self.assertRaises(DBusError): + monitor.add_peer(bus_id) + + process_monitor_owner.terminate() + process_monitor_listener.terminate() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tests/tier0/monitor-listener-added-more-than-once/test_monitor_listener_added_more_than_once.py b/tests/tests/tier0/monitor-listener-added-more-than-once/test_monitor_listener_added_more_than_once.py new file mode 100644 index 0000000000..46ac32bf19 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-added-more-than-once/test_monitor_listener_added_more_than_once.py @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +from typing import Dict + +from bluechi_test.test import BluechiTest +from bluechi_test.container import BluechiControllerContainer, BluechiNodeContainer +from bluechi_test.config import BluechiControllerConfig, BluechiNodeConfig + + +node_name_foo = "node-foo" +service_simple = "simple.service" + + +def exec(ctrl: BluechiControllerContainer, nodes: Dict[str, BluechiNodeContainer]): + # copy prepared python scripts into container + # will be run by not_added_as_peer.py script to create two processes with different bus ids + ctrl.copy_container_script("create_monitor.py") + ctrl.copy_container_script("listen.py") + + result, output = ctrl.run_python(os.path.join("python", "added_more_than_once.py")) + if result != 0: + raise Exception(output) + + +def test_monitor_listener_added_more_than_once( + bluechi_test: BluechiTest, + bluechi_ctrl_default_config: BluechiControllerConfig, + bluechi_node_default_config: BluechiNodeConfig): + + config_node_foo = bluechi_node_default_config.deep_copy() + config_node_foo.node_name = node_name_foo + + bluechi_ctrl_default_config.allowed_node_names = [node_name_foo] + + bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config) + bluechi_test.add_bluechi_node_config(config_node_foo) + + bluechi_test.run(exec) diff --git a/tests/tests/tier0/monitor-listener-not-added-as-peer/main.fmf b/tests/tests/tier0/monitor-listener-not-added-as-peer/main.fmf new file mode 100644 index 0000000000..fb41f4932d --- /dev/null +++ b/tests/tests/tier0/monitor-listener-not-added-as-peer/main.fmf @@ -0,0 +1,3 @@ +summary: Test if a listener on a monitor does not receive signals from it when not + added +id: 7c84300a-4cd7-4184-8b75-5b574b8c3037 diff --git a/tests/tests/tier0/monitor-listener-not-added-as-peer/python/not_added_as_peer.py b/tests/tests/tier0/monitor-listener-not-added-as-peer/python/not_added_as_peer.py new file mode 100644 index 0000000000..036ad6b567 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-not-added-as-peer/python/not_added_as_peer.py @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import time +import threading +import unittest +from typing import List +from queue import Queue, Empty +from subprocess import Popen, PIPE + +from bluechi.api import Node + +node = "node-foo" +service = "simple.service" + + +class TestListenerNotAddedAsPeer(unittest.TestCase): + + def test_listener_not_added_as_peer(self): + + process_monitor_owner = Popen(["python3", "/var/create_monitor.py", node, + service], stdout=PIPE, universal_newlines=True) + monitor_path = process_monitor_owner.stdout.readline().strip() + + process_monitor_listener = Popen(["python3", "/var/listen.py", monitor_path], + stdout=PIPE, universal_newlines=True) + process_monitor_listener.stdout.readline().strip() + + # start the unit on the node monitored and wait a bit for all signals to be processed + node_foo = Node(node) + node_foo.start_unit(service, "replace") + time.sleep(1) + + received_events = self.read_no_wait(process_monitor_listener.stdout) + # the listener is not added as peer to the monitor, + # so it shouldn't receive any signal and Empty exception is raised + assert len(received_events) == 0 + + process_monitor_owner.terminate() + process_monitor_listener.terminate() + + ################## + # Helper functions + + worker_queue: Queue = None + worker: threading.Thread = None + + def read_no_wait(self, out) -> List[str]: + """ + Helper function. + stdout.readline() is a blocking call, use threads to make it non-blocking. + """ + + def enqueue_output(out, queue): + while True: + event = out.readline().strip() + if event == "": + break + queue.put(event) + + if self.worker_queue is None: + self.worker_queue = Queue() + if self.worker is None: + self.worker = threading.Thread(target=enqueue_output, args=(out, self.worker_queue)) + self.worker.daemon = True + self.worker.start() + + # wait a bit for the new worker to catch up + time.sleep(0.5) + + res = [] + while True: + try: + res.append(self.worker_queue.get_nowait()) + except Empty: + break + + return res + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tests/tier0/monitor-listener-not-added-as-peer/systemd/simple.service b/tests/tests/tier0/monitor-listener-not-added-as-peer/systemd/simple.service new file mode 100644 index 0000000000..c3413f8891 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-not-added-as-peer/systemd/simple.service @@ -0,0 +1,6 @@ +[Unit] +Description=Just being true once + +[Service] +Type=simple +ExecStart=/bin/true diff --git a/tests/tests/tier0/monitor-listener-not-added-as-peer/test_monitor_listener_not_added.py b/tests/tests/tier0/monitor-listener-not-added-as-peer/test_monitor_listener_not_added.py new file mode 100644 index 0000000000..68a5090b3b --- /dev/null +++ b/tests/tests/tier0/monitor-listener-not-added-as-peer/test_monitor_listener_not_added.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +from typing import Dict + +from bluechi_test.test import BluechiTest +from bluechi_test.container import BluechiControllerContainer, BluechiNodeContainer +from bluechi_test.config import BluechiControllerConfig, BluechiNodeConfig + + +node_name_foo = "node-foo" +service_simple = "simple.service" + + +def exec(ctrl: BluechiControllerContainer, nodes: Dict[str, BluechiNodeContainer]): + + nodes[node_name_foo].copy_systemd_service( + service_simple, "systemd", os.path.join("/", "etc", "systemd", "system")) + assert nodes[node_name_foo].wait_for_unit_state_to_be(service_simple, "inactive") + + # copy prepared python scripts into container + # will be run by not_added_as_peer.py script to create two processes with different bus ids + ctrl.copy_container_script("create_monitor.py") + ctrl.copy_container_script("listen.py") + + result, output = ctrl.run_python(os.path.join("python", "not_added_as_peer.py")) + if result != 0: + raise Exception(output) + + +def test_monitor_listener_not_added( + bluechi_test: BluechiTest, + bluechi_ctrl_default_config: BluechiControllerConfig, + bluechi_node_default_config: BluechiNodeConfig): + + config_node_foo = bluechi_node_default_config.deep_copy() + config_node_foo.node_name = node_name_foo + + bluechi_ctrl_default_config.allowed_node_names = [node_name_foo] + + bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config) + bluechi_test.add_bluechi_node_config(config_node_foo) + + bluechi_test.run(exec) diff --git a/tests/tests/tier0/monitor-listener-owner-added-as-peer/main.fmf b/tests/tests/tier0/monitor-listener-owner-added-as-peer/main.fmf new file mode 100644 index 0000000000..53b34eef50 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-owner-added-as-peer/main.fmf @@ -0,0 +1,2 @@ +summary: Test if the listener to add on a monitor is the owner will be rejected added +id: d5bb97f6-8832-44b8-977b-7bb3089b36ed diff --git a/tests/tests/tier0/monitor-listener-owner-added-as-peer/python/added_owner_as_peer.py b/tests/tests/tier0/monitor-listener-owner-added-as-peer/python/added_owner_as_peer.py new file mode 100644 index 0000000000..c0ec896d69 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-owner-added-as-peer/python/added_owner_as_peer.py @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import unittest + +from bluechi.api import Manager, Monitor +from dasbus.error import DBusError + +node = "node-foo" +service = "simple.service" + + +class TestListenerAddedIsOwner(unittest.TestCase): + + def test_listener_added_is_owner(self): + + mgr = Manager() + monitor_path = mgr.create_monitor() + monitor = Monitor(monitor_path) + + with self.assertRaises(DBusError): + monitor.add_peer(mgr.bus.connection.get_unique_name()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tests/tier0/monitor-listener-owner-added-as-peer/test_monitor_listener_owner_added.py b/tests/tests/tier0/monitor-listener-owner-added-as-peer/test_monitor_listener_owner_added.py new file mode 100644 index 0000000000..c71959956c --- /dev/null +++ b/tests/tests/tier0/monitor-listener-owner-added-as-peer/test_monitor_listener_owner_added.py @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +from typing import Dict + +from bluechi_test.test import BluechiTest +from bluechi_test.container import BluechiControllerContainer, BluechiNodeContainer +from bluechi_test.config import BluechiControllerConfig, BluechiNodeConfig + + +node_name_foo = "node-foo" +service_simple = "simple.service" + + +def exec(ctrl: BluechiControllerContainer, nodes: Dict[str, BluechiNodeContainer]): + result, output = ctrl.run_python(os.path.join("python", "added_owner_as_peer.py")) + if result != 0: + raise Exception(output) + + +def test_monitor_listener_added_more_than_once( + bluechi_test: BluechiTest, + bluechi_ctrl_default_config: BluechiControllerConfig, + bluechi_node_default_config: BluechiNodeConfig): + + config_node_foo = bluechi_node_default_config.deep_copy() + config_node_foo.node_name = node_name_foo + + bluechi_ctrl_default_config.allowed_node_names = [node_name_foo] + + bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config) + bluechi_test.add_bluechi_node_config(config_node_foo) + + bluechi_test.run(exec) diff --git a/tests/tests/tier0/monitor-listener-removed-as-peer/main.fmf b/tests/tests/tier0/monitor-listener-removed-as-peer/main.fmf new file mode 100644 index 0000000000..12a1ab45ec --- /dev/null +++ b/tests/tests/tier0/monitor-listener-removed-as-peer/main.fmf @@ -0,0 +1,2 @@ +summary: Test if a listener on a monitor does receive remove signal, but no other signals after removing it as peer +id: b7b061ec-7a6b-495b-aef7-b791fb9eb251 diff --git a/tests/tests/tier0/monitor-listener-removed-as-peer/python/removed_as_peer.py b/tests/tests/tier0/monitor-listener-removed-as-peer/python/removed_as_peer.py new file mode 100644 index 0000000000..60514b6b74 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-removed-as-peer/python/removed_as_peer.py @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import time +import threading +import unittest +from typing import List +from queue import Queue, Empty +from subprocess import Popen, PIPE + +from bluechi.api import Node, Monitor + +node = "node-foo" +service = "simple.service" + + +class TestListenerAddedAsPeer(unittest.TestCase): + + def test_listener_added_as_peer(self): + + process_monitor_owner = Popen(["python3", "/var/create_monitor.py", node, + service], stdout=PIPE, universal_newlines=True) + monitor_path = process_monitor_owner.stdout.readline().strip() + + process_monitor_listener = Popen(["python3", "/var/listen.py", monitor_path], + stdout=PIPE, universal_newlines=True) + bus_id = process_monitor_listener.stdout.readline().strip() + + # add listener as peer so it receives signals from the monitor + monitor = Monitor(monitor_path) + peer_id = monitor.add_peer(bus_id) + + # start the unit on the node monitored + node_foo = Node(node) + node_foo.start_unit(service, "replace") + + events = self.read_no_wait(process_monitor_listener.stdout) + assert len(events) > 0 + + # remove listener as peer again + remove_reason = "for integration test" + monitor.remove_peer(peer_id, remove_reason) + + # start the unit on the node monitored another time and + # wait a bit for all signals to be processed + node_foo = Node(node) + node_foo.start_unit(service, "replace") + time.sleep(1) + + events = self.read_no_wait(process_monitor_listener.stdout) + assert len(events) == 1 + assert events[0] == f"Removed, reason: {remove_reason}" + + process_monitor_owner.terminate() + process_monitor_listener.terminate() + + ################## + # Helper functions + + worker_queue: Queue = None + worker: threading.Thread = None + + def read_no_wait(self, out) -> List[str]: + """ + Helper function. + stdout.readline() is a blocking call, use threads to make it non-blocking. + """ + + def enqueue_output(out, queue): + while True: + event = out.readline().strip() + if event == "": + break + queue.put(event) + + if self.worker_queue is None: + self.worker_queue = Queue() + if self.worker is None: + self.worker = threading.Thread(target=enqueue_output, args=(out, self.worker_queue)) + self.worker.daemon = True + self.worker.start() + + # wait a bit for the new worker to catch up + time.sleep(0.5) + + res = [] + while True: + try: + res.append(self.worker_queue.get_nowait()) + except Empty: + break + + return res + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tests/tier0/monitor-listener-removed-as-peer/systemd/simple.service b/tests/tests/tier0/monitor-listener-removed-as-peer/systemd/simple.service new file mode 100644 index 0000000000..c3413f8891 --- /dev/null +++ b/tests/tests/tier0/monitor-listener-removed-as-peer/systemd/simple.service @@ -0,0 +1,6 @@ +[Unit] +Description=Just being true once + +[Service] +Type=simple +ExecStart=/bin/true diff --git a/tests/tests/tier0/monitor-listener-removed-as-peer/test_monitor_listener_removed.py b/tests/tests/tier0/monitor-listener-removed-as-peer/test_monitor_listener_removed.py new file mode 100644 index 0000000000..1196b9083d --- /dev/null +++ b/tests/tests/tier0/monitor-listener-removed-as-peer/test_monitor_listener_removed.py @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +from typing import Dict + +from bluechi_test.test import BluechiTest +from bluechi_test.container import BluechiControllerContainer, BluechiNodeContainer +from bluechi_test.config import BluechiControllerConfig, BluechiNodeConfig + + +node_name_foo = "node-foo" +service_simple = "simple.service" + + +def exec(ctrl: BluechiControllerContainer, nodes: Dict[str, BluechiNodeContainer]): + + nodes[node_name_foo].copy_systemd_service( + service_simple, "systemd", os.path.join("/", "etc", "systemd", "system")) + assert nodes[node_name_foo].wait_for_unit_state_to_be(service_simple, "inactive") + + # copy prepared python scripts into container + # will be run by not_added_as_peer.py script to create two processes with different bus ids + ctrl.copy_container_script("create_monitor.py") + ctrl.copy_container_script("listen.py") + + result, output = ctrl.run_python(os.path.join("python", "removed_as_peer.py")) + if result != 0: + raise Exception(output) + + +def test_monitor_listener_removed( + bluechi_test: BluechiTest, + bluechi_ctrl_default_config: BluechiControllerConfig, + bluechi_node_default_config: BluechiNodeConfig): + + config_node_foo = bluechi_node_default_config.deep_copy() + config_node_foo.node_name = node_name_foo + + bluechi_ctrl_default_config.allowed_node_names = [node_name_foo] + + bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config) + bluechi_test.add_bluechi_node_config(config_node_foo) + + bluechi_test.run(exec)