From dcecb6427d2a140a3c5e260d572c76e337b549c7 Mon Sep 17 00:00:00 2001 From: Michael Joyce Date: Tue, 20 Aug 2019 15:59:02 -0700 Subject: [PATCH 1/3] Issue #126 - Update playback server endpoints with graceful failure Update the server endpoints and Playback class so a failed database connection doesn't prevent the GUI Plugin from functioning / booting normally. The Playback class tracks whether playback is "enabled" and the server endpoints use this status flag to determine how best to respond to requests. --- ait/gui/__init__.py | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/ait/gui/__init__.py b/ait/gui/__init__.py index 58d93b6f..faff0812 100644 --- a/ait/gui/__init__.py +++ b/ait/gui/__init__.py @@ -127,21 +127,31 @@ class Playback(object): A Playback manages the state for the playback component. playback.dbconn: connection to database playback.query: time query map of {timestamp: list of (uid, data)} from database - playback.on: true if gui is currently in playback mode + playback.on: True if gui is currently in playback mode. Real-time telemetry will not + be sent to the frontend during this. + playback.enabled: True if historical data playback is enabled. This will be False + if a database connection cannot be made or if data playback is disabled for + some other reason. """ def __init__(self): """Creates a new Playback""" - self._db_connect() + self.enabled = False self.query = {} self.on = False + self.dbconn = None + + self._db_connect() + + if self.dbconn: + self.enabled = True def _db_connect(self): """Connect to database""" # Get datastore from config plugins = ait.config.get('server.plugins') - datastore = '' + datastore = None other_args = {} for i in range(len(plugins)): if plugins[i]['plugin']['name'] == 'ait.core.server.plugin.DataArchive': @@ -152,11 +162,21 @@ def _db_connect(self): other_args.pop('outputs', None) other_args.pop('datastore', None) break - mod, cls = datastore.rsplit('.', 1) - # Connect to database - self.dbconn = getattr(importlib.import_module(mod), cls)() - self.dbconn.connect(**other_args) + if datastore: + mod, cls = datastore.rsplit('.', 1) + + # Connect to database + self.dbconn = getattr(importlib.import_module(mod), cls)() + self.dbconn.connect(**other_args) + else: + msg = ( + '[GUI Playback Configuration]' + 'Unable to locate DataArchive plugin configuration for ' + 'historical data queries. Historical telemetry playback ' + 'will be disabled in monitoring UI and server endpoints.' + ) + log.warn(msg) def reset(self): """Reset fields""" @@ -1047,6 +1067,9 @@ def handle(): global playback ranges = [] + if not playback.enabled: + return json.dumps([]) + # Loop through each packet from database packets = list(playback.dbconn.query('SHOW MEASUREMENTS').get_points()) for i in range(len(packets)): @@ -1076,6 +1099,10 @@ def handle(): def handle(): """Set playback query with packet name, start time, and end time from form""" global playback + + if not playback.enabled: + return HttpResponse(status=404, body='Historic data playback is disabled') + tlm_dict = tlm.getDefaultDict() # Get values from form From 923cf611013ec5f4b80ba938aeb6c6698036f5bd Mon Sep 17 00:00:00 2001 From: Michael Joyce Date: Tue, 20 Aug 2019 16:00:53 -0700 Subject: [PATCH 2/3] Issue #126 - Add warning message to Playback component if disabled Update the Playback component to display a warning banner if no valid time ranges were returned from the backend. This can either be because there's no valid time ranges of data in the database or (more likely) because the database connection isn't configured or failed. --- ait/gui/static/js/ait/gui/Playback.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ait/gui/static/js/ait/gui/Playback.js b/ait/gui/static/js/ait/gui/Playback.js index 35b44132..fd4467d7 100644 --- a/ait/gui/static/js/ait/gui/Playback.js +++ b/ait/gui/static/js/ait/gui/Playback.js @@ -54,11 +54,20 @@ const Playback = { // Display time ranges available let range = m('div', {class: 'form-group'}, [ m('label', 'Time ranges available'), - this._range.map(function(i) { - return m('div', i[0] + ': ' + i[1] + ' to ' + i[2]) - }) + m('div', {'class': 'alert alert-warning'}, + 'No time ranges found. Is your database connection configured?' + ) ]) + if (this._range.length > 0) { + range = m('div', {class: 'form-group'}, [ + m('label', 'Time ranges available'), + this._range.map(function(i) { + return m('div', i[0] + ': ' + i[1] + ' to ' + i[2]) + }) + ]) + } + // Packet select drop down menu let packets = m('div', {class: 'form-group col-xs-3'}, [ m('label', 'Telemetry packet:'), @@ -277,4 +286,4 @@ const Playback = { export default Playback -export { Playback } \ No newline at end of file +export { Playback } From 46fbd83ac7dc8dfd1cf342c49ed55b1870992d3c Mon Sep 17 00:00:00 2001 From: Anna Waldron Date: Wed, 21 Aug 2019 17:04:46 -0700 Subject: [PATCH 3/3] Add error handling for failed connection to database (#129) --- ait/gui/__init__.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ait/gui/__init__.py b/ait/gui/__init__.py index faff0812..e93cbae8 100644 --- a/ait/gui/__init__.py +++ b/ait/gui/__init__.py @@ -164,11 +164,15 @@ def _db_connect(self): break if datastore: - mod, cls = datastore.rsplit('.', 1) - - # Connect to database - self.dbconn = getattr(importlib.import_module(mod), cls)() - self.dbconn.connect(**other_args) + try: + mod, cls = datastore.rsplit('.', 1) + + # Connect to database + self.dbconn = getattr(importlib.import_module(mod), cls)() + self.dbconn.connect(**other_args) + except Exception as e: + log.error('Error connecting to datastore {}: {}'.format(datastore, e)) + log.warn('Disabling telemetry playback.') else: msg = ( '[GUI Playback Configuration]'