Skip to content

Commit

Permalink
[OGUI-1550] Fix issues with status service when missing components (#…
Browse files Browse the repository at this point in the history
…2580)

* renames StatusService to StatusController as it deals with HTTP requests
* moves StatusController to controller folder
* Improves StatusController by:
  * using naming conventions for private variables
  * initializing logger in constructor rather than on module import 
* Fixes bug in which if mysql configuration was missing, live mode would not work on front-end due to incorrect response on status
  • Loading branch information
graduta authored Sep 7, 2024
1 parent a31c115 commit de3f0ea
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 200 deletions.
12 changes: 6 additions & 6 deletions InfoLogger/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

const { InfoLoggerReceiver, MySQL } = require('@aliceo2/web-ui');

const { StatusController } = require('./controller/StatusController.js');
const { LiveService } = require('./services/LiveService.js');
const { QueryService } = require('./services/QueryService.js');
const ProfileService = require('./ProfileService.js');
const JsonFileConnector = require('./JSONFileConnector.js');
const StatusService = require('./StatusService.js');

const { serviceAvailabilityCheck } = require('./middleware/serviceAvailabilityCheck.middleware.js');

Expand All @@ -44,9 +44,9 @@ module.exports.attachTo = async (http, ws) => {
}
const queryController = new QueryController(queryService);

const statusService = new StatusService(config, projPackage, ws);
statusService.setQuerySource(queryService);
statusService.liveSource = liveService;
const statusController = new StatusController(config, projPackage, ws);
statusController.querySource = queryService;
statusController.liveSource = liveService;

const jsonDb = new JsonFileConnector(config.dbFile || `${__dirname}/../db.json`);
const profileService = new ProfileService(jsonDb);
Expand All @@ -64,8 +64,8 @@ module.exports.attachTo = async (http, ws) => {
{ public: true },
);

http.get('/status/gui', statusService.getILGStatus.bind(statusService), { public: true });
http.get('/getFrameworkInfo', statusService.frameworkInfo.bind(statusService));
http.get('/status/gui', statusController.getILGStatus.bind(statusController), { public: true });
http.get('/getFrameworkInfo', statusController.frameworkInfo.bind(statusController));

http.get('/getUserProfile', (req, res) => profileService.getUserProfile(req, res));
http.get('/getProfile', (req, res) => profileService.getProfile(req, res));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,24 @@
* or submit itself to any jurisdiction.
*/

const logger = require('@aliceo2/web-ui').LogManager
.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/status`);
const { LogManager } = require('@aliceo2/web-ui');

/**
* Gateway for all calls with regards to the status
* of the framework and its dependencies
*/
class StatusService {
class StatusController {
/**
* Setup StatusService
* @param {JSON} config - of the framework
* @param {JSON} projPackage - package json file
* Setup StatusController for the application which is to provide status of used services
* @param {object} config - of the framework
* @param {object} projPackage - package json file
* @param {WebSocket} webSocketServer - instance of the web socket server used by the application
*/
constructor(config, projPackage, webSocketServer) {
if (!config) {
throw new Error('Empty Framework configuration');
}
this.config = config;
this.projPackage = projPackage;
this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'ilg'}/status`);

this._config = config;
this._projPackage = projPackage;

