mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-08 01:30:01 -05:00
initial Pulse implementation
This commit is contained in:
@@ -595,8 +595,8 @@ class MonitorController {
|
|||||||
{ new: true }
|
{ new: true }
|
||||||
);
|
);
|
||||||
monitor.isActive === true
|
monitor.isActive === true
|
||||||
? await this.jobQueue.addJob(monitor._id, monitor)
|
? await this.jobQueue.resumeJob(monitor._id, monitor)
|
||||||
: await this.jobQueue.deleteJob(monitor);
|
: await this.jobQueue.pauseJob(monitor);
|
||||||
|
|
||||||
return res.success({
|
return res.success({
|
||||||
msg: monitor.isActive
|
msg: monitor.isActive
|
||||||
|
|||||||
@@ -46,18 +46,6 @@ class JobQueueController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
obliterateQueue = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
await this.jobQueue.obliterate();
|
|
||||||
return res.success({
|
|
||||||
msg: this.stringService.queueObliterate,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(handleError(error, SERVICE_NAME, "obliterateQueue"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
flushQueue = async (req, res, next) => {
|
flushQueue = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.jobQueue.flushQueues();
|
const result = await this.jobQueue.flushQueues();
|
||||||
|
|||||||
+25
-12
@@ -50,6 +50,9 @@ import JobQueue from "./service/JobQueue/JobQueue.js";
|
|||||||
import JobQueueHelper from "./service/JobQueue/JobQueueHelper.js";
|
import JobQueueHelper from "./service/JobQueue/JobQueueHelper.js";
|
||||||
import { Queue, Worker } from "bullmq";
|
import { Queue, Worker } from "bullmq";
|
||||||
|
|
||||||
|
import PulseQueue from "./service/PulseQueue/PulseQueue.js";
|
||||||
|
import PulseQueueHelper from "./service/PulseQueue/PulseQueueHelper.js";
|
||||||
|
|
||||||
//Network service and dependencies
|
//Network service and dependencies
|
||||||
import NetworkService from "./service/networkService.js";
|
import NetworkService from "./service/networkService.js";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -183,25 +186,35 @@ const startApp = async () => {
|
|||||||
|
|
||||||
const redisService = new RedisService({ Redis: IORedis, logger });
|
const redisService = new RedisService({ Redis: IORedis, logger });
|
||||||
|
|
||||||
const jobQueueHelper = new JobQueueHelper({
|
// const jobQueueHelper = new JobQueueHelper({
|
||||||
redisService,
|
// redisService,
|
||||||
Queue,
|
// Queue,
|
||||||
Worker,
|
// Worker,
|
||||||
logger,
|
// logger,
|
||||||
|
// db,
|
||||||
|
// networkService,
|
||||||
|
// statusService,
|
||||||
|
// notificationService,
|
||||||
|
// });
|
||||||
|
// const jobQueue = await JobQueue.create({
|
||||||
|
// db,
|
||||||
|
// jobQueueHelper,
|
||||||
|
// logger,
|
||||||
|
// stringService,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const pulseQueueHelper = new PulseQueueHelper({
|
||||||
db,
|
db,
|
||||||
|
logger,
|
||||||
networkService,
|
networkService,
|
||||||
statusService,
|
statusService,
|
||||||
notificationService,
|
notificationService,
|
||||||
});
|
});
|
||||||
const jobQueue = await JobQueue.create({
|
const pulseQueue = new PulseQueue({ appSettings, db, pulseQueueHelper, logger });
|
||||||
db,
|
|
||||||
jobQueueHelper,
|
|
||||||
logger,
|
|
||||||
stringService,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register services
|
// Register services
|
||||||
ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
|
// ServiceRegistry.register(JobQueue.SERVICE_NAME, jobQueue);
|
||||||
|
ServiceRegistry.register(JobQueue.SERVICE_NAME, pulseQueue);
|
||||||
ServiceRegistry.register(MongoDB.SERVICE_NAME, db);
|
ServiceRegistry.register(MongoDB.SERVICE_NAME, db);
|
||||||
ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService);
|
ServiceRegistry.register(SettingsService.SERVICE_NAME, settingsService);
|
||||||
ServiceRegistry.register(EmailService.SERVICE_NAME, emailService);
|
ServiceRegistry.register(EmailService.SERVICE_NAME, emailService);
|
||||||
|
|||||||
Generated
+95
-3
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pulsecron/pulse": "1.6.8",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"bcryptjs": "3.0.2",
|
"bcryptjs": "3.0.2",
|
||||||
"bullmq": "5.41.2",
|
"bullmq": "5.41.2",
|
||||||
@@ -1012,6 +1013,37 @@
|
|||||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@pulsecron/pulse": {
|
||||||
|
"version": "1.6.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pulsecron/pulse/-/pulse-1.6.8.tgz",
|
||||||
|
"integrity": "sha512-fg9sT/pfpZTlUYr/ktDu6om6XEm2zmulcZ8TP4JZa+Dmol7/35mMKzDwT1H5a0AUcxiFIRfB4KOpkJ+BI2fgmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cron-parser": "^4.9.0",
|
||||||
|
"date.js": "~0.3.3",
|
||||||
|
"debug": "~4.3.4",
|
||||||
|
"human-interval": "~2.0.1",
|
||||||
|
"moment-timezone": "^0.5.45",
|
||||||
|
"mongodb": "^6.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@pulsecron/pulse/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@scarf/scarf": {
|
"node_modules/@scarf/scarf": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
||||||
@@ -2394,6 +2426,30 @@
|
|||||||
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
|
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
|
||||||
"license": "CC0-1.0"
|
"license": "CC0-1.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/date.js": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date.js/-/date.js-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "~3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date.js/node_modules/debug": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date.js/node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
@@ -3664,6 +3720,15 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/human-interval": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"numbered": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@@ -4986,6 +5051,27 @@
|
|||||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.48",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||||
|
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mongodb": {
|
"node_modules/mongodb": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.13.0.tgz",
|
||||||
@@ -5385,6 +5471,12 @@
|
|||||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/numbered": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/numbered/-/numbered-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-pv/ue2Odr7IfYOO0byC1KgBI10wo5YDauLhxY6/saNzAdAs0r1SotGCPzzCLNPL0xtrAwWRialLu23AAu9xO1g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -7099,9 +7191,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
"node_modules/tar-fs": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
|
||||||
"integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
|
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chownr": "^1.1.1",
|
"chownr": "^1.1.1",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pulsecron/pulse": "1.6.8",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"bcryptjs": "3.0.2",
|
"bcryptjs": "3.0.2",
|
||||||
"bullmq": "5.41.2",
|
"bullmq": "5.41.2",
|
||||||
|
|||||||
@@ -25,12 +25,6 @@ class QueueRoutes {
|
|||||||
this.queueController.addJob
|
this.queueController.addJob
|
||||||
);
|
);
|
||||||
|
|
||||||
this.router.post(
|
|
||||||
"/obliterate",
|
|
||||||
isAllowed(["admin", "superadmin"]),
|
|
||||||
this.queueController.obliterateQueue
|
|
||||||
);
|
|
||||||
|
|
||||||
this.router.post(
|
this.router.post(
|
||||||
"/flush",
|
"/flush",
|
||||||
isAllowed(["admin", "superadmin"]),
|
isAllowed(["admin", "superadmin"]),
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ class JobQueue {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this.healthCheckInterval = setInterval(async () => {
|
this.healthCheckInterval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const queueHealthChecks = await this.checkQueueHealth();
|
const queueHealthChecks = await this.checkQueueHealth();
|
||||||
@@ -116,6 +115,14 @@ class JobQueue {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pauseJob = async (monitor) => {
|
||||||
|
this.deleteJob(monitor);
|
||||||
|
};
|
||||||
|
|
||||||
|
resumeJob = async (monitor) => {
|
||||||
|
this.addJob(monitor._id, monitor);
|
||||||
|
};
|
||||||
|
|
||||||
async addJob(jobName, monitor) {
|
async addJob(jobName, monitor) {
|
||||||
this.logger.info({
|
this.logger.info({
|
||||||
message: `Adding job ${monitor?.url ?? "No URL"}`,
|
message: `Adding job ${monitor?.url ?? "No URL"}`,
|
||||||
|
|||||||
@@ -254,7 +254,8 @@ class JobQueueHelper {
|
|||||||
|
|
||||||
// Get the current status
|
// Get the current status
|
||||||
await job.updateProgress(30);
|
await job.updateProgress(30);
|
||||||
const networkResponse = await this.networkService.getStatus(job);
|
const monitor = job.data;
|
||||||
|
const networkResponse = await this.networkService.getStatus(monitor);
|
||||||
if (
|
if (
|
||||||
job.data.type === "distributed_http" ||
|
job.data.type === "distributed_http" ||
|
||||||
job.data.type === "distributed_test"
|
job.data.type === "distributed_test"
|
||||||
@@ -271,14 +272,17 @@ class JobQueueHelper {
|
|||||||
|
|
||||||
// Handle status change
|
// Handle status change
|
||||||
await job.updateProgress(60);
|
await job.updateProgress(60);
|
||||||
const { monitor, statusChanged, prevStatus } =
|
const {
|
||||||
await this.statusService.updateStatus(networkResponse);
|
monitor: updatedMonitor,
|
||||||
|
statusChanged,
|
||||||
|
prevStatus,
|
||||||
|
} = await this.statusService.updateStatus(networkResponse);
|
||||||
// Handle notifications
|
// Handle notifications
|
||||||
await job.updateProgress(80);
|
await job.updateProgress(80);
|
||||||
this.notificationService
|
this.notificationService
|
||||||
.handleNotifications({
|
.handleNotifications({
|
||||||
...networkResponse,
|
...networkResponse,
|
||||||
monitor,
|
monitor: updatedMonitor,
|
||||||
prevStatus,
|
prevStatus,
|
||||||
statusChanged,
|
statusChanged,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { Pulse } from "@pulsecron/pulse";
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
|
||||||
|
const SERVICE_NAME = "PulseQueue";
|
||||||
|
class PulseQueue {
|
||||||
|
static SERVICE_NAME = SERVICE_NAME;
|
||||||
|
|
||||||
|
constructor({ appSettings, db, pulseQueueHelper, logger }) {
|
||||||
|
this.db = db;
|
||||||
|
this.appSettings = appSettings;
|
||||||
|
this.pulseQueueHelper = pulseQueueHelper;
|
||||||
|
this.logger = logger;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ****************************************
|
||||||
|
// Core methods
|
||||||
|
// ****************************************
|
||||||
|
init = async () => {
|
||||||
|
try {
|
||||||
|
const mongoConnectionString =
|
||||||
|
this.appSettings.dbConnectionString || "mongodb://localhost:27017/uptime_db";
|
||||||
|
this.pulse = new Pulse({ db: { address: mongoConnectionString } });
|
||||||
|
await this.pulse.start();
|
||||||
|
this.pulse.define("monitor-job", this.pulseQueueHelper.getMonitorJob(), {});
|
||||||
|
|
||||||
|
const monitors = await this.db.getAllMonitors();
|
||||||
|
for (const monitor of monitors) {
|
||||||
|
await this.addJob(monitor._id, monitor);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error({
|
||||||
|
message: "Failed to initialize PulseQueue",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "init",
|
||||||
|
details: error,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addJob = async (monitorId, monitor) => {
|
||||||
|
this.logger.debug({
|
||||||
|
message: `Adding job ${monitor?.url ?? "No URL"}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "addJob",
|
||||||
|
});
|
||||||
|
const intervalInSeconds = monitor.interval / 1000;
|
||||||
|
const job = this.pulse.create("monitor-job", {
|
||||||
|
monitor,
|
||||||
|
});
|
||||||
|
job.unique({ "data.monitor._id": monitor._id });
|
||||||
|
job.attrs.jobId = monitorId.toString();
|
||||||
|
job.repeatEvery(`${intervalInSeconds} seconds`);
|
||||||
|
await job.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteJob = async (monitor) => {
|
||||||
|
this.logger.debug({
|
||||||
|
message: `Deleting job ${monitor?.url ?? "No URL"}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "deleteJob",
|
||||||
|
});
|
||||||
|
const result = await this.pulse.cancel({
|
||||||
|
"data.monitor._id": monitor._id,
|
||||||
|
});
|
||||||
|
console.log(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
pauseJob = async (monitor) => {
|
||||||
|
const result = await this.pulse.disable({
|
||||||
|
"data.monitor._id": monitor._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.length < 1) {
|
||||||
|
throw new Error("Failed to pause monitor");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug({
|
||||||
|
message: `Paused monitor ${monitor._id}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "pauseJob",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
resumeJob = async (monitor) => {
|
||||||
|
const result = await this.pulse.enable({
|
||||||
|
"data.monitor._id": monitor._id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.length < 1) {
|
||||||
|
throw new Error("Failed to resume monitor");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug({
|
||||||
|
message: `Resumed monitor ${monitor._id}`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "resumeJob",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
shutdown = async () => {
|
||||||
|
this.logger.info({
|
||||||
|
message: "Shutting down JobQueue",
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "shutdown",
|
||||||
|
});
|
||||||
|
await this.pulse.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ****************************************
|
||||||
|
// Diagnostic methods
|
||||||
|
// ****************************************
|
||||||
|
|
||||||
|
getMetrics = async () => {
|
||||||
|
const jobs = await this.pulse.jobs();
|
||||||
|
const metrics = jobs.reduce(
|
||||||
|
(acc, job) => {
|
||||||
|
acc.jobs++;
|
||||||
|
if (job.attrs.failCount > 0 && job.attrs.failedAt >= job.attrs.lastFinishedAt) {
|
||||||
|
acc.failingJobs++;
|
||||||
|
}
|
||||||
|
if (job.attrs.lockedAt) {
|
||||||
|
acc.activeJobs++;
|
||||||
|
}
|
||||||
|
if (job.attrs.failCount > 0) {
|
||||||
|
acc.jobsWithFailures.push({
|
||||||
|
monitorId: job.attrs.data.monitor._id,
|
||||||
|
monitorUrl: job.attrs.data.monitor.url,
|
||||||
|
failCount: job.attrs.failCount,
|
||||||
|
failReason: job.attrs.failReason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ jobs: 0, activeJobs: 0, failingJobs: 0, jobsWithFailures: [] }
|
||||||
|
);
|
||||||
|
return metrics;
|
||||||
|
};
|
||||||
|
|
||||||
|
getJobs = async () => {
|
||||||
|
const jobs = await this.pulse.jobs();
|
||||||
|
return jobs.map((job) => {
|
||||||
|
return {
|
||||||
|
monitorId: job.attrs.data.monitor._id,
|
||||||
|
monitorUrl: job.attrs.data.monitor.url,
|
||||||
|
lockedAt: job.attrs.lockedAt,
|
||||||
|
runCount: job.attrs.runCount || 0,
|
||||||
|
failCount: job.attrs.failCount || 0,
|
||||||
|
failReason: job.attrs.failReason,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
flushQueues = async () => {
|
||||||
|
const cancelRes = await this.pulse.cancel();
|
||||||
|
await this.pulse.stop();
|
||||||
|
const initRes = await this.init();
|
||||||
|
return {
|
||||||
|
flushedJobs: cancelRes,
|
||||||
|
initSuccess: initRes,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
obliterate = async () => {
|
||||||
|
await this.flushQueues();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PulseQueue;
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
const SERVICE_NAME = "PulseQueueHelper";
|
||||||
|
|
||||||
|
class PulseQueueHelper {
|
||||||
|
constructor({ db, logger, networkService, statusService, notificationService }) {
|
||||||
|
this.db = db;
|
||||||
|
this.logger = logger;
|
||||||
|
this.networkService = networkService;
|
||||||
|
this.statusService = statusService;
|
||||||
|
this.notificationService = notificationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMonitorJob = () => {
|
||||||
|
return async (job) => {
|
||||||
|
try {
|
||||||
|
const monitor = job.attrs.data.monitor;
|
||||||
|
const monitorId = job.attrs.data.monitor._id;
|
||||||
|
if (!monitorId) {
|
||||||
|
throw new Error("No monitor id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
|
||||||
|
if (maintenanceWindowActive) {
|
||||||
|
this.logger.info({
|
||||||
|
message: `Monitor ${monitorId} is in maintenance window`,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "getMonitorJob",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const networkResponse = await this.networkService.getStatus(monitor);
|
||||||
|
if (monitor.type === "distributed_http" || monitor.type === "distributed_test") {
|
||||||
|
await job.updateProgress(100);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkResponse) {
|
||||||
|
throw new Error("No network response");
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
monitor: updatedMonitor,
|
||||||
|
statusChanged,
|
||||||
|
prevStatus,
|
||||||
|
} = await this.statusService.updateStatus(networkResponse);
|
||||||
|
|
||||||
|
this.notificationService
|
||||||
|
.handleNotifications({
|
||||||
|
...networkResponse,
|
||||||
|
monitor: updatedMonitor,
|
||||||
|
prevStatus,
|
||||||
|
statusChanged,
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.logger.error({
|
||||||
|
message: error.message,
|
||||||
|
service: SERVICE_NAME,
|
||||||
|
method: "createJobHandler",
|
||||||
|
details: `Error sending notifications for job ${job.id}: ${error.message}`,
|
||||||
|
stack: error.stack,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn({
|
||||||
|
message: error.message,
|
||||||
|
service: error.service || SERVICE_NAME,
|
||||||
|
method: error.method || "getMonitorJob",
|
||||||
|
stack: error.stack,
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async isInMaintenanceWindow(monitorId) {
|
||||||
|
const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId);
|
||||||
|
// Check for active maintenance window:
|
||||||
|
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
|
||||||
|
if (window.active) {
|
||||||
|
const start = new Date(window.start);
|
||||||
|
const end = new Date(window.end);
|
||||||
|
const now = new Date();
|
||||||
|
const repeatInterval = window.repeat || 0;
|
||||||
|
|
||||||
|
// If start is < now and end > now, we're in maintenance
|
||||||
|
if (start <= now && end >= now) return true;
|
||||||
|
|
||||||
|
// If maintenance window was set in the past with a repeat,
|
||||||
|
// we need to advance start and end to see if we are in range
|
||||||
|
|
||||||
|
while (start < now && repeatInterval !== 0) {
|
||||||
|
start.setTime(start.getTime() + repeatInterval);
|
||||||
|
end.setTime(end.getTime() + repeatInterval);
|
||||||
|
if (start <= now && end >= now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, false);
|
||||||
|
return maintenanceWindowIsActive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PulseQueueHelper;
|
||||||
@@ -125,7 +125,7 @@ class NetworkService {
|
|||||||
* @property {number} code - The response code (200 if successful, error code otherwise).
|
* @property {number} code - The response code (200 if successful, error code otherwise).
|
||||||
* @property {string} message - The message indicating the result of the HTTP request.
|
* @property {string} message - The message indicating the result of the HTTP request.
|
||||||
*/
|
*/
|
||||||
async requestHttp(job) {
|
async requestHttp(monitor) {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
@@ -138,7 +138,7 @@ class NetworkService {
|
|||||||
jsonPath,
|
jsonPath,
|
||||||
matchMethod,
|
matchMethod,
|
||||||
expectedValue,
|
expectedValue,
|
||||||
} = job.data;
|
} = monitor;
|
||||||
const config = {};
|
const config = {};
|
||||||
|
|
||||||
secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` });
|
secret !== undefined && (config.headers = { Authorization: `Bearer ${secret}` });
|
||||||
@@ -248,10 +248,10 @@ class NetworkService {
|
|||||||
* @property {number} code - The response code (200 if successful, error code otherwise).
|
* @property {number} code - The response code (200 if successful, error code otherwise).
|
||||||
* @property {string} message - The message indicating the result of the PageSpeed request.
|
* @property {string} message - The message indicating the result of the PageSpeed request.
|
||||||
*/
|
*/
|
||||||
async requestPagespeed(job) {
|
async requestPagespeed(monitor) {
|
||||||
try {
|
try {
|
||||||
const url = job.data.url;
|
const url = monitor.url;
|
||||||
const updatedJob = { ...job };
|
const updatedMonitor = { ...monitor };
|
||||||
let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
let pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
||||||
|
|
||||||
const dbSettings = await this.settingsService.getDBSettings();
|
const dbSettings = await this.settingsService.getDBSettings();
|
||||||
@@ -266,8 +266,8 @@ class NetworkService {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updatedJob.data.url = pagespeedUrl;
|
updatedMonitor.url = pagespeedUrl;
|
||||||
return await this.requestHttp(updatedJob);
|
return await this.requestHttp(updatedMonitor);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.service = this.SERVICE_NAME;
|
error.service = this.SERVICE_NAME;
|
||||||
error.method = "requestPagespeed";
|
error.method = "requestPagespeed";
|
||||||
@@ -292,9 +292,9 @@ class NetworkService {
|
|||||||
* @property {number} code - The response code (200 if successful, error code otherwise).
|
* @property {number} code - The response code (200 if successful, error code otherwise).
|
||||||
* @property {string} message - The message indicating the result of the hardware status request.
|
* @property {string} message - The message indicating the result of the hardware status request.
|
||||||
*/
|
*/
|
||||||
async requestHardware(job) {
|
async requestHardware(monitor) {
|
||||||
try {
|
try {
|
||||||
return await this.requestHttp(job);
|
return await this.requestHttp(monitor);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.service = this.SERVICE_NAME;
|
error.service = this.SERVICE_NAME;
|
||||||
error.method = "requestHardware";
|
error.method = "requestHardware";
|
||||||
@@ -318,7 +318,7 @@ class NetworkService {
|
|||||||
* @property {number} code - The response code (200 if successful, error code otherwise).
|
* @property {number} code - The response code (200 if successful, error code otherwise).
|
||||||
* @property {string} message - The message indicating the result of the Docker inspection.
|
* @property {string} message - The message indicating the result of the Docker inspection.
|
||||||
*/
|
*/
|
||||||
async requestDocker(job) {
|
async requestDocker(monitor) {
|
||||||
try {
|
try {
|
||||||
const docker = new this.Docker({
|
const docker = new this.Docker({
|
||||||
socketPath: "/var/run/docker.sock",
|
socketPath: "/var/run/docker.sock",
|
||||||
@@ -326,19 +326,19 @@ class NetworkService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const containers = await docker.listContainers({ all: true });
|
const containers = await docker.listContainers({ all: true });
|
||||||
const containerExists = containers.some((c) => c.Id.startsWith(job.data.url));
|
const containerExists = containers.some((c) => c.Id.startsWith(monitor.url));
|
||||||
if (!containerExists) {
|
if (!containerExists) {
|
||||||
throw new Error(this.stringService.dockerNotFound);
|
throw new Error(this.stringService.dockerNotFound);
|
||||||
}
|
}
|
||||||
const container = docker.getContainer(job.data.url);
|
const container = docker.getContainer(monitor.url);
|
||||||
|
|
||||||
const { response, responseTime, error } = await this.timeRequest(() =>
|
const { response, responseTime, error } = await this.timeRequest(() =>
|
||||||
container.inspect()
|
container.inspect()
|
||||||
);
|
);
|
||||||
|
|
||||||
const dockerResponse = {
|
const dockerResponse = {
|
||||||
monitorId: job.data._id,
|
monitorId: monitor._id,
|
||||||
type: job.data.type,
|
type: monitor.type,
|
||||||
responseTime,
|
responseTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -360,9 +360,9 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestPort(job) {
|
async requestPort(monitor) {
|
||||||
try {
|
try {
|
||||||
const { url, port } = job.data;
|
const { url, port } = monitor;
|
||||||
const { response, responseTime, error } = await this.timeRequest(async () => {
|
const { response, responseTime, error } = await this.timeRequest(async () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const socket = this.net.createConnection(
|
const socket = this.net.createConnection(
|
||||||
@@ -391,8 +391,8 @@ class NetworkService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const portResponse = {
|
const portResponse = {
|
||||||
monitorId: job.data._id,
|
monitorId: monitor._id,
|
||||||
type: job.data.type,
|
type: monitor.type,
|
||||||
responseTime,
|
responseTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -414,9 +414,8 @@ class NetworkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async requestDistributedHttp(job) {
|
async requestDistributedHttp(monitor) {
|
||||||
try {
|
try {
|
||||||
const monitor = job.data;
|
|
||||||
const CALLBACK_URL = process.env.CALLBACK_URL;
|
const CALLBACK_URL = process.env.CALLBACK_URL;
|
||||||
|
|
||||||
const response = await this.axios.post(
|
const response = await this.axios.post(
|
||||||
@@ -503,23 +502,23 @@ class NetworkService {
|
|||||||
* @returns {Promise<Object>} The response object from the appropriate request method.
|
* @returns {Promise<Object>} The response object from the appropriate request method.
|
||||||
* @throws {Error} Throws an error if the job type is unsupported.
|
* @throws {Error} Throws an error if the job type is unsupported.
|
||||||
*/
|
*/
|
||||||
async getStatus(job) {
|
async getStatus(monitor) {
|
||||||
const type = job?.data?.type ?? "unknown";
|
const type = monitor.type ?? "unknown";
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case this.TYPE_PING:
|
case this.TYPE_PING:
|
||||||
return await this.requestPing(job);
|
return await this.requestPing(monitor);
|
||||||
case this.TYPE_HTTP:
|
case this.TYPE_HTTP:
|
||||||
return await this.requestHttp(job);
|
return await this.requestHttp(monitor);
|
||||||
case this.TYPE_PAGESPEED:
|
case this.TYPE_PAGESPEED:
|
||||||
return await this.requestPagespeed(job);
|
return await this.requestPagespeed(monitor);
|
||||||
case this.TYPE_HARDWARE:
|
case this.TYPE_HARDWARE:
|
||||||
return await this.requestHardware(job);
|
return await this.requestHardware(monitor);
|
||||||
case this.TYPE_DOCKER:
|
case this.TYPE_DOCKER:
|
||||||
return await this.requestDocker(job);
|
return await this.requestDocker(monitor);
|
||||||
case this.TYPE_PORT:
|
case this.TYPE_PORT:
|
||||||
return await this.requestPort(job);
|
return await this.requestPort(monitor);
|
||||||
case this.TYPE_DISTRIBUTED_HTTP:
|
case this.TYPE_DISTRIBUTED_HTTP:
|
||||||
return await this.requestDistributedHttp(job);
|
return await this.requestDistributedHttp(monitor);
|
||||||
case this.TYPE_DISTRIBUTED_TEST:
|
case this.TYPE_DISTRIBUTED_TEST:
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -228,6 +228,12 @@ class StatusService {
|
|||||||
|
|
||||||
if (type === "pagespeed") {
|
if (type === "pagespeed") {
|
||||||
if (typeof payload === "undefined") {
|
if (typeof payload === "undefined") {
|
||||||
|
this.logger.warn({
|
||||||
|
message: "Failed to build check",
|
||||||
|
service: this.SERVICE_NAME,
|
||||||
|
method: "buildCheck",
|
||||||
|
details: "empty payload",
|
||||||
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const categories = payload?.lighthouseResult?.categories ?? {};
|
const categories = payload?.lighthouseResult?.categories ?? {};
|
||||||
@@ -287,9 +293,11 @@ class StatusService {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error({
|
this.logger.error({
|
||||||
message: error.message,
|
message: error.message,
|
||||||
service: this.SERVICE_NAME,
|
service: error.service || this.SERVICE_NAME,
|
||||||
method: "insertCheck",
|
method: error.method || "insertCheck",
|
||||||
details: `Error inserting check for monitor: ${networkResponse?.monitorId}`,
|
details:
|
||||||
|
error.details ||
|
||||||
|
`Error inserting check for monitor: ${networkResponse?.monitorId}`,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user