diff --git a/packages/cli/src/__tests__/license.test.ts b/packages/cli/src/__tests__/license.test.ts index d33d7c37cf..aa0aba1d53 100644 --- a/packages/cli/src/__tests__/license.test.ts +++ b/packages/cli/src/__tests__/license.test.ts @@ -38,7 +38,7 @@ describe('License', () => { license: licenseConfig, multiMainSetup: { enabled: false }, }); - license = new License(mockLogger(), instanceSettings, mock(), mock(), mock(), globalConfig); + license = new License(mockLogger(), instanceSettings, mock(), mock(), globalConfig); await license.init(); }); @@ -70,7 +70,6 @@ describe('License', () => { mock({ instanceType: 'worker' }), mock(), mock(), - mock(), mock({ license: licenseConfig }), ); await license.init(); @@ -211,7 +210,6 @@ describe('License', () => { mock({ instanceType: 'main' }), mock(), mock(), - mock(), globalConfig, ).init(); @@ -229,7 +227,6 @@ describe('License', () => { mock(), mock(), mock(), - mock(), ).init(); expect(LicenseManager).toHaveBeenCalledWith( @@ -250,7 +247,7 @@ describe('License', () => { }); config.set('multiMainSetup.instanceType', status); - await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init(); + await new License(mockLogger(), mock(), mock(), mock(), globalConfig).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), @@ -267,7 +264,7 @@ describe('License', () => { }); config.set('multiMainSetup.instanceType', status); - await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init(); + await new License(mockLogger(), mock(), mock(), mock(), globalConfig).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: false, renewOnInit: false }), @@ -281,7 +278,7 @@ describe('License', () => { }); config.set('multiMainSetup.instanceType', 'leader'); - await new License(mockLogger(), mock(), mock(), mock(), mock(), globalConfig).init(); + await new License(mockLogger(), mock(), mock(), mock(), globalConfig).init(); expect(LicenseManager).toHaveBeenCalledWith( expect.objectContaining({ autoRenewEnabled: true, renewOnInit: true }), @@ -293,7 +290,7 @@ describe('License', () => { describe('reinit', () => { it('should reinitialize license manager', async () => { - const license = new License(mockLogger(), mock(), mock(), mock(), mock(), mock()); + const license = new License(mockLogger(), mock(), mock(), mock(), mock()); await license.init(); const initSpy = jest.spyOn(license, 'init'); diff --git a/packages/cli/src/__tests__/wait-tracker.test.ts b/packages/cli/src/__tests__/wait-tracker.test.ts index ef420e7d78..6721c31bae 100644 --- a/packages/cli/src/__tests__/wait-tracker.test.ts +++ b/packages/cli/src/__tests__/wait-tracker.test.ts @@ -23,7 +23,7 @@ describe('WaitTracker', () => { const executionRepository = mock(); const multiMainSetup = mock(); const orchestrationService = new OrchestrationService(mock(), multiMainSetup, mock()); - const instanceSettings = mock({ isLeader: true }); + const instanceSettings = mock({ isLeader: true, isMultiMain: false }); const project = mock({ id: 'projectId' }); const execution = mock({ @@ -221,8 +221,6 @@ describe('WaitTracker', () => { describe('multi-main setup', () => { it('should start tracking if leader', () => { - jest.spyOn(orchestrationService, 'isSingleMainSetup', 'get').mockReturnValue(false); - executionRepository.getWaitingExecutions.mockResolvedValue([]); waitTracker.init(); @@ -238,9 +236,8 @@ describe('WaitTracker', () => { activeExecutions, workflowRunner, orchestrationService, - mock({ isLeader: false }), + mock({ isLeader: false, isMultiMain: false }), ); - jest.spyOn(orchestrationService, 'isSingleMainSetup', 'get').mockReturnValue(false); executionRepository.getWaitingExecutions.mockResolvedValue([]); diff --git a/packages/cli/src/active-workflow-manager.ts b/packages/cli/src/active-workflow-manager.ts index 1d8d06a857..7e3d045fe4 100644 --- a/packages/cli/src/active-workflow-manager.ts +++ b/packages/cli/src/active-workflow-manager.ts @@ -511,7 +511,7 @@ export class ActiveWorkflowManager { existingWorkflow?: WorkflowEntity, { shouldPublish } = { shouldPublish: true }, ) { - if (this.orchestrationService.isMultiMainSetupEnabled && shouldPublish) { + if (this.instanceSettings.isMultiMain && shouldPublish) { void this.publisher.publishCommand({ command: 'add-webhooks-triggers-and-pollers', payload: { workflowId }, @@ -703,7 +703,7 @@ export class ActiveWorkflowManager { // TODO: this should happen in a transaction // maybe, see: https://github.com/n8n-io/n8n/pull/8904#discussion_r1530150510 async remove(workflowId: string) { - if (this.orchestrationService.isMultiMainSetupEnabled) { + if (this.instanceSettings.isMultiMain) { try { await this.clearWebhooks(workflowId); } catch (error) { diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 42b5df13e6..63ec3d9240 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -100,7 +100,7 @@ export class Start extends BaseCommand { await this.activeWorkflowManager.removeAllTriggerAndPollerBasedWorkflows(); - if (Container.get(OrchestrationService).isMultiMainSetupEnabled) { + if (this.instanceSettings.isMultiMain) { await Container.get(OrchestrationService).shutdown(); } @@ -192,6 +192,9 @@ export class Start extends BaseCommand { await super.init(); this.activeWorkflowManager = Container.get(ActiveWorkflowManager); + this.instanceSettings.setMultiMainEnabled( + config.getEnv('executions.mode') === 'queue' && this.globalConfig.multiMainSetup.enabled, + ); await this.initLicense(); await this.initOrchestration(); @@ -253,7 +256,7 @@ export class Start extends BaseCommand { this.logger.scoped(['scaling', 'pubsub']).debug('Pubsub setup completed'); - if (!orchestrationService.isMultiMainSetupEnabled) return; + if (this.instanceSettings.isSingleMain) return; orchestrationService.multiMainSetup .on('leader-stepdown', async () => { diff --git a/packages/cli/src/license.ts b/packages/cli/src/license.ts index 8f1bd26e64..2a3ae6fd6d 100644 --- a/packages/cli/src/license.ts +++ b/packages/cli/src/license.ts @@ -9,7 +9,6 @@ import { SettingsRepository } from '@/databases/repositories/settings.repository import { OnShutdown } from '@/decorators/on-shutdown'; import { Logger } from '@/logging/logger.service'; import { LicenseMetricsService } from '@/metrics/license-metrics.service'; -import { OrchestrationService } from '@/services/orchestration.service'; import { LICENSE_FEATURES, @@ -35,7 +34,6 @@ export class License { constructor( private readonly logger: Logger, private readonly instanceSettings: InstanceSettings, - private readonly orchestrationService: OrchestrationService, private readonly settingsRepository: SettingsRepository, private readonly licenseMetricsService: LicenseMetricsService, private readonly globalConfig: GlobalConfig, @@ -138,23 +136,24 @@ export class License { this.logger.debug('License feature change detected', _features); if (config.getEnv('executions.mode') === 'queue' && this.globalConfig.multiMainSetup.enabled) { - const isMultiMainLicensed = _features[LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES] as - | boolean - | undefined; + const isMultiMainLicensed = + (_features[LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES] as boolean | undefined) ?? false; - this.orchestrationService.setMultiMainSetupLicensed(isMultiMainLicensed ?? false); + this.instanceSettings.setMultiMainLicensed(isMultiMainLicensed); - if (this.orchestrationService.isMultiMainSetupEnabled && this.instanceSettings.isFollower) { - this.logger.debug( - '[Multi-main setup] Instance is follower, skipping sending of "reload-license" command...', - ); + if (this.instanceSettings.isMultiMain && !this.instanceSettings.isLeader) { + this.logger + .scoped(['scaling', 'multi-main-setup', 'license']) + .debug('Instance is not leader, skipping sending of "reload-license" command...'); return; } - if (this.orchestrationService.isMultiMainSetupEnabled && !isMultiMainLicensed) { - 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 supports this feature.', - ); + if (this.globalConfig.multiMainSetup.enabled && !isMultiMainLicensed) { + this.logger + .scoped(['scaling', 'multi-main-setup', 'license']) + .debug( + '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 supports this feature.', + ); } } diff --git a/packages/cli/src/push/index.ts b/packages/cli/src/push/index.ts index bfbfb43a51..3b29d85242 100644 --- a/packages/cli/src/push/index.ts +++ b/packages/cli/src/push/index.ts @@ -2,6 +2,7 @@ import type { PushPayload, PushType } from '@n8n/api-types'; import type { Application } from 'express'; import { ServerResponse } from 'http'; import type { Server } from 'http'; +import { InstanceSettings } from 'n8n-core'; import type { Socket } from 'net'; import { Container, Service } from 'typedi'; import { parse as parseUrl } from 'url'; @@ -13,7 +14,6 @@ import type { User } from '@/databases/entities/user'; import { OnShutdown } from '@/decorators/on-shutdown'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { Publisher } from '@/scaling/pubsub/publisher.service'; -import { OrchestrationService } from '@/services/orchestration.service'; import { TypedEmitter } from '@/typed-emitter'; import { SSEPush } from './sse.push'; @@ -41,7 +41,7 @@ export class Push extends TypedEmitter { private backend = useWebSockets ? Container.get(WebSocketPush) : Container.get(SSEPush); constructor( - private readonly orchestrationService: OrchestrationService, + private readonly instanceSettings: InstanceSettings, private readonly publisher: Publisher, ) { super(); @@ -92,7 +92,7 @@ export class Push extends TypedEmitter { * the webhook. If so, the handler process commands the creator process to * relay the former's execution lifecycle events to the creator's frontend. */ - if (this.orchestrationService.isMultiMainSetupEnabled && !this.backend.hasPushRef(pushRef)) { + if (this.instanceSettings.isMultiMain && !this.backend.hasPushRef(pushRef)) { void this.publisher.publishCommand({ command: 'relay-execution-lifecycle-event', payload: { type, args: data, pushRef }, diff --git a/packages/cli/src/scaling/__tests__/scaling.service.test.ts b/packages/cli/src/scaling/__tests__/scaling.service.test.ts index dbd914fdee..b400bf6dfb 100644 --- a/packages/cli/src/scaling/__tests__/scaling.service.test.ts +++ b/packages/cli/src/scaling/__tests__/scaling.service.test.ts @@ -5,7 +5,6 @@ import { InstanceSettings } from 'n8n-core'; import { ApplicationError } from 'n8n-workflow'; import Container from 'typedi'; -import type { OrchestrationService } from '@/services/orchestration.service'; import { mockInstance, mockLogger } from '@test/mocking'; import { JOB_TYPE_NAME, QUEUE_NAME } from '../constants'; @@ -47,7 +46,6 @@ describe('ScalingService', () => { }); const instanceSettings = Container.get(InstanceSettings); - const orchestrationService = mock({ isMultiMainSetupEnabled: false }); const jobProcessor = mock(); let scalingService: ScalingService; @@ -82,7 +80,7 @@ describe('ScalingService', () => { globalConfig, mock(), instanceSettings, - orchestrationService, + mock(), mock(), ); diff --git a/packages/cli/src/scaling/scaling.service.ts b/packages/cli/src/scaling/scaling.service.ts index a6ad4cf51f..ebc8e4499c 100644 --- a/packages/cli/src/scaling/scaling.service.ts +++ b/packages/cli/src/scaling/scaling.service.ts @@ -66,9 +66,11 @@ export class ScalingService { this.registerListeners(); - if (this.instanceSettings.isLeader) this.scheduleQueueRecovery(); + const { isLeader, isMultiMain } = this.instanceSettings; - if (this.orchestrationService.isMultiMainSetupEnabled) { + if (isLeader) this.scheduleQueueRecovery(); + + if (isMultiMain) { this.orchestrationService.multiMainSetup .on('leader-takeover', () => this.scheduleQueueRecovery()) .on('leader-stepdown', () => this.stopQueueRecovery()); @@ -127,7 +129,7 @@ export class ScalingService { } private async stopMain() { - if (this.orchestrationService.isSingleMainSetup) { + if (this.instanceSettings.isSingleMain) { await this.queue.pause(true, true); // no more jobs will be picked up this.logger.debug('Queue paused'); } @@ -373,7 +375,7 @@ export class ScalingService { return ( this.globalConfig.endpoints.metrics.includeQueueMetrics && this.instanceSettings.instanceType === 'main' && - !this.orchestrationService.isMultiMainSetupEnabled + this.instanceSettings.isSingleMain ); } diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index 06d2dbe4f8..74a1311444 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -32,7 +32,6 @@ import { setupPushServer, setupPushHandler, Push } from '@/push'; import type { APIRequest } from '@/requests'; import * as ResponseHelper from '@/response-helper'; import type { FrontendService } from '@/services/frontend.service'; -import { OrchestrationService } from '@/services/orchestration.service'; import '@/controllers/active-workflows.controller'; import '@/controllers/annotation-tags.controller.ee'; @@ -79,7 +78,6 @@ export class Server extends AbstractServer { constructor( private readonly loadNodesAndCredentials: LoadNodesAndCredentials, - private readonly orchestrationService: OrchestrationService, private readonly postHogClient: PostHogClient, private readonly eventService: EventService, private readonly instanceSettings: InstanceSettings, @@ -111,7 +109,7 @@ export class Server extends AbstractServer { } private async registerAdditionalControllers() { - if (!inProduction && this.orchestrationService.isMultiMainSetupEnabled) { + if (!inProduction && this.instanceSettings.isMultiMain) { await import('@/controllers/debug.controller'); } diff --git a/packages/cli/src/services/orchestration.service.ts b/packages/cli/src/services/orchestration.service.ts index 19da88e412..adf03d97bf 100644 --- a/packages/cli/src/services/orchestration.service.ts +++ b/packages/cli/src/services/orchestration.service.ts @@ -22,29 +22,6 @@ export class OrchestrationService { isInitialized = false; - private isMultiMainSetupLicensed = false; - - setMultiMainSetupLicensed(newState: boolean) { - this.isMultiMainSetupLicensed = newState; - } - - get isMultiMainSetupEnabled() { - return ( - config.getEnv('executions.mode') === 'queue' && - this.globalConfig.multiMainSetup.enabled && - this.instanceSettings.instanceType === 'main' && - this.isMultiMainSetupLicensed - ); - } - - get isSingleMainSetup() { - return !this.isMultiMainSetupEnabled; - } - - sanityCheck() { - return this.isInitialized && config.get('executions.mode') === 'queue'; - } - async init() { if (this.isInitialized) return; @@ -56,7 +33,7 @@ export class OrchestrationService { this.subscriber = Container.get(Subscriber); } - if (this.isMultiMainSetupEnabled) { + if (this.instanceSettings.isMultiMain) { await this.multiMainSetup.init(); } else { this.instanceSettings.markAsLeader(); @@ -69,7 +46,7 @@ export class OrchestrationService { async shutdown() { if (!this.isInitialized) return; - if (this.isMultiMainSetupEnabled) await this.multiMainSetup.shutdown(); + if (this.instanceSettings.isMultiMain) await this.multiMainSetup.shutdown(); this.publisher.shutdown(); this.subscriber.shutdown(); diff --git a/packages/cli/src/services/pruning/__tests__/pruning.service.test.ts b/packages/cli/src/services/pruning/__tests__/pruning.service.test.ts index 42fe73dd5e..050d1b82d9 100644 --- a/packages/cli/src/services/pruning/__tests__/pruning.service.test.ts +++ b/packages/cli/src/services/pruning/__tests__/pruning.service.test.ts @@ -17,11 +17,10 @@ describe('PruningService', () => { it('should start pruning on main instance that is the leader', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: true }), + mock({ isLeader: true, isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock(), @@ -36,11 +35,10 @@ describe('PruningService', () => { it('should not start pruning on main instance that is a follower', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: false }), + mock({ isLeader: false, isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock(), @@ -55,11 +53,10 @@ describe('PruningService', () => { it('should register leadership events if main on multi-main setup', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: true }), + mock({ isLeader: true, isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock({ on: jest.fn() }), }), mock(), @@ -85,11 +82,10 @@ describe('PruningService', () => { it('should return `true` based on config if leader main', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: true, instanceType: 'main' }), + mock({ isLeader: true, instanceType: 'main', isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock({ pruneData: true }), @@ -101,11 +97,10 @@ describe('PruningService', () => { it('should return `false` based on config if leader main', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: true, instanceType: 'main' }), + mock({ isLeader: true, instanceType: 'main', isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock({ pruneData: false }), @@ -117,11 +112,10 @@ describe('PruningService', () => { it('should return `false` if non-main even if config is enabled', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: false, instanceType: 'worker' }), + mock({ isLeader: false, instanceType: 'worker', isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock({ pruneData: true }), @@ -133,11 +127,15 @@ describe('PruningService', () => { it('should return `false` if follower main even if config is enabled', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: false, isFollower: true, instanceType: 'main' }), + mock({ + isLeader: false, + isFollower: true, + instanceType: 'main', + isMultiMain: true, + }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock({ pruneData: true }), @@ -151,11 +149,10 @@ describe('PruningService', () => { it('should not start pruning if service is disabled', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: true, instanceType: 'main' }), + mock({ isLeader: true, instanceType: 'main', isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock({ pruneData: false }), @@ -179,11 +176,10 @@ describe('PruningService', () => { it('should start pruning if service is enabled and DB is migrated', () => { const pruningService = new PruningService( mockLogger(), - mock({ isLeader: true, instanceType: 'main' }), + mock({ isLeader: true, instanceType: 'main', isMultiMain: true }), mock(), mock(), mock({ - isMultiMainSetupEnabled: true, multiMainSetup: mock(), }), mock({ pruneData: true }), diff --git a/packages/cli/src/services/pruning/pruning.service.ts b/packages/cli/src/services/pruning/pruning.service.ts index 3006d3fbd9..a7bc56725d 100644 --- a/packages/cli/src/services/pruning/pruning.service.ts +++ b/packages/cli/src/services/pruning/pruning.service.ts @@ -51,7 +51,7 @@ export class PruningService { if (this.instanceSettings.isLeader) this.startPruning(); - if (this.orchestrationService.isMultiMainSetupEnabled) { + if (this.instanceSettings.isMultiMain) { this.orchestrationService.multiMainSetup.on('leader-takeover', () => this.startPruning()); this.orchestrationService.multiMainSetup.on('leader-stepdown', () => this.stopPruning()); } diff --git a/packages/cli/src/wait-tracker.ts b/packages/cli/src/wait-tracker.ts index a80ae8f259..f42905ace1 100644 --- a/packages/cli/src/wait-tracker.ts +++ b/packages/cli/src/wait-tracker.ts @@ -40,12 +40,11 @@ export class WaitTracker { * @important Requires `OrchestrationService` to be initialized. */ init() { - const { isLeader } = this.instanceSettings; - const { isMultiMainSetupEnabled } = this.orchestrationService; + const { isLeader, isMultiMain } = this.instanceSettings; if (isLeader) this.startTracking(); - if (isMultiMainSetupEnabled) { + if (isMultiMain) { this.orchestrationService.multiMainSetup .on('leader-takeover', () => this.startTracking()) .on('leader-stepdown', () => this.stopTracking()); diff --git a/packages/cli/src/webhooks/__tests__/test-webhook-registrations.service.test.ts b/packages/cli/src/webhooks/__tests__/test-webhook-registrations.service.test.ts index 0642a5eaa5..b3b4515d68 100644 --- a/packages/cli/src/webhooks/__tests__/test-webhook-registrations.service.test.ts +++ b/packages/cli/src/webhooks/__tests__/test-webhook-registrations.service.test.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; +import type { InstanceSettings } from 'n8n-core'; import type { CacheService } from '@/services/cache/cache.service'; -import type { OrchestrationService } from '@/services/orchestration.service'; import type { TestWebhookRegistration } from '@/webhooks/test-webhook-registrations.service'; import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service'; @@ -9,7 +9,7 @@ describe('TestWebhookRegistrationsService', () => { const cacheService = mock(); const registrations = new TestWebhookRegistrationsService( cacheService, - mock({ isMultiMainSetupEnabled: false }), + mock({ isMultiMain: false }), ); const registration = mock({ diff --git a/packages/cli/src/webhooks/test-webhook-registrations.service.ts b/packages/cli/src/webhooks/test-webhook-registrations.service.ts index 6a3e205f58..e25b3102db 100644 --- a/packages/cli/src/webhooks/test-webhook-registrations.service.ts +++ b/packages/cli/src/webhooks/test-webhook-registrations.service.ts @@ -1,10 +1,10 @@ +import { InstanceSettings } from 'n8n-core'; import type { IWebhookData } from 'n8n-workflow'; import { Service } from 'typedi'; import { TEST_WEBHOOK_TIMEOUT, TEST_WEBHOOK_TIMEOUT_BUFFER } from '@/constants'; import type { IWorkflowDb } from '@/interfaces'; import { CacheService } from '@/services/cache/cache.service'; -import { OrchestrationService } from '@/services/orchestration.service'; export type TestWebhookRegistration = { pushRef?: string; @@ -17,7 +17,7 @@ export type TestWebhookRegistration = { export class TestWebhookRegistrationsService { constructor( private readonly cacheService: CacheService, - private readonly orchestrationService: OrchestrationService, + private readonly instanceSettings: InstanceSettings, ) {} private readonly cacheKey = 'test-webhooks'; @@ -27,7 +27,7 @@ export class TestWebhookRegistrationsService { await this.cacheService.setHash(this.cacheKey, { [hashKey]: registration }); - if (!this.orchestrationService.isMultiMainSetupEnabled) return; + if (this.instanceSettings.isSingleMain) return; /** * Multi-main setup: In a manual webhook execution, the main process that diff --git a/packages/cli/src/webhooks/test-webhooks.ts b/packages/cli/src/webhooks/test-webhooks.ts index 2bdf94b312..8c742946c5 100644 --- a/packages/cli/src/webhooks/test-webhooks.ts +++ b/packages/cli/src/webhooks/test-webhooks.ts @@ -1,5 +1,6 @@ import type express from 'express'; import * as NodeExecuteFunctions from 'n8n-core'; +import { InstanceSettings } from 'n8n-core'; import { WebhookPathTakenError, Workflow } from 'n8n-workflow'; import type { IWebhookData, @@ -17,7 +18,6 @@ import type { IWorkflowDb } from '@/interfaces'; import { NodeTypes } from '@/node-types'; import { Push } from '@/push'; import { Publisher } from '@/scaling/pubsub/publisher.service'; -import { OrchestrationService } from '@/services/orchestration.service'; import { removeTrailingSlash } from '@/utils'; import type { TestWebhookRegistration } from '@/webhooks/test-webhook-registrations.service'; import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registrations.service'; @@ -42,7 +42,7 @@ export class TestWebhooks implements IWebhookManager { private readonly push: Push, private readonly nodeTypes: NodeTypes, private readonly registrations: TestWebhookRegistrationsService, - private readonly orchestrationService: OrchestrationService, + private readonly instanceSettings: InstanceSettings, private readonly publisher: Publisher, ) {} @@ -155,7 +155,7 @@ export class TestWebhooks implements IWebhookManager { * the handler process commands the creator process to clear its test webhooks. */ if ( - this.orchestrationService.isMultiMainSetupEnabled && + this.instanceSettings.isMultiMain && pushRef && !this.push.getBackend().hasPushRef(pushRef) ) { diff --git a/packages/cli/test/integration/shared/utils/index.ts b/packages/cli/test/integration/shared/utils/index.ts index d8225a415d..ba99e1ca07 100644 --- a/packages/cli/test/integration/shared/utils/index.ts +++ b/packages/cli/test/integration/shared/utils/index.ts @@ -1,5 +1,10 @@ import { mock } from 'jest-mock-extended'; -import { BinaryDataService, UnrecognizedNodeTypeError, type DirectoryLoader } from 'n8n-core'; +import { + BinaryDataService, + InstanceSettings, + UnrecognizedNodeTypeError, + type DirectoryLoader, +} from 'n8n-core'; import { Ftp } from 'n8n-nodes-base/credentials/Ftp.credentials'; import { GithubApi } from 'n8n-nodes-base/credentials/GithubApi.credentials'; import { Cron } from 'n8n-nodes-base/nodes/Cron/Cron.node'; @@ -18,7 +23,6 @@ import { SettingsRepository } from '@/databases/repositories/settings.repository import { ExecutionService } from '@/executions/execution.service'; import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials'; import { Push } from '@/push'; -import { OrchestrationService } from '@/services/orchestration.service'; import { mockInstance } from '../../../shared/mocking'; @@ -32,8 +36,8 @@ export { setupTestServer } from './test-server'; * Initialize node types. */ export async function initActiveWorkflowManager() { - mockInstance(OrchestrationService, { - isMultiMainSetupEnabled: false, + mockInstance(InstanceSettings, { + isMultiMain: false, }); mockInstance(Push); diff --git a/packages/core/src/InstanceSettings.ts b/packages/core/src/InstanceSettings.ts index 7d38f21184..c51b0e94bb 100644 --- a/packages/core/src/InstanceSettings.ts +++ b/packages/core/src/InstanceSettings.ts @@ -86,6 +86,29 @@ export class InstanceSettings { */ readonly hostId: string; + private isMultiMainEnabled = false; + + private isMultiMainLicensed = false; + + /** Set whether multi-main mode is enabled. Does not imply licensed status. */ + setMultiMainEnabled(newState: boolean) { + this.isMultiMainEnabled = newState; + } + + setMultiMainLicensed(newState: boolean) { + this.isMultiMainLicensed = newState; + } + + /** Whether this `main` instance is running in multi-main mode. */ + get isMultiMain() { + return this.instanceType === 'main' && this.isMultiMainEnabled && this.isMultiMainLicensed; + } + + /** Whether this `main` instance is running in single-main mode. */ + get isSingleMain() { + return !this.isMultiMain; + } + get isLeader() { return this.instanceRole === 'leader'; }