-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.js
163 lines (152 loc) · 6.29 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
const config = require('./data/config');
const fs = require('fs');
const path = require('path');
const cron = require('node-cron');
const fetch = require('node-fetch');
const abort = require('abort-controller');
const nmap = require('libnmap');
let cachePath = path.resolve("./data/cache.json");
const cache = fs.existsSync(cachePath) ? JSON.parse(fs.readFileSync(cachePath, {encoding: "utf8"})) : {};
// delete cache file to remove orphaned cache values
if (fs.existsSync(cachePath)) {
fs.unlinkSync(cachePath);
}
process.on('SIGINT', () => {
fs.writeFileSync(cachePath, JSON.stringify(cache), {encoding: "utf8"});
process.exit(0);
});
const cachetStatusMapping = {
"ONLINE": 1,
"SLOW": 2,
"OFFLINE": 3,
"INCIDENT": 4
};
config.cron = config.cron || "* * * * *";
config.defaults = config.defaults || {};
config.defaults.retry = config.defaults.retry || 0;
config.defaults.waitUntilRetry = config.defaults.waitUntilRetry || 5;
config.defaults.performanceTimeout = config.defaults.performanceTimeout || 1;
config.defaults.requestTimeout = config.defaults.requestTimeout || 30;
config.defaults.offlineTimeUntilMajor = config.defaults.offlineTimeUntilMajor || 300;
const checkHttp = async (url, performanceTimeout, requestTimeout) => {
const controller = new abort.AbortController();
const timeout = setTimeout(() => controller.abort(), requestTimeout * 1000);
try {
const start = new Date().getTime();
const response = await fetch(url, {signal: controller.signal});
const stop = new Date().getTime();
if (response.ok) {
if (stop - start > performanceTimeout * 1000) {
return {status: "SLOW", message: response.statusText};
}
return {status: "ONLINE", message: response.statusText};
}
return {status: "OFFLINE", message: response.statusText};
} catch (e) {
return {status: "OFFLINE", message: e.message};
} finally {
clearTimeout(timeout);
}
};
const checkPort = async (host, port, type, performanceTimeout, requestTimeout) => {
return await new Promise(resolve => {
nmap.scan({
flags: ['-Pn'],
range: [host],
ports: port.toString(),
timeout: requestTimeout,
udp: type === 'udp'
}, (error, report) => {
try {
if (error) {
resolve({status: "OFFLINE", message: error});
} else {
const result = report[host].host[0];
const time = parseInt(result.item.endtime) - parseInt(result.item.starttime);
const status = result.ports[0].port[0].state[0].item;
if (status.state.includes('open')) {
if (time > performanceTimeout * 1000) {
resolve({status: "SLOW", message: status.state});
} else {
resolve({status: "ONLINE", message: status.state});
}
} else {
resolve({status: "OFFLINE", message: status.state});
}
}
} catch (e) {
console.error(e);
resolve({status: "OFFLINE", message: 'an unexpected error occurred'});
}
});
});
};
async function checkStatus(service) {
const performanceTimeout = service.performanceTimeout || config.defaults.performanceTimeout;
const requestTimeout = service.requestTimeout || config.defaults.requestTimeout;
switch (service.type) {
case 'HTTP':
return await checkHttp(service.url, performanceTimeout, requestTimeout);
case 'TCP':
return await checkPort(service.host, service.port, 'tcp', performanceTimeout, requestTimeout);
case 'UDP':
return await checkPort(service.host, service.port, 'udp', performanceTimeout, requestTimeout);
default:
throw new Error('unsupported type "' + type + '"')
}
}
const checkService = async (service, oldStatus) => {
const retry = service.retry || config.defaults.retry;
const waitUntilRetry = (service.waitUntilRetry || config.defaults.waitUntilRetry) * 1000;
let newStatus;
for (let i = retry; i >= 0; i--) {
newStatus = await checkStatus(service);
newStatus.changed = new Date().getTime();
if (newStatus.status === "ONLINE") {
return newStatus;
}
const offlineTimeUntilMajor = (service.offlineTimeUntilMajor || config.defaults.offlineTimeUntilMajor) * 1000;
if (newStatus === "OFFLINE" && oldStatus && (oldStatus.status === "INCIDENT" || oldStatus.status === "OFFLINE" && oldStatus.changed + offlineTimeUntilMajor < newStatus.changed)) {
newStatus.status = "INCIDENT";
}
if (i >= 0 && waitUntilRetry > 0) {
await new Promise(r => setTimeout(r, waitUntilRetry));
}
}
return newStatus;
};
const pushStatusToCachet = async (id, status) => {
try {
let currentCachetStatus = cachetStatusMapping[status];
const oldState = await fetch(config.api + '/components/' + id).then(r => r.json());
if (oldState.data.status === currentCachetStatus) {
console.log('state already set');
return;
}
const update = await fetch(config.api + '/components/' + id, {
method: 'PUT',
body: JSON.stringify({status: currentCachetStatus}),
headers: {
'Content-Type': 'application/json',
'X-Cachet-Token': config.token
}
});
if (!update.ok) {
console.log('failed to update status: ' + update.statusText);
}
} catch (e) {
console.log('failed to update status', e);
}
};
const check = async () => {
await Promise.all(config.services.map(async service => {
const oldStatus = cache[service.id];
const newStatus = await checkService(service, oldStatus);
if (!oldStatus || oldStatus.status !== newStatus.status) {
console.log(service.id + ' status changed: ' + newStatus.status);
await pushStatusToCachet(service.id, newStatus.status);
cache[service.id] = newStatus;
}
}));
};
cron.schedule(config.cron, async () => await check(), {});