Skip to content

Commit

Permalink
Test that bluechictl monitor receives UnitCreated/UnitRemoved signals
Browse files Browse the repository at this point in the history
Adds a test, which verifies that bluechictl monitor is able to receive
UnitCreated and UnitRemoved signals.

Fixes: #784
Signed-off-by: Martin Perina <mperina@redhat.com>
  • Loading branch information
mwperina committed Mar 13, 2024
1 parent de7d1e9 commit 80eb334
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
summary: Test that bluechictl monitor can receive UnitCreated and UnitRemoved signals
id: 1becd1cf-6f07-4bbb-9b16-ec438bbb6c22
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

import signal
import subprocess
import tempfile
import threading
import time
import unittest

node_name_foo = "node-foo"

service_simple = "simple.service"


class FileFollower:
def __init__(self, file_name):
self.pos = 0
self.file_name = file_name
self.file_desc = None

def __enter__(self):
self.file_desc = open(self.file_name, mode='r')
return self

def __exit__(self, exception_type, exception_value, exception_traceback):
if exception_type or exception_type or exception_traceback:
print(f"Exception raised: exception_type='{exception_type}', "
f"exception_value='{exception_value}', exception_traceback: {exception_traceback}")
if self.file_desc:
self.file_desc.close()

def __iter__(self):
while self.new_lines():
self.seek()
line = self.file_desc.read().split('\n')[0]
yield line

self.pos += len(line) + 1

def seek(self):
self.file_desc.seek(self.pos)

def new_lines(self):
self.seek()
return '\n' in self.file_desc.read()


class TestMonitorSpecificNodeAndUnit(unittest.TestCase):

def setUp(self) -> None:
self.bluechictl_proc = None

self.created = False
self.removed = False

def timeout_guard(self):
time.sleep(10)
print(f"Loop timeout - UnitRemoved signal for service '{service_simple}' on node '{node_name_foo}' "
f"was not successfully received on time")
self.bluechictl_proc.send_signal(signal.SIGINT)
self.bluechictl_proc.wait()

def process_events(self):
out_file = None

with tempfile.NamedTemporaryFile() as out_file:
try:
self.bluechictl_proc = subprocess.Popen(
["/usr/bin/bluechictl", "monitor", f"{node_name_foo}", f"{service_simple}"],
stdout=out_file,
bufsize=1)

with FileFollower(out_file.name) as bluechictl_out:
events_received = False
while not events_received and self.bluechictl_proc.poll() is None:
for line in bluechictl_out:
print(f"Evaluating line '{line}'")
if not self.created and "Unit created (reason: real)" in line:
print(f"Received UnitCreated signal for service '{service_simple}' "
f"on node '{node_name_foo}'")
self.created = True

elif self.created and not self.removed and "Unit removed (reason: real)" in line:
print(f"Received UnitRemoved signal for service '{service_simple}' "
f"on node '{node_name_foo}'")
self.removed = True
events_received = True
break

else:
print(f"Ignoring line '{line}'")

# Wait for the new output from bluechictl monitor
time.sleep(0.5)
finally:
if self.bluechictl_proc:
self.bluechictl_proc.send_signal(signal.SIGINT)
self.bluechictl_proc.wait()
self.bluechictl_proc = None

def test_monitor_specific_node_and_unit(self):
t = threading.Thread(target=self.process_events)
# mark the failsafe thread as daemon so it stops when the main process stops
failsafe_thread = threading.Thread(target=self.timeout_guard, daemon=True)
t.start()
failsafe_thread.start()

t.join()

assert self.created
assert self.removed


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[Unit]
Description=Run a simple service

[Service]
Type=simple
ExecStart=/bin/true
RemainAfterExit=yes
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
import threading
import time

from typing import Dict

from bluechi_test.test import BluechiTest
from bluechi_test.machine import BluechiControllerMachine, BluechiAgentMachine
from bluechi_test.config import BluechiControllerConfig, BluechiAgentConfig

node_name_foo = "node-foo"
service_simple = "simple.service"


def monitor_service(ctrl: BluechiControllerMachine):
result, output = ctrl.run_python(os.path.join("python", "monitor.py"))
if result != 0:
raise Exception(output)


def exec(ctrl: BluechiControllerMachine, nodes: Dict[str, BluechiAgentMachine]):
node_foo = nodes[node_name_foo]
node_foo.copy_systemd_service(service_simple)
assert node_foo.wait_for_unit_state_to_be(service_simple, "inactive")

t = threading.Thread(target=monitor_service, args=(ctrl,))
t.start()

# Wait till monitoring thread on controller starts properly
time.sleep(0.5)

# Running systemctl status on inactive unit should raise UnitCreated and UnitRemoved signals
assert node_foo.systemctl.get_unit_state(service_simple) == "inactive"

t.join()


def test_bluechictl_monitor_unit_created_removed(
bluechi_test: BluechiTest,
bluechi_ctrl_default_config: BluechiControllerConfig,
bluechi_node_default_config: BluechiAgentConfig):

bluechi_node_default_config.node_name = node_name_foo
bluechi_ctrl_default_config.allowed_node_names = [bluechi_node_default_config.node_name]

bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config)
bluechi_test.add_bluechi_agent_config(bluechi_node_default_config)

bluechi_test.run(exec)

0 comments on commit 80eb334

Please sign in to comment.