Adjust the whole framework to event handler dependency inversion

This commit is contained in:
Kristóf Tóth
2019-07-12 23:25:16 +02:00
parent 42beafcf04
commit de78f48930
26 changed files with 169 additions and 254 deletions

View File

@ -1,7 +1,5 @@
from .directory_snapshotting_event_handler import DirectorySnapshottingEventHandler
from .event_handler import EventHandler
from .frontend_event_handler import FrontendEventHandler
from .fsm_aware_event_handler import FSMAwareEventHandler
from .fsm_managing_event_handler import FSMManagingEventHandler
from .ide_event_handler import IdeEventHandler
from .log_monitoring_event_handler import LogMonitoringEventHandler
@ -10,4 +8,3 @@ from .pipe_io_event_handler import TransformerPipeIOEventHandler, CommandEventHa
from .process_managing_event_handler import ProcessManagingEventHandler
from .terminal_commands_event_handler import TerminalCommandsEventHandler
from .terminal_event_handler import TerminalEventHandler
from .tfw_server_connector import TFWServerUplinkConnector, TFWServerConnector

View File

@ -10,14 +10,14 @@ from tfw.components.snapshot_provider import SnapshotProvider
from tfw.config import TFWENV
from tfw.networking import Scope
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
class DirectorySnapshottingEventHandler(EventHandler):
def __init__(self, key, directories, exclude_unix_patterns=None):
super().__init__(key, scope=Scope.WEBSOCKET)
class DirectorySnapshottingEventHandler:
keys = ['snapshot']
def __init__(self, directories, exclude_unix_patterns=None):
self.snapshot_providers = {}
self._exclude_unix_patterns = exclude_unix_patterns
self.init_snapshot_providers(directories)
@ -46,11 +46,11 @@ class DirectorySnapshottingEventHandler(EventHandler):
makedirs(git_dir, exist_ok=True)
return git_dir
def handle_event(self, message):
def handle_event(self, message, server_connector):
try:
data = message['data']
message['data'] = self.command_handlers[data['command']](data)
self.send_message(message)
server_connector.send_message(message, scope=Scope.WEBSOCKET)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)

View File

@ -1,9 +0,0 @@
from tfw.event_handlers import EventHandlerBase
from .tfw_server_connector import TFWServerConnector
class EventHandler(EventHandlerBase):
# pylint: disable=abstract-method
def _build_server_connector(self):
return TFWServerConnector()

View File

@ -1,16 +1,19 @@
from tfw.networking import Scope
from tfw.components import FrontendMessageStorage
from .event_handler import EventHandler
class FrontendEventHandler:
keys = ['message', 'queueMessages', 'dashboard', 'console']
class FrontendEventHandler(EventHandler):
def __init__(self):
frontend_keys = ('message', 'queueMessages', 'dashboard', 'console')
self._frontend_message_storage = FrontendMessageStorage(frontend_keys)
super().__init__((*frontend_keys, 'recover'), scope=Scope.WEBSOCKET)
self.server_connector = None
self.keys = [*type(self).keys, 'recover']
self._frontend_message_storage = FrontendMessageStorage(type(self).keys)
def handle_event(self, message):
def send_message(self, message):
self.server_connector.send_message(message, scope=Scope.WEBSOCKET)
def handle_event(self, message, _):
self._frontend_message_storage.save_message(message)
if message['key'] == 'recover':
self.recover_frontend()

View File

@ -1,20 +0,0 @@
from tfw.components import FSMAware
from tfw.networking import Scope
from .event_handler import EventHandler
class FSMAwareEventHandler(EventHandler, FSMAware):
# pylint: disable=abstract-method
"""
Abstract base class for EventHandlers which automatically
keep track of the state of the TFW FSM.
"""
def __init__(self, key, scope=Scope.ZMQ):
EventHandler.__init__(self, key, scope=scope)
FSMAware.__init__(self)
self.subscribe('fsm_update')
def dispatch_handling(self, message):
if not self.refresh_on_fsm_update(message):
super().dispatch_handling(message)

View File

