diff --git a/index.js b/index.js index 7ccaeb4..71e512d 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,7 @@ 'use strict'; -const { makeId } = require('core/makeid'); -const { testService } = require('./lib/testservice'); -const { testServices } = require('./lib/testservices'); +const { fork } = require('child_process'); +const { processOutage } = require('./lib/processoutage'); const guiCluster = 'web service status'; const icons = { @@ -181,7 +180,29 @@ module.exports = { runAtBoot: true, active: true, interval: Number(settings.autotestInterval) * 60 * 1000, - action: () => testServices({ server, settings, makeId }), + action: async () => { + const services = await server + .storage + .store('smartyellow/webservice') + .find({ autotestEnabled: true }) + .toArray(); + + if (!services.length) { + return; + } + + const runtime = fork(__dirname + '/lib/runtime.js'); + runtime.send({ command: 'testAll', services }); + + runtime.on('message', message => { + if (message.error) { + server.error(message.error); + } + else if (message.outage) { + processOutage({ outage: message.outage, server, settings }); + } + }); + }, }, ], @@ -191,12 +212,24 @@ module.exports = { event: 'saveEntity', entity: [ 'smartyellow/webservice' ], purpose: 'Check whether services are up and send a notification if not.', - handler: ({ item }) => testService({ - service: item, - server, - settings, - makeId, - }), + handler: ({ item }) => { + const runtime = fork(__dirname + '/lib/runtime.js'); + runtime.send({ command: 'testOne', service: item }); + runtime.on('message', message => { + if (message.error) { + server.error(message.error); + } + else if (message.outage) { + processOutage({ + outage: { + [item.id]: message.outage, + }, + server, + settings, + }); + } + }); + }, }, ], diff --git a/lib/testservice.js b/lib/processoutage.js similarity index 66% rename from lib/testservice.js rename to lib/processoutage.js index 0d28560..34c3191 100644 --- a/lib/testservice.js +++ b/lib/processoutage.js @@ -1,46 +1,37 @@ 'use strict'; -const { testEndpoints } = require('./testendpoints'); -const { roundDate } = require('./utils'); +const { makeId } = require('core/makeid'); -async function testService({ service, server, settings, makeId }) { - if (!service.autotestEnabled) { - return; - } - - try { - // Autotest the service - const result = await testEndpoints(service.autotest); - const name = service.name.en; - - // Insert check date +async function processOutage({ outage, server, settings }) { + for (const [ id, testResult ] of Object.entries(outage)) { + // Update check date await server.storage.store('smartyellow/webservice').update( - { id: service.id }, + { id }, { $set: { lastChecked: new Date() } } ); - // Get all heartbeats plus the last one + // Get service entry + const service = await server + .storage + .store('smartyellow/webservice') + .findOne({ id }); + + // Get last heartbeat const heartbeat = await server .storage .store('smartyellow/webserviceheartbeat') - .find({ webservice: service.id }) + .find({ webservice: id }) .toArray(); const lastBeat = heartbeat[heartbeat.length - 1]; - // Get date - const date = roundDate(new Date()); - - // Error - if (result.error) { - server.error('Error while checking status: ' + name); - server.error(result); + // Encountered an error while checking status + if (testResult.error) { + server.error('Error while checking status of ' + id); + server.error(testResult); } - // Service down - else if (!result.serviceUp) { - server.warn('Service down: ' + name); - server.warn(result); - + // Service is down + else if (!testResult.serviceUp) { // Don't perform automatic actions if already done if ((lastBeat && lastBeat.down == false) || !lastBeat) { // Insert heartbeat if last one is not valid anymore @@ -48,9 +39,9 @@ async function testService({ service, server, settings, makeId }) { await server.storage.store('smartyellow/webserviceheartbeat').insert({ id: makeId(6), down: true, - webservice: service.id, - testResult: result, - date: date, + webservice: id, + testResult, + date: new Date(), }); } catch (err) { @@ -64,10 +55,10 @@ async function testService({ service, server, settings, makeId }) { await server.sendEmail({ sender: settings.emailSender, to: settings.emailRecipient, - subject: `[outage] ${name} is down`, + subject: `[outage] ${service.name} is down`, body: `

Dear recipient,

This is to inform you about web service outage. - The service ${name} does not meet the + The service ${service.name} does not meet the requirements for being considered as 'working'.

Please always check this before taking action.

`, }); @@ -87,7 +78,7 @@ async function testService({ service, server, settings, makeId }) { .insert({ id: makeId(6), name: { - en: `[automatic] Outage for ${name}`, + en: `[automatic] Outage for ${service.name}`, }, state: 'concept', resolved: false, @@ -96,7 +87,7 @@ async function testService({ service, server, settings, makeId }) { notes: [ { date: new Date(), userId: 'system', - text: `Automatically created outage. Reason: ${JSON.stringify(result, null, 2)}`, + text: `Automatically created outage. Reason: ${JSON.stringify(testResult, null, 2)}`, } ], }); } @@ -110,17 +101,16 @@ async function testService({ service, server, settings, makeId }) { // Service up else { - server.info('Service up: ' + name); - - // Insert heartbeat if last one is not valid anymore + // Don't perform automatic actions if already done if ((lastBeat && lastBeat.down == true) || !lastBeat) { + // Insert heartbeat if last one is not valid anymore try { await server.storage.store('smartyellow/webserviceheartbeat').insert({ id: makeId(6), down: false, - webservice: service.id, - date: date, - testResult: result, + webservice: id, + testResult, + date: new Date(), }); } catch (err) { @@ -130,9 +120,6 @@ async function testService({ service, server, settings, makeId }) { } } } - catch (err) { - server.error(err); - } } -module.exports = { testService }; +module.exports = { processOutage }; diff --git a/lib/runtime.js b/lib/runtime.js new file mode 100644 index 0000000..0017d92 --- /dev/null +++ b/lib/runtime.js @@ -0,0 +1,46 @@ +'use strict'; + +const { testEndpoints } = require('./testendpoints'); + +process.on('message', async message => { + switch (message.command) { + case 'testAll': + if (!message.services) { + process.send({ error: 'services is not defined' }); + } + else { + const ids = []; + const promises = []; + for (const service of message.services) { + if (service.autotestEnabled) { + ids.push(service.id); + promises.push(testEndpoints(service.autotest)); + } + } + + const result = await Promise.all(promises); + const mapped = {}; + for (const [ i, id ] of ids.entries()) { + mapped[id] = result[i]; + } + + process.send({ outage: mapped }); + } + break; + + case 'testOne': + if (!message.service) { + process.send({ error: 'service is not defined' }); + } + else { + const { service } = message; + const result = await testEndpoints(service.autotest); + process.send({ outage: result }); + } + break; + + default: + console.error('Unknown command:', message.command); + break; + } +}); diff --git a/lib/testservices.js b/lib/testservices.js deleted file mode 100644 index 924cac0..0000000 --- a/lib/testservices.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const { testService } = require('./testservice'); - -async function testServices({ server, settings, makeId }) { - const services = await server - .storage - .store('smartyellow/webservice') - .find({ autotestEnabled: true }) - .toArray(); - - services.forEach(async service => { - if (service.autotestEnabled) { - testService({ service, server, settings, makeId }); - } - }); -} - -module.exports = { testServices }; diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index daa76ae..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -// Round minutes up to 10 and remove seconds -// e.g. 15:34:51 -> 15:30:00 -function roundDate(d) { - d.setMinutes(Math.round(d.getMinutes() / 10) * 10); - d.setSeconds(0, 0); - return d; -} - -module.exports = { roundDate };