From 06c1fb985efea1f6697bedbe340557cde7225bf4 Mon Sep 17 00:00:00 2001 From: Hemna Date: Wed, 20 Nov 2024 15:55:02 -0500 Subject: [PATCH] Update Client to report login status This patch updates the aprsd client base class to report login succes and any error string associated with a login failure. Also exposes the login status so anyone using the client to check for login failure. Update webchat, listen, server commands to check for initial login failures and exit if the login failed. No reason to continue on if the login fails. --- aprsd/client/aprsis.py | 34 ++++++++++++++++++++++++++++------ aprsd/client/base.py | 16 ++++++++++++++++ aprsd/client/drivers/aprsis.py | 3 +++ aprsd/client/fake.py | 7 +++++-- aprsd/client/kiss.py | 10 ++++++++-- aprsd/client/stats.py | 20 +------------------- aprsd/cmds/listen.py | 6 ++++++ aprsd/cmds/server.py | 8 ++++++++ aprsd/cmds/webchat.py | 18 +++++++++++++++++- aprsd/threads/keep_alive.py | 4 ++++ aprsd/threads/rx.py | 8 ++++++++ 11 files changed, 104 insertions(+), 30 deletions(-) diff --git a/aprsd/client/aprsis.py b/aprsd/client/aprsis.py index e0c5f1d..1cd544d 100644 --- a/aprsd/client/aprsis.py +++ b/aprsd/client/aprsis.py @@ -23,13 +23,24 @@ class APRSISClient(base.APRSClient): max_timeout = {"hours": 0.0, "minutes": 2, "seconds": 0} self.max_delta = datetime.timedelta(**max_timeout) - def stats(self) -> dict: + def stats(self, serializable=False) -> dict: stats = {} if self.is_configured(): + if self._client: + keepalive = self._client.aprsd_keepalive + server_string = self._client.server_string + if serializable: + keepalive = keepalive.isoformat() + else: + keepalive = "None" + server_string = "None" stats = { - "server_string": self._client.server_string, - "sever_keepalive": self._client.aprsd_keepalive, + "connected": self.is_connected, "filter": self.filter, + "keepalive": keepalive, + "login_status": self.login_status, + "server_string": server_string, + "transport": self.transport(), } return stats @@ -99,22 +110,31 @@ class APRSISClient(base.APRSClient): self.connected = False backoff = 1 aprs_client = None + retries = 3 + retry_count = 0 while not self.connected: + retry_count += 1 + if retry_count >= retries: + break try: LOG.info(f"Creating aprslib client({host}:{port}) and logging in {user}.") aprs_client = aprsis.Aprsdis(user, passwd=password, host=host, port=port) # Force the log to be the same aprs_client.logger = LOG aprs_client.connect() - self.connected = True + self.connected = self.login_status["success"] = True + self.login_status["message"] = aprs_client.server_string backoff = 1 except LoginError as e: LOG.error(f"Failed to login to APRS-IS Server '{e}'") - self.connected = False + self.connected = self.login_status["success"] = False + self.login_status["message"] = e.message + LOG.error(e.message) time.sleep(backoff) except Exception as e: LOG.error(f"Unable to connect to APRS-IS server. '{e}' ") - self.connected = False + self.connected = self.login_status["success"] = False + self.login_status["message"] = e.message time.sleep(backoff) # Don't allow the backoff to go to inifinity. if backoff > 5: @@ -135,5 +155,7 @@ class APRSISClient(base.APRSClient): except Exception as e: LOG.error(e) LOG.info(e.__cause__) + raise e else: LOG.warning("client is None, might be resetting.") + self.connected = False diff --git a/aprsd/client/base.py b/aprsd/client/base.py index 1e87eea..8470546 100644 --- a/aprsd/client/base.py +++ b/aprsd/client/base.py @@ -19,6 +19,10 @@ class APRSClient: _client = None connected = False + login_status = { + "success": False, + "message": None, + } filter = None lock = threading.Lock() @@ -38,6 +42,18 @@ class APRSClient: dict: Statistics about the connection and packet handling """ + @property + def is_connected(self): + return self.connected + + @property + def login_success(self): + return self.login_status.get("success", False) + + @property + def login_failure(self): + return self.login_status["message"] + def set_filter(self, filter): self.filter = filter if self._client: diff --git a/aprsd/client/drivers/aprsis.py b/aprsd/client/drivers/aprsis.py index 405de57..afcbd63 100644 --- a/aprsd/client/drivers/aprsis.py +++ b/aprsd/client/drivers/aprsis.py @@ -27,6 +27,9 @@ class Aprsdis(aprslib.IS): # date for last time we heard from the server aprsd_keepalive = datetime.datetime.now() + # Which server we are connected to? + server_string = "None" + # timeout in seconds select_timeout = 1 lock = threading.Lock() diff --git a/aprsd/client/fake.py b/aprsd/client/fake.py index c8e255b..b17262d 100644 --- a/aprsd/client/fake.py +++ b/aprsd/client/fake.py @@ -14,8 +14,11 @@ LOG = logging.getLogger("APRSD") class APRSDFakeClient(base.APRSClient, metaclass=trace.TraceWrapperMetaclass): - def stats(self) -> dict: - return {} + def stats(self, serializable=False) -> dict: + return { + "transport": "Fake", + "connected": True, + } @staticmethod def is_enabled(): diff --git a/aprsd/client/kiss.py b/aprsd/client/kiss.py index 8318fc1..852d524 100644 --- a/aprsd/client/kiss.py +++ b/aprsd/client/kiss.py @@ -17,12 +17,18 @@ class KISSClient(base.APRSClient): _client = None - def stats(self) -> dict: + def stats(self, serializable=False) -> dict: stats = {} if self.is_configured(): - return { + stats = { + "connected": self.is_connected, "transport": self.transport(), } + if self.transport() == client.TRANSPORT_TCPKISS: + stats["host"] = CONF.kiss_tcp.host + stats["port"] = CONF.kiss_tcp.port + elif self.transport() == client.TRANSPORT_SERIALKISS: + stats["device"] = CONF.kiss_serial.device return stats @staticmethod diff --git a/aprsd/client/stats.py b/aprsd/client/stats.py index 5b0d088..29692ad 100644 --- a/aprsd/client/stats.py +++ b/aprsd/client/stats.py @@ -17,22 +17,4 @@ class APRSClientStats: @wrapt.synchronized(lock) def stats(self, serializable=False): - cl = client.client_factory.create() - stats = { - "transport": cl.transport(), - "filter": cl.filter, - "connected": cl.connected, - } - - if cl.transport() == client.TRANSPORT_APRSIS: - stats["server_string"] = cl.client.server_string - keepalive = cl.client.aprsd_keepalive - if serializable: - keepalive = keepalive.isoformat() - stats["server_keepalive"] = keepalive - elif cl.transport() == client.TRANSPORT_TCPKISS: - stats["host"] = CONF.kiss_tcp.host - stats["port"] = CONF.kiss_tcp.port - elif cl.transport() == client.TRANSPORT_SERIALKISS: - stats["device"] = CONF.kiss_serial.device - return stats + return client.client_factory.create().stats(serializable=serializable) diff --git a/aprsd/cmds/listen.py b/aprsd/cmds/listen.py index 47a6deb..f42f16a 100644 --- a/aprsd/cmds/listen.py +++ b/aprsd/cmds/listen.py @@ -256,6 +256,12 @@ def listen( LOG.info("Creating client connection") aprs_client = client_factory.create() LOG.info(aprs_client) + if not aprs_client.login_success: + # We failed to login, will just quit! + msg = f"Login Failure: {aprs_client.login_failure}" + LOG.error(msg) + print(msg) + sys.exit(-1) LOG.debug(f"Filter by '{filter}'") aprs_client.set_filter(filter) diff --git a/aprsd/cmds/server.py b/aprsd/cmds/server.py index 176d592..a355478 100644 --- a/aprsd/cmds/server.py +++ b/aprsd/cmds/server.py @@ -58,6 +58,14 @@ def server(ctx, flush): LOG.info("Creating client connection") aprs_client = client_factory.create() LOG.info(aprs_client) + if not aprs_client.login_success: + # We failed to login, will just quit! + msg = f"Login Failure: {aprs_client.login_failure}" + LOG.error(msg) + print(msg) + sys.exit(-1) + + # Check to make sure the login worked. # Create the initial PM singleton and Register plugins # We register plugins first here so we can register each diff --git a/aprsd/cmds/webchat.py b/aprsd/cmds/webchat.py index 1900b95..8ce38ba 100644 --- a/aprsd/cmds/webchat.py +++ b/aprsd/cmds/webchat.py @@ -24,7 +24,9 @@ from aprsd import utils as aprsd_utils from aprsd.client import client_factory, kiss from aprsd.main import cli from aprsd.threads import aprsd as aprsd_threads -from aprsd.threads import keep_alive, rx, tx +from aprsd.threads import keep_alive, rx +from aprsd.threads import stats as stats_thread +from aprsd.threads import tx from aprsd.utils import trace @@ -614,10 +616,24 @@ def webchat(ctx, flush, port): LOG.error("APRS client is not properly configured in config file.") sys.exit(-1) + # Creates the client object + LOG.info("Creating client connection") + aprs_client = client_factory.create() + LOG.info(aprs_client) + if not aprs_client.login_success: + # We failed to login, will just quit! + msg = f"Login Failure: {aprs_client.login_failure}" + LOG.error(msg) + print(msg) + sys.exit(-1) + keepalive = keep_alive.KeepAliveThread() LOG.info("Start KeepAliveThread") keepalive.start() + stats_store_thread = stats_thread.APRSDStatsStoreThread() + stats_store_thread.start() + socketio = init_flask(loglevel, quiet) rx_thread = rx.APRSDPluginRXThread( packet_queue=threads.packet_queue, diff --git a/aprsd/threads/keep_alive.py b/aprsd/threads/keep_alive.py index b212ac4..309ac8c 100644 --- a/aprsd/threads/keep_alive.py +++ b/aprsd/threads/keep_alive.py @@ -5,6 +5,7 @@ import tracemalloc from loguru import logger from oslo_config import cfg +import timeago from aprsd import packets, utils from aprsd.client import client_factory @@ -98,6 +99,9 @@ class KeepAliveThread(APRSDThread): # check the APRS connection cl = client_factory.create() + cl_stats = cl.stats() + keepalive = timeago.format(cl_stats.get("keepalive", None)) + LOGU.opt(colors=True).info(f"Client keepalive {keepalive}") # Reset the connection if it's dead and this isn't our # First time through the loop. # The first time through the loop can happen at startup where diff --git a/aprsd/threads/rx.py b/aprsd/threads/rx.py index b75f520..f641535 100644 --- a/aprsd/threads/rx.py +++ b/aprsd/threads/rx.py @@ -18,6 +18,8 @@ LOG = logging.getLogger("APRSD") class APRSDRXThread(APRSDThread): + _client = None + def __init__(self, packet_queue): super().__init__("RX_PKT") self.packet_queue = packet_queue @@ -33,6 +35,12 @@ class APRSDRXThread(APRSDThread): self._client = client_factory.create() time.sleep(1) return True + + if not self._client.is_connected: + self._client = client_factory.create() + time.sleep(1) + return True + # setup the consumer of messages and block until a messages try: # This will register a packet consumer with aprslib