Skip to content
Open
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
46 changes: 37 additions & 9 deletions packages/daemons/src/hostReboot/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import { logs } from "@dappnode/logger";
import { runAtMostEvery } from "@dappnode/utils";
import { notifications } from "@dappnode/notifications";
import { Category, Priority, Status } from "@dappnode/types";
import { Category, Notification, Priority, Status } from "@dappnode/types";
import { getRebootRequiredMemoized } from "@dappnode/hostscriptsservices";
import { params } from "@dappnode/params";

const CHECK_INTERVAL = 7 * 24 * 60 * 60 * 1000; // 7 days
const BANNER_LOOKBACK_SECONDS = 30 * 24 * 60 * 60; // 30 days
const CORRELATION_ID = "core-reboot-required";

let notificationSent = false;

export function getLatestNotificationByCorrelationId(
notificationList: Notification[],
correlationId: string
): Notification | undefined {
return notificationList
.filter((notification) => notification.correlationId === correlationId && !notification.errors)
.sort((a, b) => b.timestamp - a.timestamp)[0];
}

async function getLatestRebootBannerNotification(): Promise<Notification | undefined> {
try {
const { isNotifierRunning } = await notifications.notificationsPackageStatus();
if (!isNotifierRunning) return undefined;

const timestamp = Math.floor(Date.now() / 1000) - BANNER_LOOKBACK_SECONDS;
const bannerNotifications = await notifications.getBannerNotifications(timestamp);
return getLatestNotificationByCorrelationId(bannerNotifications, CORRELATION_ID);
} catch (e) {
logs.warn("Error getting previous host reboot banner notification", e);
return undefined;
}
}

/**
* Monitors if the host requires a reboot.
* Sends a notification if a reboot is required.
Expand All @@ -17,12 +42,15 @@ let notificationSent = false;
async function monitorHostReboot(): Promise<void> {
try {
const rebootRequired = await getRebootRequiredMemoized();
const correlationId = "core-reboot-required";
const latestNotification = await getLatestRebootBannerNotification();
const isRebootNotificationActive = latestNotification
? latestNotification.status === Status.triggered
: notificationSent;

if (rebootRequired?.rebootRequired) {
logs.warn("Host reboot is required");

if (!notificationSent) {
if (!isRebootNotificationActive) {
await notifications
.sendNotification({
title: "DAppNode host reboot required",
Expand All @@ -37,13 +65,13 @@ async function monitorHostReboot(): Promise<void> {
status: Status.triggered,
isBanner: true,
isRemote: false,
correlationId
correlationId: CORRELATION_ID
})
.catch((e) => logs.error("Error sending host reboot notification", e));
notificationSent = true;
}
notificationSent = true;
} else {
if (notificationSent) {
if (isRebootNotificationActive) {
logs.info("Host reboot no longer required, sending resolve notification");

await notifications
Expand All @@ -54,13 +82,13 @@ async function monitorHostReboot(): Promise<void> {
category: Category.system,
priority: Priority.low,
status: Status.resolved,
isBanner: false,
isBanner: true,
isRemote: false,
correlationId
correlationId: CORRELATION_ID
})
.catch((e) => logs.error("Error sending host reboot resolve notification", e));
notificationSent = false;
}
notificationSent = false;
}
} catch (e) {
logs.error("Error monitoring host reboot requirement", e);
Expand Down
2 changes: 1 addition & 1 deletion packages/daemons/src/internetConnection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async function monitorInternetConnection(): Promise<void> {
category: Category.system,
priority: Priority.critical,
status: Status.resolved,
isBanner: false,
isBanner: true,
isRemote: false,
correlationId
})
Expand Down
4 changes: 2 additions & 2 deletions packages/daemons/src/repositoryHealth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function checkIpfsHealth(): Promise<void> {
category: Category.system,
priority: Priority.high,
status: Status.resolved,
isBanner: false,
isBanner: true,
isRemote: false,
correlationId
});
Expand Down Expand Up @@ -121,7 +121,7 @@ Syncing and access to Ethereum chain data should now resume normally.`,
category: Category.system,
priority: Priority.high,
status: Status.resolved,
isBanner: false,
isBanner: true,
isRemote: false,
correlationId
});
Expand Down
68 changes: 68 additions & 0 deletions packages/daemons/test/unit/hostReboot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import "mocha";
import { expect } from "chai";
import { Category, Notification, Priority, Status } from "@dappnode/types";
import { getLatestNotificationByCorrelationId } from "../../src/hostReboot/index.js";

function notification({
id,
correlationId,
timestamp,
status = Status.triggered,
errors
}: {
id: number;
correlationId: string;
timestamp: number;
status?: Status;
errors?: string;
}): Notification {
return {
id,
timestamp,
title: "Notification",
body: "Body",
dnpName: "core.dnp.dappnode.eth",
category: Category.system,
priority: Priority.low,
status,
isBanner: true,
isRemote: false,
seen: false,
correlationId,
errors
};
}

describe("daemons > hostReboot", () => {
describe("getLatestNotificationByCorrelationId", () => {
it("returns the newest notification for the correlation id", () => {
const result = getLatestNotificationByCorrelationId(
[
notification({ id: 1, correlationId: "core-reboot-required", timestamp: 100 }),
notification({ id: 2, correlationId: "other", timestamp: 300 }),
notification({
id: 3,
correlationId: "core-reboot-required",
timestamp: 200,
status: Status.resolved
})
],
"core-reboot-required"
);

expect(result?.id).to.equal(3);
});

it("ignores notifications with errors", () => {
const result = getLatestNotificationByCorrelationId(
[
notification({ id: 1, correlationId: "core-reboot-required", timestamp: 100 }),
notification({ id: 2, correlationId: "core-reboot-required", timestamp: 200, errors: "failed" })
],
"core-reboot-required"
);

expect(result?.id).to.equal(1);
});
});
});
Loading