diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js index d1f0ef5a7..6d718e9cd 100644 --- a/Server/db/models/Monitor.js +++ b/Server/db/models/Monitor.js @@ -29,7 +29,7 @@ const MonitorSchema = mongoose.Schema( type: { type: String, required: true, - enum: ["http", "ping", "pagespeed"], + enum: ["http", "ping", "pagespeed", "hardware"], }, url: { type: String, diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 61e60e836..9031a741f 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -99,6 +99,11 @@ import { deletePageSpeedChecksByMonitorId, } from "./modules/pageSpeedCheckModule.js"; +//**************************************** +// Hardware Checks +//**************************************** +import { createHardwareCheck } from "./modules/hardwareCheckModule.js"; + //**************************************** // Checks //**************************************** @@ -179,6 +184,7 @@ export default { createPageSpeedCheck, getPageSpeedChecks, deletePageSpeedChecksByMonitorId, + createHardwareCheck, createMaintenanceWindow, getMaintenanceWindowsByTeamId, getMaintenanceWindowById, diff --git a/Server/db/mongo/modules/hardwareCheckModule.js b/Server/db/mongo/modules/hardwareCheckModule.js new file mode 100644 index 000000000..de46828e5 --- /dev/null +++ b/Server/db/mongo/modules/hardwareCheckModule.js @@ -0,0 +1,16 @@ +import HardwareCheck from "../../models/HardwareCheck.js"; +const SERVICE_NAME = "hardwareCheckModule"; +const createHardwareCheck = async (hardwareCheckData) => { + try { + const hardwareCheck = await new HardwareCheck({ + ...hardwareCheckData, + }).save(); + return hardwareCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createHardwareCheck"; + throw error; + } +}; + +export { createHardwareCheck }; diff --git a/Server/service/networkService.js b/Server/service/networkService.js index ad372dbce..e90180835 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -24,6 +24,7 @@ class NetworkService { this.TYPE_PING = "ping"; this.TYPE_HTTP = "http"; this.TYPE_PAGESPEED = "pagespeed"; + this.TYPE_HARDWARE = "hardware"; this.SERVICE_NAME = "NetworkService"; this.NETWORK_ERROR = 5000; this.axios = axios; @@ -293,6 +294,85 @@ class NetworkService { } } + async handleHardware(job) { + const url = job.data.url; + let isAlive; + //TODO Fetch hardware data + //For now, fake hardware data: + + const hardwareData = { + monitorId: job.data._id, + cpu: { + physical_core: 1, + logical_core: 1, + frequency: 266, + temperature: null, + free_percent: null, + usage_percent: null, + }, + memory: { + total_bytes: 4, + available_bytes: 4, + used_bytes: 2, + usage_percent: 0.5, + }, + disk: [ + { + read_speed_bytes: 3, + write_speed_bytes: 3, + total_bytes: 10, + free_bytes: 2, + usage_percent: 0.8, + }, + ], + host: { + os: "Linux", + platform: "Ubuntu", + kernel_version: "24.04", + }, + }; + try { + isAlive = true; + this.logAndStoreCheck(hardwareData, this.db.createHardwareCheck); + } catch (error) { + isAlive = false; + const nullData = { + monitorId: job.data._id, + cpu: { + physical_core: 0, + logical_core: 0, + frequency: 0, + temperature: 0, + free_percent: 0, + usage_percent: 0, + }, + memory: { + total_bytes: 0, + available_bytes: 0, + used_bytes: 0, + usage_percent: 0, + }, + disk: [ + { + read_speed_bytes: 0, + write_speed_bytes: 0, + total_bytes: 0, + free_bytes: 0, + usage_percent: 0, + }, + ], + host: { + os: "", + platform: "", + kernel_version: "", + }, + }; + this.logAndStoreCheck(nullData, this.db.createHardwareCheck); + } finally { + this.handleStatusUpdate(job, isAlive); + } + } + /** * Retrieves the status of a given job based on its type. * For unsupported job types, it logs an error and returns false. @@ -308,6 +388,8 @@ class NetworkService { return await this.handleHttp(job); case this.TYPE_PAGESPEED: return await this.handlePagespeed(job); + case this.TYPE_HARDWARE: + return await this.handleHardware(job); default: this.logger.error(`Unsupported type: ${job.data.type}`, { service: this.SERVICE_NAME, diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js index 619fa0fc9..5d3e81a22 100644 --- a/Server/tests/services/networkService.test.js +++ b/Server/tests/services/networkService.test.js @@ -625,6 +625,105 @@ describe("networkService - handlePagespeed", () => { }); }); +describe("networkService - handleHardware", () => { + let dbMock, + axiosMock, + jobMock, + emailServiceMock, + pingMock, + loggerMock, + httpMock, + networkService, + logAndStoreCheckStub, + handleStatusUpdateStub; + beforeEach(() => { + jobMock = { + data: { + _id: "12345", + url: "http://example.com", + }, + }; + dbMock = { getMonitorById: sinon.stub() }; + axiosMock = { get: sinon.stub() }; + + emailServiceMock = sinon.stub(); + pingMock = { promise: { probe: sinon.stub() } }; + loggerMock = { error: sinon.stub() }; + httpMock = { + STATUS_CODES: { + 200: "OK", + 500: "Internal Server Error", + }, + }; + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves(); + handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should handle a successful Hardware response", async () => { + const responseMock = { + monitorId: jobMock.data._id, + cpu: { + physical_core: 1, + logical_core: 1, + frequency: 266, + temperature: null, + free_percent: null, + usage_percent: null, + }, + memory: { + total_bytes: 4, + available_bytes: 4, + used_bytes: 2, + usage_percent: 0.5, + }, + disk: [ + { + read_speed_bytes: 3, + write_speed_bytes: 3, + total_bytes: 10, + free_bytes: 2, + usage_percent: 0.8, + }, + ], + host: { + os: "Linux", + platform: "Ubuntu", + kernel_version: "24.04", + }, + }; + axiosMock.get.resolves(responseMock); + + await networkService.handleHardware(jobMock); + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const hardwareData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(hardwareData.cpu).to.include({ + ...responseMock.cpu, + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true; + }); + + it("should handle an error Hardware response", async () => { + logAndStoreCheckStub.throws(new Error("Hardware error")); + try { + await networkService.handleHardware(jobMock); + } catch (error) { + expect(error.message).to.equal("Hardware error"); + } + }); +}); + describe("NetworkService - getStatus", () => { let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock, networkService; @@ -685,6 +784,18 @@ describe("NetworkService - getStatus", () => { const result = await networkService.getStatus(job); expect(result).to.be.false; }); + it("should return true if the job type is hardware and handleHardware is successful", async () => { + const job = { data: { type: networkService.TYPE_HARDWARE } }; + sinon.stub(networkService, "handleHardware").resolves(true); + const result = await networkService.getStatus(job); + expect(result).to.be.true; + }); + it("should return false if the job type is hardware and handleHardware is not successful", async () => { + const job = { data: { type: networkService.TYPE_HARDWARE } }; + sinon.stub(networkService, "handleHardware").resolves(false); + const result = await networkService.getStatus(job); + expect(result).to.be.false; + }); it("should log an error and return false if the job type is unknown", async () => { const job = { data: { type: "unknown" } }; const result = await networkService.getStatus(job);