mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(core): Simplify worker pubsub message handler (#11086)
This commit is contained in:
parent
2343634c64
commit
383b4765d2
|
@ -112,10 +112,9 @@ export class Webhook extends BaseCommand {
|
||||||
async initOrchestration() {
|
async initOrchestration() {
|
||||||
await Container.get(OrchestrationWebhookService).init();
|
await Container.get(OrchestrationWebhookService).init();
|
||||||
|
|
||||||
|
Container.get(PubSubHandler).init();
|
||||||
const subscriber = Container.get(Subscriber);
|
const subscriber = Container.get(Subscriber);
|
||||||
await subscriber.subscribe('n8n.commands');
|
await subscriber.subscribe('n8n.commands');
|
||||||
subscriber.setCommandMessageHandler();
|
subscriber.setCommandMessageHandler();
|
||||||
|
|
||||||
Container.get(PubSubHandler).init();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ import { EventMessageGeneric } from '@/eventbus/event-message-classes/event-mess
|
||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||||
import { LogStreamingEventRelay } from '@/events/relays/log-streaming.event-relay';
|
import { LogStreamingEventRelay } from '@/events/relays/log-streaming.event-relay';
|
||||||
import { JobProcessor } from '@/scaling/job-processor';
|
import { JobProcessor } from '@/scaling/job-processor';
|
||||||
import { Publisher } from '@/scaling/pubsub/publisher.service';
|
import { PubSubHandler } from '@/scaling/pubsub/pubsub-handler';
|
||||||
|
import { Subscriber } from '@/scaling/pubsub/subscriber.service';
|
||||||
import type { ScalingService } from '@/scaling/scaling.service';
|
import type { ScalingService } from '@/scaling/scaling.service';
|
||||||
import type { WorkerServerEndpointsConfig } from '@/scaling/worker-server';
|
import type { WorkerServerEndpointsConfig } from '@/scaling/worker-server';
|
||||||
import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service';
|
|
||||||
import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service';
|
import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service';
|
||||||
|
|
||||||
import { BaseCommand } from './base-command';
|
import { BaseCommand } from './base-command';
|
||||||
|
@ -128,12 +128,11 @@ export class Worker extends BaseCommand {
|
||||||
*/
|
*/
|
||||||
async initOrchestration() {
|
async initOrchestration() {
|
||||||
await Container.get(OrchestrationWorkerService).init();
|
await Container.get(OrchestrationWorkerService).init();
|
||||||
await Container.get(OrchestrationHandlerWorkerService).initWithOptions({
|
|
||||||
queueModeId: this.queueModeId,
|
Container.get(PubSubHandler).init();
|
||||||
publisher: Container.get(Publisher),
|
const subscriber = Container.get(Subscriber);
|
||||||
getRunningJobIds: () => this.jobProcessor.getRunningJobIds(),
|
await subscriber.subscribe('n8n.commands');
|
||||||
getRunningJobsSummary: () => this.jobProcessor.getRunningJobsSummary(),
|
subscriber.setCommandMessageHandler();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setConcurrency() {
|
async setConcurrency() {
|
||||||
|
|
|
@ -7,7 +7,9 @@ import type { ExternalSecretsManager } from '@/external-secrets/external-secrets
|
||||||
import type { License } from '@/license';
|
import type { License } from '@/license';
|
||||||
import type { CommunityPackagesService } from '@/services/community-packages.service';
|
import type { CommunityPackagesService } from '@/services/community-packages.service';
|
||||||
|
|
||||||
|
import type { Publisher } from '../pubsub/publisher.service';
|
||||||
import { PubSubHandler } from '../pubsub/pubsub-handler';
|
import { PubSubHandler } from '../pubsub/pubsub-handler';
|
||||||
|
import type { WorkerStatus } from '../worker-status';
|
||||||
|
|
||||||
describe('PubSubHandler', () => {
|
describe('PubSubHandler', () => {
|
||||||
const eventService = new EventService();
|
const eventService = new EventService();
|
||||||
|
@ -15,13 +17,19 @@ describe('PubSubHandler', () => {
|
||||||
const eventbus = mock<MessageEventBus>();
|
const eventbus = mock<MessageEventBus>();
|
||||||
const externalSecretsManager = mock<ExternalSecretsManager>();
|
const externalSecretsManager = mock<ExternalSecretsManager>();
|
||||||
const communityPackagesService = mock<CommunityPackagesService>();
|
const communityPackagesService = mock<CommunityPackagesService>();
|
||||||
|
const publisher = mock<Publisher>();
|
||||||
|
const workerStatus = mock<WorkerStatus>();
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
eventService.removeAllListeners();
|
||||||
|
});
|
||||||
|
|
||||||
describe('in webhook process', () => {
|
describe('in webhook process', () => {
|
||||||
const instanceSettings = mock<InstanceSettings>({ instanceType: 'webhook' });
|
const instanceSettings = mock<InstanceSettings>({ instanceType: 'webhook' });
|
||||||
|
|
||||||
it('should set up handlers in webhook process', () => {
|
it('should set up handlers in webhook process', () => {
|
||||||
// @ts-expect-error Spying on private method
|
// @ts-expect-error Spying on private method
|
||||||
const setupWebhookHandlersSpy = jest.spyOn(PubSubHandler.prototype, 'setupWebhookHandlers');
|
const setupHandlersSpy = jest.spyOn(PubSubHandler.prototype, 'setupHandlers');
|
||||||
|
|
||||||
new PubSubHandler(
|
new PubSubHandler(
|
||||||
eventService,
|
eventService,
|
||||||
|
@ -30,9 +38,18 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
expect(setupWebhookHandlersSpy).toHaveBeenCalled();
|
expect(setupHandlersSpy).toHaveBeenCalledWith({
|
||||||
|
'reload-license': expect.any(Function),
|
||||||
|
'restart-event-bus': expect.any(Function),
|
||||||
|
'reload-external-secrets-providers': expect.any(Function),
|
||||||
|
'community-package-install': expect.any(Function),
|
||||||
|
'community-package-update': expect.any(Function),
|
||||||
|
'community-package-uninstall': expect.any(Function),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reload license on `reload-license` event', () => {
|
it('should reload license on `reload-license` event', () => {
|
||||||
|
@ -43,6 +60,8 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
eventService.emit('reload-license');
|
eventService.emit('reload-license');
|
||||||
|
@ -58,6 +77,8 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
eventService.emit('restart-event-bus');
|
eventService.emit('restart-event-bus');
|
||||||
|
@ -73,6 +94,8 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
eventService.emit('reload-external-secrets-providers');
|
eventService.emit('reload-external-secrets-providers');
|
||||||
|
@ -88,6 +111,8 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
eventService.emit('community-package-install', {
|
eventService.emit('community-package-install', {
|
||||||
|
@ -109,6 +134,8 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
eventService.emit('community-package-update', {
|
eventService.emit('community-package-update', {
|
||||||
|
@ -130,6 +157,8 @@ describe('PubSubHandler', () => {
|
||||||
eventbus,
|
eventbus,
|
||||||
externalSecretsManager,
|
externalSecretsManager,
|
||||||
communityPackagesService,
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
).init();
|
).init();
|
||||||
|
|
||||||
eventService.emit('community-package-uninstall', {
|
eventService.emit('community-package-uninstall', {
|
||||||
|
@ -139,4 +168,123 @@ describe('PubSubHandler', () => {
|
||||||
expect(communityPackagesService.removeNpmPackage).toHaveBeenCalledWith('test-package');
|
expect(communityPackagesService.removeNpmPackage).toHaveBeenCalledWith('test-package');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('in worker process', () => {
|
||||||
|
const instanceSettings = mock<InstanceSettings>({ instanceType: 'worker' });
|
||||||
|
|
||||||
|
it('should set up handlers in worker process', () => {
|
||||||
|
// @ts-expect-error Spying on private method
|
||||||
|
const setupHandlersSpy = jest.spyOn(PubSubHandler.prototype, 'setupHandlers');
|
||||||
|
|
||||||
|
new PubSubHandler(
|
||||||
|
eventService,
|
||||||
|
instanceSettings,
|
||||||
|
license,
|
||||||
|
eventbus,
|
||||||
|
externalSecretsManager,
|
||||||
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
|
).init();
|
||||||
|
|
||||||
|
expect(setupHandlersSpy).toHaveBeenCalledWith({
|
||||||
|
'reload-license': expect.any(Function),
|
||||||
|
'restart-event-bus': expect.any(Function),
|
||||||
|
'reload-external-secrets-providers': expect.any(Function),
|
||||||
|
'community-package-install': expect.any(Function),
|
||||||
|
'community-package-update': expect.any(Function),
|
||||||
|
'community-package-uninstall': expect.any(Function),
|
||||||
|
'get-worker-status': expect.any(Function),
|
||||||
|
'get-worker-id': expect.any(Function),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reload license on `reload-license` event', () => {
|
||||||
|
new PubSubHandler(
|
||||||
|
eventService,
|
||||||
|
instanceSettings,
|
||||||
|
license,
|
||||||
|
eventbus,
|
||||||
|
externalSecretsManager,
|
||||||
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
|
).init();
|
||||||
|
|
||||||
|
eventService.emit('reload-license');
|
||||||
|
|
||||||
|
expect(license.reload).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should restart event bus on `restart-event-bus` event', () => {
|
||||||
|
new PubSubHandler(
|
||||||
|
eventService,
|
||||||
|
instanceSettings,
|
||||||
|
license,
|
||||||
|
eventbus,
|
||||||
|
externalSecretsManager,
|
||||||
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
|
).init();
|
||||||
|
|
||||||
|
eventService.emit('restart-event-bus');
|
||||||
|
|
||||||
|
expect(eventbus.restart).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reload providers on `reload-external-secrets-providers` event', () => {
|
||||||
|
new PubSubHandler(
|
||||||
|
eventService,
|
||||||
|
instanceSettings,
|
||||||
|
license,
|
||||||
|
eventbus,
|
||||||
|
externalSecretsManager,
|
||||||
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
|
).init();
|
||||||
|
|
||||||
|
eventService.emit('reload-external-secrets-providers');
|
||||||
|
|
||||||
|
expect(externalSecretsManager.reloadAllProviders).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate status on `get-worker-status` event', () => {
|
||||||
|
new PubSubHandler(
|
||||||
|
eventService,
|
||||||
|
instanceSettings,
|
||||||
|
license,
|
||||||
|
eventbus,
|
||||||
|
externalSecretsManager,
|
||||||
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
|
).init();
|
||||||
|
|
||||||
|
eventService.emit('get-worker-status');
|
||||||
|
|
||||||
|
expect(workerStatus.generateStatus).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get worker ID on `get-worker-id` event', () => {
|
||||||
|
new PubSubHandler(
|
||||||
|
eventService,
|
||||||
|
instanceSettings,
|
||||||
|
license,
|
||||||
|
eventbus,
|
||||||
|
externalSecretsManager,
|
||||||
|
communityPackagesService,
|
||||||
|
publisher,
|
||||||
|
workerStatus,
|
||||||
|
).init();
|
||||||
|
|
||||||
|
eventService.emit('get-worker-id');
|
||||||
|
|
||||||
|
expect(publisher.publishWorkerResponse).toHaveBeenCalledWith({
|
||||||
|
workerId: expect.any(String),
|
||||||
|
command: 'get-worker-id',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import { InstanceSettings } from 'n8n-core';
|
import { InstanceSettings } from 'n8n-core';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
import config from '@/config';
|
||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
||||||
import { EventService } from '@/events/event.service';
|
import { EventService } from '@/events/event.service';
|
||||||
import type { PubSubEventMap } from '@/events/maps/pub-sub.event-map';
|
import type { PubSubEventMap } from '@/events/maps/pub-sub.event-map';
|
||||||
import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee';
|
import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee';
|
||||||
import { License } from '@/license';
|
import { License } from '@/license';
|
||||||
|
import { Publisher } from '@/scaling/pubsub/publisher.service';
|
||||||
import { CommunityPackagesService } from '@/services/community-packages.service';
|
import { CommunityPackagesService } from '@/services/community-packages.service';
|
||||||
|
import { assertNever } from '@/utils';
|
||||||
|
|
||||||
|
import { WorkerStatus } from '../worker-status';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for handling events emitted from messages received via a pubsub channel.
|
* Responsible for handling events emitted from messages received via a pubsub channel.
|
||||||
|
@ -20,10 +25,37 @@ export class PubSubHandler {
|
||||||
private readonly eventbus: MessageEventBus,
|
private readonly eventbus: MessageEventBus,
|
||||||
private readonly externalSecretsManager: ExternalSecretsManager,
|
private readonly externalSecretsManager: ExternalSecretsManager,
|
||||||
private readonly communityPackagesService: CommunityPackagesService,
|
private readonly communityPackagesService: CommunityPackagesService,
|
||||||
|
private readonly publisher: Publisher,
|
||||||
|
private readonly workerStatus: WorkerStatus,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (this.instanceSettings.instanceType === 'webhook') this.setupWebhookHandlers();
|
switch (this.instanceSettings.instanceType) {
|
||||||
|
case 'webhook':
|
||||||
|
this.setupHandlers(this.commonHandlers);
|
||||||
|
break;
|
||||||
|
case 'worker':
|
||||||
|
this.setupHandlers({
|
||||||
|
...this.commonHandlers,
|
||||||
|
'get-worker-status': async () =>
|
||||||
|
await this.publisher.publishWorkerResponse({
|
||||||
|
workerId: config.getEnv('redis.queueModeId'),
|
||||||
|
command: 'get-worker-status',
|
||||||
|
payload: this.workerStatus.generateStatus(),
|
||||||
|
}),
|
||||||
|
'get-worker-id': async () =>
|
||||||
|
await this.publisher.publishWorkerResponse({
|
||||||
|
workerId: config.getEnv('redis.queueModeId'),
|
||||||
|
command: 'get-worker-id',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'main':
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assertNever(this.instanceSettings.instanceType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupHandlers<EventNames extends keyof PubSubEventMap>(
|
private setupHandlers<EventNames extends keyof PubSubEventMap>(
|
||||||
|
@ -40,22 +72,27 @@ export class PubSubHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region Webhook process
|
/** Handlers shared by webhook and worker processes. */
|
||||||
|
private commonHandlers: {
|
||||||
private setupWebhookHandlers() {
|
[K in keyof Pick<
|
||||||
this.setupHandlers({
|
PubSubEventMap,
|
||||||
'reload-license': async () => await this.license.reload(),
|
| 'reload-license'
|
||||||
'restart-event-bus': async () => await this.eventbus.restart(),
|
| 'restart-event-bus'
|
||||||
'reload-external-secrets-providers': async () =>
|
| 'reload-external-secrets-providers'
|
||||||
await this.externalSecretsManager.reloadAllProviders(),
|
| 'community-package-install'
|
||||||
'community-package-install': async ({ packageName, packageVersion }) =>
|
| 'community-package-update'
|
||||||
await this.communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion),
|
| 'community-package-uninstall'
|
||||||
'community-package-update': async ({ packageName, packageVersion }) =>
|
>]: (event: PubSubEventMap[K]) => Promise<void>;
|
||||||
await this.communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion),
|
} = {
|
||||||
'community-package-uninstall': async ({ packageName }) =>
|
'reload-license': async () => await this.license.reload(),
|
||||||
await this.communityPackagesService.removeNpmPackage(packageName),
|
'restart-event-bus': async () => await this.eventbus.restart(),
|
||||||
});
|
'reload-external-secrets-providers': async () =>
|
||||||
}
|
await this.externalSecretsManager.reloadAllProviders(),
|
||||||
|
'community-package-install': async ({ packageName, packageVersion }) =>
|
||||||
// #endregion
|
await this.communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion),
|
||||||
|
'community-package-update': async ({ packageName, packageVersion }) =>
|
||||||
|
await this.communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion),
|
||||||
|
'community-package-uninstall': async ({ packageName }) =>
|
||||||
|
await this.communityPackagesService.removeNpmPackage(packageName),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
43
packages/cli/src/scaling/worker-status.ts
Normal file
43
packages/cli/src/scaling/worker-status.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import os from 'node:os';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
import config from '@/config';
|
||||||
|
import { N8N_VERSION } from '@/constants';
|
||||||
|
|
||||||
|
import { JobProcessor } from './job-processor';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class WorkerStatus {
|
||||||
|
constructor(private readonly jobProcessor: JobProcessor) {}
|
||||||
|
|
||||||
|
generateStatus() {
|
||||||
|
return {
|
||||||
|
workerId: config.getEnv('redis.queueModeId'),
|
||||||
|
runningJobsSummary: this.jobProcessor.getRunningJobsSummary(),
|
||||||
|
freeMem: os.freemem(),
|
||||||
|
totalMem: os.totalmem(),
|
||||||
|
uptime: process.uptime(),
|
||||||
|
loadAvg: os.loadavg(),
|
||||||
|
cpus: this.getOsCpuString(),
|
||||||
|
arch: os.arch(),
|
||||||
|
platform: os.platform(),
|
||||||
|
hostname: os.hostname(),
|
||||||
|
interfaces: Object.values(os.networkInterfaces()).flatMap((interfaces) =>
|
||||||
|
(interfaces ?? [])?.map((net) => ({
|
||||||
|
family: net.family,
|
||||||
|
address: net.address,
|
||||||
|
internal: net.internal,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
version: N8N_VERSION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOsCpuString() {
|
||||||
|
const cpus = os.cpus();
|
||||||
|
|
||||||
|
if (cpus.length === 0) return 'no CPU info';
|
||||||
|
|
||||||
|
return `${cpus.length}x ${cpus[0].model} - speed: ${cpus[0].speed}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,153 +0,0 @@
|
||||||
import { jsonParse } from 'n8n-workflow';
|
|
||||||
import os from 'node:os';
|
|
||||||
import Container from 'typedi';
|
|
||||||
|
|
||||||
import { N8N_VERSION } from '@/constants';
|
|
||||||
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
|
|
||||||
import { ExternalSecretsManager } from '@/external-secrets/external-secrets-manager.ee';
|
|
||||||
import { License } from '@/license';
|
|
||||||
import { Logger } from '@/logging/logger.service';
|
|
||||||
import { COMMAND_PUBSUB_CHANNEL } from '@/scaling/constants';
|
|
||||||
import type { PubSub } from '@/scaling/pubsub/pubsub.types';
|
|
||||||
import { CommunityPackagesService } from '@/services/community-packages.service';
|
|
||||||
|
|
||||||
import type { WorkerCommandReceivedHandlerOptions } from './types';
|
|
||||||
import { debounceMessageReceiver, getOsCpuString } from '../helpers';
|
|
||||||
|
|
||||||
// eslint-disable-next-line complexity
|
|
||||||
export async function getWorkerCommandReceivedHandler(
|
|
||||||
messageString: string,
|
|
||||||
options: WorkerCommandReceivedHandlerOptions,
|
|
||||||
) {
|
|
||||||
if (!messageString) return;
|
|
||||||
|
|
||||||
const logger = Container.get(Logger);
|
|
||||||
let message: PubSub.Command;
|
|
||||||
try {
|
|
||||||
message = jsonParse<PubSub.Command>(messageString);
|
|
||||||
} catch {
|
|
||||||
logger.debug(
|
|
||||||
`Received invalid message via channel ${COMMAND_PUBSUB_CHANNEL}: "${messageString}"`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message) {
|
|
||||||
logger.debug(
|
|
||||||
`RedisCommandHandler(worker): Received command message ${message.command} from ${message.senderId}`,
|
|
||||||
);
|
|
||||||
if (message.targets && !message.targets.includes(options.queueModeId)) {
|
|
||||||
return; // early return if the message is not for this worker
|
|
||||||
}
|
|
||||||
switch (message.command) {
|
|
||||||
case 'get-worker-status':
|
|
||||||
if (!debounceMessageReceiver(message, 500)) return;
|
|
||||||
await options.publisher.publishWorkerResponse({
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
command: 'get-worker-status',
|
|
||||||
payload: {
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
runningJobsSummary: options.getRunningJobsSummary(),
|
|
||||||
freeMem: os.freemem(),
|
|
||||||
totalMem: os.totalmem(),
|
|
||||||
uptime: process.uptime(),
|
|
||||||
loadAvg: os.loadavg(),
|
|
||||||
cpus: getOsCpuString(),
|
|
||||||
arch: os.arch(),
|
|
||||||
platform: os.platform(),
|
|
||||||
hostname: os.hostname(),
|
|
||||||
interfaces: Object.values(os.networkInterfaces()).flatMap((interfaces) =>
|
|
||||||
(interfaces ?? [])?.map((net) => ({
|
|
||||||
family: net.family,
|
|
||||||
address: net.address,
|
|
||||||
internal: net.internal,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
version: N8N_VERSION,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'get-worker-id':
|
|
||||||
if (!debounceMessageReceiver(message, 500)) return;
|
|
||||||
await options.publisher.publishWorkerResponse({
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
command: 'get-worker-id',
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'restart-event-bus':
|
|
||||||
if (!debounceMessageReceiver(message, 500)) return;
|
|
||||||
try {
|
|
||||||
await Container.get(MessageEventBus).restart();
|
|
||||||
await options.publisher.publishWorkerResponse({
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
command: 'restart-event-bus',
|
|
||||||
payload: {
|
|
||||||
result: 'success',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
await options.publisher.publishWorkerResponse({
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
command: 'restart-event-bus',
|
|
||||||
payload: {
|
|
||||||
result: 'error',
|
|
||||||
error: (error as Error).message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'reload-external-secrets-providers':
|
|
||||||
if (!debounceMessageReceiver(message, 500)) return;
|
|
||||||
try {
|
|
||||||
await Container.get(ExternalSecretsManager).reloadAllProviders();
|
|
||||||
await options.publisher.publishWorkerResponse({
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
command: 'reload-external-secrets-providers',
|
|
||||||
payload: {
|
|
||||||
result: 'success',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
await options.publisher.publishWorkerResponse({
|
|
||||||
workerId: options.queueModeId,
|
|
||||||
command: 'reload-external-secrets-providers',
|
|
||||||
payload: {
|
|
||||||
result: 'error',
|
|
||||||
error: (error as Error).message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'community-package-install':
|
|
||||||
case 'community-package-update':
|
|
||||||
case 'community-package-uninstall':
|
|
||||||
if (!debounceMessageReceiver(message, 500)) return;
|
|
||||||
const { packageName } = message.payload;
|
|
||||||
const communityPackagesService = Container.get(CommunityPackagesService);
|
|
||||||
if (message.command === 'community-package-uninstall') {
|
|
||||||
await communityPackagesService.removeNpmPackage(packageName);
|
|
||||||
} else {
|
|
||||||
await communityPackagesService.installOrUpdateNpmPackage(
|
|
||||||
packageName,
|
|
||||||
message.payload.packageVersion,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'reload-license':
|
|
||||||
if (!debounceMessageReceiver(message, 500)) return;
|
|
||||||
await Container.get(License).reload();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (
|
|
||||||
message.command === 'relay-execution-lifecycle-event' ||
|
|
||||||
message.command === 'clear-test-webhooks'
|
|
||||||
) {
|
|
||||||
break; // meant only for main
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Received unknown command via channel ${COMMAND_PUBSUB_CHANNEL}: "${message.command}"`,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { Service } from 'typedi';
|
|
||||||
|
|
||||||
import { Subscriber } from '@/scaling/pubsub/subscriber.service';
|
|
||||||
|
|
||||||
import { getWorkerCommandReceivedHandler } from './handle-command-message-worker';
|
|
||||||
import type { WorkerCommandReceivedHandlerOptions } from './types';
|
|
||||||
import { OrchestrationHandlerService } from '../../orchestration.handler.base.service';
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export class OrchestrationHandlerWorkerService extends OrchestrationHandlerService {
|
|
||||||
constructor(private readonly subscriber: Subscriber) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async initSubscriber(options: WorkerCommandReceivedHandlerOptions) {
|
|
||||||
await this.subscriber.subscribe('n8n.commands');
|
|
||||||
|
|
||||||
this.subscriber.setMessageHandler('n8n.commands', async (message: string) => {
|
|
||||||
await getWorkerCommandReceivedHandler(message, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,8 +11,8 @@ import { ExternalSecretsManager } from '@/external-secrets/external-secrets-mana
|
||||||
import { License } from '@/license';
|
import { License } from '@/license';
|
||||||
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
|
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
|
||||||
import { Publisher } from '@/scaling/pubsub/publisher.service';
|
import { Publisher } from '@/scaling/pubsub/publisher.service';
|
||||||
|
import { Subscriber } from '@/scaling/pubsub/subscriber.service';
|
||||||
import { ScalingService } from '@/scaling/scaling.service';
|
import { ScalingService } from '@/scaling/scaling.service';
|
||||||
import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.service';
|
|
||||||
import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service';
|
import { OrchestrationWorkerService } from '@/services/orchestration/worker/orchestration.worker.service';
|
||||||
import { setupTestCommand } from '@test-integration/utils/test-command';
|
import { setupTestCommand } from '@test-integration/utils/test-command';
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ const externalSecretsManager = mockInstance(ExternalSecretsManager);
|
||||||
const license = mockInstance(License, { loadCertStr: async () => '' });
|
const license = mockInstance(License, { loadCertStr: async () => '' });
|
||||||
const messageEventBus = mockInstance(MessageEventBus);
|
const messageEventBus = mockInstance(MessageEventBus);
|
||||||
const logStreamingEventRelay = mockInstance(LogStreamingEventRelay);
|
const logStreamingEventRelay = mockInstance(LogStreamingEventRelay);
|
||||||
const orchestrationHandlerWorkerService = mockInstance(OrchestrationHandlerWorkerService);
|
|
||||||
const scalingService = mockInstance(ScalingService);
|
const scalingService = mockInstance(ScalingService);
|
||||||
const orchestrationWorkerService = mockInstance(OrchestrationWorkerService);
|
const orchestrationWorkerService = mockInstance(OrchestrationWorkerService);
|
||||||
mockInstance(Publisher);
|
mockInstance(Publisher);
|
||||||
|
mockInstance(Subscriber);
|
||||||
|
|
||||||
const command = setupTestCommand(Worker);
|
const command = setupTestCommand(Worker);
|
||||||
|
|
||||||
|
@ -48,6 +48,5 @@ test('worker initializes all its components', async () => {
|
||||||
expect(scalingService.setupWorker).toHaveBeenCalledTimes(1);
|
expect(scalingService.setupWorker).toHaveBeenCalledTimes(1);
|
||||||
expect(logStreamingEventRelay.init).toHaveBeenCalledTimes(1);
|
expect(logStreamingEventRelay.init).toHaveBeenCalledTimes(1);
|
||||||
expect(orchestrationWorkerService.init).toHaveBeenCalledTimes(1);
|
expect(orchestrationWorkerService.init).toHaveBeenCalledTimes(1);
|
||||||
expect(orchestrationHandlerWorkerService.initWithOptions).toHaveBeenCalledTimes(1);
|
|
||||||
expect(messageEventBus.send).toHaveBeenCalledTimes(1);
|
expect(messageEventBus.send).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue