From d519996959e0e20b1e89ee96f8abf07d7557a1f9 Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Tue, 29 Mar 2022 17:14:50 +0000 Subject: [PATCH] Application: Add flag in application to keep track if its running and which version. Lib: Add restart support to shut down server. Runner: Add better logging when shut down request is sent. Many: Add bunch of event emitters for interested parties --- core/application.mjs | 54 ++++++++++++++++++++++++++++------- core/lib.mjs | 6 +++- core/runner.mjs | 9 ++---- package.json | 2 +- test/application.run.test.mjs | 22 ++++++++++++++ test/application.test.mjs | 5 +++- 6 files changed, 79 insertions(+), 19 deletions(-) diff --git a/core/application.mjs b/core/application.mjs index 29f4990..45c1bce 100644 --- a/core/application.mjs +++ b/core/application.mjs @@ -34,7 +34,7 @@ export default class Application extends EventEmitter { this.updating = false this.http = new HttpServer(this.config) this.module = null - this.running = false + this.running = '' this.workers = {} // Fresh is used to indicate that when we run the application and it fails, @@ -98,7 +98,6 @@ export default class Application extends EventEmitter { updateLog(message, level = 'info') { this.ctx.db.data.core[this.name].updater += message this.ctx.log[level](message) - this.emit('updatelog') return message } @@ -113,6 +112,7 @@ export default class Application extends EventEmitter { if (this.ctx.db.data.core[this.name].updater !== this.msgStatic) { this.ctx.db.data.core[this.name].updater = '' this.updateLog(this.msgStatic) + this.emit('updatelog', this.msgStatic) return this.ctx.db.write().then(function() { return null }) } return Promise.resolve(null) @@ -163,6 +163,7 @@ export default class Application extends EventEmitter { try { log += this.updateLog(`Checking for latest version at ${new Date().toISOString().replace('T', ' ').split('.')[0]}. `, 'debug') + '\n' + this.emit('updatelog', log) // Get the latest version from our provider latest = await this.provider.getLatestVersion() @@ -170,7 +171,8 @@ export default class Application extends EventEmitter { // If the versino matches the latest installed, then there's nothing to do if (this.ctx.db.data.core[this.name].latestInstalled === latest.version) { log += this.updateLog(`Found ${latest.version}. `, 'debug') + '\n' - this.updateLog('Already up to date, nothing to do. ', 'debug') + log += this.updateLog('Already up to date, nothing to do. ', 'debug') + this.emit('updatelog', log) return null } @@ -185,7 +187,8 @@ export default class Application extends EventEmitter { if (found) { // Check if the existing version found was already installed. if (found.installed) { - this.updateLog('Version was already installed, nothing to do. ') + log += this.updateLog('Version was already installed, nothing to do. ') + this.emit('updatelog', log) return null } @@ -201,12 +204,14 @@ export default class Application extends EventEmitter { // if so, we should skip them and consider those versions as black // listed and avoid at all cost. if (latest.failtodownload && latest.failtodownload > 3) { - this.updateLog('Version failed to download too many times, skipping this version. ') + log += this.updateLog('Version failed to download too many times, skipping this version. ') + this.emit('updatelog', log) return null } if (latest.failtoinstall && latest.failtoinstall > 3) { - this.updateLog('Version failed to install too many times, skipping this version. ') + log += this.updateLog('Version failed to install too many times, skipping this version. ') + this.emit('updatelog', log) return null } @@ -217,7 +222,7 @@ export default class Application extends EventEmitter { latest.stable = 0 this.ctx.db.upsertFirst(this.ctx.db.data.core[this.name].versions, latest) } - + this.emit('updatelog', log) this.emit('update', latest) // The target file for the archive and the target folder for new our version @@ -228,6 +233,7 @@ export default class Application extends EventEmitter { await this.fs.mkdir(folder, { recursive: true }) log += this.updateLog(`Downloading ${latest.link} to ${target}. `) + '\n' + this.emit('updatelog', log) await this.ctx.db.write() // Download the latest version using the provider in question. @@ -238,11 +244,13 @@ export default class Application extends EventEmitter { }) log += '\n' + this.updateLog(`Extracting ${target}. `) + '\n' + this.emit('updatelog', log) await this.ctx.db.write() // Download was successful, extract the archived file that we downloaded - await this.ctx.util.extractFile(target, function(msg) { + await this.ctx.util.extractFile(target, (msg) => { log += msg + this.emit('updatelog', log) }).catch(function(err) { latest.failtodownload = (latest.failtodownload || 0) + 1 return Promise.reject(err) @@ -262,6 +270,7 @@ export default class Application extends EventEmitter { .catch((err) => { latest.failtodownload = (latest.failtodownload || 0) + 1 log += this.updateLog('Version did not include or was missing index.mjs. ') + '\n' + this.emit('updatelog', log) return Promise.reject(err) }) @@ -279,6 +288,7 @@ export default class Application extends EventEmitter { if (packageStat) { log += this.updateLog(`running npm install --production. `) + '\n' + this.emit('updatelog', log) await this.ctx.db.write() // For some weird reason, --loglevel=notice is required otherwise @@ -287,15 +297,20 @@ export default class Application extends EventEmitter { this.ctx.util.getNpmExecutable(), ['install', '--production', '--no-optional', '--no-package-lock', '--no-audit', '--loglevel=notice'], folder, - function(msg) { log += msg } + (msg) => { + log += msg + this.emit('updatelog', log) + } ).catch(function(err) { latest.failtoinstall = (latest.failtoinstall || 0) + 1 return Promise.reject(err) }) log = this.logAddSeperator(log) + this.emit('updatelog', log) } else { log += this.updateLog('Release did not contain package.json, skipping npm install. ') + '\n' + this.emit('updatelog', log) } } catch (err) { log += this.updateLog(`Error: ${err.message}. `) + '\n' @@ -311,12 +326,14 @@ export default class Application extends EventEmitter { if (latest) { latest.log = log } + this.emit('updatelog', log) return Promise.reject(err) } // If we reached here then everything went swimmingly. Mark the version // as being installed and attach the install log to it. log += this.updateLog(`Finished updating ${this.name} to version ${latest.version}.`) + '\n' + this.emit('updatelog', log) this.ctx.db.data.core[this.name].latestInstalled = latest.version latest.installed = true latest.log = log @@ -369,7 +386,22 @@ export default class Application extends EventEmitter { this.workers[i].started = new Date() } - async runVersion(version) { + runVersion(version) { + this.ctx.db.data.core[this.name].active = version + this.running = version + this.emit('running', this.running) + + return this.ctx.db.write().then(() => { + return this._runVersion(version) + .catch((err) => { + this.running = '' + this.emit('running', this.running) + return Promise.reject(err) + }) + }) + } + + async _runVersion(version) { this.ctx.db.data.core[this.name].active = version await this.ctx.db.write() @@ -443,6 +475,8 @@ export default class Application extends EventEmitter { } closeServer() { + this.running = '' + this.emit('running', this.running) if (this.config.cluster && !this.isSlave) { if (this.__clusterWorkerDied) { this.cluster.off('exit', this.__clusterWorkerDied) diff --git a/core/lib.mjs b/core/lib.mjs index 38bd080..eb48606 100644 --- a/core/lib.mjs +++ b/core/lib.mjs @@ -37,7 +37,11 @@ export default class ServiceCore { async init(module = null) { this.db = await GetDB(this.config, this.log, this.dbfilename) - this.core = new Core(this.db, this.util, this.log) + this.core = new Core(this.db, this.util, this.log, () => { + let err = new Error('Got request to restart') + this.log.fatal(err) + process.exit(0) + }) let provider = new StaticProvider() this.app = new Application({ diff --git a/core/runner.mjs b/core/runner.mjs index 30f8dec..9cc5fda 100644 --- a/core/runner.mjs +++ b/core/runner.mjs @@ -34,12 +34,9 @@ export async function runner(root_import_meta_url, configname = 'config.json', d const db = await GetDB(config, log, util.getPathFromRoot('./' + dbname)) const core = new Core(db, util, log, function(msg) { - if (msg) { - runner.log.warn('Got a restart request: ' + msg) - } else { - runner.log.warn('Got a restart request with no message or reason') - } - process.exit(1) + let err = new Error('Got request to restart' + (msg ? ': ' + msg : '')) + runner.log.fatal(err) + process.exit(0) }) await core.init() await core.run() diff --git a/package.json b/package.json index ed8432c..860062d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "service-core", - "version": "3.0.0-beta.13", + "version": "3.0.0-beta.14", "description": "Core boiler plate code to install node server as windows service", "main": "index.mjs", "scripts": { diff --git a/test/application.run.test.mjs b/test/application.run.test.mjs index fbda27a..fbe0631 100644 --- a/test/application.run.test.mjs +++ b/test/application.run.test.mjs @@ -48,9 +48,11 @@ t.describe('#runVersion("static")', function() { assert.strictEqual(checkCtx.app, app) }) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('static')) assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, '') assert.match(err.message, /http/i) assert.match(err.message, /createServer/i) @@ -62,9 +64,11 @@ t.describe('#runVersion("static")', function() { app.config.startWaitUntilFail = 50 app.registerModule(function() { return new Promise(function() {}) }) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('static')) assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, '') assert.match(err.message, /time/i) assert.match(err.message, /out/i) @@ -83,9 +87,11 @@ t.describe('#runVersion("static")', function() { }) }) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) await app.runVersion('static') assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, 'static') assert.strictEqual(ctx.db.data.core.testapp.active, 'static') }) @@ -101,9 +107,11 @@ t.describe('#runVersion("static")', function() { app.config.heartbeatAttemptsWait = 5 app.registerModule(defaultHandler(handler)) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('static')) assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, '') assert.match(err.message, /failed/i) assert.match(err.message, /400/i) @@ -121,11 +129,13 @@ t.describe('#runVersion("static")', function() { app.config.heartbeatAttemptsWait = 10 app.registerModule(defaultHandler(handler)) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let start = performance.now() let err = await assert.isRejected(app.runVersion('static')) let end = performance.now() assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, '') assert.match(err.message, /failed/i) assert.match(err.message, /time/i) @@ -150,9 +160,11 @@ t.describe('#runVersion("static")', function() { app.config.heartbeatAttemptsWait = 5 app.registerModule(defaultHandler(handler)) + assert.strictEqual(app.running, '') let err = await assert.isRejected(app.runVersion('static')) assert.match(err.message, /failed/i) assert.match(err.message, /400/i) + assert.strictEqual(app.running, '') await app.closeServer() app.registerModule(defaultHandler(handler)) @@ -173,9 +185,11 @@ t.describe('#runVersion("static")', function() { app.registerModule(defaultHandler(handler)) app.isSlave = true + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) await app.runVersion('static') assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, 'static') assert.strictEqual(called, 0) assert.strictEqual(ctx.db.data.core.testapp.active, 'static') @@ -226,9 +240,11 @@ t.describe('#runVersion("version")', function() { app.config.port = assertPort stubFsStat.rejects(assertNotError) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('v100')) assert.strictEqual(app.fresh, true) + assert.strictEqual(app.running, '') assert.notStrictEqual(err, assertNotError) assert.match(err.message, new RegExp(assertNotError.message)) @@ -246,9 +262,11 @@ t.describe('#runVersion("version")', function() { await fs.mkdir(util.getPathFromRoot('./testnoexisting/v99'), { recursive: true }) await fs.writeFile(util.getPathFromRoot('./testnoexisting/v99/index.mjs'), `throw new Error('${assertError.message}')`) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('v99')) assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, '') assert.notStrictEqual(err, assertError) assert.strictEqual(err.message, assertError.message) @@ -262,10 +280,12 @@ t.describe('#runVersion("version")', function() { await fs.mkdir(util.getPathFromRoot('./testnoexisting/v98'), { recursive: true }) await fs.writeFile(util.getPathFromRoot('./testnoexisting/v98/index.mjs'), ``) + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) let err = await assert.isRejected(app.runVersion('v98')) assert.strictEqual(app.fresh, false) assert.match(err.message, /start/i) + assert.strictEqual(app.running, '') assert.strictEqual(app.ctx.db.data.core.testnoexisting.active, 'v98') let checkDb = await lowdb({}, ctx.log, assertConfig) @@ -282,9 +302,11 @@ t.describe('#runVersion("version")', function() { app.ctx.log.info.reset() app.ctx.log.event.info.reset() + assert.strictEqual(app.running, '') assert.strictEqual(app.fresh, true) await app.runVersion('v97') assert.strictEqual(app.fresh, false) + assert.strictEqual(app.running, 'v97') assert.ok(app.ctx.log.info.called) assert.ok(app.ctx.log.event.info.called) diff --git a/test/application.test.mjs b/test/application.test.mjs index e0833e5..2027cfd 100644 --- a/test/application.test.mjs +++ b/test/application.test.mjs @@ -73,7 +73,7 @@ t.describe('constructor()', function() { assert.strictEqual(app.ctx.sc.getLog, getLog) assert.strictEqual(app.name, assertName) assert.strictEqual(app.fresh, true) - assert.strictEqual(app.running, false) + assert.strictEqual(app.running, '') assert.strictEqual(app.monitoringCluster, false) assert.deepStrictEqual(app.workers, {}) assert.strictEqual(app.isSlave, false) @@ -328,10 +328,13 @@ t.describe('#closeServer()', function() { }) t.test('should call closeServer correctly', async function() { + const assertNotVersion = 'v1521' const assertError = new Error('Moonlight Fiesta') stubCloseServer.rejects(assertError) + app.running = assertNotVersion let err = await assert.isRejected(app.closeServer()) + assert.strictEqual(app.running, '') assert.strictEqual(err, assertError) })