/**
* @type {WebSocket}
Expand All @@ -43,8 +41,8 @@ class StatusService {
* Set source of data once enabled
* @param {SQLDataSource} querySource - source of data
*/
setQuerySource(querySource) {
this.querySource = querySource;
set querySource(querySource) {
this._querySource = querySource;
}

/**
Expand All @@ -61,11 +59,10 @@ class StatusService {
* @param {Response} res - HTTP Response object
*/
async getILGStatus(_, res) {
let result = {};
if (this.projPackage && this.projPackage.version) {
result.version = this.projPackage.version;
}
if (this.config.http) {
let result = {
version: this?._projPackage?.version ?? 'unknown',
};
if (this._config.http) {
const ilg = { status: { ok: true } };
result = Object.assign(result, ilg);
}
Expand All @@ -80,29 +77,26 @@ class StatusService {
* @param {Response} res - HTTP Response object
*/
async frameworkInfo(_, res) {
const result = {};
result['infoLogger-gui'] = this.getProjectInfo();
const { infoLoggerServer: ilgServerConfig, mysql: dataSourceConfig } = this._config;
const result = {
'infoLogger-gui': this._getProjectInfo(),
infoLoggerServer: this._getLiveSourceStatus(ilgServerConfig ?? {}),
mysql: await this._getDataSourceStatus(dataSourceConfig ?? {}),
};

if (this.config.infoLoggerServer) {
result.infoLoggerServer = this._getLiveSourceStatus(this.config.infoLoggerServer);
}
if (this.config.mysql) {
result.mysql = await this.getDataSourceStatus(this.config.mysql);
}
res.status(200).json(result);
}

/**
* Build an object containing InfoLogger GUI's information
* @returns {object} - information about the application
*/
getProjectInfo() {
let info = {};
if (this.projPackage && this.projPackage.version) {
info.version = this.projPackage.version;
}
if (this.config.http) {
const { http } = this.config;
_getProjectInfo() {
let info = {
version: this?._projPackage?.version ?? 'unknown',
};
if (this._config.http) {
const { http } = this._config;
const ilg = { hostname: http.hostname, port: http.port, status: { ok: true }, name: http.name ?? '' };
info = Object.assign(info, ilg);
}
Expand Down Expand Up @@ -130,31 +124,29 @@ class StatusService {
/**
* Build object with information and status about data source
* @param {object} config used for retrieving data form data source
* @param {string} config.host - host of the data source
* @param {number} config.port - port of the data source
* @param {string} config.database - database name
* @returns {object} - information on statue of the data source
*/
async getDataSourceStatus(config) {
const mysql = {
host: config.host,
port: config.port,
database: config.database,
async _getDataSourceStatus({ host, port, database }) {
const dataSourceStatus = {
host,
port,
database,
};
if (this.querySource) {
if (this._querySource) {
try {
await this.querySource.isConnectionUpAndRunning();
mysql.status = { ok: true };
await this._querySource.isConnectionUpAndRunning();
dataSourceStatus.status = { ok: true };
} catch (error) {
logger.error(error.message || error);
if (error.stack) {
logger.trace(error);
}
mysql.status = { ok: false, message: error.message || error };
dataSourceStatus.status = { ok: false, message: error.message || error };
}
} else {
logger.error('There was no data source set up');
mysql.status = { ok: false, message: 'There was no data source set up' };
dataSourceStatus.status = { ok: false, message: 'There was no data source set up' };
}
return mysql;
return dataSourceStatus;
}
}

module.exports = StatusService;
module.exports.StatusController = StatusController;
184 changes: 184 additions & 0 deletions InfoLogger/test/lib/controller/mocha-status-controller.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

const sinon = require('sinon');
const assert = require('assert');
const config = require('./../../test-config.js');

const { StatusController } = require('./../../../lib/controller/StatusController.js');

describe('Status Service test suite', () => {
config.mysql = {
host: 'localhost',
port: 6103,
database: 'INFOLOGGER',
};
describe('Creating a new StatusController instance', () => {
it('should successfully initialize StatusController', () => {
assert.doesNotThrow(() => new StatusController({ hostname: 'localhost', port: 8080 }, {}));
});
});

describe('`_getProjectInfo()` tests', () => {
it('should successfully return ilg info even if version is missing', () => {
const statusController = new StatusController(config, undefined);
const info = {
hostname: 'localhost',
port: 8080,
status: { ok: true },
name: 'TST',
clients: -1,
version: 'unknown',
};
assert.deepStrictEqual(statusController._getProjectInfo(), info);
});

it('should successfully return ilg version even if http configuration is missing', () => {
const statusController = new StatusController({}, { version: '1.9.2' });
const info = { version: '1.9.2', clients: -1 };
assert.deepStrictEqual(statusController._getProjectInfo(), info);
});

it('should successfully add project version if package.json was provided', () => {
const statusController = new StatusController(config, { version: '1.9.2' });
const info = {
hostname: 'localhost', port: 8080, status: { ok: true }, version: '1.9.2', name: 'TST', clients: -1,
};
assert.deepStrictEqual(statusController._getProjectInfo(), info);
});
});

describe('`_getLiveSourceStatus()` tests', () => {
it('should successfully return InfoLogger Server info with status ok false if live source is missing', () => {
const statusController = new StatusController(config, undefined);
const info = {
host: 'localhost', port: 6102, status: { ok: false, message: 'Unable to connect to InfoLogger Server' },
};
assert.deepStrictEqual(statusController._getLiveSourceStatus(config.infoLoggerServer), info);
});

it('should successfully return InfoLogger Server info with status ok when live source is present', () => {
const statusController = new StatusController(config, undefined);
statusController.liveSource = { isAvailable: true, onconnect: () => true };

const info = { host: 'localhost', port: 6102, status: { ok: true } };
assert.deepStrictEqual(statusController._getLiveSourceStatus(config.infoLoggerServer), info);
});
});

describe('`_getDataSourceStatus()` tests', () => {
it('should successfully return mysql info with status ok false if data source is missing', async () => {
const statusController = new StatusController(config, undefined);
const info = {
host: 'localhost',
port: 6103,
database: 'INFOLOGGER',
status: {
ok: false, message: 'There was no data source set up',
},
};
const mysql = await statusController._getDataSourceStatus(config.mysql);
assert.deepStrictEqual(mysql, info);
});

it(
'should successfully return mysql info with status ok true when data source is present and connected',
async () => {
const statusController = new StatusController(config, undefined);
const info = { host: 'localhost', port: 6103, database: 'INFOLOGGER', status: { ok: true } };

const dataSource = {
isConnectionUpAndRunning: sinon.stub().resolves(),
};
statusController.querySource = dataSource;
const mysql = await statusController._getDataSourceStatus(config.mysql);
assert.deepStrictEqual(mysql, info);
},
);

it(
'should successfully return mysql info with status ok false when data source is present but it is not connected',
async () => {
const statusController = new StatusController(config, undefined);
const info = {
host: 'localhost',
port: 6103,
database: 'INFOLOGGER',
status: {
ok: false, message: 'Could not connect',
},
};

const dataSource = {
isConnectionUpAndRunning: sinon.stub().rejects(new Error('Could not connect')),
};
statusController.querySource = dataSource;
const mysql = await statusController._getDataSourceStatus(config.mysql);
assert.deepStrictEqual(mysql, info);
},
);
});

describe('`frameworkInfo()` tests', () => {
it('should successfully send response with built JSON information', async () => {
const statusController = new StatusController(config, undefined);
const res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
await statusController.frameworkInfo(undefined, res);

const info = {
'infoLogger-gui': {
hostname: 'localhost',
port: 8080,
status: { ok: true },
name: 'TST',
clients: -1,
version: 'unknown',
},
mysql: {
host: 'localhost',
port: 6103,
database: 'INFOLOGGER',
status: {
ok: false, message: 'There was no data source set up',
},
},
infoLoggerServer: {
host: 'localhost', port: 6102, status: { ok: false, message: 'Unable to connect to InfoLogger Server' },
},
};

assert.ok(res.status.calledWith(200));
assert.ok(res.json.calledWith(info));
});
});

describe('`getILGStatus()` tests', () => {
it('should successfully send response with JSON information about ILG', async () => {
const statusController = new StatusController(config, undefined);
const res = {
status: sinon.stub().returnsThis(),
json: sinon.stub(),
};
await statusController.getILGStatus(undefined, res);

const info = { status: { ok: true }, clients: -1, version: 'unknown' };

assert.ok(res.status.calledWith(200));
assert.ok(res.json.calledWith(info));
});
});
});
Loading

0 comments on commit de3f0ea

Please sign in to comment.