mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(core): Simplify OrchestrationService
(no-changelog) (#8364)
This commit is contained in:
parent
2ccb754e52
commit
f35d4fcbd8
|
@ -47,7 +47,7 @@ import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { WebhookService } from './services/webhook.service';
|
import { WebhookService } from './services/webhook.service';
|
||||||
import { Logger } from './Logger';
|
import { Logger } from './Logger';
|
||||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { ActivationErrorsService } from '@/ActivationErrors.service';
|
import { ActivationErrorsService } from '@/ActivationErrors.service';
|
||||||
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
import { ActiveWorkflowsService } from '@/services/activeWorkflows.service';
|
||||||
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
|
import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service';
|
||||||
|
@ -72,7 +72,7 @@ export class ActiveWorkflowRunner {
|
||||||
private readonly nodeTypes: NodeTypes,
|
private readonly nodeTypes: NodeTypes,
|
||||||
private readonly webhookService: WebhookService,
|
private readonly webhookService: WebhookService,
|
||||||
private readonly workflowRepository: WorkflowRepository,
|
private readonly workflowRepository: WorkflowRepository,
|
||||||
private readonly multiMainSetup: MultiMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly activationErrorsService: ActivationErrorsService,
|
private readonly activationErrorsService: ActivationErrorsService,
|
||||||
private readonly executionService: ExecutionService,
|
private readonly executionService: ExecutionService,
|
||||||
private readonly workflowStaticDataService: WorkflowStaticDataService,
|
private readonly workflowStaticDataService: WorkflowStaticDataService,
|
||||||
|
@ -80,7 +80,7 @@ export class ActiveWorkflowRunner {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.multiMainSetup.init();
|
await this.orchestrationService.init();
|
||||||
|
|
||||||
await this.addActiveWorkflows('init');
|
await this.addActiveWorkflows('init');
|
||||||
|
|
||||||
|
@ -470,25 +470,23 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
if (dbWorkflows.length === 0) return;
|
if (dbWorkflows.length === 0) return;
|
||||||
|
|
||||||
this.logger.info(' ================================');
|
if (this.orchestrationService.isLeader) {
|
||||||
this.logger.info(' Start Active Workflows:');
|
this.logger.info(' ================================');
|
||||||
this.logger.info(' ================================');
|
this.logger.info(' Start Active Workflows:');
|
||||||
|
this.logger.info(' ================================');
|
||||||
|
}
|
||||||
|
|
||||||
for (const dbWorkflow of dbWorkflows) {
|
for (const dbWorkflow of dbWorkflows) {
|
||||||
this.logger.info(` - ${dbWorkflow.display()}`);
|
|
||||||
this.logger.debug(`Initializing active workflow ${dbWorkflow.display()} (startup)`, {
|
|
||||||
workflowName: dbWorkflow.name,
|
|
||||||
workflowId: dbWorkflow.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.add(dbWorkflow.id, activationMode, dbWorkflow);
|
const wasActivated = await this.add(dbWorkflow.id, activationMode, dbWorkflow);
|
||||||
|
|
||||||
this.logger.verbose(`Successfully started workflow ${dbWorkflow.display()}`, {
|
if (wasActivated) {
|
||||||
workflowName: dbWorkflow.name,
|
this.logger.verbose(`Successfully started workflow ${dbWorkflow.display()}`, {
|
||||||
workflowId: dbWorkflow.id,
|
workflowName: dbWorkflow.name,
|
||||||
});
|
workflowId: dbWorkflow.id,
|
||||||
this.logger.info(' => Started');
|
});
|
||||||
|
this.logger.info(' => Started');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorReporter.error(error);
|
ErrorReporter.error(error);
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
|
@ -571,16 +569,18 @@ export class ActiveWorkflowRunner {
|
||||||
* again, and the new leader should take over the triggers and pollers that stopped
|
* again, and the new leader should take over the triggers and pollers that stopped
|
||||||
* running when the former leader became unresponsive.
|
* running when the former leader became unresponsive.
|
||||||
*/
|
*/
|
||||||
if (this.multiMainSetup.isEnabled) {
|
if (this.orchestrationService.isMultiMainSetupEnabled) {
|
||||||
if (activationMode !== 'leadershipChange') {
|
if (activationMode !== 'leadershipChange') {
|
||||||
shouldAddWebhooks = this.multiMainSetup.isLeader;
|
shouldAddWebhooks = this.orchestrationService.isLeader;
|
||||||
shouldAddTriggersAndPollers = this.multiMainSetup.isLeader;
|
shouldAddTriggersAndPollers = this.orchestrationService.isLeader;
|
||||||
} else {
|
} else {
|
||||||
shouldAddWebhooks = false;
|
shouldAddWebhooks = false;
|
||||||
shouldAddTriggersAndPollers = this.multiMainSetup.isLeader;
|
shouldAddTriggersAndPollers = this.orchestrationService.isLeader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldActivate = shouldAddWebhooks || shouldAddTriggersAndPollers;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dbWorkflow = existingWorkflow ?? (await this.workflowRepository.findById(workflowId));
|
const dbWorkflow = existingWorkflow ?? (await this.workflowRepository.findById(workflowId));
|
||||||
|
|
||||||
|
@ -588,6 +588,14 @@ export class ActiveWorkflowRunner {
|
||||||
throw new WorkflowActivationError(`Failed to find workflow with ID "${workflowId}"`);
|
throw new WorkflowActivationError(`Failed to find workflow with ID "${workflowId}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldActivate) {
|
||||||
|
this.logger.info(` - ${dbWorkflow.display()}`);
|
||||||
|
this.logger.debug(`Initializing active workflow ${dbWorkflow.display()} (startup)`, {
|
||||||
|
workflowName: dbWorkflow.name,
|
||||||
|
workflowId: dbWorkflow.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
workflow = new Workflow({
|
workflow = new Workflow({
|
||||||
id: dbWorkflow.id,
|
id: dbWorkflow.id,
|
||||||
name: dbWorkflow.name,
|
name: dbWorkflow.name,
|
||||||
|
@ -644,6 +652,8 @@ export class ActiveWorkflowRunner {
|
||||||
// If for example webhooks get created it sometimes has to save the
|
// If for example webhooks get created it sometimes has to save the
|
||||||
// id of them in the static data. So make sure that data gets persisted.
|
// id of them in the static data. So make sure that data gets persisted.
|
||||||
await this.workflowStaticDataService.saveStaticData(workflow);
|
await this.workflowStaticDataService.saveStaticData(workflow);
|
||||||
|
|
||||||
|
return shouldActivate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -804,7 +814,7 @@ export class ActiveWorkflowRunner {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (workflow.getTriggerNodes().length !== 0 || workflow.getPollNodes().length !== 0) {
|
if (workflow.getTriggerNodes().length !== 0 || workflow.getPollNodes().length !== 0) {
|
||||||
this.logger.debug(`Adding triggers and pollers for workflow "${dbWorkflow.display()}"`);
|
this.logger.debug(`Adding triggers and pollers for workflow ${dbWorkflow.display()}`);
|
||||||
|
|
||||||
await this.activeWorkflows.add(
|
await this.activeWorkflows.add(
|
||||||
workflow.id,
|
workflow.id,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { License } from '@/License';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { updateIntervalTime } from './externalSecretsHelper.ee';
|
import { updateIntervalTime } from './externalSecretsHelper.ee';
|
||||||
import { ExternalSecretsProviders } from './ExternalSecretsProviders.ee';
|
import { ExternalSecretsProviders } from './ExternalSecretsProviders.ee';
|
||||||
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExternalSecretsManager {
|
export class ExternalSecretsManager {
|
||||||
|
@ -79,7 +79,7 @@ export class ExternalSecretsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async broadcastReloadExternalSecretsProviders() {
|
async broadcastReloadExternalSecretsProviders() {
|
||||||
await Container.get(SingleMainSetup).broadcastReloadExternalSecretsProviders();
|
await Container.get(OrchestrationService).publish('reloadExternalSecretsProviders');
|
||||||
}
|
}
|
||||||
|
|
||||||
private decryptSecretsSettings(value: string): ExternalSecretsSettings {
|
private decryptSecretsSettings(value: string): ExternalSecretsSettings {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||||
import type { BooleanLicenseFeature, N8nInstanceType, NumericLicenseFeature } from './Interfaces';
|
import type { BooleanLicenseFeature, N8nInstanceType, NumericLicenseFeature } from './Interfaces';
|
||||||
import type { RedisServicePubSubPublisher } from './services/redis/RedisServicePubSubPublisher';
|
import type { RedisServicePubSubPublisher } from './services/redis/RedisServicePubSubPublisher';
|
||||||
import { RedisService } from './services/redis.service';
|
import { RedisService } from './services/redis.service';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { OnShutdown } from '@/decorators/OnShutdown';
|
import { OnShutdown } from '@/decorators/OnShutdown';
|
||||||
|
|
||||||
type FeatureReturnType = Partial<
|
type FeatureReturnType = Partial<
|
||||||
|
@ -36,7 +36,7 @@ export class License {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly instanceSettings: InstanceSettings,
|
private readonly instanceSettings: InstanceSettings,
|
||||||
private readonly multiMainSetup: MultiMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly settingsRepository: SettingsRepository,
|
private readonly settingsRepository: SettingsRepository,
|
||||||
private readonly workflowRepository: WorkflowRepository,
|
private readonly workflowRepository: WorkflowRepository,
|
||||||
) {}
|
) {}
|
||||||
|
@ -51,8 +51,6 @@ export class License {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.multiMainSetup.init();
|
|
||||||
|
|
||||||
const isMainInstance = instanceType === 'main';
|
const isMainInstance = instanceType === 'main';
|
||||||
const server = config.getEnv('license.serverUrl');
|
const server = config.getEnv('license.serverUrl');
|
||||||
const autoRenewEnabled = isMainInstance && config.getEnv('license.autoRenewEnabled');
|
const autoRenewEnabled = isMainInstance && config.getEnv('license.autoRenewEnabled');
|
||||||
|
@ -123,16 +121,19 @@ export class License {
|
||||||
| boolean
|
| boolean
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
this.multiMainSetup.setLicensed(isMultiMainLicensed ?? false);
|
this.orchestrationService.setMultiMainSetupLicensed(isMultiMainLicensed ?? false);
|
||||||
|
|
||||||
if (this.multiMainSetup.isEnabled && this.multiMainSetup.isFollower) {
|
if (
|
||||||
|
this.orchestrationService.isMultiMainSetupEnabled &&
|
||||||
|
this.orchestrationService.isFollower
|
||||||
|
) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'[Multi-main setup] Instance is follower, skipping sending of "reloadLicense" command...',
|
'[Multi-main setup] Instance is follower, skipping sending of "reloadLicense" command...',
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.multiMainSetup.isEnabled && !isMultiMainLicensed) {
|
if (this.orchestrationService.isMultiMainSetupEnabled && !isMultiMainLicensed) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'[Multi-main setup] License changed with no support for multi-main setup - no new followers will be allowed to init. To restore multi-main setup, please upgrade to a license that supporst this feature.',
|
'[Multi-main setup] License changed with no support for multi-main setup - no new followers will be allowed to init. To restore multi-main setup, please upgrade to a license that supporst this feature.',
|
||||||
);
|
);
|
||||||
|
|
|
@ -101,7 +101,7 @@ import { CollaborationService } from './collaboration/collaboration.service';
|
||||||
import { RoleController } from './controllers/role.controller';
|
import { RoleController } from './controllers/role.controller';
|
||||||
import { BadRequestError } from './errors/response-errors/bad-request.error';
|
import { BadRequestError } from './errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from './errors/response-errors/not-found.error';
|
import { NotFoundError } from './errors/response-errors/not-found.error';
|
||||||
import { MultiMainSetup } from './services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { WorkflowSharingService } from './workflows/workflowSharing.service';
|
import { WorkflowSharingService } from './workflows/workflowSharing.service';
|
||||||
|
|
||||||
const exec = promisify(callbackExec);
|
const exec = promisify(callbackExec);
|
||||||
|
@ -252,7 +252,10 @@ export class Server extends AbstractServer {
|
||||||
ExecutionsController,
|
ExecutionsController,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production' && Container.get(MultiMainSetup).isEnabled) {
|
if (
|
||||||
|
process.env.NODE_ENV !== 'production' &&
|
||||||
|
Container.get(OrchestrationService).isMultiMainSetupEnabled
|
||||||
|
) {
|
||||||
const { DebugController } = await import('@/controllers/debug.controller');
|
const { DebugController } = await import('@/controllers/debug.controller');
|
||||||
controllers.push(DebugController);
|
controllers.push(DebugController);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import * as NodeExecuteFunctions from 'n8n-core';
|
||||||
import { removeTrailingSlash } from './utils';
|
import { removeTrailingSlash } from './utils';
|
||||||
import type { TestWebhookRegistration } from '@/services/test-webhook-registrations.service';
|
import type { TestWebhookRegistration } from '@/services/test-webhook-registrations.service';
|
||||||
import { TestWebhookRegistrationsService } from '@/services/test-webhook-registrations.service';
|
import { TestWebhookRegistrationsService } from '@/services/test-webhook-registrations.service';
|
||||||
import { MultiMainSetup } from './services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
|
@ -34,7 +34,7 @@ export class TestWebhooks implements IWebhookManager {
|
||||||
private readonly push: Push,
|
private readonly push: Push,
|
||||||
private readonly nodeTypes: NodeTypes,
|
private readonly nodeTypes: NodeTypes,
|
||||||
private readonly registrations: TestWebhookRegistrationsService,
|
private readonly registrations: TestWebhookRegistrationsService,
|
||||||
private readonly multiMainSetup: MultiMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private timeouts: { [webhookKey: string]: NodeJS.Timeout } = {};
|
private timeouts: { [webhookKey: string]: NodeJS.Timeout } = {};
|
||||||
|
@ -144,12 +144,12 @@ export class TestWebhooks implements IWebhookManager {
|
||||||
* the handler process commands the creator process to clear its test webhooks.
|
* the handler process commands the creator process to clear its test webhooks.
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
this.multiMainSetup.isEnabled &&
|
this.orchestrationService.isMultiMainSetupEnabled &&
|
||||||
sessionId &&
|
sessionId &&
|
||||||
!this.push.getBackend().hasSessionId(sessionId)
|
!this.push.getBackend().hasSessionId(sessionId)
|
||||||
) {
|
) {
|
||||||
const payload = { webhookKey: key, workflowEntity, sessionId };
|
const payload = { webhookKey: key, workflowEntity, sessionId };
|
||||||
void this.multiMainSetup.publish('clear-test-webhooks', payload);
|
void this.orchestrationService.publish('clear-test-webhooks', payload);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,9 @@ import { BaseCommand } from './BaseCommand';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import type { IConfig } from '@oclif/config';
|
import type { IConfig } from '@oclif/config';
|
||||||
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service';
|
import { OrchestrationHandlerMainService } from '@/services/orchestration/main/orchestration.handler.main.service';
|
||||||
import { PruningService } from '@/services/pruning.service';
|
import { PruningService } from '@/services/pruning.service';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
|
||||||
import { UrlService } from '@/services/url.service';
|
import { UrlService } from '@/services/url.service';
|
||||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||||
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
import { ExecutionRepository } from '@db/repositories/execution.repository';
|
||||||
|
@ -104,10 +103,10 @@ export class Start extends BaseCommand {
|
||||||
|
|
||||||
await this.externalHooks?.run('n8n.stop', []);
|
await this.externalHooks?.run('n8n.stop', []);
|
||||||
|
|
||||||
if (Container.get(MultiMainSetup).isEnabled) {
|
if (Container.get(OrchestrationService).isMultiMainSetupEnabled) {
|
||||||
await this.activeWorkflowRunner.removeAllTriggerAndPollerBasedWorkflows();
|
await this.activeWorkflowRunner.removeAllTriggerAndPollerBasedWorkflows();
|
||||||
|
|
||||||
await Container.get(MultiMainSetup).shutdown();
|
await Container.get(OrchestrationService).shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Container.get(InternalHooks).onN8nStop();
|
await Container.get(InternalHooks).onN8nStop();
|
||||||
|
@ -216,43 +215,48 @@ export class Start extends BaseCommand {
|
||||||
async initOrchestration() {
|
async initOrchestration() {
|
||||||
if (config.getEnv('executions.mode') !== 'queue') return;
|
if (config.getEnv('executions.mode') !== 'queue') return;
|
||||||
|
|
||||||
// queue mode in single-main scenario
|
if (
|
||||||
|
config.getEnv('multiMainSetup.enabled') &&
|
||||||
if (!config.getEnv('multiMainSetup.enabled')) {
|
!Container.get(License).isMultipleMainInstancesLicensed()
|
||||||
await Container.get(SingleMainSetup).init();
|
) {
|
||||||
await Container.get(OrchestrationHandlerMainService).init();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// queue mode in multi-main scenario
|
|
||||||
|
|
||||||
if (!Container.get(License).isMultipleMainInstancesLicensed()) {
|
|
||||||
throw new FeatureNotLicensedError(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES);
|
throw new FeatureNotLicensedError(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const orchestrationService = Container.get(OrchestrationService);
|
||||||
|
|
||||||
|
await orchestrationService.init();
|
||||||
|
|
||||||
await Container.get(OrchestrationHandlerMainService).init();
|
await Container.get(OrchestrationHandlerMainService).init();
|
||||||
|
|
||||||
const multiMainSetup = Container.get(MultiMainSetup);
|
if (!orchestrationService.isMultiMainSetupEnabled) return;
|
||||||
|
|
||||||
await multiMainSetup.init();
|
orchestrationService.multiMainSetup
|
||||||
|
.addListener('leadershipChange', async () => {
|
||||||
|
if (orchestrationService.isLeader) {
|
||||||
|
this.logger.debug('[Leadership change] Clearing all activation errors...');
|
||||||
|
|
||||||
multiMainSetup.on('leadershipChange', async () => {
|
await this.activeWorkflowRunner.clearAllActivationErrors();
|
||||||
if (multiMainSetup.isLeader) {
|
|
||||||
this.logger.debug('[Leadership change] Clearing all activation errors...');
|
|
||||||
|
|
||||||
await this.activeWorkflowRunner.clearAllActivationErrors();
|
this.logger.debug(
|
||||||
|
'[Leadership change] Adding all trigger- and poller-based workflows...',
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.debug('[Leadership change] Adding all trigger- and poller-based workflows...');
|
await this.activeWorkflowRunner.addAllTriggerAndPollerBasedWorkflows();
|
||||||
|
} else {
|
||||||
|
this.logger.debug(
|
||||||
|
'[Leadership change] Removing all trigger- and poller-based workflows...',
|
||||||
|
);
|
||||||
|
|
||||||
await this.activeWorkflowRunner.addAllTriggerAndPollerBasedWorkflows();
|
await this.activeWorkflowRunner.removeAllTriggerAndPollerBasedWorkflows();
|
||||||
} else {
|
}
|
||||||
|
})
|
||||||
|
.addListener('leadershipVacant', async () => {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
'[Leadership change] Removing all trigger- and poller-based workflows...',
|
'[Leadership vacant] Removing all trigger- and poller-based workflows...',
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.activeWorkflowRunner.removeAllTriggerAndPollerBasedWorkflows();
|
await this.activeWorkflowRunner.removeAllTriggerAndPollerBasedWorkflows();
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
|
@ -361,27 +365,27 @@ export class Start extends BaseCommand {
|
||||||
async initPruning() {
|
async initPruning() {
|
||||||
this.pruningService = Container.get(PruningService);
|
this.pruningService = Container.get(PruningService);
|
||||||
|
|
||||||
if (this.pruningService.isPruningEnabled()) {
|
this.pruningService.startPruning();
|
||||||
this.pruningService.startPruning();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.getEnv('executions.mode') === 'queue' && config.getEnv('multiMainSetup.enabled')) {
|
if (config.getEnv('executions.mode') !== 'queue') return;
|
||||||
const multiMainSetup = Container.get(MultiMainSetup);
|
|
||||||
|
|
||||||
await multiMainSetup.init();
|
const orchestrationService = Container.get(OrchestrationService);
|
||||||
|
|
||||||
multiMainSetup.on('leadershipChange', async () => {
|
await orchestrationService.init();
|
||||||
if (multiMainSetup.isLeader) {
|
|
||||||
if (this.pruningService.isPruningEnabled()) {
|
if (!orchestrationService.isMultiMainSetupEnabled) return;
|
||||||
this.pruningService.startPruning();
|
|
||||||
}
|
orchestrationService.multiMainSetup
|
||||||
|
.addListener('leadershipChange', async () => {
|
||||||
|
if (orchestrationService.isLeader) {
|
||||||
|
this.pruningService.startPruning();
|
||||||
} else {
|
} else {
|
||||||
if (this.pruningService.isPruningEnabled()) {
|
this.pruningService.stopPruning();
|
||||||
this.pruningService.stopPruning();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.addListener('leadershipVacant', () => {
|
||||||
|
this.pruningService.stopPruning();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async catch(error: Error) {
|
async catch(error: Error) {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { Get, RestController } from '@/decorators';
|
import { Get, RestController } from '@/decorators';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
|
|
||||||
@RestController('/debug')
|
@RestController('/debug')
|
||||||
export class DebugController {
|
export class DebugController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly multiMainSetup: MultiMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
||||||
private readonly workflowRepository: WorkflowRepository,
|
private readonly workflowRepository: WorkflowRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/multi-main-setup')
|
@Get('/multi-main-setup')
|
||||||
async getMultiMainSetupDetails() {
|
async getMultiMainSetupDetails() {
|
||||||
const leaderKey = await this.multiMainSetup.fetchLeaderKey();
|
const leaderKey = await this.orchestrationService.multiMainSetup.fetchLeaderKey();
|
||||||
|
|
||||||
const triggersAndPollers = await this.workflowRepository.findIn(
|
const triggersAndPollers = await this.workflowRepository.findIn(
|
||||||
this.activeWorkflowRunner.allActiveInMemory(),
|
this.activeWorkflowRunner.allActiveInMemory(),
|
||||||
|
@ -24,9 +24,9 @@ export class DebugController {
|
||||||
const activationErrors = await this.activeWorkflowRunner.getAllWorkflowActivationErrors();
|
const activationErrors = await this.activeWorkflowRunner.getAllWorkflowActivationErrors();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instanceId: this.multiMainSetup.instanceId,
|
instanceId: this.orchestrationService.instanceId,
|
||||||
leaderKey,
|
leaderKey,
|
||||||
isLeader: this.multiMainSetup.isLeader,
|
isLeader: this.orchestrationService.isLeader,
|
||||||
activeWorkflows: {
|
activeWorkflows: {
|
||||||
webhooks, // webhook-based active workflows
|
webhooks, // webhook-based active workflows
|
||||||
triggersAndPollers, // poller- and trigger-based active workflows
|
triggersAndPollers, // poller- and trigger-based active workflows
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Authorized, Post, RestController, RequireGlobalScope } from '@/decorators';
|
import { Authorized, Post, RestController, RequireGlobalScope } from '@/decorators';
|
||||||
import { OrchestrationRequest } from '@/requests';
|
import { OrchestrationRequest } from '@/requests';
|
||||||
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
|
|
||||||
@Authorized()
|
@Authorized()
|
||||||
@RestController('/orchestration')
|
@RestController('/orchestration')
|
||||||
export class OrchestrationController {
|
export class OrchestrationController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly singleMainSetup: SingleMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly licenseService: License,
|
private readonly licenseService: License,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -20,20 +20,20 @@ export class OrchestrationController {
|
||||||
async getWorkersStatus(req: OrchestrationRequest.Get) {
|
async getWorkersStatus(req: OrchestrationRequest.Get) {
|
||||||
if (!this.licenseService.isWorkerViewLicensed()) return;
|
if (!this.licenseService.isWorkerViewLicensed()) return;
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
return await this.singleMainSetup.getWorkerStatus(id);
|
return await this.orchestrationService.getWorkerStatus(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireGlobalScope('orchestration:read')
|
@RequireGlobalScope('orchestration:read')
|
||||||
@Post('/worker/status')
|
@Post('/worker/status')
|
||||||
async getWorkersStatusAll() {
|
async getWorkersStatusAll() {
|
||||||
if (!this.licenseService.isWorkerViewLicensed()) return;
|
if (!this.licenseService.isWorkerViewLicensed()) return;
|
||||||
return await this.singleMainSetup.getWorkerStatus();
|
return await this.orchestrationService.getWorkerStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequireGlobalScope('orchestration:list')
|
@RequireGlobalScope('orchestration:list')
|
||||||
@Post('/worker/ids')
|
@Post('/worker/ids')
|
||||||
async getWorkerIdsAll() {
|
async getWorkerIdsAll() {
|
||||||
if (!this.licenseService.isWorkerViewLicensed()) return;
|
if (!this.licenseService.isWorkerViewLicensed()) return;
|
||||||
return await this.singleMainSetup.getWorkerIds();
|
return await this.orchestrationService.getWorkerIds();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { ExecutionRepository } from '@db/repositories/execution.repository';
|
||||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||||
import type { AbstractEventMessageOptions } from '../EventMessageClasses/AbstractEventMessageOptions';
|
import type { AbstractEventMessageOptions } from '../EventMessageClasses/AbstractEventMessageOptions';
|
||||||
import { getEventMessageObjectByType } from '../EventMessageClasses/Helpers';
|
import { getEventMessageObjectByType } from '../EventMessageClasses/Helpers';
|
||||||
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { EventDestinationsRepository } from '@db/repositories/eventDestinations.repository';
|
import { EventDestinationsRepository } from '@db/repositories/eventDestinations.repository';
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ export class MessageEventBus extends EventEmitter {
|
||||||
this.destinations[destination.getId()] = destination;
|
this.destinations[destination.getId()] = destination;
|
||||||
this.destinations[destination.getId()].startListening();
|
this.destinations[destination.getId()].startListening();
|
||||||
if (notifyWorkers) {
|
if (notifyWorkers) {
|
||||||
await Container.get(SingleMainSetup).broadcastRestartEventbusAfterDestinationUpdate();
|
await Container.get(OrchestrationService).publish('restartEventBus');
|
||||||
}
|
}
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ export class MessageEventBus extends EventEmitter {
|
||||||
delete this.destinations[id];
|
delete this.destinations[id];
|
||||||
}
|
}
|
||||||
if (notifyWorkers) {
|
if (notifyWorkers) {
|
||||||
await Container.get(SingleMainSetup).broadcastRestartEventbusAfterDestinationUpdate();
|
await Container.get(OrchestrationService).publish('restartEventBus');
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { assert, jsonStringify } from 'n8n-workflow';
|
||||||
import type { IPushDataType } from '@/Interfaces';
|
import type { IPushDataType } from '@/Interfaces';
|
||||||
import type { Logger } from '@/Logger';
|
import type { Logger } from '@/Logger';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import type { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import type { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class for two-way push communication.
|
* Abstract class for two-way push communication.
|
||||||
|
@ -21,7 +21,7 @@ export abstract class AbstractPush<T> extends EventEmitter {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly logger: Logger,
|
protected readonly logger: Logger,
|
||||||
private readonly multiMainSetup: MultiMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -84,10 +84,10 @@ export abstract class AbstractPush<T> extends EventEmitter {
|
||||||
* the webhook. If so, the handler process commands the creator process to
|
* the webhook. If so, the handler process commands the creator process to
|
||||||
* relay the former's execution lifecyle events to the creator's frontend.
|
* relay the former's execution lifecyle events to the creator's frontend.
|
||||||
*/
|
*/
|
||||||
if (this.multiMainSetup.isEnabled && !this.hasSessionId(sessionId)) {
|
if (this.orchestrationService.isMultiMainSetupEnabled && !this.hasSessionId(sessionId)) {
|
||||||
const payload = { type, args: data, sessionId };
|
const payload = { type, args: data, sessionId };
|
||||||
|
|
||||||
void this.multiMainSetup.publish('relay-execution-lifecycle-event', payload);
|
void this.orchestrationService.publish('relay-execution-lifecycle-event', payload);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Logger } from '@/Logger';
|
||||||
import { AbstractPush } from './abstract.push';
|
import { AbstractPush } from './abstract.push';
|
||||||
import type { PushRequest, PushResponse } from './types';
|
import type { PushRequest, PushResponse } from './types';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
type Connection = { req: PushRequest; res: PushResponse };
|
type Connection = { req: PushRequest; res: PushResponse };
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ export class SSEPush extends AbstractPush<Connection> {
|
||||||
|
|
||||||
readonly connections: Record<string, Connection> = {};
|
readonly connections: Record<string, Connection> = {};
|
||||||
|
|
||||||
constructor(logger: Logger, multiMainSetup: MultiMainSetup) {
|
constructor(logger: Logger, orchestrationService: OrchestrationService) {
|
||||||
super(logger, multiMainSetup);
|
super(logger, orchestrationService);
|
||||||
|
|
||||||
this.channel.on('disconnect', (channel, { req }) => {
|
this.channel.on('disconnect', (channel, { req }) => {
|
||||||
this.remove(req?.query?.sessionId);
|
this.remove(req?.query?.sessionId);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Service } from 'typedi';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { AbstractPush } from './abstract.push';
|
import { AbstractPush } from './abstract.push';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
function heartbeat(this: WebSocket) {
|
function heartbeat(this: WebSocket) {
|
||||||
this.isAlive = true;
|
this.isAlive = true;
|
||||||
|
@ -11,8 +11,8 @@ function heartbeat(this: WebSocket) {
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class WebSocketPush extends AbstractPush<WebSocket> {
|
export class WebSocketPush extends AbstractPush<WebSocket> {
|
||||||
constructor(logger: Logger, multiMainSetup: MultiMainSetup) {
|
constructor(logger: Logger, orchestrationService: OrchestrationService) {
|
||||||
super(logger, multiMainSetup);
|
super(logger, orchestrationService);
|
||||||
|
|
||||||
// Ping all connected clients every 60 seconds
|
// Ping all connected clients every 60 seconds
|
||||||
setInterval(() => this.pingAll(), 60 * 1000);
|
setInterval(() => this.pingAll(), 60 * 1000);
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
import Container from 'typedi';
|
|
||||||
import { RedisService } from './redis.service';
|
|
||||||
import type { RedisServicePubSubPublisher } from './redis/RedisServicePubSubPublisher';
|
|
||||||
import config from '@/config';
|
|
||||||
import { EventEmitter } from 'node:events';
|
|
||||||
|
|
||||||
export abstract class OrchestrationService extends EventEmitter {
|
|
||||||
protected isInitialized = false;
|
|
||||||
|
|
||||||
protected queueModeId: string;
|
|
||||||
|
|
||||||
redisPublisher: RedisServicePubSubPublisher;
|
|
||||||
|
|
||||||
readonly redisService: RedisService;
|
|
||||||
|
|
||||||
get isQueueMode(): boolean {
|
|
||||||
return config.get('executions.mode') === 'queue';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isMainInstance(): boolean {
|
|
||||||
return config.get('generic.instanceType') === 'main';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isWebhookInstance(): boolean {
|
|
||||||
return config.get('generic.instanceType') === 'webhook';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isWorkerInstance(): boolean {
|
|
||||||
return config.get('generic.instanceType') === 'worker';
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.redisService = Container.get(RedisService);
|
|
||||||
this.queueModeId = config.getEnv('redis.queueModeId');
|
|
||||||
}
|
|
||||||
|
|
||||||
sanityCheck(): boolean {
|
|
||||||
return this.isInitialized && this.isQueueMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
await this.initPublisher();
|
|
||||||
this.isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async shutdown() {
|
|
||||||
await this.redisPublisher?.destroy();
|
|
||||||
this.isInitialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async initPublisher() {
|
|
||||||
this.redisPublisher = await this.redisService.getPubSubPublisher();
|
|
||||||
}
|
|
||||||
}
|
|
121
packages/cli/src/services/orchestration.service.ts
Normal file
121
packages/cli/src/services/orchestration.service.ts
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import { Logger } from '@/Logger';
|
||||||
|
import config from '@/config';
|
||||||
|
import type { RedisServicePubSubPublisher } from './redis/RedisServicePubSubPublisher';
|
||||||
|
import type { RedisServiceBaseCommand, RedisServiceCommand } from './redis/RedisServiceCommands';
|
||||||
|
|
||||||
|
import { RedisService } from './redis.service';
|
||||||
|
import { MultiMainSetup } from './orchestration/main/MultiMainSetup.ee';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class OrchestrationService {
|
||||||
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly redisService: RedisService,
|
||||||
|
readonly multiMainSetup: MultiMainSetup,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
protected isInitialized = false;
|
||||||
|
|
||||||
|
private isMultiMainSetupLicensed = false;
|
||||||
|
|
||||||
|
setMultiMainSetupLicensed(newState: boolean) {
|
||||||
|
this.isMultiMainSetupLicensed = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMultiMainSetupEnabled() {
|
||||||
|
return (
|
||||||
|
config.getEnv('executions.mode') === 'queue' &&
|
||||||
|
config.getEnv('multiMainSetup.enabled') &&
|
||||||
|
config.getEnv('generic.instanceType') === 'main' &&
|
||||||
|
this.isMultiMainSetupLicensed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
redisPublisher: RedisServicePubSubPublisher;
|
||||||
|
|
||||||
|
get instanceId() {
|
||||||
|
return config.getEnv('redis.queueModeId');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLeader() {
|
||||||
|
return config.getEnv('multiMainSetup.instanceType') === 'leader';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFollower() {
|
||||||
|
return config.getEnv('multiMainSetup.instanceType') !== 'leader';
|
||||||
|
}
|
||||||
|
|
||||||
|
sanityCheck() {
|
||||||
|
return this.isInitialized && config.get('executions.mode') === 'queue';
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (this.isInitialized) return;
|
||||||
|
|
||||||
|
if (config.get('executions.mode') === 'queue') await this.initPublisher();
|
||||||
|
|
||||||
|
if (this.isMultiMainSetupEnabled) {
|
||||||
|
await this.multiMainSetup.init();
|
||||||
|
} else {
|
||||||
|
config.set('multiMainSetup.instanceType', 'leader');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async shutdown() {
|
||||||
|
if (!this.isInitialized) return;
|
||||||
|
|
||||||
|
if (this.isMultiMainSetupEnabled) await this.multiMainSetup.shutdown();
|
||||||
|
|
||||||
|
await this.redisPublisher.destroy();
|
||||||
|
|
||||||
|
this.isInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// pubsub
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
protected async initPublisher() {
|
||||||
|
this.redisPublisher = await this.redisService.getPubSubPublisher();
|
||||||
|
}
|
||||||
|
|
||||||
|
async publish(command: RedisServiceCommand, data?: unknown) {
|
||||||
|
if (!this.sanityCheck()) return;
|
||||||
|
|
||||||
|
const payload = data as RedisServiceBaseCommand['payload'];
|
||||||
|
|
||||||
|
this.logger.debug(`[Instance ID ${this.instanceId}] Publishing command "${command}"`, payload);
|
||||||
|
|
||||||
|
await this.redisPublisher.publishToCommandChannel({ command, payload });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// workers status
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
async getWorkerStatus(id?: string) {
|
||||||
|
if (!this.sanityCheck()) return;
|
||||||
|
|
||||||
|
const command = 'getStatus';
|
||||||
|
|
||||||
|
this.logger.debug(`Sending "${command}" to command channel`);
|
||||||
|
|
||||||
|
await this.redisPublisher.publishToCommandChannel({
|
||||||
|
command,
|
||||||
|
targets: id ? [id] : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkerIds() {
|
||||||
|
if (!this.sanityCheck()) return;
|
||||||
|
|
||||||
|
const command = 'getId';
|
||||||
|
|
||||||
|
this.logger.debug(`Sending "${command}" to command channel`);
|
||||||
|
|
||||||
|
await this.redisPublisher.publishToCommandChannel({ command });
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +1,23 @@
|
||||||
|
import { EventEmitter } from 'node:events';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import { TIME } from '@/constants';
|
import { TIME } from '@/constants';
|
||||||
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
|
||||||
import { getRedisPrefix } from '@/services/redis/RedisServiceHelper';
|
import { getRedisPrefix } from '@/services/redis/RedisServiceHelper';
|
||||||
import { ErrorReporterProxy as EventReporter } from 'n8n-workflow';
|
import { ErrorReporterProxy as EventReporter } from 'n8n-workflow';
|
||||||
import type {
|
import { Logger } from '@/Logger';
|
||||||
RedisServiceBaseCommand,
|
import { RedisServicePubSubPublisher } from '@/services/redis/RedisServicePubSubPublisher';
|
||||||
RedisServiceCommand,
|
|
||||||
} from '@/services/redis/RedisServiceCommands';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class MultiMainSetup extends SingleMainSetup {
|
export class MultiMainSetup extends EventEmitter {
|
||||||
private id = this.queueModeId;
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
private isLicensed = false;
|
private readonly redisPublisher: RedisServicePubSubPublisher,
|
||||||
|
) {
|
||||||
get isEnabled() {
|
super();
|
||||||
return (
|
|
||||||
config.getEnv('executions.mode') === 'queue' &&
|
|
||||||
config.getEnv('multiMainSetup.enabled') &&
|
|
||||||
config.getEnv('generic.instanceType') === 'main' &&
|
|
||||||
this.isLicensed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isLeader() {
|
|
||||||
return config.getEnv('multiMainSetup.instanceType') === 'leader';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isFollower() {
|
|
||||||
return !this.isLeader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get instanceId() {
|
get instanceId() {
|
||||||
return this.id;
|
return config.getEnv('redis.queueModeId');
|
||||||
}
|
|
||||||
|
|
||||||
setLicensed(newState: boolean) {
|
|
||||||
this.isLicensed = newState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly leaderKey = getRedisPrefix() + ':main_instance_leader';
|
private readonly leaderKey = getRedisPrefix() + ':main_instance_leader';
|
||||||
|
@ -47,12 +27,6 @@ export class MultiMainSetup extends SingleMainSetup {
|
||||||
private leaderCheckInterval: NodeJS.Timer | undefined;
|
private leaderCheckInterval: NodeJS.Timer | undefined;
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
if (!this.isEnabled || this.isInitialized) return;
|
|
||||||
|
|
||||||
await this.initPublisher();
|
|
||||||
|
|
||||||
this.isInitialized = true;
|
|
||||||
|
|
||||||
await this.tryBecomeLeader(); // prevent initial wait
|
await this.tryBecomeLeader(); // prevent initial wait
|
||||||
|
|
||||||
this.leaderCheckInterval = setInterval(
|
this.leaderCheckInterval = setInterval(
|
||||||
|
@ -64,35 +38,35 @@ export class MultiMainSetup extends SingleMainSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
async shutdown() {
|
async shutdown() {
|
||||||
if (!this.isInitialized) return;
|
|
||||||
|
|
||||||
clearInterval(this.leaderCheckInterval);
|
clearInterval(this.leaderCheckInterval);
|
||||||
|
|
||||||
if (this.isLeader) await this.redisPublisher.clear(this.leaderKey);
|
const isLeader = config.getEnv('multiMainSetup.instanceType') === 'leader';
|
||||||
|
|
||||||
|
if (isLeader) await this.redisPublisher.clear(this.leaderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkLeader() {
|
private async checkLeader() {
|
||||||
const leaderId = await this.redisPublisher.get(this.leaderKey);
|
const leaderId = await this.redisPublisher.get(this.leaderKey);
|
||||||
|
|
||||||
if (leaderId === this.id) {
|
if (leaderId === this.instanceId) {
|
||||||
this.logger.debug(`[Instance ID ${this.id}] Leader is this instance`);
|
this.logger.debug(`[Instance ID ${this.instanceId}] Leader is this instance`);
|
||||||
|
|
||||||
await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
|
await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leaderId && leaderId !== this.id) {
|
if (leaderId && leaderId !== this.instanceId) {
|
||||||
this.logger.debug(`[Instance ID ${this.id}] Leader is other instance "${leaderId}"`);
|
this.logger.debug(`[Instance ID ${this.instanceId}] Leader is other instance "${leaderId}"`);
|
||||||
|
|
||||||
if (config.getEnv('multiMainSetup.instanceType') === 'leader') {
|
if (config.getEnv('multiMainSetup.instanceType') === 'leader') {
|
||||||
this.emit('leadershipChange', leaderId); // stop triggers, pruning, etc.
|
config.set('multiMainSetup.instanceType', 'follower');
|
||||||
|
|
||||||
|
this.emit('leadershipChange'); // stop triggers, pollers, pruning
|
||||||
|
|
||||||
EventReporter.report('[Multi-main setup] Leader failed to renew leader key', {
|
EventReporter.report('[Multi-main setup] Leader failed to renew leader key', {
|
||||||
level: 'info',
|
level: 'info',
|
||||||
});
|
});
|
||||||
|
|
||||||
config.set('multiMainSetup.instanceType', 'follower');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -100,42 +74,37 @@ export class MultiMainSetup extends SingleMainSetup {
|
||||||
|
|
||||||
if (!leaderId) {
|
if (!leaderId) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[Instance ID ${this.id}] Leadership vacant, attempting to become leader...`,
|
`[Instance ID ${this.instanceId}] Leadership vacant, attempting to become leader...`,
|
||||||
);
|
);
|
||||||
|
|
||||||
config.set('multiMainSetup.instanceType', 'follower');
|
config.set('multiMainSetup.instanceType', 'follower');
|
||||||
|
|
||||||
|
this.emit('leadershipVacant'); // stop triggers, pollers, pruning
|
||||||
|
|
||||||
await this.tryBecomeLeader();
|
await this.tryBecomeLeader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async tryBecomeLeader() {
|
private async tryBecomeLeader() {
|
||||||
// this can only succeed if leadership is currently vacant
|
// this can only succeed if leadership is currently vacant
|
||||||
const keySetSuccessfully = await this.redisPublisher.setIfNotExists(this.leaderKey, this.id);
|
const keySetSuccessfully = await this.redisPublisher.setIfNotExists(
|
||||||
|
this.leaderKey,
|
||||||
|
this.instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
if (keySetSuccessfully) {
|
if (keySetSuccessfully) {
|
||||||
this.logger.debug(`[Instance ID ${this.id}] Leader is now this instance`);
|
this.logger.debug(`[Instance ID ${this.instanceId}] Leader is now this instance`);
|
||||||
|
|
||||||
config.set('multiMainSetup.instanceType', 'leader');
|
config.set('multiMainSetup.instanceType', 'leader');
|
||||||
|
|
||||||
await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
|
await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
|
||||||
|
|
||||||
this.emit('leadershipChange', this.id);
|
this.emit('leadershipChange'); // start triggers, pollers, pruning
|
||||||
} else {
|
} else {
|
||||||
config.set('multiMainSetup.instanceType', 'follower');
|
config.set('multiMainSetup.instanceType', 'follower');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish(command: RedisServiceCommand, data: unknown) {
|
|
||||||
if (!this.sanityCheck()) return;
|
|
||||||
|
|
||||||
const payload = data as RedisServiceBaseCommand['payload'];
|
|
||||||
|
|
||||||
this.logger.debug(`[Instance ID ${this.id}] Publishing command "${command}"`, payload);
|
|
||||||
|
|
||||||
await this.redisPublisher.publishToCommandChannel({ command, payload });
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchLeaderKey() {
|
async fetchLeaderKey() {
|
||||||
return await this.redisPublisher.get(this.leaderKey);
|
return await this.redisPublisher.get(this.leaderKey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { Logger } from '@/Logger';
|
|
||||||
import { Service } from 'typedi';
|
|
||||||
import { OrchestrationService } from '@/services/orchestration.base.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For use in main instance, in single main instance scenario.
|
|
||||||
*/
|
|
||||||
@Service()
|
|
||||||
export class SingleMainSetup extends OrchestrationService {
|
|
||||||
constructor(protected readonly logger: Logger) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
sanityCheck() {
|
|
||||||
return this.isInitialized && this.isQueueMode && this.isMainInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWorkerStatus(id?: string) {
|
|
||||||
if (!this.sanityCheck()) return;
|
|
||||||
|
|
||||||
const command = 'getStatus';
|
|
||||||
|
|
||||||
this.logger.debug(`Sending "${command}" to command channel`);
|
|
||||||
|
|
||||||
await this.redisPublisher.publishToCommandChannel({
|
|
||||||
command,
|
|
||||||
targets: id ? [id] : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWorkerIds() {
|
|
||||||
if (!this.sanityCheck()) return;
|
|
||||||
|
|
||||||
const command = 'getId';
|
|
||||||
|
|
||||||
this.logger.debug(`Sending "${command}" to command channel`);
|
|
||||||
|
|
||||||
await this.redisPublisher.publishToCommandChannel({ command });
|
|
||||||
}
|
|
||||||
|
|
||||||
async broadcastRestartEventbusAfterDestinationUpdate() {
|
|
||||||
if (!this.sanityCheck()) return;
|
|
||||||
|
|
||||||
const command = 'restartEventBus';
|
|
||||||
|
|
||||||
this.logger.debug(`Sending "${command}" to command channel`);
|
|
||||||
|
|
||||||
await this.redisPublisher.publishToCommandChannel({ command });
|
|
||||||
}
|
|
||||||
|
|
||||||
async broadcastReloadExternalSecretsProviders() {
|
|
||||||
if (!this.sanityCheck()) return;
|
|
||||||
|
|
||||||
const command = 'reloadExternalSecretsProviders';
|
|
||||||
|
|
||||||
this.logger.debug(`Sending "${command}" to command channel`);
|
|
||||||
|
|
||||||
await this.redisPublisher.publishToCommandChannel({ command });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ import { License } from '@/License';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { Push } from '@/push';
|
import { Push } from '@/push';
|
||||||
import { MultiMainSetup } from './MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||||
import { TestWebhooks } from '@/TestWebhooks';
|
import { TestWebhooks } from '@/TestWebhooks';
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ export async function handleCommandMessageMain(messageString: string) {
|
||||||
versionId,
|
versionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await Container.get(MultiMainSetup).publish('workflowFailedToActivate', {
|
await Container.get(OrchestrationService).publish('workflowFailedToActivate', {
|
||||||
workflowId,
|
workflowId,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import { OrchestrationService } from '../../orchestration.base.service';
|
import { OrchestrationService } from '../../orchestration.service';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class OrchestrationWebhookService extends OrchestrationService {
|
export class OrchestrationWebhookService extends OrchestrationService {
|
||||||
sanityCheck(): boolean {
|
sanityCheck(): boolean {
|
||||||
return this.isInitialized && this.isQueueMode && this.isWebhookInstance;
|
return (
|
||||||
|
this.isInitialized &&
|
||||||
|
config.get('executions.mode') === 'queue' &&
|
||||||
|
config.get('generic.instanceType') === 'webhook'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import type { AbstractEventMessage } from '@/eventbus/EventMessageClasses/AbstractEventMessage';
|
import type { AbstractEventMessage } from '@/eventbus/EventMessageClasses/AbstractEventMessage';
|
||||||
import { OrchestrationService } from '../../orchestration.base.service';
|
import { OrchestrationService } from '../../orchestration.service';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class OrchestrationWorkerService extends OrchestrationService {
|
export class OrchestrationWorkerService extends OrchestrationService {
|
||||||
sanityCheck(): boolean {
|
sanityCheck(): boolean {
|
||||||
return this.isInitialized && this.isQueueMode && this.isWorkerInstance;
|
return (
|
||||||
|
this.isInitialized &&
|
||||||
|
config.get('executions.mode') === 'queue' &&
|
||||||
|
config.get('generic.instanceType') === 'worker'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async publishToEventLog(message: AbstractEventMessage) {
|
async publishToEventLog(message: AbstractEventMessage) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class PruningService {
|
||||||
private readonly binaryDataService: BinaryDataService,
|
private readonly binaryDataService: BinaryDataService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
isPruningEnabled() {
|
private isPruningEnabled() {
|
||||||
if (
|
if (
|
||||||
!config.getEnv('executions.pruneData') ||
|
!config.getEnv('executions.pruneData') ||
|
||||||
inTest ||
|
inTest ||
|
||||||
|
@ -52,6 +52,8 @@ export class PruningService {
|
||||||
* @important Call this method only after DB migrations have completed.
|
* @important Call this method only after DB migrations have completed.
|
||||||
*/
|
*/
|
||||||
startPruning() {
|
startPruning() {
|
||||||
|
if (!this.isPruningEnabled()) return;
|
||||||
|
|
||||||
if (this.isShuttingDown) {
|
if (this.isShuttingDown) {
|
||||||
this.logger.warn('[Pruning] Cannot start pruning while shutting down');
|
this.logger.warn('[Pruning] Cannot start pruning while shutting down');
|
||||||
return;
|
return;
|
||||||
|
@ -64,6 +66,8 @@ export class PruningService {
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPruning() {
|
stopPruning() {
|
||||||
|
if (!this.isPruningEnabled()) return;
|
||||||
|
|
||||||
this.logger.debug('[Pruning] Removing soft-deletion and hard-deletion timers');
|
this.logger.debug('[Pruning] Removing soft-deletion and hard-deletion timers');
|
||||||
|
|
||||||
clearInterval(this.softDeletionInterval);
|
clearInterval(this.softDeletionInterval);
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { InternalHooks } from '@/InternalHooks';
|
||||||
import { OwnershipService } from '@/services/ownership.service';
|
import { OwnershipService } from '@/services/ownership.service';
|
||||||
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
|
import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export class WorkflowService {
|
||||||
private readonly ownershipService: OwnershipService,
|
private readonly ownershipService: OwnershipService,
|
||||||
private readonly tagService: TagService,
|
private readonly tagService: TagService,
|
||||||
private readonly workflowHistoryService: WorkflowHistoryService,
|
private readonly workflowHistoryService: WorkflowHistoryService,
|
||||||
private readonly multiMainSetup: MultiMainSetup,
|
private readonly orchestrationService: OrchestrationService,
|
||||||
private readonly externalHooks: ExternalHooks,
|
private readonly externalHooks: ExternalHooks,
|
||||||
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
private readonly activeWorkflowRunner: ActiveWorkflowRunner,
|
||||||
) {}
|
) {}
|
||||||
|
@ -227,12 +227,12 @@ export class WorkflowService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.multiMainSetup.init();
|
await this.orchestrationService.init();
|
||||||
|
|
||||||
const newState = updatedWorkflow.active;
|
const newState = updatedWorkflow.active;
|
||||||
|
|
||||||
if (this.multiMainSetup.isEnabled && oldState !== newState) {
|
if (this.orchestrationService.isMultiMainSetupEnabled && oldState !== newState) {
|
||||||
await this.multiMainSetup.publish('workflowActiveStateChanged', {
|
await this.orchestrationService.publish('workflowActiveStateChanged', {
|
||||||
workflowId,
|
workflowId,
|
||||||
oldState,
|
oldState,
|
||||||
newState,
|
newState,
|
||||||
|
|
|
@ -17,7 +17,7 @@ import type { User } from '@db/entities/User';
|
||||||
import type { WebhookEntity } from '@db/entities/WebhookEntity';
|
import type { WebhookEntity } from '@db/entities/WebhookEntity';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { chooseRandomly } from './shared/random';
|
import { chooseRandomly } from './shared/random';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
import { setSchedulerAsLoadedNode } from './shared/utils';
|
import { setSchedulerAsLoadedNode } from './shared/utils';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
|
@ -34,8 +34,8 @@ mockInstance(ExecutionService);
|
||||||
mockInstance(WorkflowService);
|
mockInstance(WorkflowService);
|
||||||
|
|
||||||
const webhookService = mockInstance(WebhookService);
|
const webhookService = mockInstance(WebhookService);
|
||||||
const multiMainSetup = mockInstance(MultiMainSetup, {
|
const orchestrationService = mockInstance(OrchestrationService, {
|
||||||
isEnabled: false,
|
isMultiMainSetupEnabled: false,
|
||||||
isLeader: false,
|
isLeader: false,
|
||||||
isFollower: false,
|
isFollower: false,
|
||||||
});
|
});
|
||||||
|
@ -266,8 +266,8 @@ describe('add()', () => {
|
||||||
|
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
const workflow = await createWorkflow({ active: true }, owner);
|
||||||
|
|
||||||
jest.replaceProperty(multiMainSetup, 'isEnabled', true);
|
jest.replaceProperty(orchestrationService, 'isMultiMainSetupEnabled', true);
|
||||||
jest.replaceProperty(multiMainSetup, 'isLeader', true);
|
jest.replaceProperty(orchestrationService, 'isLeader', true);
|
||||||
|
|
||||||
const addWebhooksSpy = jest.spyOn(activeWorkflowRunner, 'addWebhooks');
|
const addWebhooksSpy = jest.spyOn(activeWorkflowRunner, 'addWebhooks');
|
||||||
const addTriggersAndPollersSpy = jest.spyOn(
|
const addTriggersAndPollersSpy = jest.spyOn(
|
||||||
|
@ -290,8 +290,8 @@ describe('add()', () => {
|
||||||
test('should add triggers and pollers only', async () => {
|
test('should add triggers and pollers only', async () => {
|
||||||
const mode = 'leadershipChange';
|
const mode = 'leadershipChange';
|
||||||
|
|
||||||
jest.replaceProperty(multiMainSetup, 'isEnabled', true);
|
jest.replaceProperty(orchestrationService, 'isMultiMainSetupEnabled', true);
|
||||||
jest.replaceProperty(multiMainSetup, 'isLeader', true);
|
jest.replaceProperty(orchestrationService, 'isLeader', true);
|
||||||
|
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
const workflow = await createWorkflow({ active: true }, owner);
|
||||||
|
|
||||||
|
@ -318,8 +318,8 @@ describe('add()', () => {
|
||||||
test('should not add webhooks, triggers or pollers', async () => {
|
test('should not add webhooks, triggers or pollers', async () => {
|
||||||
const mode = chooseRandomly(NON_LEADERSHIP_CHANGE_MODES);
|
const mode = chooseRandomly(NON_LEADERSHIP_CHANGE_MODES);
|
||||||
|
|
||||||
jest.replaceProperty(multiMainSetup, 'isEnabled', true);
|
jest.replaceProperty(orchestrationService, 'isMultiMainSetupEnabled', true);
|
||||||
jest.replaceProperty(multiMainSetup, 'isLeader', false);
|
jest.replaceProperty(orchestrationService, 'isLeader', false);
|
||||||
|
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
const workflow = await createWorkflow({ active: true }, owner);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { PostHogClient } from '@/posthog';
|
||||||
import { RedisService } from '@/services/redis.service';
|
import { RedisService } from '@/services/redis.service';
|
||||||
import { OrchestrationHandlerWorkerService } from '@/services/orchestration/worker/orchestration.handler.worker.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 { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
import { mockInstance } from '../../shared/mocking';
|
import { mockInstance } from '../../shared/mocking';
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ beforeAll(async () => {
|
||||||
mockInstance(RedisService);
|
mockInstance(RedisService);
|
||||||
mockInstance(RedisServicePubSubPublisher);
|
mockInstance(RedisServicePubSubPublisher);
|
||||||
mockInstance(RedisServicePubSubSubscriber);
|
mockInstance(RedisServicePubSubSubscriber);
|
||||||
mockInstance(MultiMainSetup);
|
mockInstance(OrchestrationService);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('worker initializes all its components', async () => {
|
test('worker initializes all its components', async () => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
||||||
import { setupTestServer } from './shared/utils';
|
import { setupTestServer } from './shared/utils';
|
||||||
import type { SuperAgentTest } from 'supertest';
|
import type { SuperAgentTest } from 'supertest';
|
||||||
import { createOwner } from './shared/db/users';
|
import { createOwner } from './shared/db/users';
|
||||||
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
||||||
|
|
||||||
describe('DebugController', () => {
|
describe('DebugController', () => {
|
||||||
|
@ -36,9 +37,9 @@ describe('DebugController', () => {
|
||||||
activeWorkflowRunner.allActiveInMemory.mockReturnValue([workflowId]);
|
activeWorkflowRunner.allActiveInMemory.mockReturnValue([workflowId]);
|
||||||
activeWorkflowRunner.getAllWorkflowActivationErrors.mockResolvedValue(activationErrors);
|
activeWorkflowRunner.getAllWorkflowActivationErrors.mockResolvedValue(activationErrors);
|
||||||
|
|
||||||
jest.spyOn(MultiMainSetup.prototype, 'instanceId', 'get').mockReturnValue(instanceId);
|
jest.spyOn(OrchestrationService.prototype, 'instanceId', 'get').mockReturnValue(instanceId);
|
||||||
jest.spyOn(MultiMainSetup.prototype, 'fetchLeaderKey').mockResolvedValue(leaderKey);
|
jest.spyOn(MultiMainSetup.prototype, 'fetchLeaderKey').mockResolvedValue(leaderKey);
|
||||||
jest.spyOn(MultiMainSetup.prototype, 'isLeader', 'get').mockReturnValue(true);
|
jest.spyOn(OrchestrationService.prototype, 'isLeader', 'get').mockReturnValue(true);
|
||||||
|
|
||||||
const response = await ownerAgent.get('/debug/multi-main-setup').expect(200);
|
const response = await ownerAgent.get('/debug/multi-main-setup').expect(200);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { AUTH_COOKIE_NAME } from '@/constants';
|
||||||
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
|
||||||
import { SettingsRepository } from '@db/repositories/settings.repository';
|
import { SettingsRepository } from '@db/repositories/settings.repository';
|
||||||
import { mockNodeTypesData } from '../../../unit/Helpers';
|
import { mockNodeTypesData } from '../../../unit/Helpers';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { mockInstance } from '../../../shared/mocking';
|
import { mockInstance } from '../../../shared/mocking';
|
||||||
import { ExecutionService } from '@/executions/execution.service';
|
import { ExecutionService } from '@/executions/execution.service';
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export { setupTestServer } from './testServer';
|
||||||
* Initialize node types.
|
* Initialize node types.
|
||||||
*/
|
*/
|
||||||
export async function initActiveWorkflowRunner() {
|
export async function initActiveWorkflowRunner() {
|
||||||
mockInstance(MultiMainSetup);
|
mockInstance(OrchestrationService);
|
||||||
|
|
||||||
mockInstance(ExecutionService);
|
mockInstance(ExecutionService);
|
||||||
const { ActiveWorkflowRunner } = await import('@/ActiveWorkflowRunner');
|
const { ActiveWorkflowRunner } = await import('@/ActiveWorkflowRunner');
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
|
||||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||||
import { Telemetry } from '@/telemetry';
|
import { Telemetry } from '@/telemetry';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import { WorkflowService } from '@/workflows/workflow.service';
|
import { WorkflowService } from '@/workflows/workflow.service';
|
||||||
|
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
|
@ -14,13 +14,13 @@ import { createWorkflow } from '../shared/db/workflows';
|
||||||
|
|
||||||
let workflowService: WorkflowService;
|
let workflowService: WorkflowService;
|
||||||
let activeWorkflowRunner: ActiveWorkflowRunner;
|
let activeWorkflowRunner: ActiveWorkflowRunner;
|
||||||
let multiMainSetup: MultiMainSetup;
|
let orchestrationService: OrchestrationService;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await testDb.init();
|
await testDb.init();
|
||||||
|
|
||||||
activeWorkflowRunner = mockInstance(ActiveWorkflowRunner);
|
activeWorkflowRunner = mockInstance(ActiveWorkflowRunner);
|
||||||
multiMainSetup = mockInstance(MultiMainSetup);
|
orchestrationService = mockInstance(OrchestrationService);
|
||||||
mockInstance(Telemetry);
|
mockInstance(Telemetry);
|
||||||
|
|
||||||
workflowService = new WorkflowService(
|
workflowService = new WorkflowService(
|
||||||
|
@ -33,7 +33,7 @@ beforeAll(async () => {
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
mock(),
|
||||||
multiMainSetup,
|
orchestrationService,
|
||||||
mock(),
|
mock(),
|
||||||
activeWorkflowRunner,
|
activeWorkflowRunner,
|
||||||
);
|
);
|
||||||
|
@ -89,7 +89,7 @@ describe('update()', () => {
|
||||||
const owner = await createOwner();
|
const owner = await createOwner();
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
const workflow = await createWorkflow({ active: true }, owner);
|
||||||
|
|
||||||
const publishSpy = jest.spyOn(multiMainSetup, 'publish');
|
const publishSpy = jest.spyOn(orchestrationService, 'publish');
|
||||||
|
|
||||||
workflow.active = false;
|
workflow.active = false;
|
||||||
await workflowService.update(owner, workflow, workflow.id);
|
await workflowService.update(owner, workflow, workflow.id);
|
||||||
|
@ -109,7 +109,7 @@ describe('update()', () => {
|
||||||
const owner = await createOwner();
|
const owner = await createOwner();
|
||||||
const workflow = await createWorkflow({ active: true }, owner);
|
const workflow = await createWorkflow({ active: true }, owner);
|
||||||
|
|
||||||
const publishSpy = jest.spyOn(multiMainSetup, 'publish');
|
const publishSpy = jest.spyOn(orchestrationService, 'publish');
|
||||||
|
|
||||||
await workflowService.update(owner, workflow, workflow.id);
|
await workflowService.update(owner, workflow, workflow.id);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { License } from '@/License';
|
||||||
import { Logger } from '@/Logger';
|
import { Logger } from '@/Logger';
|
||||||
import { N8N_VERSION } from '@/constants';
|
import { N8N_VERSION } from '@/constants';
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
import { MultiMainSetup } from '@/services/orchestration/main/MultiMainSetup.ee';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
|
|
||||||
jest.mock('@n8n_io/license-sdk');
|
jest.mock('@n8n_io/license-sdk');
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ describe('License', () => {
|
||||||
let license: License;
|
let license: License;
|
||||||
const logger = mockInstance(Logger);
|
const logger = mockInstance(Logger);
|
||||||
const instanceSettings = mockInstance(InstanceSettings, { instanceId: MOCK_INSTANCE_ID });
|
const instanceSettings = mockInstance(InstanceSettings, { instanceId: MOCK_INSTANCE_ID });
|
||||||
mockInstance(MultiMainSetup);
|
mockInstance(OrchestrationService);
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
license = new License(logger, instanceSettings, mock(), mock(), mock());
|
license = new License(logger, instanceSettings, mock(), mock(), mock());
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { SingleMainSetup } from '@/services/orchestration/main/SingleMainSetup';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import type { RedisServiceWorkerResponseObject } from '@/services/redis/RedisServiceCommands';
|
import type { RedisServiceWorkerResponseObject } from '@/services/redis/RedisServiceCommands';
|
||||||
import { eventBus } from '@/eventbus';
|
import { eventBus } from '@/eventbus';
|
||||||
import { RedisService } from '@/services/redis.service';
|
import { RedisService } from '@/services/redis.service';
|
||||||
|
@ -14,7 +14,7 @@ import { Push } from '@/push';
|
||||||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { mockInstance } from '../../shared/mocking';
|
import { mockInstance } from '../../shared/mocking';
|
||||||
|
|
||||||
const os = Container.get(SingleMainSetup);
|
const os = Container.get(OrchestrationService);
|
||||||
const handler = Container.get(OrchestrationHandlerMainService);
|
const handler = Container.get(OrchestrationHandlerMainService);
|
||||||
mockInstance(ActiveWorkflowRunner);
|
mockInstance(ActiveWorkflowRunner);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue