refactor(core): Move queueModeId as hostId to InstanceSettings (#11262)

This commit is contained in:
Iván Ovejero
2024-10-15 14:55:13 +02:00
committed by GitHub
parent d3b05f1c54
commit 05467fd101
27 changed files with 123 additions and 127 deletions

View File

@@ -1,8 +1,8 @@
import type { Redis as SingleNodeClient } from 'ioredis';
import { mock } from 'jest-mock-extended';
import type { InstanceSettings } from 'n8n-core';
import config from '@/config';
import { generateNanoId } from '@/databases/utils/generators';
import type { RedisClientService } from '@/services/redis-client.service';
import { mockLogger } from '@test/mocking';
@@ -10,28 +10,26 @@ import { Publisher } from '../pubsub/publisher.service';
import type { PubSub } from '../pubsub/pubsub.types';
describe('Publisher', () => {
let queueModeId: string;
beforeEach(() => {
config.set('executions.mode', 'queue');
queueModeId = generateNanoId();
config.set('redis.queueModeId', queueModeId);
});
const client = mock<SingleNodeClient>();
const logger = mockLogger();
const hostId = 'main-bnxa1riryKUNHtln';
const instanceSettings = mock<InstanceSettings>({ hostId });
const redisClientService = mock<RedisClientService>({ createClient: () => client });
describe('constructor', () => {
it('should init Redis client in scaling mode', () => {
const publisher = new Publisher(logger, redisClientService);
const publisher = new Publisher(logger, redisClientService, instanceSettings);
expect(publisher.getClient()).toEqual(client);
});
it('should not init Redis client in regular mode', () => {
config.set('executions.mode', 'regular');
const publisher = new Publisher(logger, redisClientService);
const publisher = new Publisher(logger, redisClientService, instanceSettings);
expect(publisher.getClient()).toBeUndefined();
});
@@ -39,7 +37,7 @@ describe('Publisher', () => {
describe('shutdown', () => {
it('should disconnect Redis client', () => {
const publisher = new Publisher(logger, redisClientService);
const publisher = new Publisher(logger, redisClientService, instanceSettings);
publisher.shutdown();
expect(client.disconnect).toHaveBeenCalled();
});
@@ -47,21 +45,21 @@ describe('Publisher', () => {
describe('publishCommand', () => {
it('should publish command into `n8n.commands` pubsub channel', async () => {
const publisher = new Publisher(logger, redisClientService);
const publisher = new Publisher(logger, redisClientService, instanceSettings);
const msg = mock<PubSub.Command>({ command: 'reload-license' });
await publisher.publishCommand(msg);
expect(client.publish).toHaveBeenCalledWith(
'n8n.commands',
JSON.stringify({ ...msg, senderId: queueModeId, selfSend: false, debounce: true }),
JSON.stringify({ ...msg, senderId: hostId, selfSend: false, debounce: true }),
);
});
});
describe('publishWorkerResponse', () => {
it('should publish worker response into `n8n.worker-response` pubsub channel', async () => {
const publisher = new Publisher(logger, redisClientService);
const publisher = new Publisher(logger, redisClientService, instanceSettings);
const msg = mock<PubSub.WorkerResponse>({
response: 'response-to-get-worker-status',
});

View File

@@ -17,14 +17,14 @@ describe('Subscriber', () => {
describe('constructor', () => {
it('should init Redis client in scaling mode', () => {
const subscriber = new Subscriber(mock(), redisClientService, mock());
const subscriber = new Subscriber(mock(), redisClientService, mock(), mock());
expect(subscriber.getClient()).toEqual(client);
});
it('should not init Redis client in regular mode', () => {
config.set('executions.mode', 'regular');
const subscriber = new Subscriber(mock(), redisClientService, mock());
const subscriber = new Subscriber(mock(), redisClientService, mock(), mock());
expect(subscriber.getClient()).toBeUndefined();
});
@@ -32,7 +32,7 @@ describe('Subscriber', () => {
describe('shutdown', () => {
it('should disconnect Redis client', () => {
const subscriber = new Subscriber(mock(), redisClientService, mock());
const subscriber = new Subscriber(mock(), redisClientService, mock(), mock());
subscriber.shutdown();
expect(client.disconnect).toHaveBeenCalled();
});
@@ -40,7 +40,7 @@ describe('Subscriber', () => {
describe('subscribe', () => {
it('should subscribe to pubsub channel', async () => {
const subscriber = new Subscriber(mock(), redisClientService, mock());
const subscriber = new Subscriber(mock(), redisClientService, mock(), mock());
await subscriber.subscribe('n8n.commands');

View File

@@ -1,5 +1,5 @@
import type { RunningJobSummary } from '@n8n/api-types';
import { WorkflowExecute } from 'n8n-core';
import { InstanceSettings, WorkflowExecute } from 'n8n-core';
import { BINARY_ENCODING, ApplicationError, Workflow } from 'n8n-workflow';
import type { ExecutionStatus, IExecuteResponsePromiseData, IRun } from 'n8n-workflow';
import type PCancelable from 'p-cancelable';
@@ -33,6 +33,7 @@ export class JobProcessor {
private readonly executionRepository: ExecutionRepository,
private readonly workflowRepository: WorkflowRepository,
private readonly nodeTypes: NodeTypes,
private readonly instanceSettings: InstanceSettings,
) {
this.logger = this.logger.withScope('scaling');
}
@@ -120,7 +121,7 @@ export class JobProcessor {
kind: 'respond-to-webhook',
executionId,
response: this.encodeWebhookResponse(response),
workerId: config.getEnv('redis.queueModeId'),
workerId: this.instanceSettings.hostId,
};
await job.progress(msg);
@@ -173,7 +174,7 @@ export class JobProcessor {
const msg: JobFinishedMessage = {
kind: 'job-finished',
executionId,
workerId: config.getEnv('redis.queueModeId'),
workerId: this.instanceSettings.hostId,
};
await job.progress(msg);

View File

@@ -1,4 +1,5 @@
import type { Redis as SingleNodeClient, Cluster as MultiNodeClient } from 'ioredis';
import { InstanceSettings } from 'n8n-core';
import { Service } from 'typedi';
import config from '@/config';
@@ -20,6 +21,7 @@ export class Publisher {
constructor(
private readonly logger: Logger,
private readonly redisClientService: RedisClientService,
private readonly instanceSettings: InstanceSettings,
) {
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
if (config.getEnv('executions.mode') !== 'queue') return;
@@ -48,7 +50,7 @@ export class Publisher {
'n8n.commands',
JSON.stringify({
...msg,
senderId: config.getEnv('redis.queueModeId'),
senderId: this.instanceSettings.hostId,
selfSend: SELF_SEND_COMMANDS.has(msg.command),
debounce: !IMMEDIATE_COMMANDS.has(msg.command),
}),

View File

@@ -3,7 +3,6 @@ import { ensureError } from 'n8n-workflow';
import { Service } from 'typedi';
import { ActiveWorkflowManager } from '@/active-workflow-manager';
import config from '@/config';
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
import { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import { EventService } from '@/events/event.service';
@@ -49,7 +48,7 @@ export class PubSubHandler {
...this.commonHandlers,
'get-worker-status': async () =>
await this.publisher.publishWorkerResponse({
senderId: config.getEnv('redis.queueModeId'),
senderId: this.instanceSettings.hostId,
response: 'response-to-get-worker-status',
payload: this.workerStatusService.generateStatus(),
}),

View File

@@ -1,5 +1,6 @@
import type { Redis as SingleNodeClient, Cluster as MultiNodeClient } from 'ioredis';
import debounce from 'lodash/debounce';
import { InstanceSettings } from 'n8n-core';
import { jsonParse } from 'n8n-workflow';
import { Service } from 'typedi';
@@ -21,6 +22,7 @@ export class Subscriber {
private readonly logger: Logger,
private readonly redisClientService: RedisClientService,
private readonly eventService: EventService,
private readonly instanceSettings: InstanceSettings,
) {
// @TODO: Once this class is only ever initialized in scaling mode, throw in the next line instead.
if (config.getEnv('executions.mode') !== 'queue') return;
@@ -77,12 +79,12 @@ export class Subscriber {
return null;
}
const queueModeId = config.getEnv('redis.queueModeId');
const { hostId } = this.instanceSettings;
if (
'command' in msg &&
!msg.selfSend &&
(msg.senderId === queueModeId || (msg.targets && !msg.targets.includes(queueModeId)))
(msg.senderId === hostId || (msg.targets && !msg.targets.includes(hostId)))
) {
return null;
}

View File

@@ -112,7 +112,7 @@ export class ScalingService {
const msg: JobFailedMessage = {
kind: 'job-failed',
executionId,
workerId: config.getEnv('redis.queueModeId'),
workerId: this.instanceSettings.hostId,
errorMsg: error.message,
};

View File

@@ -1,19 +1,22 @@
import type { WorkerStatus } from '@n8n/api-types';
import { InstanceSettings } from 'n8n-core';
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 WorkerStatusService {
constructor(private readonly jobProcessor: JobProcessor) {}
constructor(
private readonly jobProcessor: JobProcessor,
private readonly instanceSettings: InstanceSettings,
) {}
generateStatus(): WorkerStatus {
return {
senderId: config.getEnv('redis.queueModeId'),
senderId: this.instanceSettings.hostId,
runningJobsSummary: this.jobProcessor.getRunningJobsSummary(),
freeMem: os.freemem(),
totalMem: os.totalmem(),