diff --git a/lib/application/index.js b/lib/application/index.js index 7d5312f276..6ecdab87ad 100644 --- a/lib/application/index.js +++ b/lib/application/index.js @@ -11,10 +11,61 @@ * or submit itself to any jurisdiction. */ +const { Application } = require('./interfaces'); +const database = require('../database'); const Server = require('../server'); -const persistence = require('../database'); -module.exports = { - server: new Server(), - persistence, -}; +const _1_S_IN_MS = 1000; +const SHUTDOWN_DELAY_MS = 5 * _1_S_IN_MS; + +/** + * Bookkeeping Application + */ +class BookkeepingApplication extends Application { + /** + * Creates a new `Bookkeeping Application` instance. + */ + constructor() { + super(); + + this.server = new Server(); + this.database = database; + } + + /** + * Causes the application to be scheduled for execution. + * + * @returns {Promise} Promise object represents the outcome. + */ + async run() { + try { + await this.server.listen(); + } catch (error) { + // TODO: add logging + return this.stop(); + } + } + + /** + * Begins the process of terminating the application. Calling this method terminates the process. + * + * @param {Boolean} [immediate=false] Indicates if the underlying services should be closed immediately; if *false* + * it will close the underlying services gracefully. + * @returns {Promise} Promise object represents the outcome. + */ + async stop(immediate) { + this.server.acceptIncomingConnections(false); + + if (immediate) { + return this.server.close(); + } + + setTimeout(async () => { + this.server.close() + .then(() => process.exit(0)) + .catch(() => process.exit(1)); + }, SHUTDOWN_DELAY_MS); + } +} + +module.exports = new BookkeepingApplication(); diff --git a/lib/application/interfaces/Application.js b/lib/application/interfaces/Application.js new file mode 100644 index 0000000000..bb495511ec --- /dev/null +++ b/lib/application/interfaces/Application.js @@ -0,0 +1,39 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * 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. + */ + +/** + * Application + */ +class Application { + /** + * Causes the application to be scheduled for execution. + * + * @returns {Promise} Promise object represents the outcome. + */ + async run() { + return Promise.reject('The method or operation is not implemented.'); + } + + /** + * Begins the process of terminating the application. Calling this method terminates the process. + * + * @param {Boolean} [immediate=false] Indicates if the underlying services should be closed immediately; if *false* + * it will close the underlying services gracefully. + * @returns {Promise} Promise object represents the outcome. + */ + async stop(immediate) { + return Promise.reject('The method or operation is not implemented.'); + } +} + +module.exports = Application; diff --git a/lib/application/interfaces/index.js b/lib/application/interfaces/index.js index 5ecf2ade80..23adf921b8 100644 --- a/lib/application/interfaces/index.js +++ b/lib/application/interfaces/index.js @@ -11,11 +11,13 @@ * or submit itself to any jurisdiction. */ +const Application = require('./Application'); const Repository = require('./Repository'); const Server = require('./Server'); const UseCase = require('./UseCase'); module.exports = { + Application, Repository, Server, UseCase, diff --git a/lib/main.js b/lib/main.js index 4404d95d37..6f1a9f2354 100644 --- a/lib/main.js +++ b/lib/main.js @@ -11,36 +11,13 @@ * or submit itself to any jurisdiction. */ -const { server } = require('./application'); +const application = require('./application'); -const SHUTDOWN_DELAY_MS = 5 * 1000; - -server.listen(); - -/** - * Stops the application. - * - * @returns {undefined} - */ -const doGracefulShutdown = () => { - server.close() - .then(() => process.exit(0)) - .catch(() => process.exit(1)); -}; - -/** - * Starts the graceful shutdown process. - * - * @returns {undefined} - */ -const startGracefulShutdown = () => { - server.acceptIncomingConnections(false); - setTimeout(doGracefulShutdown, SHUTDOWN_DELAY_MS); -}; +application.run(); /* * 'SIGTERM' and 'SIGINT' have default handlers on non-Windows platforms that reset the terminal mode before exiting * with code 128 + signal number. If one of these signals has a listener installed, its default behavior will be removed * (Node.js will no longer exit). */ -['SIGTERM', 'SIGINT'].forEach((event) => process.on(event, startGracefulShutdown)); +['SIGTERM', 'SIGINT'].forEach((event) => process.on(event, application.stop.bind(application))); diff --git a/lib/server/controllers/logs.controller.js b/lib/server/controllers/logs.controller.js index 75a6a1f8b8..7acd4e03f3 100644 --- a/lib/server/controllers/logs.controller.js +++ b/lib/server/controllers/logs.controller.js @@ -12,6 +12,7 @@ */ const { log: { GetAllLogsUseCase } } = require('../../application/usecases'); +const { repositories: { LogRepository } } = require('../../database'); /** * Create a new log @@ -82,7 +83,6 @@ const getAttachment = (request, response, next) => { /** * Get all logs. * - * @param {Object} application The *application* object represents the Application. * @param {Object} request The *request* object represents the HTTP request and has properties for the request query * string, parameters, body, HTTP headers, and so on. * @param {Object} response The *response* object represents the HTTP response that an Express app sends when it gets an @@ -91,7 +91,7 @@ const getAttachment = (request, response, next) => { * next middleware function. * @returns {undefined} */ -const index = async ({ persistence: { repositories: { LogRepository } } }, request, response, next) => { +const index = async (request, response, next) => { let logs; try { logs = await new GetAllLogsUseCase() diff --git a/lib/server/index.js b/lib/server/index.js index 5a1ac50fb4..86d10e4ead 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -102,6 +102,7 @@ class WebServer extends Server { * @returns {Promise} Promise object represents ... */ async listen() { + this.acceptIncomingConnectionsFlag = true; return this.http.listen(); } } diff --git a/test/application/usecases/log/GetAllLogsUseCase.test.js b/test/application/usecases/log/GetAllLogsUseCase.test.js index 3811999e8e..8961bbb50e 100644 --- a/test/application/usecases/log/GetAllLogsUseCase.test.js +++ b/test/application/usecases/log/GetAllLogsUseCase.test.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -const { persistence: { repositories: { LogRepository } } } = require('../../../../lib/application'); +const { repositories: { LogRepository } } = require('../../../../lib/database'); const { log: { GetAllLogsUseCase } } = require('../../../../lib/application/usecases'); const chai = require('chai'); diff --git a/test/e2e/home.test.js b/test/e2e/home.test.js index 02801d398a..cb7d5e845b 100644 --- a/test/e2e/home.test.js +++ b/test/e2e/home.test.js @@ -22,14 +22,16 @@ chai.use(chaiResponseValidator(path.resolve(__dirname, '..', '..', 'spec', 'open module.exports = () => { describe('GET /api/', () => { - const { server } = require('../../lib/application'); + const application = require('../../lib/application'); + + const { server } = application; before(async () => { - await server.listen(); + await application.run(); }); after(async () => { - await server.close(); + await application.stop(true); }); it('should satisfy OpenAPI spec', (done) => { diff --git a/test/e2e/shutdown.test.js b/test/e2e/shutdown.test.js index 0395b716ce..525d89feb8 100644 --- a/test/e2e/shutdown.test.js +++ b/test/e2e/shutdown.test.js @@ -21,14 +21,16 @@ const { expect } = chai; chai.use(chaiResponseValidator(path.resolve(__dirname, '..', '..', 'spec', 'openapi.yaml'))); module.exports = () => { - const { server } = require('../../lib/application'); + const application = require('../../lib/application'); + + const { server } = application; before(async () => { - await server.listen(); + await application.run(); }); after(async () => { - await server.close(); + await application.stop(true); }); it('should return the server information when the server is not in shutdown', (done) => { diff --git a/test/public/overview.test.js b/test/public/overview.test.js index a45ba9de24..7d0a6cc351 100644 --- a/test/public/overview.test.js +++ b/test/public/overview.test.js @@ -14,7 +14,7 @@ const assert = require('assert'); const puppeteer = require('puppeteer'); const pti = require('puppeteer-to-istanbul'); -const { server } = require('../../lib/application'); +const application = require('../../lib/application'); module.exports = function () { // Configure this suite to have a default timeout of 5s @@ -24,8 +24,10 @@ module.exports = function () { let browser; let url; + const { server } = application; + before(async () => { - await server.listen(); + await application.run(); browser = await puppeteer.launch({ args: ['--no-sandbox'] }); page = await browser.newPage(); await Promise.all([ @@ -49,7 +51,7 @@ module.exports = function () { pti.write([...jsCoverage, ...cssCoverage]); await browser.close(); - await server.close(); + await application.stop(true); }); it('loads the page successfully', async () => {