fix(core): Make senderId required for all command messages (#7252)
all commands sent between main instance and workers need to contain a server id to prevent senders from reacting to their own messages, causing loops this PR makes sure all sent messages contain a sender id by default as part of constructing a sending redis client. --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
committed by
GitHub
parent
77d6e3fc07
commit
4b014286cf
@@ -10,20 +10,13 @@ import { handleCommandMessage } from './orchestration/handleCommandMessage';
|
||||
export class OrchestrationService {
|
||||
private initialized = false;
|
||||
|
||||
private _uniqueInstanceId = '';
|
||||
|
||||
get uniqueInstanceId(): string {
|
||||
return this._uniqueInstanceId;
|
||||
}
|
||||
|
||||
redisPublisher: RedisServicePubSubPublisher;
|
||||
|
||||
redisSubscriber: RedisServicePubSubSubscriber;
|
||||
|
||||
constructor(readonly redisService: RedisService) {}
|
||||
|
||||
async init(uniqueInstanceId: string) {
|
||||
this._uniqueInstanceId = uniqueInstanceId;
|
||||
async init() {
|
||||
await this.initPublisher();
|
||||
await this.initSubscriber();
|
||||
this.initialized = true;
|
||||
@@ -50,7 +43,7 @@ export class OrchestrationService {
|
||||
if (channel === WORKER_RESPONSE_REDIS_CHANNEL) {
|
||||
await handleWorkerResponseMessage(messageString);
|
||||
} else if (channel === COMMAND_REDIS_CHANNEL) {
|
||||
await handleCommandMessage(messageString, this.uniqueInstanceId);
|
||||
await handleCommandMessage(messageString);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -61,7 +54,6 @@ export class OrchestrationService {
|
||||
throw new Error('OrchestrationService not initialized');
|
||||
}
|
||||
await this.redisPublisher.publishToCommandChannel({
|
||||
senderId: this.uniqueInstanceId,
|
||||
command: 'getStatus',
|
||||
targets: id ? [id] : undefined,
|
||||
});
|
||||
@@ -72,32 +64,7 @@ export class OrchestrationService {
|
||||
throw new Error('OrchestrationService not initialized');
|
||||
}
|
||||
await this.redisPublisher.publishToCommandChannel({
|
||||
senderId: this.uniqueInstanceId,
|
||||
command: 'getId',
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: not implemented yet on worker side
|
||||
async stopWorker(id?: string) {
|
||||
if (!this.initialized) {
|
||||
throw new Error('OrchestrationService not initialized');
|
||||
}
|
||||
await this.redisPublisher.publishToCommandChannel({
|
||||
senderId: this.uniqueInstanceId,
|
||||
command: 'stopWorker',
|
||||
targets: id ? [id] : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// reload the license on workers after it was changed on the main instance
|
||||
async reloadLicense(id?: string) {
|
||||
if (!this.initialized) {
|
||||
throw new Error('OrchestrationService not initialized');
|
||||
}
|
||||
await this.redisPublisher.publishToCommandChannel({
|
||||
senderId: this.uniqueInstanceId,
|
||||
command: 'reloadLicense',
|
||||
targets: id ? [id] : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import { messageToRedisServiceCommandObject } from './helpers';
|
||||
import config from '@/config';
|
||||
import { MessageEventBus } from '../../eventbus/MessageEventBus/MessageEventBus';
|
||||
import Container from 'typedi';
|
||||
import { License } from '@/License';
|
||||
|
||||
// this function handles commands sent to the MAIN instance. the workers handle their own commands
|
||||
export async function handleCommandMessage(messageString: string, uniqueInstanceId: string) {
|
||||
export async function handleCommandMessage(messageString: string) {
|
||||
const queueModeId = config.get('redis.queueModeId');
|
||||
const message = messageToRedisServiceCommandObject(messageString);
|
||||
if (message) {
|
||||
if (
|
||||
message.senderId === uniqueInstanceId ||
|
||||
(message.targets && !message.targets.includes(uniqueInstanceId))
|
||||
message.senderId === queueModeId ||
|
||||
(message.targets && !message.targets.includes(queueModeId))
|
||||
) {
|
||||
// Skipping command message because it's not for this instance
|
||||
LoggerProxy.debug(
|
||||
`Skipping command message ${message.command} because it's not for this instance.`,
|
||||
);
|
||||
@@ -18,8 +21,16 @@ export async function handleCommandMessage(messageString: string, uniqueInstance
|
||||
}
|
||||
switch (message.command) {
|
||||
case 'reloadLicense':
|
||||
await Container.get(License).reload();
|
||||
// at this point in time, only a single main instance is supported, thus this
|
||||
// command _should_ never be caught currently (which is why we log a warning)
|
||||
LoggerProxy.warn(
|
||||
'Received command to reload license via Redis, but this should not have happened and is not supported on the main instance yet.',
|
||||
);
|
||||
// once multiple main instances are supported, this command should be handled
|
||||
// await Container.get(License).reload();
|
||||
break;
|
||||
case 'restartEventBus':
|
||||
await Container.get(MessageEventBus).restart();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type Redis from 'ioredis';
|
||||
import type { Cluster } from 'ioredis';
|
||||
import { getDefaultRedisClient } from './RedisServiceHelper';
|
||||
import { LoggerProxy } from 'n8n-workflow';
|
||||
import config from '@/config';
|
||||
|
||||
export type RedisClientType =
|
||||
| 'subscriber'
|
||||
@@ -57,8 +58,9 @@ class RedisServiceBase {
|
||||
export abstract class RedisServiceBaseSender extends RedisServiceBase {
|
||||
senderId: string;
|
||||
|
||||
setSenderId(senderId?: string): void {
|
||||
this.senderId = senderId ?? '';
|
||||
async init(type: RedisClientType = 'client'): Promise<void> {
|
||||
await super.init(type);
|
||||
this.senderId = config.get('redis.queueModeId');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export type RedisServiceCommand =
|
||||
* @field payload: Optional arguments to be sent with the command.
|
||||
*/
|
||||
type RedisServiceBaseCommand = {
|
||||
senderId?: string;
|
||||
senderId: string;
|
||||
command: RedisServiceCommand;
|
||||
payload?: {
|
||||
[key: string]: string | number | boolean | string[] | number[] | boolean[];
|
||||
|
||||
@@ -5,9 +5,8 @@ import { RedisServiceBaseSender } from './RedisServiceBaseClasses';
|
||||
|
||||
@Service()
|
||||
export class RedisServiceListSender extends RedisServiceBaseSender {
|
||||
async init(senderId?: string): Promise<void> {
|
||||
async init(): Promise<void> {
|
||||
await super.init('list-sender');
|
||||
this.setSenderId(senderId);
|
||||
}
|
||||
|
||||
async prepend(list: string, message: string): Promise<void> {
|
||||
|
||||
@@ -13,9 +13,8 @@ import { RedisServiceBaseSender } from './RedisServiceBaseClasses';
|
||||
|
||||
@Service()
|
||||
export class RedisServicePubSubPublisher extends RedisServiceBaseSender {
|
||||
async init(senderId?: string): Promise<void> {
|
||||
async init(): Promise<void> {
|
||||
await super.init('publisher');
|
||||
this.setSenderId(senderId);
|
||||
}
|
||||
|
||||
async publish(channel: string, message: string): Promise<void> {
|
||||
@@ -29,8 +28,12 @@ export class RedisServicePubSubPublisher extends RedisServiceBaseSender {
|
||||
await this.publish(EVENT_BUS_REDIS_CHANNEL, message.toString());
|
||||
}
|
||||
|
||||
async publishToCommandChannel(message: RedisServiceCommandObject): Promise<void> {
|
||||
await this.publish(COMMAND_REDIS_CHANNEL, JSON.stringify(message));
|
||||
async publishToCommandChannel(
|
||||
message: Omit<RedisServiceCommandObject, 'senderId'>,
|
||||
): Promise<void> {
|
||||
const messageWithSenderId = message as RedisServiceCommandObject;
|
||||
messageWithSenderId.senderId = this.senderId;
|
||||
await this.publish(COMMAND_REDIS_CHANNEL, JSON.stringify(messageWithSenderId));
|
||||
}
|
||||
|
||||
async publishToWorkerChannel(message: RedisServiceWorkerResponseObject): Promise<void> {
|
||||
|
||||
@@ -14,9 +14,8 @@ import { RedisServiceBaseSender } from './RedisServiceBaseClasses';
|
||||
|
||||
@Service()
|
||||
export class RedisServiceStreamProducer extends RedisServiceBaseSender {
|
||||
async init(senderId?: string): Promise<void> {
|
||||
async init(): Promise<void> {
|
||||
await super.init('producer');
|
||||
this.setSenderId(senderId);
|
||||
}
|
||||
|
||||
async add(streamName: string, values: RedisValue[]): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user