From ea0e73fbcd1f13b4c0c72c732d9eead6f9af0e81 Mon Sep 17 00:00:00 2001 From: Jonathan Weth Date: Thu, 1 Jul 2021 19:01:48 +0200 Subject: Refactor ID system --- rwa/support/sessionservice/service.py | 156 +++++++++++++++++++++------------- rwa/support/sessionservice/session.py | 27 ++++-- test_client.py | 63 ++++++++++---- 3 files changed, 165 insertions(+), 81 deletions(-) diff --git a/rwa/support/sessionservice/service.py b/rwa/support/sessionservice/service.py index 471d599..9a843ae 100755 --- a/rwa/support/sessionservice/service.py +++ b/rwa/support/sessionservice/service.py @@ -30,6 +30,7 @@ import signal import time from threading import Thread from typing import Dict, Union +from uuid import uuid4 import click import dbus @@ -43,7 +44,7 @@ from gi.repository import GLib from .config import ALLOW_ONLY_ONE_SESSION, API_PATH, SUPPORTED_API_VERSIONS from .lock import is_locked, lock, unlock from .log import logging -from .session import Session +from .session import Session, combine from .trigger import TriggerServerThread @@ -76,9 +77,14 @@ class RWASupportSessionService(dbus.service.Object): self.sessions = {} self.settings = usersettings.Settings("org.ArcticaProject.RWASupportSessionService") - self.settings.add_setting("web_app_hosts", list, ["http://127.0.0.1:8000"]) + self.settings.add_setting("web_app_hosts", dict) self.settings.load_settings() + # Ensure default value for web app hosts settings + if not self.settings.web_app_hosts: + self.settings.web_app_hosts = {} + self.settings.save_settings() + super().__init__(name, "/RWASupportSessionService") logging.info("D-Bus service has been started.") @@ -98,9 +104,15 @@ class RWASupportSessionService(dbus.service.Object): Helper function: No D-Bus API. """ - hosts = self.settings.web_app_hosts + hosts = [ + self._build_host_dict(key, value) for key, value in self.settings.web_app_hosts.items() + ] return json.dumps(hosts) + def _build_host_dict(self, host_id: str, host: dict) -> dict: + """Include the host ID in the host dictionary.""" + return host | {"id": host_id} + @dbus.service.method("org.ArcticaProject.RWASupportSessionService", out_signature="s") def get_web_app_hosts(self) -> str: """Get all registered RWA.Support.WebApp hosts. @@ -111,7 +123,7 @@ class RWASupportSessionService(dbus.service.Object): :: - ["https://example.org", "http://127.0.0.1:8000"] + [{"id": , "https://example.org"}, {"id": , "http://127.0.0.1:8000"}] """ logging.info("D-Bus method call: %s()", "get_web_app_hosts") @@ -174,13 +186,13 @@ class RWASupportSessionService(dbus.service.Object): """Add a RWA.Support.WebApp host. :param host: Exact hostname of the RWA.Support.WebApp host (D-Bus string) - :return: All registered hosts as JSON array (D-Bus string) + :return: The registered host as JSOn object (D-Bus string) **Structure of returned JSON (success):** :: - ["https://example.org", "http://127.0.0.1:8000"] + {"id": , "http://127.0.0.1:8000"} **Structure of returned JSON (error):** @@ -215,56 +227,67 @@ class RWASupportSessionService(dbus.service.Object): logging.debug('Did not add "%s" to "web_app_hosts" in user_settings', host) return json.dumps(res) - self.settings.web_app_hosts.append(host) + host_id = str(uuid4()) + host_object = {"url": host} + + self.settings.web_app_hosts[host_id] = host_object self.settings.save_settings() + logging.debug('Added "%s" to "web_app_hosts" in user_settings', host) - return self._get_web_app_hosts() + return json.dumps(self._build_host_dict(host_id, host_object)) @dbus.service.method( "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" ) - def remove_web_app_host(self, host_idx: int) -> str: + def remove_web_app_host(self, host_id: str) -> str: """Remove a RWA.Support.WebApp host. - :param idx: Index of web app host (D-Bus integer) + :param host_id: ID of web app host (D-Bus string) :return: All registered hosts as JSON array (D-Bus string) **Structure of returned JSON:** :: - ["https://example.org", "http://127.0.0.1:8000"] + [{"id": , "https://example.org"}, {"id": , "http://127.0.0.1:8000"}] """ - logging.info("D-Bus method call: %s(%d)", "remove_web_app_host", host_idx) + logging.info("D-Bus method call: %s(%s)", "remove_web_app_host", host_id) - if host_idx >= 0 and host_idx < len(self.settings.web_app_hosts): - host = self.settings.web_app_hosts[host_idx] - del self.settings.web_app_hosts[host_idx] + if host_id in self.settings.web_app_hosts: + host_object = self.settings.web_app_hosts[host_id] + del self.settings.web_app_hosts[host_id] self.settings.save_settings() - logging.debug('Removed web_app_hosts[%d]="%s" in user settings', host_idx, host) + logging.debug('Removed web_app_hosts[%s]="%s" in user settings', host_id, host_object) else: logging.warning("Given host index is not valid!") logging.debug( - "Did not remove web_app_hosts[%d] (not existent!) in " "user settings", host_idx + "Did not remove web_app_hosts[%s] (not existent!) in " "user settings", host_id ) + return json.dumps({"status": "error", "type": "host_not_found"}) return self._get_web_app_hosts() @dbus.service.method( "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" ) - def start(self, host_idx: int) -> str: + def start(self, host_id: str) -> str: """Start a new remote session and register it in RWA.Support.WebApp. - :param host_idx: Index of web app host (D-Bus integer) + :param host_id: ID of web app host (D-Bus string) :return: Result as JSON (D-Bus string) **Structure of returned JSON (success):** :: - {"status": "success", "id": , "url": "", "pin": } + { + "status": "success", + "host_id": "", + "session_id": , + "url": "", + "pin": + } **Structure of returned JSON (error):** @@ -280,7 +303,7 @@ class RWASupportSessionService(dbus.service.Object): * ``permission_denied`` * ``unsupported_server`` """ - logging.info("D-Bus method call: %s(%d)", "start", host_idx) + logging.info("D-Bus method call: %s(%s)", "start", host_id) if ALLOW_ONLY_ONE_SESSION and len(self.sessions.values()) > 0: logging.warning( @@ -293,25 +316,26 @@ class RWASupportSessionService(dbus.service.Object): return response try: - host = self.settings.web_app_hosts[host_idx] - logging.debug('web_app_hosts[%d] is the following host: "%s"', host_idx, host) + host_object = self.settings.web_app_hosts[host_id] + host_object = self._build_host_dict(host_id, host_object) + logging.debug('web_app_hosts[%s] is the following host: "%s"', host_id, host_object) except IndexError: - logging.error("web_app_hosts[%d] does not exist!", host_idx) + logging.error("web_app_hosts[%s] does not exist!", host_id) response = json.dumps({"status": "error", "type": "host_not_found"}) logging.debug("The response to the D-Bus caller: '%s'", response) return response # Check host by doing a handshake - res = self._do_api_handshake(host) + res = self._do_api_handshake(host_object["url"]) if res["status"] == "error": return json.dumps(res) # Start session try: - session = Session(host, self.trigger_service.port, self.mockup_mode) + session = Session(host_object, self.trigger_service.port, self.mockup_mode) # Add session to sessions list - self.sessions[session.pid] = session + self.sessions[session.combined_id] = session # Start session update service self._ensure_update_service() @@ -335,23 +359,25 @@ class RWASupportSessionService(dbus.service.Object): return response @dbus.service.method( - "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" + "org.ArcticaProject.RWASupportSessionService", in_signature="si", out_signature="s" ) - def status(self, pid: int) -> str: + def status(self, host_id: str, session_id: int) -> str: """Return the status of a session. .. note:: This uses the last status version got by the update service in the background. - :param pid: (Process) ID of session (D-Bus integer) + :param host_id: Host ID (D-Bus string) + :param session_id: Session ID (D-Bus integer) + :return: Session status as JSON (D-Bus string) **Structure of returned JSON:** :: - {"id": , "status": } + {"host_id": "", "session_id": , "status": } **Possible status options:** @@ -362,56 +388,65 @@ class RWASupportSessionService(dbus.service.Object): ``dead`` There was a problem, so that the session is dead. ============ ====================== """ - logging.info("D-Bus method call: %s(%d)", "status", pid) - response = self._get_status(pid) + logging.info("D-Bus method call: %s(%s, %d)", "status", host_id, session_id) + response = self._get_status(host_id, session_id) logging.debug("The response to the D-Bus caller: '%s'", response) return response @dbus.service.method( - "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" + "org.ArcticaProject.RWASupportSessionService", in_signature="si", out_signature="s" ) - def refresh_status(self, pid: int) -> str: + def refresh_status(self, host_id: str, session_id: int) -> str: """Update status from WebApp before returning it here like :meth:`status`.""" - logging.info("D-Bus method call: %s(%d)", "refresh_status", pid) + logging.info("D-Bus method call: %s(%s, %d)", "refresh_status", host_id, session_id) - self._update_session(pid) - response = self._get_status(pid) + self._update_session(host_id, session_id) + response = self._get_status(host_id, session_id) logging.debug("The response to the D-Bus caller: '%s'", response) return response @dbus.service.method( - "org.ArcticaProject.RWASupportSessionService", in_signature="i", out_signature="s" + "org.ArcticaProject.RWASupportSessionService", in_signature="si", out_signature="s" ) - def stop(self, pid: int) -> str: + def stop(self, host_id: str, session_id: int) -> str: """Stop a remote session. - :param pid: (Process) ID of session (D-Bus integer) + :param host_id: Host ID (D-Bus string) + :param session_id: Session ID (D-Bus integer) :return: Session status as JSON (D-Bus string) **Structure of returned JSON:** :: - {"id": , "status": "stopped"} + {"host_id": "", "session_id": , "status": "stopped"} """ - logging.info("D-Bus method call: %s(%d)", "stop", pid) + logging.info("D-Bus method call: %s(%s, %d)", "stop", host_id, session_id) + combined_id = combine(host_id, session_id) try: - session = self.sessions[pid] + session = self.sessions[combined_id] except KeyError: - response = json.dumps({"pid": pid, "status": "stopped"}, sort_keys=True) + response = json.dumps( + {"host_id": host_id, "session_id": session_id, "status": "stopped"}, sort_keys=True + ) logging.debug("The response to the D-Bus caller: '%s'", response) return response session.stop() - response = json.dumps({"id": pid, "status": "stopped"}, sort_keys=True) + response = json.dumps( + {"host_id": host_id, "session_id": session_id, "status": "stopped"}, sort_keys=True + ) logging.debug("The response to the D-Bus caller: '%s'", response) return response - def _get_status(self, pid: int) -> str: + def _get_status(self, host_id: str, session_id: int) -> str: + combined_id = combine(host_id, session_id) try: - session = self.sessions[pid] + session = self.sessions[combined_id] except KeyError: - return json.dumps({"id": pid, "status": "dead"}, sort_keys=True) + return json.dumps( + {"host_id": host_id, "session_id": session_id, "status": "dead"}, sort_keys=True + ) return json.dumps(session.status) def _ensure_update_service(self): @@ -420,12 +455,13 @@ class RWASupportSessionService(dbus.service.Object): self.update_thread = Thread(target=self._update_sessions) self.update_thread.start() - def _update_session(self, pid: int): + def _update_session(self, host_id: str, session_id: int): """Update the status of a session.""" + combined_id = combine(host_id, session_id) try: - session = self.sessions[pid] + session = self.sessions[combined_id] except KeyError: - logging.info(f"Update status for session #{pid} …") + logging.info(f"Update status for session #{session_id} on host {host_id} …") logging.warning(" Session is dead.") return @@ -434,23 +470,23 @@ class RWASupportSessionService(dbus.service.Object): if running: pass elif session.status_text == "stopped" and session.pid in self.sessions: - logging.info(f"Update status for session #{pid} …") + logging.info(f"Update status for session #{session_id} on host {host_id} …") logging.warning(" Session is dead.") - del self.sessions[session.pid] + del self.sessions[combined_id] else: - logging.info(f"Update status for session #{pid} …") + logging.info(f"Update status for session #{session_id} on host {host_id} …") logging.warning(" VNC was stopped, so session is dead.") session.stop() - del self.sessions[session.pid] + del self.sessions[combined_id] def _update_sessions(self): """Go through all running sessions and update their status using ``_update_session``.""" logging.info("Started update service for sessions.") while len(self.sessions.values()) > 0: for session in list(self.sessions.values()): - self._update_session(session.pid) + self._update_session(session.host_id, session.session_id) time.sleep(2) @@ -466,7 +502,9 @@ class RWASupportSessionService(dbus.service.Object): for session in self.sessions.values(): if session.session_id == session_id: r = session.trigger(data, method) - logging.info(f"Session #{session.pid} matches the ID: {r}") + logging.info( + f"Session #{session.session_id} on host {session.host_id} matches the ID: {r}" + ) return r logging.warning(" No matching session found for this ID.") @@ -477,7 +515,7 @@ class RWASupportSessionService(dbus.service.Object): logging.info("Stop all sessions.") for session in list(self.sessions.values()): session.stop() - del self.sessions[session.pid] + del self.sessions[session.combined_id] def _stop_daemon(self): """Stop all sessions and this daemon.""" diff --git a/rwa/support/sessionservice/session.py b/rwa/support/sessionservice/session.py index aa97086..52f6471 100644 --- a/rwa/support/sessionservice/session.py +++ b/rwa/support/sessionservice/session.py @@ -51,6 +51,10 @@ def get_desktop_dir(): return output.strip().replace("\n", "") +def combine(host_id: str, session_id: int): + return f"{host_id}-{session_id}" + + class Session: #: Session is running STATUS_RUNNING = "running" @@ -58,14 +62,16 @@ class Session: #: Remote has joined the session STATUS_JOINED = "active" - def __init__(self, host: str, trigger_port: int, mockup_session: bool = False): - self.host = host - self.BASE_URL = self.host + API_PATH + def __init__(self, host_object: dict, trigger_port: int, mockup_session: bool = False): + self.host_object = host_object + self.host_url = self.host_object["url"] + self.host_id = self.host_object["id"] + self.BASE_URL = self.host_url + API_PATH self.REGISTER_URL = self.BASE_URL + "register/" self.STOP_URL = self.BASE_URL + "stop/" self.STATUS_URL = self.BASE_URL + "status/" self.MARK_JOB_AS_DONE_URL = self.BASE_URL + "jobs/mark_as_done/" - logging.info(f"Load API config: {self.host}") + logging.info(f"Load API config: {self.host_url}") self.trigger_token = secrets.token_urlsafe(20) self.trigger_port = trigger_port @@ -78,6 +84,10 @@ class Session: self._register_session() self.status_text = self.STATUS_RUNNING + @property + def combined_id(self): + return combine(self.host_id, self.session_id) + @property def pid(self) -> int: return self.vnc_pid @@ -296,8 +306,13 @@ class Session: @property def client_meta(self) -> Dict[str, Union[str, int]]: - return {"id": self.pid, "session_id": self.session_id, "url": self.web_url, "pin": self.pin} + return { + "host_id": self.host_id, + "session_id": self.session_id, + "url": self.web_url, + "pin": self.pin, + } @property def status(self) -> Dict[str, Union[str, int]]: - return {"id": self.pid, "status": self.status_text} + return {"host_id": self.host_id, "session_id": self.session_id, "status": self.status_text} diff --git a/test_client.py b/test_client.py index 72538c1..4a0f83a 100755 --- a/test_client.py +++ b/test_client.py @@ -43,38 +43,69 @@ def cli(): @cli.command() -@click.argument("host", type=int) -def start(host: int): +def get_web_app_hosts(): """Start a session on the RWA.Support.WebApp host with index HOST.""" - click.echo(f"Sending D-Bus request 'start': {host}") - response = req.start(host) + click.echo("Sending D-Bus request 'get_web_app_hosts'") + response = req.get_web_app_hosts() click.echo(f"Your response is: {response}") @cli.command() -@click.argument("pid", type=int) -def stop(pid: int): +@click.argument("host", type=str) +def add_web_app_host(host: str): + """Start a session on the RWA.Support.WebApp host with index HOST.""" + click.echo(f"Sending D-Bus request 'add_web_app_host': {host}") + response = req.add_web_app_host(host) + click.echo(f"Your response is: {response}") + + +@cli.command() +@click.argument("host", type=str) +def remove_web_app_host(host: str): + """Start a session on the RWA.Support.WebApp host with index HOST.""" + click.echo(f"Sending D-Bus request 'remove_web_app_host': {host}") + response = req.remove_web_app_host(host) + click.echo(f"Your response is: {response}") + + +@cli.command() +@click.argument("host_id", type=str) +def start(host_id: str): + """Start a session on the RWA.Support.WebApp host with index HOST.""" + click.echo(f"Sending D-Bus request 'start': {host_id}") + response = req.start(host_id) + click.echo(f"Your response is: {response}") + + +@cli.command() +@click.argument("host_id", type=str) +@click.argument("session_id", type=int) +def stop(host_id: str, session_id: int): """Stop the session with the pid PID.""" - click.echo(f"Sending D-Bus request 'stop' with PID {pid}") - response = req.stop(pid) + click.echo(f"Sending D-Bus request 'stop' with host ID {host_id} and session ID {session_id}") + response = req.stop(host_id, session_id) click.echo(f"Your response is: {response}") @cli.command() -@click.argument("pid", type=int) -def status(pid: int): +@click.argument("host_id", type=str) +@click.argument("session_id", type=int) +def status(host_id: str, session_id: int): """Get the status of the session with the pid PID.""" - click.echo(f"Sending D-Bus request 'status' with PID {pid}") - response = req.status(pid) + click.echo(f"Sending D-Bus request 'status' with host ID {host_id} and session ID {session_id}") + response = req.status(host_id, session_id) click.echo(f"Your response is: {response}") @cli.command() -@click.argument("pid", type=int) -def refresh_status(pid: int): +@click.argument("host_id", type=str) +@click.argument("session_id", type=int) +def refresh_status(host_id: str, session_id: int): """Refresh and get the status of the session with the pid PID.""" - click.echo(f"Sending D-Bus request 'refresh_status' with PID {pid}") - response = req.refresh_status(pid) + click.echo( + f"Sending D-Bus request 'refresh_status' with host ID {host_id} and session ID {session_id}" + ) + response = req.refresh_status(host_id, session_id) click.echo(f"Your response is: {response}") -- cgit v1.2.3