Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: [3.9, 3.11]
python-version: [3.9, 3.12, 3.14]

steps:
- uses: actions/checkout@v2
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Monitor
:target: https://github.com/ReCodEx/wiki/wiki/Changelog

Monitor is an optional part of the ReCodEx solution for reporting progress of
job evaluation back to users in the real time. It is a daemon that reads status messages of all running job evaluations from one ZeroMQ socket and send them to proper WebSocket connection. Monitor is written in Python, tested versions are 3.4 and 3.5.
job evaluation back to users in the real time. It is a daemon that reads status messages of all running job evaluations from one ZeroMQ socket and send them to proper WebSocket connection. Monitor is written in Python (>= 3.9).

There is just one monitor instance required per broker. Also, monitor has to be
publicly visible (has to have public IP address or be behind public proxy
Expand Down Expand Up @@ -64,7 +64,7 @@ Installation will provide you following files:
- `/etc/recodex/monitor/config.yml` -- configuration file
- `/etc/systemd/system/recodex-monitor.service` -- systemd startup script
- code files will be installed in location depending on your system settings,
mostly into `/usr/lib/python3.5/site-packages/monitor/` or similar
mostly into `/usr/lib/python3.9/site-packages/monitor/` or similar

Systemd script runs monitor binary as specific _recodex_ user, so in `postinst`
script user and group of this name are created. Also, ownership of configuration
Expand Down
8 changes: 8 additions & 0 deletions monitor/test/test_ConfigManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_websock_uri_default(self):

def test_websock_uri_loaded(self):
handle, path = tempfile.mkstemp()
os.close(handle)
with open(path, 'w') as f:
f.write('websocket_uri:\n - 77.75.76.3\n - 8080')

Expand All @@ -35,6 +36,7 @@ def test_zeromq_uri_default(self):

def test_zeromq_uri_loaded(self):
handle, path = tempfile.mkstemp()
os.close(handle)
with open(path, 'w') as f:
f.write('zeromq_uri:\n - 77.75.76.3\n - 8080')

Expand All @@ -52,6 +54,7 @@ def test_logger_path_default(self):

def test_logger_path_loaded(self):
handle, path = tempfile.mkstemp()
os.close(handle)
with open(path, 'w') as f:
f.write('logger:\n file: /var/log/tmp/file.log\n level: "debug"\n max-size: 564\n rotations: 7')

Expand All @@ -78,6 +81,7 @@ def test_invalid_path(self):

def test_valid_creation(self):
handle, path = tempfile.mkstemp()
os.close(handle)
logger = init_logger(path, logging.WARNING, 450, 2)
logger.debug("aaa")
logger.warning("bbb")
Expand All @@ -90,4 +94,8 @@ def test_valid_creation(self):
# expect 5 lines - 3 of header and one with 'bbb' and one 'ccc'
self.assertEqual(len(content), 5)

for handler in list(logger.handlers):
handler.close()
logger.removeHandler(handler)

os.remove(path)
21 changes: 13 additions & 8 deletions monitor/test/test_websock_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class TestWebsocketServer(unittest.TestCase):
@patch('asyncio.set_event_loop')
@patch('websockets.serve')
@patch('monitor.websocket_connections.serve')
def test_init(self, mock_websock_serve, mock_set_loop):
loop = MagicMock()
logger = MagicMock()
Expand All @@ -22,7 +22,8 @@ def test_init(self, mock_websock_serve, mock_set_loop):
mock_websock_serve.assert_called_once_with(server.connection_handler, "ip_address", 4512)
loop.run_until_complete.assert_called_once_with("0101")

def test_connection_handler(self):
@patch('monitor.websocket_connections.serve')
def test_connection_handler(self, mock_websock_serve):
queue = asyncio.Queue()
logger = MagicMock()
connection_mock = MagicMock()
Expand All @@ -31,20 +32,22 @@ def test_connection_handler(self):

websocket_mock = MagicMock()

received_id = asyncio.Future()
loop = asyncio.new_event_loop()
received_id = loop.create_future()
received_id.set_result("1234")
response = asyncio.Future()
response = loop.create_future()
response.set_result(None)
websocket_mock.recv.return_value = received_id
websocket_mock.send.return_value = response

loop = asyncio.new_event_loop()
queue.put_nowait("result text")
queue.put_nowait(None)

start_server_future = loop.create_future()
start_server_future.set_result("0101")
mock_websock_serve.return_value = start_server_future
websock_server = WebsocketServer(("localhost", 11111), connection_mock, loop, logger)
# actually call the method
loop.run_until_complete(websock_server.connection_handler(websocket_mock, None))
loop.run_until_complete(websock_server.connection_handler(websocket_mock))

# test the constraints
websocket_mock.recv.assert_called_once_with()
Expand All @@ -53,9 +56,11 @@ def test_connection_handler(self):
connection_mock.remove_client.assert_called_once_with("1234", queue)

@patch('asyncio.set_event_loop')
def test_run(self, mock_set_loop):
@patch('monitor.websocket_connections.serve')
def test_run(self, mock_websock_serve, mock_set_loop):
loop = MagicMock()
logger = MagicMock()
mock_websock_serve.return_value = "0101"
server = WebsocketServer(("ip_address", 123), None, loop, logger)
server.run()
mock_set_loop.assert_called_with(loop)
Expand Down
13 changes: 7 additions & 6 deletions monitor/websocket_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"""

import asyncio
import websockets
import threading

from websockets.asyncio.server import serve
from websockets.exceptions import ConnectionClosed


class ClientConnections:
"""
Expand Down Expand Up @@ -147,16 +149,15 @@ def __init__(self, websock_uri, connections, loop, logger):
self._logger = logger
hostname, port = websock_uri
asyncio.set_event_loop(loop)
start_server = websockets.serve(self.connection_handler, hostname, port)
loop.run_until_complete(start_server)
start_server = serve(self.connection_handler, hostname, port)
self._server = loop.run_until_complete(start_server)
self._logger.info("websocket server initialized at {}:{}".format(hostname, port))

async def connection_handler(self, websocket, path):
async def connection_handler(self, websocket):
"""
Internal asyncio.coroutine function for handling one websocket request.

:param websocket: Socket with request
:param path: Requested path of socket (not used)
:return: Returns when socket is closed or poison pill is found in message queue
from ClientConnections.
"""
Expand All @@ -175,7 +176,7 @@ async def connection_handler(self, websocket, path):
# send message to client
await websocket.send(result)
self._logger.debug("websocket server: message sent to channel '{}'".format(wanted_id))
except websockets.ConnectionClosed:
except ConnectionClosed:
if wanted_id:
self._logger.info("websocket server: connection closed for channel '{}'". format(wanted_id))
finally:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ classifiers = [
]
dependencies = [
"pyzmq",
"websockets==12.0",
"websockets>=14.2",
"PyYAML",
]
requires-python = ">=3.9"
Expand Down
6 changes: 3 additions & 3 deletions recodex-monitor.spec
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
%define name recodex-monitor
%define short_name monitor
%define version 1.2.0
%define unmangled_version 0086f2b6faa213766bc50f2746ee1bf0bdae77c1
%define release 3
%define unmangled_version 3bf3625a97b4970ef2c8b64889e7b32c9ac27e53
%define release 4

Summary: Publish ZeroMQ messages through WebSockets
Name: %{name}
Expand All @@ -26,7 +26,7 @@ Requires(preun): systemd
Requires(postun): systemd
# %{?fedora:Requires: python3-PyYAML python3-websockets python3-zmq}
# %{?rhel:Requires: python3-PyYAML python3-websockets python3-pyzmq}
Requires: python3-PyYAML python3-websockets <= 12.0 python3-pyzmq
Requires: python3-PyYAML python3-websockets >= 14.2 python3-pyzmq

Source0: https://github.com/ReCodEx/%{short_name}/archive/%{unmangled_version}.tar.gz#/%{short_name}-%{unmangled_version}.tar.gz

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
pyzmq

# WebSockets
websockets==12.0
websockets>=14.2

# Configuration parsing
PyYAML
Expand Down