n8n
Version:
n8n Workflow Automation Tool
248 lines • 9.63 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CheckService = void 0;
exports.computeDiff = computeDiff;
const backend_common_1 = require("@n8n/backend-common");
const decorators_1 = require("@n8n/decorators");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const message_event_bus_1 = require("../../../eventbus/message-event-bus/message-event-bus");
const push_1 = require("../../../push");
const instance_registry_service_1 = require("../instance-registry.service");
const instance_registry_types_1 = require("../instance-registry.types");
let CheckService = class CheckService {
constructor(logger, instanceSettings, instanceRegistryService, clusterCheckMetadata, messageEventBus, push) {
this.instanceSettings = instanceSettings;
this.instanceRegistryService = instanceRegistryService;
this.clusterCheckMetadata = clusterCheckMetadata;
this.messageEventBus = messageEventBus;
this.push = push;
this.isShuttingDown = false;
this.checks = [];
this.logger = logger.scoped('instance-registry');
}
init() {
this.discoverChecks();
if (this.instanceSettings.isLeader)
this.startReconciliation();
}
startReconciliation() {
if (this.isShuttingDown || this.reconcileController)
return;
this.reconcileController = new AbortController();
const { signal } = this.reconcileController;
void this.runReconcileSafely(signal);
this.scheduleNextReconcile(signal);
this.logger.debug('Cluster check reconciliation scheduled');
}
stopReconciliation() {
this.reconcileController?.abort();
this.reconcileController = undefined;
clearTimeout(this.reconcileTimer);
this.reconcileTimer = undefined;
}
shutdown() {
this.isShuttingDown = true;
this.stopReconciliation();
}
scheduleNextReconcile(signal) {
if (signal.aborted)
return;
this.reconcileTimer = setTimeout(async () => {
await this.runReconcileSafely(signal);
this.scheduleNextReconcile(signal);
}, instance_registry_types_1.REGISTRY_CONSTANTS.RECONCILIATION_INTERVAL_MS);
}
discoverChecks() {
const checkClasses = this.clusterCheckMetadata.getClasses();
for (const CheckClass of checkClasses) {
try {
const check = di_1.Container.get(CheckClass);
this.checks.push(check);
}
catch (error) {
this.logger.error(`Failed to instantiate cluster check "${CheckClass.name}"`, {
error,
});
}
}
this.logger.info(`Discovered ${this.checks.length} cluster checks`, {
names: this.checks.map((c) => c.checkDescription.name),
});
}
async runReconcileSafely(signal) {
try {
await this.reconcile(signal);
}
catch (error) {
this.logger.warn('Reconciliation cycle failed', { error });
}
}
async runChecks() {
if (this.checks.length === 0) {
return { currentState: new Map(), results: [] };
}
const instances = await this.instanceRegistryService.getAllInstances();
const currentState = new Map(instances.map((i) => [i.instanceKey, i]));
const previousState = await this.instanceRegistryService.getLastKnownState();
const diff = computeDiff(previousState, currentState);
const context = { currentState, previousState, diff };
const settled = await Promise.allSettled(this.checks.map(async (check) => await check.run(context)));
const results = [];
for (let i = 0; i < settled.length; i++) {
const outcome = settled[i];
const check = this.checks[i];
const checkResult = {
checkName: check.checkDescription.name,
checkDisplayName: check.checkDescription.displayName,
};
if (outcome.status === 'fulfilled') {
checkResult.result = outcome.value;
}
else {
this.logger.error('Cluster check failed', {
...checkResult,
error: outcome.reason,
});
checkResult.failed = true;
}
results.push(checkResult);
}
return { currentState, results };
}
async reconcile(signal) {
if (this.checks.length === 0)
return;
if (signal.aborted)
return;
const { currentState, results } = await this.runChecks();
if (signal.aborted)
return;
for (const { checkName, result } of results) {
this.processResult(checkName, result);
}
try {
if (signal.aborted)
return;
await this.instanceRegistryService.saveLastKnownState(currentState);
}
catch (error) {
this.logger.warn('Failed to persist last known cluster state', { error });
}
}
processResult(checkName, result) {
for (const warning of result?.warnings ?? []) {
this.logWarning(checkName, warning);
}
for (const event of result?.auditEvents ?? []) {
this.emitAuditEvent(checkName, event);
}
for (const notification of result?.pushNotifications ?? []) {
this.broadcastPush(checkName, notification);
}
}
logWarning(checkName, warning) {
const severity = warning.severity ?? 'warning';
const method = severity === 'info' ? 'info' : severity === 'error' ? 'error' : 'warn';
this.logger[method]('Cluster check warning', {
check: checkName,
code: warning.code,
message: warning.message,
context: warning.context,
});
}
emitAuditEvent(checkName, event) {
void this.messageEventBus
.sendAuditEvent({
eventName: event.eventName,
payload: event.payload,
})
.catch((error) => {
this.logger.warn('Failed to emit cluster check audit event', {
check: checkName,
eventName: event.eventName,
error,
});
});
}
broadcastPush(checkName, notification) {
try {
this.push.broadcast({
type: notification.type,
data: notification.data,
});
}
catch (error) {
this.logger.warn('Failed to broadcast cluster check push notification', {
check: checkName,
type: notification.type,
error,
});
}
}
};
exports.CheckService = CheckService;
__decorate([
(0, decorators_1.OnLeaderTakeover)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], CheckService.prototype, "startReconciliation", null);
__decorate([
(0, decorators_1.OnLeaderStepdown)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], CheckService.prototype, "stopReconciliation", null);
__decorate([
(0, decorators_1.OnShutdown)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], CheckService.prototype, "shutdown", null);
exports.CheckService = CheckService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger,
n8n_core_1.InstanceSettings,
instance_registry_service_1.InstanceRegistryService,
decorators_1.ClusterCheckMetadata,
message_event_bus_1.MessageEventBus,
push_1.Push])
], CheckService);
function computeDiff(previousState, currentState) {
const added = [];
const removed = [];
const changed = [];
for (const [key, current] of currentState) {
const previous = previousState.get(key);
if (!previous) {
added.push(current);
}
else if (!isEquivalent(previous, current)) {
changed.push({ previous, current });
}
}
for (const [key, previous] of previousState) {
if (!currentState.has(key))
removed.push(previous);
}
return { added, removed, changed };
}
function isEquivalent(a, b) {
return (a.schemaVersion === b.schemaVersion &&
a.instanceKey === b.instanceKey &&
a.hostId === b.hostId &&
a.instanceType === b.instanceType &&
a.instanceRole === b.instanceRole &&
a.version === b.version);
}
//# sourceMappingURL=check.service.js.map