mirror of
https://github.com/smartyellow/status.git
synced 2025-06-28 04:35:11 +00:00
25
lib/operators.js
Normal file
25
lib/operators.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const operatorNames = {
|
||||
equal: 'equal to',
|
||||
start: 'starting with',
|
||||
end: 'ending in',
|
||||
contain: 'containing',
|
||||
greater: 'greater than',
|
||||
less: 'less than',
|
||||
greatereq: 'greater than or equal to',
|
||||
lesseq: 'less than or equal to',
|
||||
};
|
||||
|
||||
const operators = {
|
||||
equal: (a, b) => String(a) === String(b),
|
||||
start: (a, b) => String(a).startsWith(b),
|
||||
end: (a, b) => String(a).startsWith(b),
|
||||
contain: (a, b) => String(a).includes(b),
|
||||
greater: (a, b) => Number(a) > Number(b),
|
||||
less: (a, b) => Number(a) < Number(b),
|
||||
greatereq: (a, b) => Number(a) >= Number(b),
|
||||
lesseq: (a, b) => Number(a) <= Number(b),
|
||||
};
|
||||
|
||||
module.exports = { operatorNames, operators };
|
21
lib/realvalues.js
Normal file
21
lib/realvalues.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const realValueNames = {
|
||||
httpstatus: 'HTTP status code',
|
||||
body: 'Response body',
|
||||
bodylength: 'Response body length',
|
||||
ok: 'Response OK?',
|
||||
redir: 'Redirected?',
|
||||
restype: 'Response type',
|
||||
};
|
||||
|
||||
const realValues = {
|
||||
httpstatus: ({ res }) => res.status,
|
||||
body: ({ body }) => body,
|
||||
bodylength: ({ body }) => body.length,
|
||||
ok: ({ res }) => res.ok,
|
||||
redir: ({ res }) => res.redirected,
|
||||
restype: ({ res }) => res.type,
|
||||
};
|
||||
|
||||
module.exports = { realValueNames, realValues };
|
66
lib/testendpoints.js
Normal file
66
lib/testendpoints.js
Normal file
@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const { operators } = require('./operators');
|
||||
const { realValues } = require('./realvalues');
|
||||
|
||||
async function testEndpoints(endpoints) {
|
||||
const output = {
|
||||
serviceUp: true,
|
||||
success: true,
|
||||
error: false,
|
||||
requirement: undefined,
|
||||
realValue: undefined,
|
||||
};
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
const headers = endpoint.headers.reduce((obj, item) => {
|
||||
obj[item.name] = item.value;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
const res = await fetch(endpoint.uri, { headers });
|
||||
const body = await res.text();
|
||||
|
||||
endpoint.requirements.forEach(requirement => {
|
||||
if (output.success === false || output.serviceUp === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Object.keys(operators).includes(requirement.operator)) {
|
||||
output.success = false;
|
||||
output.error = 'unknown operator: ' + requirement.operator;
|
||||
}
|
||||
|
||||
if (!Object.keys(realValues).includes(requirement.type)) {
|
||||
output.success = false;
|
||||
output.error = 'unknown type: ' + requirement.type;
|
||||
}
|
||||
|
||||
const realValue = realValues[requirement.type]({ res, body });
|
||||
let result = operators[requirement.operator](realValue, requirement.string);
|
||||
|
||||
if (!requirement.truth) {
|
||||
result = !result;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
output.success = true;
|
||||
output.serviceUp = false;
|
||||
output.requirement = requirement;
|
||||
output.realValue = realValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
output.success = false;
|
||||
output.error = err;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = { testEndpoints };
|
138
lib/testservice.js
Normal file
138
lib/testservice.js
Normal file
@ -0,0 +1,138 @@
|
||||
'use strict';
|
||||
|
||||
const { testEndpoints } = require('./testendpoints');
|
||||
const { roundDate } = require('./utils');
|
||||
|
||||
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
|
||||
await server.storage.store('smartyellow/webservice').update(
|
||||
{ id: service.id },
|
||||
{ $set: { lastChecked: new Date() } }
|
||||
);
|
||||
|
||||
// Get all heartbeats plus the last one
|
||||
const heartbeat = await server
|
||||
.storage
|
||||
.store('smartyellow/webserviceheartbeat')
|
||||
.find({ webservice: service.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);
|
||||
}
|
||||
|
||||
// Service down
|
||||
else if (!result.serviceUp) {
|
||||
server.warn('Service down: ' + name);
|
||||
server.warn(result);
|
||||
|
||||
// Don't perform automatic actions if already done
|
||||
if ((lastBeat && lastBeat.down == false) || !lastBeat) {
|
||||
// Insert heartbeat if last one is not valid anymore
|
||||
try {
|
||||
await server.storage.store('smartyellow/webserviceheartbeat').insert({
|
||||
id: makeId(6),
|
||||
down: true,
|
||||
webservice: service.id,
|
||||
testResult: result,
|
||||
date: date,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error(err);
|
||||
server.error('could not save web service heartbeat');
|
||||
}
|
||||
|
||||
// Send e-mail notification
|
||||
if (server.sendEmail && settings.emailSender && settings.emailRecipient) {
|
||||
try {
|
||||
await server.sendEmail({
|
||||
sender: settings.emailSender,
|
||||
to: settings.emailRecipient,
|
||||
subject: `[outage] ${name} is down`,
|
||||
body: `<p>Dear recipient,</p>
|
||||
<p>This is to inform you about web service outage.
|
||||
The service <em>${name}</em> does not meet the
|
||||
requirements for being considered as 'working'.</p>
|
||||
<p>Please always check this before taking action.</p>`,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error(err);
|
||||
server.error('could not send endpoint status notification e-mail');
|
||||
}
|
||||
}
|
||||
|
||||
// Draft outage entry
|
||||
if (settings.draftOutageEntries) {
|
||||
try {
|
||||
await server
|
||||
.storage
|
||||
.store('smartyellow/webserviceoutage')
|
||||
.insert({
|
||||
id: makeId(6),
|
||||
name: {
|
||||
en: `[automatic] Outage for ${name}`,
|
||||
},
|
||||
state: 'concept',
|
||||
resolved: false,
|
||||
services: [ service.id ],
|
||||
tags: [ 'automatically created' ],
|
||||
notes: [ {
|
||||
date: new Date(),
|
||||
userId: 'system',
|
||||
text: `Automatically created outage. Reason: ${JSON.stringify(result, null, 2)}`,
|
||||
} ],
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error(err);
|
||||
server.error('could not automatically draft outage entry');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service up
|
||||
else {
|
||||
server.info('Service up: ' + name);
|
||||
|
||||
// Insert heartbeat if last one is not valid anymore
|
||||
if ((lastBeat && lastBeat.down == true) || !lastBeat) {
|
||||
try {
|
||||
await server.storage.store('smartyellow/webserviceheartbeat').insert({
|
||||
id: makeId(6),
|
||||
down: false,
|
||||
webservice: service.id,
|
||||
date: date,
|
||||
testResult: result,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
server.error(err);
|
||||
server.error('could not save web service heartbeat');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
server.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { testService };
|
19
lib/testservices.js
Normal file
19
lib/testservices.js
Normal file
@ -0,0 +1,19 @@
|
||||
'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 };
|
11
lib/utils.js
Normal file
11
lib/utils.js
Normal file
@ -0,0 +1,11 @@
|
||||
'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 };
|
Reference in New Issue
Block a user