@ -4,12 +4,12 @@ from tfw.crypto import KeyManager, sign_message, verify_message
from tfw.networking import Scope
from tfw.components import FSMUpdater
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
class FSMManagingEventHandler(EventHandler):
class FSMManagingEventHandler:
keys = ['fsm']
"""
EventHandler responsible for managing the state machine of
the framework (TFW FSM).
@ -24,8 +24,7 @@ class FSMManagingEventHandler(EventHandler):
An 'fsm_update' message is broadcasted after every successful
command.
"""
def __init__(self, key, fsm_type, require_signature=False):
super().__init__(key, scope=Scope.WEBSOCKET)
def __init__(self, fsm_type, require_signature=False):
self.fsm = fsm_type()
self._fsm_updater = FSMUpdater(self.fsm)
self.auth_key = KeyManager().auth_key
@ -36,15 +35,14 @@ class FSMManagingEventHandler(EventHandler):
'update': self.handle_update
}
def handle_event(self, message):
def handle_event(self, message, server_connector):
try:
message = self.command_handlers[message['data']['command']](message)
if message:
fsm_update_message = self._fsm_updater.fsm_update
sign_message(self.auth_key, message)
sign_message(self.auth_key, fsm_update_message)
self.server_connector.send_message(fsm_update_message, Scope.BROADCAST)
self.send_message(message)
server_connector.send_message(fsm_update_message, Scope.BROADCAST)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)

View File

