Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/email notification #143

Merged
merged 5 commits into from
May 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Models/NodeUptime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export class NodeUptime extends Model {
public isWorking: boolean;
public nodeId: number;

// Virtual property returned from query
public foundDown?: boolean;

public static initialize(sequelize: Sequelize) {
this.init({
isWorking: {
Expand Down
12 changes: 8 additions & 4 deletions src/Scheduler/Tasks/UptimeNotificationTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ export class UptimeNotificationTask implements Task {
}
}

// TODO: Should possibly optimize with excluding reported downtimes
private async findAllNotWorkingNodes(): Promise<NodeUptime[] | null> {
try {
// select, from table of latest uptime records for each node,
// entries that are older than 1 hour or are entries in last hour that reported node is down
return await database.runQuery<NodeUptime>(
`select NU.*
`select "id", "isWorking", "createdAt", "updatedAt", Nu."nodeId",
case WHEN ("isWorking" = true and "updatedAt" > now() - interval '59 minutes') then false
else true
end as "foundDown"
from (
select "nodeId", max("createdAt") as "lastUptimeReported"
from "NodeUptime"
Expand All @@ -38,12 +42,12 @@ export class UptimeNotificationTask implements Task {
left outer join "NodeUptime" NU on
NU."nodeId" = latest_uptimes."nodeId" and
NU."createdAt" = "lastUptimeReported"
where "lastUptimeReported" < now() - interval '1 hour' or
("lastUptimeReported" > now() - interval '1 hour' and NU."isWorking" = false);`,
where "lastUptimeReported" < now() - interval '59 minutes' or
("lastUptimeReported" > now() - interval '1 hour' and NU."isWorking" = false);`,
{type: QueryTypes.SELECT})
} catch (e) {
logger.error("Failed to find not working nodes in database", e)
}
return null;
}
}
}
3 changes: 2 additions & 1 deletion src/Services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export class Services {
this.emailService,
this.userService,
this.nodeService,
this.nodeStatusService
this.nodeStatusService,
this.nodeUptimeService,
);
// define scheduled tasks
this.schedulingService = new SchedulingService(
Expand Down
34 changes: 23 additions & 11 deletions src/Services/NodeUptimeNotificationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,64 @@ import {NodeService} from "./NodeService";
import {NodeStatusService} from "./NodeStatusService";
import config from "../Config/Config";
import {NodeStatus} from "../Models/NodeStatus";
import {NodeUptimeService} from "./NodeUptimeService";

export class NodeUptimeNotificationService {

private emailService: EmailService;
private userService: UserService;
private nodeService: NodeService;
private nodeStatusService: NodeStatusService;
private nodeUptimeService: NodeUptimeService;

constructor(
emailService: EmailService,
userService: UserService,
nodeService: NodeService,
nodeStatusService: NodeStatusService
nodeStatusService: NodeStatusService,
nodeUptimeService: NodeUptimeService
) {
this.emailService = emailService;
this.userService = userService;
this.nodeService = nodeService;
this.nodeStatusService = nodeStatusService
this.nodeStatusService = nodeStatusService;
this.nodeUptimeService = nodeUptimeService;
}

public async processNodeUptime(nodeUptime: NodeUptime): Promise<void> {
logger.info(`Processing node uptime for node ID ${nodeUptime.nodeId}`);
// check if node status already created for node
const currentNodeStatus = await this.nodeStatusService.getNodeStatusByNodeId(nodeUptime.nodeId);
const newNodeStatus = {nodeId: nodeUptime.nodeId, isUp: nodeUptime.isWorking, isReported: true} as NodeStatus;
const newNodeStatus = {
nodeId: nodeUptime.nodeId,
isUp: nodeUptime.foundDown ? false : nodeUptime.isWorking,
isReported: true
} as NodeStatus;
if (currentNodeStatus != null) {
logger.info(`Updating existing status to ${newNodeStatus.isUp} for node ID ${nodeUptime.nodeId}`);
await this.updateExistingNodeStatus(nodeUptime, newNodeStatus, currentNodeStatus);
} else {
logger.info(`Creating new status (${newNodeStatus.isUp}) for node ID ${nodeUptime.nodeId}`);
await this.createNewNodeStatus(nodeUptime, newNodeStatus);
}

if (nodeUptime.foundDown && (currentNodeStatus && !currentNodeStatus.isReported || !currentNodeStatus)) {
logger.info("Creating new record for node uptime with status offline.");
await this.nodeUptimeService.createNodeUptime(false, nodeUptime.nodeId);
}
}

private async createNewNodeStatus(nodeUptime: NodeUptime, newNodeStatus: NodeStatus): Promise<void> {
// create new node status entry
await this.nodeStatusService.storeNodeStatus(
nodeUptime.nodeId,
nodeUptime.isWorking,
newNodeStatus.nodeId,
newNodeStatus.isUp,
true
);

logger.info(`Stored new node status (${nodeUptime.isWorking}) for node ${nodeUptime.nodeId}.`);
logger.info(`Stored new node status (${newNodeStatus.isUp}) for node ${newNodeStatus.nodeId}.`);
// send notification if node is down
if (!newNodeStatus.isUp) {
logger.info(`Sending uptime notification for node ID ${nodeUptime.nodeId}`);
logger.info(`Sending uptime notification for node ID ${newNodeStatus.nodeId}`);
await this.sendUptimeNotification(nodeUptime);
}
}
Expand All @@ -62,7 +74,7 @@ export class NodeUptimeNotificationService {
oldNodeStatus: NodeStatus,
): Promise<void> {
// if reported node is down
if (!nodeUptime.isWorking) {
if (!newNodeStatus.isUp) {
logger.info(`Node ID ${nodeUptime.nodeId} is down.`);
// send email notification if current node was up until now or was down but report wasn't sent
if (oldNodeStatus.isUp) {
Expand All @@ -78,7 +90,7 @@ export class NodeUptimeNotificationService {
if (oldNodeStatus.isReported != newNodeStatus.isReported
|| oldNodeStatus.isUp != newNodeStatus.isUp) {
logger.info(`Found node ID ${nodeUptime.nodeId} that has old status` +
`[isReported: ${oldNodeStatus.isReported}, isUp: ${oldNodeStatus.isNewRecord}].`);
`[isReported: ${oldNodeStatus.isReported}, isUp: ${oldNodeStatus.isUp}].`);

await this.nodeStatusService.updateNodeStatus(
nodeUptime.nodeId,
Expand Down Expand Up @@ -118,4 +130,4 @@ export class NodeUptimeNotificationService {
logger.error(`Failed to find user:${node.userId} for uptime entry: ${uptime.id}`);
}
}
}
}
7 changes: 6 additions & 1 deletion test/unit/Services/NodeUptimeNotificationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as chai from "chai";
// eslint-disable-next-line @typescript-eslint/no-require-imports
import sinonChai = require('sinon-chai');
import {NodeStatus} from "../../../src/Models/NodeStatus";
import {NodeUptimeService} from "../../../src/Services/NodeUptimeService";

chai.should();
chai.use(sinonChai);
Expand Down Expand Up @@ -59,6 +60,9 @@ describe("NodeUptimeNotificationService", function () {
getNodeByPkStub = sinon.stub(nodeService, "getNodeByPk");
getNodeByPkStub.returns(testNode);

const nodeUptimeService = new NodeUptimeService();
sinon.stub(nodeUptimeService, "createNodeUptime").resolves();

nodeStatusService = new NodeStatusService();
updateNodeStatusStub = sinon.stub(nodeStatusService, "updateNodeStatus");
createNodeStatusStub = sinon.stub(nodeStatusService, "storeNodeStatus");
Expand All @@ -68,7 +72,8 @@ describe("NodeUptimeNotificationService", function () {
emailService,
userService,
nodeService,
nodeStatusService
nodeStatusService,
nodeUptimeService,
)
});

Expand Down