@ -4,7 +4,6 @@ from tfw.networking import Scope
from tfw.components import FileManager
from tfw.components.inotify import InotifyObserver
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
@ -32,7 +31,8 @@ BUILD_ARTIFACTS = (
)
class IdeEventHandler(EventHandler):
class IdeEventHandler:
keys = ['ide']
# pylint: disable=too-many-arguments,anomalous-backslash-in-string
"""
Event handler implementing the backend of our browser based IDE.
@ -47,7 +47,7 @@ class IdeEventHandler(EventHandler):
The API of each command is documented in their respective handler.
"""
def __init__(self, key, directory, allowed_directories, selected_file=None, exclude=None):
def __init__(self, directory, allowed_directories, selected_file=None, exclude=None):
"""
:param key: the key this instance should listen to
:param directory: working directory which the EventHandler should serve files from
@ -55,7 +55,7 @@ class IdeEventHandler(EventHandler):
:param selected_file: file that is selected by default
:param exclude: list of filenames that should not appear between files (for .o, .pyc, etc.)
"""
super().__init__(key, scope=Scope.WEBSOCKET)
self.server_connector = None
try:
self.filemanager = FileManager(
allowed_directories=allowed_directories,
@ -84,10 +84,13 @@ class IdeEventHandler(EventHandler):
}
def _reload_frontend(self, event): # pylint: disable=unused-argument
self.server_connector.send_message({
self.send_message({
'key': 'ide',
'data': {'command': 'reload'}
}, Scope.WEBSOCKET)
})
def send_message(self, message):
self.server_connector.send_message(message, scope=Scope.WEBSOCKET)
def read(self, data):
"""
@ -179,7 +182,7 @@ class IdeEventHandler(EventHandler):
data['files'] = self.filemanager.files
data['directory'] = self.filemanager.workdir
def handle_event(self, message):
def handle_event(self, message, _):
try:
data = message['data']
message['data'] = self.commands[data['command']](data)

View File

@ -1,15 +1,14 @@
import logging
from tfw.config import TFWENV
from tfw.networking import Scope
from tfw.components import LogInotifyObserver
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
class LogMonitoringEventHandler(EventHandler):
class LogMonitoringEventHandler:
keys = ['logmonitor']
"""
Monitors the output of a supervisor process (stdout, stderr) and
sends the results to the frontend.
@ -19,23 +18,27 @@ class LogMonitoringEventHandler(EventHandler):
The API of each command is documented in their respective handler.
"""
def __init__(self, key, process_name, log_tail=0):
super().__init__(key, scope=Scope.WEBSOCKET)
def __init__(self, process_name, log_tail=0):
self.server_connector = None
self.process_name = process_name
self._monitor = LogInotifyObserver(
server_connector=self.server_connector,
supervisor_uri=TFWENV.SUPERVISOR_HTTP_URI,
process_name=process_name,
log_tail=log_tail
)
self._monitor.start()
self._initial_log_tail = log_tail
self._monitor = None
self.command_handlers = {
'process_name': self.handle_process_name,
'log_tail': self.handle_log_tail
}
def handle_event(self, message):
def start(self):
self._monitor = LogInotifyObserver(
server_connector=self.server_connector,
supervisor_uri=TFWENV.SUPERVISOR_HTTP_URI,
process_name=self.process_name,
log_tail=self._initial_log_tail
)
self._monitor.start()
def handle_event(self, message, _):
try:
data = message['data']
self.command_handlers[data['command']](data)

View File

@ -12,15 +12,16 @@ from contextlib import suppress
from tfw.components.pipe_io_server import PipeIOServer, terminate_process_on_failure
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
DEFAULT_PERMISSIONS = 0o600
class PipeIOEventHandlerBase(EventHandler):
def __init__(self, key, in_pipe_path, out_pipe_path, permissions=DEFAULT_PERMISSIONS):
super().__init__(key)
class PipeIOEventHandlerBase:
keys = ['']
def __init__(self, in_pipe_path, out_pipe_path, permissions=DEFAULT_PERMISSIONS):
self.server_connector = None
self.pipe_io = CallbackPipeIOServer(
in_pipe_path,
out_pipe_path,
@ -50,25 +51,25 @@ class CallbackPipeIOServer(PipeIOServer):
class PipeIOEventHandler(PipeIOEventHandlerBase):
def handle_event(self, message):
def handle_event(self, message, _):
json_bytes = dumps(message).encode()
self.pipe_io.send_message(json_bytes)
def handle_pipe_event(self, message_bytes):
json = loads(message_bytes)
self.send_message(json)
self.server_connector.send_message(json)
class TransformerPipeIOEventHandler(PipeIOEventHandlerBase):
# pylint: disable=too-many-arguments
def __init__(
self, key, in_pipe_path, out_pipe_path,
self, in_pipe_path, out_pipe_path,
transform_in_cmd, transform_out_cmd,
permissions=DEFAULT_PERMISSIONS
):
self._transform_in = partial(self._transform_message, transform_in_cmd)
self._transform_out = partial(self._transform_message, transform_out_cmd)
super().__init__(key, in_pipe_path, out_pipe_path, permissions)
super().__init__(in_pipe_path, out_pipe_path, permissions)
@staticmethod
def _transform_message(transform_cmd, message):
@ -83,7 +84,7 @@ class TransformerPipeIOEventHandler(PipeIOEventHandlerBase):
return proc.stdout
raise ValueError(f'Transforming message {message} failed!')
def handle_event(self, message):
def handle_event(self, message, _):
json_bytes = dumps(message).encode()
transformed_bytes = self._transform_out(json_bytes)
if transformed_bytes:
@ -93,13 +94,12 @@ class TransformerPipeIOEventHandler(PipeIOEventHandlerBase):
transformed_bytes = self._transform_in(message_bytes)
if transformed_bytes:
json_message = loads(transformed_bytes)
self.send_message(json_message)
self.server_connector.send_message(json_message)
class CommandEventHandler(PipeIOEventHandler):
def __init__(self, key, command, permissions=DEFAULT_PERMISSIONS):
def __init__(self, command, permissions=DEFAULT_PERMISSIONS):
super().__init__(
key,
self._generate_tempfilename(),
self._generate_tempfilename(),
permissions

View File

@ -5,12 +5,12 @@ from tfw.config import TFWENV
from tfw.networking import Scope
from tfw.components import ProcessManager, LogManager
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
class ProcessManagingEventHandler(EventHandler, ProcessManager, LogManager):
class ProcessManagingEventHandler(ProcessManager, LogManager):
keys = ['processmanager']
"""
Event handler that can manage processes managed by supervisor.
@ -23,8 +23,7 @@ class ProcessManagingEventHandler(EventHandler, ProcessManager, LogManager):
Commands available: start, stop, restart, readlog
(the names are as self-documenting as it gets)
"""
def __init__(self, key, log_tail=0):
EventHandler.__init__(self, key, scope=Scope.WEBSOCKET)
def __init__(self, log_tail=0):
ProcessManager.__init__(self, TFWENV.SUPERVISOR_HTTP_URI)
LogManager.__init__(self, TFWENV.SUPERVISOR_HTTP_URI)
self.log_tail = log_tail
@ -34,7 +33,7 @@ class ProcessManagingEventHandler(EventHandler, ProcessManager, LogManager):
'restart': self.restart_process
}
def handle_event(self, message):
def handle_event(self, message, server_connector):
try:
data = message['data']
try:
@ -50,6 +49,6 @@ class ProcessManagingEventHandler(EventHandler, ProcessManager, LogManager):
data['process_name'],
self.log_tail
)
self.send_message(message)
server_connector.send_message(message, scope=Scope.WEBSOCKET)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)

View File

@ -1,14 +1,9 @@
from tfw.components import TerminalCommands
from tfw.networking import Scope
from .event_handler import EventHandler
class TerminalCommandsEventHandler(EventHandler, TerminalCommands):
def __init__(self, key, scope=Scope.ZMQ, bashrc=None):
EventHandler.__init__(self, key, scope)
TerminalCommands.__init__(self, bashrc)
class TerminalCommandsEventHandler(TerminalCommands):
keys = ['history.bash']
def handle_event(self, message):
def handle_event(self, message, _):
command = message['value']
self.callback(command)

View File

@ -1,16 +1,15 @@
import logging
from tfw.networking import Scope
from tfw.components import BashMonitor, TerminadoMiniServer
from tfw.config import TFWENV
from tao.config import TAOENV
from .event_handler import EventHandler
LOG = logging.getLogger(__name__)
class TerminalEventHandler(EventHandler):
class TerminalEventHandler:
keys = ['shell']
"""
Event handler responsible for managing terminal sessions for frontend xterm
sessions to connect to. You need to instanciate this in order for frontend
@ -20,13 +19,13 @@ class TerminalEventHandler(EventHandler):
a command to be executed.
The API of each command is documented in their respective handler.
"""
def __init__(self, key):
def __init__(self):
"""
:param key: key this EventHandler listens to
:param monitor: tfw.components.HistoryMonitor instance to read command history from
"""
super().__init__(key, scope=Scope.WEBSOCKET)
self._historymonitor = BashMonitor(self.server_connector, TFWENV.HISTFILE)
self.server_connector = None
self._historymonitor = None
bash_as_user_cmd = ['sudo', '-u', TAOENV.USER, 'bash']
self.terminado_server = TerminadoMiniServer(
@ -41,18 +40,20 @@ class TerminalEventHandler(EventHandler):
'read': self.read
}
self._historymonitor.start()
self.terminado_server.listen()
def start(self):
self._historymonitor = BashMonitor(self.server_connector, TFWENV.HISTFILE)
self._historymonitor.start()
@property
def historymonitor(self):
return self._historymonitor
def handle_event(self, message):
def handle_event(self, message, _):
try:
data = message['data']
message['data'] = self.commands[data['command']](data)
self.send_message(message)
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)

View File

@ -1,25 +0,0 @@
from tfw.networking import ServerUplinkConnector, ServerConnector
from tfw.config import TFWENV
class ConnAddrMixin:
@property
def uplink_conn_addr(self):
return f'tcp://localhost:{TFWENV.PULL_PORT}'
@property
def downlink_conn_addr(self):
return f'tcp://localhost:{TFWENV.PUB_PORT}'
class TFWServerUplinkConnector(ServerUplinkConnector, ConnAddrMixin):
def __init__(self):
super().__init__(self.uplink_conn_addr)
class TFWServerConnector(ServerConnector, ConnAddrMixin):
def __init__(self):
super().__init__(
self.downlink_conn_addr,
self.uplink_conn_addr
)